NR UE: introduce support to define multiple cells

This feature shall allow in future to simulate an arbitratry number of UEs connected to an other arbitrary number of cells.
Therefore each RF-frontend (also called RU) needs to be configured for one specific cell.
In case of handover of a UE from one cell to another cell, it needs to switch RUs accordingly (TODO).

There has to be a 1-to-1 mapping between RUs and cells.
Each UE will be assigned to one of the defined cells.
Initially all UEs are assigned to cell 0.
This commit is contained in:
Thomas Schlichter
2026-01-27 11:10:53 +01:00
parent bcccfd90b8
commit 0b06d55f5f
7 changed files with 247 additions and 33 deletions
@@ -29,6 +29,18 @@ RUs = (
}
);
cells = (
{
ru_id = 0;
band = 254;
rf_freq = 2488400000L;
rf_offset = -873500000L;
numerology = 0;
N_RB_DL = 25;
ssb_start = 60;
}
);
#/* configuration for channel modelisation */
#/* To be included in main config file when */
#/* channel modelisation is used (rfsimulator with chanmod options enabled) */
+131 -6
View File
@@ -68,12 +68,116 @@
}
// clang-format on
/* NR UE cell configuration section name */
#define CONFIG_STRING_NRUE_CELL_LIST "cells"
#define NRUE_CELL_RU_ID "ru_id"
#define NRUE_CELL_BAND "band"
#define NRUE_CELL_RF_FREQUENCY "rf_freq"
#define NRUE_CELL_RF_FREQ_OFFSET "rf_offset"
#define NRUE_CELL_NUMEROLOGY "numerology"
#define NRUE_CELL_N_RB_DL "N_RB_DL"
#define NRUE_CELL_SSB_START "ssb_start"
/*------------------------------------------------------------------------------------------------------------------------------------------------*/
/* NR UE cell configuration parameters */
/* optname helpstr paramflags XXXptr defXXXval type numelt chkPptr */
/*------------------------------------------------------------------------------------------------------------------------------------------------*/
// clang-format off
#define NRUE_CELL_PARAMS_DESC { \
{NRUE_CELL_RU_ID, NULL, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, NULL }, \
{NRUE_CELL_BAND, CONFIG_HLP_BAND, 0, .iptr=NULL, .defintval=78, TYPE_INT, 0, NULL }, \
{NRUE_CELL_RF_FREQUENCY, CONFIG_HLP_DLF, 0, .u64ptr=NULL, .defint64val=0, TYPE_UINT64, 0, NULL }, \
{NRUE_CELL_RF_FREQ_OFFSET, CONFIG_HLP_ULF, 0, .i64ptr=NULL, .defint64val=0, TYPE_INT64, 0, NULL }, \
{NRUE_CELL_NUMEROLOGY, CONFIG_HLP_NUMEROLOGY, 0, .iptr=NULL, .defintval=1, TYPE_INT, 0, NULL }, \
{NRUE_CELL_N_RB_DL, CONFIG_HLP_PRB_SA, 0, .iptr=NULL, .defintval=106, TYPE_INT, 0, NULL }, \
{NRUE_CELL_SSB_START, CONFIG_HLP_SSC, 0, .iptr=NULL, .defintval=516, TYPE_INT, 0, NULL }, \
}
// clang-format on
static int nrue_cell_count;
static nrUE_cell_params_t *nrue_cells;
static NR_DL_FRAME_PARMS *nrue_cell_fp;
static int nrue_ru_count;
static nrUE_RU_params_t *nrue_rus;
openair0_config_t openair0_cfg[MAX_CARDS];
openair0_device_t openair0_dev[MAX_CARDS];
int nrue_get_cell_count(void)
{
return nrue_cell_count;
}
const nrUE_cell_params_t *nrue_get_cell(int cell_id)
{
AssertFatal(cell_id >= 0 && cell_id < nrue_cell_count, "Invalid cell ID %d! Cell count = %d\n", cell_id, nrue_cell_count);
return &nrue_cells[cell_id];
}
NR_DL_FRAME_PARMS *nrue_get_cell_fp(int cell_id)
{
AssertFatal(cell_id >= 0 && cell_id < nrue_cell_count, "Invalid cell ID %d! Cell count = %d\n", cell_id, nrue_cell_count);
return &nrue_cell_fp[cell_id];
}
void nrue_set_cell(int cell_id, const nrUE_cell_params_t *cell)
{
AssertFatal(cell_id >= 0 && cell_id < nrue_cell_count, "Invalid cell ID %d! Cell count = %d\n", cell_id, nrue_cell_count);
nrue_cells[cell_id] = *cell;
}
void nrue_set_cell_params(configmodule_interface_t *cfg)
{
paramdef_t cellParams[] = NRUE_CELL_PARAMS_DESC;
paramlist_def_t cellParamList = {CONFIG_STRING_NRUE_CELL_LIST, NULL, 0};
config_getlist(cfg, &cellParamList, cellParams, sizeofArray(cellParams), NULL);
if (cellParamList.numelt <= 0) {
nrue_cell_count = 1;
nrue_cell_fp = calloc_or_fail(nrue_cell_count, sizeof(NR_DL_FRAME_PARMS));
nrue_cells = calloc_or_fail(nrue_cell_count, sizeof(nrUE_cell_params_t));
nrue_cells[0] = (nrUE_cell_params_t){.ru_id = 0,
.band = get_softmodem_params()->band,
.rf_frequency = downlink_frequency[0][0],
.rf_freq_offset = uplink_frequency_offset[0][0],
.numerology = get_softmodem_params()->numerology,
.N_RB_DL = get_nrUE_params()->N_RB_DL,
.ssb_start = get_nrUE_params()->ssb_start_subcarrier,
.used_by_ue = -1};
return;
}
nrue_cell_count = cellParamList.numelt;
nrue_cell_fp = calloc_or_fail(nrue_cell_count, sizeof(NR_DL_FRAME_PARMS));
nrue_cells = calloc_or_fail(nrue_cell_count, sizeof(nrUE_cell_params_t));
LOG_W(NR_PHY, "Ignoring command line cell parameters, using the following instead:\n");
for (int cell_id = 0; cell_id < nrue_cell_count; cell_id++) {
nrue_cells[cell_id].ru_id = *(gpd(cellParamList.paramarray[cell_id], sizeofArray(cellParams), NRUE_CELL_RU_ID)->iptr);
nrue_cells[cell_id].band = *(gpd(cellParamList.paramarray[cell_id], sizeofArray(cellParams), NRUE_CELL_BAND)->iptr);
nrue_cells[cell_id].rf_frequency = *(gpd(cellParamList.paramarray[cell_id], sizeofArray(cellParams), NRUE_CELL_RF_FREQUENCY)->u64ptr);
nrue_cells[cell_id].rf_freq_offset = *(gpd(cellParamList.paramarray[cell_id], sizeofArray(cellParams), NRUE_CELL_RF_FREQ_OFFSET)->i64ptr);
nrue_cells[cell_id].numerology = *(gpd(cellParamList.paramarray[cell_id], sizeofArray(cellParams), NRUE_CELL_NUMEROLOGY)->iptr);
nrue_cells[cell_id].N_RB_DL = *(gpd(cellParamList.paramarray[cell_id], sizeofArray(cellParams), NRUE_CELL_N_RB_DL)->iptr);
nrue_cells[cell_id].ssb_start = *(gpd(cellParamList.paramarray[cell_id], sizeofArray(cellParams), NRUE_CELL_SSB_START)->iptr);
nrue_cells[cell_id].used_by_ue = -1;
LOG_I(NR_PHY,
"cell %d: ru_id %d, band %d, DL freq %lu, UL freq_offset %ld, numerology %d, N_RB_DL %d, ssb_start %d\n",
cell_id,
nrue_cells[cell_id].ru_id,
nrue_cells[cell_id].band,
nrue_cells[cell_id].rf_frequency,
nrue_cells[cell_id].rf_freq_offset,
nrue_cells[cell_id].numerology,
nrue_cells[cell_id].N_RB_DL,
nrue_cells[cell_id].ssb_start);
}
}
int nrue_get_ru_count(void)
{
return nrue_ru_count;
@@ -85,6 +189,13 @@ const nrUE_RU_params_t *nrue_get_ru(int ru_id)
return &nrue_rus[ru_id];
}
void nrue_set_ru_cell_id(int ru_id, int cell_id)
{
AssertFatal(ru_id >= 0 && ru_id < nrue_ru_count, "Invalid RU ID %d! RU count = %d\n", ru_id, nrue_ru_count);
AssertFatal(cell_id >= 0 && cell_id < nrue_cell_count, "Invalid cell ID %d! Cell count = %d\n", cell_id, nrue_cell_count);
nrue_rus[ru_id].used_by_cell = cell_id;
}
void nrue_set_ru_params(configmodule_interface_t *cfg)
{
paramdef_t RUParams[] = NRUE_RU_PARAMS_DESC;
@@ -106,7 +217,8 @@ void nrue_set_ru_params(configmodule_interface_t *cfg)
.time_source = get_softmodem_params()->timing_source,
.tune_offset = get_softmodem_params()->tune_offset,
.if_frequency = get_nrUE_params()->if_freq,
.if_freq_offset = get_nrUE_params()->if_freq_off};
.if_freq_offset = get_nrUE_params()->if_freq_off,
.used_by_cell = -1};
return;
}
@@ -124,6 +236,7 @@ void nrue_set_ru_params(configmodule_interface_t *cfg)
nrue_rus[ru_id].tune_offset = *(gpd(RUParamList.paramarray[ru_id], sizeofArray(RUParams), NRUE_RU_TUNE_OFFSET)->dblptr);
nrue_rus[ru_id].if_frequency = *(gpd(RUParamList.paramarray[ru_id], sizeofArray(RUParams), NRUE_RU_IF_FREQUENCY)->u64ptr);
nrue_rus[ru_id].if_freq_offset = *(gpd(RUParamList.paramarray[ru_id], sizeofArray(RUParams), NRUE_RU_IF_FREQ_OFFSET)->iptr);
nrue_rus[ru_id].used_by_cell = -1;
nrue_rus[ru_id].sdr_addrs = strdup(*(gpd(RUParamList.paramarray[ru_id], sizeofArray(RUParams), NRUE_RU_SDR_ADDRS)->strptr));
nrue_rus[ru_id].tx_subdev = strdup(*(gpd(RUParamList.paramarray[ru_id], sizeofArray(RUParams), NRUE_RU_TX_SUBDEV)->strptr));
@@ -157,7 +270,7 @@ void nrue_set_ru_params(configmodule_interface_t *cfg)
}
}
void nrue_init_openair0(const PHY_VARS_NR_UE *ue)
void nrue_init_openair0(void)
{
int freq_off = 0;
bool is_sidelink = (get_softmodem_params()->sl_mode) ? true : false;
@@ -170,11 +283,23 @@ void nrue_init_openair0(const PHY_VARS_NR_UE *ue)
nrue_ru_count,
MAX_CARDS);
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
if (is_sidelink)
frame_parms = &ue->SL_UE_PHY_PARAMS.sl_frame_params;
for (int ru_id = 0; ru_id < nrue_ru_count; ru_id++) {
int cell_id = nrue_rus[ru_id].used_by_cell;
if (cell_id < 0) {
LOG_W(PHY, "Skipping initialization of RU %d because it is not used by any cell!\n", ru_id);
continue;
}
NR_DL_FRAME_PARMS *frame_parms = &nrue_cell_fp[cell_id];
if (is_sidelink) {
int UE_id = nrue_cells[cell_id].used_by_ue;
if (UE_id < 0) {
LOG_W(PHY, "Skipping initialization of RU %d because it is not used by any UE!\n", ru_id);
continue;
}
extern PHY_VARS_NR_UE ***PHY_vars_UE_g;
frame_parms = &PHY_vars_UE_g[UE_id][0]->SL_UE_PHY_PARAMS.sl_frame_params;
}
openair0_config_t *cfg = &openair0_cfg[ru_id];
cfg->configFilename = NULL;
cfg->sample_rate = frame_parms->samples_per_subframe * 1e3;
+8 -1
View File
@@ -25,11 +25,18 @@
#include "PHY/defs_nr_UE.h"
#include "radio/COMMON/common_lib.h"
int nrue_get_cell_count(void);
const nrUE_cell_params_t *nrue_get_cell(int cell_id);
NR_DL_FRAME_PARMS *nrue_get_cell_fp(int cell_id);
void nrue_set_cell(int cell_id, const nrUE_cell_params_t *cell);
void nrue_set_cell_params(configmodule_interface_t *cfg);
int nrue_get_ru_count(void);
const nrUE_RU_params_t *nrue_get_ru(int ru_id);
void nrue_set_ru_cell_id(int ru_id, int cell_id);
void nrue_set_ru_params(configmodule_interface_t *cfg);
void nrue_init_openair0(const PHY_VARS_NR_UE *ue);
void nrue_init_openair0(void);
void nrue_ru_start(void);
void nrue_ru_end(void);
+74 -23
View File
@@ -175,9 +175,8 @@ static void get_options(configmodule_interface_t *cfg)
}
// set PHY vars from command line
static void set_options(int CC_id, PHY_VARS_NR_UE *UE, int ru_id)
static void set_UE_options(int CC_id, PHY_VARS_NR_UE *UE, int ru_id)
{
NR_DL_FRAME_PARMS *fp = &UE->frame_parms;
const nrUE_RU_params_t *RU = nrue_get_ru(ru_id);
// Set UE variables
@@ -200,6 +199,12 @@ static void set_options(int CC_id, PHY_VARS_NR_UE *UE, int ru_id)
LOG_I(PHY,"Set UE_fo_compensation %d, UE_scan_carrier %d, UE_no_timing_correction %d \n, chest-freq %d, chest-time %d\n",
UE->UE_fo_compensation, UE->UE_scan_carrier, UE->no_timing_correction, UE->chest_freq, UE->chest_time);
}
static void set_fp_options(int cell_id, int ru_id)
{
NR_DL_FRAME_PARMS *fp = nrue_get_cell_fp(cell_id);
const nrUE_RU_params_t *RU = nrue_get_ru(ru_id);
// Set FP variables
fp->nb_antennas_rx = RU->nb_rx;
@@ -307,7 +312,14 @@ int main(int argc, char **argv)
nr_pdcp_layer_init();
nas_init_nrue(NB_UE_INST);
init_NR_UE(NB_UE_INST, get_nrUE_params()->uecap_file, get_nrUE_params()->reconfig_file, get_nrUE_params()->rbconfig_file, get_softmodem_params()->numerology);
nrue_set_ru_params(uniqCfg);
nrue_set_cell_params(uniqCfg);
init_NR_UE(NB_UE_INST,
get_nrUE_params()->uecap_file,
get_nrUE_params()->reconfig_file,
get_nrUE_params()->rbconfig_file,
nrue_get_cell(0)->numerology);
// start time manager with some reasonable default for the running mode
// (may be overwritten in configuration file or command line)
@@ -324,29 +336,52 @@ int main(int argc, char **argv)
IS_SOFTMODEM_RFSIM ? TIME_SOURCE_IQ_SAMPLES
: TIME_SOURCE_REALTIME);
nrue_set_ru_params(uniqCfg);
// initialize per-cell frame parameters
for (int cell_id = 0; cell_id < nrue_get_cell_count(); cell_id++) {
int ru_id = nrue_get_cell(cell_id)->ru_id;
AssertFatal(ru_id >= 0 && ru_id < nrue_get_ru_count(),
"Invalid ru_id (%d) for cell %d. Should be >= 0 and < %d\n",
ru_id,
cell_id,
nrue_get_ru_count());
AssertFatal(nrue_get_ru(ru_id)->used_by_cell == -1,
"RU %d is already used by cell %d and therefore cannot also be used by cell %d\n",
ru_id,
nrue_get_ru(ru_id)->used_by_cell,
cell_id);
nrue_set_ru_cell_id(ru_id, cell_id);
int ru_id = 0; // initialize all UEs to RU 0 for now
set_fp_options(cell_id, ru_id);
if (IS_SA_MODE(get_softmodem_params()) || get_softmodem_params()->sl_mode)
nr_init_frame_parms_ue_sa(nrue_get_cell_fp(cell_id), nrue_get_cell(cell_id));
}
int cell_id = 0;
for (int inst = 0; inst < NB_UE_INST; inst++) {
NR_UE_MAC_INST_t *mac = get_mac_inst(inst);
for (int CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
PHY_VARS_NR_UE *UE_CC = PHY_vars_UE_g[inst][CC_id];
set_options(CC_id, UE_CC, ru_id);
NR_UE_MAC_INST_t *mac = get_mac_inst(inst);
AssertFatal(cell_id >= 0 && cell_id < nrue_get_cell_count(),
"There are not enough cell definitions for all UEs! NB_UE_INST = %d, MAX_NUM_CCs = %d, nrue_cell_count = %d\n",
NB_UE_INST,
MAX_NUM_CCs,
nrue_get_cell_count());
nrUE_cell_params_t cell = *nrue_get_cell(cell_id);
AssertFatal(cell.used_by_ue == -1,
"Cell %d is already used by UE %d and cannot also be used by UE %d as cells map 1:1 to RUs and RU sharing is not implemented\n",
cell_id,
cell.used_by_ue,
inst);
cell.used_by_ue = inst;
set_UE_options(CC_id, UE_CC, cell.ru_id);
init_nr_ue_phy_cpu_stats(&UE_CC->phy_cpu_stats);
// set frame config to initial values from command line and assume that the SSB is centered on the grid
if (IS_SA_MODE(get_softmodem_params()) || get_softmodem_params()->sl_mode) {
uint16_t nr_band = get_softmodem_params()->band;
mac->nr_band = nr_band;
mac->ssb_start_subcarrier = UE_CC->frame_parms.ssb_start_subcarrier;
mac->dl_frequency = downlink_frequency[CC_id][0];
nr_init_frame_parms_ue_sa(&UE_CC->frame_parms,
downlink_frequency[CC_id][0],
uplink_frequency_offset[CC_id][0],
get_softmodem_params()->numerology,
nrUE_params.N_RB_DL,
nrUE_params.ssb_start_subcarrier,
nr_band);
} else {
NR_DL_FRAME_PARMS *fp = nrue_get_cell_fp(cell_id);
if (!IS_SA_MODE(get_softmodem_params()) && !get_softmodem_params()->sl_mode) {
do {
notifiedFIFO_elt_t *elt = pollNotifiedFIFO(&mac->input_nf);
if (!elt) {
@@ -356,8 +391,21 @@ int main(int argc, char **argv)
delNotifiedFIFO_elt(elt);
} while (true);
fapi_nr_config_request_t *nrUE_config = &UE_CC->nrUE_config;
nr_init_frame_parms_ue(&UE_CC->frame_parms, nrUE_config, mac->nr_band);
nr_init_frame_parms_ue(fp, nrUE_config, mac->nr_band);
cell.band = fp->nr_band;
cell.rf_frequency = fp->dl_CarrierFreq;
cell.rf_freq_offset = fp->ul_CarrierFreq - fp->dl_CarrierFreq;
cell.numerology = fp->numerology_index;
cell.N_RB_DL = fp->N_RB_DL;
cell.ssb_start = fp->ssb_start_subcarrier;
}
nrue_set_cell(cell_id, &cell);
UE_CC->frame_parms = *fp;
mac->nr_band = cell.band;
mac->ssb_start_subcarrier = cell.ssb_start;
mac->dl_frequency = cell.rf_frequency;
UE_CC->sl_mode = get_softmodem_params()->sl_mode;
init_actor(&UE_CC->sync_actor, "SYNC_", -1);
@@ -389,10 +437,13 @@ int main(int argc, char **argv)
get_nrUE_params()->ofdm_offset_divisor);
sl_ue_phy_init(UE_CC);
}
nrue_init_openair0(UE_CC);
cell_id++; // initially connect each UE and carrier to its own cell
}
}
nrue_init_openair0();
lock_memory_to_ram();
if (IS_SOFTMODEM_DOSCOPE) {
+9 -2
View File
@@ -525,9 +525,16 @@ int nr_init_frame_parms_ue(NR_DL_FRAME_PARMS *fp,
return 0;
}
void nr_init_frame_parms_ue_sa(NR_DL_FRAME_PARMS *frame_parms, uint64_t downlink_frequency, int32_t delta_duplex, uint8_t mu, int N_RB_DL, int ssb_start_subcarrier, uint16_t nr_band)
void nr_init_frame_parms_ue_sa(NR_DL_FRAME_PARMS *frame_parms, const nrUE_cell_params_t *cell)
{
LOG_I(PHY,"SA init parameters. DL freq %lu UL offset %d SSB numerology %d N_RB_DL %d\n",
const uint64_t downlink_frequency = cell->rf_frequency;
const int64_t delta_duplex = cell->rf_freq_offset;
const uint8_t mu = cell->numerology;
const int N_RB_DL = cell->N_RB_DL;
const int ssb_start_subcarrier = cell->ssb_start;
const uint16_t nr_band = cell->band;
LOG_I(PHY,"SA init parameters. DL freq %lu UL offset %ld SSB numerology %d N_RB_DL %d\n",
downlink_frequency,
delta_duplex,
mu,
+1 -1
View File
@@ -28,7 +28,7 @@
int nr_get_ssb_start_symbol(const NR_DL_FRAME_PARMS *fp, uint8_t i_ssb);
void nr_init_frame_parms(nfapi_nr_config_request_scf_t *config, NR_DL_FRAME_PARMS *frame_parms);
int nr_init_frame_parms_ue(NR_DL_FRAME_PARMS *frame_parms, fapi_nr_config_request_t *config, uint16_t nr_band);
void nr_init_frame_parms_ue_sa(NR_DL_FRAME_PARMS *frame_parms, uint64_t downlink_frequency, int32_t delta_duplex, uint8_t mu, int N_RB_DL, int ssb_start_subcarrier, uint16_t nr_band);
void nr_init_frame_parms_ue_sa(NR_DL_FRAME_PARMS *frame_parms, const nrUE_cell_params_t *cell);
int nr_init_frame_parms_ue_sl(NR_DL_FRAME_PARMS *fp,
sl_nr_phy_config_request_t *config,
int threequarter_fs,
+12
View File
@@ -356,8 +356,20 @@ typedef struct {
double tune_offset;
uint64_t if_frequency;
int if_freq_offset;
int used_by_cell;
} nrUE_RU_params_t;
typedef struct {
int ru_id;
int band;
uint64_t rf_frequency;
int64_t rf_freq_offset;
int numerology;
int N_RB_DL;
int ssb_start;
int used_by_ue;
} nrUE_cell_params_t;
/// Top-level PHY Data Structure for UE
typedef struct PHY_VARS_NR_UE_s {
/// \brief Module ID indicator for this instance