Files
openairinterface5g/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c
T
2026-09-19 23:28:08 +09:00

1472 lines
62 KiB
C

/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
/*!
* \brief Implementation of UE procedures from 36.213 LTE specifications
*/
#define _GNU_SOURCE
#include "nr/nr_common.h"
#include "assertions.h"
#include "defs.h"
#include "PHY/defs_nr_UE.h"
#include "PHY/NR_REFSIG/dmrs_nr.h"
#include "PHY/MODULATION/modulation_UE.h"
#include "PHY/INIT/nr_phy_init.h"
#include "PHY/nr_phy_common/inc/nr_phy_common.h"
#include "PHY/NR_REFSIG/ptrs_nr.h"
#include "PHY/NR_UE_TRANSPORT/nr_transport_ue.h"
#include "PHY/NR_UE_TRANSPORT/nr_transport_proto_ue.h"
#include "SCHED_NR_UE/phy_sch_processing_time.h"
#include "PHY/NR_UE_ESTIMATION/nr_estimation.h"
#ifdef EMOS
#include "SCHED/phy_procedures_emos.h"
#endif
#include "executables/softmodem-common.h"
#include "executables/nr-uesoftmodem.h"
#include "SCHED_NR_UE/pucch_uci_ue_nr.h"
#include <openair1/PHY/TOOLS/phy_scope_interface.h>
#include "nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface.h"
//#define DEBUG_PHY_PROC
//#define NR_PDCCH_SCHED_DEBUG
//#define NR_PUCCH_SCHED
//#define NR_PUCCH_SCHED_DEBUG
//#define NR_PDSCH_DEBUG
#ifndef PUCCH
#define PUCCH
#endif
#include "common/utils/LOG/log.h"
#ifdef EMOS
fifo_dump_emos_UE emos_dump_UE;
#endif
#include "UTIL/OPT/opt.h"
#include "intertask_interface.h"
#include "T.h"
#include "instrumentation.h"
static const unsigned int gain_table[31] = {100, 112, 126, 141, 158, 178, 200, 224, 251, 282, 316,
359, 398, 447, 501, 562, 631, 708, 794, 891, 1000, 1122,
1258, 1412, 1585, 1778, 1995, 2239, 2512, 2818, 3162};
typedef struct {
fourDimArray_t *array;
size_t capacity;
} nr_ue_reusable_array_t;
/* Return a correctly-strided view without shrinking an existing allocation.
* Rank and RB allocation vary from one PDSCH to the next; exact-dimension
* resize logic otherwise oscillates between small control grants and large
* rank-8 data grants, reintroducing allocation/zeroing on every slot. */
static fourDimArray_t *nr_ue_reuse_array(nr_ue_reusable_array_t *storage,
size_t element_size,
int dim1,
int dim2,
int dim3,
int dim4)
{
DevAssert(dim1 > 0);
size_t elements = dim1;
if (dim2 > 0)
elements *= dim2;
if (dim3 > 0)
elements *= dim3;
if (dim4 > 0)
elements *= dim4;
const size_t required = sizeof(fourDimArray_t) + elements * element_size;
if (required > storage->capacity) {
free(storage->array);
storage->array = malloc16_clear(required);
AssertFatal(storage->array != NULL, "Could not allocate %zu-byte UE PDSCH workspace\n", required);
storage->capacity = required;
}
storage->array->dim1 = dim1;
storage->array->dim2 = dim2;
storage->array->dim3 = dim3;
storage->array->dim4 = dim4;
return storage->array;
}
static void nr_ue_prach_procedures(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, c16_t **txData);
void nr_fill_dl_indication(nr_downlink_indication_t *dl_ind,
fapi_nr_dci_indication_t *dci_ind,
fapi_nr_rx_indication_t *rx_ind,
const UE_nr_rxtx_proc_t *proc,
PHY_VARS_NR_UE *ue,
void *phy_data)
{
memset((void*)dl_ind, 0, sizeof(nr_downlink_indication_t));
dl_ind->gNB_index = proc->gNB_id;
dl_ind->module_id = ue->Mod_id;
dl_ind->cc_id = ue->CC_id;
dl_ind->hfn = proc->hfn_rx;
dl_ind->frame = proc->frame_rx;
dl_ind->slot = proc->nr_slot_rx;
dl_ind->phy_data = phy_data;
if (dci_ind) {
dl_ind->rx_ind = NULL; //no data, only dci for now
dl_ind->dci_ind = dci_ind;
} else if (rx_ind) {
dl_ind->rx_ind = rx_ind; // hang on rx_ind instance
dl_ind->dci_ind = NULL;
}
}
static uint32_t get_ssb_arfcn(NR_DL_FRAME_PARMS *frame_parms)
{
uint32_t band_size_hz = frame_parms->N_RB_DL * 12 * frame_parms->subcarrier_spacing;
int ssb_center_sc = frame_parms->ssb_start_subcarrier + 120; // ssb is 20 PRBs -> 240 sub-carriers
uint64_t ssb_freq = frame_parms->dl_CarrierFreq - (band_size_hz / 2) + frame_parms->subcarrier_spacing * ssb_center_sc;
return to_nrarfcn(ssb_freq);
}
void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
uint8_t pdu_type,
PHY_VARS_NR_UE *ue,
int cw_idx,
int harq_pid,
NR_UE_DLSCH_t *dlsch,
uint16_t n_pdus,
const UE_nr_rxtx_proc_t *proc,
void *typeSpecific,
uint8_t *b)
{
if (n_pdus > 1) {
LOG_E(PHY, "Multiple number of DL PDUs not supported yet...\n");
return;
}
fapi_nr_rx_indication_body_t *rx = rx_ind->rx_indication_body + n_pdus - 1;
switch (pdu_type){
case FAPI_NR_RX_PDU_TYPE_SIB:
case FAPI_NR_RX_PDU_TYPE_RAR:
case FAPI_NR_RX_PDU_TYPE_DLSCH:
if(dlsch) {
NR_DL_UE_HARQ_t *dl_harq = &ue->dl_harq_processes[cw_idx][harq_pid];
rx->pdsch_pdu.harq_pid = harq_pid;
rx->pdsch_pdu.cw_idx = cw_idx;
rx->pdsch_pdu.ack_nack = dl_harq->decodeResult;
rx->pdsch_pdu.pdu = b;
rx->pdsch_pdu.pdu_length = dlsch->cw_info.TBS / 8;
if (dl_harq->decodeResult) {
int t = WS_C_RNTI;
if (pdu_type == FAPI_NR_RX_PDU_TYPE_RAR)
t = WS_RA_RNTI;
if (pdu_type == FAPI_NR_RX_PDU_TYPE_SIB)
t = WS_SI_RNTI;
ws_trace_t tmp = {.nr = true,
.direction = DIRECTION_DOWNLINK,
.pdu_buffer = b,
.pdu_buffer_size = rx->pdsch_pdu.pdu_length,
.ueid = 0,
.rntiType = t,
.rnti = dlsch->rnti,
.sysFrame = proc->frame_rx,
.subframe = proc->nr_slot_rx,
.harq_pid = harq_pid};
trace_pdu(&tmp);
}
}
break;
case FAPI_NR_RX_PDU_TYPE_SSB: {
if (typeSpecific) {
NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
fapiPbch_t *pbch = (fapiPbch_t *)typeSpecific;
memcpy(rx->ssb_pdu.pdu, pbch->decoded_output, sizeof(pbch->decoded_output));
rx->ssb_pdu.additional_bits = pbch->xtra_byte;
rx->ssb_pdu.ssb_index = (frame_parms->ssb_index) & 0x7;
rx->ssb_pdu.ssb_length = frame_parms->Lmax;
rx->ssb_pdu.cell_id = frame_parms->Nid_cell;
rx->ssb_pdu.ssb_start_subcarrier = frame_parms->ssb_start_subcarrier;
rx->ssb_pdu.arfcn = get_ssb_arfcn(frame_parms);
rx->ssb_pdu.radiolink_monitoring = RLM_in_sync; // TODO to be removed from here
rx->ssb_pdu.decoded_pdu = true;
} else {
rx->ssb_pdu.radiolink_monitoring = RLM_out_of_sync; // TODO to be removed from here
rx->ssb_pdu.decoded_pdu = false;
}
} break;
case FAPI_NR_MEAS_IND:
memcpy(&rx->l1_measurements, typeSpecific, sizeof(fapi_nr_l1_measurements_t));
break;
default:
break;
}
rx->pdu_type = pdu_type;
rx_ind->number_pdus = n_pdus;
}
int get_tx_amp_prach(int power_dBm, int power_max_dBm, int N_RB_UL){
int gain_dB = power_dBm - power_max_dBm, amp_x_100 = -1;
switch (N_RB_UL) {
case 6:
amp_x_100 = AMP; // PRACH is 6 PRBS so no scale
break;
case 15:
amp_x_100 = 158*AMP; // 158 = 100*sqrt(15/6)
break;
case 25:
amp_x_100 = 204*AMP; // 204 = 100*sqrt(25/6)
break;
case 50:
amp_x_100 = 286*AMP; // 286 = 100*sqrt(50/6)
break;
case 75:
amp_x_100 = 354*AMP; // 354 = 100*sqrt(75/6)
break;
case 100:
amp_x_100 = 408*AMP; // 408 = 100*sqrt(100/6)
break;
default:
LOG_E(PHY, "Unknown PRB size %d\n", N_RB_UL);
return (amp_x_100);
break;
}
if (gain_dB < -30) {
return (amp_x_100/3162);
} else if (gain_dB > 0)
return (amp_x_100);
else
return (amp_x_100/gain_table[-gain_dB]); // 245 corresponds to the factor sqrt(25/6)
return (amp_x_100);
}
// UL time alignment procedures:
// - If the current tx frame and slot match the TA configuration
// then timing advance is processed and set to be applied in the next UL transmission
// - Application of timing adjustment according to TS 38.213 p4.2
// - handle RAR TA application as per ch 4.2 TS 38.213
void ue_ta_procedures(PHY_VARS_NR_UE *ue, int slot_tx, int frame_tx)
{
if (frame_tx == ue->ta_frame && slot_tx == ue->ta_slot) {
uint16_t ofdm_symbol_size = ue->frame_parms.ofdm_symbol_size;
// convert time factor "16 * 64 * T_c / (2^mu)" in N_TA calculation in TS38.213 section 4.2 to samples by multiplying with
// samples per second
// 16 * 64 * T_c / (2^mu) * samples_per_second
// = 16 * T_s / (2^mu) * samples_per_second
// = 16 * 1 / (15 kHz * 2048) / (2^mu) * (15 kHz * 2^mu * ofdm_symbol_size)
// = 16 * 1 / 2048 * ofdm_symbol_size
// = 16 * ofdm_symbol_size / 2048
uint16_t bw_scaling = 16 * ofdm_symbol_size / 2048;
ue->timing_advance += (ue->ta_command - 31) * bw_scaling;
LOG_D(PHY,
"[UE %d] [%d.%d] Got timing advance command %u from MAC, new value is %d\n",
ue->Mod_id,
frame_tx,
slot_tx,
ue->ta_command,
ue->timing_advance);
ue->ta_frame = -1;
ue->ta_slot = -1;
}
}
static void configure_srs_info(const fapi_nr_ul_config_srs_pdu *srs_config_pdu, nr_srs_info_t *nr_srs_info)
{
nr_srs_info->B_SRS = srs_config_pdu->bandwidth_index;
nr_srs_info->C_SRS = srs_config_pdu->config_index;
nr_srs_info->b_hop = srs_config_pdu->frequency_hopping;
nr_srs_info->comb_size = srs_config_pdu->comb_size;
nr_srs_info->K_TC_overbar = srs_config_pdu->comb_offset;
nr_srs_info->n_SRS_cs = srs_config_pdu->cyclic_shift;
nr_srs_info->n_ID_SRS = srs_config_pdu->sequence_id;
// It adjusts the SRS allocation to align with the common resource block grid in multiples of four
nr_srs_info->n_shift = srs_config_pdu->frequency_shift;
nr_srs_info->n_RRC = srs_config_pdu->frequency_position;
nr_srs_info->groupOrSequenceHopping = srs_config_pdu->group_or_sequence_hopping;
nr_srs_info->l_offset = srs_config_pdu->time_start_position;
nr_srs_info->T_SRS = srs_config_pdu->t_srs;
nr_srs_info->T_offset = srs_config_pdu->t_offset;
nr_srs_info->R = 1 << srs_config_pdu->num_repetitions;
nr_srs_info->N_symb_SRS = 1 << srs_config_pdu->num_symbols; // Number of consecutive OFDM symbols
nr_srs_info->n_srs_ports = 1 << srs_config_pdu->num_ant_ports; // Number of antenna port for transmission
nr_srs_info->resource_type = srs_config_pdu->resource_type;
}
/*******************************************************************
*
* NAME : ue_srs_procedures_nr
*
* PARAMETERS : pointer to ue context
* pointer to rxtx context*
*
* DESCRIPTION : ue srs procedure
* send srs according to current configuration
*
*********************************************************************/
void ue_srs_procedures_nr(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
c16_t **txdataF,
const fapi_nr_ul_config_srs_pdu *srs_config_pdu,
bool was_symbol_used[NR_SYMBOLS_PER_SLOT])
{
NR_DL_FRAME_PARMS *frame_parms = &(ue->frame_parms);
const uint8_t l0 = frame_parms->symbols_per_slot - 1 - srs_config_pdu->time_start_position;
// Num consecutive SRS symbols according to 38.211 6.4.1.4.1
int num_srs_symbols[] = {1, 2, 4, 8, 12};
int last_srs_symbol = l0 + num_srs_symbols[srs_config_pdu->num_symbols] - 1;
for (int i = l0; i <= last_srs_symbol; i++) {
was_symbol_used[i] = true;
}
#ifdef SRS_DEBUG
LOG_I(NR_PHY,"Frame = %i, slot = %i\n", proc->frame_tx, proc->nr_slot_tx);
LOG_I(NR_PHY,"srs_config_pdu->rnti = 0x%04x\n", srs_config_pdu->rnti);
LOG_I(NR_PHY,"srs_config_pdu->handle = %u\n", srs_config_pdu->handle);
LOG_I(NR_PHY,"srs_config_pdu->bwp_size = %u\n", srs_config_pdu->bwp_size);
LOG_I(NR_PHY,"srs_config_pdu->bwp_start = %u\n", srs_config_pdu->bwp_start);
LOG_I(NR_PHY,"srs_config_pdu->subcarrier_spacing = %u\n", srs_config_pdu->subcarrier_spacing);
LOG_I(NR_PHY,"srs_config_pdu->cyclic_prefix = %u (0: Normal; 1: Extended)\n", srs_config_pdu->cyclic_prefix);
LOG_I(NR_PHY,"srs_config_pdu->num_ant_ports = %u (0 = 1 port, 1 = 2 ports, 2 = 4 ports)\n", srs_config_pdu->num_ant_ports);
LOG_I(NR_PHY,"srs_config_pdu->num_symbols = %u (0 = 1 symbol, 1 = 2 symbols, 2 = 4 symbols)\n", srs_config_pdu->num_symbols);
LOG_I(NR_PHY,"srs_config_pdu->num_repetitions = %u (0 = 1, 1 = 2, 2 = 4)\n", srs_config_pdu->num_repetitions);
LOG_I(NR_PHY,"srs_config_pdu->time_start_position = %u\n", srs_config_pdu->time_start_position);
LOG_I(NR_PHY,"srs_config_pdu->config_index = %u\n", srs_config_pdu->config_index);
LOG_I(NR_PHY,"srs_config_pdu->sequence_id = %u\n", srs_config_pdu->sequence_id);
LOG_I(NR_PHY,"srs_config_pdu->bandwidth_index = %u\n", srs_config_pdu->bandwidth_index);
LOG_I(NR_PHY,"srs_config_pdu->comb_size = %u (0 = comb size 2, 1 = comb size 4, 2 = comb size 8)\n", srs_config_pdu->comb_size);
LOG_I(NR_PHY,"srs_config_pdu->comb_offset = %u\n", srs_config_pdu->comb_offset);
LOG_I(NR_PHY,"srs_config_pdu->cyclic_shift = %u\n", srs_config_pdu->cyclic_shift);
LOG_I(NR_PHY,"srs_config_pdu->frequency_position = %u\n", srs_config_pdu->frequency_position);
LOG_I(NR_PHY,"srs_config_pdu->frequency_shift = %u\n", srs_config_pdu->frequency_shift);
LOG_I(NR_PHY,"srs_config_pdu->frequency_hopping = %u\n", srs_config_pdu->frequency_hopping);
LOG_I(NR_PHY,"srs_config_pdu->group_or_sequence_hopping = %u (0 = No hopping, 1 = Group hopping groupOrSequenceHopping, 2 = Sequence hopping)\n", srs_config_pdu->group_or_sequence_hopping);
LOG_I(NR_PHY,"srs_config_pdu->resource_type = %u (0: aperiodic, 1: semi-persistent, 2: periodic)\n", srs_config_pdu->resource_type);
LOG_I(NR_PHY,"srs_config_pdu->t_srs = %u\n", srs_config_pdu->t_srs);
LOG_I(NR_PHY,"srs_config_pdu->t_offset = %u\n", srs_config_pdu->t_offset);
#endif
nr_srs_info_t nr_srs_info = {0};
configure_srs_info(srs_config_pdu, &nr_srs_info);
uint16_t symbol_offset = l0 * frame_parms->ofdm_symbol_size;
bool generated = generate_srs_nr(frame_parms,
txdataF,
symbol_offset,
srs_config_pdu->bwp_start,
&nr_srs_info,
AMP,
proc->frame_tx,
proc->nr_slot_tx,
frame_parms->nb_antennas_tx);
DevAssert(generated); // if we can't generate despite the SRS config, there
// is a problem
}
void phy_procedures_nrUE_TX(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_data_tx_t *phy_data, c16_t **txp)
{
const int slot_tx = proc->nr_slot_tx;
const int frame_tx = proc->frame_tx;
AssertFatal(ue->CC_id == 0, "Transmission on secondary CCs is not supported yet\n");
#if T_TRACER
T(T_UE_PHY_UL_TICK, T_INT(ue->Mod_id), T_INT(frame_tx % 1024), T_INT(slot_tx));
#endif
const int samplesF_per_slot = ue->frame_parms.symbols_per_slot * ue->frame_parms.ofdm_symbol_size;
c16_t txdataF_buf[ue->frame_parms.nb_antennas_tx * samplesF_per_slot] __attribute__((aligned(32)));
memset(txdataF_buf, 0, sizeof(txdataF_buf));
c16_t *txdataF[ue->frame_parms.nb_antennas_tx]; /* workaround to be compatible with current txdataF usage in all tx procedures. */
for(int i=0; i< ue->frame_parms.nb_antennas_tx; ++i)
txdataF[i] = &txdataF_buf[i * samplesF_per_slot];
LOG_D(PHY,"****** start TX-Chain for AbsSubframe %d.%d ******\n", frame_tx, slot_tx);
bool was_symbol_used[NR_SYMBOLS_PER_SLOT] = {0};
start_meas_nr_ue_phy(ue, PHY_PROC_TX);
nr_ue_ulsch_procedures(ue, frame_tx, slot_tx, phy_data, (c16_t **)&txdataF, was_symbol_used);
if (phy_data->srs_vars.active)
ue_srs_procedures_nr(ue, proc, (c16_t **)&txdataF, &phy_data->srs_vars.srs_config_pdu, was_symbol_used);
pucch_procedures_ue_nr(ue, proc, phy_data, (c16_t **)&txdataF, was_symbol_used);
LOG_D(PHY, "Sending Uplink data \n");
// Don't do OFDM Mod if txdata contains prach
const NR_UE_PRACH *prach_var = ue->prach_vars[proc->gNB_id];
if (!prach_var->active) {
start_meas_nr_ue_phy(ue, OFDM_MOD_STATS);
nr_tx_rotation_and_ofdm_mod(proc->nr_slot_tx,
&ue->frame_parms,
ue->frame_parms.nb_antennas_tx,
(c16_t **)txdataF,
txp,
link_type_ul,
was_symbol_used,
ue->no_phase_pre_comp);
stop_meas_nr_ue_phy(ue, OFDM_MOD_STATS);
}
nr_ue_prach_procedures(ue, proc, txp);
LOG_D(PHY, "****** end TX-Chain for AbsSubframe %d.%d ******\n", proc->frame_tx, proc->nr_slot_tx);
stop_meas_nr_ue_phy(ue, PHY_PROC_TX);
}
static void nr_ue_measurement_procedures(uint16_t l,
PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
int number_rbs,
uint32_t pdsch_est_size,
int32_t dl_ch_estimates[][pdsch_est_size])
{
NR_DL_FRAME_PARMS *frame_parms=&ue->frame_parms;
int nr_slot_rx = proc->nr_slot_rx;
int gNB_id = proc->gNB_id;
if (l == 2) {
LOG_D(PHY,"Doing UE measurement procedures in symbol l %u Ncp %d nr_slot_rx %d, rxdata %p\n",
l,
ue->frame_parms.Ncp,
nr_slot_rx,
ue->common_vars.rxdata);
nr_ue_measurements(ue, proc, number_rbs, pdsch_est_size, dl_ch_estimates);
#if T_TRACER
if(nr_slot_rx == 0)
T(T_UE_PHY_MEAS,
T_INT(gNB_id),
T_INT(proc->frame_rx % 1024),
T_INT(nr_slot_rx),
T_INT((int)(10 * log10(ue->measurements.rsrp[0]) - ue->rx_total_gain_dB)),
T_INT((int)ue->measurements.rx_rssi_dBm[0]),
T_INT((int)(ue->measurements.rx_power_avg_dB[0] - ue->measurements.n0_power_avg_dB)),
T_INT((int)ue->measurements.rx_power_avg_dB[0]),
T_INT((int)ue->measurements.n0_power_avg_dB),
T_INT((int)ue->measurements.wideband_cqi_avg[0]),
T_INT((int)ue->common_vars.freq_offset));
#endif
}
// accumulate and filter timing offset estimation every subframe (instead of every frame)
if (( nr_slot_rx == 2) && (l==(2-frame_parms->Ncp))) {
// AGC
//printf("start adjust gain power avg db %d\n", ue->measurements.rx_power_avg_dB[gNB_id]);
phy_adjust_gain_nr (ue,ue->measurements.rx_power_avg_dB[gNB_id],gNB_id);
}
}
static int nr_ue_pbch_procedures(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
int estimateSz,
struct complex16 dl_ch_estimates[][estimateSz],
c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP])
{
TracyCZone(ctx, true);
int ret = 0;
DevAssert(ue);
int frame_rx = proc->frame_rx;
int nr_slot_rx = proc->nr_slot_rx;
int gNB_id = proc->gNB_id;
LOG_D(PHY,"[UE %d] Frame %d Slot %d, Trying PBCH (NidCell %d, gNB_id %d)\n",ue->Mod_id,frame_rx,nr_slot_rx,ue->frame_parms.Nid_cell,gNB_id);
fapiPbch_t result;
int hf_frame_bit, ssb_index, symb_offset;
ret = nr_rx_pbch(ue,
proc,
ue->is_synchronized,
estimateSz,
dl_ch_estimates,
&ue->frame_parms,
(ue->frame_parms.ssb_index) & 7,
ue->frame_parms.ssb_start_subcarrier,
ue->frame_parms.Nid_cell,
&result,
&hf_frame_bit,
&ssb_index,
&symb_offset,
ue->frame_parms.samples_per_frame_wCP,
rxdataF);
if (ret==0) {
T(T_NRUE_PHY_MIB, T_INT(frame_rx), T_INT(nr_slot_rx),
T_INT(ssb_index), T_BUFFER(result.decoded_output, 3));
#ifdef DEBUG_PHY_PROC
LOG_D(PHY,
"[UE %d] frame %d, nr_slot_rx %d, Received PBCH (MIB): ssb idx: %d, N_RB_DL %d\n",
ue->Mod_id,
frame_rx,
nr_slot_rx,
ssb_index,
ue->frame_parms.N_RB_DL);
#endif
} else {
LOG_E(PHY, "[UE %d] frame %d, nr_slot_rx %d, Error decoding PBCH!\n", ue->Mod_id, frame_rx, nr_slot_rx);
}
TracyCZoneEnd(ctx);
return ret;
}
static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t *dlsch,
NR_DL_UE_HARQ_t *dlsch_harq,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlschCfg,
int16_t *llr,
c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP],
int G,
freq_alloc_bitmap_t *freq_alloc)
{
int frame_rx = proc->frame_rx;
int nr_slot_rx = proc->nr_slot_rx;
if (dlsch->cw_info.Nl < 1 || dlsch->cw_info.Nl > NR_MAX_NB_LAYERS) {
LOG_E(NR_PHY, "Invalid PDSCH layer count %u\n", dlsch->cw_info.Nl);
return -1;
}
if (dlsch->cw_info.Nl > ue->frame_parms.nb_antennas_rx) {
LOG_E(NR_PHY,
"Cannot detect rank-%u PDSCH with only %u UE RX antennas\n",
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx);
return -1;
}
if (dlsch->cw_info.Nl > 4 && (dlschCfg->pduBitmap & 0x1)) {
LOG_E(NR_PHY, "PTRS is not supported by the rank-5--8 joint PDSCH detector\n");
return -1;
}
int harq_pid = dlschCfg->harq_process_nbr;
/* These workspaces are several tens of megabytes at rank 8. Allocating and
* zeroing them for every PDSCH made the UE miss its HARQ-feedback slot even
* when the cheap permutation-diagonal detector was selected. Each DL actor
* owns one persistent thread, so thread-local storage is race-free and
* preserves the legacy zero-initialized first use. Every active RE is
* overwritten by channel estimation/equalization before it is consumed. */
static _Thread_local nr_ue_reusable_array_t pdsch_dl_ch_estimates_storage;
static _Thread_local nr_ue_reusable_array_t rxdataF_comp_storage;
static _Thread_local nr_ue_reusable_array_t dl_ch_mag_storage;
static _Thread_local nr_ue_reusable_array_t dl_ch_magb_storage;
static _Thread_local nr_ue_reusable_array_t dl_ch_magr_storage;
LOG_D(PHY,
"[UE %d] frame_rx %d, nr_slot_rx %d, harq_pid %d (%d), BWP start %d, start RB %d, end RB %d, symbol_start %d, nb_symbols "
"%d, DMRS mask "
"%x, Nl %d\n",
ue->Mod_id,
frame_rx,
nr_slot_rx,
harq_pid,
dlsch_harq->status,
dlschCfg->BWPStart,
freq_alloc->first_rb,
freq_alloc->last_rb,
dlschCfg->start_symbol,
dlschCfg->number_symbols,
dlschCfg->dlDmrsSymbPos,
dlsch->cw_info.Nl);
const uint32_t pdsch_est_size = ((ue->frame_parms.symbols_per_slot * ue->frame_parms.ofdm_symbol_size + 15) / 16) * 16;
fourDimArray_t *pdsch_dl_ch_estimates_view =
nr_ue_reuse_array(&pdsch_dl_ch_estimates_storage,
sizeof(int32_t),
ue->frame_parms.nb_antennas_rx * dlsch->cw_info.Nl,
pdsch_est_size,
0,
0);
cast2Darray(pdsch_dl_ch_estimates, int32_t, pdsch_dl_ch_estimates_view);
c16_t ptrs_phase_per_slot[ue->frame_parms.nb_antennas_rx][NR_SYMBOLS_PER_SLOT];
memset(ptrs_phase_per_slot, 0, sizeof(ptrs_phase_per_slot));
int32_t ptrs_re_per_slot[ue->frame_parms.nb_antennas_rx][NR_SYMBOLS_PER_SLOT];
memset(ptrs_re_per_slot, 0, sizeof(ptrs_re_per_slot));
const uint32_t rx_size_symbol = (freq_alloc->num_rbs * NR_NB_SC_PER_RB + 15) & ~15;
fourDimArray_t *rxdataF_comp_view = nr_ue_reuse_array(&rxdataF_comp_storage,
sizeof(c16_t),
ue->frame_parms.symbols_per_slot,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(rxdataF_comp, c16_t, rxdataF_comp_view);
uint32_t nvar = 0;
start_meas_nr_ue_phy(ue, DLSCH_CHANNEL_ESTIMATION_STATS);
for (int m = dlschCfg->start_symbol; m < (dlschCfg->start_symbol + dlschCfg->number_symbols); m++) {
if (dlschCfg->dlDmrsSymbPos & (1 << m)) {
for (int nl = 0; nl < dlsch->cw_info.Nl; nl++) { // for MIMO Config: it shall loop over no_layers
LOG_D(PHY, "PDSCH Channel estimation layer %d, slot %d, symbol %d\n", nl, nr_slot_rx, m);
uint32_t nvar_tmp = 0;
nr_pdsch_channel_estimation(ue,
proc,
dlschCfg,
freq_alloc,
nl,
get_dmrs_port(nl, dlschCfg->dmrs_ports),
m,
pdsch_est_size,
pdsch_dl_ch_estimates,
ue->frame_parms.samples_per_slot_wCP,
rxdataF,
&nvar_tmp);
nvar += nvar_tmp;
#if 0
///LOG_M: the channel estimation
char filename[100];
for (uint8_t aarx=0; aarx<ue->frame_parms.nb_antennas_rx; aarx++) {
sprintf(filename,"PDSCH_CHANNEL_frame%d_slot%d_sym%d_port%d_rx%d.m", frame_rx, nr_slot_rx, m, nl, aarx);
int **dl_ch_estimates = ue->pdsch_vars[gNB_id]->dl_ch_estimates;
LOG_M(filename,"channel_F",&dl_ch_estimates[nl*ue->frame_parms.nb_antennas_rx+aarx][ue->frame_parms.ofdm_symbol_size*m],ue->frame_parms.ofdm_symbol_size, 1, 1);
}
#endif
}
}
}
stop_meas_nr_ue_phy(ue, DLSCH_CHANNEL_ESTIMATION_STATS);
nvar /= (dlschCfg->number_symbols * dlsch->cw_info.Nl * ue->frame_parms.nb_antennas_rx);
nr_ue_measurement_procedures(2, ue, proc, freq_alloc->num_rbs, pdsch_est_size, pdsch_dl_ch_estimates);
if (ue->chest_time == 1 || dlsch->cw_info.Nl > 4) {
/* Rank 5--8 uses maxLength=len2 DMRS. Ports 1004--1007 share
* frequency-domain OCC with ports 1000--1003 and are separated by the
* time-domain OCC across the two front-loaded symbols, so averaging is
* mandatory. Apply it to every layer, not only the first RX block. */
for (int layer = 0; layer < dlsch->cw_info.Nl; layer++) {
int32_t *layer_ch_estimates[ue->frame_parms.nb_antennas_rx];
for (int aarx = 0; aarx < ue->frame_parms.nb_antennas_rx; aarx++)
layer_ch_estimates[aarx] = pdsch_dl_ch_estimates[layer * ue->frame_parms.nb_antennas_rx + aarx];
nr_chest_time_domain_avg(&ue->frame_parms,
layer_ch_estimates,
dlschCfg->number_symbols,
dlschCfg->start_symbol,
dlschCfg->dlDmrsSymbPos,
freq_alloc->num_rbs);
}
}
uint16_t first_symbol_with_data = dlschCfg->start_symbol;
uint32_t dmrs_data_re;
if (dlschCfg->dmrsConfigType == NFAPI_NR_DMRS_TYPE1)
dmrs_data_re = 12 - 6 * dlschCfg->n_dmrs_cdm_groups;
else
dmrs_data_re = 12 - 4 * dlschCfg->n_dmrs_cdm_groups;
while ((dmrs_data_re == 0) && (dlschCfg->dlDmrsSymbPos & (1 << first_symbol_with_data))) {
first_symbol_with_data++;
}
uint32_t dl_valid_re[NR_SYMBOLS_PER_SLOT] = {0};
int32_t log2_maxh = 0;
start_meas_nr_ue_phy(ue, RX_PDSCH_STATS);
pdsch_scope_req_t scope_req = {.copy_chanest_to_scope = false, .copy_rxdataF_to_scope = false, .scope_rxdataF_offset = 0};
if (UEScopeHasTryLock(ue)) {
metadata mt = {.frame = proc->frame_rx, .slot = proc->nr_slot_rx};
scope_req.copy_chanest_to_scope = UETryLockScopeData(ue,
pdschChanEstimates,
sizeof(c16_t),
1,
freq_alloc->num_rbs * NR_NB_SC_PER_RB * dlschCfg->number_symbols,
&mt);
scope_req.copy_rxdataF_to_scope = UETryLockScopeData(ue,
pdschRxdataF,
sizeof(c16_t),
1,
freq_alloc->num_rbs * NR_NB_SC_PER_RB * dlschCfg->number_symbols,
&mt);
}
fourDimArray_t *dl_ch_mag_view = nr_ue_reuse_array(&dl_ch_mag_storage,
sizeof(c16_t),
NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(dl_ch_mag, c16_t, dl_ch_mag_view);
fourDimArray_t *dl_ch_magb_view = nr_ue_reuse_array(&dl_ch_magb_storage,
sizeof(c16_t),
NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(dl_ch_magb, c16_t, dl_ch_magb_view);
fourDimArray_t *dl_ch_magr_view = nr_ue_reuse_array(&dl_ch_magr_storage,
sizeof(c16_t),
NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(dl_ch_magr, c16_t, dl_ch_magr_view);
int pdsch_status = 0;
for (int m = dlschCfg->start_symbol; m < (dlschCfg->number_symbols + dlschCfg->start_symbol); m++) {
bool first_symbol_flag = false;
if (m == first_symbol_with_data)
first_symbol_flag = true;
// process DLSCH received symbols in the slot
// symbol by symbol processing (if data/DMRS are multiplexed is checked inside the function)
if (nr_rx_pdsch(ue,
proc,
dlsch,
freq_alloc,
dlschCfg,
dlsch_harq,
m,
first_symbol_flag,
harq_pid,
pdsch_est_size,
pdsch_dl_ch_estimates,
llr,
dl_valid_re,
rxdataF,
&log2_maxh,
rx_size_symbol,
ue->frame_parms.nb_antennas_rx,
rxdataF_comp,
dl_ch_mag,
dl_ch_magb,
dl_ch_magr,
ptrs_phase_per_slot,
ptrs_re_per_slot,
G,
nvar,
&scope_req)
< 0) {
pdsch_status = -1;
break;
}
} // CRNTI active
stop_meas_nr_ue_phy(ue, RX_PDSCH_STATS);
if (scope_req.copy_chanest_to_scope) {
UEunlockScopeData(ue, pdschChanEstimates);
}
if (scope_req.copy_rxdataF_to_scope) {
UEunlockScopeData(ue, pdschRxdataF);
}
return pdsch_status;
}
static uint32_t compute_csi_rm_unav_res(fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config)
{
uint32_t unav_res = 0;
for (int i = 0; i < dlsch_config->numCsiRsForRateMatching; i++) {
fapi_nr_dl_config_csirs_pdu_rel15_t *csi_pdu = &dlsch_config->csiRsForRateMatching[i];
// check overlapping symbols
int num_overlap_symb = 0;
// num of consecutive csi symbols from l0 included
int num_l0 [18] = {1, 1, 1, 1, 2, 1, 2, 2, 1, 2, 2, 2, 2, 2, 4, 2, 2, 4};
int num_symb = num_l0[csi_pdu->row - 1];
for (int s = 0; s < num_symb; s++) {
int l0_symb = csi_pdu->symb_l0 + s;
if (l0_symb >= dlsch_config->start_symbol && l0_symb < dlsch_config->start_symbol + dlsch_config->number_symbols)
num_overlap_symb++;
}
// check also l1 if relevant
if (csi_pdu->row == 13 || csi_pdu->row == 14 || csi_pdu->row == 16 || csi_pdu->row == 17) {
num_symb += 2;
for (int s = 0; s < 2; s++) { // two consecutive symbols including l1
int l1_symb = csi_pdu->symb_l1 + s;
if (l1_symb >= dlsch_config->start_symbol && l1_symb < dlsch_config->start_symbol + dlsch_config->number_symbols)
num_overlap_symb++;
}
}
if (num_overlap_symb == 0)
continue;
// check number overlapping prbs
// assuming CSI is spanning the whole BW
AssertFatal(dlsch_config->BWPSize <= csi_pdu->nr_of_rbs, "Assuming CSI-RS is spanning the whold BWP this shouldn't happen\n");
int dlsch_start = dlsch_config->start_rb + dlsch_config->BWPStart;
int num_overlapping_prbs = dlsch_config->number_rbs;
if (num_overlapping_prbs < 1)
continue; // no overlapping prbs
if (csi_pdu->freq_density < 2) { // 0.5 density
num_overlapping_prbs /= 2;
// odd number of prbs and the start PRB is even/odd when CSI is in even/odd PRBs
if ((num_overlapping_prbs % 2) && ((dlsch_start % 2) == csi_pdu->freq_density))
num_overlapping_prbs += 1;
}
// density is number or res per port per rb (over all symbols)
int ports [18] = {1, 1, 2, 4, 4, 8, 8, 8, 12, 12, 16, 16, 24, 24, 24, 32, 32, 32};
int num_csi_res_per_prb = csi_pdu->freq_density == 3 ? 3 : 1;
num_csi_res_per_prb *= ports[csi_pdu->row - 1];
unav_res += num_overlapping_prbs * num_csi_res_per_prb * num_overlap_symb / num_symb;
}
return unav_res;
}
/*! \brief Process the whole DLSCH slot
*/
static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t *dlsch,
int cw_idx,
int G,
freq_alloc_bitmap_t *freq_alloc,
fapi_nr_dl_config_dlsch_pdu_rel15_t *config,
int16_t *llr)
{
if (dlsch->active == false) {
LOG_E(PHY, "DLSCH should be active when calling this function\n");
return;
}
int harq_pid = config->harq_process_nbr;
int frame_rx = proc->frame_rx;
int nr_slot_rx = proc->nr_slot_rx;
nr_downlink_indication_t dl_indication;
fapi_nr_rx_indication_t rx_ind = {0};
uint16_t number_pdus = 1;
LOG_D(PHY, "AbsSubframe %d.%d Start LDPC Decoder for CW%d [harq_pid %d]\n", frame_rx % 1024, nr_slot_rx, cw_idx, harq_pid);
// exit dlsch procedures as there are no active dlsch
NR_DL_UE_HARQ_t *dl_harq = &ue->dl_harq_processes[cw_idx][harq_pid];
if (dl_harq->status != NR_ACTIVE) {
// The slot-level caller releases the RX/TX barrier after all codewords.
LOG_E(NR_PHY, "Internal error nr_ue_dlsch_procedure() called but no active cw on slot %d, harq %d\n", nr_slot_rx, harq_pid);
return;
}
start_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
nr_dlsch_unscrambling(llr, G, cw_idx, config->dlDataScramblingId, dlsch->rnti);
stop_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
start_meas_nr_ue_phy(ue, DLSCH_DECODING_STATS);
nr_dlsch_decoding(ue, proc, dlsch, cw_idx, config, llr, dl_harq->b, freq_alloc->num_rbs, G);
stop_meas_nr_ue_phy(ue, DLSCH_DECODING_STATS);
int ind_type = -1;
switch (dlsch->rnti_type) {
case TYPE_RA_RNTI_:
ind_type = FAPI_NR_RX_PDU_TYPE_RAR;
break;
case TYPE_SI_RNTI_:
ind_type = FAPI_NR_RX_PDU_TYPE_SIB;
break;
case TYPE_C_RNTI_:
ind_type = FAPI_NR_RX_PDU_TYPE_DLSCH;
break;
default:
AssertFatal(false, "Invalid DLSCH type %d\n", dlsch->rnti_type);
break;
}
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, ind_type, ue, cw_idx, harq_pid, dlsch, number_pdus, proc, NULL, dl_harq->b);
LOG_D(PHY, "DL PDU length in bits: %d, in bytes: %d \n", dlsch->cw_info.TBS, dlsch->cw_info.TBS / 8);
if (cpumeas(CPUMEAS_GETSTATE)) {
LOG_D(PHY,
" --> Unscrambling %5.3f\n",
ue->phy_cpu_stats.cpu_time_stats[DLSCH_UNSCRAMBLING_STATS].p_time / (cpuf * 1000.0));
LOG_D(PHY,
"AbsSubframe %d.%d --> LDPC Decoding %5.3f\n",
frame_rx % 1024,
nr_slot_rx,
ue->phy_cpu_stats.cpu_time_stats[DLSCH_DECODING_STATS].p_time / (cpuf * 1000.0));
}
// send to mac
if (ue->if_inst && ue->if_inst->dl_indication) {
ue->if_inst->dl_indication(&dl_indication);
}
int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * NR_MAX_NB_LAYERS; // number of segments to be allocated
if (freq_alloc->num_rbs != 273) {
a_segments = a_segments * freq_alloc->num_rbs;
a_segments = (a_segments / 273) + 1;
}
uint32_t dlsch_bytes = a_segments * 1056; // allocated bytes per segment
if (ue->phy_sim_dlsch_b)
memcpy(ue->phy_sim_dlsch_b + cw_idx * dlsch_bytes, dl_harq->b, dlsch_bytes);
}
static bool check_meas_to_perform(PHY_VARS_NR_UE *ue, int nr_slot_rx)
{
if (nr_slot_rx != 0) {
return false;
}
if (ue->measurements.meas_request_pending == true) {
return false;
}
// Check if any intra-frequency neighbor cells are configured
uint32_t serving_ssb_freq = get_ssb_arfcn(&ue->frame_parms);
bool has_intra_freq_neighbors = false;
for (int cell_idx = 0; cell_idx < NUMBER_OF_NEIGHBORING_CELLS_MAX; cell_idx++) {
fapi_nr_neighboring_cell_t *nr_neighboring_cell = &ue->nrUE_config.meas_config.nr_neighboring_cell[cell_idx];
if (nr_neighboring_cell->active == 1) {
if (nr_neighboring_cell->ssb_freq == 0 || nr_neighboring_cell->ssb_freq == serving_ssb_freq) {
has_intra_freq_neighbors = true;
}
}
}
return has_intra_freq_neighbors;
}
static bool is_ssb_index_transmitted(const PHY_VARS_NR_UE *ue, const int index)
{
if (ue->received_config_request) {
const fapi_nr_config_request_t *cfg = &ue->nrUE_config;
const uint32_t curr_mask = cfg->ssb_table.ssb_mask_list[index / 32].ssb_mask;
return ((curr_mask >> (31 - (index % 32))) & 0x01);
} else
return ue->frame_parms.ssb_index == index;
}
static int get_pdcch_max_rbs(NR_UE_PDCCH_CONFIG *phy_pdcch_config)
{
int nb_rb = 0;
int rb_offset = 0;
for (int i = 0; i < phy_pdcch_config->nb_search_space; i++) {
int tmp = 0;
get_coreset_rballoc(phy_pdcch_config->pdcch_config[i].coreset.frequency_domain_resource, &tmp, &rb_offset);
if (tmp > nb_rb)
nb_rb = tmp;
}
return nb_rb;
}
static int get_max_pdcch_symb(const NR_UE_PDCCH_CONFIG *phy_pdcch_config)
{
int max_pdcch_symb = 0;
for (int i = 0; i < phy_pdcch_config->nb_search_space; i++)
if (phy_pdcch_config->pdcch_config[i].coreset.duration > max_pdcch_symb)
max_pdcch_symb = phy_pdcch_config->pdcch_config[i].coreset.duration;
return max_pdcch_symb;
}
void pdcch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_data_t *phy_data)
{
NR_UE_PDCCH_CONFIG *phy_pdcch_config = &phy_data->phy_pdcch_config;
if (phy_pdcch_config->nb_search_space == 0)
return;
TracyCZone(ctx, true);
/* process PDCCH */
LOG_D(PHY, " ------ --> PDCCH ChannelComp/LLR Frame.slot %d.%d ------ \n", proc->frame_rx % 1024, proc->nr_slot_rx);
start_meas_nr_ue_phy(ue, DLSCH_RX_PDCCH_STATS);
NR_DL_FRAME_PARMS *fp = &ue->frame_parms;
int num_monitoring_occ = get_max_pdcch_monOcc(phy_pdcch_config, fp->symbols_per_slot);
int max_nb_symb_pdcch = get_max_pdcch_symb(phy_pdcch_config);
int llr_size_symbol = get_pdcch_max_rbs(phy_pdcch_config) * 9;
c16_t pdcch_llr[phy_pdcch_config->nb_search_space][num_monitoring_occ][max_nb_symb_pdcch * llr_size_symbol];
int start_symb_pdcch, last_symb_pdcch;
set_first_last_pdcch_symb(phy_pdcch_config, fp->symbols_per_slot, &start_symb_pdcch, &last_symb_pdcch);
/* Temporarily loop over symbols in the slot, perform OFDM demod and process PDCCH.
When symbol based proc design is fully merged, this function will be called to process only one symbol
and OFDM demod will be removed from here. */
const uint32_t rxdataF_sz = fp->samples_per_slot_wCP;
__attribute__((aligned(32))) c16_t rxdataF[fp->nb_antennas_rx][rxdataF_sz];
for (int symbol = start_symb_pdcch; symbol <= last_symb_pdcch; symbol++) {
nr_slot_fep(ue, fp, proc->nr_slot_rx, symbol, rxdataF, link_type_dl, 0, ue->common_vars.rxdata);
__attribute__((aligned(32))) c16_t rxdataF_symb[fp->nb_antennas_rx][((fp->ofdm_symbol_size + 7) / 8) * 8];
for (int ant = 0; ant < fp->nb_antennas_rx; ant++)
memcpy(rxdataF_symb[ant], &rxdataF[ant][symbol * fp->ofdm_symbol_size], sizeof(c16_t) * fp->ofdm_symbol_size);
nr_pdcch_generate_llr(ue, proc, symbol, phy_data, llr_size_symbol, num_monitoring_occ, max_nb_symb_pdcch, rxdataF_symb, pdcch_llr);
if (symbol == last_symb_pdcch) {
nr_pdcch_dci_indication(proc, llr_size_symbol * max_nb_symb_pdcch, num_monitoring_occ, ue, phy_data, pdcch_llr);
UEscopeCopy(ue, pdcchLlr, pdcch_llr, sizeof(c16_t), 1, sizeof(pdcch_llr) / sizeof(c16_t), 0);
}
}
stop_meas_nr_ue_phy(ue, DLSCH_RX_PDCCH_STATS);
TracyCZoneEnd(ctx);
if (ue->phy_sim_rxdataF)
memcpy(ue->phy_sim_rxdataF, rxdataF[0], sizeof(int32_t) * max_nb_symb_pdcch * fp->ofdm_symbol_size);
}
int pbch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_data_t *phy_data)
{
TracyCZone(ctx, true);
int frame_rx = proc->frame_rx;
int nr_slot_rx = proc->nr_slot_rx;
NR_DL_FRAME_PARMS *fp = &ue->frame_parms;
int sampleShift = INT_MAX;
nr_ue_dlsch_init(phy_data->dlsch, NR_MAX_NB_LAYERS>4 ? 2:1, ue->max_ldpc_iterations);
#if T_TRACER
T(T_UE_PHY_DL_TICK, T_INT(ue->Mod_id), T_INT(frame_rx % 1024), T_INT(nr_slot_rx));
#endif
LOG_D(PHY," ****** start RX-Chain for Frame.Slot %d.%d ****** \n",
frame_rx%1024, nr_slot_rx);
const uint32_t rxdataF_sz = ue->frame_parms.samples_per_slot_wCP;
__attribute__ ((aligned(32))) c16_t rxdataF[ue->frame_parms.nb_antennas_rx][rxdataF_sz];
// checking if current frame is compatible with SSB periodicity
const int default_ssb_period = 2;
const int ssb_period = ue->received_config_request ? ue->nrUE_config.ssb_table.ssb_period : default_ssb_period;
if (ssb_period == 0 || !(frame_rx % (1 << (ssb_period - 1)))) {
const int estimateSz = fp->symbols_per_slot * fp->ofdm_symbol_size;
// loop over SSB blocks
for (int ssb_index = 0; ssb_index < fp->Lmax; ssb_index++) {
// check if current SSB is transmitted
if (is_ssb_index_transmitted(ue, ssb_index)) {
int ssb_start_symbol = nr_get_ssb_start_symbol(fp, ssb_index);
int ssb_slot = ssb_start_symbol/fp->symbols_per_slot;
int ssb_slot_2 = (ssb_period == 0) ? ssb_slot + (fp->slots_per_frame >> 1) : -1;
if (ssb_slot == nr_slot_rx || ssb_slot_2 == nr_slot_rx) {
LOG_D(PHY," ------ PBCH ChannelComp/LLR: frame.slot %d.%d ------ \n", frame_rx%1024, nr_slot_rx);
__attribute__ ((aligned(32))) struct complex16 dl_ch_estimates[fp->nb_antennas_rx][estimateSz];
__attribute__ ((aligned(32))) struct complex16 dl_ch_estimates_time[fp->nb_antennas_rx][fp->ofdm_symbol_size];
for (int i=1; i<4; i++) {
nr_slot_fep(ue,
fp,
proc->nr_slot_rx,
(ssb_start_symbol + i) % (fp->symbols_per_slot),
rxdataF,
link_type_dl,
0,
ue->common_vars.rxdata);
nr_pbch_channel_estimation(&ue->frame_parms,
NULL,
estimateSz,
dl_ch_estimates,
dl_ch_estimates_time,
proc,
(ssb_start_symbol + i) % (fp->symbols_per_slot),
i - 1,
ssb_index & 7,
ssb_slot_2 == nr_slot_rx,
fp->ssb_start_subcarrier,
rxdataF,
false,
fp->Nid_cell);
if (i - 1 == 2)
UEscopeCopy(ue,
pbchDlChEstimateTime,
(void *)dl_ch_estimates_time,
sizeof(c16_t),
fp->nb_antennas_rx,
fp->ofdm_symbol_size,
0);
}
nr_ue_ssb_rsrp_measurements(ue, ssb_index, proc, rxdataF);
// resetting ssb index for PBCH detection if there is a stronger SSB index
if(ue->measurements.ssb_rsrp_dBm[ssb_index] > ue->measurements.ssb_rsrp_dBm[fp->ssb_index]) {
fp->ssb_index = ssb_index;
LOG_D(PHY, "New best SSB: index %d RSRP %d\n", ssb_index, ue->measurements.ssb_rsrp_dBm[ssb_index]);
}
if(ssb_index != fp->ssb_index)
continue;
LOG_D(PHY, " ------ Decode MIB: frame.slot %d.%d ------ \n", frame_rx % 1024, nr_slot_rx);
const int pbchSuccess = nr_ue_pbch_procedures(ue, proc, estimateSz, dl_ch_estimates, rxdataF);
if (ue->no_timing_correction == 0 && pbchSuccess == 0) {
LOG_D(PHY,"start adjust sync slot = %d no timing %d\n", nr_slot_rx, ue->no_timing_correction);
sampleShift =
nr_adjust_synch_ue(fp, ue, fp->ofdm_symbol_size, dl_ch_estimates_time, frame_rx, nr_slot_rx, 16384);
}
if (get_nrUE_params()->cont_fo_comp && pbchSuccess == 0) {
double freq_offset = nr_ue_pbch_freq_offset(fp, estimateSz, dl_ch_estimates);
LOG_D(PHY,
"compensated freq offset = %.3f Hz, detected residual freq offset = %.3f Hz, accumulated freq offset = %.3f Hz\n",
ue->freq_offset,
freq_offset,
ue->freq_off_acc);
// PI controller
const double PID_P = get_nrUE_params()->freq_sync_P;
const double PID_I = get_nrUE_params()->freq_sync_I;
ue->freq_offset += freq_offset * PID_P + ue->freq_off_acc * PID_I;
ue->freq_off_acc += freq_offset;
}
LOG_D(PHY, "Doing N0 measurements in %s\n", __FUNCTION__);
nr_ue_rrc_measurements(ue, proc, rxdataF);
}
}
}
}
// Check for PRS slot - section 7.4.1.7.4 in 3GPP rel16 38.211
for(int gNB_id = 0; gNB_id < ue->prs_active_gNBs; gNB_id++)
{
for(int rsc_id = 0; rsc_id < ue->prs_vars[gNB_id]->NumPRSResources; rsc_id++)
{
prs_config_t *prs_config = &ue->prs_vars[gNB_id]->prs_resource[rsc_id].prs_cfg;
for (int i = 0; i < prs_config->PRSResourceRepetition; i++)
{
if( (((frame_rx*fp->slots_per_frame + nr_slot_rx) - (prs_config->PRSResourceSetPeriod[1] + prs_config->PRSResourceOffset) + prs_config->PRSResourceSetPeriod[0])%prs_config->PRSResourceSetPeriod[0]) == i*prs_config->PRSResourceTimeGap)
{
for(int j = prs_config->SymbolStart; j < (prs_config->SymbolStart+prs_config->NumPRSSymbols); j++)
{
nr_slot_fep(ue, fp, proc->nr_slot_rx, (j % fp->symbols_per_slot), rxdataF, link_type_dl, 0, ue->common_vars.rxdata);
}
nr_prs_channel_estimation(gNB_id, rsc_id, i, ue, proc, fp, rxdataF);
}
} // for i
} // for rsc_id
} // for gNB_id
PHY_NR_MEASUREMENTS *measurements = &ue->measurements;
if (check_meas_to_perform(ue, nr_slot_rx)) {
measurements->meas_request_pending = true;
// Copy rxdata
uint32_t rxdata_size = (2 * (fp->samples_per_frame) + fp->ofdm_symbol_size);
size_t total_size = sizeof(nr_meas_task_args_t) + fp->nb_antennas_rx * rxdata_size * sizeof(c16_t);
nr_meas_task_args_t *args = malloc_or_fail(total_size);
args->proc = *proc;
args->ue = ue;
args->rxdata_size = rxdata_size;
args->rxdata = malloc_or_fail(fp->nb_antennas_rx * sizeof(*args->rxdata));
for (int i = 0; i < fp->nb_antennas_rx; i++) {
args->rxdata[i] = &args->rxdata_ant[i * rxdata_size];
memcpy(args->rxdata[i], ue->common_vars.rxdata[i], rxdata_size * sizeof(c16_t));
}
task_t t = {.func = nr_ue_meas_neighboring_cell, .args = args};
pushTpool(&get_nrUE_params()->Tpool, t);
}
TracyCZoneEnd(ctx);
return sampleShift;
}
void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_data_t *phy_data)
{
int frame_rx = proc->frame_rx;
int nr_slot_rx = proc->nr_slot_rx;
int gNB_id = proc->gNB_id;
// do procedures for C-RNTI
bool slot_fep_map[14] = {0};
const uint32_t rxdataF_sz = ue->frame_parms.samples_per_slot_wCP;
__attribute__ ((aligned(32))) c16_t rxdataF[ue->frame_parms.nb_antennas_rx][rxdataF_sz];
// do procedures for CSI-IM
if (phy_data->csiim_vars.active == 1) {
for(int symb_idx = 0; symb_idx < 4; symb_idx++) {
int symb = phy_data->csiim_vars.csiim_config_pdu.l_csiim[symb_idx];
if (!slot_fep_map[symb]) {
nr_slot_fep(ue, &ue->frame_parms, proc->nr_slot_rx, symb, rxdataF, link_type_dl, 0, ue->common_vars.rxdata);
slot_fep_map[symb] = true;
}
}
nr_ue_csi_im_procedures(ue, rxdataF, &phy_data->csiim_vars.csiim_config_pdu);
}
// do procedures for CSI-RS
if (phy_data->csirs_vars.active == 1) {
for(int symb = 0; symb < ue->frame_parms.symbols_per_slot; symb++) {
if(is_csi_rs_in_symbol(phy_data->csirs_vars.csirs_config_pdu, symb)) {
if (!slot_fep_map[symb]) {
nr_slot_fep(ue, &ue->frame_parms, proc->nr_slot_rx, symb, rxdataF, link_type_dl, 0, ue->common_vars.rxdata);
slot_fep_map[symb] = true;
}
}
}
nr_ue_csi_rs_procedures(ue, proc, rxdataF, &phy_data->csirs_vars.csirs_config_pdu);
}
int16_t *llr[NR_MAX_NB_CODEWORDS] = {0};
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &phy_data->dlsch_config;
AssertFatal(phy_data->n_dlsch_codewords >= 0 && phy_data->n_dlsch_codewords <= NR_MAX_NB_CODEWORDS,
"Invalid number of PDSCH codewords %d\n",
phy_data->n_dlsch_codewords);
bool has_active_dlsch = false;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++)
has_active_dlsch |= phy_data->dlsch[c].active;
if (has_active_dlsch) {
uint16_t nb_symb_sch = dlsch_config->number_symbols;
uint16_t start_symb_sch = dlsch_config->start_symbol;
LOG_D(PHY," ------ --> PDSCH ChannelComp/LLR Frame.slot %d.%d ------ \n", frame_rx % 1024, nr_slot_rx);
for (int m = start_symb_sch; m < (nb_symb_sch + start_symb_sch) ; m++) {
if (!slot_fep_map[m]) {
nr_slot_fep(ue, &ue->frame_parms, proc->nr_slot_rx, m, rxdataF, link_type_dl, 0, ue->common_vars.rxdata);
slot_fep_map[m] = true;
}
}
freq_alloc_bitmap_t freq_alloc = {0};
if (dlsch_config->resource_alloc == 0) {
int alloc_size = (dlsch_config->BWPSize / 8) + (dlsch_config->BWPSize % 8 > 0);
freq_alloc = set_start_end_from_bitmap(dlsch_config->BWPSize, alloc_size, dlsch_config->rb_bitmap);
} else
freq_alloc = set_bitmap_from_start_size(dlsch_config->start_rb, dlsch_config->number_rbs);
const uint8_t nb_re_dmrs = get_num_dmrs_re_per_rb(dlsch_config->dmrsConfigType, dlsch_config->n_dmrs_cdm_groups);
uint16_t dmrs_len = get_num_dmrs(dlsch_config->dlDmrsSymbPos);
uint32_t unav_res = 0;
if(dlsch_config->pduBitmap & 0x1) {
uint16_t ptrsSymbPos = 0;
set_ptrs_symb_idx(&ptrsSymbPos,
dlsch_config->number_symbols,
dlsch_config->start_symbol,
1 << dlsch_config->PTRSTimeDensity,
dlsch_config->dlDmrsSymbPos);
int n_ptrs = (freq_alloc.num_rbs + dlsch_config->PTRSFreqDensity - 1) / dlsch_config->PTRSFreqDensity;
int ptrsSymbPerSlot = get_ptrs_symbols_in_slot(ptrsSymbPos, dlsch_config->start_symbol, dlsch_config->number_symbols);
unav_res = n_ptrs * ptrsSymbPerSlot;
}
unav_res += compute_csi_rm_unav_res(dlsch_config);
int G[NR_MAX_NB_CODEWORDS] = {0};
uint32_t rx_llr_buf_sz[NR_MAX_NB_CODEWORDS] = {0};
static _Thread_local nr_ue_reusable_array_t llr_storage[NR_MAX_NB_CODEWORDS];
static _Thread_local nr_ue_reusable_array_t joint_llr_storage;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
G[c] = nr_get_G(freq_alloc.num_rbs,
dlsch_config->number_symbols,
nb_re_dmrs,
dmrs_len,
unav_res,
dlsch->cw_info.qamModOrder,
dlsch->cw_info.Nl);
rx_llr_buf_sz[c] = ALIGNARRAYSIZE(G[c], 32); // each LLR is 2 bytes hence 64 byte aligned
fourDimArray_t *llr_view =
nr_ue_reuse_array(&llr_storage[c], sizeof(int16_t), rx_llr_buf_sz[c], 0, 0, 0);
llr[c] = (int16_t *)llr_view->data;
}
int ret_pdsch = 0;
if (phy_data->n_dlsch_codewords == 2) {
NR_UE_DLSCH_t *cw0 = &phy_data->dlsch[0];
NR_UE_DLSCH_t *cw1 = &phy_data->dlsch[1];
const int total_layers = cw0->cw_info.Nl + cw1->cw_info.Nl;
if (total_layers <= 4 || total_layers > NR_MAX_NB_LAYERS) {
LOG_E(NR_PHY, "Two-codeword PDSCH has invalid aggregate rank %d\n", total_layers);
ret_pdsch = -1;
} else if (cw0->cw_info.qamModOrder != cw1->cw_info.qamModOrder) {
/* The joint detector is modulation-independent, but the current slot
* LLR layout uses one modulation order. The local gNB scheduler sends
* the same MCS on both TBs; reject unsupported external FAPI input
* explicitly rather than silently corrupting CW1. */
LOG_E(NR_PHY,
"Rank-%d PDSCH with different modulation orders per codeword (%u/%u) is not supported\n",
total_layers,
cw0->cw_info.qamModOrder,
cw1->cw_info.qamModOrder);
ret_pdsch = -1;
} else {
NR_UE_DLSCH_t joint_dlsch = *cw0;
joint_dlsch.active = true;
joint_dlsch.cw_info.Nl = total_layers;
const int joint_G = G[0] + G[1];
const uint32_t joint_buf_sz = ALIGNARRAYSIZE(joint_G, 32);
fourDimArray_t *joint_llr_view =
nr_ue_reuse_array(&joint_llr_storage, sizeof(int16_t), joint_buf_sz, 0, 0, 0);
int16_t *joint_llr = (int16_t *)joint_llr_view->data;
NR_DL_UE_HARQ_t joint_harq = ue->dl_harq_processes[0][dlsch_config->harq_process_nbr];
joint_harq.status = NR_ACTIVE;
ret_pdsch = nr_ue_pdsch_procedures(
ue, proc, &joint_dlsch, &joint_harq, dlsch_config, joint_llr, rxdataF, joint_G, &freq_alloc);
if (ret_pdsch >= 0) {
const int qm = cw0->cw_info.qamModOrder;
const int joint_stride = total_layers * qm;
AssertFatal(joint_G % joint_stride == 0,
"Rank-%d PDSCH G=%d is not divisible by the layer stride %d\n",
total_layers,
joint_G,
joint_stride);
int cw_offset[NR_MAX_NB_CODEWORDS] = {0};
for (int re = 0; re < joint_G / joint_stride; re++) {
const int16_t *joint_re = joint_llr + re * joint_stride;
const int cw0_bits = cw0->cw_info.Nl * qm;
const int cw1_bits = cw1->cw_info.Nl * qm;
memcpy(llr[0] + cw_offset[0], joint_re, cw0_bits * sizeof(*joint_re));
memcpy(llr[1] + cw_offset[1], joint_re + cw0_bits, cw1_bits * sizeof(*joint_re));
cw_offset[0] += cw0_bits;
cw_offset[1] += cw1_bits;
}
AssertFatal(cw_offset[0] == G[0] && cw_offset[1] == G[1],
"Dual-codeword LLR split mismatch: %d/%d expected %d/%d\n",
cw_offset[0],
cw_offset[1],
G[0],
G[1]);
}
}
} else {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[0];
NR_DL_UE_HARQ_t *harq = &ue->dl_harq_processes[0][dlsch_config->harq_process_nbr];
ret_pdsch = nr_ue_pdsch_procedures(ue, proc, dlsch, harq, dlsch_config, llr[0], rxdataF, G[0], &freq_alloc);
}
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
TracyCPlot("pdsch mcs", dlsch->cw_info.mcs);
if (dlsch->active)
UEscopeCopy(ue, pdschLlr, llr[c], sizeof(int16_t), 1, G[c], 0);
LOG_D(PHY, "DLSCH CW%d data reception at nr_slot_rx: %d\n", c, nr_slot_rx);
start_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
if (ret_pdsch >= 0) {
if (dlsch->active)
nr_ue_dlsch_procedures(ue, proc, dlsch, c, G[c], &freq_alloc, dlsch_config, llr[c]);
} else if (dlsch->active) {
LOG_E(NR_PHY, "PDSCH demodulation failed for CW%d\n", c);
LOG_W(NR_PHY, "nr_ue_pdsch_procedures failed in slot %d\n", proc->nr_slot_rx);
}
stop_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
if (cpumeas(CPUMEAS_GETSTATE))
LOG_D(PHY,
"[SFN %d] Slot0 Slot1: Dlsch Proc %5.2f\n",
nr_slot_rx,
ue->phy_cpu_stats.cpu_time_stats[DLSCH_PROCEDURES_STATS].p_time / (cpuf * 1000.0));
}
/* One PDSCH occasion has one RX/TX synchronization point even when it
* carries two transport blocks. Joining this barrier in each codeword's
* decoder advances it twice: the second join waits for a TX participant
* in the next generation and deadlocks the UE after its first rank>4
* PDSCH. Publish both ACK/NACK results first, then release TX once. */
bool needs_ack_barrier = false;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
const NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
needs_ack_barrier |= dlsch->active
&& (dlsch->rnti_type == TYPE_C_RNTI_ || dlsch->rnti_type == TYPE_RA_RNTI_);
}
if (needs_ack_barrier && dlsch_config->k1_feedback) {
const int ack_nack_slot_and_frame =
proc->nr_slot_rx + dlsch_config->k1_feedback + proc->frame_rx * ue->frame_parms.slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
}
if (ue->phy_sim_rxdataF)
memcpy(ue->phy_sim_rxdataF + start_symb_sch * ue->frame_parms.ofdm_symbol_size * sizeof(c16_t),
&rxdataF[0][start_symb_sch * ue->frame_parms.ofdm_symbol_size],
sizeof(int32_t) * nb_symb_sch * ue->frame_parms.ofdm_symbol_size);
if (ue->phy_sim_pdsch_llr) {
int16_t *sim_llr = ue->phy_sim_pdsch_llr;
size_t llr_offset = 0;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
memcpy(sim_llr + llr_offset, llr[c], sizeof(*sim_llr) * G[c]);
llr_offset += G[c];
}
}
}
if (nr_slot_rx==9) {
if (frame_rx % 10 == 0) {
if ((ue->dlsch_received[gNB_id] - ue->dlsch_received_last[gNB_id]) != 0)
ue->dlsch_fer[gNB_id] = (100*(ue->dlsch_errors[gNB_id] - ue->dlsch_errors_last[gNB_id]))/(ue->dlsch_received[gNB_id] - ue->dlsch_received_last[gNB_id]);
ue->dlsch_errors_last[gNB_id] = ue->dlsch_errors[gNB_id];
ue->dlsch_received_last[gNB_id] = ue->dlsch_received[gNB_id];
}
ue->bitrate[gNB_id] = (ue->total_TBS[gNB_id] - ue->total_TBS_last[gNB_id])*100;
ue->total_TBS_last[gNB_id] = ue->total_TBS[gNB_id];
LOG_D(PHY,"[UE %d] Calculating bitrate Frame %d: total_TBS = %d, total_TBS_last = %d, bitrate %f kbits\n",
ue->Mod_id,frame_rx,ue->total_TBS[gNB_id],
ue->total_TBS_last[gNB_id],(float) ue->bitrate[gNB_id]/1000.0);
}
#ifdef EMOS
phy_procedures_emos_UE_RX(ue,slot,gNB_id);
#endif
LOG_D(PHY," ****** end RX-Chain for AbsSubframe %d.%d ****** \n", frame_rx%1024, nr_slot_rx);
UEscopeCopy(ue, commonRxdataF, rxdataF, sizeof(int32_t), ue->frame_parms.nb_antennas_rx, rxdataF_sz, 0);
}
// todo:
// - power control as per 38.213 ch 7.4
static void nr_ue_prach_procedures(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, c16_t **txData)
{
int gNB_id = proc->gNB_id;
int frame_tx = proc->frame_tx, nr_slot_tx = proc->nr_slot_tx, prach_power; // tx_amp
uint8_t mod_id = ue->Mod_id;
NR_UE_PRACH *prach_var = ue->prach_vars[gNB_id];
if (prach_var->active) {
fapi_nr_ul_config_prach_pdu *prach_pdu = &prach_var->prach_pdu;
// Generate PRACH in first slot. For L839, the following slots are also filled in this slot.
if (prach_pdu->prach_slot == nr_slot_tx) {
ue->tx_power_dBm[nr_slot_tx] = prach_pdu->prach_tx_power;
LOG_D(PHY,
"In %s: [UE %d][RAPROC][%d.%d]: Generating PRACH Msg1 (preamble %d, P0_PRACH %d)\n",
__FUNCTION__,
mod_id,
frame_tx,
nr_slot_tx,
prach_pdu->ra_PreambleIndex,
ue->tx_power_dBm[nr_slot_tx]);
prach_var->amp = AMP;
start_meas_nr_ue_phy(ue, PRACH_GEN_STATS);
prach_power = generate_nr_prach(ue, gNB_id, frame_tx, nr_slot_tx, txData);
stop_meas_nr_ue_phy(ue, PRACH_GEN_STATS);
if (cpumeas(CPUMEAS_GETSTATE)) {
LOG_D(PHY,
"[SFN %d.%d] PRACH Proc %5.2f\n",
proc->frame_tx,
proc->nr_slot_tx,
ue->phy_cpu_stats.cpu_time_stats[PRACH_GEN_STATS].p_time / (cpuf * 1000.0));
}
LOG_D(PHY,
"In %s: [UE %d][RAPROC][%d.%d]: Generated PRACH Msg1 (TX power PRACH %d dBm, digital power %d dBW (amp %d)\n",
__FUNCTION__,
mod_id,
frame_tx,
nr_slot_tx,
ue->tx_power_dBm[nr_slot_tx],
dB_fixed(prach_power),
ue->prach_vars[gNB_id]->amp);
// set duration of prach slots so we know when to skip OFDM modulation
const int prach_format = ue->prach_vars[gNB_id]->prach_pdu.prach_format;
const int prach_slots = (prach_format < 4) ? get_long_prach_dur(prach_format, ue->frame_parms.numerology_index) : 1;
prach_var->num_prach_slots = prach_slots;
}
// set as inactive in the last slot
prach_var->active = !(nr_slot_tx == (prach_pdu->prach_slot + prach_var->num_prach_slots - 1));
}
}