Merge remote-tracking branch 'origin/oran_beamforming_static_weights' into integration_2026_w13 (!3681)

Configure DigitalBeamTable in OAI and pass down to Aerial L1

With this MR it will be possible to define a Beamtable in OAI and pass it down
to a L1 (Aerial or OAI) which then will either use them locally to apply
static beamforming (Split 8 with USRP) or pass them down to the RU using 7.2
split with Section Extension 1.
This commit is contained in:
Jaroslava Fiedlerova
2026-03-26 13:45:23 +01:00
8 changed files with 358 additions and 14 deletions
+8 -3
View File
@@ -77,6 +77,7 @@
#define CONFIG_STRING_MACRLC_BEAM_DURATION "beam_duration"
#define CONFIG_STRING_MACRLC_BEAMS_PERIOD "beams_per_period"
#define CONFIG_STRING_MACRLC_BEAM_WEIGHTS_LIST "beam_weights"
#define CONFIG_STRING_MACRLC_DBT_FILE "dbt_file"
#define CONFIG_STRING_MACRLC_PUSCH_RSSI_THRESHOLD "pusch_RSSI_Threshold"
#define CONFIG_STRING_MACRLC_PUCCH_RSSI_THRESHOLD "pucch_RSSI_Threshold"
#define CONFIG_STRING_MACRLC_STATS_MAX_UE "stats_max_ue"
@@ -97,6 +98,7 @@
#define HLP_MACRLC_AB "Flag to enable analog beamforming"
#define HLP_MACRLC_BEAM_DURATION "number of consecutive slots for a given set of beams"
#define HLP_MACRLC_BEAMS_PERIOD "set of beams that can be simultaneously allocated in a period"
#define HLP_MACRLC_DBT_FILE "File path to CSV file to read digital beamforming table"
#define HLP_MACRLC_PUSCH_RSSI_THRESHOLD "Limits PUSCH TPC commands based on RSSI to prevent ADC railing. Value range [-1280, 0], unit 0.1 dBm/dBFS"
#define HLP_MACRLC_PUCCH_RSSI_THRESHOLD "Limits PUCCH TPC commands based on RSSI to prevent ADC railing. Value range [-1280, 0], unit 0.1 dBm/dBFS"
#define HLP_MACRLC_STATS_MAX_UE "Maximum number of UEs before disabling periodical output (0 to disable)"
@@ -147,6 +149,7 @@
{CONFIG_STRING_MACRLC_BEAM_DURATION, HLP_MACRLC_BEAM_DURATION, 0, .u8ptr=NULL, .defintval=1, TYPE_UINT8, 0}, \
{CONFIG_STRING_MACRLC_BEAMS_PERIOD, HLP_MACRLC_BEAMS_PERIOD, 0, .u8ptr=NULL, .defintval=1, TYPE_UINT8, 0}, \
{CONFIG_STRING_MACRLC_BEAM_WEIGHTS_LIST, NULL, 0, .iptr=NULL, .defintarrayval=0, TYPE_INTARRAY,0}, \
{CONFIG_STRING_MACRLC_DBT_FILE, HLP_MACRLC_DBT_FILE, 0, .strptr=NULL, .defstrval=NULL, TYPE_STRING, 0}, \
{CONFIG_STRING_MACRLC_PUSCH_RSSI_THRESHOLD, HLP_MACRLC_PUSCH_RSSI_THRESHOLD, \
0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \
{CONFIG_STRING_MACRLC_PUCCH_RSSI_THRESHOLD, HLP_MACRLC_PUCCH_RSSI_THRESHOLD, \
@@ -195,9 +198,10 @@
#define MACRLC_ANALOG_BEAM_DURATION_IDX 37
#define MACRLC_ANALOG_BEAMS_PERIOD_IDX 38
#define MACRLC_BEAMWEIGHTS_IDX 39
#define MACRLC_PUSCH_RSSI_THRES_IDX 40
#define MACRLC_PUCCH_RSSI_THRES_IDX 41
#define MACRLC_STATS_MAX_UE_IDX 42
#define MACRLC_DBT_FILE_IDX 40
#define MACRLC_PUSCH_RSSI_THRES_IDX 41
#define MACRLC_PUCCH_RSSI_THRES_IDX 42
#define MACRLC_STATS_MAX_UE_IDX 43
#define MACRLCPARAMS_CHECK { \
{ .s5 = { NULL } }, \
@@ -243,6 +247,7 @@
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s2 = { config_check_intrange, {-1280, 0}} }, /* PUSCH RSSI threshold range */ \
{ .s2 = { config_check_intrange, {-1280, 0}} }, /* PUCCH RSSI threshold range */ \
{ .s5 = { NULL } }, \
+178
View File
@@ -28,6 +28,9 @@
*/
#include "gnb_config.h"
#include <ctype.h>
#include <complex.h>
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
@@ -1366,6 +1369,166 @@ static void get_bwp_config(nr_mac_config_t *configuration, const NR_ServingCellC
}
}
static bool parse_complex_token(const char *tok, double complex *out)
{
double re = 0.0;
double im = 0.0;
if (sscanf(tok, "%lf%lfi", &re, &im) == 2 || sscanf(tok, "%lf%lfj", &re, &im) == 2) {
*out = re + I * im;
return true;
}
if (sscanf(tok, "%lfi", &im) == 1 || sscanf(tok, "%lfj", &im) == 1) {
*out = I * im;
return true;
}
if (sscanf(tok, "%lf", &re) == 1) {
*out = re;
return true;
}
return false;
}
static double complex **read_dbt_from_csv(const char *filename,
int *num_beams,
int *num_weights_per_beam,
uint16_t **beam_ids)
{
FILE *fp = fopen(filename, "r");
AssertFatal(fp != NULL, "Failed to open DBT CSV file '%s'\n", filename);
char line[16384];
AssertFatal(fgets(line, sizeof(line), fp) != NULL, "Failed to read DBT CSV header from '%s'\n", filename);
int weights = 1;
for (char *p = line; *p; ++p)
if (*p == ',')
weights++;
weights -= 1; // first column is beam ID
AssertFatal(weights > 0, "DBT CSV '%s' has no weight columns\n", filename);
int beams = 0;
while (fgets(line, sizeof(line), fp))
beams++;
AssertFatal(beams > 0, "No DBT beam rows found in CSV file '%s'\n", filename);
rewind(fp);
AssertFatal(fgets(line, sizeof(line), fp) != NULL, "Failed to reread DBT CSV header from '%s'\n", filename);
double complex **table = calloc_or_fail(beams, sizeof(*table));
uint16_t *ids = calloc_or_fail(beams, sizeof(*ids));
int b = 0;
while (fgets(line, sizeof(line), fp)) {
line[strcspn(line, "\r\n")] = '\0';
char *saveptr = NULL;
char *tok = strtok_r(line, ",", &saveptr);
AssertFatal(tok != NULL, "Malformed DBT row %d in '%s'\n", b + 1, filename);
errno = 0;
char *endptr = NULL;
long beam_id = strtol(tok, &endptr, 10);
AssertFatal(endptr != tok && errno != ERANGE && *endptr == '\0' && beam_id >= 0 && beam_id <= UINT16_MAX,
"Invalid DBT beam id '%s' in file '%s', beam row %d\n",
tok,
filename,
b + 1);
ids[b] = (uint16_t)beam_id;
table[b] = calloc_or_fail(weights, sizeof(*table[b]));
for (int w = 0; w < weights; ++w) {
tok = strtok_r(NULL, ",", &saveptr);
AssertFatal(tok != NULL, "Missing DBT weight in file '%s', beam row %d column %d\n", filename, b + 1, w + 1);
AssertFatal(parse_complex_token(tok, &table[b][w]),
"Invalid DBT complex weight '%s' in file '%s', beam row %d column %d\n",
tok,
filename,
b + 1,
w + 1);
}
AssertFatal(strtok_r(NULL, ",", &saveptr) == NULL,
"Too many DBT columns in file '%s', beam row %d\n",
filename,
b + 1);
b++;
}
AssertFatal(b == beams, "Parsed %d DBT rows but counted %d in file '%s'\n", b, beams, filename);
fclose(fp);
*num_beams = beams;
*num_weights_per_beam = weights;
*beam_ids = ids;
return table;
}
#define DBT_PARAMS_DESC { \
{"", "beam", 0, .strlistptr=NULL, .defstrlistval=NULL, TYPE_STRINGLIST, 0}, \
}
static double complex **read_dbt_from_config(const char *prefix,
int *num_beams,
int *num_weights_per_beam,
uint16_t **beam_ids)
{
paramdef_t dbt_params[] = DBT_PARAMS_DESC;
paramlist_def_t dbt_list = {"dbt", NULL, 0};
int ret = config_getlist(config_get_if(), &dbt_list, dbt_params, sizeofArray(dbt_params), prefix);
if (ret <= 0) {
*num_beams = 0;
*num_weights_per_beam = 0;
*beam_ids = NULL;
return NULL;
}
const int beams = ret;
int weights = 0;
double complex **table = calloc_or_fail(beams, sizeof(*table));
uint16_t *ids = calloc_or_fail(beams, sizeof(*ids));
for (int b = 0; b < beams; ++b) {
paramdef_t *row = &dbt_list.paramarray[b][0];
AssertFatal(row->strlistptr != NULL, "Invalid dbt row %d in section '%s': missing string list\n", b, prefix);
AssertFatal(row->numelt >= 2,
"Invalid dbt row %d in section '%s': expected beam_id + >=1 weights, got %d elements\n",
b,
prefix,
row->numelt);
const int row_weights = row->numelt - 1;
if (weights == 0)
weights = row_weights;
AssertFatal(weights == row_weights,
"Inconsistent dbt row width in section '%s': row %d has %d weights, expected %d\n",
prefix,
b,
row_weights,
weights);
errno = 0;
char *endptr = NULL;
long beam_id = strtol(row->strlistptr[0], &endptr, 10);
AssertFatal(endptr != row->strlistptr[0] && errno != ERANGE && *endptr == '\0' && beam_id >= 0 && beam_id <= UINT16_MAX,
"Invalid dbt beam id '%s' in section '%s', row %d\n",
row->strlistptr[0],
prefix,
b);
ids[b] = (uint16_t)beam_id;
table[b] = calloc_or_fail(weights, sizeof(*table[b]));
for (int w = 0; w < weights; ++w)
AssertFatal(parse_complex_token(row->strlistptr[w + 1], &table[b][w]),
"Invalid dbt complex weight '%s' in section '%s', row %d col %d\n",
row->strlistptr[w + 1],
prefix,
b,
w + 1);
}
*num_beams = beams;
*num_weights_per_beam = weights;
*beam_ids = ids;
return table;
}
void RCconfig_nr_macrlc(configmodule_interface_t *cfg)
{
int j = 0;
@@ -1634,6 +1797,21 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg)
for (int b = 0; b < n; b++)
config.bw_list[b] = b;
}
config.bt.num_beams = 0;
config.bt.num_weights_per_beam = 0;
config.bt.beam_ids = NULL;
config.bt.beam_weights = NULL;
char **fptr = MacRLC_ParamList.paramarray[j][MACRLC_DBT_FILE_IDX].strptr;
if (fptr && *fptr && **fptr != '\0') {
LOG_I(GNB_APP, "loading DBT table from file %s\n", *fptr);
config.bt.beam_weights =
read_dbt_from_csv(*fptr, &config.bt.num_beams, &config.bt.num_weights_per_beam, &config.bt.beam_ids);
} else {
char prefix[MAX_OPTNAME_SIZE * 2 + 8];
snprintf(prefix, sizeof(prefix), CONFIG_STRING_MACRLC_LIST ".[%d]", j);
config.bt.beam_weights =
read_dbt_from_config(prefix, &config.bt.num_beams, &config.bt.num_weights_per_beam, &config.bt.beam_ids);
}
// triggers also PHY initialization in case we have L1 via FAPI
nr_mac_config_scc(RC.nrmac[j], scc, &config);
} // for (j=0;j<RC.nb_nr_macrlc_inst;j++)