Merge remote-tracking branch 'karimboutiba/xran_fdd' into integration_2026_w28

xran: fixes for FDD operation (#270)

Two fixes in the xran fronthaul found while bringing up an FDD cell
(Band n1, 10 MHz).  Verified on a Band n1 FDD 10 MHz cell using Keysight
RUsim/UEsim, End-to-end UE attach and traffic work.

- Derive TTI from the configured numerology instead of a hardcoded 20
  slots per frame: xran_fh_tx_send_slot() assumed 30 kHz SCS. Compute
  slots_per_frame as 10 << mu from the fronthaul config (mu_number[0]
  for K release, frame_conf.nNumerology for F release), so the TTI is
  correct for any numerology. With the original code, PRACH was not
  working properly.
- Fix DL transmission in FDD mode: the TDD DL/guard slot check skipped
  every DL slot in FDD, where no TDD pattern exists and all slots carry
  DL. The check is now only applied when the frame duplex type is not
  FDD. Without this, no DL traffic was sent.

Reviewed-by: Teodora Vladić <teodora.vladic@openairinterface.org>
Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
This commit is contained in:
Robert Schmidt
2026-07-09 17:10:29 +02:00
2 changed files with 14 additions and 17 deletions

View File

@@ -1556,7 +1556,10 @@ Edit the sample OAI gNB configuration file and check following parameters:
* `RUs` section * `RUs` section
* Set an isolated core for RU thread `ru_thread_core`, in our environment we are using CPU 6 * Set an isolated core for RU thread `ru_thread_core`, in our environment we are using CPU 6
* If testing with a numerology different than 1 (e.g., FDD with numerology 0),
set `nr_scs_for_raster` to the used numerology, and adapt `sl_ahead`: it must be
strictly less than the number of slots in a frame (e.g., 5 for numerology 0).
* `fhi_72` (FrontHaul Interface) section: this config follows the structure * `fhi_72` (FrontHaul Interface) section: this config follows the structure
that is employed by the xRAN library (`xran_fh_init` and `xran_fh_config` that is employed by the xRAN library (`xran_fh_init` and `xran_fh_config`
structs in the code): structs in the code):

View File

@@ -518,9 +518,6 @@ int xran_fh_rx_prach_read_slot(PHY_VARS_gNB *gNB, ru_info_t *ru, int *frame, int
* @param sym_idx the current symbol index */ * @param sym_idx the current symbol index */
static bool is_tdd_ul_symbol(const struct xran_frame_config *frame_conf, int slot, int sym_idx) static bool is_tdd_ul_symbol(const struct xran_frame_config *frame_conf, int slot, int sym_idx)
{ {
/* in FDD, every symbol is also UL */
if (frame_conf->nFrameDuplexType == XRAN_FDD)
return true;
int tdd_period = frame_conf->nTddPeriod; int tdd_period = frame_conf->nTddPeriod;
int slot_in_period = slot % tdd_period; int slot_in_period = slot % tdd_period;
/* check if symbol is UL */ /* check if symbol is UL */
@@ -534,9 +531,6 @@ static bool is_tdd_ul_symbol(const struct xran_frame_config *frame_conf, int slo
* @param sym_idx the current symbol index */ * @param sym_idx the current symbol index */
static bool is_tdd_dl_symbol(const struct xran_frame_config *frame_conf, int slot, int sym_idx) static bool is_tdd_dl_symbol(const struct xran_frame_config *frame_conf, int slot, int sym_idx)
{ {
/* in FDD, every symbol is also UL */
if (frame_conf->nFrameDuplexType == XRAN_FDD)
return true;
int tdd_period = frame_conf->nTddPeriod; int tdd_period = frame_conf->nTddPeriod;
int slot_in_period = slot % tdd_period; int slot_in_period = slot % tdd_period;
/* check if symbol is UL */ /* check if symbol is UL */
@@ -633,13 +627,13 @@ int xran_fh_rx_read_slot(ru_info_t *ru, int *frame, int *slot)
start_ptr = rx_data + (slot_size * slot_offset_rxdata); start_ptr = rx_data + (slot_size * slot_offset_rxdata);
const struct xran_frame_config *frame_conf = &get_xran_fh_config(ant_id / nb_rx_per_ru)->frame_conf; const struct xran_frame_config *frame_conf = &get_xran_fh_config(ant_id / nb_rx_per_ru)->frame_conf;
// skip processing this slot is TX (no RX in this slot) // skip processing this slot is TX (no RX in this slot)
if (!is_tdd_ul_guard_slot(frame_conf, *slot)) if (frame_conf->nFrameDuplexType != XRAN_FDD && !is_tdd_ul_guard_slot(frame_conf, *slot))
continue; continue;
// This loop would better be more inner to avoid confusion and maybe also errors. // This loop would better be more inner to avoid confusion and maybe also errors.
for (int32_t sym_idx = 0; sym_idx < XRAN_NUM_OF_SYMBOL_PER_SLOT; sym_idx++) { for (int32_t sym_idx = 0; sym_idx < XRAN_NUM_OF_SYMBOL_PER_SLOT; sym_idx++) {
/* the callback is for mixed and UL slots. In mixed, we have to /* the callback is for mixed and UL slots. In mixed, we have to
* skip DL and guard symbols. */ * skip DL and guard symbols. */
if (!is_tdd_ul_symbol(frame_conf, *slot, sym_idx)) if (frame_conf->nFrameDuplexType != XRAN_FDD && !is_tdd_ul_symbol(frame_conf, *slot, sym_idx))
continue; continue;
oran_buf_list_t *bufs = get_xran_buffers(ant_id / nb_rx_per_ru); oran_buf_list_t *bufs = get_xran_buffers(ant_id / nb_rx_per_ru);
@@ -766,9 +760,6 @@ int xran_fh_rx_read_slot(ru_info_t *ru, int *frame, int *slot)
* before writing. */ * before writing. */
int xran_fh_tx_send_slot(ru_info_t *ru, int frame, int slot, uint64_t timestamp) int xran_fh_tx_send_slot(ru_info_t *ru, int frame, int slot, uint64_t timestamp)
{ {
int tti = /*frame*SUBFRAMES_PER_SYSTEMFRAME*SLOTNUM_PER_SUBFRAME+*/ 20 * frame
+ slot; // commented out temporarily to check that compilation of oran 5g is working.
void *ptr = NULL; void *ptr = NULL;
int32_t *pos = NULL; int32_t *pos = NULL;
int idx = 0; int idx = 0;
@@ -778,9 +769,12 @@ int xran_fh_tx_send_slot(ru_info_t *ru, int frame, int slot, uint64_t timestamp)
#if defined K_RELEASE #if defined K_RELEASE
uint8_t mu_number = fh_cfg->mu_number[0]; uint8_t mu_number = fh_cfg->mu_number[0];
int fftsize = 1 << fh_cfg->perMu[mu_number].nDLFftSize; int fftsize = 1 << fh_cfg->perMu[mu_number].nDLFftSize;
int slots_per_frame = 10 << mu_number;
#elif defined F_RELEASE #elif defined F_RELEASE
int fftsize = 1 << fh_cfg->nDLFftSize; int fftsize = 1 << fh_cfg->nDLFftSize;
int slots_per_frame = 10 << fh_cfg->frame_conf.nNumerology;
#endif #endif
int tti = slots_per_frame * frame + slot;
int nb_tx_per_ru = ru->nb_tx / fh_init->xran_ports; int nb_tx_per_ru = ru->nb_tx / fh_init->xran_ports;
int nb_rx_per_ru = ru->nb_rx / fh_init->xran_ports; int nb_rx_per_ru = ru->nb_rx / fh_init->xran_ports;
@@ -791,13 +785,13 @@ int xran_fh_tx_send_slot(ru_info_t *ru, int frame, int slot, uint64_t timestamp)
for (uint8_t ant_id = 0; ant_id < ru->nb_rx; ant_id++) { for (uint8_t ant_id = 0; ant_id < ru->nb_rx; ant_id++) {
const struct xran_frame_config *frame_conf = &get_xran_fh_config(ant_id / nb_rx_per_ru)->frame_conf; const struct xran_frame_config *frame_conf = &get_xran_fh_config(ant_id / nb_rx_per_ru)->frame_conf;
// skip processing this slot is TX (no RX in this slot) // skip processing this slot is TX (no RX in this slot)
if (!is_tdd_ul_guard_slot(frame_conf, slot)) { if (frame_conf->nFrameDuplexType != XRAN_FDD && !is_tdd_ul_guard_slot(frame_conf, slot)) {
continue; continue;
} }
// This loop would better be more inner to avoid confusion and maybe also errors. // This loop would better be more inner to avoid confusion and maybe also errors.
for (int32_t sym_idx = 0; sym_idx < XRAN_NUM_OF_SYMBOL_PER_SLOT; sym_idx++) { for (int32_t sym_idx = 0; sym_idx < XRAN_NUM_OF_SYMBOL_PER_SLOT; sym_idx++) {
/* skip DL and guard symbols. */ /* skip DL and guard symbols. */
if (!is_tdd_ul_symbol(frame_conf, slot, sym_idx)) { if (frame_conf->nFrameDuplexType != XRAN_FDD && !is_tdd_ul_symbol(frame_conf, slot, sym_idx)) {
continue; continue;
} }
oran_buf_list_t *bufs = get_xran_buffers(ant_id / nb_rx_per_ru); oran_buf_list_t *bufs = get_xran_buffers(ant_id / nb_rx_per_ru);
@@ -832,7 +826,7 @@ int xran_fh_tx_send_slot(ru_info_t *ru, int frame, int slot, uint64_t timestamp)
oran_buf_list_t *bufs = get_xran_buffers(ant_id / nb_tx_per_ru); oran_buf_list_t *bufs = get_xran_buffers(ant_id / nb_tx_per_ru);
const struct xran_frame_config *frame_conf = &get_xran_fh_config(ant_id / nb_tx_per_ru)->frame_conf; const struct xran_frame_config *frame_conf = &get_xran_fh_config(ant_id / nb_tx_per_ru)->frame_conf;
// skip processing this slot is TX (no TX in this slot) // skip processing this slot is TX (no TX in this slot)
if (!is_tdd_dl_guard_slot(frame_conf, slot)) { if (frame_conf->nFrameDuplexType != XRAN_FDD && !is_tdd_dl_guard_slot(frame_conf, slot)) {
continue; continue;
} }
@@ -848,7 +842,7 @@ int xran_fh_tx_send_slot(ru_info_t *ru, int frame, int slot, uint64_t timestamp)
break; break;
} }
} }
if (is_tdd_guard_slot(frame_conf, slot)) if (frame_conf->nFrameDuplexType != XRAN_FDD && is_tdd_guard_slot(frame_conf, slot))
pRbMap->nPrbElm = dl_sym_end; pRbMap->nPrbElm = dl_sym_end;
else else
pRbMap->nPrbElm = XRAN_NUM_OF_SYMBOL_PER_SLOT; pRbMap->nPrbElm = XRAN_NUM_OF_SYMBOL_PER_SLOT;
@@ -857,7 +851,7 @@ int xran_fh_tx_send_slot(ru_info_t *ru, int frame, int slot, uint64_t timestamp)
// This loop would better be more inner to avoid confusion and maybe also errors. // This loop would better be more inner to avoid confusion and maybe also errors.
for (int32_t sym_idx = 0; sym_idx < XRAN_NUM_OF_SYMBOL_PER_SLOT; sym_idx++) { for (int32_t sym_idx = 0; sym_idx < XRAN_NUM_OF_SYMBOL_PER_SLOT; sym_idx++) {
/* skip UL and guard symbols. */ /* skip UL and guard symbols. */
if (!is_tdd_dl_symbol(frame_conf, slot, sym_idx)) { if (frame_conf->nFrameDuplexType != XRAN_FDD && !is_tdd_dl_symbol(frame_conf, slot, sym_idx)) {
continue; continue;
} }
uint8_t *pData = uint8_t *pData =