feat(nr): implement rank-8 PDSCH
This commit is contained in:
@@ -978,6 +978,8 @@ set(PHY_SRC_UE
|
|||||||
${OPENAIR1_DIR}/PHY/nr_phy_common/src/nr_phy_common_srs.c
|
${OPENAIR1_DIR}/PHY/nr_phy_common/src/nr_phy_common_srs.c
|
||||||
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_scrambling.c
|
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_scrambling.c
|
||||||
${OPENAIR1_DIR}/PHY/NR_REFSIG/refsig.c
|
${OPENAIR1_DIR}/PHY/NR_REFSIG/refsig.c
|
||||||
|
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_state.c
|
||||||
|
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_control.c
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PHY_NR_SRC
|
set(PHY_NR_SRC
|
||||||
|
|||||||
@@ -71,9 +71,28 @@ static inline const char *rnti_types(nr_rnti_type_t rr)
|
|||||||
#define MAX_GSCN_BAND 620 // n78 has the highest GSCN range of 619
|
#define MAX_GSCN_BAND 620 // n78 has the highest GSCN range of 619
|
||||||
#define NR_SYMBOLS_PER_SLOT 14
|
#define NR_SYMBOLS_PER_SLOT 14
|
||||||
#define NR_SYMBOLS_PER_SLOT_EXTENDED_CP 12
|
#define NR_SYMBOLS_PER_SLOT_EXTENDED_CP 12
|
||||||
#define NR_MAX_NB_LAYERS 4 // 8
|
#define NR_MAX_NB_LAYERS 8
|
||||||
|
#define NR_MAX_NB_CODEWORDS 2
|
||||||
#define NR_MAX_CSI_PORTS 12
|
#define NR_MAX_CSI_PORTS 12
|
||||||
|
|
||||||
|
/* TS 38.211 Table 7.3.1.3-1: PDSCH uses one codeword up to rank four and
|
||||||
|
* two codewords from rank five onward. Keep this split in one shared helper
|
||||||
|
* so MAC and PHY cannot silently disagree about the 4+4 rank-eight mapping. */
|
||||||
|
static inline uint8_t nr_pdsch_num_codewords(uint8_t layers)
|
||||||
|
{
|
||||||
|
AssertFatal(layers >= 1 && layers <= NR_MAX_NB_LAYERS, "Invalid PDSCH layer count %u\n", layers);
|
||||||
|
return layers > 4 ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t nr_pdsch_layers_per_codeword(uint8_t layers, uint8_t codeword)
|
||||||
|
{
|
||||||
|
const uint8_t n_codewords = nr_pdsch_num_codewords(layers);
|
||||||
|
AssertFatal(codeword < n_codewords, "Invalid codeword %u for %u PDSCH layers\n", codeword, layers);
|
||||||
|
if (n_codewords == 1)
|
||||||
|
return layers;
|
||||||
|
return layers / 2 + (codeword == 1 ? layers % 2 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Since the IQ samples are represented by SQ15 R+I (see https://en.wikipedia.org/wiki/Q_(number_format)) we need to compensate when
|
// Since the IQ samples are represented by SQ15 R+I (see https://en.wikipedia.org/wiki/Q_(number_format)) we need to compensate when
|
||||||
// calcualting signal energy. Instead of shifting each sample right by 15, we can normalize the result in dB scale once its
|
// calcualting signal energy. Instead of shifting each sample right by 15, we can normalize the result in dB scale once its
|
||||||
// calcualted. Signal energy is calculated using RMS^2, where each sample is squared before taking the average of the sum, therefore
|
// calcualted. Signal energy is calculated using RMS^2, where each sample is squared before taking the average of the sum, therefore
|
||||||
|
|||||||
@@ -21,6 +21,22 @@ TEST(nr_common, nr_timer) {
|
|||||||
EXPECT_TRUE(nr_timer_expired(&timer));
|
EXPECT_TRUE(nr_timer_expired(&timer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(nr_common, pdsch_codeword_layer_split)
|
||||||
|
{
|
||||||
|
constexpr uint8_t expected_codewords[] = {1, 1, 1, 1, 2, 2, 2, 2};
|
||||||
|
constexpr uint8_t expected_cw0_layers[] = {1, 2, 3, 4, 2, 3, 3, 4};
|
||||||
|
constexpr uint8_t expected_cw1_layers[] = {0, 0, 0, 0, 3, 3, 4, 4};
|
||||||
|
|
||||||
|
for (uint8_t layers = 1; layers <= NR_MAX_NB_LAYERS; layers++) {
|
||||||
|
const int index = layers - 1;
|
||||||
|
EXPECT_EQ(nr_pdsch_num_codewords(layers), expected_codewords[index]);
|
||||||
|
EXPECT_EQ(nr_pdsch_layers_per_codeword(layers, 0), expected_cw0_layers[index]);
|
||||||
|
if (expected_codewords[index] == 2) {
|
||||||
|
EXPECT_EQ(nr_pdsch_layers_per_codeword(layers, 1), expected_cw1_layers[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
#include "NR_PHY_INTERFACE/NR_IF_Module.h"
|
#include "NR_PHY_INTERFACE/NR_IF_Module.h"
|
||||||
#include "PHY/INIT/nr_phy_init.h"
|
#include "PHY/INIT/nr_phy_init.h"
|
||||||
#include "PHY/MODULATION/nr_modulation.h"
|
#include "PHY/MODULATION/nr_modulation.h"
|
||||||
|
#include "openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
|
||||||
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
|
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
|
||||||
#include "PHY/TOOLS/tools_defs.h"
|
#include "PHY/TOOLS/tools_defs.h"
|
||||||
#include "PHY/defs_RU.h"
|
#include "PHY/defs_RU.h"
|
||||||
@@ -372,6 +374,14 @@ void init_eNB_afterRU(void)
|
|||||||
*/
|
*/
|
||||||
//init_precoding_weights(RC.gNB[inst]);
|
//init_precoding_weights(RC.gNB[inst]);
|
||||||
init_gNB_Tpool(inst);
|
init_gNB_Tpool(inst);
|
||||||
|
|
||||||
|
// antenna control
|
||||||
|
if (nr_antenna_control_start_test_server(&gNB->antenna_state, 9999))
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] Test control server started on port 9999\n");
|
||||||
|
if (inst < RC.nb_nr_macrlc_inst && RC.nrmac && RC.nrmac[inst]) {
|
||||||
|
RC.nrmac[inst]->antenna_state = &gNB->antenna_state;
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Connected MAC scheduler to PHY antenna state for dynamic layer adaptation\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -302,8 +302,8 @@ void nrue_init_openair0(void)
|
|||||||
|
|
||||||
cfg->ru_id = ru_id;
|
cfg->ru_id = ru_id;
|
||||||
cfg->num_rb_dl = frame_parms->N_RB_DL;
|
cfg->num_rb_dl = frame_parms->N_RB_DL;
|
||||||
cfg->tx_num_channels = min(4, frame_parms->nb_antennas_tx);
|
cfg->tx_num_channels = min(MAX_ANT, frame_parms->nb_antennas_tx);
|
||||||
cfg->rx_num_channels = min(4, frame_parms->nb_antennas_rx);
|
cfg->rx_num_channels = min(MAX_ANT, frame_parms->nb_antennas_rx);
|
||||||
|
|
||||||
LOG_I(PHY,
|
LOG_I(PHY,
|
||||||
"HW: Configuring ru_id %d, sample_rate %f, tx/rx num_channels %d/%d, duplex_mode %s\n",
|
"HW: Configuring ru_id %d, sample_rate %f, tx/rx num_channels %d/%d, duplex_mode %s\n",
|
||||||
|
|||||||
+3
-2
@@ -763,8 +763,9 @@ void *UE_thread(void *arg)
|
|||||||
|
|
||||||
if (get_softmodem_params()->sync_ref && UE->sl_mode == 2) {
|
if (get_softmodem_params()->sync_ref && UE->sl_mode == 2) {
|
||||||
UE->is_synchronized = 1;
|
UE->is_synchronized = 1;
|
||||||
} else {
|
} else if (!IS_SOFTMODEM_RFSIM) {
|
||||||
//warm up the RF board
|
// Warm up physical RF hardware. RFsim has no RF board to settle, and
|
||||||
|
// discarding 100 simulated frames only delays initial synchronization.
|
||||||
openair0_timestamp_t tmp;
|
openair0_timestamp_t tmp;
|
||||||
for (int i = 0; i < 50; i++)
|
for (int i = 0; i < 50; i++)
|
||||||
readFrame(UE, &tmp, duration_rx_to_tx, true);
|
readFrame(UE, &tmp, duration_rx_to_tx, true);
|
||||||
|
|||||||
@@ -143,6 +143,9 @@ void phy_init_nr_gNB(PHY_VARS_gNB *gNB)
|
|||||||
|
|
||||||
init_DLSCH_struct(gNB);
|
init_DLSCH_struct(gNB);
|
||||||
|
|
||||||
|
nr_init_antenna_state(&gNB->antenna_state, Ptx, Prx, "tx-array-0", "rx-array-0");
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Initialized with %d TX, %d RX antennas\n", Ptx, Prx);
|
||||||
|
|
||||||
/* Do NOT allocate per-antenna rxdataF: the gNB gets a pointer to the
|
/* Do NOT allocate per-antenna rxdataF: the gNB gets a pointer to the
|
||||||
* RU to copy/recover freq-domain memory from there */
|
* RU to copy/recover freq-domain memory from there */
|
||||||
common_vars->rxdataF = (c16_t ***)malloc16(common_vars->num_beams_period * sizeof(c16_t**));
|
common_vars->rxdataF = (c16_t ***)malloc16(common_vars->num_beams_period * sizeof(c16_t**));
|
||||||
|
|||||||
@@ -160,8 +160,12 @@ int init_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB)
|
|||||||
LOG_I(PHY, "Initializing UE vars for gNB TXant %u, UE RXant %u\n", fp->nb_antennas_tx, fp->nb_antennas_rx);
|
LOG_I(PHY, "Initializing UE vars for gNB TXant %u, UE RXant %u\n", fp->nb_antennas_tx, fp->nb_antennas_rx);
|
||||||
|
|
||||||
phy_init_nr_top(ue);
|
phy_init_nr_top(ue);
|
||||||
// many memory allocation sizes are hard coded
|
// NR UE receive buffers are dimensioned dynamically. Keep the explicit
|
||||||
AssertFatal( fp->nb_antennas_rx <= 4, "hard coded allocation for ue_common_vars->dl_ch_estimates[gNB_id]" );
|
// platform bound so an invalid RF configuration fails before allocation.
|
||||||
|
AssertFatal(fp->nb_antennas_rx > 0 && fp->nb_antennas_rx <= MAX_ANT,
|
||||||
|
"UE RX antenna count %u exceeds platform maximum %u",
|
||||||
|
fp->nb_antennas_rx,
|
||||||
|
MAX_ANT);
|
||||||
AssertFatal( nb_connected_gNB <= NUMBER_OF_CONNECTED_gNB_MAX, "n_connected_gNB is too large" );
|
AssertFatal( nb_connected_gNB <= NUMBER_OF_CONNECTED_gNB_MAX, "n_connected_gNB is too large" );
|
||||||
// init phy_vars_ue
|
// init phy_vars_ue
|
||||||
|
|
||||||
@@ -170,7 +174,7 @@ int init_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB)
|
|||||||
ue->measurements.ssb_sinr_dB[i] = INT_MIN;
|
ue->measurements.ssb_sinr_dB[i] = INT_MIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < fp->nb_antennas_rx; i++) {
|
||||||
ue->rx_gain_max[i] = 135;
|
ue->rx_gain_max[i] = 135;
|
||||||
ue->rx_gain_med[i] = 128;
|
ue->rx_gain_med[i] = 128;
|
||||||
ue->rx_gain_byp[i] = 120;
|
ue->rx_gain_byp[i] = 120;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "PHY/LTE_TRANSPORT/transport_eNB.h"
|
#include "PHY/LTE_TRANSPORT/transport_eNB.h"
|
||||||
#include "modulation_eNB.h"
|
#include "modulation_eNB.h"
|
||||||
#include "common/utils/LOG/vcd_signal_dumper.h"
|
#include "common/utils/LOG/vcd_signal_dumper.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||||
|
|
||||||
int beam_precoding(int32_t **txdataF,
|
int beam_precoding(int32_t **txdataF,
|
||||||
int32_t **txdataF_BF,
|
int32_t **txdataF_BF,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#include "nr_modulation.h"
|
#include "nr_modulation.h"
|
||||||
#include "openair1/PHY/TOOLS/tools_defs.h"
|
#include "openair1/PHY/TOOLS/tools_defs.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||||
|
#include "common/utils/LOG/log.h"
|
||||||
|
|
||||||
void nr_beam_precoding(c16_t **txdataF,
|
void nr_beam_precoding(c16_t **txdataF,
|
||||||
c16_t **txdataF_BF,
|
c16_t **txdataF_BF,
|
||||||
@@ -25,3 +27,33 @@ void nr_beam_precoding(c16_t **txdataF,
|
|||||||
15);
|
15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nr_beam_precoding_with_muting(c16_t **txdataF,
|
||||||
|
c16_t **txdataF_BF,
|
||||||
|
NR_DL_FRAME_PARMS *frame_parms,
|
||||||
|
int32_t ***beam_weights,
|
||||||
|
int symbol,
|
||||||
|
int aa,
|
||||||
|
int nb_antenna_ports,
|
||||||
|
int offset,
|
||||||
|
const nr_antenna_state_t *ant_state)
|
||||||
|
{
|
||||||
|
// Clear output buffer first
|
||||||
|
memset(&txdataF_BF[aa][symbol * frame_parms->ofdm_symbol_size], 0,
|
||||||
|
sizeof(c16_t) * frame_parms->ofdm_symbol_size);
|
||||||
|
// O-RAN energy saving: skip precoding if antenna is muted
|
||||||
|
// This implements transmission blanking per WG4 spec
|
||||||
|
if (ant_state != NULL && !nr_is_tx_antenna_active(ant_state, aa)) {
|
||||||
|
LOG_D(PHY, "[ANTENNA_MUTE] Antenna %d muted, blanking transmission for symbol %d\n",
|
||||||
|
aa, symbol);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Normal precoding for active antennas
|
||||||
|
for (int p = 0; p < nb_antenna_ports; p++) {
|
||||||
|
multadd_cpx_vector((c16_t *)&txdataF[p][(symbol * frame_parms->ofdm_symbol_size) + offset],
|
||||||
|
(c16_t *)beam_weights[p][aa],
|
||||||
|
(c16_t *)&txdataF_BF[aa][symbol * frame_parms->ofdm_symbol_size],
|
||||||
|
frame_parms->ofdm_symbol_size,
|
||||||
|
15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -216,15 +216,19 @@ void nr_modulation(const uint32_t *in, uint32_t length, uint16_t mod_order, int1
|
|||||||
}
|
}
|
||||||
|
|
||||||
void nr_layer_mapping(int nbCodes,
|
void nr_layer_mapping(int nbCodes,
|
||||||
int encoded_len,
|
const c16_t *const mod_symbs[NR_MAX_NB_CODEWORDS],
|
||||||
c16_t mod_symbs[nbCodes][encoded_len],
|
const uint32_t n_symbs_per_codeword[NR_MAX_NB_CODEWORDS],
|
||||||
uint8_t n_layers,
|
uint8_t n_layers,
|
||||||
int layerSz,
|
int layerSz,
|
||||||
uint32_t n_symbs,
|
|
||||||
c16_t tx_layers[][layerSz])
|
c16_t tx_layers[][layerSz])
|
||||||
{
|
{
|
||||||
|
const uint32_t n_symbs = n_symbs_per_codeword[0];
|
||||||
LOG_D(PHY, "Doing layer mapping for %d layers, %d symbols\n", n_layers, n_symbs);
|
LOG_D(PHY, "Doing layer mapping for %d layers, %d symbols\n", n_layers, n_symbs);
|
||||||
c16_t *mod = mod_symbs[0];
|
const c16_t *mod = mod_symbs[0];
|
||||||
|
AssertFatal(nbCodes == nr_pdsch_num_codewords(n_layers),
|
||||||
|
"Layer mapping got %d codewords for %u layers\n",
|
||||||
|
nbCodes,
|
||||||
|
n_layers);
|
||||||
switch (n_layers) {
|
switch (n_layers) {
|
||||||
case 1:
|
case 1:
|
||||||
memcpy(tx_layers[0], mod, n_symbs * sizeof(**mod_symbs));
|
memcpy(tx_layers[0], mod, n_symbs * sizeof(**mod_symbs));
|
||||||
@@ -472,58 +476,26 @@ void nr_layer_mapping(int nbCodes,
|
|||||||
case 5:
|
case 5:
|
||||||
case 6:
|
case 6:
|
||||||
case 7:
|
case 7:
|
||||||
case 8:
|
case 8: {
|
||||||
/*
|
int first_layer = 0;
|
||||||
// Layer 0,1
|
for (int q = 0; q < nbCodes; q++) {
|
||||||
for (int i = 0; i < n_symbs; i += 2) {
|
const int cw_layers = nr_pdsch_layers_per_codeword(n_layers, q);
|
||||||
const int txIdx = i / 2;
|
AssertFatal(n_symbs_per_codeword[q] % cw_layers == 0,
|
||||||
tx_layer[0][txIdx] = mod_symbs[0][i];
|
"Codeword %d has %u symbols, not divisible by %d layers\n",
|
||||||
tx_layer[1][txIdx] = mod_symbs[0][i + 1];
|
q,
|
||||||
|
n_symbs_per_codeword[q],
|
||||||
|
cw_layers);
|
||||||
|
const uint32_t symbols_per_layer = n_symbs_per_codeword[q] / cw_layers;
|
||||||
|
AssertFatal(symbols_per_layer <= layerSz,
|
||||||
|
"Layer buffer too small: need %u, have %d symbols\n",
|
||||||
|
symbols_per_layer,
|
||||||
|
layerSz);
|
||||||
|
for (uint32_t i = 0; i < symbols_per_layer; i++)
|
||||||
|
for (int l = 0; l < cw_layers; l++)
|
||||||
|
tx_layers[first_layer + l][i] = mod_symbs[q][i * cw_layers + l];
|
||||||
|
first_layer += cw_layers;
|
||||||
}
|
}
|
||||||
// layers 2,3,4
|
} break;
|
||||||
else
|
|
||||||
for (int i = 0; i < n_symbs; i += 3) {
|
|
||||||
const int txIdx = i / 3;
|
|
||||||
tx_layer[2][txIdx] = mod_symbs[1][i + 2];
|
|
||||||
tx_layer[3][txIdx] = mod_symbs[1][i + 3];
|
|
||||||
tx_layer[4][txIdx] = mod_symbs[1][i + 4];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
for (int q=0; q<2; q++)
|
|
||||||
for (int i = 0; i < n_symbs; i += 3) {
|
|
||||||
const int txIdx = i / 3;
|
|
||||||
tx_layer[0][txIdx] = mod_symbs[q][i + layer];
|
|
||||||
tx_layer[1][txIdx] = mod_symbs[q][i + layer];
|
|
||||||
tx_layer[2][txIdx] = mod_symbs[q][i + layer];
|
|
||||||
tx_layer[3][txIdx] = mod_symbs[q][i + layer];
|
|
||||||
tx_layer[4][txIdx] = mod_symbs[q][i + layer];
|
|
||||||
tx_layer[5][txIdx] = mod_symbs[q][i + layer];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 7:
|
|
||||||
if (layer < 3)
|
|
||||||
for (int i = 0; i < n_symbs; i += 3) {
|
|
||||||
const int txIdx = i / 3;
|
|
||||||
tx_layer[txIdx] = mod_symbs[1][i + layer];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
for (int i = 0; i < n_symbs; i += 4) {
|
|
||||||
const int txIdx = i / 4;
|
|
||||||
tx_layer[txIdx] = mod_symbs[0][i + layer];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 8:
|
|
||||||
for (int q=0; q<2; q++)
|
|
||||||
for (int i = 0; i < n_symbs; i += 4) {
|
|
||||||
const int txIdx = i / 4;
|
|
||||||
tx_layer[txIdx] = mod_symbs[q][i + layer];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
*/
|
|
||||||
default:
|
default:
|
||||||
AssertFatal(0, "Invalid number of layers %d\n", n_layers);
|
AssertFatal(0, "Invalid number of layers %d\n", n_layers);
|
||||||
}
|
}
|
||||||
@@ -687,7 +659,7 @@ c16_t nr_layer_precoder(int sz, c16_t datatx_F_precoding[][sz], const char *prec
|
|||||||
|
|
||||||
c16_t nr_layer_precoder_cm(int n_layers,
|
c16_t nr_layer_precoder_cm(int n_layers,
|
||||||
int symSz,
|
int symSz,
|
||||||
c16_t datatx_F_precoding[n_layers][symSz],
|
const c16_t datatx_F_precoding[n_layers][symSz],
|
||||||
int ap,
|
int ap,
|
||||||
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
|
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
|
||||||
int offset)
|
int offset)
|
||||||
@@ -819,7 +791,19 @@ void nr_layer_precoder_simd(const int n_layers,
|
|||||||
{
|
{
|
||||||
// For x86, use 256 SIMD for every 8 RE and 128 SIMD for last 4 RE
|
// For x86, use 256 SIMD for every 8 RE and 128 SIMD for last 4 RE
|
||||||
// For aarch64, use 128 SIMD for every 4 RE
|
// For aarch64, use 128 SIMD for every 4 RE
|
||||||
AssertFatal(n_layers > 0 && n_layers <= 4, "Shouldn't get here, n_layers %d\n", n_layers);
|
AssertFatal(n_layers > 0 && n_layers <= NR_MAX_NB_LAYERS, "Invalid PDSCH layer count %d\n", n_layers);
|
||||||
|
|
||||||
|
/* The hand-vectorized kernels below remain the fast path for ranks 1--4.
|
||||||
|
* Rank 5--8 is uncommon and uses the same full complex matrix operation via
|
||||||
|
* the scalar reference kernel. Unlike antenna fan-out, this combines every
|
||||||
|
* distinct layer with its own precoder weight and is therefore a genuine
|
||||||
|
* high-rank precoder. */
|
||||||
|
if (n_layers > 4) {
|
||||||
|
for (int re = 0; re < re_cnt; re++)
|
||||||
|
txdataF_precoded[sc_offset + re] =
|
||||||
|
nr_layer_precoder_cm(n_layers, symSz, txdataF_res_mapped, ant, weights, sc_offset + re);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 512/256 SIMD: Do 16/8 RE in one iteration, 3 iterations for 2 RB
|
// 512/256 SIMD: Do 16/8 RE in one iteration, 3 iterations for 2 RB
|
||||||
c16_t *beginning = txdataF_precoded + sc_offset;
|
c16_t *beginning = txdataF_precoded + sc_offset;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "PHY/defs_nr_common.h"
|
#include "PHY/defs_nr_common.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||||
|
|
||||||
#define DMRS_MOD_ORDER 2
|
#define DMRS_MOD_ORDER 2
|
||||||
/*Precoding matices: W[pmi][antenna_port][layer]*/
|
/*Precoding matices: W[pmi][antenna_port][layer]*/
|
||||||
@@ -36,11 +37,10 @@ void nr_modulation(const uint32_t *in,
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
void nr_layer_mapping(int nbCodes,
|
void nr_layer_mapping(int nbCodes,
|
||||||
int encoded_len,
|
const c16_t *const mod_symbs[NR_MAX_NB_CODEWORDS],
|
||||||
c16_t mod_symbs[nbCodes][encoded_len],
|
const uint32_t n_symbs_per_codeword[NR_MAX_NB_CODEWORDS],
|
||||||
uint8_t n_layers,
|
uint8_t n_layers,
|
||||||
int layerSz,
|
int layerSz,
|
||||||
uint32_t n_symbs,
|
|
||||||
c16_t tx_layers[][layerSz]);
|
c16_t tx_layers[][layerSz]);
|
||||||
|
|
||||||
/*! \brief Perform NR layer mapping. TS 38.211 V15.4.0 subclause 7.3.1.3
|
/*! \brief Perform NR layer mapping. TS 38.211 V15.4.0 subclause 7.3.1.3
|
||||||
@@ -84,6 +84,33 @@ void nr_beam_precoding(c16_t **txdataF,
|
|||||||
int nb_antenna_ports,
|
int nb_antenna_ports,
|
||||||
int offset);
|
int offset);
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief NR beam precoding with O-RAN compliant antenna muting
|
||||||
|
*
|
||||||
|
* Extends nr_beam_precoding to support antenna muting for energy saving
|
||||||
|
* per O-RAN WG4 MPLANE-TRX-CONTROL specification (o-ran-uplane-conf.yang).
|
||||||
|
* When an antenna is muted, transmission is blanked (output zeroed).
|
||||||
|
*
|
||||||
|
* @param[in] txdataF Input frequency-domain data per antenna port
|
||||||
|
* @param[out] txdataF_BF Output beamformed data per physical antenna
|
||||||
|
* @param[in] frame_parms Frame parameters
|
||||||
|
* @param[in] beam_weights Beamforming weights
|
||||||
|
* @param[in] symbol Current symbol number
|
||||||
|
* @param[in] aa Physical antenna index
|
||||||
|
* @param[in] nb_antenna_ports Number of antenna ports
|
||||||
|
* @param[in] offset Frequency offset
|
||||||
|
* @param[in] ant_state Antenna state for muting control (NULL = all active)
|
||||||
|
*/
|
||||||
|
void nr_beam_precoding_with_muting(c16_t **txdataF,
|
||||||
|
c16_t **txdataF_BF,
|
||||||
|
NR_DL_FRAME_PARMS *frame_parms,
|
||||||
|
int32_t ***beam_weights,
|
||||||
|
int symbol,
|
||||||
|
int aa,
|
||||||
|
int nb_antenna_ports,
|
||||||
|
int offset,
|
||||||
|
const nr_antenna_state_t *ant_state);
|
||||||
|
|
||||||
void apply_nr_rotation_TX(const NR_DL_FRAME_PARMS *fp,
|
void apply_nr_rotation_TX(const NR_DL_FRAME_PARMS *fp,
|
||||||
c16_t *txdataF,
|
c16_t *txdataF,
|
||||||
bool is_flat_buff,
|
bool is_flat_buff,
|
||||||
@@ -124,7 +151,7 @@ c16_t nr_layer_precoder(int sz, c16_t datatx_F_precoding[][sz], const char *prec
|
|||||||
|
|
||||||
c16_t nr_layer_precoder_cm(int n_layers,
|
c16_t nr_layer_precoder_cm(int n_layers,
|
||||||
int symSz,
|
int symSz,
|
||||||
c16_t datatx_F_precoding[n_layers][symSz],
|
const c16_t datatx_F_precoding[n_layers][symSz],
|
||||||
int ap,
|
int ap,
|
||||||
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
|
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
|
||||||
int offset);
|
int offset);
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ extern "C" {
|
|||||||
|
|
||||||
#include "openair1/PHY/TOOLS/tools_defs.h" // c16_t
|
#include "openair1/PHY/TOOLS/tools_defs.h" // c16_t
|
||||||
#include "nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h" // fapi_nr_dl_config_dci_dl_pdu_rel15_t
|
#include "nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h" // fapi_nr_dl_config_dci_dl_pdu_rel15_t
|
||||||
|
#include "common/utils/LOG/log.h"
|
||||||
|
|
||||||
//#include "openair1/PHY/MODULATION/nr_modulation.h" // nr_layer_precoder_simd_[cm, simd]
|
//#include "openair1/PHY/MODULATION/nr_modulation.h" // nr_layer_precoder_simd_[cm, simd]
|
||||||
|
|
||||||
// Forward declare the function but hide the Variable-Length Array (VLA) feature from C not available in C++
|
// Forward declare the function but hide the Variable-Length Array (VLA) feature from C not available in C++
|
||||||
c16_t nr_layer_precoder_cm(int n_layers,
|
c16_t nr_layer_precoder_cm(int n_layers,
|
||||||
int symbol_size,
|
int symbol_size,
|
||||||
c16_t (*dataF_in)[/*symbol_size*/], // c16_t dataF_in[n_layers][symbol_size]
|
const c16_t (*dataF_in)[/*symbol_size*/], // c16_t dataF_in[n_layers][symbol_size]
|
||||||
int ant,
|
int ant,
|
||||||
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
|
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
|
||||||
int offset);
|
int offset);
|
||||||
@@ -37,6 +38,14 @@ void nr_layer_precoder_simd(int n_layers,
|
|||||||
int re_cnt,
|
int re_cnt,
|
||||||
c16_t *dataF_out);
|
c16_t *dataF_out);
|
||||||
|
|
||||||
|
// Forward declare the layer mapper while hiding its C VLA dimension.
|
||||||
|
void nr_layer_mapping(int nb_codewords,
|
||||||
|
const c16_t *const mod_symbols[2],
|
||||||
|
const uint32_t n_symbols_per_codeword[2],
|
||||||
|
uint8_t n_layers,
|
||||||
|
int layer_size,
|
||||||
|
c16_t (*tx_layers)[/*layer_size*/]);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -185,11 +194,50 @@ TEST(NrLayerPrecoderTest, Compare_CM_SIMD)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(NrLayerMappingTest, Rank8UsesTwoCodewordsAcrossEightDistinctLayers)
|
||||||
|
{
|
||||||
|
constexpr int n_codewords = 2;
|
||||||
|
constexpr int n_layers = 8;
|
||||||
|
constexpr int layers_per_codeword = 4;
|
||||||
|
constexpr int symbols_per_layer = 7;
|
||||||
|
constexpr int symbols_per_codeword = layers_per_codeword * symbols_per_layer;
|
||||||
|
|
||||||
|
std::vector<c16_t> cw0(symbols_per_codeword);
|
||||||
|
std::vector<c16_t> cw1(symbols_per_codeword);
|
||||||
|
for (int i = 0; i < symbols_per_codeword; ++i) {
|
||||||
|
cw0[i] = {static_cast<int16_t>(100 + i), static_cast<int16_t>(-100 - i)};
|
||||||
|
cw1[i] = {static_cast<int16_t>(1000 + i), static_cast<int16_t>(-1000 - i)};
|
||||||
|
}
|
||||||
|
|
||||||
|
const c16_t *mod_symbols[2] = {cw0.data(), cw1.data()};
|
||||||
|
const uint32_t n_symbols_per_codeword[2] = {symbols_per_codeword, symbols_per_codeword};
|
||||||
|
std::vector<c16_t> layer_storage(n_layers * symbols_per_layer, c16_t{SHRT_MIN, SHRT_MIN});
|
||||||
|
c16_t(*tx_layers)[symbols_per_layer] = reinterpret_cast<c16_t(*)[symbols_per_layer]>(layer_storage.data());
|
||||||
|
|
||||||
|
nr_layer_mapping(n_codewords,
|
||||||
|
mod_symbols,
|
||||||
|
n_symbols_per_codeword,
|
||||||
|
n_layers,
|
||||||
|
symbols_per_layer,
|
||||||
|
tx_layers);
|
||||||
|
|
||||||
|
for (int re = 0; re < symbols_per_layer; ++re) {
|
||||||
|
for (int layer = 0; layer < n_layers; ++layer) {
|
||||||
|
const int cw = layer / layers_per_codeword;
|
||||||
|
const int cw_layer = layer % layers_per_codeword;
|
||||||
|
const c16_t expected = mod_symbols[cw][re * layers_per_codeword + cw_layer];
|
||||||
|
EXPECT_EQ(tx_layers[layer][re].r, expected.r) << "real mismatch at layer " << layer << ", RE " << re;
|
||||||
|
EXPECT_EQ(tx_layers[layer][re].i, expected.i) << "imag mismatch at layer " << layer << ", RE " << re;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
// Initialize random seed
|
// Initialize random seed
|
||||||
srand(time(0));
|
srand(time(0));
|
||||||
|
|
||||||
|
logInit();
|
||||||
testing::InitGoogleTest(&argc, argv);
|
testing::InitGoogleTest(&argc, argv);
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,651 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The OpenAirInterface Software Alliance licenses this file to You under
|
||||||
|
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
||||||
|
* except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.openairinterface.org/?page_id=698
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*-------------------------------------------------------------------------------
|
||||||
|
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
||||||
|
* contact@openairinterface.org
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \file PHY/NR_TRANSPORT/nr_antenna_control.c
|
||||||
|
* \brief Antenna control interface implementation with O-RAN M-Plane support
|
||||||
|
* \author OpenAirInterface
|
||||||
|
* \date 2025
|
||||||
|
* \version 2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nr_antenna_control.h"
|
||||||
|
#include "common/utils/LOG/log.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
// Try to include M-Plane header if available
|
||||||
|
#ifdef OAI_MPLANE
|
||||||
|
#include "radio/fhi_72/mplane/antenna-control.h"
|
||||||
|
#include "radio/fhi_72/mplane/ru-mplane-api.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Global control state
|
||||||
|
static struct {
|
||||||
|
pthread_t thread;
|
||||||
|
int server_fd;
|
||||||
|
nr_antenna_state_t *ant_state;
|
||||||
|
volatile bool running;
|
||||||
|
int port;
|
||||||
|
nr_antenna_ctrl_backend_t backend;
|
||||||
|
int64_t tx_lock_until_ms[NR_MAX_ANTENNAS];
|
||||||
|
#ifdef OAI_MPLANE
|
||||||
|
ru_session_t *ru_session;
|
||||||
|
#else
|
||||||
|
void *ru_session;
|
||||||
|
#endif
|
||||||
|
} g_ctrl = {0};
|
||||||
|
|
||||||
|
static bool valid_tsm_duration(int seconds)
|
||||||
|
{
|
||||||
|
return seconds == 0 || seconds == 15 || seconds == 30 ||
|
||||||
|
seconds == 45 || seconds == 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t monotonic_ms(void)
|
||||||
|
{
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t tx_lock_remaining_ms(int ant_id)
|
||||||
|
{
|
||||||
|
if (ant_id < 0 || ant_id >= NR_MAX_ANTENNAS)
|
||||||
|
return 0;
|
||||||
|
int64_t remaining = g_ctrl.tx_lock_until_ms[ant_id] - monotonic_ms();
|
||||||
|
if (remaining <= 0) {
|
||||||
|
g_ctrl.tx_lock_until_ms[ant_id] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_tx_lock(int ant_id, int seconds)
|
||||||
|
{
|
||||||
|
if (ant_id < 0 || ant_id >= NR_MAX_ANTENNAS)
|
||||||
|
return;
|
||||||
|
g_ctrl.tx_lock_until_ms[ant_id] =
|
||||||
|
seconds > 0 ? monotonic_ms() + (int64_t)seconds * 1000 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_all_tx_locks(void)
|
||||||
|
{
|
||||||
|
memset(g_ctrl.tx_lock_until_ms, 0, sizeof(g_ctrl.tx_lock_until_ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test Socket Server Implementation (Development/Testing Mode)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Process a single command and generate response
|
||||||
|
static void process_command(const char *cmd, char *response, size_t resp_size)
|
||||||
|
{
|
||||||
|
char cmd_upper[256];
|
||||||
|
strncpy(cmd_upper, cmd, sizeof(cmd_upper) - 1);
|
||||||
|
cmd_upper[sizeof(cmd_upper) - 1] = '\0';
|
||||||
|
|
||||||
|
// Convert to uppercase for case-insensitive matching
|
||||||
|
for (char *p = cmd_upper; *p; p++) {
|
||||||
|
if (*p >= 'a' && *p <= 'z') *p -= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim whitespace
|
||||||
|
char *p = cmd_upper;
|
||||||
|
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
|
||||||
|
char *end = p + strlen(p) - 1;
|
||||||
|
while (end > p && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = '\0';
|
||||||
|
|
||||||
|
if (strncmp(p, "MUTE TX ", 8) == 0) {
|
||||||
|
int ant_id = -1;
|
||||||
|
int duration = 0;
|
||||||
|
int fields = sscanf(p + 8, "%d %d", &ant_id, &duration);
|
||||||
|
if (fields < 1 || ant_id < 0 || ant_id >= g_ctrl.ant_state->num_tx_antennas) {
|
||||||
|
snprintf(response, resp_size, "ERROR: Invalid TX antenna id\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fields >= 2 && !valid_tsm_duration(duration)) {
|
||||||
|
snprintf(response, resp_size,
|
||||||
|
"ERROR: Unsupported TSM duration %d; use 15, 30, 45, or 60 seconds\n",
|
||||||
|
duration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nr_mute_tx_antenna(g_ctrl.ant_state, ant_id);
|
||||||
|
set_tx_lock(ant_id, duration);
|
||||||
|
snprintf(response, resp_size, "OK: TX antenna %d muted (TSM=%ds)\n",
|
||||||
|
ant_id, duration);
|
||||||
|
}
|
||||||
|
else if (strncmp(p, "UNMUTE TX ", 10) == 0) {
|
||||||
|
int ant_id = -1;
|
||||||
|
char option[16] = {0};
|
||||||
|
int fields = sscanf(p + 10, "%d %15s", &ant_id, option);
|
||||||
|
if (fields < 1 || ant_id < 0 || ant_id >= g_ctrl.ant_state->num_tx_antennas) {
|
||||||
|
snprintf(response, resp_size, "ERROR: Invalid TX antenna id\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool force = fields >= 2 && strcmp(option, "FORCE") == 0;
|
||||||
|
int64_t remaining = tx_lock_remaining_ms(ant_id);
|
||||||
|
if (remaining > 0 && !force) {
|
||||||
|
snprintf(response, resp_size,
|
||||||
|
"ERROR: TX antenna %d locked for %ld more ms\n",
|
||||||
|
ant_id, (long)remaining);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set_tx_lock(ant_id, 0);
|
||||||
|
nr_unmute_tx_antenna(g_ctrl.ant_state, ant_id);
|
||||||
|
snprintf(response, resp_size, "OK: TX antenna %d unmuted%s\n",
|
||||||
|
ant_id, force ? " (emergency override)" : "");
|
||||||
|
}
|
||||||
|
else if (strncmp(p, "MUTE RX ", 8) == 0) {
|
||||||
|
int ant_id = atoi(p + 8);
|
||||||
|
nr_mute_rx_antenna(g_ctrl.ant_state, ant_id);
|
||||||
|
snprintf(response, resp_size, "OK: RX antenna %d muted\n", ant_id);
|
||||||
|
}
|
||||||
|
else if (strncmp(p, "UNMUTE RX ", 10) == 0) {
|
||||||
|
int ant_id = atoi(p + 10);
|
||||||
|
nr_unmute_rx_antenna(g_ctrl.ant_state, ant_id);
|
||||||
|
snprintf(response, resp_size, "OK: RX antenna %d unmuted\n", ant_id);
|
||||||
|
}
|
||||||
|
else if (strncmp(p, "SET TX MASK ", 12) == 0) {
|
||||||
|
unsigned int mask;
|
||||||
|
int duration = 0;
|
||||||
|
char option[16] = {0};
|
||||||
|
int fields = sscanf(p + 12, "%x %d %15s", &mask, &duration, option);
|
||||||
|
if (fields < 1) {
|
||||||
|
snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fields >= 2 && !valid_tsm_duration(duration)) {
|
||||||
|
snprintf(response, resp_size,
|
||||||
|
"ERROR: Unsupported TSM duration %d; use 15, 30, 45, or 60 seconds\n",
|
||||||
|
duration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool force = fields >= 3 && strcmp(option, "FORCE") == 0;
|
||||||
|
uint8_t valid_mask = (1u << g_ctrl.ant_state->num_tx_antennas) - 1u;
|
||||||
|
uint8_t requested = (uint8_t)mask & valid_mask;
|
||||||
|
uint8_t old = nr_get_tx_antenna_bitmask(g_ctrl.ant_state);
|
||||||
|
uint8_t waking = requested & ~old;
|
||||||
|
for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) {
|
||||||
|
if ((waking & (1u << ant_id)) && tx_lock_remaining_ms(ant_id) > 0 && !force) {
|
||||||
|
snprintf(response, resp_size,
|
||||||
|
"ERROR: TX antenna %d is still TSM-locked\n", ant_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nr_set_tx_antenna_bitmask(g_ctrl.ant_state, requested);
|
||||||
|
uint8_t muted = old & ~requested;
|
||||||
|
for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) {
|
||||||
|
uint8_t bit = 1u << ant_id;
|
||||||
|
if (muted & bit)
|
||||||
|
set_tx_lock(ant_id, duration);
|
||||||
|
if (waking & bit)
|
||||||
|
set_tx_lock(ant_id, 0);
|
||||||
|
}
|
||||||
|
snprintf(response, resp_size,
|
||||||
|
"OK: TX mask 0x%02X -> 0x%02X (TSM=%ds%s)\n",
|
||||||
|
old, requested, duration, force ? ", emergency override" : "");
|
||||||
|
}
|
||||||
|
else if (strncmp(p, "SET RX MASK ", 12) == 0) {
|
||||||
|
unsigned int mask;
|
||||||
|
if (sscanf(p + 12, "%x", &mask) == 1 || sscanf(p + 12, "0X%x", &mask) == 1) {
|
||||||
|
uint8_t old = nr_set_rx_antenna_bitmask(g_ctrl.ant_state, (uint8_t)mask);
|
||||||
|
snprintf(response, resp_size, "OK: RX mask 0x%02X -> 0x%02X\n", old, (uint8_t)mask);
|
||||||
|
} else {
|
||||||
|
snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(p, "STATUS") == 0) {
|
||||||
|
bool in_recovery = nr_is_in_recovery_mode(g_ctrl.ant_state);
|
||||||
|
snprintf(response, resp_size,
|
||||||
|
"=== Antenna Status ===\n"
|
||||||
|
"TX Array: %s\n"
|
||||||
|
" Antennas: %d configured, %d active\n"
|
||||||
|
" Bitmask: 0x%02X\n"
|
||||||
|
" Antenna 0 protected: %s\n"
|
||||||
|
"RX Array: %s\n"
|
||||||
|
" Antennas: %d configured, %d active\n"
|
||||||
|
" Bitmask: 0x%02X\n"
|
||||||
|
"Recovery Mode: %s\n"
|
||||||
|
"Backend: %s\n",
|
||||||
|
g_ctrl.ant_state->tx_array_name,
|
||||||
|
g_ctrl.ant_state->num_tx_antennas,
|
||||||
|
nr_get_active_tx_antenna_count(g_ctrl.ant_state),
|
||||||
|
nr_get_tx_antenna_bitmask(g_ctrl.ant_state),
|
||||||
|
g_ctrl.ant_state->config.protect_antenna0 ? "YES" : "NO",
|
||||||
|
g_ctrl.ant_state->rx_array_name,
|
||||||
|
g_ctrl.ant_state->num_rx_antennas,
|
||||||
|
nr_get_active_rx_antenna_count(g_ctrl.ant_state),
|
||||||
|
nr_get_rx_antenna_bitmask(g_ctrl.ant_state),
|
||||||
|
in_recovery ? "ACTIVE (use identity matrix)" : "OFF",
|
||||||
|
g_ctrl.backend == ANTENNA_CTRL_BACKEND_MPLANE ? "O-RAN M-Plane NETCONF" :
|
||||||
|
g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET ? "Test Socket (NOT O-RAN)" :
|
||||||
|
"None");
|
||||||
|
size_t used = strlen(response);
|
||||||
|
if (used < resp_size) {
|
||||||
|
used += snprintf(response + used, resp_size - used, "TSM locks:");
|
||||||
|
for (int ant_id = 0;
|
||||||
|
ant_id < g_ctrl.ant_state->num_tx_antennas && used < resp_size;
|
||||||
|
++ant_id) {
|
||||||
|
int64_t remaining = tx_lock_remaining_ms(ant_id);
|
||||||
|
if (remaining > 0)
|
||||||
|
used += snprintf(response + used, resp_size - used,
|
||||||
|
" TX%d=%lds", ant_id, (long)((remaining + 999) / 1000));
|
||||||
|
}
|
||||||
|
if (used < resp_size)
|
||||||
|
snprintf(response + used, resp_size - used, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(p, "STATS") == 0) {
|
||||||
|
uint64_t tx_events, rx_events, muted_slots;
|
||||||
|
nr_get_antenna_stats(g_ctrl.ant_state, &tx_events, &rx_events, &muted_slots);
|
||||||
|
snprintf(response, resp_size,
|
||||||
|
"=== Energy Saving Statistics ===\n"
|
||||||
|
"TX mute events: %lu\n"
|
||||||
|
"RX mute events: %lu\n"
|
||||||
|
"Total muted slots: %lu\n",
|
||||||
|
tx_events, rx_events, muted_slots);
|
||||||
|
}
|
||||||
|
else if (strcmp(p, "RESET STATS") == 0) {
|
||||||
|
nr_reset_antenna_stats(g_ctrl.ant_state);
|
||||||
|
snprintf(response, resp_size, "OK: Statistics reset\n");
|
||||||
|
}
|
||||||
|
else if (strcmp(p, "RESET") == 0 || strcmp(p, "UNMUTE ALL") == 0) {
|
||||||
|
// Reset all antennas to active state with recovery mode
|
||||||
|
clear_all_tx_locks();
|
||||||
|
nr_unmute_all_tx_antennas(g_ctrl.ant_state);
|
||||||
|
nr_unmute_all_rx_antennas(g_ctrl.ant_state);
|
||||||
|
snprintf(response, resp_size,
|
||||||
|
"OK: All antennas unmuted (recovery mode: %u ms)\n"
|
||||||
|
" TX mask: 0x%02X (%d antennas)\n"
|
||||||
|
" RX mask: 0x%02X (%d antennas)\n",
|
||||||
|
g_ctrl.ant_state->recovery.recovery_duration_ms,
|
||||||
|
nr_get_tx_antenna_bitmask(g_ctrl.ant_state), g_ctrl.ant_state->num_tx_antennas,
|
||||||
|
nr_get_rx_antenna_bitmask(g_ctrl.ant_state), g_ctrl.ant_state->num_rx_antennas);
|
||||||
|
}
|
||||||
|
else if (strcmp(p, "PROTECT ANT0 ON") == 0) {
|
||||||
|
nr_set_antenna0_protection(g_ctrl.ant_state, true);
|
||||||
|
snprintf(response, resp_size, "OK: Antenna 0 protection enabled (SSB)\n");
|
||||||
|
}
|
||||||
|
else if (strcmp(p, "PROTECT ANT0 OFF") == 0) {
|
||||||
|
nr_set_antenna0_protection(g_ctrl.ant_state, false);
|
||||||
|
snprintf(response, resp_size, "OK: Antenna 0 protection disabled (WARNING: may cause UE sync loss)\n");
|
||||||
|
}
|
||||||
|
else if (strcmp(p, "SYNC") == 0) {
|
||||||
|
// Synchronize to RU (only works with M-Plane backend)
|
||||||
|
if (nr_antenna_control_sync_to_ru(g_ctrl.ant_state)) {
|
||||||
|
snprintf(response, resp_size, "OK: Synchronized to RU\n");
|
||||||
|
} else {
|
||||||
|
snprintf(response, resp_size, "ERROR: Sync failed (M-Plane backend required)\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(p, "HELP") == 0) {
|
||||||
|
snprintf(response, resp_size,
|
||||||
|
"=== Antenna Control ===\n"
|
||||||
|
"MUTE TX <id> [15|30|45|60] - Mute TX antenna with optional TSM lock\n"
|
||||||
|
"UNMUTE TX <id> [FORCE] - Unmute TX antenna\n"
|
||||||
|
"MUTE/UNMUTE RX <id> - Change RX antenna state\n"
|
||||||
|
"SET TX MASK <hex> [duration] [FORCE] - Set TX mask and TSM lock\n"
|
||||||
|
"SET RX MASK <hex> - Set RX bitmask\n"
|
||||||
|
"STATUS - Show state\n"
|
||||||
|
"STATS - Show statistics\n"
|
||||||
|
"RESET - Unmute all\n"
|
||||||
|
"PROTECT ANT0 ON/OFF\n"
|
||||||
|
"HELP/QUIT\n");
|
||||||
|
}
|
||||||
|
else if (strcmp(p, "QUIT") == 0 || strcmp(p, "EXIT") == 0) {
|
||||||
|
snprintf(response, resp_size, "BYE\n");
|
||||||
|
}
|
||||||
|
else if (strlen(p) == 0) {
|
||||||
|
response[0] = '\0'; // Empty command, no response
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snprintf(response, resp_size, "ERROR: Unknown command '%s'. Type HELP for usage.\n", cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle a single client connection
|
||||||
|
static void handle_client(int client_fd)
|
||||||
|
{
|
||||||
|
char buffer[256];
|
||||||
|
char response[1024];
|
||||||
|
|
||||||
|
// Send welcome message with warning about non-compliance
|
||||||
|
const char *welcome =
|
||||||
|
"=== OAI Antenna Control Test Server ===\n"
|
||||||
|
"WARNING: This is a TEST interface, NOT O-RAN compliant!\n"
|
||||||
|
"For production, use NETCONF via radio/fhi_72/mplane/\n"
|
||||||
|
"Type HELP for commands.\n\n";
|
||||||
|
send(client_fd, welcome, strlen(welcome), 0);
|
||||||
|
|
||||||
|
while (g_ctrl.running) {
|
||||||
|
// Send prompt
|
||||||
|
send(client_fd, "> ", 2, 0);
|
||||||
|
|
||||||
|
// Receive command
|
||||||
|
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
|
||||||
|
if (n <= 0) break;
|
||||||
|
|
||||||
|
buffer[n] = '\0';
|
||||||
|
|
||||||
|
// Process command
|
||||||
|
process_command(buffer, response, sizeof(response));
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
if (strlen(response) > 0) {
|
||||||
|
send(client_fd, response, strlen(response), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for quit
|
||||||
|
if (strstr(buffer, "QUIT") || strstr(buffer, "quit") ||
|
||||||
|
strstr(buffer, "EXIT") || strstr(buffer, "exit")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(client_fd);
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] Client disconnected\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server thread function
|
||||||
|
static void *server_thread(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
struct sockaddr_in client_addr;
|
||||||
|
socklen_t addr_len = sizeof(client_addr);
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] Test server listening on port %d\n", g_ctrl.port);
|
||||||
|
|
||||||
|
while (g_ctrl.running) {
|
||||||
|
// Accept connection with timeout
|
||||||
|
fd_set read_fds;
|
||||||
|
FD_ZERO(&read_fds);
|
||||||
|
FD_SET(g_ctrl.server_fd, &read_fds);
|
||||||
|
|
||||||
|
struct timeval tv = {.tv_sec = 1, .tv_usec = 0};
|
||||||
|
int ret = select(g_ctrl.server_fd + 1, &read_fds, NULL, NULL, &tv);
|
||||||
|
|
||||||
|
if (ret <= 0) continue;
|
||||||
|
|
||||||
|
int client_fd = accept(g_ctrl.server_fd, (struct sockaddr *)&client_addr, &addr_len);
|
||||||
|
if (client_fd < 0) continue;
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] Client connected from %s:%d\n",
|
||||||
|
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
|
||||||
|
|
||||||
|
handle_client(client_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Public API Implementation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state, void *ru_session)
|
||||||
|
{
|
||||||
|
#ifdef OAI_MPLANE
|
||||||
|
if (g_ctrl.running) {
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ant_state == NULL || ru_session == NULL) {
|
||||||
|
LOG_E(PHY, "[ANTENNA_CTRL] Invalid parameters\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ctrl.ant_state = ant_state;
|
||||||
|
g_ctrl.ru_session = (ru_session_t *)ru_session;
|
||||||
|
g_ctrl.backend = ANTENNA_CTRL_BACKEND_MPLANE;
|
||||||
|
g_ctrl.running = true;
|
||||||
|
|
||||||
|
// Check if RU supports TRX control feature
|
||||||
|
if (!mplane_supports_trx_control(g_ctrl.ru_session)) {
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] RU does not support MPLANE-TRX-CONTROL feature\n");
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] Antenna muting may not work on this RU\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] Initialized with O-RAN M-Plane NETCONF backend\n");
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] TX array: %s, RX array: %s\n",
|
||||||
|
ant_state->tx_array_name, ant_state->rx_array_name);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
LOG_E(PHY, "[ANTENNA_CTRL] M-Plane support not compiled in (OAI_MPLANE not defined)\n");
|
||||||
|
LOG_E(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_start_test_server() for testing\n");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port)
|
||||||
|
{
|
||||||
|
if (g_ctrl.running) {
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ant_state == NULL) {
|
||||||
|
LOG_E(PHY, "[ANTENNA_CTRL] Antenna state is NULL\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n");
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] Starting TEST socket server - NOT O-RAN COMPLIANT!\n");
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] For production O-RAN compliance, use M-Plane NETCONF\n");
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] See: radio/fhi_72/mplane/antenna-control-mplane.h\n");
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n");
|
||||||
|
|
||||||
|
g_ctrl.ant_state = ant_state;
|
||||||
|
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_TEST_PORT;
|
||||||
|
g_ctrl.backend = ANTENNA_CTRL_BACKEND_TEST_SOCKET;
|
||||||
|
|
||||||
|
// Create socket
|
||||||
|
g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (g_ctrl.server_fd < 0) {
|
||||||
|
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create socket: %s\n", strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow address reuse
|
||||||
|
int opt = 1;
|
||||||
|
setsockopt(g_ctrl.server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
|
||||||
|
// Bind
|
||||||
|
struct sockaddr_in addr = {
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_addr.s_addr = htonl(INADDR_ANY),
|
||||||
|
.sin_port = htons(g_ctrl.port)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bind(g_ctrl.server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||||
|
LOG_E(PHY, "[ANTENNA_CTRL] Failed to bind port %d: %s\n", g_ctrl.port, strerror(errno));
|
||||||
|
close(g_ctrl.server_fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen
|
||||||
|
if (listen(g_ctrl.server_fd, 1) < 0) {
|
||||||
|
LOG_E(PHY, "[ANTENNA_CTRL] Failed to listen: %s\n", strerror(errno));
|
||||||
|
close(g_ctrl.server_fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start server thread
|
||||||
|
g_ctrl.running = true;
|
||||||
|
if (pthread_create(&g_ctrl.thread, NULL, server_thread, NULL) != 0) {
|
||||||
|
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create server thread\n");
|
||||||
|
g_ctrl.running = false;
|
||||||
|
close(g_ctrl.server_fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] Test server started on port %d\n", g_ctrl.port);
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] Connect with: nc localhost %d\n", g_ctrl.port);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
|
||||||
|
{
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] nr_antenna_control_start() is DEPRECATED\n");
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_init_mplane() for O-RAN compliance\n");
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] or nr_antenna_control_start_test_server() for testing\n");
|
||||||
|
|
||||||
|
return nr_antenna_control_start_test_server(ant_state, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_antenna_control_stop(void)
|
||||||
|
{
|
||||||
|
if (!g_ctrl.running) return;
|
||||||
|
|
||||||
|
g_ctrl.running = false;
|
||||||
|
|
||||||
|
if (g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET) {
|
||||||
|
// Close server socket to unblock accept()
|
||||||
|
if (g_ctrl.server_fd >= 0) {
|
||||||
|
close(g_ctrl.server_fd);
|
||||||
|
g_ctrl.server_fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for thread
|
||||||
|
pthread_join(g_ctrl.thread, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ctrl.backend = ANTENNA_CTRL_BACKEND_NONE;
|
||||||
|
g_ctrl.ant_state = NULL;
|
||||||
|
g_ctrl.ru_session = NULL;
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] Control interface stopped\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nr_antenna_control_is_running(void)
|
||||||
|
{
|
||||||
|
return g_ctrl.running;
|
||||||
|
}
|
||||||
|
|
||||||
|
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void)
|
||||||
|
{
|
||||||
|
return g_ctrl.backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state)
|
||||||
|
{
|
||||||
|
#ifdef OAI_MPLANE
|
||||||
|
if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) {
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync to RU\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ant_state == NULL) {
|
||||||
|
ant_state = g_ctrl.ant_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ant_state == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send TX antenna mask to RU
|
||||||
|
antenna_ctrl_result_t result;
|
||||||
|
result = mplane_set_tx_antenna_mask(g_ctrl.ru_session,
|
||||||
|
ant_state->tx_array_name,
|
||||||
|
nr_get_tx_antenna_bitmask(ant_state),
|
||||||
|
ant_state->num_tx_antennas);
|
||||||
|
if (result != ANTENNA_CTRL_SUCCESS) {
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync TX mask: %s\n",
|
||||||
|
antenna_ctrl_result_str(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send RX antenna mask to RU
|
||||||
|
result = mplane_set_rx_antenna_mask(g_ctrl.ru_session,
|
||||||
|
ant_state->rx_array_name,
|
||||||
|
nr_get_rx_antenna_bitmask(ant_state),
|
||||||
|
ant_state->num_rx_antennas);
|
||||||
|
if (result != ANTENNA_CTRL_SUCCESS) {
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync RX mask: %s\n",
|
||||||
|
antenna_ctrl_result_str(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] Successfully synchronized antenna masks to RU\n");
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state)
|
||||||
|
{
|
||||||
|
#ifdef OAI_MPLANE
|
||||||
|
if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) {
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync from RU\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ant_state == NULL) {
|
||||||
|
ant_state = g_ctrl.ant_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ant_state == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t tx_mask, rx_mask;
|
||||||
|
antenna_ctrl_result_t result;
|
||||||
|
|
||||||
|
// Get TX antenna mask from RU
|
||||||
|
result = mplane_get_tx_antenna_mask(g_ctrl.ru_session,
|
||||||
|
ant_state->tx_array_name,
|
||||||
|
&tx_mask);
|
||||||
|
if (result == ANTENNA_CTRL_SUCCESS) {
|
||||||
|
nr_set_tx_antenna_bitmask(ant_state, tx_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get RX antenna mask from RU
|
||||||
|
result = mplane_get_rx_antenna_mask(g_ctrl.ru_session,
|
||||||
|
ant_state->rx_array_name,
|
||||||
|
&rx_mask);
|
||||||
|
if (result == ANTENNA_CTRL_SUCCESS) {
|
||||||
|
nr_set_rx_antenna_bitmask(ant_state, rx_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_CTRL] Synchronized antenna masks from RU: TX=0x%02X, RX=0x%02X\n",
|
||||||
|
tx_mask, rx_mask);
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#ifndef NR_ANTENNA_CONTROL_H
|
||||||
|
#define NR_ANTENNA_CONTROL_H
|
||||||
|
|
||||||
|
#include "nr_antenna_state.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Default port for the test socket server (development mode only)
|
||||||
|
#define NR_ANTENNA_CONTROL_TEST_PORT 9999
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Antenna control backend type
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
ANTENNA_CTRL_BACKEND_NONE = 0, ///< No external control
|
||||||
|
ANTENNA_CTRL_BACKEND_MPLANE, ///< O-RAN M-Plane NETCONF (production)
|
||||||
|
ANTENNA_CTRL_BACKEND_TEST_SOCKET ///< Test socket interface (development)
|
||||||
|
} nr_antenna_ctrl_backend_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize antenna control with O-RAN M-Plane NETCONF backend
|
||||||
|
*
|
||||||
|
* This is the O-RAN compliant production interface. It connects the
|
||||||
|
* antenna state to a real NETCONF session to an O-RU.
|
||||||
|
*
|
||||||
|
* When antenna masks are changed via nr_set_tx_antenna_bitmask() or
|
||||||
|
* similar functions, the changes are automatically propagated to the
|
||||||
|
* RU via NETCONF edit-config operations.
|
||||||
|
*
|
||||||
|
* @param ant_state Pointer to the antenna state to control
|
||||||
|
* @param ru_session Active M-Plane NETCONF session (from radio/fhi_72/mplane/)
|
||||||
|
* @return true if initialization successful
|
||||||
|
*/
|
||||||
|
bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state,
|
||||||
|
void *ru_session);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start the test socket server (development mode only)
|
||||||
|
*
|
||||||
|
* Creates a simple TCP server for testing antenna control without
|
||||||
|
* NETCONF infrastructure. This is NOT O-RAN compliant.
|
||||||
|
*
|
||||||
|
* Commands supported (for backwards compatibility):
|
||||||
|
* MUTE TX <ant_id> [15|30|45|60] - Mute TX antenna with optional TSM lock
|
||||||
|
* UNMUTE TX <ant_id> [FORCE] - Unmute TX antenna; FORCE breaks lock
|
||||||
|
* MUTE RX <ant_id> - Mute RX antenna
|
||||||
|
* UNMUTE RX <ant_id> - Unmute RX antenna
|
||||||
|
* SET TX MASK <hex> [duration] [FORCE] - Set TX mask and per-chain TSM lock
|
||||||
|
* SET RX MASK <hex> - Set RX antenna bitmask
|
||||||
|
* STATUS - Get current antenna state
|
||||||
|
* STATS - Get energy saving statistics
|
||||||
|
* RESET STATS - Reset statistics
|
||||||
|
* QUIT - Close connection
|
||||||
|
*
|
||||||
|
* @param ant_state Pointer to the antenna state to control
|
||||||
|
* @param port TCP port to listen on (use 0 for default)
|
||||||
|
* @return true if server started successfully
|
||||||
|
*
|
||||||
|
* @note This function logs a warning that it's not O-RAN compliant
|
||||||
|
*/
|
||||||
|
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Legacy function - start antenna control server
|
||||||
|
*
|
||||||
|
* @deprecated Use nr_antenna_control_init_mplane() for O-RAN compliance
|
||||||
|
* or nr_antenna_control_start_test_server() for testing.
|
||||||
|
*
|
||||||
|
* This function starts the test socket server for backwards compatibility.
|
||||||
|
* It logs a deprecation warning.
|
||||||
|
*/
|
||||||
|
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stop the antenna control interface
|
||||||
|
*
|
||||||
|
* Stops the test socket server if running.
|
||||||
|
* For M-Plane backend, this disconnects from NETCONF notifications.
|
||||||
|
*/
|
||||||
|
void nr_antenna_control_stop(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if antenna control is active
|
||||||
|
* @return true if control interface is running
|
||||||
|
*/
|
||||||
|
bool nr_antenna_control_is_running(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current antenna control backend type
|
||||||
|
* @return Backend type currently in use
|
||||||
|
*/
|
||||||
|
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Synchronize local antenna state to RU via M-Plane NETCONF
|
||||||
|
*
|
||||||
|
* Sends the current antenna bitmask configuration to the RU.
|
||||||
|
* Only applicable when using the M-Plane NETCONF backend.
|
||||||
|
*
|
||||||
|
* @param ant_state Pointer to the antenna state
|
||||||
|
* @return true if synchronization successful or not applicable
|
||||||
|
*/
|
||||||
|
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Synchronize RU antenna state to local via M-Plane NETCONF
|
||||||
|
*
|
||||||
|
* Reads the current antenna bitmask from the RU and updates local state.
|
||||||
|
* Only applicable when using the M-Plane NETCONF backend.
|
||||||
|
*
|
||||||
|
* @param ant_state Pointer to the antenna state
|
||||||
|
* @return true if synchronization successful or not applicable
|
||||||
|
*/
|
||||||
|
bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* NR_ANTENNA_CONTROL_H */
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
#include "nr_antenna_state.h"
|
||||||
|
#include "common/utils/LOG/log.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
// Helper to count set bits in a byte
|
||||||
|
static inline int popcount8(uint8_t x) {
|
||||||
|
int count = 0;
|
||||||
|
while (x) {
|
||||||
|
count += x & 1;
|
||||||
|
x >>= 1;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current timestamp in microseconds
|
||||||
|
static inline uint64_t get_timestamp_us(void) {
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_init_antenna_state(nr_antenna_state_t *state,
|
||||||
|
uint8_t num_tx,
|
||||||
|
uint8_t num_rx,
|
||||||
|
const char *tx_array_name,
|
||||||
|
const char *rx_array_name)
|
||||||
|
{
|
||||||
|
if (state == NULL) return;
|
||||||
|
|
||||||
|
memset(state, 0, sizeof(nr_antenna_state_t));
|
||||||
|
|
||||||
|
state->num_tx_antennas = (num_tx > NR_MAX_ANTENNAS) ? NR_MAX_ANTENNAS : num_tx;
|
||||||
|
state->num_rx_antennas = (num_rx > NR_MAX_ANTENNAS) ? NR_MAX_ANTENNAS : num_rx;
|
||||||
|
|
||||||
|
// Initialize with all configured antennas active
|
||||||
|
// Create bitmask with num_tx/num_rx lowest bits set
|
||||||
|
state->tx_antenna_bitmask = (1 << state->num_tx_antennas) - 1;
|
||||||
|
state->rx_antenna_bitmask = (1 << state->num_rx_antennas) - 1;
|
||||||
|
|
||||||
|
// Set O-RAN array names
|
||||||
|
if (tx_array_name) {
|
||||||
|
strncpy(state->tx_array_name, tx_array_name, sizeof(state->tx_array_name) - 1);
|
||||||
|
} else {
|
||||||
|
strncpy(state->tx_array_name, "tx-array-0", sizeof(state->tx_array_name) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rx_array_name) {
|
||||||
|
strncpy(state->rx_array_name, rx_array_name, sizeof(state->rx_array_name) - 1);
|
||||||
|
} else {
|
||||||
|
strncpy(state->rx_array_name, "rx-array-0", sizeof(state->rx_array_name) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_init(&state->lock, NULL);
|
||||||
|
|
||||||
|
state->stats.last_update_timestamp = get_timestamp_us();
|
||||||
|
|
||||||
|
// Initialize recovery mode
|
||||||
|
state->recovery.in_recovery = false;
|
||||||
|
state->recovery.recovery_start_ts = 0;
|
||||||
|
state->recovery.recovery_duration_ms = 500; // 500ms default recovery time
|
||||||
|
|
||||||
|
// Protect antenna 0 by default (needed for SSB/PSS/SSS)
|
||||||
|
state->config.protect_antenna0 = true;
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Initialized: TX antennas=%d (mask=0x%02X, array=%s), "
|
||||||
|
"RX antennas=%d (mask=0x%02X, array=%s), antenna0_protected=%d\n",
|
||||||
|
state->num_tx_antennas, state->tx_antenna_bitmask, state->tx_array_name,
|
||||||
|
state->num_rx_antennas, state->rx_antenna_bitmask, state->rx_array_name,
|
||||||
|
state->config.protect_antenna0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_free_antenna_state(nr_antenna_state_t *state)
|
||||||
|
{
|
||||||
|
if (state == NULL) return;
|
||||||
|
pthread_mutex_destroy(&state->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
|
||||||
|
{
|
||||||
|
if (state == NULL) return 0;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&state->lock);
|
||||||
|
|
||||||
|
uint8_t old_bitmask = state->tx_antenna_bitmask;
|
||||||
|
|
||||||
|
// Mask to only valid antenna bits
|
||||||
|
uint8_t valid_mask = (1 << state->num_tx_antennas) - 1;
|
||||||
|
state->tx_antenna_bitmask = bitmask & valid_mask;
|
||||||
|
|
||||||
|
// Update statistics
|
||||||
|
if (state->tx_antenna_bitmask != old_bitmask) {
|
||||||
|
state->stats.tx_mute_events++;
|
||||||
|
state->stats.last_update_timestamp = get_timestamp_us();
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] TX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
|
||||||
|
old_bitmask, state->tx_antenna_bitmask, state->tx_array_name,
|
||||||
|
popcount8(state->tx_antenna_bitmask), state->num_tx_antennas);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&state->lock);
|
||||||
|
|
||||||
|
return old_bitmask;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
|
||||||
|
{
|
||||||
|
if (state == NULL) return 0;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&state->lock);
|
||||||
|
|
||||||
|
uint8_t old_bitmask = state->rx_antenna_bitmask;
|
||||||
|
|
||||||
|
// Mask to only valid antenna bits
|
||||||
|
uint8_t valid_mask = (1 << state->num_rx_antennas) - 1;
|
||||||
|
state->rx_antenna_bitmask = bitmask & valid_mask;
|
||||||
|
|
||||||
|
// Update statistics
|
||||||
|
if (state->rx_antenna_bitmask != old_bitmask) {
|
||||||
|
state->stats.rx_mute_events++;
|
||||||
|
state->stats.last_update_timestamp = get_timestamp_us();
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] RX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
|
||||||
|
old_bitmask, state->rx_antenna_bitmask, state->rx_array_name,
|
||||||
|
popcount8(state->rx_antenna_bitmask), state->num_rx_antennas);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&state->lock);
|
||||||
|
|
||||||
|
return old_bitmask;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t nr_get_tx_antenna_bitmask(const nr_antenna_state_t *state)
|
||||||
|
{
|
||||||
|
if (state == NULL) return 0xFF;
|
||||||
|
pthread_mutex_lock((pthread_mutex_t*)&state->lock);
|
||||||
|
uint8_t m = state->tx_antenna_bitmask;
|
||||||
|
pthread_mutex_unlock((pthread_mutex_t*)&state->lock);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state)
|
||||||
|
{
|
||||||
|
if (state == NULL) return 0xFF;
|
||||||
|
pthread_mutex_lock((pthread_mutex_t*)&state->lock);
|
||||||
|
uint8_t m = state->rx_antenna_bitmask;
|
||||||
|
pthread_mutex_unlock((pthread_mutex_t*)&state->lock);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nr_is_tx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id)
|
||||||
|
{
|
||||||
|
if (state == NULL || ant_id >= state->num_tx_antennas) return false;
|
||||||
|
return (state->tx_antenna_bitmask & (1 << ant_id)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nr_is_rx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id)
|
||||||
|
{
|
||||||
|
if (state == NULL || ant_id >= state->num_rx_antennas) return false;
|
||||||
|
return (state->rx_antenna_bitmask & (1 << ant_id)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nr_get_active_tx_antenna_count(const nr_antenna_state_t *state)
|
||||||
|
{
|
||||||
|
if (state == NULL) return 0;
|
||||||
|
return popcount8(state->tx_antenna_bitmask);
|
||||||
|
}
|
||||||
|
|
||||||
|
int nr_get_active_rx_antenna_count(const nr_antenna_state_t *state)
|
||||||
|
{
|
||||||
|
if (state == NULL) return 0;
|
||||||
|
return popcount8(state->rx_antenna_bitmask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
|
||||||
|
{
|
||||||
|
if (state == NULL || ant_id >= state->num_tx_antennas) return;
|
||||||
|
|
||||||
|
// Protect antenna 0 if configured (needed for SSB/PSS/SSS transmission)
|
||||||
|
if (ant_id == 0 && state->config.protect_antenna0) {
|
||||||
|
LOG_W(PHY, "[ANTENNA_STATE] Cannot mute TX antenna 0: protected for SSB transmission\n");
|
||||||
|
LOG_W(PHY, "[ANTENNA_STATE] Use 'SET TX MASK' to override protection if needed\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t new_mask = state->tx_antenna_bitmask & ~(1 << ant_id);
|
||||||
|
nr_set_tx_antenna_bitmask(state, new_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
|
||||||
|
{
|
||||||
|
if (state == NULL || ant_id >= state->num_tx_antennas) return;
|
||||||
|
|
||||||
|
uint8_t new_mask = state->tx_antenna_bitmask | (1 << ant_id);
|
||||||
|
nr_set_tx_antenna_bitmask(state, new_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
|
||||||
|
{
|
||||||
|
if (state == NULL || ant_id >= state->num_rx_antennas) return;
|
||||||
|
|
||||||
|
uint8_t new_mask = state->rx_antenna_bitmask & ~(1 << ant_id);
|
||||||
|
nr_set_rx_antenna_bitmask(state, new_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
|
||||||
|
{
|
||||||
|
if (state == NULL || ant_id >= state->num_rx_antennas) return;
|
||||||
|
|
||||||
|
uint8_t new_mask = state->rx_antenna_bitmask | (1 << ant_id);
|
||||||
|
nr_set_rx_antenna_bitmask(state, new_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers)
|
||||||
|
{
|
||||||
|
if (state == NULL)
|
||||||
|
return requested_layers;
|
||||||
|
|
||||||
|
// Reduce MIMO layers to match the number of active TX antennas.
|
||||||
|
// When antennas are muted, the RU blanks the corresponding TX buffers.
|
||||||
|
// If the scheduler still uses full layer count, the precoded signal is
|
||||||
|
// distributed across all antenna ports (including muted ones), which
|
||||||
|
// corrupts the UE's MIMO reception. By reducing layers to match active
|
||||||
|
// antennas, the PMI fallback to identity precoding maps layers 1:1 to
|
||||||
|
// active ports, avoiding signal loss on muted ports.
|
||||||
|
int active = nr_get_active_tx_antenna_count(state);
|
||||||
|
if (active > 0 && active < requested_layers)
|
||||||
|
return active;
|
||||||
|
return requested_layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_log_antenna_state(const nr_antenna_state_t *state)
|
||||||
|
{
|
||||||
|
if (state == NULL) {
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] State is NULL\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] === Antenna State Report ===\n");
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] TX Array: %s\n", state->tx_array_name);
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Configured: %d antennas\n", state->num_tx_antennas);
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Bitmask: 0x%02X\n", state->tx_antenna_bitmask);
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Active: %d antennas\n", nr_get_active_tx_antenna_count(state));
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Individual: ");
|
||||||
|
for (int i = 0; i < state->num_tx_antennas; i++) {
|
||||||
|
LOG_I(PHY, "TX%d=%s ", i, nr_is_tx_antenna_active(state, i) ? "ON" : "OFF");
|
||||||
|
}
|
||||||
|
LOG_I(PHY, "\n");
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] RX Array: %s\n", state->rx_array_name);
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Configured: %d antennas\n", state->num_rx_antennas);
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Bitmask: 0x%02X\n", state->rx_antenna_bitmask);
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Active: %d antennas\n", nr_get_active_rx_antenna_count(state));
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Statistics:\n");
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] TX mute events: %lu\n", state->stats.tx_mute_events);
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] RX mute events: %lu\n", state->stats.rx_mute_events);
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Total muted slots: %lu\n", state->stats.total_muted_slots);
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] =============================\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_get_antenna_stats(const nr_antenna_state_t *state,
|
||||||
|
uint64_t *tx_mute_events,
|
||||||
|
uint64_t *rx_mute_events,
|
||||||
|
uint64_t *total_muted_slots)
|
||||||
|
{
|
||||||
|
if (state == NULL) return;
|
||||||
|
|
||||||
|
if (tx_mute_events) *tx_mute_events = state->stats.tx_mute_events;
|
||||||
|
if (rx_mute_events) *rx_mute_events = state->stats.rx_mute_events;
|
||||||
|
if (total_muted_slots) *total_muted_slots = state->stats.total_muted_slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_reset_antenna_stats(nr_antenna_state_t *state)
|
||||||
|
{
|
||||||
|
if (state == NULL) return;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&state->lock);
|
||||||
|
|
||||||
|
state->stats.tx_mute_events = 0;
|
||||||
|
state->stats.rx_mute_events = 0;
|
||||||
|
state->stats.total_muted_slots = 0;
|
||||||
|
state->stats.energy_saved_estimate = 0;
|
||||||
|
state->stats.last_update_timestamp = get_timestamp_us();
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Statistics reset\n");
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&state->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_unmute_all_tx_antennas(nr_antenna_state_t *state)
|
||||||
|
{
|
||||||
|
if (state == NULL) return;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&state->lock);
|
||||||
|
|
||||||
|
uint8_t old_mask = state->tx_antenna_bitmask;
|
||||||
|
uint8_t all_on = (1 << state->num_tx_antennas) - 1;
|
||||||
|
state->tx_antenna_bitmask = all_on;
|
||||||
|
|
||||||
|
// Enter recovery mode if we were muted
|
||||||
|
if (old_mask != all_on) {
|
||||||
|
state->recovery.in_recovery = true;
|
||||||
|
state->recovery.recovery_start_ts = get_timestamp_us();
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Entering recovery mode for %u ms after unmuting TX antennas\n",
|
||||||
|
state->recovery.recovery_duration_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
state->stats.tx_mute_events++;
|
||||||
|
state->stats.last_update_timestamp = get_timestamp_us();
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] All TX antennas unmuted: 0x%02X -> 0x%02X (array=%s)\n",
|
||||||
|
old_mask, all_on, state->tx_array_name);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&state->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_unmute_all_rx_antennas(nr_antenna_state_t *state)
|
||||||
|
{
|
||||||
|
if (state == NULL) return;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&state->lock);
|
||||||
|
|
||||||
|
uint8_t old_mask = state->rx_antenna_bitmask;
|
||||||
|
uint8_t all_on = (1 << state->num_rx_antennas) - 1;
|
||||||
|
state->rx_antenna_bitmask = all_on;
|
||||||
|
|
||||||
|
state->stats.rx_mute_events++;
|
||||||
|
state->stats.last_update_timestamp = get_timestamp_us();
|
||||||
|
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] All RX antennas unmuted: 0x%02X -> 0x%02X (array=%s)\n",
|
||||||
|
old_mask, all_on, state->rx_array_name);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&state->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nr_is_in_recovery_mode(const nr_antenna_state_t *state)
|
||||||
|
{
|
||||||
|
if (state == NULL) return false;
|
||||||
|
|
||||||
|
if (!state->recovery.in_recovery) return false;
|
||||||
|
|
||||||
|
// Check if recovery period has elapsed
|
||||||
|
uint64_t now = get_timestamp_us();
|
||||||
|
uint64_t elapsed_us = now - state->recovery.recovery_start_ts;
|
||||||
|
uint64_t duration_us = (uint64_t)state->recovery.recovery_duration_ms * 1000ULL;
|
||||||
|
|
||||||
|
if (elapsed_us >= duration_us) {
|
||||||
|
// Recovery complete - cast away const for state update
|
||||||
|
nr_antenna_state_t *s = (nr_antenna_state_t *)state;
|
||||||
|
s->recovery.in_recovery = false;
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Recovery mode complete after %lu ms\n", elapsed_us / 1000);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nr_set_antenna0_protection(nr_antenna_state_t *state, bool protect)
|
||||||
|
{
|
||||||
|
if (state == NULL) return;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&state->lock);
|
||||||
|
state->config.protect_antenna0 = protect;
|
||||||
|
LOG_I(PHY, "[ANTENNA_STATE] Antenna 0 protection %s\n", protect ? "enabled" : "disabled");
|
||||||
|
pthread_mutex_unlock(&state->lock);
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
#ifndef NR_ANTENNA_STATE_H
|
||||||
|
#define NR_ANTENNA_STATE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Maximum number of physical antennas supported
|
||||||
|
#define NR_MAX_ANTENNAS 8
|
||||||
|
|
||||||
|
/// O-RAN compliant antenna state for TX/RX arrays
|
||||||
|
/// Mirrors o-ran-uplane-conf.yang: tx-array-antenna-mask-config
|
||||||
|
typedef struct {
|
||||||
|
/// Antenna bitmask per O-RAN WG4 spec: bit N = 1 means antenna N is active
|
||||||
|
/// Corresponds to o-ran-uplane-conf:antenna-bitmask
|
||||||
|
uint8_t tx_antenna_bitmask;
|
||||||
|
uint8_t rx_antenna_bitmask;
|
||||||
|
|
||||||
|
/// Total number of configured antennas
|
||||||
|
uint8_t num_tx_antennas;
|
||||||
|
uint8_t num_rx_antennas;
|
||||||
|
|
||||||
|
/// Thread safety for runtime updates
|
||||||
|
pthread_mutex_t lock;
|
||||||
|
|
||||||
|
/// Energy saving statistics (for research/monitoring)
|
||||||
|
struct {
|
||||||
|
uint64_t tx_mute_events; ///< Number of TX mute operations
|
||||||
|
uint64_t rx_mute_events; ///< Number of RX mute operations
|
||||||
|
uint64_t total_muted_slots; ///< Total slots with at least one antenna muted
|
||||||
|
uint64_t energy_saved_estimate; ///< Estimated energy saved (arbitrary units)
|
||||||
|
uint64_t last_update_timestamp; ///< Last update time (for O1 notifications)
|
||||||
|
} stats;
|
||||||
|
|
||||||
|
/// O-RAN compliance: array name for NETCONF identification
|
||||||
|
char tx_array_name[64];
|
||||||
|
char rx_array_name[64];
|
||||||
|
|
||||||
|
/// Recovery mode: after unmuting, allow CSI to update before using full capacity
|
||||||
|
struct {
|
||||||
|
bool in_recovery; ///< Currently in recovery mode
|
||||||
|
uint64_t recovery_start_ts; ///< Timestamp when recovery started (us)
|
||||||
|
uint32_t recovery_duration_ms; ///< Recovery duration in milliseconds (default 500ms)
|
||||||
|
} recovery;
|
||||||
|
|
||||||
|
/// Configuration flags
|
||||||
|
struct {
|
||||||
|
bool protect_antenna0; ///< Never mute antenna 0 (needed for SSB)
|
||||||
|
} config;
|
||||||
|
|
||||||
|
} nr_antenna_state_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize antenna state with all antennas active
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param num_tx Number of TX antennas
|
||||||
|
* @param num_rx Number of RX antennas
|
||||||
|
* @param tx_array_name O-RAN array name for TX (e.g., "tx-array-0")
|
||||||
|
* @param rx_array_name O-RAN array name for RX (e.g., "rx-array-0")
|
||||||
|
*/
|
||||||
|
void nr_init_antenna_state(nr_antenna_state_t *state,
|
||||||
|
uint8_t num_tx,
|
||||||
|
uint8_t num_rx,
|
||||||
|
const char *tx_array_name,
|
||||||
|
const char *rx_array_name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free antenna state resources
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
*/
|
||||||
|
void nr_free_antenna_state(nr_antenna_state_t *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set TX antenna bitmask (O-RAN O1 interface equivalent)
|
||||||
|
*
|
||||||
|
* This function implements the O-RAN WG4 MPLANE-TRX-CONTROL feature.
|
||||||
|
* Corresponds to NETCONF edit-config on tx-array-antenna-mask-config.
|
||||||
|
*
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param bitmask Antenna bitmask (bit N = 1 means antenna N active)
|
||||||
|
* @return Previous bitmask value
|
||||||
|
*/
|
||||||
|
uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set RX antenna bitmask (O-RAN O1 interface equivalent)
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param bitmask Antenna bitmask (bit N = 1 means antenna N active)
|
||||||
|
* @return Previous bitmask value
|
||||||
|
*/
|
||||||
|
uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current TX antenna bitmask
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @return Current TX antenna bitmask
|
||||||
|
*/
|
||||||
|
uint8_t nr_get_tx_antenna_bitmask(const nr_antenna_state_t *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current RX antenna bitmask
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @return Current RX antenna bitmask
|
||||||
|
*/
|
||||||
|
uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a specific TX antenna is active
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param ant_id Antenna index (0-based)
|
||||||
|
* @return true if antenna is active, false if muted
|
||||||
|
*/
|
||||||
|
bool nr_is_tx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a specific RX antenna is active
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param ant_id Antenna index (0-based)
|
||||||
|
* @return true if antenna is active, false if muted
|
||||||
|
*/
|
||||||
|
bool nr_is_rx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get count of currently active TX antennas
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @return Number of active TX antennas
|
||||||
|
*/
|
||||||
|
int nr_get_active_tx_antenna_count(const nr_antenna_state_t *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get count of currently active RX antennas
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @return Number of active RX antennas
|
||||||
|
*/
|
||||||
|
int nr_get_active_rx_antenna_count(const nr_antenna_state_t *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mute a specific TX antenna
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param ant_id Antenna index to mute (0-based)
|
||||||
|
*/
|
||||||
|
void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmute a specific TX antenna
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param ant_id Antenna index to unmute (0-based)
|
||||||
|
*/
|
||||||
|
void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mute a specific RX antenna
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param ant_id Antenna index to mute (0-based)
|
||||||
|
*/
|
||||||
|
void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmute a specific RX antenna
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param ant_id Antenna index to unmute (0-based)
|
||||||
|
*/
|
||||||
|
void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get effective number of MIMO layers based on active antennas
|
||||||
|
*
|
||||||
|
* For MIMO, the number of layers is limited by active antennas.
|
||||||
|
* This function returns min(requested_layers, active_antennas).
|
||||||
|
*
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param requested_layers Originally requested number of layers
|
||||||
|
* @return Effective number of layers that can be used
|
||||||
|
*/
|
||||||
|
int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Log antenna state for debugging
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
*/
|
||||||
|
void nr_log_antenna_state(const nr_antenna_state_t *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get energy saving statistics
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param tx_mute_events Output: number of TX mute events
|
||||||
|
* @param rx_mute_events Output: number of RX mute events
|
||||||
|
* @param total_muted_slots Output: total slots with muting
|
||||||
|
*/
|
||||||
|
void nr_get_antenna_stats(const nr_antenna_state_t *state,
|
||||||
|
uint64_t *tx_mute_events,
|
||||||
|
uint64_t *rx_mute_events,
|
||||||
|
uint64_t *total_muted_slots);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset energy saving statistics
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
*/
|
||||||
|
void nr_reset_antenna_stats(nr_antenna_state_t *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmute all TX antennas and enter recovery mode
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
*
|
||||||
|
* This initiates a recovery period during which the scheduler
|
||||||
|
* uses conservative settings to allow UE CSI reports to update.
|
||||||
|
*/
|
||||||
|
void nr_unmute_all_tx_antennas(nr_antenna_state_t *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmute all RX antennas
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
*/
|
||||||
|
void nr_unmute_all_rx_antennas(nr_antenna_state_t *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if system is in recovery mode after unmuting
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @return true if in recovery mode
|
||||||
|
*/
|
||||||
|
bool nr_is_in_recovery_mode(const nr_antenna_state_t *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set antenna 0 protection (SSB antenna)
|
||||||
|
* @param state Pointer to antenna state structure
|
||||||
|
* @param protect true to prevent muting antenna 0
|
||||||
|
*/
|
||||||
|
void nr_set_antenna0_protection(nr_antenna_state_t *state, bool protect);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* NR_ANTENNA_STATE_H */
|
||||||
|
|
||||||
@@ -17,9 +17,55 @@
|
|||||||
#include "executables/softmodem-common.h"
|
#include "executables/softmodem-common.h"
|
||||||
#include "SCHED_NR/sched_nr.h"
|
#include "SCHED_NR/sched_nr.h"
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <simde/x86/avx512.h>
|
#include <simde/x86/avx512.h>
|
||||||
#define USE128BIT
|
#define USE128BIT
|
||||||
|
|
||||||
|
static pthread_once_t tx_layer_fanout_once = PTHREAD_ONCE_INIT;
|
||||||
|
static bool tx_layer_fanout = false;
|
||||||
|
|
||||||
|
static void configure_tx_layer_fanout(void)
|
||||||
|
{
|
||||||
|
const char *value = getenv("OAI_TX_LAYER_FANOUT");
|
||||||
|
tx_layer_fanout = value != NULL && strcmp(value, "1") == 0;
|
||||||
|
if (tx_layer_fanout)
|
||||||
|
LOG_I(PHY,
|
||||||
|
"[8TX_FANOUT] Fixed identity-layer fan-out enabled; "
|
||||||
|
"all active physical TX ports carry PDSCH\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tx_layer_fanout_enabled(void)
|
||||||
|
{
|
||||||
|
pthread_once(&tx_layer_fanout_once, configure_tx_layer_fanout);
|
||||||
|
return tx_layer_fanout;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copy_normalized_layer(c16_t *dst,
|
||||||
|
const c16_t *src,
|
||||||
|
int count,
|
||||||
|
int replicas)
|
||||||
|
{
|
||||||
|
// Q15 approximations of 1/sqrt(n), n=1..8. Normalizing each duplicated
|
||||||
|
// layer keeps total frequency-domain energy comparable with the 4TX
|
||||||
|
// identity mapping instead of giving 8TX an artificial power advantage.
|
||||||
|
static const int16_t gain_q15[] = {
|
||||||
|
32767, 23170, 18919, 16384, 14654, 13377, 12384, 11585};
|
||||||
|
AssertFatal(replicas >= 1 && replicas <= 8,
|
||||||
|
"Invalid 8TX layer replica count %d\n",
|
||||||
|
replicas);
|
||||||
|
if (replicas == 1) {
|
||||||
|
memcpy(dst, src, count * sizeof(*dst));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int32_t gain = gain_q15[replicas - 1];
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
dst[i].r = ((int32_t)src[i].r * gain) >> 15;
|
||||||
|
dst[i].i = ((int32_t)src[i].i * gain) >> 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void nr_pdsch_codeword_scrambling(uint8_t *in, uint32_t size, uint8_t q, uint32_t Nid, uint32_t n_RNTI, uint32_t *out)
|
static void nr_pdsch_codeword_scrambling(uint8_t *in, uint32_t size, uint8_t q, uint32_t Nid, uint32_t n_RNTI, uint32_t *out)
|
||||||
{
|
{
|
||||||
nr_codeword_scrambling(in, size, q, Nid, n_RNTI, out);
|
nr_codeword_scrambling(in, size, q, Nid, n_RNTI, out);
|
||||||
@@ -436,6 +482,49 @@ static inline void do_txdataF(c16_t **txdataF,
|
|||||||
int txdataF_offset_per_symbol)
|
int txdataF_offset_per_symbol)
|
||||||
{
|
{
|
||||||
NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms;
|
NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms;
|
||||||
|
// nakaolab Patch 8: identity-precoding layer-to-antenna remapping.
|
||||||
|
// When antennas are muted, layer N must NOT be sent to muted antenna N
|
||||||
|
// (the layer's data would be lost on the blanked port). Instead, find
|
||||||
|
// this antenna's rank among active antennas — that's the layer index it
|
||||||
|
// should receive. Muted antennas get zeros.
|
||||||
|
const nr_antenna_state_t *ast = &gNB->antenna_state;
|
||||||
|
int identity_layer;
|
||||||
|
int identity_replicas = 1;
|
||||||
|
int active_count = nr_get_active_tx_antenna_count(ast);
|
||||||
|
const bool fanout = frame_parms->nb_antennas_tx == 8
|
||||||
|
&& tx_layer_fanout_enabled();
|
||||||
|
if (active_count <= 0 || !nr_is_tx_antenna_active(ast, ant)) {
|
||||||
|
identity_layer = -1;
|
||||||
|
} else if (fanout) {
|
||||||
|
// If physical TX ports outnumber the scheduled layers, map active ports
|
||||||
|
// cyclically and normalize replicas (for example, 8TX/4 layers). At
|
||||||
|
// rank 8 this naturally becomes a one-to-one mapping: every port carries
|
||||||
|
// distinct layer data and no fan-out occurs.
|
||||||
|
int active_rank = 0;
|
||||||
|
for (int a = 0; a < ant; ++a)
|
||||||
|
if (nr_is_tx_antenna_active(ast, a))
|
||||||
|
active_rank++;
|
||||||
|
identity_layer = rel15->nrOfLayers > 0
|
||||||
|
? active_rank % rel15->nrOfLayers
|
||||||
|
: -1;
|
||||||
|
if (identity_layer >= 0)
|
||||||
|
identity_replicas = (active_count + rel15->nrOfLayers - 1
|
||||||
|
- identity_layer) / rel15->nrOfLayers;
|
||||||
|
} else if (active_count < frame_parms->nb_antennas_tx) {
|
||||||
|
if (!nr_is_tx_antenna_active(ast, ant)) {
|
||||||
|
identity_layer = -1; // muted antenna → zero output
|
||||||
|
} else {
|
||||||
|
// count active antennas with index < ant
|
||||||
|
identity_layer = 0;
|
||||||
|
for (int a = 0; a < ant; ++a)
|
||||||
|
if (nr_is_tx_antenna_active(ast, a))
|
||||||
|
identity_layer++;
|
||||||
|
if (identity_layer >= rel15->nrOfLayers)
|
||||||
|
identity_layer = -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
identity_layer = (ant < rel15->nrOfLayers) ? ant : -1;
|
||||||
|
}
|
||||||
int rb = 0;
|
int rb = 0;
|
||||||
uint16_t subCarrier = get_block_start_sc(rb_start, rel15->BWPStart, symbol_sz);
|
uint16_t subCarrier = get_block_start_sc(rb_start, rel15->BWPStart, symbol_sz);
|
||||||
const nfapi_nr_tx_precoding_and_beamforming_t *pb = &rel15->precodingAndBeamforming;
|
const nfapi_nr_tx_precoding_and_beamforming_t *pb = &rel15->precodingAndBeamforming;
|
||||||
@@ -452,10 +541,13 @@ static inline void do_txdataF(c16_t **txdataF,
|
|||||||
const int rb_step = rb_step0 == 2 && pmi3 == pmi && pmi4 == pmi ? 4 : rb_step0;
|
const int rb_step = rb_step0 == 2 && pmi3 == pmi && pmi4 == pmi ? 4 : rb_step0;
|
||||||
const int re_cnt = NR_NB_SC_PER_RB * rb_step;
|
const int re_cnt = NR_NB_SC_PER_RB * rb_step;
|
||||||
if (pmi == 0) { // unitary Precoding
|
if (pmi == 0) { // unitary Precoding
|
||||||
if (ant < rel15->nrOfLayers)
|
if (identity_layer >= 0) {
|
||||||
memcpy(&txdataF[ant][txdataF_offset_per_symbol + subCarrier],
|
copy_normalized_layer(
|
||||||
&txdataF_precoding[ant][subCarrier],
|
&txdataF[ant][txdataF_offset_per_symbol + subCarrier],
|
||||||
re_cnt * sizeof(**txdataF));
|
&txdataF_precoding[identity_layer][subCarrier],
|
||||||
|
re_cnt,
|
||||||
|
fanout ? identity_replicas : 1);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
memset(&txdataF[ant][txdataF_offset_per_symbol + subCarrier], 0, re_cnt * sizeof(**txdataF));
|
memset(&txdataF[ant][txdataF_offset_per_symbol + subCarrier], 0, re_cnt * sizeof(**txdataF));
|
||||||
subCarrier += re_cnt;
|
subCarrier += re_cnt;
|
||||||
@@ -501,7 +593,7 @@ typedef struct pdschSymbolProc_s {
|
|||||||
unsigned int n_ptrs;
|
unsigned int n_ptrs;
|
||||||
unsigned int beam_nb;
|
unsigned int beam_nb;
|
||||||
unsigned int re_beginning_of_symbol[14];
|
unsigned int re_beginning_of_symbol[14];
|
||||||
c16_t *tx_layers[4];
|
c16_t *tx_layers[NR_MAX_NB_LAYERS];
|
||||||
time_stats_t dlsch_resource_mapping_stats;
|
time_stats_t dlsch_resource_mapping_stats;
|
||||||
time_stats_t dlsch_precoding_stats;
|
time_stats_t dlsch_precoding_stats;
|
||||||
} pdschSymbolProc_t;
|
} pdschSymbolProc_t;
|
||||||
@@ -528,20 +620,10 @@ static void nr_pdsch_symbol_processing(void *arg)
|
|||||||
|
|
||||||
for (int l_symbol = rdata->startSymbol; l_symbol < rdata->startSymbol + rdata->numSymbols; l_symbol++) {
|
for (int l_symbol = rdata->startSymbol; l_symbol < rdata->startSymbol + rdata->numSymbols; l_symbol++) {
|
||||||
start_meas(&rdata->dlsch_resource_mapping_stats);
|
start_meas(&rdata->dlsch_resource_mapping_stats);
|
||||||
int l_prime = 0; // single symbol layer 0
|
const int l_prime = get_pdsch_dmrs_l_prime(rel15->dlDmrsSymbPos, l_symbol);
|
||||||
int l_overline = get_l0(rel15->dlDmrsSymbPos);
|
|
||||||
|
|
||||||
/// DMRS QPSK modulation
|
/// DMRS QPSK modulation
|
||||||
if ((rel15->dlDmrsSymbPos & (1 << l_symbol))) { // DMRS time occasion
|
if ((rel15->dlDmrsSymbPos & (1 << l_symbol))) { // DMRS time occasion
|
||||||
// The reference point for is subcarrier -1 of the lowest-numbered resource block in CORESET 0 if the corresponding
|
|
||||||
// PDCCH is associated with CORESET -1 and Type0-PDCCH common search space and is addressed to SI-RNTI
|
|
||||||
// 2GPP TS 38.211 V15.8.0 Section 7.4.1.1.2 Mapping to physical resources
|
|
||||||
if (l_symbol == (l_overline + 1)) // take into account the double DMRS symbols
|
|
||||||
l_prime = 1;
|
|
||||||
else if (l_symbol > (l_overline + 1)) { // new DMRS pair
|
|
||||||
l_overline = l_symbol;
|
|
||||||
l_prime = 0;
|
|
||||||
}
|
|
||||||
const uint32_t *gold = nr_gold_pdsch(frame_parms->N_RB_DL,
|
const uint32_t *gold = nr_gold_pdsch(frame_parms->N_RB_DL,
|
||||||
frame_parms->symbols_per_slot,
|
frame_parms->symbols_per_slot,
|
||||||
rel15->dlDmrsScramblingId,
|
rel15->dlDmrsScramblingId,
|
||||||
@@ -612,12 +694,11 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
|
|||||||
const int layerSz = frame_parms->N_RB_DL * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB;
|
const int layerSz = frame_parms->N_RB_DL * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB;
|
||||||
const int nb_re_dmrs = rel15->numDmrsCdmGrpsNoData * (rel15->dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ? 6 : 4);
|
const int nb_re_dmrs = rel15->numDmrsCdmGrpsNoData * (rel15->dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ? 6 : 4);
|
||||||
const int n_dmrs = freq_alloc->num_rbs * nb_re_dmrs;
|
const int n_dmrs = freq_alloc->num_rbs * nb_re_dmrs;
|
||||||
|
uint32_t encoded_length[NR_MAX_NB_CODEWORDS] = {0};
|
||||||
const int xOverhead = 0;
|
uint32_t n_mod_symbols[NR_MAX_NB_CODEWORDS] = {0};
|
||||||
const int nb_re =
|
const c16_t *mod_symbs[NR_MAX_NB_CODEWORDS] = {0};
|
||||||
(12 * rel15->NrOfSymbols - nb_re_dmrs * get_num_dmrs(rel15->dlDmrsSymbPos) - xOverhead) * freq_alloc->num_rbs * rel15->nrOfLayers;
|
c16_t *mod_symbs_storage[NR_MAX_NB_CODEWORDS] = {0};
|
||||||
const int Qm = rel15->qamModOrder[0];
|
size_t encoded_offset = 0;
|
||||||
const int encoded_length = nb_re * Qm;
|
|
||||||
|
|
||||||
/* PTRS */
|
/* PTRS */
|
||||||
uint16_t dlPtrsSymPos = 0;
|
uint16_t dlPtrsSymPos = 0;
|
||||||
@@ -633,34 +714,51 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
|
|||||||
|
|
||||||
#ifdef DEBUG_DLSCH
|
#ifdef DEBUG_DLSCH
|
||||||
printf("PDSCH encoding:\nPayload:\n");
|
printf("PDSCH encoding:\nPayload:\n");
|
||||||
for (int i = 0; i < (dlsch->B >> 3); i += 16) {
|
for (int q = 0; q < rel15->NrOfCodewords; q++) {
|
||||||
for (int j = 0; j < 16; j++)
|
printf("codeword %d:\n", q);
|
||||||
printf("0x%02x\t", dlsch->pdu[i + j]);
|
for (int i = 0; i < rel15->TBSize[q]; i++)
|
||||||
printf("\n");
|
printf("0x%02x%s", dlsch->pdu[q][i], (i & 15) == 15 ? "\n" : "\t");
|
||||||
}
|
|
||||||
printf("\nEncoded payload:\n");
|
|
||||||
for (int i = 0; i < encoded_length; i += 8) {
|
|
||||||
for (int j = 0; j < 8; j++)
|
|
||||||
printf("%d", (input_ptr[i >> 3] >> j) & 1);
|
|
||||||
printf("\t");
|
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (IS_SOFTMODEM_DLSIM)
|
|
||||||
memcpy(dlsch->f, input_ptr, (encoded_length + 7) >> 3);
|
|
||||||
|
|
||||||
c16_t mod_symbs[rel15->NrOfCodewords][encoded_length] __attribute__((aligned(64)));
|
|
||||||
for (int codeWord = 0; codeWord < rel15->NrOfCodewords; codeWord++) {
|
for (int codeWord = 0; codeWord < rel15->NrOfCodewords; codeWord++) {
|
||||||
|
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, codeWord);
|
||||||
|
const int Qm = rel15->qamModOrder[codeWord];
|
||||||
|
encoded_length[codeWord] = nr_get_G(freq_alloc->num_rbs,
|
||||||
|
rel15->NrOfSymbols,
|
||||||
|
nb_re_dmrs,
|
||||||
|
get_num_dmrs(rel15->dlDmrsSymbPos),
|
||||||
|
dlsch->unav_res,
|
||||||
|
Qm,
|
||||||
|
cw_layers);
|
||||||
|
n_mod_symbols[codeWord] = encoded_length[codeWord] / Qm;
|
||||||
|
unsigned char *codeword_input = input_ptr + encoded_offset;
|
||||||
|
const size_t max_encoded_bits =
|
||||||
|
freq_alloc->num_rbs * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * Qm * cw_layers;
|
||||||
|
encoded_offset += ceil_mod(max_encoded_bits, 8 * 64) >> 3;
|
||||||
|
|
||||||
|
if (IS_SOFTMODEM_DLSIM)
|
||||||
|
memcpy(dlsch->f[codeWord], codeword_input, (encoded_length[codeWord] + 7) >> 3);
|
||||||
|
|
||||||
|
mod_symbs_storage[codeWord] = malloc16(n_mod_symbols[codeWord] * sizeof(c16_t));
|
||||||
|
AssertFatal(mod_symbs_storage[codeWord], "Cannot allocate codeword %d modulation buffer\n", codeWord);
|
||||||
|
mod_symbs[codeWord] = mod_symbs_storage[codeWord];
|
||||||
|
|
||||||
/// scrambling
|
/// scrambling
|
||||||
start_meas(dlsch_scrambling_stats);
|
start_meas(dlsch_scrambling_stats);
|
||||||
uint32_t scrambled_output[(encoded_length >> 5) + 4]; // modulator acces by 4 bytes in some cases
|
uint32_t scrambled_output[(encoded_length[codeWord] >> 5) + 4]; // modulator accesses four-byte groups
|
||||||
memset(scrambled_output, 0, sizeof(scrambled_output));
|
memset(scrambled_output, 0, sizeof(scrambled_output));
|
||||||
nr_pdsch_codeword_scrambling(input_ptr, encoded_length, codeWord, rel15->dataScramblingId, rel15->rnti, scrambled_output);
|
nr_pdsch_codeword_scrambling(codeword_input,
|
||||||
|
encoded_length[codeWord],
|
||||||
|
codeWord,
|
||||||
|
rel15->dataScramblingId,
|
||||||
|
rel15->rnti,
|
||||||
|
scrambled_output);
|
||||||
|
|
||||||
#ifdef DEBUG_DLSCH
|
#ifdef DEBUG_DLSCH
|
||||||
printf("PDSCH scrambling:\n");
|
printf("PDSCH scrambling:\n");
|
||||||
for (int i = 0; i < encoded_length >> 8; i++) {
|
for (int i = 0; i < encoded_length[codeWord] >> 8; i++) {
|
||||||
for (int j = 0; j < 8; j++)
|
for (int j = 0; j < 8; j++)
|
||||||
printf("0x%08x\t", scrambled_output[(i << 3) + j]);
|
printf("0x%08x\t", scrambled_output[(i << 3) + j]);
|
||||||
printf("\n");
|
printf("\n");
|
||||||
@@ -670,16 +768,10 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
|
|||||||
stop_meas(dlsch_scrambling_stats);
|
stop_meas(dlsch_scrambling_stats);
|
||||||
/// Modulation
|
/// Modulation
|
||||||
start_meas(dlsch_modulation_stats);
|
start_meas(dlsch_modulation_stats);
|
||||||
nr_modulation(scrambled_output, encoded_length, Qm, (int16_t *)mod_symbs[codeWord]);
|
nr_modulation(scrambled_output, encoded_length[codeWord], Qm, (int16_t *)mod_symbs_storage[codeWord]);
|
||||||
stop_meas(dlsch_modulation_stats);
|
stop_meas(dlsch_modulation_stats);
|
||||||
#ifdef DEBUG_DLSCH
|
#ifdef DEBUG_DLSCH
|
||||||
printf("PDSCH Modulation: Qm %d(%d)\n", Qm, nb_re);
|
printf("PDSCH codeword %d modulation: Qm %d, %u symbols\n", codeWord, Qm, n_mod_symbols[codeWord]);
|
||||||
for (int i = 0; i < nb_re; i += 8) {
|
|
||||||
for (int j = 0; j < 8; j++) {
|
|
||||||
printf("%d %d\t", mod_symbs[codeWord][i + j].r, mod_symbs[codeWord][i + j].i);
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,7 +786,7 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
|
|||||||
int layerSz2 = (layerSz + 63) & ~63;
|
int layerSz2 = (layerSz + 63) & ~63;
|
||||||
c16_t tx_layers[rel15->nrOfLayers][layerSz2] __attribute__((aligned(64)));
|
c16_t tx_layers[rel15->nrOfLayers][layerSz2] __attribute__((aligned(64)));
|
||||||
memset(tx_layers, 0, sizeof(tx_layers));
|
memset(tx_layers, 0, sizeof(tx_layers));
|
||||||
nr_layer_mapping(rel15->NrOfCodewords, encoded_length, mod_symbs, rel15->nrOfLayers, layerSz2, nb_re, tx_layers);
|
nr_layer_mapping(rel15->NrOfCodewords, mod_symbs, n_mod_symbols, rel15->nrOfLayers, layerSz2, tx_layers);
|
||||||
|
|
||||||
/// Layer Precoding and Antenna port mapping
|
/// Layer Precoding and Antenna port mapping
|
||||||
// tx_layers 1-8 are mapped on antenna ports 1000-1007
|
// tx_layers 1-8 are mapped on antenna ports 1000-1007
|
||||||
@@ -777,12 +869,13 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
|
|||||||
merge_meas(&gNB->dlsch_resource_mapping_stats, &arr[i].dlsch_resource_mapping_stats);
|
merge_meas(&gNB->dlsch_resource_mapping_stats, &arr[i].dlsch_resource_mapping_stats);
|
||||||
merge_meas(&gNB->dlsch_precoding_stats, &arr[i].dlsch_precoding_stats);
|
merge_meas(&gNB->dlsch_precoding_stats, &arr[i].dlsch_precoding_stats);
|
||||||
}
|
}
|
||||||
|
for (int q = 0; q < rel15->NrOfCodewords; q++)
|
||||||
|
free16(mod_symbs_storage[q], n_mod_symbols[q] * sizeof(c16_t));
|
||||||
stop_meas(&gNB->dlsch_pdsch_generation_stats);
|
stop_meas(&gNB->dlsch_pdsch_generation_stats);
|
||||||
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
|
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
|
||||||
* should remain a multiple of 8 * 64 with enough offset to fit each dlsch
|
* should remain a multiple of 8 * 64 with enough offset to fit each dlsch
|
||||||
*/
|
*/
|
||||||
uint32_t size_output_tb = freq_alloc->num_rbs * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * Qm * rel15->nrOfLayers;
|
return encoded_offset;
|
||||||
return ((size_output_tb + 511) >> 9) << 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void nr_generate_pdsch(PHY_VARS_gNB *gNB, int n_dlsch, NR_gNB_DLSCH_t *dlsch_array, int frame, int slot)
|
void nr_generate_pdsch(PHY_VARS_gNB *gNB, int n_dlsch, NR_gNB_DLSCH_t *dlsch_array, int frame, int slot)
|
||||||
@@ -816,8 +909,6 @@ void nr_generate_pdsch(PHY_VARS_gNB *gNB, int n_dlsch, NR_gNB_DLSCH_t *dlsch_arr
|
|||||||
dlsch->freq_alloc.last_rb,
|
dlsch->freq_alloc.last_rb,
|
||||||
dlsch->freq_alloc.num_rbs);
|
dlsch->freq_alloc.num_rbs);
|
||||||
|
|
||||||
const int Qm = rel15->qamModOrder[0];
|
|
||||||
|
|
||||||
/* PTRS */
|
/* PTRS */
|
||||||
uint16_t dlPtrsSymPos = 0;
|
uint16_t dlPtrsSymPos = 0;
|
||||||
int n_ptrs = 0;
|
int n_ptrs = 0;
|
||||||
@@ -834,13 +925,14 @@ void nr_generate_pdsch(PHY_VARS_gNB *gNB, int n_dlsch, NR_gNB_DLSCH_t *dlsch_arr
|
|||||||
dlsch->unav_res = ptrsSymbPerSlot * n_ptrs;
|
dlsch->unav_res = ptrsSymbPerSlot * n_ptrs;
|
||||||
|
|
||||||
/// CRC, coding, interleaving and rate matching
|
/// CRC, coding, interleaving and rate matching
|
||||||
AssertFatal(dlsch->pdu != NULL, "%4d.%2d no PDU for PDSCH generation\n", frame, slot);
|
for (int q = 0; q < rel15->NrOfCodewords; q++) {
|
||||||
|
AssertFatal(dlsch->pdu[q] != NULL, "%4d.%2d no PDU for PDSCH codeword %d generation\n", frame, slot, q);
|
||||||
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
|
/* Every LDPC output starts at a 64-byte boundary. */
|
||||||
* => size_output is a sum of parts sizes rounded up to a multiple of 8 * 64
|
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, q);
|
||||||
*/
|
const size_t size_output_tb =
|
||||||
size_t size_output_tb = dlsch->freq_alloc.num_rbs * NR_SYMBOLS_PER_SLOT * NR_NB_SC_PER_RB * Qm * rel15->nrOfLayers;
|
dlsch->freq_alloc.num_rbs * NR_SYMBOLS_PER_SLOT * NR_NB_SC_PER_RB * rel15->qamModOrder[q] * cw_layers;
|
||||||
size_output += ceil_mod(size_output_tb, 8 * 64);
|
size_output += ceil_mod(size_output_tb, 8 * 64);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char output[size_output >> 3] __attribute__((aligned(64)));
|
unsigned char output[size_output >> 3] __attribute__((aligned(64)));
|
||||||
|
|||||||
@@ -23,35 +23,53 @@
|
|||||||
// #define DEBUG_DLSCH_CODING
|
// #define DEBUG_DLSCH_CODING
|
||||||
// #define DEBUG_DLSCH_FREE 1
|
// #define DEBUG_DLSCH_FREE 1
|
||||||
|
|
||||||
|
/* Keep the PHY encoder independent of the MAC helper with the same rule from
|
||||||
|
* TS 38.212 section 5.3.2. A dual-codeword PDSCH may have a different TBS
|
||||||
|
* (and therefore a different base graph) for each codeword. */
|
||||||
|
static uint8_t dlsch_base_graph(uint32_t A, uint16_t R)
|
||||||
|
{
|
||||||
|
const float code_rate = (float)R / 10240.0f;
|
||||||
|
return (A <= 292 || (A <= NR_MAX_PDSCH_TBS && code_rate <= 0.6667f) || code_rate <= 0.25f) ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
void free_gNB_dlsch(NR_gNB_DLSCH_t *dlsch, uint16_t N_RB, const NR_DL_FRAME_PARMS *frame_parms)
|
void free_gNB_dlsch(NR_gNB_DLSCH_t *dlsch, uint16_t N_RB, const NR_DL_FRAME_PARMS *frame_parms)
|
||||||
{
|
{
|
||||||
int max_layers = (frame_parms->nb_antennas_tx < NR_MAX_NB_LAYERS) ? frame_parms->nb_antennas_tx : NR_MAX_NB_LAYERS;
|
int max_layers = (frame_parms->nb_antennas_tx < NR_MAX_NB_LAYERS) ? frame_parms->nb_antennas_tx : NR_MAX_NB_LAYERS;
|
||||||
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_layers;
|
const int max_cw_layers = min(max_layers, 4);
|
||||||
|
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_cw_layers;
|
||||||
|
|
||||||
if (N_RB != 273) {
|
if (N_RB != 273) {
|
||||||
a_segments = a_segments * N_RB;
|
a_segments = a_segments * N_RB;
|
||||||
a_segments = a_segments / 273 + 1;
|
a_segments = a_segments / 273 + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dlsch->b) {
|
const size_t f_bytes = N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * max_cw_layers;
|
||||||
free16(dlsch->b, a_segments * 1056);
|
(void)f_bytes; /* free16 ignores its size argument on the supported allocators. */
|
||||||
dlsch->b = NULL;
|
for (int q = 0; q < NR_MAX_NB_CODEWORDS; q++) {
|
||||||
|
if (dlsch->b[q]) {
|
||||||
|
free16(dlsch->b[q], a_segments * 1056);
|
||||||
|
dlsch->b[q] = NULL;
|
||||||
|
}
|
||||||
|
if (dlsch->f[q]) {
|
||||||
|
free16(dlsch->f[q], f_bytes);
|
||||||
|
dlsch->f[q] = NULL;
|
||||||
|
}
|
||||||
|
if (!dlsch->c[q])
|
||||||
|
continue;
|
||||||
|
for (int r = 0; r < a_segments; r++) {
|
||||||
|
free(dlsch->c[q][r]);
|
||||||
|
dlsch->c[q][r] = NULL;
|
||||||
|
}
|
||||||
|
free(dlsch->c[q]);
|
||||||
|
dlsch->c[q] = NULL;
|
||||||
}
|
}
|
||||||
if (dlsch->f) {
|
|
||||||
free16(dlsch->f, N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * NR_MAX_NB_LAYERS);
|
|
||||||
dlsch->f = NULL;
|
|
||||||
}
|
|
||||||
for (int r = 0; r < a_segments; r++) {
|
|
||||||
free(dlsch->c[r]);
|
|
||||||
dlsch->c[r] = NULL;
|
|
||||||
}
|
|
||||||
free(dlsch->c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NR_gNB_DLSCH_t new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, uint16_t N_RB)
|
NR_gNB_DLSCH_t new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, uint16_t N_RB)
|
||||||
{
|
{
|
||||||
int max_layers = (frame_parms->nb_antennas_tx < NR_MAX_NB_LAYERS) ? frame_parms->nb_antennas_tx : NR_MAX_NB_LAYERS;
|
int max_layers = (frame_parms->nb_antennas_tx < NR_MAX_NB_LAYERS) ? frame_parms->nb_antennas_tx : NR_MAX_NB_LAYERS;
|
||||||
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_layers; // number of segments to be allocated
|
const int max_cw_layers = min(max_layers, 4);
|
||||||
|
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_cw_layers; // segments allocated per codeword
|
||||||
|
|
||||||
if (N_RB != 273) {
|
if (N_RB != 273) {
|
||||||
a_segments = a_segments * N_RB;
|
a_segments = a_segments * N_RB;
|
||||||
@@ -59,28 +77,29 @@ NR_gNB_DLSCH_t new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, uint16_t N_RB)
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOG_D(PHY, "Allocating %d segments (MAX %d, N_PRB %d)\n", a_segments, MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER, N_RB);
|
LOG_D(PHY, "Allocating %d segments (MAX %d, N_PRB %d)\n", a_segments, MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER, N_RB);
|
||||||
uint32_t dlsch_bytes = a_segments * 1056; // allocated bytes per segment
|
uint32_t dlsch_bytes = a_segments * 1056;
|
||||||
NR_gNB_DLSCH_t dlsch = {0};
|
NR_gNB_DLSCH_t dlsch = {0};
|
||||||
|
const size_t f_bytes = N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * max_cw_layers;
|
||||||
|
|
||||||
dlsch.b = malloc16(dlsch_bytes);
|
for (int q = 0; q < NR_MAX_NB_CODEWORDS; q++) {
|
||||||
AssertFatal(dlsch.b, "cannot allocate memory for dlsch.b\n");
|
dlsch.b[q] = malloc16(dlsch_bytes);
|
||||||
bzero(dlsch.b, dlsch_bytes);
|
AssertFatal(dlsch.b[q], "cannot allocate memory for dlsch.b[%d]\n", q);
|
||||||
|
bzero(dlsch.b[q], dlsch_bytes);
|
||||||
|
|
||||||
dlsch.c = (uint8_t **)malloc16(a_segments * sizeof(uint8_t *));
|
dlsch.c[q] = (uint8_t **)malloc16(a_segments * sizeof(uint8_t *));
|
||||||
for (int r = 0; r < a_segments; r++) {
|
AssertFatal(dlsch.c[q], "cannot allocate memory for dlsch.c[%d]\n", q);
|
||||||
// account for filler in first segment and CRCs for multiple segment case
|
for (int r = 0; r < a_segments; r++) {
|
||||||
// [hna] 8448 is the maximum CB size in NR
|
// 8448 is the maximum NR code-block size, including filler and CRC.
|
||||||
// 68*348 = 68*(maximum size of Zc)
|
dlsch.c[q][r] = malloc16(8448);
|
||||||
// In section 5.3.2 in 38.212, the for loop is up to N + 2*Zc (maximum size of N is 66*Zc, therefore 68*Zc)
|
AssertFatal(dlsch.c[q][r], "cannot allocate dlsch.c[%d][%d]\n", q, r);
|
||||||
dlsch.c[r] = malloc16(8448);
|
bzero(dlsch.c[q][r], 8448);
|
||||||
AssertFatal(dlsch.c[r], "cannot allocate dlsch.c[%d]\n", r);
|
}
|
||||||
bzero(dlsch.c[r], 8448);
|
|
||||||
|
dlsch.f[q] = malloc16(f_bytes);
|
||||||
|
AssertFatal(dlsch.f[q], "cannot allocate dlsch.f[%d]\n", q);
|
||||||
|
bzero(dlsch.f[q], f_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
dlsch.f = malloc16(N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * NR_MAX_NB_LAYERS);
|
|
||||||
AssertFatal(dlsch.f, "cannot allocate dlsch->f\n");
|
|
||||||
bzero(dlsch.f, N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * NR_MAX_NB_LAYERS);
|
|
||||||
|
|
||||||
return (dlsch);
|
return (dlsch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,92 +117,104 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
|
|||||||
time_stats_t *dlsch_interleaving_stats,
|
time_stats_t *dlsch_interleaving_stats,
|
||||||
time_stats_t *dlsch_segmentation_stats)
|
time_stats_t *dlsch_segmentation_stats)
|
||||||
{
|
{
|
||||||
nrLDPC_TB_encoding_parameters_t TBs[n_dlsch];
|
int n_tbs = 0;
|
||||||
|
for (int i = 0; i < n_dlsch; i++) {
|
||||||
|
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch_array[i].pdsch_pdu->pdsch_pdu_rel15;
|
||||||
|
AssertFatal(rel15->NrOfCodewords == nr_pdsch_num_codewords(rel15->nrOfLayers),
|
||||||
|
"PDSCH has %u codewords for %u layers\n",
|
||||||
|
rel15->NrOfCodewords,
|
||||||
|
rel15->nrOfLayers);
|
||||||
|
n_tbs += rel15->NrOfCodewords;
|
||||||
|
}
|
||||||
|
|
||||||
|
nrLDPC_TB_encoding_parameters_t TBs[n_tbs];
|
||||||
memset(TBs, 0, sizeof(TBs));
|
memset(TBs, 0, sizeof(TBs));
|
||||||
|
NR_gNB_DLSCH_t *tb_dlsch[n_tbs];
|
||||||
|
uint8_t tb_cw[n_tbs];
|
||||||
|
|
||||||
int num_segments = 0;
|
int num_segments = 0;
|
||||||
|
int tb_index = 0;
|
||||||
|
|
||||||
for (int i = 0; i < n_dlsch; i++) {
|
for (int i = 0; i < n_dlsch; i++) {
|
||||||
NR_gNB_DLSCH_t *dlsch = &dlsch_array[i];
|
NR_gNB_DLSCH_t *dlsch = &dlsch_array[i];
|
||||||
|
|
||||||
unsigned int crc = 1;
|
|
||||||
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch->pdsch_pdu->pdsch_pdu_rel15;
|
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch->pdsch_pdu->pdsch_pdu_rel15;
|
||||||
uint32_t A = rel15->TBSize[0] << 3;
|
|
||||||
unsigned char *a = dlsch->pdu;
|
|
||||||
if (rel15->rnti != SI_RNTI) {
|
|
||||||
ws_trace_t tmp = {.nr = true,
|
|
||||||
.direction = DIRECTION_DOWNLINK,
|
|
||||||
.pdu_buffer = a,
|
|
||||||
.pdu_buffer_size = rel15->TBSize[0],
|
|
||||||
.ueid = 0,
|
|
||||||
.rntiType = WS_C_RNTI,
|
|
||||||
.rnti = rel15->rnti,
|
|
||||||
.sysFrame = frame,
|
|
||||||
.subframe = slot,
|
|
||||||
.harq_pid = 0, // difficult to find the harq pid here
|
|
||||||
.oob_event = 0,
|
|
||||||
.oob_event_value = 0};
|
|
||||||
trace_pdu(&tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
NR_gNB_PHY_STATS_t *phy_stats = NULL;
|
NR_gNB_PHY_STATS_t *phy_stats = NULL;
|
||||||
if (rel15->rnti != 0xFFFF)
|
if (rel15->rnti != 0xFFFF)
|
||||||
phy_stats = get_phy_stats(gNB, rel15->rnti);
|
phy_stats = get_phy_stats(gNB, rel15->rnti);
|
||||||
|
|
||||||
if (phy_stats) {
|
if (phy_stats) {
|
||||||
phy_stats->frame = frame;
|
phy_stats->frame = frame;
|
||||||
phy_stats->dlsch_stats.total_bytes_tx += rel15->TBSize[0];
|
|
||||||
phy_stats->dlsch_stats.current_RI = rel15->nrOfLayers;
|
phy_stats->dlsch_stats.current_RI = rel15->nrOfLayers;
|
||||||
phy_stats->dlsch_stats.current_Qm = rel15->qamModOrder[0];
|
phy_stats->dlsch_stats.current_Qm = rel15->qamModOrder[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
int max_bytes = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * rel15->nrOfLayers * 1056;
|
for (int q = 0; q < rel15->NrOfCodewords; q++, tb_index++) {
|
||||||
int B;
|
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, q);
|
||||||
if (A > NR_MAX_PDSCH_TBS) {
|
const uint32_t A = rel15->TBSize[q] << 3;
|
||||||
// Add 24-bit crc (polynomial A) to payload
|
unsigned char *a = dlsch->pdu[q];
|
||||||
crc = crc24a(a, A) >> 8;
|
AssertFatal(a != NULL, "Missing PDSCH transport block for codeword %d\n", q);
|
||||||
a[A >> 3] = ((uint8_t *)&crc)[2];
|
|
||||||
a[1 + (A >> 3)] = ((uint8_t *)&crc)[1];
|
if (rel15->rnti != SI_RNTI) {
|
||||||
a[2 + (A >> 3)] = ((uint8_t *)&crc)[0];
|
ws_trace_t tmp = {.nr = true,
|
||||||
// printf("CRC %x (A %d)\n",crc,A);
|
.direction = DIRECTION_DOWNLINK,
|
||||||
// printf("a0 %d a1 %d a2 %d\n", a[A>>3], a[1+(A>>3)], a[2+(A>>3)]);
|
.pdu_buffer = a,
|
||||||
B = A + 24;
|
.pdu_buffer_size = rel15->TBSize[q],
|
||||||
AssertFatal((A / 8) + 4 <= max_bytes, "A %d is too big (A/8+4 = %d > %d)\n", A, (A / 8) + 4, max_bytes);
|
.ueid = 0,
|
||||||
memcpy(dlsch->b, a, (A / 8) + 4); // why is this +4 if the CRC is only 3 bytes?
|
.rntiType = WS_C_RNTI,
|
||||||
} else {
|
.rnti = rel15->rnti,
|
||||||
// Add 16-bit crc (polynomial A) to payload
|
.sysFrame = frame,
|
||||||
crc = crc16(a, A) >> 16;
|
.subframe = slot,
|
||||||
a[A >> 3] = ((uint8_t *)&crc)[1];
|
.harq_pid = 0,
|
||||||
a[1 + (A >> 3)] = ((uint8_t *)&crc)[0];
|
.oob_event = 0,
|
||||||
// printf("CRC %x (A %d)\n",crc,A);
|
.oob_event_value = 0};
|
||||||
// printf("a0 %d a1 %d \n", a[A>>3], a[1+(A>>3)]);
|
trace_pdu(&tmp);
|
||||||
B = A + 16;
|
}
|
||||||
AssertFatal((A / 8) + 3 <= max_bytes, "A %d is too big (A/8+3 = %d > %d)\n", A, (A / 8) + 3, max_bytes);
|
if (phy_stats)
|
||||||
memcpy(dlsch->b, a, (A / 8) + 3); // using 3 bytes to mimic the case of 24 bit crc
|
phy_stats->dlsch_stats.total_bytes_tx += rel15->TBSize[q];
|
||||||
|
|
||||||
|
unsigned int crc = 1;
|
||||||
|
const int max_bytes = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * cw_layers * 1056;
|
||||||
|
int B;
|
||||||
|
if (A > NR_MAX_PDSCH_TBS) {
|
||||||
|
crc = crc24a(a, A) >> 8;
|
||||||
|
a[A >> 3] = ((uint8_t *)&crc)[2];
|
||||||
|
a[1 + (A >> 3)] = ((uint8_t *)&crc)[1];
|
||||||
|
a[2 + (A >> 3)] = ((uint8_t *)&crc)[0];
|
||||||
|
B = A + 24;
|
||||||
|
AssertFatal((A / 8) + 4 <= max_bytes, "A %u is too big (%u > %d)\n", A, (A / 8) + 4, max_bytes);
|
||||||
|
memcpy(dlsch->b[q], a, (A / 8) + 4);
|
||||||
|
} else {
|
||||||
|
crc = crc16(a, A) >> 16;
|
||||||
|
a[A >> 3] = ((uint8_t *)&crc)[1];
|
||||||
|
a[1 + (A >> 3)] = ((uint8_t *)&crc)[0];
|
||||||
|
B = A + 16;
|
||||||
|
AssertFatal((A / 8) + 3 <= max_bytes, "A %u is too big (%u > %d)\n", A, (A / 8) + 3, max_bytes);
|
||||||
|
memcpy(dlsch->b[q], a, (A / 8) + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[tb_index];
|
||||||
|
tb_dlsch[tb_index] = dlsch;
|
||||||
|
tb_cw[tb_index] = q;
|
||||||
|
// A HARQ PID is not globally unique, so use the flattened TB index.
|
||||||
|
TB_parameters->harq_unique_pid = tb_index;
|
||||||
|
TB_parameters->BG = dlsch_base_graph(A, rel15->targetCodeRate[q]);
|
||||||
|
TB_parameters->A = A;
|
||||||
|
start_meas(dlsch_segmentation_stats);
|
||||||
|
TB_parameters->Kb = nr_segmentation(dlsch->b[q],
|
||||||
|
dlsch->c[q],
|
||||||
|
B,
|
||||||
|
&TB_parameters->C,
|
||||||
|
&TB_parameters->K,
|
||||||
|
&TB_parameters->Z,
|
||||||
|
&TB_parameters->F,
|
||||||
|
TB_parameters->BG);
|
||||||
|
stop_meas(dlsch_segmentation_stats);
|
||||||
|
|
||||||
|
if (TB_parameters->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * cw_layers) {
|
||||||
|
LOG_E(PHY, "nr_segmentation.c: too many segments %d, B %d, codeword layers %d\n", TB_parameters->C, B, cw_layers);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
num_segments += TB_parameters->C;
|
||||||
}
|
}
|
||||||
|
|
||||||
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
|
|
||||||
|
|
||||||
// The harq_pid is not unique among the active HARQ processes in the instance so we use i instead
|
|
||||||
TB_parameters->harq_unique_pid = i;
|
|
||||||
TB_parameters->BG = rel15->maintenance_parms_v3.ldpcBaseGraph;
|
|
||||||
TB_parameters->A = A;
|
|
||||||
start_meas(dlsch_segmentation_stats);
|
|
||||||
TB_parameters->Kb = nr_segmentation(dlsch->b,
|
|
||||||
dlsch->c,
|
|
||||||
B,
|
|
||||||
&TB_parameters->C,
|
|
||||||
&TB_parameters->K,
|
|
||||||
&TB_parameters->Z,
|
|
||||||
&TB_parameters->F,
|
|
||||||
TB_parameters->BG);
|
|
||||||
stop_meas(dlsch_segmentation_stats);
|
|
||||||
|
|
||||||
if (TB_parameters->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * rel15->nrOfLayers) {
|
|
||||||
LOG_E(PHY, "nr_segmentation.c: too many segments %d, B %d\n", TB_parameters->C, B);
|
|
||||||
return (-1);
|
|
||||||
}
|
|
||||||
num_segments += TB_parameters->C;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nrLDPC_segment_encoding_parameters_t segments[num_segments];
|
nrLDPC_segment_encoding_parameters_t segments[num_segments];
|
||||||
@@ -191,19 +222,26 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
|
|||||||
size_t segments_offset = 0;
|
size_t segments_offset = 0;
|
||||||
size_t dlsch_offset = 0;
|
size_t dlsch_offset = 0;
|
||||||
|
|
||||||
for (int i = 0; i < n_dlsch; i++) {
|
for (int i = 0; i < n_tbs; i++) {
|
||||||
NR_gNB_DLSCH_t *dlsch = &dlsch_array[i];
|
NR_gNB_DLSCH_t *dlsch = tb_dlsch[i];
|
||||||
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch->pdsch_pdu->pdsch_pdu_rel15;
|
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch->pdsch_pdu->pdsch_pdu_rel15;
|
||||||
|
const int q = tb_cw[i];
|
||||||
|
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, q);
|
||||||
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
|
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
|
||||||
|
|
||||||
#ifdef DEBUG_DLSCH_CODING
|
#ifdef DEBUG_DLSCH_CODING
|
||||||
for (int r = 0; r < TB_parameters->C; r++) {
|
for (int r = 0; r < TB_parameters->C; r++) {
|
||||||
LOG_D(PHY, "Encoder: B %d F %d \n", dlsch->B, TB_parameters->F);
|
LOG_D(PHY, "Encoder: B %d F %d \n", dlsch->B, TB_parameters->F);
|
||||||
LOG_D(PHY, "start ldpc encoder segment %d/%d\n", r, TB_parameters->C);
|
LOG_D(PHY, "start ldpc encoder segment %d/%d\n", r, TB_parameters->C);
|
||||||
LOG_D(PHY, "input %d %d %d %d %d \n", dlsch->c[r][0], dlsch->c[r][1], dlsch->c[r][2], dlsch->c[r][3], dlsch->c[r][4]);
|
LOG_D(PHY,
|
||||||
|
"input %d %d %d %d %d \n",
|
||||||
|
dlsch->c[q][r][0],
|
||||||
|
dlsch->c[q][r][1],
|
||||||
|
dlsch->c[q][r][2],
|
||||||
|
dlsch->c[q][r][3],
|
||||||
|
dlsch->c[q][r][4]);
|
||||||
for (int cnt = 0; cnt < 22 * (TB_parameters->Z) / 8; cnt++) {
|
for (int cnt = 0; cnt < 22 * (TB_parameters->Z) / 8; cnt++) {
|
||||||
LOG_D(PHY, "%d ", dlsch->c[r][cnt]);
|
LOG_D(PHY, "%d ", dlsch->c[q][r][cnt]);
|
||||||
}
|
}
|
||||||
LOG_D(PHY, "\n");
|
LOG_D(PHY, "\n");
|
||||||
}
|
}
|
||||||
@@ -211,10 +249,10 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
|
|||||||
|
|
||||||
int rbsize = dlsch->freq_alloc.num_rbs;
|
int rbsize = dlsch->freq_alloc.num_rbs;
|
||||||
TB_parameters->nb_rb = rbsize;
|
TB_parameters->nb_rb = rbsize;
|
||||||
TB_parameters->Qm = rel15->qamModOrder[0];
|
TB_parameters->Qm = rel15->qamModOrder[q];
|
||||||
TB_parameters->mcs = rel15->mcsIndex[0];
|
TB_parameters->mcs = rel15->mcsIndex[q];
|
||||||
TB_parameters->nb_layers = rel15->nrOfLayers;
|
TB_parameters->nb_layers = cw_layers;
|
||||||
TB_parameters->rv_index = rel15->rvIndex[0];
|
TB_parameters->rv_index = rel15->rvIndex[q];
|
||||||
|
|
||||||
int nb_re_dmrs =
|
int nb_re_dmrs =
|
||||||
(rel15->dmrsConfigType == NFAPI_NR_DMRS_TYPE1) ? (6 * rel15->numDmrsCdmGrpsNoData) : (4 * rel15->numDmrsCdmGrpsNoData);
|
(rel15->dmrsConfigType == NFAPI_NR_DMRS_TYPE1) ? (6 * rel15->numDmrsCdmGrpsNoData) : (4 * rel15->numDmrsCdmGrpsNoData);
|
||||||
@@ -223,8 +261,8 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
|
|||||||
nb_re_dmrs,
|
nb_re_dmrs,
|
||||||
get_num_dmrs(rel15->dlDmrsSymbPos),
|
get_num_dmrs(rel15->dlDmrsSymbPos),
|
||||||
dlsch->unav_res,
|
dlsch->unav_res,
|
||||||
rel15->qamModOrder[0],
|
rel15->qamModOrder[q],
|
||||||
rel15->nrOfLayers);
|
cw_layers);
|
||||||
|
|
||||||
TB_parameters->tbslbrm = rel15->maintenance_parms_v3.tbSizeLbrmBytes;
|
TB_parameters->tbslbrm = rel15->maintenance_parms_v3.tbSizeLbrmBytes;
|
||||||
TB_parameters->output = &output[dlsch_offset >> 3];
|
TB_parameters->output = &output[dlsch_offset >> 3];
|
||||||
@@ -232,8 +270,8 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
|
|||||||
|
|
||||||
for (int r = 0; r < TB_parameters->C; r++) {
|
for (int r = 0; r < TB_parameters->C; r++) {
|
||||||
nrLDPC_segment_encoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
|
nrLDPC_segment_encoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
|
||||||
segment_parameters->c = dlsch->c[r];
|
segment_parameters->c = dlsch->c[q][r];
|
||||||
segment_parameters->E = nr_get_E(TB_parameters->G, TB_parameters->C, TB_parameters->Qm, rel15->nrOfLayers, r);
|
segment_parameters->E = nr_get_E(TB_parameters->G, TB_parameters->C, TB_parameters->Qm, cw_layers, r);
|
||||||
|
|
||||||
reset_meas(&segment_parameters->ts_interleave);
|
reset_meas(&segment_parameters->ts_interleave);
|
||||||
reset_meas(&segment_parameters->ts_rate_match);
|
reset_meas(&segment_parameters->ts_rate_match);
|
||||||
@@ -245,13 +283,14 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
|
|||||||
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
|
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
|
||||||
* => dlsch_offset should remain a multiple of 8 * 64 with enough offset to fit each dlsch
|
* => dlsch_offset should remain a multiple of 8 * 64 with enough offset to fit each dlsch
|
||||||
*/
|
*/
|
||||||
const size_t dlsch_size = rbsize * gNB->frame_parms.symbols_per_slot * NR_NB_SC_PER_RB * rel15->qamModOrder[0] * rel15->nrOfLayers;
|
const size_t dlsch_size =
|
||||||
|
rbsize * gNB->frame_parms.symbols_per_slot * NR_NB_SC_PER_RB * rel15->qamModOrder[q] * cw_layers;
|
||||||
dlsch_offset += ceil_mod(dlsch_size, 8 * 64);
|
dlsch_offset += ceil_mod(dlsch_size, 8 * 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
nrLDPC_slot_encoding_parameters_t slot_parameters = {.frame = frame,
|
nrLDPC_slot_encoding_parameters_t slot_parameters = {.frame = frame,
|
||||||
.slot = slot,
|
.slot = slot,
|
||||||
.nb_TBs = n_dlsch,
|
.nb_TBs = n_tbs,
|
||||||
.threadPool = &gNB->threadPool,
|
.threadPool = &gNB->threadPool,
|
||||||
.tinput = tinput,
|
.tinput = tinput,
|
||||||
.tprep = tprep,
|
.tprep = tprep,
|
||||||
@@ -260,7 +299,7 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
|
|||||||
.TBs = TBs};
|
.TBs = TBs};
|
||||||
gNB->nrLDPC_coding_interface.nrLDPC_coding_encoder(&slot_parameters);
|
gNB->nrLDPC_coding_interface.nrLDPC_coding_encoder(&slot_parameters);
|
||||||
|
|
||||||
for (int i = 0; i < n_dlsch; i++) {
|
for (int i = 0; i < n_tbs; i++) {
|
||||||
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
|
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
|
||||||
for (int r = 0; r < TB_parameters->C; r++) {
|
for (int r = 0; r < TB_parameters->C; r++) {
|
||||||
nrLDPC_segment_encoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
|
nrLDPC_segment_encoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
|
||||||
|
|||||||
@@ -19,4 +19,12 @@ uint16_t get_dmrs_freq_idx(uint16_t n, uint8_t k_prime, uint8_t delta, uint8_t d
|
|||||||
|
|
||||||
uint8_t get_l0(uint16_t dlDmrsSymbPos);
|
uint8_t get_l0(uint16_t dlDmrsSymbPos);
|
||||||
|
|
||||||
|
/* For a double-symbol PDSCH DMRS occasion, l'=1 on the second symbol of
|
||||||
|
* each adjacent pair and l'=0 on the first. Single-symbol occasions always
|
||||||
|
* use l'=0. */
|
||||||
|
static inline uint8_t get_pdsch_dmrs_l_prime(uint16_t dmrs_symbol_mask, uint8_t symbol)
|
||||||
|
{
|
||||||
|
return symbol > 0 && ((dmrs_symbol_mask >> (symbol - 1)) & 0x1);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1282,7 +1282,8 @@ void nr_pdsch_channel_estimation(PHY_VARS_NR_UE *ue,
|
|||||||
float beta_dmrs_pdsch = get_beta_dmrs(dlsch->n_dmrs_cdm_groups, config_type == NFAPI_NR_DMRS_TYPE2);
|
float beta_dmrs_pdsch = get_beta_dmrs(dlsch->n_dmrs_cdm_groups, config_type == NFAPI_NR_DMRS_TYPE2);
|
||||||
int16_t dmrs_scaling = (int16_t)((1 / beta_dmrs_pdsch) * (1 << 14));
|
int16_t dmrs_scaling = (int16_t)((1 / beta_dmrs_pdsch) * (1 << 14));
|
||||||
const uint32_t *gold = nr_gold_pdsch(fp->N_RB_DL, fp->symbols_per_slot, dlsch->dlDmrsScramblingId, dlsch->nscid, slot, symbol);
|
const uint32_t *gold = nr_gold_pdsch(fp->N_RB_DL, fp->symbols_per_slot, dlsch->dlDmrsScramblingId, dlsch->nscid, slot, symbol);
|
||||||
nr_pdsch_dmrs_rx(fp->Ncp, gold, pilot, 1000 + p, 0, nb_rb_pdsch + rb_offset, config_type, dmrs_scaling);
|
const uint8_t l_prime = get_pdsch_dmrs_l_prime(dlsch->dlDmrsSymbPos, symbol);
|
||||||
|
nr_pdsch_dmrs_rx(fp->Ncp, gold, pilot, 1000 + p, l_prime, nb_rb_pdsch + rb_offset, config_type, dmrs_scaling);
|
||||||
|
|
||||||
delay_t delay = {0};
|
delay_t delay = {0};
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
#include "executables/nr-softmodem-common.h"
|
#include "executables/nr-softmodem-common.h"
|
||||||
#include "nr_transport_proto_ue.h"
|
#include "nr_transport_proto_ue.h"
|
||||||
@@ -22,6 +23,101 @@
|
|||||||
#include "common/utils/nr/nr_common.h"
|
#include "common/utils/nr/nr_common.h"
|
||||||
#include "PHY/NR_UE_ESTIMATION/filt16a_32.h"
|
#include "PHY/NR_UE_ESTIMATION/filt16a_32.h"
|
||||||
|
|
||||||
|
// nakaolab CSI muting study: float Gram matrix + Jacobi eigenvalues for RI and
|
||||||
|
// spatial-spectrum telemetry. MAX_ANT is eight on this platform; the UE still
|
||||||
|
// reports at most NR_MAX_NB_LAYERS (four) because that is the supported PDSCH
|
||||||
|
// decoder/scheduler ceiling.
|
||||||
|
// Sorts eigenvalues in descending order on output.
|
||||||
|
static void nr_hermitian_eigenvalues(const int N,
|
||||||
|
float A_re[MAX_ANT][MAX_ANT],
|
||||||
|
float A_im[MAX_ANT][MAX_ANT],
|
||||||
|
float eigenvalues[MAX_ANT])
|
||||||
|
{
|
||||||
|
for (int sweep = 0; sweep < 64; sweep++) {
|
||||||
|
float off_norm = 0;
|
||||||
|
float diag_norm = 0;
|
||||||
|
for (int i = 0; i < N; i++)
|
||||||
|
diag_norm += A_re[i][i] * A_re[i][i];
|
||||||
|
for (int i = 0; i < N; i++)
|
||||||
|
for (int j = i + 1; j < N; j++)
|
||||||
|
off_norm += A_re[i][j] * A_re[i][j] + A_im[i][j] * A_im[i][j];
|
||||||
|
if (off_norm <= 1e-10f * (diag_norm + 1.0f))
|
||||||
|
break;
|
||||||
|
|
||||||
|
for (int p = 0; p < N - 1; p++) {
|
||||||
|
for (int q = p + 1; q < N; q++) {
|
||||||
|
float re_pq = A_re[p][q], im_pq = A_im[p][q];
|
||||||
|
float mag = sqrtf(re_pq * re_pq + im_pq * im_pq);
|
||||||
|
if (mag < 1e-12f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float cos_phi = re_pq / mag;
|
||||||
|
float sin_phi = im_pq / mag;
|
||||||
|
|
||||||
|
// Phase elimination: rotate column/row q so A[p][q] is real.
|
||||||
|
// Patch 1: skip k==q (was a bug — corrupted diagonal A[q][q]).
|
||||||
|
for (int k = 0; k < N; k++) {
|
||||||
|
if (k == q) continue;
|
||||||
|
float old_re = A_re[k][q], old_im = A_im[k][q];
|
||||||
|
A_re[k][q] = old_re * cos_phi + old_im * sin_phi;
|
||||||
|
A_im[k][q] = -old_re * sin_phi + old_im * cos_phi;
|
||||||
|
}
|
||||||
|
for (int k = 0; k < N; k++) {
|
||||||
|
if (k == q) continue;
|
||||||
|
A_re[q][k] = A_re[k][q];
|
||||||
|
A_im[q][k] = -A_im[k][q];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real Jacobi rotation
|
||||||
|
float d = A_re[q][q] - A_re[p][p];
|
||||||
|
float t;
|
||||||
|
if (fabsf(d) < 1e-12f) {
|
||||||
|
t = 1.0f;
|
||||||
|
} else {
|
||||||
|
float tau = d / (2.0f * mag);
|
||||||
|
t = ((tau >= 0) ? 1.0f : -1.0f) / (fabsf(tau) + sqrtf(1.0f + tau * tau));
|
||||||
|
}
|
||||||
|
float c = 1.0f / sqrtf(1.0f + t * t);
|
||||||
|
float s = t * c;
|
||||||
|
|
||||||
|
A_re[p][p] -= t * mag;
|
||||||
|
A_re[q][q] += t * mag;
|
||||||
|
A_re[p][q] = 0;
|
||||||
|
A_im[p][q] = 0;
|
||||||
|
A_re[q][p] = 0;
|
||||||
|
A_im[q][p] = 0;
|
||||||
|
|
||||||
|
for (int r = 0; r < N; r++) {
|
||||||
|
if (r == p || r == q) continue;
|
||||||
|
float rp_re = A_re[r][p], rp_im = A_im[r][p];
|
||||||
|
float rq_re = A_re[r][q], rq_im = A_im[r][q];
|
||||||
|
A_re[r][p] = c * rp_re - s * rq_re;
|
||||||
|
A_im[r][p] = c * rp_im - s * rq_im;
|
||||||
|
A_re[p][r] = A_re[r][p];
|
||||||
|
A_im[p][r] = -A_im[r][p];
|
||||||
|
A_re[r][q] = s * rp_re + c * rq_re;
|
||||||
|
A_im[r][q] = s * rp_im + c * rq_im;
|
||||||
|
A_re[q][r] = A_re[r][q];
|
||||||
|
A_im[q][r] = -A_im[r][q];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch 2: clamp eigenvalues to >=0 (numerical errors can produce negative values)
|
||||||
|
for (int i = 0; i < N; i++)
|
||||||
|
eigenvalues[i] = A_re[i][i] > 0.0f ? A_re[i][i] : 0.0f;
|
||||||
|
|
||||||
|
// Sort descending
|
||||||
|
for (int i = 0; i < N - 1; i++)
|
||||||
|
for (int j = i + 1; j < N; j++)
|
||||||
|
if (eigenvalues[j] > eigenvalues[i]) {
|
||||||
|
float tmp = eigenvalues[i];
|
||||||
|
eigenvalues[i] = eigenvalues[j];
|
||||||
|
eigenvalues[j] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//#define NR_CSIRS_DEBUG
|
//#define NR_CSIRS_DEBUG
|
||||||
//#define NR_CSIIM_DEBUG
|
//#define NR_CSIIM_DEBUG
|
||||||
|
|
||||||
@@ -65,6 +161,16 @@ void nr_det_A_MF_2x2(int32_t *a_mf_00,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int samples;
|
||||||
|
int dimensions;
|
||||||
|
float eig_rel_db[NR_MAX_NB_LAYERS];
|
||||||
|
// Retained diagonal Gram-matrix energy for active prefix sizes 1/2/4/8,
|
||||||
|
// relative to all configured CSI-RS ports. This is an offline feature for
|
||||||
|
// testing whether CSI adds value to antenna-muting decisions.
|
||||||
|
float prefix_rel_db[4];
|
||||||
|
} nr_csi_muting_summary_t;
|
||||||
|
|
||||||
void nr_squared_matrix_element(int32_t *a,
|
void nr_squared_matrix_element(int32_t *a,
|
||||||
int32_t *a_sq,
|
int32_t *a_sq,
|
||||||
const unsigned short nb_rb) {
|
const unsigned short nb_rb) {
|
||||||
@@ -421,21 +527,114 @@ static int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
|
|||||||
const uint8_t N_ports,
|
const uint8_t N_ports,
|
||||||
c16_t csi_rs_estimated_channel_freq[][N_ports][ue->frame_parms.ofdm_symbol_size],
|
c16_t csi_rs_estimated_channel_freq[][N_ports][ue->frame_parms.ofdm_symbol_size],
|
||||||
const int16_t log2_maxh,
|
const int16_t log2_maxh,
|
||||||
uint8_t *rank_indicator)
|
uint8_t *rank_indicator,
|
||||||
|
nr_csi_muting_summary_t *muting_summary)
|
||||||
{
|
{
|
||||||
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
|
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
|
||||||
const int16_t cond_dB_threshold = 5;
|
const int16_t cond_dB_threshold = 5;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
*rank_indicator = 0;
|
*rank_indicator = 0;
|
||||||
|
if (muting_summary != NULL)
|
||||||
|
memset(muting_summary, 0, sizeof(*muting_summary));
|
||||||
|
|
||||||
if (ue->frame_parms.nb_antennas_rx == 1 || N_ports == 1) {
|
if (ue->frame_parms.nb_antennas_rx == 1 || N_ports == 1) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if( !(ue->frame_parms.nb_antennas_rx == 2 && N_ports == 2) ) {
|
}
|
||||||
LOG_W(NR_PHY, "Rank indicator computation is not implemented for %i x %i system\n",
|
|
||||||
ue->frame_parms.nb_antennas_rx, N_ports);
|
if (N_ports > MAX_ANT || frame_parms->nb_antennas_rx > MAX_ANT) {
|
||||||
|
LOG_W(NR_PHY,
|
||||||
|
"CSI spatial-spectrum computation exceeds platform bound for %i x %i system\n",
|
||||||
|
frame_parms->nb_antennas_rx,
|
||||||
|
N_ports);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int physical_rank = N_ports < frame_parms->nb_antennas_rx ? N_ports : frame_parms->nb_antennas_rx;
|
||||||
|
const int max_rank = physical_rank < NR_MAX_NB_LAYERS ? physical_rank : NR_MAX_NB_LAYERS;
|
||||||
|
if (max_rank < 2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2x2 keeps the existing condition-number path below.
|
||||||
|
// For >2 ports, use Patches 1-3: float Gram matrix + Jacobi eigenvalues + rank voting.
|
||||||
|
if (!(ue->frame_parms.nb_antennas_rx == 2 && N_ports == 2)) {
|
||||||
|
int rank_count[NR_MAX_NB_LAYERS] = {0};
|
||||||
|
double eig_sum[MAX_ANT] = {0};
|
||||||
|
double port_power[MAX_ANT] = {0};
|
||||||
|
const float eig_ratio_dB_threshold = 8.0f;
|
||||||
|
const int sc_stride = physical_rank > 4 ? 4 : 1;
|
||||||
|
for (int rb = csirs_config_pdu->start_rb; rb < (csirs_config_pdu->start_rb + csirs_config_pdu->nr_of_rbs); rb++) {
|
||||||
|
if (csirs_config_pdu->freq_density <= 1 && csirs_config_pdu->freq_density != (rb % 2)) continue;
|
||||||
|
uint16_t k = rb * NR_NB_SC_PER_RB;
|
||||||
|
for (int sc_idx = 0; sc_idx < NR_NB_SC_PER_RB; sc_idx += sc_stride) {
|
||||||
|
// Patch 3: float Gram matrix A = H^H H computed directly from channel estimates
|
||||||
|
// (avoids int16 saturation that capped RI at 2 in 4-RX setups).
|
||||||
|
float A_re[MAX_ANT][MAX_ANT] = {{0}}, A_im[MAX_ANT][MAX_ANT] = {{0}};
|
||||||
|
for (int rx = 0; rx < frame_parms->nb_antennas_rx; rx++) {
|
||||||
|
for (int p1 = 0; p1 < N_ports; p1++) {
|
||||||
|
c16_t h1 = csi_rs_estimated_channel_freq[rx][p1][k + sc_idx];
|
||||||
|
float h1_re = (float)h1.r, h1_im = (float)h1.i;
|
||||||
|
for (int p2 = 0; p2 < N_ports; p2++) {
|
||||||
|
c16_t h2 = csi_rs_estimated_channel_freq[rx][p2][k + sc_idx];
|
||||||
|
float h2_re = (float)h2.r, h2_im = (float)h2.i;
|
||||||
|
A_re[p1][p2] += h1_re * h2_re + h1_im * h2_im;
|
||||||
|
A_im[p1][p2] += -h1_im * h2_re + h1_re * h2_im;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int p = 0; p < N_ports; p++)
|
||||||
|
port_power[p] += A_re[p][p];
|
||||||
|
float eig[MAX_ANT] = {0};
|
||||||
|
nr_hermitian_eigenvalues(N_ports, A_re, A_im, eig);
|
||||||
|
if (eig[0] < 1e-6f) continue;
|
||||||
|
rank_count[0]++;
|
||||||
|
for (int r = 0; r < N_ports; r++)
|
||||||
|
eig_sum[r] += eig[r];
|
||||||
|
for (int r = 1; r < max_rank; r++) {
|
||||||
|
if (eig[r] > 1e-6f && 10.0f * log10f(eig[0] / eig[r]) < eig_ratio_dB_threshold) {
|
||||||
|
rank_count[r]++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int total_sc = rank_count[0];
|
||||||
|
if (total_sc > 0) {
|
||||||
|
for (int r = max_rank - 1; r >= 1; r--) {
|
||||||
|
if (rank_count[r] > total_sc / 2) {
|
||||||
|
*rank_indicator = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (muting_summary != NULL) {
|
||||||
|
muting_summary->samples = total_sc;
|
||||||
|
muting_summary->dimensions = physical_rank;
|
||||||
|
for (int r = 0; r < NR_MAX_NB_LAYERS; r++) {
|
||||||
|
muting_summary->eig_rel_db[r] =
|
||||||
|
r < physical_rank && eig_sum[r] > 0.0 && eig_sum[0] > 0.0
|
||||||
|
? 10.0f * log10f((float)(eig_sum[r] / eig_sum[0]))
|
||||||
|
: -INFINITY;
|
||||||
|
}
|
||||||
|
double full_power = 0.0;
|
||||||
|
for (int p = 0; p < N_ports; p++)
|
||||||
|
full_power += port_power[p];
|
||||||
|
const int prefix_size[4] = {1, 2, 4, 8};
|
||||||
|
for (int candidate = 0; candidate < 4; candidate++) {
|
||||||
|
const int keep = prefix_size[candidate] < N_ports ? prefix_size[candidate] : N_ports;
|
||||||
|
double prefix_power = 0.0;
|
||||||
|
for (int p = 0; p < keep; p++)
|
||||||
|
prefix_power += port_power[p];
|
||||||
|
muting_summary->prefix_rel_db[candidate] =
|
||||||
|
prefix_power > 0.0 && full_power > 0.0
|
||||||
|
? 10.0f * log10f((float)(prefix_power / full_power))
|
||||||
|
: -INFINITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Example 2x2: Hh x H =
|
/* Example 2x2: Hh x H =
|
||||||
* | conjch00 conjch10 | x | ch00 ch01 | = | conjch00*ch00+conjch10*ch10 conjch00*ch01+conjch10*ch11 |
|
* | conjch00 conjch10 | x | ch00 ch01 | = | conjch00*ch00+conjch10*ch10 conjch00*ch01+conjch10*ch11 |
|
||||||
* | conjch01 conjch11 | | ch10 ch11 | | conjch01*ch00+conjch11*ch10 conjch01*ch01+conjch11*ch11 |
|
* | conjch01 conjch11 | | ch10 ch11 | | conjch01*ch00+conjch11*ch10 conjch01*ch01+conjch11*ch11 |
|
||||||
@@ -558,6 +757,9 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
|
|||||||
int32_t *precoded_sinr_dB)
|
int32_t *precoded_sinr_dB)
|
||||||
{
|
{
|
||||||
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
|
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
|
||||||
|
// RF simulators can report exactly zero interference/noise. Keep every PMI
|
||||||
|
// rank path finite, including the local rank-3/4 extension below.
|
||||||
|
const uint32_t non_zero_noise_power = interference_plus_noise_power == 0 ? 1 : interference_plus_noise_power;
|
||||||
|
|
||||||
// i1 is a three-element vector in the form of [i11 i12 i13], when CodebookType is specified as 'Type1SinglePanel'.
|
// i1 is a three-element vector in the form of [i11 i12 i13], when CodebookType is specified as 'Type1SinglePanel'.
|
||||||
// Note that i13 is not applicable when the number of transmission layers is one of {1, 5, 6, 7, 8}.
|
// Note that i13 is not applicable when the number of transmission layers is one of {1, 5, 6, 7, 8}.
|
||||||
@@ -587,7 +789,7 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
|
|||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
const int64_t avg_signal_power = signal_power / count;
|
const int64_t avg_signal_power = signal_power / count;
|
||||||
// Non RF devices like ZMQ has virtually zero noise. So here we make noise as 1 to return maximum sinr.
|
// Non RF devices like ZMQ has virtually zero noise. So here we make noise as 1 to return maximum sinr.
|
||||||
const uint32_t sinr = avg_signal_power / ((interference_plus_noise_power == 0) ? 1 : interference_plus_noise_power);
|
const uint32_t sinr = avg_signal_power / non_zero_noise_power;
|
||||||
*precoded_sinr_dB = dB_fixed(sinr);
|
*precoded_sinr_dB = dB_fixed(sinr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,7 +842,7 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
|
|||||||
for(int p = 0; p<4; p++) {
|
for(int p = 0; p<4; p++) {
|
||||||
int64_t power_re = sum2[p].r - (sum[p].r >> log2_re) * (sum[p].r >> log2_re);
|
int64_t power_re = sum2[p].r - (sum[p].r >> log2_re) * (sum[p].r >> log2_re);
|
||||||
int64_t power_im = sum2[p].i - (sum[p].i >> log2_re) * (sum[p].i >> log2_re);
|
int64_t power_im = sum2[p].i - (sum[p].i >> log2_re) * (sum[p].i >> log2_re);
|
||||||
tested_precoded_sinr[p] = (power_re + power_im) / interference_plus_noise_power;
|
tested_precoded_sinr[p] = (power_re + power_im) / non_zero_noise_power;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(rank_indicator == 0) {
|
if(rank_indicator == 0) {
|
||||||
@@ -655,6 +857,29 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
|
|||||||
*precoded_sinr_dB = dB_fixed((tested_precoded_sinr[i2[0]] + tested_precoded_sinr[i2[0]+2])>>1);
|
*precoded_sinr_dB = dB_fixed((tested_precoded_sinr[i2[0]] + tested_precoded_sinr[i2[0]+2])>>1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (rank_indicator == 2 || rank_indicator == 3) {
|
||||||
|
// RI=3 or RI=4 (4-port): Use identity-like precoder (i2=0).
|
||||||
|
// gNB uses identity_precoding_matrix=1 anyway, so feedback isn't acted on
|
||||||
|
// beyond the SINR-derived CQI. Without this branch, CQI is reported as 0
|
||||||
|
// and the gNB conservatively backs off rank/MCS.
|
||||||
|
// Restored from commit 3117df91cb (lost in upstream csi_rx.c refactor).
|
||||||
|
int nlayers = rank_indicator + 1;
|
||||||
|
int64_t total_power = 0;
|
||||||
|
for (int rb = csirs_config_pdu->start_rb;
|
||||||
|
rb < (csirs_config_pdu->start_rb + csirs_config_pdu->nr_of_rbs); rb++) {
|
||||||
|
if (csirs_config_pdu->freq_density <= 1 && csirs_config_pdu->freq_density != (rb % 2))
|
||||||
|
continue;
|
||||||
|
uint16_t k = rb * NR_NB_SC_PER_RB;
|
||||||
|
for (int ant_rx = 0; ant_rx < frame_parms->nb_antennas_rx; ant_rx++) {
|
||||||
|
for (int p = 0; p < nlayers; p++) {
|
||||||
|
const c16_t h = csi_rs_estimated_channel_freq[ant_rx][p][k];
|
||||||
|
total_power += (int64_t)h.r * h.r + (int64_t)h.i * h.i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i2[0] = 0;
|
||||||
|
if (total_power > 0)
|
||||||
|
*precoded_sinr_dB = dB_fixed((total_power / nlayers) / non_zero_noise_power);
|
||||||
} else {
|
} else {
|
||||||
LOG_W(NR_PHY, "PMI computation is not implemented for rank indicator %i\n", rank_indicator+1);
|
LOG_W(NR_PHY, "PMI computation is not implemented for rank indicator %i\n", rank_indicator+1);
|
||||||
return -1;
|
return -1;
|
||||||
@@ -895,6 +1120,7 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
|
|||||||
&noise_power);
|
&noise_power);
|
||||||
|
|
||||||
uint8_t rank_indicator = 0;
|
uint8_t rank_indicator = 0;
|
||||||
|
nr_csi_muting_summary_t muting_summary = {0};
|
||||||
// bit 1 in bitmap to indicate RI measurment
|
// bit 1 in bitmap to indicate RI measurment
|
||||||
if (csirs_config_pdu->measurement_bitmap & 2) {
|
if (csirs_config_pdu->measurement_bitmap & 2) {
|
||||||
nr_csi_rs_ri_estimation(ue,
|
nr_csi_rs_ri_estimation(ue,
|
||||||
@@ -902,7 +1128,8 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
|
|||||||
mapping_parms.ports,
|
mapping_parms.ports,
|
||||||
csi_rs_estimated_channel_freq,
|
csi_rs_estimated_channel_freq,
|
||||||
log2_maxh,
|
log2_maxh,
|
||||||
&rank_indicator);
|
&rank_indicator,
|
||||||
|
&muting_summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t i1[3] = {0};
|
uint8_t i1[3] = {0};
|
||||||
@@ -926,6 +1153,31 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
|
|||||||
nr_csi_rs_cqi_estimation(precoded_sinr_dB, &cqi);
|
nr_csi_rs_cqi_estimation(precoded_sinr_dB, &cqi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *muting_telemetry = getenv("OAI_CSI_MUTING_TELEMETRY");
|
||||||
|
if (muting_summary.samples > 0 && muting_telemetry != NULL && strcmp(muting_telemetry, "1") == 0) {
|
||||||
|
LOG_I(NR_PHY,
|
||||||
|
"[CSI_MUTING] frame=%d slot=%d ue=%d rx=%u ports=%u samples=%d "
|
||||||
|
"ri=%u cqi=%u sinr_db=%d eig_rel_db=%.2f,%.2f,%.2f,%.2f "
|
||||||
|
"prefix_rel_db=%.2f,%.2f,%.2f,%.2f\n",
|
||||||
|
proc->frame_rx,
|
||||||
|
proc->nr_slot_rx,
|
||||||
|
ue->Mod_id,
|
||||||
|
frame_parms->nb_antennas_rx,
|
||||||
|
mapping_parms.ports,
|
||||||
|
muting_summary.samples,
|
||||||
|
rank_indicator + 1,
|
||||||
|
cqi,
|
||||||
|
precoded_sinr_dB,
|
||||||
|
muting_summary.eig_rel_db[0],
|
||||||
|
muting_summary.eig_rel_db[1],
|
||||||
|
muting_summary.eig_rel_db[2],
|
||||||
|
muting_summary.eig_rel_db[3],
|
||||||
|
muting_summary.prefix_rel_db[0],
|
||||||
|
muting_summary.prefix_rel_db[1],
|
||||||
|
muting_summary.prefix_rel_db[2],
|
||||||
|
muting_summary.prefix_rel_db[3]);
|
||||||
|
}
|
||||||
|
|
||||||
switch (csirs_config_pdu->measurement_bitmap) {
|
switch (csirs_config_pdu->measurement_bitmap) {
|
||||||
case 1 :
|
case 1 :
|
||||||
LOG_I(NR_PHY, "[UE %d] RSRP = %i dBm\n", ue->Mod_id, rsrp_dBm);
|
LOG_I(NR_PHY, "[UE %d] RSRP = %i dBm\n", ue->Mod_id, rsrp_dBm);
|
||||||
|
|||||||
@@ -290,14 +290,19 @@ static void nr_pdcch_channel_compensation(int arraySz,
|
|||||||
static void nr_pdcch_detection_mrc(int nb_ant, int sz, c16_t rxdataF_comp[][sz])
|
static void nr_pdcch_detection_mrc(int nb_ant, int sz, c16_t rxdataF_comp[][sz])
|
||||||
{
|
{
|
||||||
c16_t *rx0 = rxdataF_comp[0];
|
c16_t *rx0 = rxdataF_comp[0];
|
||||||
// MRC on each re of rb
|
/* Accumulate every matched-filter branch with equal weight. The former
|
||||||
// input always aligned and accepting tail padding to process all actual samples
|
* pairwise right shift repeatedly attenuated the existing accumulator, so
|
||||||
for (int a = 1; a < nb_ant; a++) {
|
* with eight RX antennas branch 0 was divided by 128 and PDCCH decoding
|
||||||
c16_t *rx = rxdataF_comp[a];
|
* failed even at high SNR. Use a wide accumulator and saturate once. */
|
||||||
for (int i = 0; i < sz; i += 4) {
|
for (int i = 0; i < sz; i++) {
|
||||||
*(simde__m128i *)(rx0 + i) = simde_mm_adds_epi16(simde_mm_srai_epi16(*(simde__m128i *)(rx0 + i), 1),
|
int32_t real = 0;
|
||||||
simde_mm_srai_epi16(*(simde__m128i *)(rx + i), 1));
|
int32_t imag = 0;
|
||||||
|
for (int a = 0; a < nb_ant; a++) {
|
||||||
|
real += rxdataF_comp[a][i].r;
|
||||||
|
imag += rxdataF_comp[a][i].i;
|
||||||
}
|
}
|
||||||
|
rx0[i].r = min(max(real, INT16_MIN), INT16_MAX);
|
||||||
|
rx0[i].i = min(max(imag, INT16_MIN), INT16_MAX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -852,6 +852,164 @@ static void nr_dlsch_mmse(uint32_t rx_size_symbol,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Joint linear MMSE detector for rank 5--8 PDSCH.
|
||||||
|
*
|
||||||
|
* The legacy fixed-point detector above constructs cofactors recursively and
|
||||||
|
* is deliberately limited to four layers. Two-codeword PDSCH still shares
|
||||||
|
* one physical MIMO channel, so detecting each four-layer codeword in
|
||||||
|
* isolation is incorrect. This path builds H^H H over all active layers for
|
||||||
|
* every data RE, inverts the Hermitian matrix with pivoted Gauss-Jordan
|
||||||
|
* elimination, and equalizes all layers together. The right half of the
|
||||||
|
* augmented matrix is retained to remove the MMSE bias before the existing
|
||||||
|
* QAM soft demappers are used.
|
||||||
|
*/
|
||||||
|
static int nr_dlsch_mmse_high_rank(uint32_t rx_size_symbol,
|
||||||
|
uint8_t n_rx,
|
||||||
|
uint8_t nl,
|
||||||
|
const c16_t rxdataF_ext[][rx_size_symbol],
|
||||||
|
int32_t dl_ch_estimates_ext[][rx_size_symbol],
|
||||||
|
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
|
||||||
|
uint8_t symbol,
|
||||||
|
int length,
|
||||||
|
uint32_t noise_var)
|
||||||
|
{
|
||||||
|
AssertFatal(nl > 4 && nl <= NR_MAX_NB_LAYERS, "Invalid high-rank PDSCH layer count %u\n", nl);
|
||||||
|
if (n_rx < nl) {
|
||||||
|
LOG_E(NR_PHY, "Rank-%u PDSCH requires at least %u UE RX antennas, only %u configured\n", nl, nl, n_rx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double q15 = 32768.0;
|
||||||
|
const double normalized_noise = (double)noise_var / (q15 * q15);
|
||||||
|
|
||||||
|
for (int re = 0; re < length; re++) {
|
||||||
|
double complex gram[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS] = {{0}};
|
||||||
|
double complex rhs[NR_MAX_NB_LAYERS] = {0};
|
||||||
|
|
||||||
|
for (int aarx = 0; aarx < n_rx; aarx++) {
|
||||||
|
const c16_t y_fixed = rxdataF_ext[aarx][re];
|
||||||
|
const double complex y = (double)y_fixed.r + I * (double)y_fixed.i;
|
||||||
|
double complex h[NR_MAX_NB_LAYERS];
|
||||||
|
for (int l = 0; l < nl; l++) {
|
||||||
|
const c16_t *ch = (const c16_t *)dl_ch_estimates_ext[l * n_rx + aarx];
|
||||||
|
h[l] = (double)ch[re].r / q15 + I * ((double)ch[re].i / q15);
|
||||||
|
rhs[l] += conj(h[l]) * y;
|
||||||
|
}
|
||||||
|
for (int row = 0; row < nl; row++)
|
||||||
|
for (int col = 0; col < nl; col++)
|
||||||
|
gram[row][col] += conj(h[row]) * h[col];
|
||||||
|
}
|
||||||
|
|
||||||
|
double trace = 0.0;
|
||||||
|
for (int l = 0; l < nl; l++)
|
||||||
|
trace += creal(gram[l][l]);
|
||||||
|
const double average_gain = trace / nl;
|
||||||
|
/* Channel-estimation residuals include some uncancelled CDM energy before
|
||||||
|
* the two front-loaded DMRS symbols are averaged. Bound that estimate so
|
||||||
|
* it regularizes an ill-conditioned channel without suppressing a valid
|
||||||
|
* spatial stream. */
|
||||||
|
const double ridge_floor = fmax(average_gain * 1e-7, 1e-12);
|
||||||
|
const double ridge = fmax(ridge_floor, fmin(normalized_noise, average_gain * 0.25));
|
||||||
|
|
||||||
|
double complex aug[NR_MAX_NB_LAYERS][2 * NR_MAX_NB_LAYERS] = {{0}};
|
||||||
|
for (int row = 0; row < nl; row++) {
|
||||||
|
for (int col = 0; col < nl; col++)
|
||||||
|
aug[row][col] = gram[row][col] + (row == col ? ridge : 0.0);
|
||||||
|
aug[row][nl + row] = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int col = 0; col < nl; col++) {
|
||||||
|
int pivot_row = col;
|
||||||
|
double pivot_abs = cabs(aug[col][col]);
|
||||||
|
for (int row = col + 1; row < nl; row++) {
|
||||||
|
const double candidate = cabs(aug[row][col]);
|
||||||
|
if (candidate > pivot_abs) {
|
||||||
|
pivot_abs = candidate;
|
||||||
|
pivot_row = row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pivot_abs < 1e-15) {
|
||||||
|
LOG_W(NR_PHY, "Singular rank-%u PDSCH channel matrix at RE %d\n", nl, re);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (pivot_row != col) {
|
||||||
|
for (int j = 0; j < 2 * nl; j++) {
|
||||||
|
const double complex tmp = aug[col][j];
|
||||||
|
aug[col][j] = aug[pivot_row][j];
|
||||||
|
aug[pivot_row][j] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const double complex pivot = aug[col][col];
|
||||||
|
for (int j = 0; j < 2 * nl; j++)
|
||||||
|
aug[col][j] /= pivot;
|
||||||
|
for (int row = 0; row < nl; row++) {
|
||||||
|
if (row == col)
|
||||||
|
continue;
|
||||||
|
const double complex factor = aug[row][col];
|
||||||
|
if (cabs(factor) == 0.0)
|
||||||
|
continue;
|
||||||
|
for (int j = 0; j < 2 * nl; j++)
|
||||||
|
aug[row][j] -= factor * aug[col][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int layer = 0; layer < nl; layer++) {
|
||||||
|
double complex detected = 0.0;
|
||||||
|
for (int col = 0; col < nl; col++)
|
||||||
|
detected += aug[layer][nl + col] * rhs[col];
|
||||||
|
/* diag((G + lambda I)^-1 G) = 1 - lambda*diag(inv),
|
||||||
|
* so divide out the linear-MMSE attenuation before applying fixed QAM
|
||||||
|
* decision boundaries. */
|
||||||
|
const double gain = fmax(0.05, 1.0 - ridge * creal(aug[layer][nl + layer]));
|
||||||
|
detected /= gain;
|
||||||
|
/* Channel-estimation normalization leaves the equalized constellation
|
||||||
|
* sqrt(2) above the native NR modulation-table scale. At 256-QAM that
|
||||||
|
* clips the outer levels in Q15 (15/sqrt(170) * 32768 * sqrt(2) is
|
||||||
|
* greater than INT16_MAX), making the two outermost levels
|
||||||
|
* indistinguishable. Restore the native scale before quantization. */
|
||||||
|
detected *= 0.7071067811865475244;
|
||||||
|
const long real = lround(creal(detected));
|
||||||
|
const long imag = lround(cimag(detected));
|
||||||
|
rxdataF_comp[symbol][layer][0][re].r = min(max(real, -32767), 32767);
|
||||||
|
rxdataF_comp[symbol][layer][0][re].i = min(max(imag, -32767), 32767);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_high_rank_qam_thresholds(uint8_t mod_order,
|
||||||
|
int length,
|
||||||
|
c16_t *mag,
|
||||||
|
c16_t *magb,
|
||||||
|
c16_t *magr)
|
||||||
|
{
|
||||||
|
/* The joint detector is rescaled to the native NR modulation-table domain
|
||||||
|
* before Q15 quantization. Those tables include the per-I/Q 1/sqrt(2)
|
||||||
|
* factor, unlike the legacy matched-filter output for which the QAM*_n
|
||||||
|
* constants are used directly. Scale the decision boundaries likewise. */
|
||||||
|
#define HIGH_RANK_QAM_BOUNDARY(value) (((int32_t)(value) * ONE_OVER_SQRT2_Q15 + (1 << 14)) >> 15)
|
||||||
|
int16_t a = 0;
|
||||||
|
int16_t b = 0;
|
||||||
|
int16_t c = 0;
|
||||||
|
if (mod_order == 4)
|
||||||
|
a = HIGH_RANK_QAM_BOUNDARY(QAM16_n1);
|
||||||
|
else if (mod_order == 6) {
|
||||||
|
a = HIGH_RANK_QAM_BOUNDARY(QAM64_n1);
|
||||||
|
b = HIGH_RANK_QAM_BOUNDARY(QAM64_n2);
|
||||||
|
} else if (mod_order == 8) {
|
||||||
|
a = HIGH_RANK_QAM_BOUNDARY(QAM256_n1);
|
||||||
|
b = HIGH_RANK_QAM_BOUNDARY(QAM256_n2);
|
||||||
|
c = HIGH_RANK_QAM_BOUNDARY(QAM256_n3);
|
||||||
|
}
|
||||||
|
#undef HIGH_RANK_QAM_BOUNDARY
|
||||||
|
for (int re = 0; re < length; re++) {
|
||||||
|
mag[re] = (c16_t){a, a};
|
||||||
|
magb[re] = (c16_t){b, b};
|
||||||
|
magr[re] = (c16_t){c, c};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void nr_dlsch_layer_demapping(const uint8_t Nl,
|
static void nr_dlsch_layer_demapping(const uint8_t Nl,
|
||||||
const uint8_t mod_order,
|
const uint8_t mod_order,
|
||||||
const int llrLayerSize,
|
const int llrLayerSize,
|
||||||
@@ -875,6 +1033,10 @@ static void nr_dlsch_layer_demapping(const uint8_t Nl,
|
|||||||
case 2:
|
case 2:
|
||||||
case 3:
|
case 3:
|
||||||
case 4:
|
case 4:
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
for (int i = s0; i < (s0 + s1); i++) {
|
for (int i = s0; i < (s0 + s1); i++) {
|
||||||
int m = 0;
|
int m = 0;
|
||||||
for (int j = 0; j < re_len[i]; j++) {
|
for (int j = 0; j < re_len[i]; j++) {
|
||||||
@@ -1027,7 +1189,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
|
|||||||
nl,
|
nl,
|
||||||
fp,
|
fp,
|
||||||
csi_res_bitmap,
|
csi_res_bitmap,
|
||||||
ue->chest_time);
|
nl > 4 ? 1 : ue->chest_time);
|
||||||
stop_meas_nr_ue_phy(ue, DLSCH_EXTRACT_RBS_STATS);
|
stop_meas_nr_ue_phy(ue, DLSCH_EXTRACT_RBS_STATS);
|
||||||
if (scope_req->copy_chanest_to_scope) {
|
if (scope_req->copy_chanest_to_scope) {
|
||||||
size_t size = sizeof(c16_t) * nb_rb_pdsch * NR_NB_SC_PER_RB;
|
size_t size = sizeof(c16_t) * nb_rb_pdsch * NR_NB_SC_PER_RB;
|
||||||
@@ -1154,22 +1316,42 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
|
|||||||
//----------------------------------------------------------
|
//----------------------------------------------------------
|
||||||
// Disable correlation measurement for optimizing UE
|
// Disable correlation measurement for optimizing UE
|
||||||
start_meas_nr_ue_phy(ue, DLSCH_CHANNEL_COMPENSATION_STATS);
|
start_meas_nr_ue_phy(ue, DLSCH_CHANNEL_COMPENSATION_STATS);
|
||||||
nr_dlsch_channel_compensation(rx_size_symbol,
|
if (nl > 4) {
|
||||||
nbRx,
|
if (nb_re_pdsch
|
||||||
nl,
|
&& nr_dlsch_mmse_high_rank(rx_size_symbol,
|
||||||
rxdataF_ext,
|
n_rx,
|
||||||
dl_ch_estimates_ext,
|
nl,
|
||||||
dl_ch_mag[symbol],
|
rxdataF_ext,
|
||||||
dl_ch_magb[symbol],
|
dl_ch_estimates_ext,
|
||||||
dl_ch_magr[symbol],
|
rxdataF_comp,
|
||||||
rxdataF_comp,
|
symbol,
|
||||||
NULL,
|
nb_re_pdsch,
|
||||||
fp,
|
nvar)
|
||||||
symbol,
|
< 0)
|
||||||
nb_re_pdsch,
|
return -1;
|
||||||
dlsch->cw_info.qamModOrder,
|
set_high_rank_qam_thresholds(dlsch->cw_info.qamModOrder,
|
||||||
nb_rb_pdsch,
|
nb_re_pdsch,
|
||||||
*log2_maxh); // log2_maxh+I0_shift
|
dl_ch_mag[symbol][0][0],
|
||||||
|
dl_ch_magb[symbol][0][0],
|
||||||
|
dl_ch_magr[symbol][0][0]);
|
||||||
|
} else {
|
||||||
|
nr_dlsch_channel_compensation(rx_size_symbol,
|
||||||
|
nbRx,
|
||||||
|
nl,
|
||||||
|
rxdataF_ext,
|
||||||
|
dl_ch_estimates_ext,
|
||||||
|
dl_ch_mag[symbol],
|
||||||
|
dl_ch_magb[symbol],
|
||||||
|
dl_ch_magr[symbol],
|
||||||
|
rxdataF_comp,
|
||||||
|
NULL,
|
||||||
|
fp,
|
||||||
|
symbol,
|
||||||
|
nb_re_pdsch,
|
||||||
|
dlsch->cw_info.qamModOrder,
|
||||||
|
nb_rb_pdsch,
|
||||||
|
*log2_maxh); // log2_maxh+I0_shift
|
||||||
|
}
|
||||||
stop_meas_nr_ue_phy(ue, DLSCH_CHANNEL_COMPENSATION_STATS);
|
stop_meas_nr_ue_phy(ue, DLSCH_CHANNEL_COMPENSATION_STATS);
|
||||||
if (meas_enabled) {
|
if (meas_enabled) {
|
||||||
LOG_D(PHY,
|
LOG_D(PHY,
|
||||||
@@ -1203,7 +1385,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
|
|||||||
}
|
}
|
||||||
|
|
||||||
start_meas_nr_ue_phy(ue, DLSCH_MRC_MMSE_STATS);
|
start_meas_nr_ue_phy(ue, DLSCH_MRC_MMSE_STATS);
|
||||||
if (n_rx > 1) {
|
if (nl <= 4 && n_rx > 1) {
|
||||||
nr_dlsch_detection_mrc(rx_size_symbol,
|
nr_dlsch_detection_mrc(rx_size_symbol,
|
||||||
nl,
|
nl,
|
||||||
n_rx,
|
n_rx,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "common/utils/threadPool/task_ans.h"
|
#include "common/utils/threadPool/task_ans.h"
|
||||||
#include "openair1/PHY/defs_RU.h"
|
#include "openair1/PHY/defs_RU.h"
|
||||||
#include "common/utils/ds/spsc_q.h"
|
#include "common/utils/ds/spsc_q.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||||
|
|
||||||
#define MAX_NUM_RU_PER_gNB 8
|
#define MAX_NUM_RU_PER_gNB 8
|
||||||
#define MAX_PUCCH0_NID 8
|
#define MAX_PUCCH0_NID 8
|
||||||
@@ -105,13 +106,13 @@ typedef struct {
|
|||||||
/// freq allocation information
|
/// freq allocation information
|
||||||
freq_alloc_bitmap_t freq_alloc;
|
freq_alloc_bitmap_t freq_alloc;
|
||||||
/// pointer to pdu from MAC interface (this is "a" in 36.212)
|
/// pointer to pdu from MAC interface (this is "a" in 36.212)
|
||||||
uint8_t *pdu;
|
uint8_t *pdu[NR_MAX_NB_CODEWORDS];
|
||||||
/// Pointer to the payload
|
/// Pointer to the payload
|
||||||
uint8_t *b;
|
uint8_t *b[NR_MAX_NB_CODEWORDS];
|
||||||
/// Pointers to transport block segments
|
/// Pointers to transport block segments
|
||||||
uint8_t **c;
|
uint8_t **c[NR_MAX_NB_CODEWORDS];
|
||||||
/// Interleaver outputs
|
/// Interleaver outputs
|
||||||
uint8_t *f;
|
uint8_t *f[NR_MAX_NB_CODEWORDS];
|
||||||
/// REs unavailable for DLSCH (overlapping with PTRS, CSIRS etc.)
|
/// REs unavailable for DLSCH (overlapping with PTRS, CSIRS etc.)
|
||||||
uint32_t unav_res;
|
uint32_t unav_res;
|
||||||
} NR_gNB_DLSCH_t;
|
} NR_gNB_DLSCH_t;
|
||||||
@@ -430,6 +431,8 @@ typedef struct PHY_VARS_gNB_s {
|
|||||||
|
|
||||||
bool enable_analog_das;
|
bool enable_analog_das;
|
||||||
|
|
||||||
|
nr_antenna_state_t antenna_state;
|
||||||
|
|
||||||
time_stats_t l1_tx_proc;
|
time_stats_t l1_tx_proc;
|
||||||
time_stats_t l1_rx_proc;
|
time_stats_t l1_rx_proc;
|
||||||
|
|
||||||
|
|||||||
@@ -302,11 +302,11 @@ typedef struct PHY_VARS_NR_UE_s {
|
|||||||
/// \brief Total gain of the RX chain (antenna to baseband I/Q) This is a function of rx_gain_mode (and the corresponding gain) and the rx_gain of the card.
|
/// \brief Total gain of the RX chain (antenna to baseband I/Q) This is a function of rx_gain_mode (and the corresponding gain) and the rx_gain of the card.
|
||||||
uint32_t rx_total_gain_dB;
|
uint32_t rx_total_gain_dB;
|
||||||
/// \brief Total gains with maximum RF gain stage (ExpressMIMO2/Lime)
|
/// \brief Total gains with maximum RF gain stage (ExpressMIMO2/Lime)
|
||||||
uint32_t rx_gain_max[4];
|
uint32_t rx_gain_max[MAX_ANT];
|
||||||
/// \brief Total gains with medium RF gain stage (ExpressMIMO2/Lime)
|
/// \brief Total gains with medium RF gain stage (ExpressMIMO2/Lime)
|
||||||
uint32_t rx_gain_med[4];
|
uint32_t rx_gain_med[MAX_ANT];
|
||||||
/// \brief Total gains with bypassed RF gain stage (ExpressMIMO2/Lime)
|
/// \brief Total gains with bypassed RF gain stage (ExpressMIMO2/Lime)
|
||||||
uint32_t rx_gain_byp[4];
|
uint32_t rx_gain_byp[MAX_ANT];
|
||||||
/// \brief Current transmit power
|
/// \brief Current transmit power
|
||||||
int16_t tx_power_dBm[NR_MAX_SLOTS_PER_FRAME];
|
int16_t tx_power_dBm[NR_MAX_SLOTS_PER_FRAME];
|
||||||
/// \brief Total number of REs in current transmission
|
/// \brief Total number of REs in current transmission
|
||||||
|
|||||||
@@ -19,8 +19,36 @@
|
|||||||
|
|
||||||
#include "assertions.h"
|
#include "assertions.h"
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
static pthread_once_t tx_chain_telemetry_once = PTHREAD_ONCE_INIT;
|
||||||
|
static bool tx_chain_telemetry = false;
|
||||||
|
static atomic_uint tx_chain_proven_mask = ATOMIC_VAR_INIT(0);
|
||||||
|
|
||||||
|
static void configure_tx_chain_telemetry(void)
|
||||||
|
{
|
||||||
|
const char *value = getenv("OAI_TX_CHAIN_TELEMETRY");
|
||||||
|
tx_chain_telemetry = value != NULL && strcmp(value, "1") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tx_chain_telemetry_enabled(void)
|
||||||
|
{
|
||||||
|
pthread_once(&tx_chain_telemetry_once, configure_tx_chain_telemetry);
|
||||||
|
return tx_chain_telemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int count_nnz_c16(const c16_t *v, int n)
|
||||||
|
{
|
||||||
|
int nnz = 0;
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
nnz += (v[i].r != 0) || (v[i].i != 0);
|
||||||
|
return nnz;
|
||||||
|
}
|
||||||
|
|
||||||
// RU OFDM Modulator gNodeB
|
// RU OFDM Modulator gNodeB
|
||||||
// OFDM modulation core routine, generates a first_symbol to first_symbol+num_symbols on a particular slot and TX antenna port
|
// OFDM modulation core routine, generates a first_symbol to first_symbol+num_symbols on a particular slot and TX antenna port
|
||||||
void nr_feptx0(RU_t *ru, int tti_tx, int first_symbol, int num_symbols, int aa)
|
void nr_feptx0(RU_t *ru, int tti_tx, int first_symbol, int num_symbols, int aa)
|
||||||
@@ -165,12 +193,20 @@ void nr_feptx_prec(RU_t *ru, int frame_tx, int slot_tx)
|
|||||||
int Ptx = cfg->carrier_config.num_tx_ant.value;
|
int Ptx = cfg->carrier_config.num_tx_ant.value;
|
||||||
// If there is no digital beamforming we just need to copy the data to RU
|
// If there is no digital beamforming we just need to copy the data to RU
|
||||||
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
|
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
|
||||||
|
const nr_antenna_state_t *ant_state = &gNB->antenna_state;
|
||||||
for (int b = 0; b < ru->num_beams_period; b++) {
|
for (int b = 0; b < ru->num_beams_period; b++) {
|
||||||
for (int i = 0; i < Ptx; ++i) {
|
for (int i = 0; i < Ptx; ++i) {
|
||||||
int tx_idx = i + b * ru->nb_tx;
|
int tx_idx = i + b * ru->nb_tx;
|
||||||
memcpy((void *)ru->common.txdataF_BF[tx_idx],
|
// Check antenna muting state for energy saving
|
||||||
(void *)gNB->common_vars.txdataF[b][i],
|
if (!nr_is_tx_antenna_active(ant_state, i)) {
|
||||||
fp->samples_per_slot_wCP * sizeof(int32_t));
|
// Antenna is muted - zero out the buffer (transmission blanking)
|
||||||
|
memset((void *)ru->common.txdataF_BF[tx_idx], 0, fp->samples_per_slot_wCP * sizeof(int32_t));
|
||||||
|
LOG_D(PHY, "[ANTENNA_MUTE] TX antenna %d muted in nr_feptx_prec, blanking slot %d\n", i, slot_tx);
|
||||||
|
} else {
|
||||||
|
memcpy((void *)ru->common.txdataF_BF[tx_idx],
|
||||||
|
(void *)gNB->common_vars.txdataF[b][i],
|
||||||
|
fp->samples_per_slot_wCP * sizeof(int32_t));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -205,19 +241,48 @@ void nr_feptx(void *arg)
|
|||||||
|
|
||||||
// If there is no digital beamforming we just need to copy the data to RU
|
// If there is no digital beamforming we just need to copy the data to RU
|
||||||
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
|
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
|
||||||
// FFT shift
|
|
||||||
const NR_DL_FRAME_PARMS *fp = &ru->gNB_list[0]->frame_parms;
|
const NR_DL_FRAME_PARMS *fp = &ru->gNB_list[0]->frame_parms;
|
||||||
fft_shift(ru->gNB_list[0]->common_vars.txdataF[bb][aa],
|
const nr_antenna_state_t *ant_state = &ru->gNB_list[0]->antenna_state;
|
||||||
fp->ofdm_symbol_size,
|
if (!nr_is_tx_antenna_active(ant_state, aa)) {
|
||||||
fp->N_RB_DL,
|
// Antenna is muted - zero out the FFT-shift output (transmission blanking)
|
||||||
(c16_t *)ru->common.txdataF_BF[tx_idx],
|
memset((void *)&ru->common.txdataF_BF[tx_idx][startSymbol * fp->ofdm_symbol_size], 0,
|
||||||
fp->ofdm_symbol_size,
|
numSymbols * fp->ofdm_symbol_size * sizeof(int32_t));
|
||||||
startSymbol,
|
} else {
|
||||||
numSymbols);
|
// FFT shift
|
||||||
|
fft_shift(ru->gNB_list[0]->common_vars.txdataF[bb][aa],
|
||||||
|
fp->ofdm_symbol_size,
|
||||||
|
fp->N_RB_DL,
|
||||||
|
(c16_t *)ru->common.txdataF_BF[tx_idx],
|
||||||
|
fp->ofdm_symbol_size,
|
||||||
|
startSymbol,
|
||||||
|
numSymbols);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n");
|
AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One-shot proof that an 8TX run exercises every physical frequency-domain
|
||||||
|
// chain. Check each generated symbol chunk until its chain first carries
|
||||||
|
// energy; normal runs leave this disabled to avoid counting overhead.
|
||||||
|
if (tx_chain_telemetry_enabled()) {
|
||||||
|
const int sample_count = numSymbols * fp->ofdm_symbol_size;
|
||||||
|
const int sample_offset = startSymbol * fp->ofdm_symbol_size;
|
||||||
|
const c16_t *dst = (const c16_t *)&ru->common.txdataF_BF[tx_idx][sample_offset];
|
||||||
|
const int nnz = count_nnz_c16(dst, sample_count);
|
||||||
|
if (nnz > 0) {
|
||||||
|
const unsigned bit = 1u << aa;
|
||||||
|
const unsigned seen = atomic_fetch_or_explicit(
|
||||||
|
&tx_chain_proven_mask, bit, memory_order_relaxed);
|
||||||
|
if ((seen & bit) == 0)
|
||||||
|
LOG_I(PHY,
|
||||||
|
"[TX_CHAIN_PROOF] frame=%d slot=%d ant=%d active=1 nnz=%d\n",
|
||||||
|
ru->proc.frame_tx,
|
||||||
|
slot,
|
||||||
|
aa,
|
||||||
|
nnz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tx_idx == 0)
|
if (tx_idx == 0)
|
||||||
stop_meas(&ru->precoding_stats);
|
stop_meas(&ru->precoding_stats);
|
||||||
|
|
||||||
|
|||||||
@@ -270,7 +270,16 @@ void phy_procedures_gNB_TX(PHY_VARS_gNB *gNB,
|
|||||||
// reuse dlsch variables, as there are multiple very large memory
|
// reuse dlsch variables, as there are multiple very large memory
|
||||||
// buffers
|
// buffers
|
||||||
gNB->dlsch[num_pdsch].pdsch_pdu = &dl_tti_pdu->pdsch_pdu;
|
gNB->dlsch[num_pdsch].pdsch_pdu = &dl_tti_pdu->pdsch_pdu;
|
||||||
gNB->dlsch[num_pdsch].pdu = (uint8_t *)TX_req->pdu_list[tx_data_idx].TLVs[0].value.direct;
|
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *pdsch = &dl_tti_pdu->pdsch_pdu.pdsch_pdu_rel15;
|
||||||
|
const nfapi_nr_pdu_t *tx_pdu = &TX_req->pdu_list[tx_data_idx];
|
||||||
|
AssertFatal(tx_pdu->num_TLV == pdsch->NrOfCodewords,
|
||||||
|
"%4d.%2d PDSCH has %u codewords but TX request has %u TLVs\n",
|
||||||
|
frame,
|
||||||
|
slot,
|
||||||
|
pdsch->NrOfCodewords,
|
||||||
|
tx_pdu->num_TLV);
|
||||||
|
for (int q = 0; q < NR_MAX_NB_CODEWORDS; q++)
|
||||||
|
gNB->dlsch[num_pdsch].pdu[q] = q < tx_pdu->num_TLV ? (uint8_t *)tx_pdu->TLVs[q].value.direct : NULL;
|
||||||
DevAssert(num_pdsch < gNB->max_nb_pdsch);
|
DevAssert(num_pdsch < gNB->max_nb_pdsch);
|
||||||
num_pdsch++;
|
num_pdsch++;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -499,9 +499,19 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
|
|||||||
int frame_rx = proc->frame_rx;
|
int frame_rx = proc->frame_rx;
|
||||||
int nr_slot_rx = proc->nr_slot_rx;
|
int nr_slot_rx = proc->nr_slot_rx;
|
||||||
|
|
||||||
// We handle only one CW now
|
if (dlsch->cw_info.Nl < 1 || dlsch->cw_info.Nl > NR_MAX_NB_LAYERS) {
|
||||||
if (NR_MAX_NB_LAYERS > 4) {
|
LOG_E(NR_PHY, "Invalid PDSCH layer count %u\n", dlsch->cw_info.Nl);
|
||||||
LOG_E(NR_PHY, "Handling of more than one CW not implemented\n");
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,14 +592,23 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
|
|||||||
nvar /= (dlschCfg->number_symbols * dlsch->cw_info.Nl * ue->frame_parms.nb_antennas_rx);
|
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);
|
nr_ue_measurement_procedures(2, ue, proc, freq_alloc->num_rbs, pdsch_est_size, pdsch_dl_ch_estimates);
|
||||||
|
|
||||||
if (ue->chest_time == 1) { // averaging time domain channel estimates
|
if (ue->chest_time == 1 || dlsch->cw_info.Nl > 4) {
|
||||||
nr_chest_time_domain_avg(&ue->frame_parms,
|
/* Rank 5--8 uses maxLength=len2 DMRS. Ports 1004--1007 share
|
||||||
(int32_t **)pdsch_dl_ch_estimates,
|
* frequency-domain OCC with ports 1000--1003 and are separated by the
|
||||||
dlschCfg->number_symbols,
|
* time-domain OCC across the two front-loaded symbols, so averaging is
|
||||||
dlschCfg->start_symbol,
|
* mandatory. Apply it to every layer, not only the first RX block. */
|
||||||
dlschCfg->dlDmrsSymbPos,
|
for (int layer = 0; layer < dlsch->cw_info.Nl; layer++) {
|
||||||
freq_alloc->num_rbs,
|
int32_t *layer_ch_estimates[ue->frame_parms.nb_antennas_rx];
|
||||||
dlsch->cw_info.Nl);
|
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;
|
uint16_t first_symbol_with_data = dlschCfg->start_symbol;
|
||||||
@@ -652,6 +671,7 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
|
|||||||
ue->frame_parms.nb_antennas_rx,
|
ue->frame_parms.nb_antennas_rx,
|
||||||
rx_size_symbol,
|
rx_size_symbol,
|
||||||
false);
|
false);
|
||||||
|
int pdsch_status = 0;
|
||||||
for (int m = dlschCfg->start_symbol; m < (dlschCfg->number_symbols + dlschCfg->start_symbol); m++) {
|
for (int m = dlschCfg->start_symbol; m < (dlschCfg->number_symbols + dlschCfg->start_symbol); m++) {
|
||||||
bool first_symbol_flag = false;
|
bool first_symbol_flag = false;
|
||||||
if (m == first_symbol_with_data)
|
if (m == first_symbol_with_data)
|
||||||
@@ -686,13 +706,8 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
|
|||||||
nvar,
|
nvar,
|
||||||
&scope_req)
|
&scope_req)
|
||||||
< 0) {
|
< 0) {
|
||||||
if (scope_req.copy_chanest_to_scope) {
|
pdsch_status = -1;
|
||||||
UEunlockScopeData(ue, pdschChanEstimates);
|
break;
|
||||||
}
|
|
||||||
if (scope_req.copy_rxdataF_to_scope) {
|
|
||||||
UEunlockScopeData(ue, pdschRxdataF);
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
} // CRNTI active
|
} // CRNTI active
|
||||||
stop_meas_nr_ue_phy(ue, RX_PDSCH_STATS);
|
stop_meas_nr_ue_phy(ue, RX_PDSCH_STATS);
|
||||||
@@ -707,7 +722,7 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
|
|||||||
free(toFree3);
|
free(toFree3);
|
||||||
free(toFree4);
|
free(toFree4);
|
||||||
free(toFree5);
|
free(toFree5);
|
||||||
return 0;
|
return pdsch_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t compute_csi_rm_unav_res(fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config)
|
static uint32_t compute_csi_rm_unav_res(fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config)
|
||||||
@@ -797,7 +812,7 @@ static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
|
|||||||
}
|
}
|
||||||
|
|
||||||
start_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
|
start_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
|
||||||
nr_dlsch_unscrambling(llr, G, 0, config->dlDataScramblingId, dlsch->rnti);
|
nr_dlsch_unscrambling(llr, G, cw_idx, config->dlDataScramblingId, dlsch->rnti);
|
||||||
stop_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
|
stop_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
|
||||||
|
|
||||||
start_meas_nr_ue_phy(ue, DLSCH_DECODING_STATS);
|
start_meas_nr_ue_phy(ue, DLSCH_DECODING_STATS);
|
||||||
@@ -854,7 +869,7 @@ static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
|
|||||||
uint32_t dlsch_bytes = a_segments * 1056; // allocated bytes per segment
|
uint32_t dlsch_bytes = a_segments * 1056; // allocated bytes per segment
|
||||||
|
|
||||||
if (ue->phy_sim_dlsch_b)
|
if (ue->phy_sim_dlsch_b)
|
||||||
memcpy(ue->phy_sim_dlsch_b, dl_harq->b, dlsch_bytes);
|
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)
|
static bool check_meas_to_perform(PHY_VARS_NR_UE *ue, int nr_slot_rx)
|
||||||
@@ -1157,13 +1172,16 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
|
|||||||
nr_ue_csi_rs_procedures(ue, proc, rxdataF, &phy_data->csirs_vars.csirs_config_pdu);
|
nr_ue_csi_rs_procedures(ue, proc, rxdataF, &phy_data->csirs_vars.csirs_config_pdu);
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t *llr[2];
|
int16_t *llr[NR_MAX_NB_CODEWORDS] = {0};
|
||||||
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &phy_data->dlsch_config;
|
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &phy_data->dlsch_config;
|
||||||
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
|
AssertFatal(phy_data->n_dlsch_codewords >= 0 && phy_data->n_dlsch_codewords <= NR_MAX_NB_CODEWORDS,
|
||||||
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
|
"Invalid number of PDSCH codewords %d\n",
|
||||||
if (!dlsch->active)
|
phy_data->n_dlsch_codewords);
|
||||||
continue;
|
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 nb_symb_sch = dlsch_config->number_symbols;
|
||||||
uint16_t start_symb_sch = dlsch_config->start_symbol;
|
uint16_t start_symb_sch = dlsch_config->start_symbol;
|
||||||
|
|
||||||
@@ -1198,52 +1216,127 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
|
|||||||
unav_res = n_ptrs * ptrsSymbPerSlot;
|
unav_res = n_ptrs * ptrsSymbPerSlot;
|
||||||
}
|
}
|
||||||
unav_res += compute_csi_rm_unav_res(dlsch_config);
|
unav_res += compute_csi_rm_unav_res(dlsch_config);
|
||||||
int G = nr_get_G(freq_alloc.num_rbs,
|
int G[NR_MAX_NB_CODEWORDS] = {0};
|
||||||
dlsch_config->number_symbols,
|
uint32_t rx_llr_buf_sz[NR_MAX_NB_CODEWORDS] = {0};
|
||||||
nb_re_dmrs,
|
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
|
||||||
dmrs_len,
|
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
|
||||||
unav_res,
|
G[c] = nr_get_G(freq_alloc.num_rbs,
|
||||||
dlsch->cw_info.qamModOrder,
|
dlsch_config->number_symbols,
|
||||||
dlsch->cw_info.Nl);
|
nb_re_dmrs,
|
||||||
const uint32_t rx_llr_buf_sz = ALIGNARRAYSIZE(G, 32); // each LLR is 2 bytes hence 64 byte aligned
|
dmrs_len,
|
||||||
llr[c] = (int16_t *)malloc16_clear(rx_llr_buf_sz * sizeof(int16_t));
|
unav_res,
|
||||||
|
dlsch->cw_info.qamModOrder,
|
||||||
// dlsch_harq contains the previous transmissions data for this harq pid
|
dlsch->cw_info.Nl);
|
||||||
NR_DL_UE_HARQ_t *harq = &ue->dl_harq_processes[c][dlsch_config->harq_process_nbr];
|
rx_llr_buf_sz[c] = ALIGNARRAYSIZE(G[c], 32); // each LLR is 2 bytes hence 64 byte aligned
|
||||||
// it returns -1 in case of internal failure, or 0 in case of normal result
|
llr[c] = (int16_t *)malloc16_clear(rx_llr_buf_sz[c] * sizeof(int16_t));
|
||||||
int ret_pdsch = nr_ue_pdsch_procedures(ue, proc, dlsch, harq, dlsch_config, llr[c], rxdataF, &freq_alloc);
|
|
||||||
TracyCPlot("pdsch mcs", dlsch->cw_info.mcs);
|
|
||||||
|
|
||||||
UEscopeCopy(ue, pdschLlr, llr[c], sizeof(int16_t), 1, G, 0);
|
|
||||||
|
|
||||||
LOG_D(PHY, "DLSCH data reception at nr_slot_rx: %d\n", nr_slot_rx);
|
|
||||||
start_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
|
|
||||||
|
|
||||||
if (ret_pdsch >= 0) {
|
|
||||||
nr_ue_dlsch_procedures(ue, proc, dlsch, c, G, &freq_alloc, dlsch_config, llr[c]);
|
|
||||||
} else {
|
|
||||||
LOG_E(NR_PHY, "Demodulation impossible, internal error\n");
|
|
||||||
if (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]);
|
|
||||||
}
|
|
||||||
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);
|
int ret_pdsch = 0;
|
||||||
if (cpumeas(CPUMEAS_GETSTATE)) {
|
if (phy_data->n_dlsch_codewords == 2) {
|
||||||
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));
|
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);
|
||||||
|
int16_t *joint_llr = (int16_t *)malloc16_clear(joint_buf_sz * sizeof(*joint_llr));
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
free(joint_llr);
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
if (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]);
|
||||||
|
}
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ue->phy_sim_rxdataF)
|
if (ue->phy_sim_rxdataF)
|
||||||
memcpy(ue->phy_sim_rxdataF + start_symb_sch * ue->frame_parms.ofdm_symbol_size * sizeof(c16_t),
|
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],
|
&rxdataF[0][start_symb_sch * ue->frame_parms.ofdm_symbol_size],
|
||||||
sizeof(int32_t) * nb_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)
|
if (ue->phy_sim_pdsch_llr) {
|
||||||
memcpy(ue->phy_sim_pdsch_llr, llr[c], sizeof(int16_t) * rx_llr_buf_sz);
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
free(llr[c]);
|
for (int c = 0; c < phy_data->n_dlsch_codewords; c++)
|
||||||
|
free(llr[c]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nr_slot_rx==9) {
|
if (nr_slot_rx==9) {
|
||||||
|
|||||||
@@ -475,7 +475,7 @@ int main(int argc, char **argv)
|
|||||||
printf("harq process ue mcs = %d Qm = %d, symb %d\n", dlsch_config.cw_info[0].mcs, dlsch_config.cw_info[0].qamModOrder, nb_symb_sch);
|
printf("harq process ue mcs = %d Qm = %d, symb %d\n", dlsch_config.cw_info[0].mcs, dlsch_config.cw_info[0].qamModOrder, nb_symb_sch);
|
||||||
|
|
||||||
uint8_t test_input[TBS / 8 + 4]; // + 3 for CRC + 1 additional byte, see nr_dlsch_encoding()
|
uint8_t test_input[TBS / 8 + 4]; // + 3 for CRC + 1 additional byte, see nr_dlsch_encoding()
|
||||||
dlsch->pdu = test_input;
|
dlsch->pdu[0] = test_input;
|
||||||
//unsigned char test_input[TBS / 8] __attribute__ ((aligned(16)));
|
//unsigned char test_input[TBS / 8] __attribute__ ((aligned(16)));
|
||||||
for (i = 0; i < TBS / 8; i++)
|
for (i = 0; i < TBS / 8; i++)
|
||||||
test_input[i] = (unsigned char) rand();
|
test_input[i] = (unsigned char) rand();
|
||||||
|
|||||||
@@ -240,14 +240,7 @@ void nr_dlsim_preprocessor(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pdsch)
|
|||||||
|
|
||||||
sched_pdsch.Qm = nr_get_Qm_dl(sched_pdsch.mcs, current_BWP->mcsTableIdx);
|
sched_pdsch.Qm = nr_get_Qm_dl(sched_pdsch.mcs, current_BWP->mcsTableIdx);
|
||||||
sched_pdsch.R = nr_get_code_rate_dl(sched_pdsch.mcs, current_BWP->mcsTableIdx);
|
sched_pdsch.R = nr_get_code_rate_dl(sched_pdsch.mcs, current_BWP->mcsTableIdx);
|
||||||
sched_pdsch.tb_size = nr_compute_tbs(sched_pdsch.Qm,
|
update_sched_pdsch_codeword_tbs(&sched_pdsch);
|
||||||
sched_pdsch.R,
|
|
||||||
sched_pdsch.rbSize,
|
|
||||||
sched_pdsch.tda_info.nrOfSymbols,
|
|
||||||
sched_pdsch.dmrs_parms.N_PRB_DMRS * sched_pdsch.dmrs_parms.N_DMRS_SLOT,
|
|
||||||
0 /* N_PRB_oh, 0 for initialBWP */,
|
|
||||||
0 /* tb_scaling */,
|
|
||||||
sched_pdsch.nrOfLayers) >> 3;
|
|
||||||
|
|
||||||
/* the simulator assumes the HARQ PID is equal to the slot number */
|
/* the simulator assumes the HARQ PID is equal to the slot number */
|
||||||
sched_pdsch.dl_harq_pid = pp_pdsch->slot;
|
sched_pdsch.dl_harq_pid = pp_pdsch->slot;
|
||||||
@@ -471,7 +464,7 @@ int main(int argc, char **argv)
|
|||||||
case 'x':
|
case 'x':
|
||||||
g_nrOfLayers = atoi(optarg);
|
g_nrOfLayers = atoi(optarg);
|
||||||
|
|
||||||
if ((g_nrOfLayers == 0) || (g_nrOfLayers > 4)) {
|
if ((g_nrOfLayers == 0) || (g_nrOfLayers > NR_MAX_NB_LAYERS)) {
|
||||||
printf("Unsupported nr Of Layers %d\n", g_nrOfLayers);
|
printf("Unsupported nr Of Layers %d\n", g_nrOfLayers);
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
@@ -490,7 +483,7 @@ int main(int argc, char **argv)
|
|||||||
case 'y':
|
case 'y':
|
||||||
n_tx=atoi(optarg);
|
n_tx=atoi(optarg);
|
||||||
|
|
||||||
if ((n_tx==0) || (n_tx>4)) {//extend gNB to support n_tx = 4
|
if ((n_tx == 0) || (n_tx > NR_MAX_NB_LAYERS)) {
|
||||||
printf("Unsupported number of tx antennas %d\n",n_tx);
|
printf("Unsupported number of tx antennas %d\n",n_tx);
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
@@ -500,7 +493,7 @@ int main(int argc, char **argv)
|
|||||||
case 'z':
|
case 'z':
|
||||||
n_rx=atoi(optarg);
|
n_rx=atoi(optarg);
|
||||||
|
|
||||||
if ((n_rx==0) || (n_rx>4)) {//extend UE to support n_tx = 4
|
if ((n_rx == 0) || (n_rx > NR_MAX_NB_LAYERS)) {
|
||||||
printf("Unsupported number of rx antennas %d\n",n_rx);
|
printf("Unsupported number of rx antennas %d\n",n_rx);
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
@@ -940,8 +933,6 @@ int main(int argc, char **argv)
|
|||||||
UE_mac->if_module = nr_ue_if_module_init(0);
|
UE_mac->if_module = nr_ue_if_module_init(0);
|
||||||
|
|
||||||
unsigned int available_bits=0;
|
unsigned int available_bits=0;
|
||||||
unsigned char *estimated_output_bit=NULL;
|
|
||||||
unsigned char *test_input_bit=NULL;
|
|
||||||
unsigned int errors_bit = 0;
|
unsigned int errors_bit = 0;
|
||||||
|
|
||||||
initFloatingCoresTpool(dlsch_threads, &nrUE_params.Tpool, false, "UE-tpool");
|
initFloatingCoresTpool(dlsch_threads, &nrUE_params.Tpool, false, "UE-tpool");
|
||||||
@@ -987,7 +978,7 @@ int main(int argc, char **argv)
|
|||||||
a_segments = (a_segments/273)+1;
|
a_segments = (a_segments/273)+1;
|
||||||
}
|
}
|
||||||
uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment
|
uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment
|
||||||
UE->phy_sim_dlsch_b = calloc(1, dlsch_bytes);
|
UE->phy_sim_dlsch_b = calloc(NR_MAX_NB_CODEWORDS, dlsch_bytes);
|
||||||
|
|
||||||
// csv file
|
// csv file
|
||||||
if (filename_csv != NULL) {
|
if (filename_csv != NULL) {
|
||||||
@@ -1069,17 +1060,19 @@ int main(int argc, char **argv)
|
|||||||
UE_proc.nr_slot_rx = slot;
|
UE_proc.nr_slot_rx = slot;
|
||||||
UE_proc.gNB_id = 0;
|
UE_proc.gNB_id = 0;
|
||||||
|
|
||||||
NR_UE_DLSCH_t *dlsch0 = &phy_data.dlsch[0];
|
|
||||||
|
|
||||||
int harq_pid = slot;
|
int harq_pid = slot;
|
||||||
NR_DL_UE_HARQ_t *UE_harq_process = &UE->dl_harq_processes[0][harq_pid];
|
NR_DL_UE_HARQ_t *UE_harq_process[NR_MAX_NB_CODEWORDS];
|
||||||
|
|
||||||
UE_harq_process->decodeResult = false;
|
|
||||||
round = 0;
|
round = 0;
|
||||||
UE_harq_process->DLround = round;
|
for (int c = 0; c < NR_MAX_NB_CODEWORDS; c++) {
|
||||||
UE_harq_process->first_rx = 1;
|
UE_harq_process[c] = &UE->dl_harq_processes[c][harq_pid];
|
||||||
|
UE_harq_process[c]->decodeResult = false;
|
||||||
|
UE_harq_process[c]->DLround = round;
|
||||||
|
UE_harq_process[c]->first_rx = 1;
|
||||||
|
}
|
||||||
|
int n_codewords = 0;
|
||||||
|
bool all_codewords_decoded = false;
|
||||||
|
|
||||||
while (round < num_rounds && !UE_harq_process->decodeResult && !stop) {
|
while (round < num_rounds && !all_codewords_decoded && !stop) {
|
||||||
reset_sched_response(Sched_INFO, frame, slot, 0, 0);
|
reset_sched_response(Sched_INFO, frame, slot, 0, 0);
|
||||||
clear_nr_nfapi_information(RC.nrmac[0], 0, frame, slot);
|
clear_nr_nfapi_information(RC.nrmac[0], 0, frame, slot);
|
||||||
UE_info->UE_sched_ctrl.harq_processes[harq_pid].ndi = !(trial&1);
|
UE_info->UE_sched_ctrl.harq_processes[harq_pid].ndi = !(trial&1);
|
||||||
@@ -1302,59 +1295,97 @@ int main(int argc, char **argv)
|
|||||||
//---------------------- count errors ----------------------
|
//---------------------- count errors ----------------------
|
||||||
//----------------------------------------------------------
|
//----------------------------------------------------------
|
||||||
|
|
||||||
if (dlsch0->last_iteration_cnt >= dlsch0->max_ldpc_iterations)
|
n_codewords = phy_data.n_dlsch_codewords;
|
||||||
|
AssertFatal(n_codewords >= 1 && n_codewords <= NR_MAX_NB_CODEWORDS,
|
||||||
|
"Invalid number of decoded PDSCH codewords %d\n",
|
||||||
|
n_codewords);
|
||||||
|
bool block_error = false;
|
||||||
|
bool any_active_codeword = false;
|
||||||
|
all_codewords_decoded = true;
|
||||||
|
for (int c = 0; c < n_codewords; c++) {
|
||||||
|
NR_UE_DLSCH_t *dlsch = &phy_data.dlsch[c];
|
||||||
|
any_active_codeword |= dlsch->active;
|
||||||
|
block_error |= dlsch->active && dlsch->last_iteration_cnt >= dlsch->max_ldpc_iterations;
|
||||||
|
all_codewords_decoded &= UE_harq_process[c]->decodeResult;
|
||||||
|
}
|
||||||
|
if (block_error)
|
||||||
n_errors[round]++;
|
n_errors[round]++;
|
||||||
|
if (any_active_codeword)
|
||||||
if (dlsch0->active)
|
|
||||||
round_trials[round]++;
|
round_trials[round]++;
|
||||||
|
|
||||||
int16_t *UE_llr = (int16_t*)UE->phy_sim_pdsch_llr;
|
int16_t *UE_llr = (int16_t*)UE->phy_sim_pdsch_llr;
|
||||||
|
|
||||||
TBS = phy_data.dlsch_config.cw_info[0].TBS;
|
TBS = 0;
|
||||||
uint16_t length_dmrs = get_num_dmrs(phy_data.dlsch_config.dlDmrsSymbPos);
|
uint16_t length_dmrs = get_num_dmrs(phy_data.dlsch_config.dlDmrsSymbPos);
|
||||||
uint16_t nb_rb = phy_data.dlsch_config.number_rbs;
|
uint16_t nb_rb = phy_data.dlsch_config.number_rbs;
|
||||||
uint8_t nb_re_dmrs = phy_data.dlsch_config.dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ?
|
uint8_t nb_re_dmrs = phy_data.dlsch_config.dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ?
|
||||||
6 * phy_data.dlsch_config.n_dmrs_cdm_groups :
|
6 * phy_data.dlsch_config.n_dmrs_cdm_groups :
|
||||||
4 * phy_data.dlsch_config.n_dmrs_cdm_groups;
|
4 * phy_data.dlsch_config.n_dmrs_cdm_groups;
|
||||||
uint8_t mod_order = phy_data.dlsch_config.cw_info[0].qamModOrder;
|
|
||||||
uint8_t nb_symb_sch = phy_data.dlsch_config.number_symbols;
|
uint8_t nb_symb_sch = phy_data.dlsch_config.number_symbols;
|
||||||
uint32_t unav_res = ptrsSymbPerSlot * ptrsRePerSymb;
|
uint32_t unav_res = ptrsSymbPerSlot * ptrsRePerSymb;
|
||||||
available_bits = nr_get_G(nb_rb, nb_symb_sch, nb_re_dmrs, length_dmrs, unav_res, mod_order, pdsch_pdu_rel15->nrOfLayers);
|
available_bits = 0;
|
||||||
|
for (int c = 0; c < n_codewords; c++) {
|
||||||
|
const fapi_nr_dl_cw_info_t *cw_info = &phy_data.dlsch_config.cw_info[c];
|
||||||
|
TBS += cw_info->TBS;
|
||||||
|
available_bits += nr_get_G(nb_rb,
|
||||||
|
nb_symb_sch,
|
||||||
|
nb_re_dmrs,
|
||||||
|
length_dmrs,
|
||||||
|
unav_res,
|
||||||
|
cw_info->qamModOrder,
|
||||||
|
cw_info->Nl);
|
||||||
|
}
|
||||||
if (pdu_bit_map & 0x1) {
|
if (pdu_bit_map & 0x1) {
|
||||||
if (trial == 0 && round == 0) {
|
if (trial == 0 && round == 0) {
|
||||||
printf("[DLSIM][PTRS] Available bits are: %5u, removed PTRS bits are: %5u \n", available_bits, (ptrsSymbPerSlot * ptrsRePerSymb * pdsch_pdu_rel15->nrOfLayers * mod_order));
|
printf("[DLSIM][PTRS] Available bits are: %5u, removed PTRS REs are: %5u \n",
|
||||||
|
available_bits,
|
||||||
|
ptrsSymbPerSlot * ptrsRePerSymb * pdsch_pdu_rel15->nrOfLayers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < available_bits; i++) {
|
uint32_t llr_offset = 0;
|
||||||
const uint8_t current_bit = (gNB_dlsch->f[i / 8] & (1 << (i & 7))) >> (i & 7);
|
for (int c = 0; c < n_codewords; c++) {
|
||||||
if (((current_bit == 0) && (UE_llr[i] <= 0)) || ((current_bit == 1) && (UE_llr[i] >= 0))) {
|
const fapi_nr_dl_cw_info_t *cw_info = &phy_data.dlsch_config.cw_info[c];
|
||||||
if (errors_scrambling[round] == 0) {
|
const uint32_t codeword_bits = nr_get_G(nb_rb,
|
||||||
LOG_D(PHY,"First bit in error in unscrambling = %d\n",i);
|
nb_symb_sch,
|
||||||
|
nb_re_dmrs,
|
||||||
|
length_dmrs,
|
||||||
|
unav_res,
|
||||||
|
cw_info->qamModOrder,
|
||||||
|
cw_info->Nl);
|
||||||
|
for (i = 0; i < codeword_bits; i++) {
|
||||||
|
const uint8_t current_bit = (gNB_dlsch->f[c][i / 8] & (1 << (i & 7))) >> (i & 7);
|
||||||
|
if (((current_bit == 0) && (UE_llr[llr_offset + i] <= 0))
|
||||||
|
|| ((current_bit == 1) && (UE_llr[llr_offset + i] >= 0))) {
|
||||||
|
if (errors_scrambling[round] == 0)
|
||||||
|
LOG_D(PHY, "First bit in error in unscrambling = CW%d bit %d\n", c, i);
|
||||||
|
errors_scrambling[round]++;
|
||||||
}
|
}
|
||||||
errors_scrambling[round]++;
|
|
||||||
}
|
}
|
||||||
|
llr_offset += codeword_bits;
|
||||||
}
|
}
|
||||||
|
AssertFatal(llr_offset == available_bits,
|
||||||
|
"PDSCH LLR accounting mismatch: %u of %u bits\n",
|
||||||
|
llr_offset,
|
||||||
|
available_bits);
|
||||||
|
|
||||||
//printf("dlsim round %d ends\n",round);
|
//printf("dlsim round %d ends\n",round);
|
||||||
round++;
|
round++;
|
||||||
} // round
|
} // round
|
||||||
|
|
||||||
if (test_input_bit == NULL) {
|
for (int c = 0; c < n_codewords; c++) {
|
||||||
test_input_bit = (unsigned char *)malloc16(8 * pdsch_pdu_rel15->TBSize[0]);
|
const uint32_t codeword_tbs = phy_data.dlsch_config.cw_info[c].TBS;
|
||||||
estimated_output_bit = (unsigned char *)malloc16(8 * pdsch_pdu_rel15->TBSize[0]);
|
const uint8_t *decoded = UE->phy_sim_dlsch_b + c * dlsch_bytes;
|
||||||
}
|
for (i = 0; i < codeword_tbs; i++) {
|
||||||
for (i = 0; i < TBS; i++) {
|
const uint8_t estimated_output_bit = (decoded[i / 8] & (1 << (i & 7))) >> (i & 7);
|
||||||
|
const uint8_t test_input_bit = (gNB_dlsch->b[c][i / 8] & (1 << (i & 7))) >> (i & 7);
|
||||||
|
|
||||||
estimated_output_bit[i] = (UE->phy_sim_dlsch_b[i/8] & (1 << (i & 7))) >> (i & 7);
|
if (estimated_output_bit != test_input_bit) {
|
||||||
test_input_bit[i] = (gNB_dlsch->b[i / 8] & (1 << (i & 7))) >> (i & 7); // Further correct for multiple segments
|
if (errors_bit == 0)
|
||||||
|
LOG_D(PHY, "First bit in error in decoding = CW%d bit %d\n", c, i);
|
||||||
if (estimated_output_bit[i] != test_input_bit[i]) {
|
errors_bit++;
|
||||||
if(errors_bit == 0)
|
}
|
||||||
LOG_D(PHY,"First bit in error in decoding = %d\n",i);
|
}
|
||||||
errors_bit++;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@@ -1364,7 +1395,7 @@ int main(int argc, char **argv)
|
|||||||
printf("errors_bit = %u (trial %d)\n", errors_bit, trial);
|
printf("errors_bit = %u (trial %d)\n", errors_bit, trial);
|
||||||
}
|
}
|
||||||
roundStats += round;
|
roundStats += round;
|
||||||
if (UE_harq_process->decodeResult)
|
if (all_codewords_decoded)
|
||||||
effRate += ((double)TBS) / round;
|
effRate += ((double)TBS) / round;
|
||||||
} // noise trials
|
} // noise trials
|
||||||
|
|
||||||
@@ -1391,7 +1422,11 @@ int main(int argc, char **argv)
|
|||||||
printf("), Channel BER (%e", berStats[0]);
|
printf("), Channel BER (%e", berStats[0]);
|
||||||
for (int r = 1; r < num_rounds; r++)
|
for (int r = 1; r < num_rounds; r++)
|
||||||
printf(",%e", berStats[r]);
|
printf(",%e", berStats[r]);
|
||||||
printf(") Avg round %.2f, Eff Rate %.4f bits/slot, Eff Throughput %.2f, TBS %u bits/slot\n", roundStats, effRate, effRate / (8 * pdsch_pdu_rel15->TBSize[0]) * 100, 8 * pdsch_pdu_rel15->TBSize[0]);
|
printf(") Avg round %.2f, Eff Rate %.4f bits/slot, Eff Throughput %.2f, TBS %u bits/slot\n",
|
||||||
|
roundStats,
|
||||||
|
effRate,
|
||||||
|
effRate / TBS * 100,
|
||||||
|
TBS);
|
||||||
printf("*****************************************\n");
|
printf("*****************************************\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
// writing to csv file
|
// writing to csv file
|
||||||
@@ -1399,7 +1434,7 @@ int main(int argc, char **argv)
|
|||||||
fprintf(csv_file,"%f,%d/%d,",SNR,n_false_positive,n_trials);
|
fprintf(csv_file,"%f,%d/%d,",SNR,n_false_positive,n_trials);
|
||||||
for (int r = 0; r < num_rounds; r++)
|
for (int r = 0; r < num_rounds; r++)
|
||||||
fprintf(csv_file,"%d/%d,%u/%u,%f,%e,",n_errors[r], round_trials[r], errors_scrambling[r], available_bits * round_trials[r],blerStats[r],berStats[r]);
|
fprintf(csv_file,"%d/%d,%u/%u,%f,%e,",n_errors[r], round_trials[r], errors_scrambling[r], available_bits * round_trials[r],blerStats[r],berStats[r]);
|
||||||
fprintf(csv_file,"%.2f,%.4f,%.2f,%u\n", roundStats, effRate, effRate / (8 * pdsch_pdu_rel15->TBSize[0]) * 100, 8 * pdsch_pdu_rel15->TBSize[0]);
|
fprintf(csv_file, "%.2f,%.4f,%.2f,%u\n", roundStats, effRate, effRate / TBS * 100, TBS);
|
||||||
}
|
}
|
||||||
if (print_perf==1) {
|
if (print_perf==1) {
|
||||||
printf("\ngNB TX function statistics (per %d us slot, NPRB %d, mcs %d, C %d, block %d)\n",
|
printf("\ngNB TX function statistics (per %d us slot, NPRB %d, mcs %d, C %d, block %d)\n",
|
||||||
@@ -1407,7 +1442,7 @@ int main(int argc, char **argv)
|
|||||||
g_rbSize,
|
g_rbSize,
|
||||||
g_mcsIndex,
|
g_mcsIndex,
|
||||||
UE->dl_harq_processes[0][slot].C,
|
UE->dl_harq_processes[0][slot].C,
|
||||||
8 * pdsch_pdu_rel15->TBSize[0]);
|
TBS);
|
||||||
printDistribution(&gNB->phy_proc_tx,table_tx,"PHY proc tx");
|
printDistribution(&gNB->phy_proc_tx,table_tx,"PHY proc tx");
|
||||||
printStatIndent2(&gNB->dci_generation_stats, "DCI encoding time");
|
printStatIndent2(&gNB->dci_generation_stats, "DCI encoding time");
|
||||||
printStatIndent2(&gNB->dlsch_encoding_stats,"DLSCH encoding time");
|
printStatIndent2(&gNB->dlsch_encoding_stats,"DLSCH encoding time");
|
||||||
@@ -1490,10 +1525,11 @@ int main(int argc, char **argv)
|
|||||||
LOG_M("chestF0.m", "chF0", UE->phy_sim_pdsch_dl_ch_estimates, frame_parms->ofdm_symbol_size * 14, dec, op_format);
|
LOG_M("chestF0.m", "chF0", UE->phy_sim_pdsch_dl_ch_estimates, frame_parms->ofdm_symbol_size * 14, dec, op_format);
|
||||||
op_format = (op_format & (~1));
|
op_format = (op_format & (~1));
|
||||||
LOG_M("rxF_llr.m", "rxFllr", UE->phy_sim_pdsch_llr, available_bits, dec, op_format);
|
LOG_M("rxF_llr.m", "rxFllr", UE->phy_sim_pdsch_llr, available_bits, dec, op_format);
|
||||||
|
ret = n_errors[0] == 0 && n_false_positive == 0 ? 0 : 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (effRate >= (eff_tp_check * 8 * pdsch_pdu_rel15->TBSize[0])) {
|
if (effRate >= eff_tp_check * TBS) {
|
||||||
printf("PDSCH test OK\n");
|
printf("PDSCH test OK\n");
|
||||||
ret = 0;
|
ret = 0;
|
||||||
break;
|
break;
|
||||||
@@ -1530,8 +1566,6 @@ int main(int argc, char **argv)
|
|||||||
free(r_re);
|
free(r_re);
|
||||||
free(r_im);
|
free(r_im);
|
||||||
free(txdata);
|
free(txdata);
|
||||||
free(test_input_bit);
|
|
||||||
free(estimated_output_bit);
|
|
||||||
free(UE->phy_sim_rxdataF);
|
free(UE->phy_sim_rxdataF);
|
||||||
free(UE->phy_sim_pdsch_llr);
|
free(UE->phy_sim_pdsch_llr);
|
||||||
free(UE->phy_sim_pdsch_rxdataF_ext);
|
free(UE->phy_sim_pdsch_rxdataF_ext);
|
||||||
|
|||||||
@@ -2277,6 +2277,14 @@ void init_channelmod(void) {
|
|||||||
|
|
||||||
|
|
||||||
int load_channellist(uint8_t nb_tx, uint8_t nb_rx, double sampling_rate, uint64_t center_freq, double channel_bandwidth) {
|
int load_channellist(uint8_t nb_tx, uint8_t nb_rx, double sampling_rate, uint64_t center_freq, double channel_bandwidth) {
|
||||||
|
// nakaolab Fix 9: ensure RNG is initialized exactly once, before any
|
||||||
|
// random_channel call (TDL/EPA/Rayleigh need gaussZiggurat). Static guard
|
||||||
|
// prevents multiple init races on the global tableNor* arrays.
|
||||||
|
static bool nakaolab_rng_done = false;
|
||||||
|
if (!nakaolab_rng_done) {
|
||||||
|
randominit();
|
||||||
|
nakaolab_rng_done = true;
|
||||||
|
}
|
||||||
paramdef_t achannel_params[] = CHANNELMOD_MODEL_PARAMS_DESC;
|
paramdef_t achannel_params[] = CHANNELMOD_MODEL_PARAMS_DESC;
|
||||||
paramlist_def_t channel_list;
|
paramlist_def_t channel_list;
|
||||||
memset(&channel_list,0,sizeof(paramlist_def_t));
|
memset(&channel_list,0,sizeof(paramlist_def_t));
|
||||||
|
|||||||
@@ -3034,7 +3034,10 @@ uint16_t nr_dci_size(const NR_UE_DL_BWP_t *DL_BWP,
|
|||||||
// TB2
|
// TB2
|
||||||
long *maxCWperDCI = pdsch_Config ? pdsch_Config->maxNrofCodeWordsScheduledByDCI : NULL;
|
long *maxCWperDCI = pdsch_Config ? pdsch_Config->maxNrofCodeWordsScheduledByDCI : NULL;
|
||||||
if (maxCWperDCI && (*maxCWperDCI == NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2)) {
|
if (maxCWperDCI && (*maxCWperDCI == NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2)) {
|
||||||
size += 8;
|
dci_pdu->mcs2.nbits = 5;
|
||||||
|
dci_pdu->ndi2.nbits = 1;
|
||||||
|
dci_pdu->rv2.nbits = 2;
|
||||||
|
size += dci_pdu->mcs2.nbits + dci_pdu->ndi2.nbits + dci_pdu->rv2.nbits;
|
||||||
}
|
}
|
||||||
// HARQ process number – 5 bits if higher layer parameter harq-ProcessNumberSizeDCI-1-1 is configured;
|
// HARQ process number – 5 bits if higher layer parameter harq-ProcessNumberSizeDCI-1-1 is configured;
|
||||||
// otherwise 4 bits. Spec 38.212 Section 7.3.1.2.2
|
// otherwise 4 bits. Spec 38.212 Section 7.3.1.2.2
|
||||||
|
|||||||
@@ -207,13 +207,16 @@ nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPort
|
|||||||
int O2 = N2 > 1 ? 4 : 1; //Vertical beam oversampling (1 or 4)
|
int O2 = N2 > 1 ? 4 : 1; //Vertical beam oversampling (1 or 4)
|
||||||
int O1 = num_antenna_ports > 2 ? 4 : 1; //Horizontal beam oversampling (1 or 4)
|
int O1 = num_antenna_ports > 2 ? 4 : 1; //Horizontal beam oversampling (1 or 4)
|
||||||
|
|
||||||
int max_mimo_layers = (num_antenna_ports < NR_MAX_NB_LAYERS) ? num_antenna_ports : NR_MAX_NB_LAYERS;
|
const int max_mimo_layers = min(num_antenna_ports, NR_MAX_NB_LAYERS);
|
||||||
AssertFatal(max_mimo_layers <= 4, "Max number of layers supported is 4\n");
|
/* Type-I codebook generation below currently covers ranks 1--4. Rank
|
||||||
|
* 5--8 is an explicit identity-PMI path and therefore needs no generated
|
||||||
|
* codebook entries. */
|
||||||
|
const int codebook_layers = min(max_mimo_layers, 4);
|
||||||
AssertFatal(num_antenna_ports < 16, "Max number of antenna ports supported is currently 16\n");
|
AssertFatal(num_antenna_ports < 16, "Max number of antenna ports supported is currently 16\n");
|
||||||
|
|
||||||
int K1 = 0, K2 = 0;
|
int K1 = 0, K2 = 0;
|
||||||
nfapi_nr_pm_list_t mat = {.num_pm_idx = 0};
|
nfapi_nr_pm_list_t mat = {.num_pm_idx = 0};
|
||||||
for (int i = 0; i < max_mimo_layers; i++) {
|
for (int i = 0; i < codebook_layers; i++) {
|
||||||
get_K1_K2(N1, N2, &K1, &K2, i + 1);
|
get_K1_K2(N1, N2, &K1, &K2, i + 1);
|
||||||
int i2_size = i == 0 ? 4 : 2;
|
int i2_size = i == 0 ? 4 : 2;
|
||||||
gNB->precoding_matrix_size[i] = i2_size * N1 * O1 * N2 * O2 * K1 * K2;
|
gNB->precoding_matrix_size[i] = i2_size * N1 * O1 * N2 * O2 * K1 * K2;
|
||||||
@@ -274,7 +277,7 @@ nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPort
|
|||||||
}
|
}
|
||||||
|
|
||||||
int pmiq = 0;
|
int pmiq = 0;
|
||||||
for (int layers = 1; layers <= max_mimo_layers; layers++)
|
for (int layers = 1; layers <= codebook_layers; layers++)
|
||||||
pmiq = precoding_weigths_generation(&mat, pmiq, layers, N1, N2, O1, O2, num_antenna_ports, theta_n, v_lm);
|
pmiq = precoding_weigths_generation(&mat, pmiq, layers, N1, N2, O1, O2, num_antenna_ports, theta_n, v_lm);
|
||||||
|
|
||||||
return mat;
|
return mat;
|
||||||
|
|||||||
@@ -404,6 +404,37 @@ bwp_info_t get_pdsch_bwp_start_size(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE)
|
|||||||
return bwp_info;
|
return bwp_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32_t compute_sched_pdsch_tbs(const NR_sched_pdsch_t *sched, uint16_t rb_size, uint8_t codeword)
|
||||||
|
{
|
||||||
|
const int cw_layers = nr_pdsch_layers_per_codeword(sched->nrOfLayers, codeword);
|
||||||
|
return nr_compute_tbs(sched->Qm,
|
||||||
|
sched->R,
|
||||||
|
rb_size,
|
||||||
|
sched->tda_info.nrOfSymbols,
|
||||||
|
sched->dmrs_parms.N_PRB_DMRS * sched->dmrs_parms.N_DMRS_SLOT,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
cw_layers)
|
||||||
|
>> 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_sched_pdsch_codeword_tbs(NR_sched_pdsch_t *sched)
|
||||||
|
{
|
||||||
|
sched->tb_size = compute_sched_pdsch_tbs(sched, sched->rbSize, 0);
|
||||||
|
sched->tb_size2 = nr_pdsch_num_codewords(sched->nrOfLayers) == 2 ? compute_sched_pdsch_tbs(sched, sched->rbSize, 1) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fit_dual_codeword_tbs(NR_sched_pdsch_t *sched, uint32_t requested_bytes, uint16_t max_rb_size)
|
||||||
|
{
|
||||||
|
if (nr_pdsch_num_codewords(sched->nrOfLayers) == 1)
|
||||||
|
return;
|
||||||
|
update_sched_pdsch_codeword_tbs(sched);
|
||||||
|
while ((uint64_t)sched->tb_size + sched->tb_size2 < requested_bytes && sched->rbSize < max_rb_size) {
|
||||||
|
sched->rbSize++;
|
||||||
|
update_sched_pdsch_codeword_tbs(sched);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
|
static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
|
||||||
post_process_pdsch_t *pp_pdsch,
|
post_process_pdsch_t *pp_pdsch,
|
||||||
int *n_rb_sched,
|
int *n_rb_sched,
|
||||||
@@ -420,7 +451,7 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
|
|||||||
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
|
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
|
||||||
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
|
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
|
||||||
NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch;
|
NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch;
|
||||||
int layers = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
|
int layers = get_dl_nrOfLayers(nr_mac, sched_ctrl, dl_bwp->dci_format);
|
||||||
int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
|
int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
|
||||||
|
|
||||||
// If the RI changed between current rtx and a previous transmission
|
// If the RI changed between current rtx and a previous transmission
|
||||||
@@ -473,26 +504,43 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
|
|||||||
* that we have enough resources */
|
* that we have enough resources */
|
||||||
NR_pdsch_dmrs_t temp_dmrs = get_dl_dmrs_params(scc, dl_bwp, &temp_tda, layers);
|
NR_pdsch_dmrs_t temp_dmrs = get_dl_dmrs_params(scc, dl_bwp, &temp_tda, layers);
|
||||||
|
|
||||||
uint32_t new_tbs;
|
uint16_t new_rbSize = 0;
|
||||||
uint16_t new_rbSize;
|
if (nr_pdsch_num_codewords(layers) == 1) {
|
||||||
bool success = nr_find_nb_rb(new_sched.Qm,
|
uint32_t new_tbs = 0;
|
||||||
new_sched.R,
|
bool success = nr_find_nb_rb(new_sched.Qm,
|
||||||
1, // no transform precoding for DL
|
new_sched.R,
|
||||||
layers,
|
1, // no transform precoding for DL
|
||||||
temp_tda.nrOfSymbols,
|
layers,
|
||||||
temp_dmrs.N_PRB_DMRS * temp_dmrs.N_DMRS_SLOT,
|
temp_tda.nrOfSymbols,
|
||||||
new_sched.tb_size,
|
temp_dmrs.N_PRB_DMRS * temp_dmrs.N_DMRS_SLOT,
|
||||||
1, /* minimum of 1RB: need to find exact TBS, don't preclude any number */
|
new_sched.tb_size,
|
||||||
bwp_size,
|
1, /* find an exact retransmission TBS */
|
||||||
&new_tbs,
|
bwp_size,
|
||||||
&new_rbSize);
|
&new_tbs,
|
||||||
|
&new_rbSize);
|
||||||
|
if (!success || new_tbs != new_sched.tb_size)
|
||||||
|
new_rbSize = 0;
|
||||||
|
} else {
|
||||||
|
NR_sched_pdsch_t candidate = new_sched;
|
||||||
|
candidate.nrOfLayers = layers;
|
||||||
|
candidate.dmrs_parms = temp_dmrs;
|
||||||
|
candidate.tda_info = temp_tda;
|
||||||
|
for (uint16_t rb = 1; rb <= bwp_size; rb++) {
|
||||||
|
candidate.rbSize = rb;
|
||||||
|
update_sched_pdsch_codeword_tbs(&candidate);
|
||||||
|
if (candidate.tb_size == new_sched.tb_size && candidate.tb_size2 == new_sched.tb_size2) {
|
||||||
|
new_rbSize = rb;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!success || new_tbs != new_sched.tb_size) {
|
if (new_rbSize == 0) {
|
||||||
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] allocation of DL retransmission failed: new TBS %d of new TDA does not match old TBS %d\n",
|
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] allocation of DL retransmission failed: new TBS %d of new TDA does not match old TBS %d\n",
|
||||||
UE->rnti,
|
UE->rnti,
|
||||||
frame,
|
frame,
|
||||||
slot,
|
slot,
|
||||||
new_tbs,
|
0,
|
||||||
new_sched.tb_size);
|
new_sched.tb_size);
|
||||||
return false; /* the maximum TBsize we might have is smaller than what we need */
|
return false; /* the maximum TBsize we might have is smaller than what we need */
|
||||||
}
|
}
|
||||||
@@ -505,13 +553,13 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
|
|||||||
|
|
||||||
/* we can allocate it. Overwrite the time_domain_allocation, the number
|
/* we can allocate it. Overwrite the time_domain_allocation, the number
|
||||||
* of RBs, and the new TB size. The rest is done below */
|
* of RBs, and the new TB size. The rest is done below */
|
||||||
new_sched.tb_size = new_tbs;
|
|
||||||
new_sched.rbSize = new_rbSize;
|
new_sched.rbSize = new_rbSize;
|
||||||
new_sched.time_domain_allocation = tda;
|
new_sched.time_domain_allocation = tda;
|
||||||
new_sched.nrOfLayers = layers;
|
new_sched.nrOfLayers = layers;
|
||||||
new_sched.pm_index = pm_index;
|
new_sched.pm_index = pm_index;
|
||||||
new_sched.dmrs_parms = temp_dmrs;
|
new_sched.dmrs_parms = temp_dmrs;
|
||||||
new_sched.tda_info = temp_tda;
|
new_sched.tda_info = temp_tda;
|
||||||
|
update_sched_pdsch_codeword_tbs(&new_sched);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Find a free CCE */
|
/* Find a free CCE */
|
||||||
@@ -538,6 +586,11 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
|
|||||||
if (!get_FeedbackDisabled(UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, current_harq_pid)) {
|
if (!get_FeedbackDisabled(UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, current_harq_pid)) {
|
||||||
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
|
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
|
||||||
alloc = nr_acknack_scheduling(nr_mac, UE, frame, slot, UE->UE_beam_index, r_pucch, 0);
|
alloc = nr_acknack_scheduling(nr_mac, UE, frame, slot, UE->UE_beam_index, r_pucch, 0);
|
||||||
|
if (alloc >= 0 && nr_pdsch_num_codewords(new_sched.nrOfLayers) == 2) {
|
||||||
|
const int second_alloc = nr_acknack_scheduling(nr_mac, UE, frame, slot, UE->UE_beam_index, r_pucch, 0);
|
||||||
|
if (second_alloc != alloc)
|
||||||
|
alloc = -1;
|
||||||
|
}
|
||||||
if (alloc < 0) {
|
if (alloc < 0) {
|
||||||
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI retransmission\n", UE->rnti, frame, slot);
|
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI retransmission\n", UE->rnti, frame, slot);
|
||||||
return false;
|
return false;
|
||||||
@@ -565,7 +618,6 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
|
|||||||
static void ack_reconfig(gNB_MAC_INST *mac, NR_UE_info_t *UE)
|
static void ack_reconfig(gNB_MAC_INST *mac, NR_UE_info_t *UE)
|
||||||
{
|
{
|
||||||
if (!UE->reconfigCellGroup) {
|
if (!UE->reconfigCellGroup) {
|
||||||
LOG_W(NR_MAC, "Received ACK for RRCReconfiguration, but nothing to apply!\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup);
|
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup);
|
||||||
@@ -700,7 +752,7 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
sched_ctrl->dl_bler_stats.mcs = selected_mcs;
|
sched_ctrl->dl_bler_stats.mcs = selected_mcs;
|
||||||
} else
|
} else
|
||||||
selected_mcs = get_mcs_from_bler(bo, stats, &sched_ctrl->dl_bler_stats, max_mcs, frame);
|
selected_mcs = get_mcs_from_bler(bo, stats, &sched_ctrl->dl_bler_stats, max_mcs, frame);
|
||||||
int l = get_dl_nrOfLayers(sched_ctrl, current_BWP->dci_format);
|
int l = get_dl_nrOfLayers(mac, sched_ctrl, current_BWP->dci_format);
|
||||||
const uint8_t Qm = nr_get_Qm_dl(selected_mcs, current_BWP->mcsTableIdx);
|
const uint8_t Qm = nr_get_Qm_dl(selected_mcs, current_BWP->mcsTableIdx);
|
||||||
const uint16_t R = nr_get_code_rate_dl(selected_mcs, current_BWP->mcsTableIdx);
|
const uint16_t R = nr_get_code_rate_dl(selected_mcs, current_BWP->mcsTableIdx);
|
||||||
uint32_t tbs = nr_compute_tbs(Qm,
|
uint32_t tbs = nr_compute_tbs(Qm,
|
||||||
@@ -822,10 +874,17 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
/* Find PUCCH occasion: if it fails, undo CCE allocation (undoing PUCCH
|
/* Find PUCCH occasion: if it fails, undo CCE allocation (undoing PUCCH
|
||||||
* allocation after CCE alloc fail would be more complex) */
|
* allocation after CCE alloc fail would be more complex) */
|
||||||
|
|
||||||
|
const int l = get_dl_nrOfLayers(mac, sched_ctrl, dl_bwp->dci_format);
|
||||||
int alloc = -1;
|
int alloc = -1;
|
||||||
if (!get_FeedbackDisabled(iterator->UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, sched_ctrl->available_dl_harq.head)) {
|
if (!get_FeedbackDisabled(iterator->UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, sched_ctrl->available_dl_harq.head)) {
|
||||||
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
|
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
|
||||||
alloc = nr_acknack_scheduling(mac, iterator->UE, frame, slot, iterator->UE->UE_beam_index, r_pucch, 0);
|
alloc = nr_acknack_scheduling(mac, iterator->UE, frame, slot, iterator->UE->UE_beam_index, r_pucch, 0);
|
||||||
|
if (alloc >= 0 && nr_pdsch_num_codewords(l) == 2) {
|
||||||
|
const int second_alloc =
|
||||||
|
nr_acknack_scheduling(mac, iterator->UE, frame, slot, iterator->UE->UE_beam_index, r_pucch, 0);
|
||||||
|
if (second_alloc != alloc)
|
||||||
|
alloc = -1;
|
||||||
|
}
|
||||||
if (alloc < 0) {
|
if (alloc < 0) {
|
||||||
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI\n", rnti, frame, slot);
|
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI\n", rnti, frame, slot);
|
||||||
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
|
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
|
||||||
@@ -837,7 +896,6 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
sched_ctrl->cce_index = CCEIndex;
|
sched_ctrl->cce_index = CCEIndex;
|
||||||
fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx);
|
fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx);
|
||||||
|
|
||||||
int l = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
|
|
||||||
NR_sched_pdsch_t sched_pdsch = {
|
NR_sched_pdsch_t sched_pdsch = {
|
||||||
// rbSize below
|
// rbSize below
|
||||||
.rbStart = rbStart,
|
.rbStart = rbStart,
|
||||||
@@ -877,6 +935,7 @@ static void pf_dl(gNB_MAC_INST *mac,
|
|||||||
max_rbSize,
|
max_rbSize,
|
||||||
&sched_pdsch.tb_size,
|
&sched_pdsch.tb_size,
|
||||||
&sched_pdsch.rbSize);
|
&sched_pdsch.rbSize);
|
||||||
|
fit_dual_codeword_tbs(&sched_pdsch, sched_ctrl->num_total_bytes + oh, max_rbSize);
|
||||||
|
|
||||||
post_process_dlsch(mac, pp_pdsch, iterator->UE, &sched_pdsch);
|
post_process_dlsch(mac, pp_pdsch, iterator->UE, &sched_pdsch);
|
||||||
|
|
||||||
@@ -943,13 +1002,15 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
|
|||||||
pdsch_pdu->SubcarrierSpacing = dl_bwp ? dl_bwp->scs : *scc->ssbSubcarrierSpacing;
|
pdsch_pdu->SubcarrierSpacing = dl_bwp ? dl_bwp->scs : *scc->ssbSubcarrierSpacing;
|
||||||
pdsch_pdu->CyclicPrefix = dl_bwp && dl_bwp->cyclicprefix ? *dl_bwp->cyclicprefix : 0;
|
pdsch_pdu->CyclicPrefix = dl_bwp && dl_bwp->cyclicprefix ? *dl_bwp->cyclicprefix : 0;
|
||||||
// Codeword information
|
// Codeword information
|
||||||
pdsch_pdu->NrOfCodewords = 1;
|
pdsch_pdu->NrOfCodewords = nr_pdsch_num_codewords(sched_pdsch->nrOfLayers);
|
||||||
pdsch_pdu->targetCodeRate[0] = sched_pdsch->R;
|
for (int q = 0; q < pdsch_pdu->NrOfCodewords; q++) {
|
||||||
pdsch_pdu->qamModOrder[0] = sched_pdsch->Qm;
|
pdsch_pdu->targetCodeRate[q] = sched_pdsch->R;
|
||||||
pdsch_pdu->mcsIndex[0] = sched_pdsch->mcs;
|
pdsch_pdu->qamModOrder[q] = sched_pdsch->Qm;
|
||||||
pdsch_pdu->mcsTable[0] = dl_bwp ? dl_bwp->mcsTableIdx : 0;
|
pdsch_pdu->mcsIndex[q] = sched_pdsch->mcs;
|
||||||
pdsch_pdu->rvIndex[0] = nr_get_rv(harq_round % 4);
|
pdsch_pdu->mcsTable[q] = dl_bwp ? dl_bwp->mcsTableIdx : 0;
|
||||||
pdsch_pdu->TBSize[0] = sched_pdsch->tb_size;
|
pdsch_pdu->rvIndex[q] = nr_get_rv(harq_round % 4);
|
||||||
|
pdsch_pdu->TBSize[q] = q == 0 ? sched_pdsch->tb_size : sched_pdsch->tb_size2;
|
||||||
|
}
|
||||||
pdsch_pdu->dataScramblingId = pdsch_Config && pdsch_Config->dataScramblingIdentityPDSCH ? *pdsch_Config->dataScramblingIdentityPDSCH : *scc->physCellId;
|
pdsch_pdu->dataScramblingId = pdsch_Config && pdsch_Config->dataScramblingIdentityPDSCH ? *pdsch_Config->dataScramblingIdentityPDSCH : *scc->physCellId;
|
||||||
pdsch_pdu->nrOfLayers = sched_pdsch->nrOfLayers;
|
pdsch_pdu->nrOfLayers = sched_pdsch->nrOfLayers;
|
||||||
pdsch_pdu->transmissionScheme = 0;
|
pdsch_pdu->transmissionScheme = 0;
|
||||||
@@ -961,7 +1022,7 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
|
|||||||
pdsch_pdu->SCID = dmrs_parms->n_scid;
|
pdsch_pdu->SCID = dmrs_parms->n_scid;
|
||||||
pdsch_pdu->dlDmrsScramblingId = dmrs_parms->scrambling_id;
|
pdsch_pdu->dlDmrsScramblingId = dmrs_parms->scrambling_id;
|
||||||
pdsch_pdu->numDmrsCdmGrpsNoData = dmrs_parms->numDmrsCdmGrpsNoData;
|
pdsch_pdu->numDmrsCdmGrpsNoData = dmrs_parms->numDmrsCdmGrpsNoData;
|
||||||
pdsch_pdu->dmrsPorts = (1 << sched_pdsch->nrOfLayers) - 1; // FIXME with a better implementation
|
pdsch_pdu->dmrsPorts = dmrs_parms->dmrs_ports;
|
||||||
// Pdsch Allocation in frequency domain
|
// Pdsch Allocation in frequency domain
|
||||||
pdsch_pdu->resourceAlloc = 1;
|
pdsch_pdu->resourceAlloc = 1;
|
||||||
pdsch_pdu->rbStart = sched_pdsch->rbStart;
|
pdsch_pdu->rbStart = sched_pdsch->rbStart;
|
||||||
@@ -998,6 +1059,98 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
|
|||||||
return pdsch_pdu;
|
return pdsch_pdu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int sdus;
|
||||||
|
int sdu_bytes;
|
||||||
|
} dlsch_fill_result_t;
|
||||||
|
|
||||||
|
static dlsch_fill_result_t fill_dlsch_transport_block(gNB_MAC_INST *mac,
|
||||||
|
NR_UE_info_t *UE,
|
||||||
|
uint8_t *tb,
|
||||||
|
uint32_t tbs,
|
||||||
|
frame_t frame,
|
||||||
|
slot_t slot,
|
||||||
|
bool include_ces)
|
||||||
|
{
|
||||||
|
const int module_id = mac->Mod_id;
|
||||||
|
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
|
||||||
|
dlsch_fill_result_t result = {0};
|
||||||
|
uint8_t *buf = tb;
|
||||||
|
|
||||||
|
if (include_ces)
|
||||||
|
buf += nr_write_ce_dlsch_pdu(module_id, sched_ctrl, buf, 255, NULL);
|
||||||
|
DevAssert(buf < tb + tbs);
|
||||||
|
uint8_t *buf_end = tb + tbs;
|
||||||
|
|
||||||
|
start_meas(&mac->rlc_data_req);
|
||||||
|
if (sched_ctrl->num_total_bytes > 0) {
|
||||||
|
for (int i = 0; i < seq_arr_size(&sched_ctrl->lc_config); ++i) {
|
||||||
|
const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, i);
|
||||||
|
const int lcid = c->lcid;
|
||||||
|
if (sched_ctrl->rlc_status[lcid].bytes_in_buffer == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tb_size_t pdu_siz[MAX_NUM_DATA_REQ];
|
||||||
|
int num = nr_mac_rlc_multi_data_req(module_id,
|
||||||
|
UE->rnti,
|
||||||
|
true,
|
||||||
|
lcid,
|
||||||
|
buf_end - buf,
|
||||||
|
(char *)buf,
|
||||||
|
pdu_siz,
|
||||||
|
sizeofArray(pdu_siz));
|
||||||
|
DevAssert(num <= sizeofArray(pdu_siz));
|
||||||
|
result.sdus += num;
|
||||||
|
int lcid_bytes = 0;
|
||||||
|
for (int j = 0; j < num; ++j) {
|
||||||
|
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *)buf;
|
||||||
|
header->R = 0;
|
||||||
|
header->F = 1;
|
||||||
|
header->LCID = lcid;
|
||||||
|
header->L = htons(pdu_siz[j]);
|
||||||
|
buf += pdu_siz[j] + sizeof(NR_MAC_SUBHEADER_LONG);
|
||||||
|
result.sdu_bytes += pdu_siz[j];
|
||||||
|
lcid_bytes += pdu_siz[j];
|
||||||
|
}
|
||||||
|
T(T_GNB_MAC_LCID_DL,
|
||||||
|
T_INT(UE->rnti),
|
||||||
|
T_INT(frame),
|
||||||
|
T_INT(slot),
|
||||||
|
T_INT(lcid),
|
||||||
|
T_INT(lcid_bytes),
|
||||||
|
T_INT(nr_rlc_tx_list_occupancy(UE->rnti, lcid)));
|
||||||
|
UE->mac_stats.dl.lc_bytes[lcid] += lcid_bytes;
|
||||||
|
}
|
||||||
|
} else if (get_softmodem_params()->phy_test || get_softmodem_params()->do_ra) {
|
||||||
|
LOG_D(NR_MAC, "Configuring DL_TX in %d.%d: TBS %d of random data\n", frame, slot, tbs);
|
||||||
|
if (buf_end - buf > sizeof(NR_MAC_SUBHEADER_LONG)) {
|
||||||
|
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *)buf;
|
||||||
|
header->R = 0;
|
||||||
|
header->F = 1;
|
||||||
|
header->LCID = DL_SCH_LCID_PADDING;
|
||||||
|
buf += sizeof(NR_MAC_SUBHEADER_LONG);
|
||||||
|
header->L = htons(buf_end - buf);
|
||||||
|
for (; ((intptr_t)buf) % 4; buf++)
|
||||||
|
*buf = lrand48() & 0xff;
|
||||||
|
for (; buf < buf_end - 3; buf += 4)
|
||||||
|
*(uint32_t *)buf = lrand48();
|
||||||
|
for (; buf < buf_end; buf++)
|
||||||
|
*buf = lrand48() & 0xff;
|
||||||
|
result.sdus++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stop_meas(&mac->rlc_data_req);
|
||||||
|
|
||||||
|
if (buf_end - buf > 0) {
|
||||||
|
NR_MAC_SUBHEADER_FIXED *padding = (NR_MAC_SUBHEADER_FIXED *)buf;
|
||||||
|
padding->R = 0;
|
||||||
|
padding->LCID = DL_SCH_LCID_PADDING;
|
||||||
|
buf++;
|
||||||
|
memset(buf, 0, buf_end - buf);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE_info_t *UE, NR_sched_pdsch_t *sched_pdsch)
|
void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE_info_t *UE, NR_sched_pdsch_t *sched_pdsch)
|
||||||
{
|
{
|
||||||
int CC_id = 0;
|
int CC_id = 0;
|
||||||
@@ -1014,6 +1167,7 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
|
|||||||
/* POST processing */
|
/* POST processing */
|
||||||
const uint8_t nrOfLayers = sched_pdsch->nrOfLayers;
|
const uint8_t nrOfLayers = sched_pdsch->nrOfLayers;
|
||||||
const uint32_t TBS = sched_pdsch->tb_size;
|
const uint32_t TBS = sched_pdsch->tb_size;
|
||||||
|
const uint32_t TBS2 = sched_pdsch->tb_size2;
|
||||||
int8_t current_harq_pid = sched_pdsch->dl_harq_pid;
|
int8_t current_harq_pid = sched_pdsch->dl_harq_pid;
|
||||||
|
|
||||||
if (current_harq_pid < 0) {
|
if (current_harq_pid < 0) {
|
||||||
@@ -1208,11 +1362,15 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
|
|||||||
harq->round,
|
harq->round,
|
||||||
harq->ndi);
|
harq->ndi);
|
||||||
if (!get_softmodem_params()->phy_test)
|
if (!get_softmodem_params()->phy_test)
|
||||||
AssertFatal(harq->sched_pdsch.tb_size == TBS,
|
AssertFatal(harq->sched_pdsch.tb_size == TBS && harq->sched_pdsch.tb_size2 == TBS2,
|
||||||
"UE %04x mismatch between scheduled TBS and buffered TB for HARQ PID %d\n",
|
"UE %04x mismatch between scheduled TBS (%u,%u) and buffered TB (%u,%u) for HARQ PID %d\n",
|
||||||
UE->rnti,
|
UE->rnti,
|
||||||
|
TBS,
|
||||||
|
TBS2,
|
||||||
|
harq->sched_pdsch.tb_size,
|
||||||
|
harq->sched_pdsch.tb_size2,
|
||||||
current_harq_pid);
|
current_harq_pid);
|
||||||
else if (harq->sched_pdsch.tb_size != TBS)
|
else if (harq->sched_pdsch.tb_size != TBS || harq->sched_pdsch.tb_size2 != TBS2)
|
||||||
LOG_E(NR_MAC,
|
LOG_E(NR_MAC,
|
||||||
"Mismatch between scheduled TBS and buffered TB for HARQ PID %d. No RTX control in phy-test mode. "
|
"Mismatch between scheduled TBS and buffered TB for HARQ PID %d. No RTX control in phy-test mode. "
|
||||||
"Possible causes: presence of CSI-RS or DLSCH scheduled in the mixed slot.\n",
|
"Possible causes: presence of CSI-RS or DLSCH scheduled in the mixed slot.\n",
|
||||||
@@ -1225,94 +1383,24 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
|
|||||||
LOG_D(NR_MAC, "Initial HARQ transmission in %d.%d\n", frame, slot);
|
LOG_D(NR_MAC, "Initial HARQ transmission in %d.%d\n", frame, slot);
|
||||||
// Flag HARQ process to start TCI timer at ACK
|
// Flag HARQ process to start TCI timer at ACK
|
||||||
harq->start_tci_timer = sched_ctrl->UE_mac_ce_ctrl.tci_state_ind.is_scheduled;
|
harq->start_tci_timer = sched_ctrl->UE_mac_ce_ctrl.tci_state_ind.is_scheduled;
|
||||||
uint8_t *buf = allocate_transportBlock_buffer(&harq->transportBlock, TBS);
|
|
||||||
/* first, write all CEs that might be there */
|
|
||||||
int written = nr_write_ce_dlsch_pdu(module_id,
|
|
||||||
sched_ctrl,
|
|
||||||
(unsigned char *)buf,
|
|
||||||
255, // no drx
|
|
||||||
NULL); // contention res id
|
|
||||||
buf += written;
|
|
||||||
uint8_t *bufEnd = buf + TBS - written;
|
|
||||||
DevAssert(TBS > written);
|
|
||||||
int dlsch_total_bytes = 0;
|
int dlsch_total_bytes = 0;
|
||||||
/* next, get RLC data */
|
|
||||||
start_meas(&nr_mac->rlc_data_req);
|
|
||||||
int sdus = 0;
|
int sdus = 0;
|
||||||
|
for (int q = 0; q < nr_pdsch_num_codewords(nrOfLayers); q++) {
|
||||||
if (sched_ctrl->num_total_bytes > 0) {
|
const uint32_t cw_tbs = q == 0 ? TBS : sched_pdsch->tb_size2;
|
||||||
/* loop over all activated logical channels */
|
byte_array_t *transport_block = q == 0 ? &harq->transportBlock : &harq->transportBlock2;
|
||||||
for (int i = 0; i < seq_arr_size(&sched_ctrl->lc_config); ++i) {
|
uint8_t *buf = allocate_transportBlock_buffer(transport_block, cw_tbs);
|
||||||
const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, i);
|
dlsch_fill_result_t fill = fill_dlsch_transport_block(nr_mac, UE, buf, cw_tbs, frame, slot, q == 0);
|
||||||
const int lcid = c->lcid;
|
sdus += fill.sdus;
|
||||||
|
dlsch_total_bytes += fill.sdu_bytes;
|
||||||
if (sched_ctrl->rlc_status[lcid].bytes_in_buffer == 0)
|
|
||||||
continue; // no data for this LC tbs_size_t len = 0;
|
|
||||||
|
|
||||||
|
|
||||||
tb_size_t pdu_siz[MAX_NUM_DATA_REQ];
|
|
||||||
int num = nr_mac_rlc_multi_data_req(module_id, rnti, true, lcid, bufEnd - buf, (char *)buf, pdu_siz, sizeofArray(pdu_siz));
|
|
||||||
DevAssert(num <= sizeofArray(pdu_siz));
|
|
||||||
|
|
||||||
sdus += num;
|
|
||||||
int lcid_bytes = 0;
|
|
||||||
for (int j = 0; j < num; ++j) {
|
|
||||||
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *) buf;
|
|
||||||
header->R = 0;
|
|
||||||
header->F = 1;
|
|
||||||
header->LCID = lcid;
|
|
||||||
header->L = htons(pdu_siz[j]);
|
|
||||||
buf += pdu_siz[j] + sizeof(NR_MAC_SUBHEADER_LONG);
|
|
||||||
dlsch_total_bytes += pdu_siz[j];
|
|
||||||
lcid_bytes += pdu_siz[j];
|
|
||||||
}
|
|
||||||
|
|
||||||
T(T_GNB_MAC_LCID_DL, T_INT(rnti), T_INT(frame), T_INT(slot), T_INT(lcid), T_INT(lcid_bytes), T_INT(nr_rlc_tx_list_occupancy(rnti, lcid)));
|
|
||||||
UE->mac_stats.dl.lc_bytes[lcid] += lcid_bytes;
|
|
||||||
}
|
|
||||||
} else if (get_softmodem_params()->phy_test || get_softmodem_params()->do_ra) {
|
|
||||||
/* we will need the large header, phy-test typically allocates all
|
|
||||||
* resources and fills to the last byte below */
|
|
||||||
LOG_D(NR_MAC, "Configuring DL_TX in %d.%d: TBS %d of random data\n", frame, slot, TBS);
|
|
||||||
|
|
||||||
if (bufEnd-buf > sizeof(NR_MAC_SUBHEADER_LONG) ) {
|
|
||||||
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *) buf;
|
|
||||||
// fill dlsch_buffer with random data
|
|
||||||
header->R = 0;
|
|
||||||
header->F = 1;
|
|
||||||
header->LCID = DL_SCH_LCID_PADDING;
|
|
||||||
buf += sizeof(NR_MAC_SUBHEADER_LONG);
|
|
||||||
header->L = htons(bufEnd-buf);
|
|
||||||
|
|
||||||
for (; ((intptr_t)buf) % 4; buf++)
|
|
||||||
*buf = lrand48() & 0xff;
|
|
||||||
for (; buf < bufEnd - 3; buf += 4) {
|
|
||||||
uint32_t *buf32 = (uint32_t *)buf;
|
|
||||||
*buf32 = lrand48();
|
|
||||||
}
|
|
||||||
for (; buf < bufEnd; buf++)
|
|
||||||
*buf = lrand48() & 0xff;
|
|
||||||
sdus +=1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stop_meas(&nr_mac->rlc_data_req);
|
const uint32_t total_tbs = TBS + sched_pdsch->tb_size2;
|
||||||
|
UE->mac_stats.dl.total_bytes += total_tbs;
|
||||||
// Add padding header and zero rest out if there is space left
|
UE->mac_stats.dl.current_bytes = total_tbs;
|
||||||
if (bufEnd-buf > 0) {
|
|
||||||
NR_MAC_SUBHEADER_FIXED *padding = (NR_MAC_SUBHEADER_FIXED *) buf;
|
|
||||||
padding->R = 0;
|
|
||||||
padding->LCID = DL_SCH_LCID_PADDING;
|
|
||||||
buf += 1;
|
|
||||||
memset(buf,0,bufEnd-buf);
|
|
||||||
buf=bufEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
UE->mac_stats.dl.total_bytes += TBS;
|
|
||||||
UE->mac_stats.dl.current_bytes = TBS;
|
|
||||||
UE->mac_stats.dl.total_rbs += sched_pdsch->rbSize;
|
UE->mac_stats.dl.total_rbs += sched_pdsch->rbSize;
|
||||||
UE->mac_stats.dl.num_mac_sdu += sdus;
|
UE->mac_stats.dl.num_mac_sdu += sdus;
|
||||||
UE->mac_stats.dl.current_rbs = sched_pdsch->rbSize;
|
UE->mac_stats.dl.current_rbs = sched_pdsch->rbSize;
|
||||||
|
UE->mac_stats.dl.last_layers = sched_pdsch->nrOfLayers;
|
||||||
UE->mac_stats.dl.total_sdu_bytes += dlsch_total_bytes;
|
UE->mac_stats.dl.total_sdu_bytes += dlsch_total_bytes;
|
||||||
nr_mac->mac_stats.dl.used_prb_aggregate += sched_pdsch->rbSize;
|
nr_mac->mac_stats.dl.used_prb_aggregate += sched_pdsch->rbSize;
|
||||||
|
|
||||||
@@ -1349,10 +1437,16 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
|
|||||||
const int ntx_req = pdsch->TX_req->Number_of_PDUs;
|
const int ntx_req = pdsch->TX_req->Number_of_PDUs;
|
||||||
nfapi_nr_pdu_t *tx_req = &pdsch->TX_req->pdu_list[ntx_req];
|
nfapi_nr_pdu_t *tx_req = &pdsch->TX_req->pdu_list[ntx_req];
|
||||||
tx_req->PDU_index = pduindex;
|
tx_req->PDU_index = pduindex;
|
||||||
tx_req->num_TLV = 1;
|
tx_req->num_TLV = nr_pdsch_num_codewords(nrOfLayers);
|
||||||
tx_req->TLVs[0].length = TBS;
|
uint32_t total_tlv_length = 0;
|
||||||
tx_req->PDU_length = compute_PDU_length(tx_req->num_TLV, tx_req->TLVs[0].length);
|
for (int q = 0; q < tx_req->num_TLV; q++) {
|
||||||
memcpy(tx_req->TLVs[0].value.direct, harq->transportBlock.buf, TBS);
|
const uint32_t cw_tbs = q == 0 ? TBS : TBS2;
|
||||||
|
const byte_array_t *transport_block = q == 0 ? &harq->transportBlock : &harq->transportBlock2;
|
||||||
|
tx_req->TLVs[q].length = cw_tbs;
|
||||||
|
memcpy(tx_req->TLVs[q].value.direct, transport_block->buf, cw_tbs);
|
||||||
|
total_tlv_length += cw_tbs;
|
||||||
|
}
|
||||||
|
tx_req->PDU_length = compute_PDU_length(tx_req->num_TLV, total_tlv_length);
|
||||||
pdsch->TX_req->Number_of_PDUs++;
|
pdsch->TX_req->Number_of_PDUs++;
|
||||||
pdsch->TX_req->SFN = frame;
|
pdsch->TX_req->SFN = frame;
|
||||||
pdsch->TX_req->Slot = slot;
|
pdsch->TX_req->Slot = slot;
|
||||||
|
|||||||
@@ -166,15 +166,7 @@ void nr_preprocessor_phytest(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch)
|
|||||||
.tda_info = tda_info,
|
.tda_info = tda_info,
|
||||||
};
|
};
|
||||||
sched_ctrl->dl_bler_stats.mcs = target_dl_mcs; /* for logging output */
|
sched_ctrl->dl_bler_stats.mcs = target_dl_mcs; /* for logging output */
|
||||||
sched_pdsch.tb_size = nr_compute_tbs(sched_pdsch.Qm,
|
update_sched_pdsch_codeword_tbs(&sched_pdsch);
|
||||||
sched_pdsch.R,
|
|
||||||
sched_pdsch.rbSize,
|
|
||||||
tda_info.nrOfSymbols,
|
|
||||||
sched_pdsch.dmrs_parms.N_PRB_DMRS * sched_pdsch.dmrs_parms.N_DMRS_SLOT,
|
|
||||||
0 /* N_PRB_oh, 0 for initialBWP */,
|
|
||||||
0 /* tb_scaling */,
|
|
||||||
target_dl_Nl)
|
|
||||||
>> 3;
|
|
||||||
|
|
||||||
post_process_dlsch(mac, pp_pdsch, UE, &sched_pdsch);
|
post_process_dlsch(mac, pp_pdsch, UE, &sched_pdsch);
|
||||||
|
|
||||||
|
|||||||
@@ -101,14 +101,13 @@ int get_ssbidx_from_beam(gNB_MAC_INST *mac, int beam_idx)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
|
uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
|
||||||
{
|
{
|
||||||
// TODO check this but it should be enough for now
|
int layers = (dci_format == NR_DL_DCI_FORMAT_1_0) ? 1
|
||||||
// if there is not csi report activated RI is 0 from initialization
|
: sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
|
||||||
if(dci_format == NR_DL_DCI_FORMAT_1_0)
|
if (mac && mac->antenna_state)
|
||||||
return 1;
|
return nr_get_effective_mimo_layers(mac->antenna_state, layers);
|
||||||
else
|
return layers;
|
||||||
return sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
|
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
|
||||||
@@ -165,63 +164,88 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
|
|||||||
int layers,
|
int layers,
|
||||||
int xp_pdsch_antenna_ports)
|
int xp_pdsch_antenna_ports)
|
||||||
{
|
{
|
||||||
if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1)
|
if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1 || layers > 4)
|
||||||
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
|
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
|
||||||
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
|
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
|
||||||
const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id;
|
|
||||||
const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
|
|
||||||
const int N1 = csi_report->N1;
|
|
||||||
const int N2 = csi_report->N2;
|
|
||||||
const int antenna_ports = (N1 * N2) << 1;
|
|
||||||
if (antenna_ports < 2)
|
|
||||||
return 0; // single antenna port
|
|
||||||
|
|
||||||
int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
|
int reported_ri_layers = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
|
||||||
const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
|
if (layers != reported_ri_layers) {
|
||||||
LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers);
|
LOG_D(NR_MAC, "PMI mismatch: using %d layers but RI report is for %d layers, using identity matrix\n",
|
||||||
|
layers, reported_ri_layers);
|
||||||
int prev_layers_size = 0;
|
return 0;
|
||||||
for (int i = 1; i < layers; i++)
|
|
||||||
prev_layers_size += nrmac->precoding_matrix_size[i - 1];
|
|
||||||
|
|
||||||
// need to return PM index to matrix initialized in init_DL_MIMO_codebook
|
|
||||||
// index 0 is for identity matrix
|
|
||||||
// order of matrices depends on layers to be transmitted
|
|
||||||
// elements from 1 to n for 1 layer
|
|
||||||
// elements from n+1 to m for 2 layers etc.
|
|
||||||
if (antenna_ports == 2)
|
|
||||||
return 1 + prev_layers_size + x2; // 0 for identity matrix
|
|
||||||
else {
|
|
||||||
int idx = layers - 1;
|
|
||||||
// the order of i1x in X1 report needs to be verified
|
|
||||||
// the standard is not very clear (Table 6.3.1.1.2-7 in 38.212)
|
|
||||||
// it says: PMI wideband information fields X1 , from left to right
|
|
||||||
int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx];
|
|
||||||
if (layers == 1 && bitlen != 0) {
|
|
||||||
LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
const int i13 = x1 & ((1 << bitlen) - 1);
|
nr_antenna_state_t* ant_state = nrmac->antenna_state;
|
||||||
x1 >>= bitlen;
|
if (ant_state) {
|
||||||
bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx];
|
if (nr_is_in_recovery_mode(ant_state)) {
|
||||||
const int i12 = x1 & ((1 << bitlen) - 1);
|
LOG_D(NR_MAC, "[ANTENNA_MUTE] Recovery mode active, using identity matrix\n");
|
||||||
x1 >>= bitlen;
|
return 0;
|
||||||
bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
|
}
|
||||||
const int i11 = x1 & ((1 << bitlen) - 1);
|
// Don't fall back to identity matrix for muted antennas.
|
||||||
const int i2 = x2;
|
// Antenna muting is handled at RU level (nr_feptx zeros muted antenna buffers).
|
||||||
int k1, k2;
|
// Using proper precoding ensures data reaches all active antennas.
|
||||||
get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values)
|
}
|
||||||
const int O2 = N2 == 1 ? 1 : 4;
|
|
||||||
int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports
|
const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id;
|
||||||
int max_i2 = 0;
|
const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
|
||||||
int lay_index = 0;
|
const int N1 = csi_report->N1;
|
||||||
max_i2 = layers == 1 ? 4 : 2;
|
const int N2 = csi_report->N2;
|
||||||
int K1, K2;
|
const int antenna_ports = (N1 * N2) << 1;
|
||||||
get_K1_K2(N1, N2, &K1, &K2, layers);
|
if (antenna_ports < 2) return 0; // single antenna port
|
||||||
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB
|
|
||||||
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
|
int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
|
||||||
return 1 + prev_layers_size + lay_index;
|
const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
|
||||||
}
|
LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers);
|
||||||
|
|
||||||
|
int prev_layers_size = 0;
|
||||||
|
for (int i = 1; i < layers; i++)
|
||||||
|
prev_layers_size += nrmac->precoding_matrix_size[i - 1];
|
||||||
|
|
||||||
|
// need to return PM index to matrix initialized in init_DL_MIMO_codebook
|
||||||
|
// index 0 is for identity matrix
|
||||||
|
// order of matrices depends on layers to be transmitted
|
||||||
|
// elements from 1 to n for 1 layer
|
||||||
|
// elements from n+1 to m for 2 layers etc.
|
||||||
|
int pm_index;
|
||||||
|
if (antenna_ports == 2)
|
||||||
|
pm_index = 1 + prev_layers_size + x2; // 0 for identity matrix
|
||||||
|
else {
|
||||||
|
int idx = layers - 1;
|
||||||
|
// the order of i1x in X1 report needs to be verified
|
||||||
|
// the standard is not very clear (Table 6.3.1.1.2-7 in 38.212)
|
||||||
|
// it says: PMI wideband information fields X1 , from left to right
|
||||||
|
int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx];
|
||||||
|
if (layers == 1 && bitlen != 0) {
|
||||||
|
LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const int i13 = x1 & ((1 << bitlen) - 1);
|
||||||
|
x1 >>= bitlen;
|
||||||
|
bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx];
|
||||||
|
const int i12 = x1 & ((1 << bitlen) - 1);
|
||||||
|
x1 >>= bitlen;
|
||||||
|
bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
|
||||||
|
const int i11 = x1 & ((1 << bitlen) - 1);
|
||||||
|
const int i2 = x2;
|
||||||
|
int k1, k2;
|
||||||
|
get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values)
|
||||||
|
const int O2 = N2 == 1 ? 1 : 4;
|
||||||
|
int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports
|
||||||
|
int max_i2 = 0;
|
||||||
|
int lay_index = 0;
|
||||||
|
max_i2 = layers == 1 ? 4 : 2;
|
||||||
|
int K1, K2;
|
||||||
|
get_K1_K2(N1, N2, &K1, &K2, layers);
|
||||||
|
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB
|
||||||
|
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
|
||||||
|
pm_index = 1 + prev_layers_size + lay_index;
|
||||||
|
}
|
||||||
|
int max_valid_pm = prev_layers_size + nrmac->precoding_matrix_size[layers - 1];
|
||||||
|
if (pm_index < 1 || pm_index > max_valid_pm) {
|
||||||
|
LOG_W(NR_MAC, "PM index %d out of range [1, %d] for %d layers, using identity matrix\n",
|
||||||
|
pm_index, max_valid_pm, layers);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return pm_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// look-up table for AMC. Based on BLER vs SNR curves from the nr_dlsim simulation
|
// look-up table for AMC. Based on BLER vs SNR curves from the nr_dlsim simulation
|
||||||
@@ -304,6 +328,11 @@ NR_pdsch_dmrs_t get_dl_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
|
|||||||
{
|
{
|
||||||
NR_pdsch_dmrs_t dmrs = {0};
|
NR_pdsch_dmrs_t dmrs = {0};
|
||||||
int frontloaded_symb = 1; // default value
|
int frontloaded_symb = 1; // default value
|
||||||
|
NR_PDSCH_Config_t *pdsch_Config = dl_bwp ? dl_bwp->pdsch_Config : NULL;
|
||||||
|
NR_DMRS_DownlinkConfig_t *dmrs_Config = NULL;
|
||||||
|
if (pdsch_Config)
|
||||||
|
dmrs_Config = tda_info->mapping_type == typeB ? pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup
|
||||||
|
: pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
|
||||||
nr_dci_format_t dci_format = dl_bwp ? dl_bwp->dci_format : NR_DL_DCI_FORMAT_1_0;
|
nr_dci_format_t dci_format = dl_bwp ? dl_bwp->dci_format : NR_DL_DCI_FORMAT_1_0;
|
||||||
if (dci_format == NR_DL_DCI_FORMAT_1_0) {
|
if (dci_format == NR_DL_DCI_FORMAT_1_0) {
|
||||||
dmrs.numDmrsCdmGrpsNoData = tda_info->nrOfSymbols <= 2 ? 1 : 2;
|
dmrs.numDmrsCdmGrpsNoData = tda_info->nrOfSymbols <= 2 ? 1 : 2;
|
||||||
@@ -338,19 +367,29 @@ NR_pdsch_dmrs_t get_dl_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
|
|||||||
dmrs.numDmrsCdmGrpsNoData = 2;
|
dmrs.numDmrsCdmGrpsNoData = 2;
|
||||||
frontloaded_symb = 1;
|
frontloaded_symb = 1;
|
||||||
break;
|
break;
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
AssertFatal(dmrs_Config && dmrs_Config->dmrs_Type == NULL && dmrs_Config->maxLength,
|
||||||
|
"Rank %d PDSCH requires DMRS type 1 with maxLength len2\n",
|
||||||
|
Layers);
|
||||||
|
/* TS 38.212 Table 7.3.1.2.2-2, two-codeword rows 0--3. */
|
||||||
|
dmrs.dmrs_ports_id = Layers - 5;
|
||||||
|
dmrs.numDmrsCdmGrpsNoData = 2;
|
||||||
|
frontloaded_symb = 2;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
AssertFatal(1==0,"Number of layers %d\n not supported or not valid\n",Layers);
|
AssertFatal(1==0,"Number of layers %d\n not supported or not valid\n",Layers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dmrs.n_scid = 0;
|
dmrs.n_scid = 0;
|
||||||
|
static const uint16_t two_cw_dmrs_ports[] = {0x1f, 0x5f, 0x7f, 0xff};
|
||||||
|
dmrs.dmrs_ports = Layers > 4 ? two_cw_dmrs_ports[Layers - 5] : (1U << Layers) - 1;
|
||||||
long *scramblingID = NULL;
|
long *scramblingID = NULL;
|
||||||
NR_PDSCH_Config_t *pdsch_Config = dl_bwp ? dl_bwp->pdsch_Config : NULL;
|
|
||||||
if (pdsch_Config) {
|
if (pdsch_Config) {
|
||||||
NR_DMRS_DownlinkConfig_t *dmrs_Config = tda_info->mapping_type == typeB ?
|
dmrs.phaseTrackingRS = Layers > 4 ? NULL : (dmrs_Config->phaseTrackingRS ? dmrs_Config->phaseTrackingRS->choice.setup : NULL);
|
||||||
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup :
|
|
||||||
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
|
|
||||||
dmrs.phaseTrackingRS = dmrs_Config->phaseTrackingRS ? dmrs_Config->phaseTrackingRS->choice.setup : NULL;
|
|
||||||
dmrs.dmrsConfigType = dmrs_Config->dmrs_Type != NULL;
|
dmrs.dmrsConfigType = dmrs_Config->dmrs_Type != NULL;
|
||||||
scramblingID = dmrs.n_scid ? dmrs_Config->scramblingID1 : dmrs_Config->scramblingID0;
|
scramblingID = dmrs.n_scid ? dmrs_Config->scramblingID1 : dmrs_Config->scramblingID0;
|
||||||
} else {
|
} else {
|
||||||
@@ -930,6 +969,11 @@ dci_pdu_rel15_t prepare_dci_dl_payload(const gNB_MAC_INST *gNB_mac,
|
|||||||
AssertFatal(harq, "HARQ process should be available for DCI with RNTI %s\n", rnti_types(rnti_type));
|
AssertFatal(harq, "HARQ process should be available for DCI with RNTI %s\n", rnti_types(rnti_type));
|
||||||
dci_payload.harq_pid.val = harq_pid;
|
dci_payload.harq_pid.val = harq_pid;
|
||||||
dci_payload.ndi = harq->ndi;
|
dci_payload.ndi = harq->ndi;
|
||||||
|
if (pdsch_pdu->NrOfCodewords == 2) {
|
||||||
|
dci_payload.mcs2.val = sched_pdsch->mcs;
|
||||||
|
dci_payload.ndi2.val = harq->ndi;
|
||||||
|
dci_payload.rv2.val = pdsch_pdu->rvIndex[1];
|
||||||
|
}
|
||||||
dci_payload.pucch_resource_indicator = pucch ? pucch->resource_indicator : 0;
|
dci_payload.pucch_resource_indicator = pucch ? pucch->resource_indicator : 0;
|
||||||
dci_payload.pdsch_to_harq_feedback_timing_indicator.val = pucch ? pucch->timing_indicator : 0; // PDSCH to HARQ TI
|
dci_payload.pdsch_to_harq_feedback_timing_indicator.val = pucch ? pucch->timing_indicator : 0; // PDSCH to HARQ TI
|
||||||
dci_payload.dai[0].val = pucch ? (pucch->dai_c - 1) & 3 : 0;
|
dci_payload.dai[0].val = pucch ? (pucch->dai_c - 1) & 3 : 0;
|
||||||
@@ -2518,8 +2562,10 @@ void delete_nr_ue_data(NR_UE_info_t *UE, uid_allocator_t *uia)
|
|||||||
destroy_nr_list(&sched_ctrl->available_ul_harq);
|
destroy_nr_list(&sched_ctrl->available_ul_harq);
|
||||||
destroy_nr_list(&sched_ctrl->feedback_ul_harq);
|
destroy_nr_list(&sched_ctrl->feedback_ul_harq);
|
||||||
destroy_nr_list(&sched_ctrl->retrans_ul_harq);
|
destroy_nr_list(&sched_ctrl->retrans_ul_harq);
|
||||||
for (int i = 0; i < NR_MAX_HARQ_PROCESSES; ++i)
|
for (int i = 0; i < NR_MAX_HARQ_PROCESSES; ++i) {
|
||||||
free_transportBlock_buffer(&sched_ctrl->harq_processes[i].transportBlock);
|
free_transportBlock_buffer(&sched_ctrl->harq_processes[i].transportBlock);
|
||||||
|
free_transportBlock_buffer(&sched_ctrl->harq_processes[i].transportBlock2);
|
||||||
|
}
|
||||||
free_sched_pucch_list(sched_ctrl);
|
free_sched_pucch_list(sched_ctrl);
|
||||||
uid_linear_allocator_free(uia, UE->uid);
|
uid_linear_allocator_free(uia, UE->uid);
|
||||||
LOG_I(NR_MAC, "Remove NR rnti 0x%04x\n", UE->rnti);
|
LOG_I(NR_MAC, "Remove NR rnti 0x%04x\n", UE->rnti);
|
||||||
@@ -3614,9 +3660,16 @@ void nr_mac_update_timers(module_id_t module_id)
|
|||||||
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
|
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
|
||||||
|
|
||||||
if (nr_mac_check_release(sched_ctrl)) {
|
if (nr_mac_check_release(sched_ctrl)) {
|
||||||
// trigger release first as nr_mac_release_ue() invalidates UE ptr
|
// nakaolab UE release race fix:
|
||||||
nr_mac_trigger_release_complete(mac, UE->rnti);
|
// Save rnti before nr_mac_trigger_release_complete — that call drops
|
||||||
nr_mac_release_ue(mac, UE->rnti);
|
// the scheduler lock briefly (to send F1AP UE Context Release Complete),
|
||||||
|
// and another thread (e.g., RRC DL handler) can free this UE during
|
||||||
|
// that window, leaving UE->rnti pointing at freed memory.
|
||||||
|
rnti_t rnti = UE->rnti;
|
||||||
|
nr_mac_trigger_release_complete(mac, rnti);
|
||||||
|
// Re-validate after lock re-acquired: skip release if already gone.
|
||||||
|
if (find_nr_UE(&mac->UE_info, rnti) != NULL)
|
||||||
|
nr_mac_release_ue(mac, rnti);
|
||||||
// go back to examine the next UE, which is at the position the
|
// go back to examine the next UE, which is at the position the
|
||||||
// current UE was
|
// current UE was
|
||||||
UE--;
|
UE--;
|
||||||
|
|||||||
@@ -497,7 +497,9 @@ static void evaluate_sinr_report(NR_UE_info_t *UE,
|
|||||||
stats->num_sinr_meas++;
|
stats->num_sinr_meas++;
|
||||||
|
|
||||||
const int mcs_table = UE->current_DL_BWP.mcsTableIdx;
|
const int mcs_table = UE->current_DL_BWP.mcsTableIdx;
|
||||||
const int nrOfLayers = get_dl_nrOfLayers(sched_ctrl, UE->current_DL_BWP.dci_format);
|
/* NULL mac arg: SINR-based MCS estimation skips antenna-state rank cap
|
||||||
|
(caller doesn't have nrmac in scope; this is only for max-MCS hint). */
|
||||||
|
const int nrOfLayers = get_dl_nrOfLayers(NULL, sched_ctrl, UE->current_DL_BWP.dci_format);
|
||||||
sched_ctrl->dl_max_mcs = get_mcs_from_SINRx10(mcs_table, sinr_report->r[0].SINRx10, nrOfLayers);
|
sched_ctrl->dl_max_mcs = get_mcs_from_SINRx10(mcs_table, sinr_report->r[0].SINRx10, nrOfLayers);
|
||||||
|
|
||||||
LOG_D(MAC, "Reported SSB-SINR = %01f, dl_max_mcs %d\n", sinr_report->r[0].SINRx10 / 10.0, sched_ctrl->dl_max_mcs);
|
LOG_D(MAC, "Reported SSB-SINR = %01f, dl_max_mcs %d\n", sinr_report->r[0].SINRx10 / 10.0, sched_ctrl->dl_max_mcs);
|
||||||
@@ -716,6 +718,7 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
|
|||||||
int cumul_bits = 0;
|
int cumul_bits = 0;
|
||||||
int r_index = -1;
|
int r_index = -1;
|
||||||
int new_bf_index = -1;
|
int new_bf_index = -1;
|
||||||
|
bool link_csi_decoded = false;
|
||||||
for (int csi_report_id = 0; csi_report_id < csi_MeasConfig->csi_ReportConfigToAddModList->list.count; csi_report_id++) {
|
for (int csi_report_id = 0; csi_report_id < csi_MeasConfig->csi_ReportConfigToAddModList->list.count; csi_report_id++) {
|
||||||
nr_csi_report_t *csi_report = &UE->csi_report_template[csi_report_id];
|
nr_csi_report_t *csi_report = &UE->csi_report_template[csi_report_id];
|
||||||
csi_report->nb_of_csi_ssb_report = 0;
|
csi_report->nb_of_csi_ssb_report = 0;
|
||||||
@@ -761,12 +764,25 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
|
|||||||
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
|
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
|
||||||
cumul_bits += cri_bitlen;
|
cumul_bits += cri_bitlen;
|
||||||
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
|
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
|
||||||
if (ri_bitlen)
|
if (ri_bitlen) {
|
||||||
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
|
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
|
||||||
|
} else {
|
||||||
|
/* nakaolab: ri_bitlen=0 (single RI allowed via ri_restriction).
|
||||||
|
* Pick that RI directly — without this, r_index stays -1 and trips
|
||||||
|
* the DevAssert(ri>=0) in evaluate_cqi_report. */
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if ((csi_report->csi_meas_bitlen.ri_restriction >> i) & 0x01) {
|
||||||
|
r_index = i;
|
||||||
|
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
cumul_bits += ri_bitlen;
|
cumul_bits += ri_bitlen;
|
||||||
if (r_index != -1)
|
if (r_index != -1)
|
||||||
skip_zero_padding(&cumul_bits, csi_report, r_index, bitlen);
|
skip_zero_padding(&cumul_bits, csi_report, r_index, bitlen);
|
||||||
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
|
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
|
||||||
|
link_csi_decoded |= r_index >= 0;
|
||||||
break;
|
break;
|
||||||
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_PMI_CQI:
|
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_PMI_CQI:
|
||||||
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.print_report = true;
|
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.print_report = true;
|
||||||
@@ -775,8 +791,20 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
|
|||||||
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
|
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
|
||||||
cumul_bits += cri_bitlen;
|
cumul_bits += cri_bitlen;
|
||||||
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
|
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
|
||||||
if (ri_bitlen)
|
if (ri_bitlen) {
|
||||||
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
|
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
|
||||||
|
} else {
|
||||||
|
/* nakaolab: ri_bitlen=0 (single RI allowed via ri_restriction).
|
||||||
|
* Pick that RI directly — without this, r_index stays -1 and trips
|
||||||
|
* the DevAssert(ri>=0) in evaluate_cqi_report. */
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if ((csi_report->csi_meas_bitlen.ri_restriction >> i) & 0x01) {
|
||||||
|
r_index = i;
|
||||||
|
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
cumul_bits += ri_bitlen;
|
cumul_bits += ri_bitlen;
|
||||||
if (r_index != -1)
|
if (r_index != -1)
|
||||||
skip_zero_padding(&cumul_bits, csi_report, r_index, bitlen);
|
skip_zero_padding(&cumul_bits, csi_report, r_index, bitlen);
|
||||||
@@ -784,6 +812,7 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
|
|||||||
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id = csi_report_id;
|
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id = csi_report_id;
|
||||||
cumul_bits += pmi_bitlen;
|
cumul_bits += pmi_bitlen;
|
||||||
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
|
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
|
||||||
|
link_csi_decoded |= r_index >= 0;
|
||||||
break;
|
break;
|
||||||
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_LI_PMI_CQI:
|
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_LI_PMI_CQI:
|
||||||
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.print_report = true;
|
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.print_report = true;
|
||||||
@@ -792,8 +821,20 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
|
|||||||
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
|
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
|
||||||
cumul_bits += cri_bitlen;
|
cumul_bits += cri_bitlen;
|
||||||
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
|
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
|
||||||
if (ri_bitlen)
|
if (ri_bitlen) {
|
||||||
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
|
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
|
||||||
|
} else {
|
||||||
|
/* nakaolab: ri_bitlen=0 (single RI allowed via ri_restriction).
|
||||||
|
* Pick that RI directly — without this, r_index stays -1 and trips
|
||||||
|
* the DevAssert(ri>=0) in evaluate_cqi_report. */
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if ((csi_report->csi_meas_bitlen.ri_restriction >> i) & 0x01) {
|
||||||
|
r_index = i;
|
||||||
|
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
cumul_bits += ri_bitlen;
|
cumul_bits += ri_bitlen;
|
||||||
li_bitlen = evaluate_li_report(payload, csi_report, cumul_bits, r_index, sched_ctrl);
|
li_bitlen = evaluate_li_report(payload, csi_report, cumul_bits, r_index, sched_ctrl);
|
||||||
cumul_bits += li_bitlen;
|
cumul_bits += li_bitlen;
|
||||||
@@ -803,6 +844,7 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
|
|||||||
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id = csi_report_id;
|
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id = csi_report_id;
|
||||||
cumul_bits += pmi_bitlen;
|
cumul_bits += pmi_bitlen;
|
||||||
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
|
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
|
||||||
|
link_csi_decoded |= r_index >= 0;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
AssertFatal(1 == 0, "Invalid or not supported CSI measurement report\n");
|
AssertFatal(1 == 0, "Invalid or not supported CSI measurement report\n");
|
||||||
@@ -810,6 +852,10 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (link_csi_decoded) {
|
||||||
|
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.report_frame = frame;
|
||||||
|
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.report_slot = slot;
|
||||||
|
}
|
||||||
if ((new_bf_index !=-1) && !nrmac->radio_config.do_TCI)
|
if ((new_bf_index !=-1) && !nrmac->radio_config.do_TCI)
|
||||||
// Trigger RRCReconfiguration. Need to be out of the for loop as it may modify csi_MeasConfig
|
// Trigger RRCReconfiguration. Need to be out of the for loop as it may modify csi_MeasConfig
|
||||||
beam_switching_procedure(nrmac, UE, new_bf_index);
|
beam_switching_procedure(nrmac, UE, new_bf_index);
|
||||||
@@ -882,8 +928,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
|
|||||||
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
|
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
|
||||||
if (((uci_01->pduBitmap >> 1) & 0x01)) {
|
if (((uci_01->pduBitmap >> 1) & 0x01)) {
|
||||||
// iterate over received harq bits
|
// iterate over received harq bits
|
||||||
for (int harq_bit = 0; harq_bit < uci_01->harq.num_harq; harq_bit++) {
|
for (int harq_bit = 0; harq_bit < uci_01->harq.num_harq;) {
|
||||||
const uint8_t harq_value = uci_01->harq.harq_list[harq_bit].harq_value;
|
|
||||||
const uint8_t harq_confidence = uci_01->harq.harq_confidence_level;
|
const uint8_t harq_confidence = uci_01->harq.harq_confidence_level;
|
||||||
NR_UE_harq_t *harq = find_harq(frame, slot, UE, nrmac->dl_bler.harq_round_max);
|
NR_UE_harq_t *harq = find_harq(frame, slot, UE, nrmac->dl_bler.harq_round_max);
|
||||||
if (!harq) {
|
if (!harq) {
|
||||||
@@ -891,11 +936,16 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
DevAssert(harq->is_waiting);
|
DevAssert(harq->is_waiting);
|
||||||
|
const int n_ack_bits = harq->sched_pdsch.nrOfLayers > 4 ? 2 : 1;
|
||||||
const int8_t pid = sched_ctrl->feedback_dl_harq.head;
|
const int8_t pid = sched_ctrl->feedback_dl_harq.head;
|
||||||
remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
|
remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
|
||||||
LOG_D(NR_MAC,"%4d.%2d bit %d pid %d ack/nack %d\n",frame, slot, harq_bit,pid,harq_value);
|
bool success = harq_confidence == 0 && harq_bit + n_ack_bits <= uci_01->harq.num_harq;
|
||||||
|
for (int q = 0; q < n_ack_bits && harq_bit + q < uci_01->harq.num_harq; q++) {
|
||||||
|
const uint8_t harq_value = uci_01->harq.harq_list[harq_bit + q].harq_value;
|
||||||
|
LOG_D(NR_MAC, "%4d.%2d bit %d pid %d cw %d ack/nack %d\n", frame, slot, harq_bit + q, pid, q, harq_value);
|
||||||
|
success &= harq_value == 0;
|
||||||
|
}
|
||||||
nr_mac_update_pdcch_closed_loop_adjust(sched_ctrl, harq_confidence != 0);
|
nr_mac_update_pdcch_closed_loop_adjust(sched_ctrl, harq_confidence != 0);
|
||||||
bool success = harq_value == 0 && harq_confidence == 0;
|
|
||||||
// TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133)
|
// TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133)
|
||||||
if (success && harq->start_tci_timer) {
|
if (success && harq->start_tci_timer) {
|
||||||
int slots = 3 * nrmac->frame_structure.numb_slots_frame / 10;
|
int slots = 3 * nrmac->frame_structure.numb_slots_frame / 10;
|
||||||
@@ -919,6 +969,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
|
|||||||
// DTX for each _bit_, but maybe once for PUCCH is enough? not sure
|
// DTX for each _bit_, but maybe once for PUCCH is enough? not sure
|
||||||
nr_mac_signal_dtx(&sched_ctrl->pucch_pc);
|
nr_mac_signal_dtx(&sched_ctrl->pucch_pc);
|
||||||
}
|
}
|
||||||
|
harq_bit += n_ack_bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tpc (power control) only if we received AckNack
|
// tpc (power control) only if we received AckNack
|
||||||
@@ -984,19 +1035,24 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c
|
|||||||
|
|
||||||
if ((uci_234->pduBitmap >> 1) & 0x01) {
|
if ((uci_234->pduBitmap >> 1) & 0x01) {
|
||||||
// iterate over received harq bits
|
// iterate over received harq bits
|
||||||
for (int harq_bit = 0; harq_bit < uci_234->harq.harq_bit_len; harq_bit++) {
|
for (int harq_bit = 0; harq_bit < uci_234->harq.harq_bit_len;) {
|
||||||
const int acknack = ((uci_234->harq.harq_payload[harq_bit >> 3]) >> harq_bit) & 0x01;
|
|
||||||
NR_UE_harq_t *harq = find_harq(frame, slot, UE, RC.nrmac[mod_id]->dl_bler.harq_round_max);
|
NR_UE_harq_t *harq = find_harq(frame, slot, UE, RC.nrmac[mod_id]->dl_bler.harq_round_max);
|
||||||
if (!harq) {
|
if (!harq) {
|
||||||
LOG_E(NR_MAC, "UE %04x: Could not find a HARQ process at %4d.%2d!\n", UE->rnti, frame, slot);
|
LOG_E(NR_MAC, "UE %04x: Could not find a HARQ process at %4d.%2d!\n", UE->rnti, frame, slot);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
DevAssert(harq->is_waiting);
|
DevAssert(harq->is_waiting);
|
||||||
|
const int n_ack_bits = harq->sched_pdsch.nrOfLayers > 4 ? 2 : 1;
|
||||||
const int8_t pid = sched_ctrl->feedback_dl_harq.head;
|
const int8_t pid = sched_ctrl->feedback_dl_harq.head;
|
||||||
remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
|
remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
|
||||||
LOG_D(NR_MAC,"%4d.%2d bit %d pid %d ack/nack %d\n",frame, slot, harq_bit, pid, acknack);
|
bool success = uci_234->harq.harq_crc != 1 && harq_bit + n_ack_bits <= uci_234->harq.harq_bit_len;
|
||||||
|
for (int q = 0; q < n_ack_bits && harq_bit + q < uci_234->harq.harq_bit_len; q++) {
|
||||||
|
const int bit = harq_bit + q;
|
||||||
|
const int acknack = (uci_234->harq.harq_payload[bit >> 3] >> (bit & 7)) & 0x01;
|
||||||
|
LOG_D(NR_MAC, "%4d.%2d bit %d pid %d cw %d ack/nack %d\n", frame, slot, bit, pid, q, acknack);
|
||||||
|
success &= acknack;
|
||||||
|
}
|
||||||
// TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133)
|
// TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133)
|
||||||
bool success = uci_234->harq.harq_crc != 1 && acknack;
|
|
||||||
if (success && harq->start_tci_timer) {
|
if (success && harq->start_tci_timer) {
|
||||||
int slots = 3 * nrmac->frame_structure.numb_slots_frame / 10;
|
int slots = 3 * nrmac->frame_structure.numb_slots_frame / 10;
|
||||||
nr_timer_setup(&sched_ctrl->tci_beam_switch, slots, 1);
|
nr_timer_setup(&sched_ctrl->tci_beam_switch, slots, 1);
|
||||||
@@ -1004,6 +1060,7 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c
|
|||||||
harq->start_tci_timer = false;
|
harq->start_tci_timer = false;
|
||||||
}
|
}
|
||||||
handle_dl_harq(nrmac, UE, pid, success, nrmac->dl_bler.harq_round_max);
|
handle_dl_harq(nrmac, UE, pid, success, nrmac->dl_bler.harq_round_max);
|
||||||
|
harq_bit += n_ack_bits;
|
||||||
}
|
}
|
||||||
free(uci_234->harq.harq_payload);
|
free(uci_234->harq.harq_payload);
|
||||||
}
|
}
|
||||||
@@ -1353,4 +1410,3 @@ void nr_sr_reporting(gNB_MAC_INST *nrmac, frame_t SFN, slot_t slot)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
|
|||||||
int get_mcs_from_SINRx10(int mcs_table, int SINRx10, int Nl);
|
int get_mcs_from_SINRx10(int mcs_table, int SINRx10, int Nl);
|
||||||
uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx);
|
uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx);
|
||||||
|
|
||||||
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
|
uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
|
||||||
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
|
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
|
||||||
|
|
||||||
void free_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl);
|
void free_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl);
|
||||||
@@ -464,6 +464,7 @@ bool prepare_initial_ul_rrc_message(gNB_MAC_INST *mac, NR_UE_info_t *UE);
|
|||||||
void send_initial_ul_rrc_message(int rnti, const uint8_t *sdu, sdu_size_t sdu_len, void *data);
|
void send_initial_ul_rrc_message(int rnti, const uint8_t *sdu, sdu_size_t sdu_len, void *data);
|
||||||
|
|
||||||
void finish_nr_dl_harq(NR_UE_sched_ctrl_t *sched_ctrl, int harq_pid);
|
void finish_nr_dl_harq(NR_UE_sched_ctrl_t *sched_ctrl, int harq_pid);
|
||||||
|
void update_sched_pdsch_codeword_tbs(NR_sched_pdsch_t *sched);
|
||||||
void abort_nr_dl_harq(NR_UE_info_t* UE, int8_t harq_pid);
|
void abort_nr_dl_harq(NR_UE_info_t* UE, int8_t harq_pid);
|
||||||
|
|
||||||
void nr_mac_trigger_release_timer(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpacing_t subcarrier_spacing);
|
void nr_mac_trigger_release_timer(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpacing_t subcarrier_spacing);
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
#include "LAYER2/NR_MAC_COMMON/nr_mac_common.h"
|
#include "LAYER2/NR_MAC_COMMON/nr_mac_common.h"
|
||||||
#include "LAYER2/NR_MAC_gNB/mac_config.h"
|
#include "LAYER2/NR_MAC_gNB/mac_config.h"
|
||||||
#include "NR_TAG.h"
|
#include "NR_TAG.h"
|
||||||
|
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||||
|
|
||||||
/* Defs */
|
/* Defs */
|
||||||
#define MAX_NUM_BWP 5
|
#define MAX_NUM_BWP 5
|
||||||
@@ -459,6 +460,7 @@ typedef struct NR_sched_pusch {
|
|||||||
|
|
||||||
typedef struct NR_pdsch_dmrs {
|
typedef struct NR_pdsch_dmrs {
|
||||||
uint8_t dmrs_ports_id;
|
uint8_t dmrs_ports_id;
|
||||||
|
uint16_t dmrs_ports;
|
||||||
uint8_t N_PRB_DMRS;
|
uint8_t N_PRB_DMRS;
|
||||||
uint8_t N_DMRS_SLOT;
|
uint8_t N_DMRS_SLOT;
|
||||||
uint16_t dl_dmrs_symb_pos;
|
uint16_t dl_dmrs_symb_pos;
|
||||||
@@ -484,6 +486,8 @@ typedef struct NR_sched_pdsch {
|
|||||||
uint16_t R;
|
uint16_t R;
|
||||||
uint8_t Qm;
|
uint8_t Qm;
|
||||||
uint32_t tb_size;
|
uint32_t tb_size;
|
||||||
|
/// Second transport block for rank 5--8 (same MCS/R/Qm for bundled HARQ)
|
||||||
|
uint32_t tb_size2;
|
||||||
|
|
||||||
/// DL HARQ PID to use for this UE, or -1 for "any new"
|
/// DL HARQ PID to use for this UE, or -1 for "any new"
|
||||||
int8_t dl_harq_pid;
|
int8_t dl_harq_pid;
|
||||||
@@ -510,6 +514,7 @@ typedef struct NR_UE_harq {
|
|||||||
|
|
||||||
/* Transport block to be sent using this HARQ process */
|
/* Transport block to be sent using this HARQ process */
|
||||||
byte_array_t transportBlock;
|
byte_array_t transportBlock;
|
||||||
|
byte_array_t transportBlock2;
|
||||||
uint32_t tb_size; // size of currently stored TB
|
uint32_t tb_size; // size of currently stored TB
|
||||||
bool start_tci_timer;
|
bool start_tci_timer;
|
||||||
/// sched_pdsch keeps information on MCS etc used for the initial transmission
|
/// sched_pdsch keeps information on MCS etc used for the initial transmission
|
||||||
@@ -546,6 +551,8 @@ struct CRI_RI_LI_PMI_CQI {
|
|||||||
uint8_t wb_cqi_2tb;
|
uint8_t wb_cqi_2tb;
|
||||||
uint8_t cqi_table;
|
uint8_t cqi_table;
|
||||||
uint8_t csi_report_id;
|
uint8_t csi_report_id;
|
||||||
|
uint16_t report_frame;
|
||||||
|
uint16_t report_slot;
|
||||||
bool print_report;
|
bool print_report;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -714,6 +721,9 @@ typedef struct NR_mac_dir_stats {
|
|||||||
uint32_t total_rbs_retx;
|
uint32_t total_rbs_retx;
|
||||||
uint32_t num_mac_sdu;
|
uint32_t num_mac_sdu;
|
||||||
uint32_t current_rbs;
|
uint32_t current_rbs;
|
||||||
|
// Most recently scheduled spatial layer count. Unlike current_rbs this is
|
||||||
|
// intentionally persistent so asynchronous E2 sampling does not miss it.
|
||||||
|
uint8_t last_layers;
|
||||||
} NR_mac_dir_stats_t;
|
} NR_mac_dir_stats_t;
|
||||||
|
|
||||||
typedef struct NR_mac_stats {
|
typedef struct NR_mac_stats {
|
||||||
@@ -965,6 +975,10 @@ typedef struct gNB_MAC_INST_s {
|
|||||||
nr_mac_config_t radio_config;
|
nr_mac_config_t radio_config;
|
||||||
nr_rlc_configuration_t rlc_config;
|
nr_rlc_configuration_t rlc_config;
|
||||||
|
|
||||||
|
/// Pointer to PHY antenna state (port mute mask) — set in nr-gnb init,
|
||||||
|
/// used by get_dl_nrOfLayers to cap MIMO rank to active TX port count.
|
||||||
|
nr_antenna_state_t *antenna_state;
|
||||||
|
|
||||||
NR_sched_ctrl_sib1_t *sched_ctrlSIB1;
|
NR_sched_ctrl_sib1_t *sched_ctrlSIB1;
|
||||||
NR_sched_pdcch_t *sched_pdcch_otherSI;
|
NR_sched_pdcch_t *sched_pdcch_otherSI;
|
||||||
uint16_t cset0_bwp_start;
|
uint16_t cset0_bwp_start;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "xer_encoder.h"
|
#include "xer_encoder.h"
|
||||||
|
|
||||||
#define NR_MAX_SUPPORTED_DL_LAYERS 4
|
#define NR_MAX_SUPPORTED_DL_LAYERS NR_MAX_NB_LAYERS
|
||||||
|
|
||||||
/* Default values for measurement gap configuration */
|
/* Default values for measurement gap configuration */
|
||||||
#define DEFAULT_MGRP NR_GapConfig__mgrp_ms160
|
#define DEFAULT_MGRP NR_GapConfig__mgrp_ms160
|
||||||
@@ -655,10 +655,18 @@ static void set_dl_maxmimolayers(NR_PDSCH_ServingCellConfig_t *pdsch_servingcell
|
|||||||
pdsch_servingcellconfig->ext1->maxMIMO_Layers = calloc(1, sizeof(*pdsch_servingcellconfig->ext1->maxMIMO_Layers));
|
pdsch_servingcellconfig->ext1->maxMIMO_Layers = calloc(1, sizeof(*pdsch_servingcellconfig->ext1->maxMIMO_Layers));
|
||||||
|
|
||||||
long l = ue_supported_dl_layers(scc, uecap);
|
long l = ue_supported_dl_layers(scc, uecap);
|
||||||
long ue_supported_layers = l > 1 ? l : 2; // min UE supported layers = 2
|
// nakaolab Patch 6: when called with NULL uecap (initial config before
|
||||||
if (maxMIMO_layers == -1)
|
// capability exchange), ue_supported_dl_layers returns -1. Fall back to
|
||||||
|
// maxMIMO_layers from config directly instead of clamping to 2.
|
||||||
|
long ue_supported_layers;
|
||||||
|
if (l < 1) {
|
||||||
|
ue_supported_layers = (maxMIMO_layers > 0) ? maxMIMO_layers : NR_MAX_SUPPORTED_DL_LAYERS;
|
||||||
|
} else {
|
||||||
|
ue_supported_layers = l;
|
||||||
|
}
|
||||||
|
if (maxMIMO_layers == -1)
|
||||||
*pdsch_servingcellconfig->ext1->maxMIMO_Layers = min(NR_MAX_SUPPORTED_DL_LAYERS, ue_supported_layers);
|
*pdsch_servingcellconfig->ext1->maxMIMO_Layers = min(NR_MAX_SUPPORTED_DL_LAYERS, ue_supported_layers);
|
||||||
else
|
else
|
||||||
*pdsch_servingcellconfig->ext1->maxMIMO_Layers = min(maxMIMO_layers, ue_supported_layers);
|
*pdsch_servingcellconfig->ext1->maxMIMO_Layers = min(maxMIMO_layers, ue_supported_layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1282,7 +1290,9 @@ static void config_pucch_resset1(const NR_ServingCellConfigCommon_t *scc,
|
|||||||
pucchfmt2->interslotFrequencyHopping = NULL;
|
pucchfmt2->interslotFrequencyHopping = NULL;
|
||||||
pucchfmt2->additionalDMRS = NULL;
|
pucchfmt2->additionalDMRS = NULL;
|
||||||
pucchfmt2->maxCodeRate = calloc(1,sizeof(*pucchfmt2->maxCodeRate));
|
pucchfmt2->maxCodeRate = calloc(1,sizeof(*pucchfmt2->maxCodeRate));
|
||||||
*pucchfmt2->maxCodeRate = NR_PUCCH_MaxCodeRate_zeroDot15;
|
// nakaolab Patch 5: bump from zeroDot15 (19.2 bit capacity) to zeroDot25
|
||||||
|
// (32 bit capacity) so 4-port CSI report (20 bits) fits in PUCCH.
|
||||||
|
*pucchfmt2->maxCodeRate = NR_PUCCH_MaxCodeRate_zeroDot25;
|
||||||
pucchfmt2->nrofSlots = NULL;
|
pucchfmt2->nrofSlots = NULL;
|
||||||
pucchfmt2->pi2BPSK = NULL;
|
pucchfmt2->pi2BPSK = NULL;
|
||||||
|
|
||||||
@@ -1691,8 +1701,14 @@ static NR_PTRS_DownlinkConfig_t *config_dlptrs(const nr_ptrs_config_t *ptrs)
|
|||||||
return ptrs_config;
|
return ptrs_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap, int bwp_Id, bool do_TCI, nr_ptrs_config_t *ptrs)
|
static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap,
|
||||||
|
int bwp_Id,
|
||||||
|
const nr_mac_config_t *configuration)
|
||||||
{
|
{
|
||||||
|
const int antenna_ports = configuration->pdsch_AntennaPorts.N1 * configuration->pdsch_AntennaPorts.N2
|
||||||
|
* configuration->pdsch_AntennaPorts.XP;
|
||||||
|
const int configured_layers = configuration->maxMIMO_layers > 0 ? configuration->maxMIMO_layers : antenna_ports;
|
||||||
|
const bool two_codewords = min(configured_layers, NR_MAX_SUPPORTED_DL_LAYERS) > 4;
|
||||||
NR_SetupRelease_PDSCH_Config_t *setup_pdsch_Config = calloc(1,sizeof(*setup_pdsch_Config));
|
NR_SetupRelease_PDSCH_Config_t *setup_pdsch_Config = calloc(1,sizeof(*setup_pdsch_Config));
|
||||||
setup_pdsch_Config->present = NR_SetupRelease_PDSCH_Config_PR_setup;
|
setup_pdsch_Config->present = NR_SetupRelease_PDSCH_Config_PR_setup;
|
||||||
NR_PDSCH_Config_t *pdsch_Config = calloc(1, sizeof(*pdsch_Config));
|
NR_PDSCH_Config_t *pdsch_Config = calloc(1, sizeof(*pdsch_Config));
|
||||||
@@ -1701,15 +1717,20 @@ static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap, int bwp
|
|||||||
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->present = NR_SetupRelease_DMRS_DownlinkConfig_PR_setup;
|
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->present = NR_SetupRelease_DMRS_DownlinkConfig_PR_setup;
|
||||||
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup = calloc(1, sizeof(*pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup));
|
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup = calloc(1, sizeof(*pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup));
|
||||||
NR_DMRS_DownlinkConfig_t *dmrs_DownlinkForPDSCH_MappingTypeA = pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
|
NR_DMRS_DownlinkConfig_t *dmrs_DownlinkForPDSCH_MappingTypeA = pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
|
||||||
if (ptrs) {
|
if (configuration->ptrs && !two_codewords) {
|
||||||
NR_PTRS_DownlinkConfig_t *ptrs_config = config_dlptrs(ptrs);
|
NR_PTRS_DownlinkConfig_t *ptrs_config = config_dlptrs(configuration->ptrs);
|
||||||
NR_SetupRelease_PTRS_DownlinkConfig_t *phaseTrackingRS = calloc(1, sizeof(*phaseTrackingRS));
|
NR_SetupRelease_PTRS_DownlinkConfig_t *phaseTrackingRS = calloc(1, sizeof(*phaseTrackingRS));
|
||||||
phaseTrackingRS->present = NR_SetupRelease_PTRS_DownlinkConfig_PR_setup;
|
phaseTrackingRS->present = NR_SetupRelease_PTRS_DownlinkConfig_PR_setup;
|
||||||
phaseTrackingRS->choice.setup = ptrs_config;
|
phaseTrackingRS->choice.setup = ptrs_config;
|
||||||
dmrs_DownlinkForPDSCH_MappingTypeA->phaseTrackingRS = phaseTrackingRS;
|
dmrs_DownlinkForPDSCH_MappingTypeA->phaseTrackingRS = phaseTrackingRS;
|
||||||
}
|
}
|
||||||
|
if (configuration->ptrs && two_codewords)
|
||||||
|
LOG_W(NR_MAC, "PDSCH PTRS is disabled for rank 5--8 until two-codeword PTRS is supported\n");
|
||||||
dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_Type = NULL;
|
dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_Type = NULL;
|
||||||
dmrs_DownlinkForPDSCH_MappingTypeA->maxLength = NULL;
|
if (two_codewords)
|
||||||
|
asn1cCallocOne(dmrs_DownlinkForPDSCH_MappingTypeA->maxLength, NR_DMRS_DownlinkConfig__maxLength_len2);
|
||||||
|
else
|
||||||
|
dmrs_DownlinkForPDSCH_MappingTypeA->maxLength = NULL;
|
||||||
dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID0 = NULL;
|
dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID0 = NULL;
|
||||||
dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID1 = NULL;
|
dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID1 = NULL;
|
||||||
dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = calloc(1, sizeof(*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition));
|
dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = calloc(1, sizeof(*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition));
|
||||||
@@ -1718,13 +1739,16 @@ static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap, int bwp
|
|||||||
|
|
||||||
pdsch_Config->dataScramblingIdentityPDSCH = NULL;
|
pdsch_Config->dataScramblingIdentityPDSCH = NULL;
|
||||||
pdsch_Config->resourceAllocation = NR_PDSCH_Config__resourceAllocation_resourceAllocationType1;
|
pdsch_Config->resourceAllocation = NR_PDSCH_Config__resourceAllocation_resourceAllocationType1;
|
||||||
|
if (two_codewords)
|
||||||
|
asn1cCallocOne(pdsch_Config->maxNrofCodeWordsScheduledByDCI,
|
||||||
|
NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2);
|
||||||
|
|
||||||
pdsch_Config->prb_BundlingType.present = NR_PDSCH_Config__prb_BundlingType_PR_staticBundling;
|
pdsch_Config->prb_BundlingType.present = NR_PDSCH_Config__prb_BundlingType_PR_staticBundling;
|
||||||
pdsch_Config->prb_BundlingType.choice.staticBundling = calloc(1, sizeof(*pdsch_Config->prb_BundlingType.choice.staticBundling));
|
pdsch_Config->prb_BundlingType.choice.staticBundling = calloc(1, sizeof(*pdsch_Config->prb_BundlingType.choice.staticBundling));
|
||||||
pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize = calloc(1, sizeof(*pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize));
|
pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize = calloc(1, sizeof(*pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize));
|
||||||
*pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize = NR_PDSCH_Config__prb_BundlingType__staticBundling__bundleSize_wideband;
|
*pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize = NR_PDSCH_Config__prb_BundlingType__staticBundling__bundleSize_wideband;
|
||||||
|
|
||||||
if (do_TCI) {
|
if (configuration->do_TCI) {
|
||||||
int n_ssb = 0;
|
int n_ssb = 0;
|
||||||
if (!pdsch_Config->tci_StatesToAddModList)
|
if (!pdsch_Config->tci_StatesToAddModList)
|
||||||
pdsch_Config->tci_StatesToAddModList=calloc(1,sizeof(*pdsch_Config->tci_StatesToAddModList));
|
pdsch_Config->tci_StatesToAddModList=calloc(1,sizeof(*pdsch_Config->tci_StatesToAddModList));
|
||||||
@@ -1840,7 +1864,7 @@ static NR_BWP_Downlink_t *config_downlinkBWP(const NR_ServingCellConfigCommon_t
|
|||||||
asn1cSeqAdd(&bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList->list, ss3);
|
asn1cSeqAdd(&bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList->list, ss3);
|
||||||
|
|
||||||
bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToReleaseList = NULL;
|
bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToReleaseList = NULL;
|
||||||
bwp->bwp_Dedicated->pdsch_Config = config_pdsch(ssb_bitmap, bwp->bwp_Id, configuration->do_TCI, configuration->ptrs);
|
bwp->bwp_Dedicated->pdsch_Config = config_pdsch(ssb_bitmap, bwp->bwp_Id, configuration);
|
||||||
|
|
||||||
set_dl_mcs_table(bwp->bwp_Common->genericParameters.subcarrierSpacing,
|
set_dl_mcs_table(bwp->bwp_Common->genericParameters.subcarrierSpacing,
|
||||||
force_256qam_off ? NULL : uecap,
|
force_256qam_off ? NULL : uecap,
|
||||||
@@ -3389,7 +3413,7 @@ static NR_BWP_DownlinkDedicated_t *configure_initial_dl_bwp(const NR_ServingCell
|
|||||||
asn1cSeqAdd(&pdcch_Config->searchSpacesToAddModList->list, ss2);
|
asn1cSeqAdd(&pdcch_Config->searchSpacesToAddModList->list, ss2);
|
||||||
bwp_Dedicated->pdcch_Config->choice.setup = pdcch_Config;
|
bwp_Dedicated->pdcch_Config->choice.setup = pdcch_Config;
|
||||||
|
|
||||||
bwp_Dedicated->pdsch_Config = config_pdsch(bitmap, 0, configuration->do_TCI, configuration->ptrs);
|
bwp_Dedicated->pdsch_Config = config_pdsch(bitmap, 0, configuration);
|
||||||
// we might call configuration of initial BWP for BWP switch when we already have UE capabilities
|
// we might call configuration of initial BWP for BWP switch when we already have UE capabilities
|
||||||
set_dl_mcs_table(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.subcarrierSpacing,
|
set_dl_mcs_table(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.subcarrierSpacing,
|
||||||
configuration->force_256qam_off ? NULL : uecap,
|
configuration->force_256qam_off ? NULL : uecap,
|
||||||
@@ -4062,7 +4086,7 @@ NR_CellGroupConfig_t *get_default_secondaryCellGroup(const NR_ServingCellConfigC
|
|||||||
|
|
||||||
configDedicated->initialDownlinkBWP = calloc(1, sizeof(*configDedicated->initialDownlinkBWP));
|
configDedicated->initialDownlinkBWP = calloc(1, sizeof(*configDedicated->initialDownlinkBWP));
|
||||||
configDedicated->initialDownlinkBWP->pdcch_Config = NULL;
|
configDedicated->initialDownlinkBWP->pdcch_Config = NULL;
|
||||||
configDedicated->initialDownlinkBWP->pdsch_Config = config_pdsch(bitmap, 0, configuration->do_TCI, configuration->ptrs);
|
configDedicated->initialDownlinkBWP->pdsch_Config = config_pdsch(bitmap, 0, configuration);
|
||||||
configDedicated->initialDownlinkBWP->sps_Config = NULL; // calloc(1,sizeof(struct NR_SetupRelease_SPS_Config));
|
configDedicated->initialDownlinkBWP->sps_Config = NULL; // calloc(1,sizeof(struct NR_SetupRelease_SPS_Config));
|
||||||
|
|
||||||
configDedicated->initialDownlinkBWP->radioLinkMonitoringConfig = NULL;
|
configDedicated->initialDownlinkBWP->radioLinkMonitoringConfig = NULL;
|
||||||
|
|||||||
@@ -362,6 +362,15 @@ static buffer_t *allocCirBuf(rfsimulator_state_t *bridge, int sock)
|
|||||||
|
|
||||||
set_channeldesc_owner(ptr->channel_model, RFSIMU_MODULEID);
|
set_channeldesc_owner(ptr->channel_model, RFSIMU_MODULEID);
|
||||||
set_channeldesc_direction(ptr->channel_model, bridge->role == SIMU_ROLE_SERVER);
|
set_channeldesc_direction(ptr->channel_model, bridge->role == SIMU_ROLE_SERVER);
|
||||||
|
// nakaolab Fix 9: ensure RNG is initialized before random_channel runs in this thread
|
||||||
|
{
|
||||||
|
static thread_local bool rng_initialized = false;
|
||||||
|
if (!rng_initialized) {
|
||||||
|
randominit();
|
||||||
|
set_taus_seed((unsigned int)(uintptr_t)pthread_self());
|
||||||
|
rng_initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
random_channel(ptr->channel_model, false);
|
random_channel(ptr->channel_model, false);
|
||||||
LOG_I(HW, "Random channel %s in rfsimulator activated\n", modelname);
|
LOG_I(HW, "Random channel %s in rfsimulator activated\n", modelname);
|
||||||
}
|
}
|
||||||
@@ -565,7 +574,11 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator)
|
|||||||
break;
|
break;
|
||||||
} else if (strcmp(rfsimuParam[p].strlistptr[i], "chanmod") == 0) {
|
} else if (strcmp(rfsimuParam[p].strlistptr[i], "chanmod") == 0) {
|
||||||
init_channelmod();
|
init_channelmod();
|
||||||
load_channellist(rfsimulator->tx_num_channels,
|
// Channel model nb_tx must cover the peer's TX count, not just ours.
|
||||||
|
// E.g., UE has tx=1, rx=4, but gNB sends 4 TX streams. Without this fix,
|
||||||
|
// the UE-side channel model is (1,4) and only 1 TX stream is processed,
|
||||||
|
// dropping 3 of the 4 layers — RI gets capped at 1 instead of 4.
|
||||||
|
load_channellist(std::max(rfsimulator->tx_num_channels, rfsimulator->rx_num_channels),
|
||||||
rfsimulator->rx_num_channels,
|
rfsimulator->rx_num_channels,
|
||||||
rfsimulator->sample_rate,
|
rfsimulator->sample_rate,
|
||||||
rfsimulator->rx_freq,
|
rfsimulator->rx_freq,
|
||||||
@@ -671,7 +684,7 @@ static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt,
|
|||||||
if (b->channel_model->model_name == NULL)
|
if (b->channel_model->model_name == NULL)
|
||||||
continue;
|
continue;
|
||||||
if (b->conn_sock >= 0 && (strcmp(b->channel_model->model_name, modelname) == 0)) {
|
if (b->conn_sock >= 0 && (strcmp(b->channel_model->model_name, modelname) == 0)) {
|
||||||
channel_desc_t *newmodel = new_channel_desc_scm(t->tx_num_channels,
|
channel_desc_t *newmodel = new_channel_desc_scm(std::max(t->tx_num_channels, t->rx_num_channels),
|
||||||
t->rx_num_channels,
|
t->rx_num_channels,
|
||||||
static_cast<SCM_t>(channelmod),
|
static_cast<SCM_t>(channelmod),
|
||||||
t->sample_rate,
|
t->sample_rate,
|
||||||
@@ -1283,6 +1296,15 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t,
|
|||||||
int rx_beam_id,
|
int rx_beam_id,
|
||||||
bool is_first_beam)
|
bool is_first_beam)
|
||||||
{
|
{
|
||||||
|
// nakaolab Fix 9: Thread-local RNG init.
|
||||||
|
// random_channel/gaussZiggurat may run in this thread; randominit() uses
|
||||||
|
// thread_local storage, so each new RX thread needs its own init.
|
||||||
|
static thread_local bool rng_initialized = false;
|
||||||
|
if (!rng_initialized) {
|
||||||
|
randominit();
|
||||||
|
set_taus_seed((unsigned int)(uintptr_t)pthread_self());
|
||||||
|
rng_initialized = true;
|
||||||
|
}
|
||||||
cf_t temp_array[nbAnt][nsamps];
|
cf_t temp_array[nbAnt][nsamps];
|
||||||
bool channel_modelling = false;
|
bool channel_modelling = false;
|
||||||
// Add all input nodes signal in the output buffer
|
// Add all input nodes signal in the output buffer
|
||||||
@@ -1306,7 +1328,23 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t,
|
|||||||
}
|
}
|
||||||
const uint64_t channel_offset = ptr->channel_model->channel_offset;
|
const uint64_t channel_offset = ptr->channel_model->channel_offset;
|
||||||
const uint64_t channel_length = ptr->channel_model->channel_length;
|
const uint64_t channel_length = ptr->channel_model->channel_length;
|
||||||
std::vector<std::vector<c16_t>> ant_buffers(ptr->nbAnt, std::vector<c16_t>(nsamps + channel_length - 1, {0, 0}));
|
// The UE-side channel descriptor is provisioned for the largest peer
|
||||||
|
// width it may receive (for example 8 TX when the UE has 8 RX). A
|
||||||
|
// matched experiment can still connect a narrower 4-TX gNB. Keep an
|
||||||
|
// input buffer for every descriptor TX port and leave absent peer
|
||||||
|
// ports zero; rxAddInput() iterates channel_model->nb_tx and therefore
|
||||||
|
// must not be handed the shorter peer-width pointer array.
|
||||||
|
const uint model_tx = ptr->channel_model->nb_tx;
|
||||||
|
AssertFatal(ptr->nbAnt <= model_tx,
|
||||||
|
"RFsim peer has %u TX ports but channel model supports only %u\n",
|
||||||
|
ptr->nbAnt,
|
||||||
|
model_tx);
|
||||||
|
AssertFatal(nbAnt <= ptr->channel_model->nb_rx,
|
||||||
|
"RFsim receiver has %d RX ports but channel model supports only %u\n",
|
||||||
|
nbAnt,
|
||||||
|
ptr->channel_model->nb_rx);
|
||||||
|
std::vector<std::vector<c16_t>> ant_buffers(model_tx,
|
||||||
|
std::vector<c16_t>(nsamps + channel_length - 1, {0, 0}));
|
||||||
c16_t *input[ant_buffers.size()];
|
c16_t *input[ant_buffers.size()];
|
||||||
for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) {
|
for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) {
|
||||||
input[aatx] = ant_buffers[aatx].data();
|
input[aatx] = ant_buffers[aatx].data();
|
||||||
|
|||||||
Reference in New Issue
Block a user