Files
openairinterface5g/openair1/PHY/NR_TRANSPORT/nr_prach.c
T
Jaroslava Fiedlerova 2d34e6cb9a Increase PRACH queue capacity from 8 to 16
After the changes introduced in !3991, multiple PRACH PDUs can be
pushed from L2 to L1 before being processed. This leads to the PRACH
queues filling up and triggering "PRACH occ queue is full" issues on
the gNB.

This commit increases the queue size from 8 to 16, providing additional
buffering and preventing queue overflows as reported in #1072.
2026-04-24 14:26:42 +02:00

612 lines
20 KiB
C

/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
/*!
* \brief Top-level routines for generating and decoding the PRACH physical channel V15.4 2018-12
*/
#include "PHY/defs_gNB.h"
#include "SCHED_NR/sched_nr.h"
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
#include "PHY/NR_TRANSPORT/nr_transport_common_proto.h"
#include "openair1/PHY/NR_TRANSPORT/nr_prach.h"
void init_nr_prach(PHY_VARS_gNB *gNB)
{
int num_prach = 16;
gNB->prach_ru_queue = spsc_q_alloc(num_prach, sizeof(prach_item_t));
gNB->prach_l1rx_queue = spsc_q_alloc(num_prach, sizeof(prach_item_t));
}
void reset_nr_prach(PHY_VARS_gNB *gNB)
{
spsc_q_free(&gNB->prach_ru_queue);
spsc_q_free(&gNB->prach_l1rx_queue);
}
void free_nr_prach_entry(prach_item_t *p)
{
free(p->prach_buf);
}
static bool drop_old_prach(const void *data, void *user)
{
const prach_item_t *p = data;
const fsn_t *now = user;
// account for long PRACH over more than 1 slot
const fsn_t t = {p->frame, p->slot + p->num_slots - 1, now->mu};
bool drop = fsn_in_the_past(t, *now);
if (drop)
LOG_E(NR_PHY, "%4d.%2d PRACH job is in the past (%4d.%2d)\n", now->f, now->s, t.f, t.s);
return drop;
}
static bool get_current_prach(const void *data, void *user)
{
const prach_item_t *p = data;
const fsn_t *now = user;
// account for long PRACH over more than 1 slot
const fsn_t t = {p->frame, p->slot + p->num_slots - 1, now->mu};
return fsn_equal(t, *now);
}
bool get_next_nr_prach(spsc_q_t *q, const fsn_t *now, prach_item_t *p)
{
spsc_q_drop_while(q, drop_old_prach, (void *)now);
return spsc_q_get_if(q, get_current_prach, (void *)now, p, sizeof(*p));
}
void nr_schedule_rx_prach(PHY_VARS_gNB *gNB, int SFN, int Slot, nfapi_nr_prach_pdu_t *prach_pdu)
{
const int fmt = prach_pdu->prach_format;
const NR_DL_FRAME_PARMS *fp = &gNB->frame_parms;
const nfapi_nr_prach_config_t *cfg = &gNB->gNB_config.prach_config;
const nfapi_nr_num_prach_fd_occasions_t *occ = &cfg->num_prach_fd_occasions_list[prach_pdu->num_ra];
prach_item_t prach = {
.frame = SFN,
.slot = Slot,
.num_slots = fmt < 4 ? get_long_prach_dur(fmt, fp->numerology_index) : 1,
.pdu = *prach_pdu,
.rootSequenceIndex = occ->prach_root_sequence_index.value,
.numrootSequenceIndex = occ->num_root_sequences.value,
.msg1_frequencystart = occ->k1.value,
.mu = cfg->prach_sub_c_spacing.value,
.prach_sequence_length = cfg->prach_sequence_length.value,
.restricted_set = cfg->restricted_set_config.value,
.numerology_index = fp->numerology_index,
.nb_rx = gNB->gNB_config.carrier_config.num_rx_ant.value,
.Xu = gNB->X_u,
.rx_prach = &gNB->rx_prach,
// TODO can be made permanently allocated?
.prach_buf = calloc_or_fail(1, sizeof(c16_t) * prach.nb_rx * NUMBER_OF_NR_RU_PRACH_OCCASIONS_MAX * NR_PRACH_SEQ_LEN_L),
};
if (gNB->common_vars.beam_id) {
int n_symb = get_nr_prach_duration(prach_pdu->prach_format);
AssertFatal(prach_pdu->beamforming.dig_bf_interface < NFAPI_MAX_NUM_BG_IF,
"impossible beams size %d\n",
prach_pdu->beamforming.dig_bf_interface);
for (int i = 0; i < prach_pdu->beamforming.dig_bf_interface; i++) {
int fapi_beam_idx = prach_pdu->beamforming.prgs_list[0].dig_bf_interface_list[i].beam_idx;
int start_symb = prach_pdu->prach_start_symbol + i * n_symb;
int bitmap = SL_to_bitmap(start_symb, n_symb);
prach.beams[i] = beam_index_allocation(gNB->enable_analog_das,
fapi_beam_idx,
&gNB->common_vars,
Slot,
gNB->frame_parms.symbols_per_slot,
bitmap);
}
}
bool found = spsc_q_put(&gNB->prach_ru_queue, &prach, sizeof(prach));
if (!found)
LOG_W(NR_PHY, "%4d.%2d PRACH occ queue is full: dropping PRACH request\n", SFN, Slot);
}
static void rx_nr_prach_ru_internal(prach_item_t *p,
int beam_id,
int prachStartSymbol,
int prachOccasion,
int32_t **rxdata,
NR_DL_FRAME_PARMS *fp,
int N_TA_offset)
{
int sample_offset_slot;
const int sum = fp->ofdm_symbol_size + fp->nb_prefix_samples;
const int sum0 = fp->ofdm_symbol_size + fp->nb_prefix_samples0;
if (prachStartSymbol == 0) {
sample_offset_slot = 0;
} else if (fp->slots_per_subframe == 1) {
if (prachStartSymbol <= 7)
sample_offset_slot = sum * (prachStartSymbol - 1) + sum0;
else
sample_offset_slot = sum * (prachStartSymbol - 2) + sum0 * 2;
} else {
if (!(p->slot % (fp->slots_per_subframe / 2)))
sample_offset_slot = sum * (prachStartSymbol - 1) + sum0;
else
sample_offset_slot = sum * prachStartSymbol;
}
LOG_D(PHY,
"frame %d, slot %d: doing rx_nr_prach_ru for format %d, numRA %d, prachStartSymbol %d, prachOccasion %d\n",
p->frame,
p->slot,
p->pdu.prach_format,
p->pdu.num_ra,
prachStartSymbol,
prachOccasion);
int reps;
int Ncp;
int dftlen;
int mu = p->numerology_index;
if (p->prach_sequence_length == 0) {
LOG_D(PHY,
"PRACH in %d.%d, format %d, msg1_frequencyStart %d\n",
p->frame,
p->slot,
p->pdu.prach_format,
p->msg1_frequencystart);
switch (p->pdu.prach_format) {
case 0:
reps = 1;
Ncp = 3168;
dftlen = 24576;
break;
case 1:
reps = 2;
Ncp = 21024;
dftlen = 24576;
break;
case 2:
reps = 4;
Ncp = 4688;
dftlen = 24576;
break;
case 3:
reps = 4;
Ncp = 3168;
dftlen = 6144;
break;
default:
AssertFatal(1 == 0, "Illegal prach format %d for length 839\n", p->pdu.prach_format);
break;
}
} else {
LOG_D(PHY,
"PRACH in %d.%d, format %s, msg1_frequencyStart %d,startSymbol %d\n",
p->frame,
p->slot,
prachfmt[p->pdu.prach_format],
p->msg1_frequencystart,
prachStartSymbol);
switch (p->pdu.prach_format) {
case 4: // A1
reps = 2;
Ncp = 288 >> mu;
break;
case 5: // A2
reps = 4;
Ncp = 576 >> mu;
break;
case 6: // A3
reps = 6;
Ncp = 864 >> mu;
break;
case 7: // B1
reps = 2;
Ncp = 216 >> mu;
break;
/*
// B2 and B3 do not exist in FAPI
case 4: //B2
reps = 4;
Ncp = 360 >> mu;
break;
case 5: //B3
reps = 6;
Ncp = 504 >> mu;
break;
*/
case 8: // B4
reps = 12;
Ncp = 936 >> mu;
break;
case 9: // C0
reps = 1;
Ncp = 1240 >> mu;
break;
case 10: // C2
reps = 4;
Ncp = 2048 >> mu;
break;
default:
AssertFatal(1 == 0, "unknown prach format %x\n", p->pdu.prach_format);
break;
}
dftlen = 2048 >> mu;
}
//actually what we should be checking here is how often the current prach crosses a 0.5ms boundary.
//I am not quite sure for which paramter set this would be the case,
//so I will ignore it for now and just check if the prach starts on a 0.5ms boundary
if (p->numerology_index == 0) {
if (prachStartSymbol == 0 || prachStartSymbol == 7)
Ncp += 16;
} else {
if (p->slot % (fp->slots_per_subframe / 2) == 0 && prachStartSymbol == 0)
Ncp += 16;
}
switch(fp->samples_per_subframe) {
case 7680:
// 5 MHz @ 7.68 Ms/s
Ncp >>= 2;
dftlen >>= 2;
break;
case 15360:
// 10, 15 MHz @ 15.36 Ms/s
Ncp >>= 1;
dftlen >>= 1;
break;
case 23040:
// 20 MHz @ 23.04 Ms/s
Ncp = (Ncp * 3) / 4;
dftlen = (dftlen * 3) / 4;
break;
case 30720:
// 20, 25, 30 MHz @ 30.72 Ms/s
break;
case 46080:
// 40 MHz @ 46.08 Ms/s
Ncp = (Ncp*3)/2;
dftlen = (dftlen*3)/2;
break;
case 61440:
// 40, 50, 60 MHz @ 61.44 Ms/s
Ncp <<= 1;
dftlen <<= 1;
break;
case 92160:
// 50, 60, 70, 80, 90 MHz @ 92.16 Ms/s
Ncp *= 3;
dftlen *= 3;
break;
case 122880:
// 70, 80, 90, 100 MHz @ 122.88 Ms/s
Ncp <<= 2;
dftlen <<= 2;
break;
case 184320:
// 100 MHz @ 184.32 Ms/s
Ncp = Ncp*6;
dftlen = dftlen*6;
break;
case 245760:
// 200 MHz @ 245.76 Ms/s
Ncp <<= 3;
dftlen <<= 3;
break;
default:
AssertFatal(1==0,"sample rate %f MHz not supported for numerology %d\n", fp->samples_per_subframe / 1000.0, mu);
}
const dft_size_idx_t dftsize = get_dft(dftlen);
// Do forward transform
if (LOG_DEBUGFLAG(DEBUG_PRACH)) {
LOG_D(PHY, "rx_prach: Doing PRACH FFT for nb_rx:%d Ncp:%d dftlen:%d\n", p->nb_rx, Ncp, dftlen);
}
const unsigned int K = get_prach_K(p->prach_sequence_length, p->pdu.prach_format, p->numerology_index, p->mu);
const uint8_t kbar = get_PRACH_k_bar(p->mu, p->numerology_index);
int n_ra_prb = p->msg1_frequencystart;
int k = (12*n_ra_prb) - 6*fp->N_RB_UL;
int N_ZC = (p->prach_sequence_length == 0) ? 839 : 139;
if (k<0) k+=(fp->ofdm_symbol_size);
k*=K;
k+=kbar;
for (int aa = 0; aa < p->nb_rx; aa++) {
// Fixme: slot or slot makes no sense ???
int slot2 = p->prach_sequence_length ? p->slot : p->slot;
int idx = aa + beam_id * p->nb_rx;
c16_t *prach = (c16_t *)&rxdata[idx][get_samples_slot_timestamp(fp, slot2) + sample_offset_slot - N_TA_offset];
// do DFT
c16_t *prach2 = prach + Ncp;
c16_t rxsigF_tmp[N_ZC];
memset(rxsigF_tmp, 0, sizeof(rxsigF_tmp));
for (int i = 0; i < reps; i++, prach2 += dftlen) {
c16_t tmp[dftlen] __attribute__((aligned(32)));
dft(dftsize, (int16_t *)prach2, (int16_t *)tmp, 1);
// Coherent combining of PRACH repetitions (assumes channel does not change, to be revisted for "long" PRACH)
LOG_D(PHY, "Doing PRACH combining of %d reptitions N_ZC %d\n", reps, N_ZC);
// if (k+N_ZC > dftlen) { // PRACH signal is split around DC
int k2 = k;
for (int j = 0; j < N_ZC; j++, k2++) {
if (k2 == dftlen)
k2 = 0;
rxsigF_tmp[j] = c16add(rxsigF_tmp[j], tmp[k2]);
}
}
memcpy(p->prach_buf[aa][prachOccasion], rxsigF_tmp, sizeof(rxsigF_tmp));
}
}
void rx_nr_prach_ru(prach_item_t *p, int32_t **rxdata, NR_DL_FRAME_PARMS *fp, int N_TA_offset)
{
int N_dur = get_nr_prach_duration(p->pdu.prach_format);
LOG_D(NR_PHY_RACH, "%d.%d try to decode %d occasions \n", p->frame, p->slot, p->pdu.num_prach_ocas);
for (int prach_oc = 0; prach_oc < p->pdu.num_prach_ocas; prach_oc++) {
int prachStartSymbol = p->pdu.prach_start_symbol + prach_oc * N_dur;
int beam_id = p->beams[prach_oc];
// comment FK: the standard 38.211 section 5.3.2 has one extra term +14*N_RA_slot. This is because there prachStartSymbol is
// given wrt to start of the 15kHz slot or 60kHz slot. Here we work slot based, so this function is anyway only called in slots
// where there is PRACH. Its up to the MAC to schedule another PRACH PDU in the case there are there N_RA_slot \in {0,1}.
rx_nr_prach_ru_internal(p, beam_id, prachStartSymbol, prach_oc, rxdata, fp, N_TA_offset);
}
}
rx_prach_out_t rx_nr_prach(const prach_item_t *in, int occasion)
{
rx_prach_out_t out = {};
uint16_t preamble_index0 = 0;
uint16_t numshift = 0;
int first_nonzero_root_idx = 0;
bool new_dft = false;
int log2_ifft_size = 10;
const int nb_rx = in->nb_rx;
const int NCS = in->pdu.num_cs;
const int prach_fmt = in->pdu.prach_format;
const int N_ZC = in->prach_sequence_length == 0 ? 839 : 139;
LOG_D(NR_PHY_RACH,
"L1 PRACH RX: rooSequenceIndex %d, numRootSeqeuences %d, NCS %d, N_ZC %d, format %d \n",
in->rootSequenceIndex,
in->numrootSequenceIndex,
NCS,
N_ZC,
prach_fmt);
if (LOG_DEBUGFLAG(DEBUG_PRACH)) {
if ((in->frame & 1023) < 20)
LOG_D(PHY, "PRACH (gNB) : running rx_prach for slot %d, rootSequenceIndex %d\n", in->slot, in->rootSequenceIndex);
}
start_meas(in->rx_prach);
const uint16_t *prach_root_sequence_map =
in->prach_sequence_length == 0 ? prach_root_sequence_map_0_3 : prach_root_sequence_map_abc;
// PDP is oversampled, e.g. 1024 sample instead of 839
// Adapt the NCS (zero-correlation zones) with oversampling factor e.g. 1024/839
int NCS2 = N_ZC == 839 ? (NCS << 10) / 839 : (NCS << 8) / 139;
if (NCS2 == 0)
NCS2 = N_ZC;
int preamble_offset = 0, preamble_offset_old = 99;
int16_t preamble_shift = 0;
const int dft_sz = N_ZC == 839 ? 1024 : 256;
int32_t prach_ifft[dft_sz] __attribute__((aligned(32)));
for (int preamble_index = 0; preamble_index < 64; preamble_index++) {
if (LOG_DEBUGFLAG(DEBUG_PRACH)) {
int en = dB_fixed(signal_energy((int32_t *)in->prach_buf[0][occasion], N_ZC == 839 ? 840 : 140));
if (en > 60)
LOG_D(PHY, "frame %d, slot %d : Trying preamble %d \n", in->frame, in->slot, preamble_index);
}
if (in->restricted_set == 0) {
// This is the relative offset in the root sequence table (5.7.2-4 from 36.211) for the given preamble index
preamble_offset = ((NCS==0)? preamble_index : (preamble_index/(N_ZC/NCS)));
if (preamble_offset != preamble_offset_old) {
preamble_offset_old = preamble_offset;
new_dft = true;
// This is the \nu corresponding to the preamble index
preamble_shift = 0;
} else {
preamble_shift -= NCS;
if (preamble_shift < 0)
preamble_shift += N_ZC;
}
} else { // This is the high-speed case
new_dft = false;
uint16_t nr_du[NR_PRACH_SEQ_LEN_L - 1];
nr_fill_du(N_ZC, prach_root_sequence_map, nr_du);
// set preamble_offset to initial rootSequenceIndex and look if we need more root sequences for this
// preamble index and find the corresponding cyclic shift
// Check if all shifts for that root have been processed
int n_shift_ra = 0, n_shift_ra_bar, d_start = 0;
if (preamble_index0 == numshift) {
bool not_found = true;
new_dft = true;
preamble_index0 -= numshift;
while (not_found) {
// current root depending on rootSequenceIndex
int index = (in->rootSequenceIndex + preamble_offset) % N_ZC;
int u = nr_du[prach_root_sequence_map[index]];
uint16_t n_group_ra = 0;
if (u < (N_ZC / 3) && u >= NCS) {
n_shift_ra = u / NCS;
d_start = (u << 1) + (n_shift_ra * NCS);
n_group_ra = N_ZC / d_start;
n_shift_ra_bar = max(0, (N_ZC - (u << 1) - (n_group_ra * d_start)) / N_ZC);
} else if (u >= (N_ZC / 3) && u <= ((N_ZC - NCS) >> 1)) {
n_shift_ra = (N_ZC - (u << 1)) / NCS;
d_start = N_ZC - (u << 1) + (n_shift_ra * NCS);
n_group_ra = u / d_start;
n_shift_ra_bar = min(n_shift_ra, max(0, (u - (n_group_ra * d_start)) / NCS));
} else {
n_shift_ra = 0;
n_shift_ra_bar = 0;
}
// This is the number of cyclic shifts for the current root u
numshift = (n_shift_ra * n_group_ra) + n_shift_ra_bar;
// skip to next root and recompute parameters if numshift==0
numshift > 0 ? not_found = false : preamble_offset++;
}
}
if (n_shift_ra>0)
preamble_shift = -(d_start * (preamble_index0 / n_shift_ra)
+ (preamble_index0 % n_shift_ra) * NCS); // minus because the channel is h(t -\tau + Cv)
else
preamble_shift = 0;
if (preamble_shift < 0)
preamble_shift+=N_ZC;
preamble_index0++;
if (preamble_index == 0)
first_nonzero_root_idx = preamble_offset;
}
// Compute DFT of RX signal (conjugate in->rxsigF[occasion], results in conjugate output) for each new rootSequenceIndex
if (LOG_DEBUGFLAG(DEBUG_PRACH)) {
int en = dB_fixed(signal_energy((int32_t *)in->prach_buf[0][occasion], 840));
if (en>60)
LOG_D(PHY,
"frame %d, slot %d : preamble index %d, NCS %d, N_ZC/NCS %d: offset %d, preamble shift %d , en %d)\n",
in->frame,
in->slot,
preamble_index,
NCS,
N_ZC / NCS,
preamble_offset,
preamble_shift,
en);
}
LOG_D(NR_PHY_RACH,
"PRACH RX preamble_index %d, preamble_offset %d, preamb shift %d new dft %d\n",
preamble_index,
preamble_offset,
preamble_shift,
new_dft);
if (new_dft) {
new_dft = false;
c16_t *Xu = in->Xu[preamble_offset - first_nonzero_root_idx];
LOG_D(PHY,"PRACH RX new dft preamble_offset-first_nonzero_root_idx %d\n",preamble_offset-first_nonzero_root_idx);
memset(prach_ifft, 0, sizeof(prach_ifft));
if (LOG_DUMPFLAG(DEBUG_PRACH)) {
LOG_M("prach_rxF0.m", "prach_rxF0", in->prach_buf[0][occasion], N_ZC, 1, 1);
LOG_M("prach_rxF1.m", "prach_rxF1", in->prach_buf[1][occasion], 6144, 1, 1);
}
c16_t prachF[dft_sz] __attribute__((aligned(32)));
for (int aa = 0; aa < nb_rx; aa++) {
// Do componentwise product with Xu* on each antenna
for (int offset = 0; offset < N_ZC; offset++) {
prachF[offset] = c16MulConjShift(Xu[offset], in->prach_buf[aa][occasion][offset], 15);
}
memset(prachF + N_ZC, 0, sizeof(*prachF) * (dft_sz - N_ZC));
// Now do IFFT of size 1024 (N_ZC=839) or 256 (N_ZC=139)
c16_t prach_ifft_tmp[dft_sz] __attribute__((aligned(32)));
idft(get_idft(dft_sz), (int16_t *)prachF, (int16_t *)prach_ifft_tmp, 1);
// compute energy and accumulate over receive antennas
for (int i = 0; i < dft_sz; i++)
prach_ifft[i] += squaredMod(prach_ifft_tmp[i]);
if (LOG_DUMPFLAG(DEBUG_PRACH)) {
if (aa == 0)
LOG_M("prach_rxF_comp0.m","prach_rxF_comp0", prachF, 1024, 1, 1);
if (aa == 1)
LOG_M("prach_rxF_comp1.m","prach_rxF_comp1", prachF, 1024, 1, 1);
}
} // antennas_rx
// Normalization of energy over ifft and receive antennas
if (N_ZC == 839) {
log2_ifft_size = 10;
for (int i = 0; i < 1024; i++)
prach_ifft[i] = (prach_ifft[i]>>log2_ifft_size)/nb_rx;
} else {
log2_ifft_size = 8;
for (int i = 0; i < 256; i++)
prach_ifft[i] = (prach_ifft[i]>>log2_ifft_size)/nb_rx;
}
} // new dft
// check energy in nth time shift, for
int preamble_shift2 = preamble_shift == 0 ? 0 : (preamble_shift << log2_ifft_size) / N_ZC;
for (int i = 0; i < NCS2; i++) {
int lev = prach_ifft[preamble_shift2 + i];
int levdB = dB_fixed_times10(lev);
if (levdB > out.max_preamble_energy || (levdB == out.max_preamble_energy && out.max_preamble_delay > i)) {
LOG_D(NR_PHY_RACH, "preamble_index %d, delay %d en %d dB > %d dB\n", preamble_index, i, levdB, out.max_preamble_energy);
out.max_preamble_energy = levdB;
out.max_preamble_delay = i; // Note: This has to be normalized to the 30.72 Ms/s sampling rate
out.max_preamble = preamble_index;
}
}
} // preamble_index
// The conversion from *max_preamble_delay from TA value is done here.
// It is normalized to the 30.72 Ms/s, considering the numerology, N_RB and the sampling rate
// See table 6.3.3.1 -1 and -2 in 38211.
// Format 0, 1, 2: 24576 samples @ 30.72 Ms/s, 98304 samples @ 122.88 Ms/s
// By solving:
// max_preamble_delay * ( (24576*(fs/30.72M)) / 1024 ) / fs = TA * 16 * 64 / 2^mu * Tc
// Format 3: 6144 samples @ 30.72 Ms/s, 24576 samples @ 122.88 Ms/s
// By solving:
// max_preamble_delay * ( (6144*(fs/30.72M)) / 1024 ) / fs = TA * 16 * 64 / 2^mu * Tc
// Format >3: 2048/2^mu samples @ 30.72 Ms/s, 2048/2^mu * 4 samples @ 122.88 Ms/s
// By solving:
// max_preamble_delay * ( (2048/2^mu*(fs/30.72M)) / 256 ) / fs = TA * 16 * 64 / 2^mu * Tc
int mu = in->numerology_index;
if (in->prach_sequence_length == 0) {
if (prach_fmt == 0 || prach_fmt == 1 || prach_fmt == 2)
out.max_preamble_delay *= 3 * (1 << mu) / 2;
else if (prach_fmt == 3)
out.max_preamble_delay *= 3 * (1 << mu) / 8;
} else
out.max_preamble_delay /= 2;
stop_meas(in->rx_prach);
return out;
}