Files
openairinterface5g/openair1/PHY/NR_UE_TRANSPORT/nr_dlsch_demodulation.c
T
2026-09-19 23:28:08 +09:00

2006 lines
81 KiB
C

/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
/*!
* \brief Top-level routines for demodulating the PDSCH physical channel from 38-211, V15.2 2018-06
*/
#include "common/platform_constants.h"
#include "nr_phy_common.h"
#include "PHY/defs_nr_UE.h"
#include "nr_transport_proto_ue.h"
#include "PHY/sse_intrin.h"
#include "T.h"
#include "bits.h"
#include "openair1/PHY/NR_UE_ESTIMATION/nr_estimation.h"
#include "PHY/NR_REFSIG/nr_refsig.h"
#include "PHY/NR_REFSIG/dmrs_nr.h"
#include "common/utils/nr/nr_common.h"
#include <complex.h>
#include <pthread.h>
#include "openair1/PHY/TOOLS/phy_scope_interface.h"
#include "nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface.h"
// #define DEBUG_HARQ(a...) printf(a)
#define DEBUG_HARQ(...)
//#define DEBUG_DLSCH_DEMOD
//#define DEBUG_PDSCH_RX
#define print_ints(s,x) printf("%s = %d %d %d %d\n",s,(x)[0],(x)[1],(x)[2],(x)[3])
#define print_shorts(s,x) printf("%s = [%d+j*%d, %d+j*%d, %d+j*%d, %d+j*%d]\n",s,(x)[0],(x)[1],(x)[2],(x)[3],(x)[4],(x)[5],(x)[6],(x)[7])
static bool overlap_csi_symbol(fapi_nr_dl_config_csirs_pdu_rel15_t *csi_pdu, int symbol)
{
int num_l0 [18] = {1, 1, 1, 1, 2, 1, 2, 2, 1, 2, 2, 2, 2, 2, 4, 2, 2, 4};
for (int s = 0; s < num_l0[csi_pdu->row - 1]; s++) {
if (symbol == csi_pdu->symb_l0 + s)
return true;
}
// check also l1 if relevant
if (csi_pdu->row == 13 || csi_pdu->row == 14 || csi_pdu->row == 16 || csi_pdu->row == 17) {
for (int s = 0; s < 2; s++) { // two consecutive symbols including l1
if (symbol == csi_pdu->symb_l1 + s)
return true;
}
}
return false;
}
static uint32_t build_csi_overlap_bitmap(fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config, int symbol)
{
// LS 16 bits for even RBs, MS 16 bits for odd RBs
uint32_t csi_res_bitmap = 0;
int num_k[18] = {1, 1, 1, 1, 1, 4, 2, 2, 6, 3, 4, 4, 3, 3, 3, 4, 4, 4};
for (int i = 0; i < dlsch_config->numCsiRsForRateMatching; i++) {
fapi_nr_dl_config_csirs_pdu_rel15_t *csi_pdu = &dlsch_config->csiRsForRateMatching[i];
if (!overlap_csi_symbol(csi_pdu, symbol))
continue;
int num_kp = 1;
int mult = 1;
int k0_step = 0;
int num_k0 = 1;
switch (csi_pdu->row) {
case 1:
k0_step = 4;
num_k0 = 3;
break;
case 2:
break;
case 4:
num_kp = 2;
mult = 4;
k0_step = 2;
num_k0 = 2;
break;
default:
num_kp = 2;
mult = 2;
}
int found = 0;
int bit = 0;
uint32_t temp_res_map = 0;
while (found < num_k[csi_pdu->row - 1]) {
if ((csi_pdu->freq_domain >> bit) & 0x01) {
for (int k0 = 0; k0 < num_k0; k0++) {
for (int kp = 0; kp < num_kp; kp++) {
int re = (bit * mult) + (k0 * k0_step) + kp;
temp_res_map |= (1 << re);
}
}
found++;
}
bit++;
AssertFatal(bit < 13,
"Couldn't find %d positive bits in bitmap %d for CSI freq. domain\n",
num_k[csi_pdu->row - 1],
csi_pdu->freq_domain);
}
if (csi_pdu->freq_density < 2)
csi_res_bitmap |= (temp_res_map << (16 * csi_pdu->freq_density));
else
csi_res_bitmap |= (temp_res_map + (temp_res_map << 16));
}
return csi_res_bitmap;
}
//==============================================================================================
// Pre-processing for LLR computation
//==============================================================================================
static void nr_dlsch_channel_compensation(uint32_t rx_size_symbol,
int nbRx,
uint8_t n_layers,
c16_t rxdataF_ext[][rx_size_symbol],
int32_t dl_ch_estimates_ext[][rx_size_symbol],
c16_t dl_ch_mag[][nbRx][rx_size_symbol],
c16_t dl_ch_magb[][nbRx][rx_size_symbol],
c16_t dl_ch_magr[][nbRx][rx_size_symbol],
c16_t rxdataF_comp[][n_layers][nbRx][rx_size_symbol],
int ***rho,
NR_DL_FRAME_PARMS *frame_parms,
unsigned char symbol,
int length,
unsigned char mod_order,
unsigned short nb_rb,
unsigned char output_shift)
{
simde__m128i *dl_ch128, *dl_ch128_2, *dl_ch_mag128, *dl_ch_mag128b, *dl_ch_mag128r, *rxdataF128, *rxdataF_comp128, *rho128;
simde__m128i QAM_amp128 = {0}, QAM_amp128b = {0}, QAM_amp128r = {0};
uint32_t nb_rb_0 = length / 12 + ((length % 12) ? 1 : 0);
for (int l = 0; l < n_layers; l++) {
if (mod_order == 4) {
QAM_amp128 = simde_mm_set1_epi16(QAM16_n1); // 2/sqrt(10)
QAM_amp128b = simde_mm_setzero_si128();
QAM_amp128r = simde_mm_setzero_si128();
} else if (mod_order == 6) {
QAM_amp128 = simde_mm_set1_epi16(QAM64_n1); //
QAM_amp128b = simde_mm_set1_epi16(QAM64_n2);
QAM_amp128r = simde_mm_setzero_si128();
} else if (mod_order == 8) {
QAM_amp128 = simde_mm_set1_epi16(QAM256_n1);
QAM_amp128b = simde_mm_set1_epi16(QAM256_n2);
QAM_amp128r = simde_mm_set1_epi16(QAM256_n3);
}
for (int aarx = 0; aarx < frame_parms->nb_antennas_rx; aarx++) {
dl_ch128 = (simde__m128i *)dl_ch_estimates_ext[(l * frame_parms->nb_antennas_rx) + aarx];
dl_ch_mag128 = (simde__m128i *)dl_ch_mag[l][aarx];
dl_ch_mag128b = (simde__m128i *)dl_ch_magb[l][aarx];
dl_ch_mag128r = (simde__m128i *)dl_ch_magr[l][aarx];
rxdataF128 = (simde__m128i *)rxdataF_ext[aarx];
rxdataF_comp128 = (simde__m128i *)(rxdataF_comp[symbol][l][aarx]);
for (int rb = 0; rb < nb_rb_0; rb++) {
if (mod_order > 2) {
// get channel amplitude if not QPSK
simde__m128i mmtmpD0 = simde_mm_madd_epi16(dl_ch128[0], dl_ch128[0]);
mmtmpD0 = simde_mm_srai_epi32(mmtmpD0, output_shift);
simde__m128i mmtmpD1 = simde_mm_madd_epi16(dl_ch128[1], dl_ch128[1]);
mmtmpD1 = simde_mm_srai_epi32(mmtmpD1, output_shift);
mmtmpD0 = simde_mm_packs_epi32(mmtmpD0, mmtmpD1); //|H[0]|^2 |H[1]|^2 |H[2]|^2 |H[3]|^2 |H[4]|^2 |H[5]|^2 |H[6]|^2 |H[7]|^2
// store channel magnitude here in a new field of dlsch
dl_ch_mag128[0] = simde_mm_unpacklo_epi16(mmtmpD0, mmtmpD0);
dl_ch_mag128b[0] = dl_ch_mag128[0];
dl_ch_mag128r[0] = dl_ch_mag128[0];
dl_ch_mag128[0] = simde_mm_mulhrs_epi16(dl_ch_mag128[0], QAM_amp128);
dl_ch_mag128b[0] = simde_mm_mulhrs_epi16(dl_ch_mag128b[0], QAM_amp128b);
dl_ch_mag128r[0] = simde_mm_mulhrs_epi16(dl_ch_mag128r[0], QAM_amp128r);
dl_ch_mag128[1] = simde_mm_unpackhi_epi16(mmtmpD0, mmtmpD0);
dl_ch_mag128b[1] = dl_ch_mag128[1];
dl_ch_mag128r[1] = dl_ch_mag128[1];
dl_ch_mag128[1] = simde_mm_mulhrs_epi16(dl_ch_mag128[1], QAM_amp128);
dl_ch_mag128b[1] = simde_mm_mulhrs_epi16(dl_ch_mag128b[1], QAM_amp128b);
dl_ch_mag128r[1] = simde_mm_mulhrs_epi16(dl_ch_mag128r[1], QAM_amp128r);
mmtmpD0 = simde_mm_madd_epi16(dl_ch128[2], dl_ch128[2]);
mmtmpD0 = simde_mm_srai_epi32(mmtmpD0, output_shift);
mmtmpD1 = simde_mm_packs_epi32(mmtmpD0, mmtmpD0);
dl_ch_mag128[2] = simde_mm_unpacklo_epi16(mmtmpD1, mmtmpD1);
dl_ch_mag128b[2] = dl_ch_mag128[2];
dl_ch_mag128r[2] = dl_ch_mag128[2];
dl_ch_mag128[2] = simde_mm_mulhrs_epi16(dl_ch_mag128[2], QAM_amp128);
dl_ch_mag128b[2] = simde_mm_mulhrs_epi16(dl_ch_mag128b[2], QAM_amp128b);
dl_ch_mag128r[2] = simde_mm_mulhrs_epi16(dl_ch_mag128r[2], QAM_amp128r);
}
// Multiply received data by conjugated channel
rxdataF_comp128[0] = oai_mm_cpx_mult_conj(dl_ch128[0], rxdataF128[0], output_shift);
rxdataF_comp128[1] = oai_mm_cpx_mult_conj(dl_ch128[1], rxdataF128[1], output_shift);
rxdataF_comp128[2] = oai_mm_cpx_mult_conj(dl_ch128[2], rxdataF128[2], output_shift);
dl_ch128 += 3;
dl_ch_mag128 += 3;
dl_ch_mag128b += 3;
dl_ch_mag128r += 3;
rxdataF128 += 3;
rxdataF_comp128 += 3;
}
}
}
if (rho) {
// we compute the Tx correlation matrix for each Rx antenna
// As an example the 2x2 MIMO case requires
// rho[aarx][nl*nl] = [cov(H_aarx_0,H_aarx_0) cov(H_aarx_0,H_aarx_1)
// cov(H_aarx_1,H_aarx_0) cov(H_aarx_1,H_aarx_1)], aarx=0,...,nb_antennas_rx-1
for (int aarx = 0; aarx < frame_parms->nb_antennas_rx; aarx++) {
for (int l = 0; l < n_layers; l++) {
for (int atx = 0; atx < n_layers; atx++) {
rho128 = (simde__m128i *)&rho[aarx][l * n_layers + atx][symbol * nb_rb * 12];
dl_ch128 = (simde__m128i *)dl_ch_estimates_ext[l * frame_parms->nb_antennas_rx + aarx];
dl_ch128_2 = (simde__m128i *)dl_ch_estimates_ext[atx * frame_parms->nb_antennas_rx + aarx];
// multiply by conjugated channel
mult_cpx_conj_vector((c16_t *)dl_ch128, (c16_t *)dl_ch128_2, (c16_t *)rho128, 12 * nb_rb_0, output_shift);
}
}
}
}
}
static void nr_dlsch_channel_level_median(uint32_t rx_size_symbol,
int32_t dl_ch_estimates_ext[][rx_size_symbol],
int32_t median[MAX_ANT][MAX_ANT],
int n_tx,
int n_rx,
int length)
{
for (int aatx = 0; aatx < n_tx; aatx++) {
for (int aarx = 0; aarx < n_rx; aarx++) {
int64_t max = median[aatx][aarx]; // initialize the med point for max
int64_t min = median[aatx][aarx]; // initialize the med point for min
simde__m128i *dl_ch128 = (simde__m128i *)dl_ch_estimates_ext[aatx * n_rx + aarx];
const int length2 = length >> 2; // length = number of REs, hence length2=nb_REs*(32/128) in SIMD loop
for (int ii = 0; ii < length2; ii++) {
simde__m128i norm128D =
simde_mm_srai_epi32(simde_mm_madd_epi16(*dl_ch128, *dl_ch128), 2); //[|H_0|²/4 |H_1|²/4 |H_2|²/4 |H_3|²/4]
int32_t *tmp = (int32_t *)&norm128D;
int64_t norm_pack = (int64_t)tmp[0] + tmp[1] + tmp[2] + tmp[3];
if (norm_pack > max)
max = norm_pack;
if (norm_pack < min)
min = norm_pack;
dl_ch128+=1;
}
median[aatx][aarx] = (max + min) >> 1;
LOG_D(PHY, "Channel level median [%d][%d]: %d max = %ld min = %ld\n", aatx, aarx, median[aatx][aarx], max, min);
}
}
}
//==============================================================================================
// Extraction functions
//==============================================================================================
static void nr_dlsch_extract_rbs(uint32_t rxdataF_sz,
c16_t rxdataF[][rxdataF_sz],
uint32_t rx_size_symbol,
uint32_t pdsch_est_size,
int32_t dl_ch_estimates[][pdsch_est_size],
c16_t rxdataF_ext[][rx_size_symbol],
int32_t dl_ch_estimates_ext[][rx_size_symbol],
unsigned char symbol,
uint8_t pilots,
const fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
const freq_alloc_bitmap_t *freq_alloc,
uint8_t Nl,
NR_DL_FRAME_PARMS *fp,
uint32_t csi_res_bitmap,
int chest_time_type)
{
int config_type = dlsch_config->dmrsConfigType;
int n_dmrs_cdm_groups = dlsch_config->n_dmrs_cdm_groups;
if (config_type == NFAPI_NR_DMRS_TYPE1)
AssertFatal(n_dmrs_cdm_groups == 1
|| n_dmrs_cdm_groups == 2,
"n_dmrs_cdm_groups %d is illegal\n",
n_dmrs_cdm_groups);
else
AssertFatal(n_dmrs_cdm_groups == 1
|| n_dmrs_cdm_groups == 2
|| n_dmrs_cdm_groups == 3,
"n_dmrs_cdm_groups %d is illegal\n",
n_dmrs_cdm_groups);
uint32_t dmrs_rb_bitmap = 0;
if (pilots) {
dmrs_rb_bitmap = 0xfff; // all REs taken by dmrs
if (config_type == NFAPI_NR_DMRS_TYPE1 && n_dmrs_cdm_groups == 1)
dmrs_rb_bitmap = 0x555; // alternating REs starting from 0
if (config_type == NFAPI_NR_DMRS_TYPE2 && n_dmrs_cdm_groups == 1)
dmrs_rb_bitmap = 0xc3; // REs 0,1 and 6,7
if (config_type == NFAPI_NR_DMRS_TYPE2 && n_dmrs_cdm_groups == 2)
dmrs_rb_bitmap = 0x3cf; // REs 0,1,2,3 and 6,7,8,9
}
// csi_res_bitmap LS 16 bits for even RBs, MS 16 bits for odd RBs
uint32_t csi_res_even = csi_res_bitmap & 0xfff;
uint32_t csi_res_odd = (csi_res_bitmap >> 16) & 0xfff;
AssertFatal((dmrs_rb_bitmap & csi_res_even) == 0, "DMRS RE overlapping with CSI RE, it shouldn't happen\n");
AssertFatal((dmrs_rb_bitmap & csi_res_odd) == 0, "DMRS RE overlapping with CSI RE, it shouldn't happen\n");
uint32_t dmrs_csi_overlap_even = csi_res_even + dmrs_rb_bitmap;
uint32_t dmrs_csi_overlap_odd = csi_res_odd + dmrs_rb_bitmap;
int8_t validDmrsEst;
if (chest_time_type == 0)
validDmrsEst = get_valid_dmrs_idx_for_channel_est(dlsch_config->dlDmrsSymbPos, symbol);
else
validDmrsEst = get_next_dmrs_symbol_in_slot(dlsch_config->dlDmrsSymbPos, 0, 14); // get first dmrs symbol index
int pos = 0;
int block_start, block_end;
while (find_next_rb_block(freq_alloc->bitmap, dlsch_config->BWPSize, &pos, &block_start, &block_end)) {
int start_rb = block_start + dlsch_config->BWPStart;
int nb_rb = block_end - block_start + 1;
const int start_re = (fp->first_carrier_offset + start_rb * NR_NB_SC_PER_RB) % fp->ofdm_symbol_size;
for (int aarx = 0; aarx < fp->nb_antennas_rx; aarx++) {
c16_t *rxF_ext = rxdataF_ext[aarx];
c16_t *rxF = &rxdataF[aarx][symbol * fp->ofdm_symbol_size];
for (int l = 0; l < Nl; l++) {
int32_t *dl_ch0 = &dl_ch_estimates[(l * fp->nb_antennas_rx) + aarx][validDmrsEst * fp->ofdm_symbol_size];
int32_t *dl_ch0_ext = dl_ch_estimates_ext[(l * fp->nb_antennas_rx) + aarx];
if (pilots == 0 && csi_res_bitmap == 0) { // data symbol only
if (l == 0) {
if (start_re + nb_rb * NR_NB_SC_PER_RB <= fp->ofdm_symbol_size) {
memcpy(rxF_ext, &rxF[start_re], nb_rb * NR_NB_SC_PER_RB * sizeof(int32_t));
} else {
int neg_length = fp->ofdm_symbol_size - start_re;
int pos_length = nb_rb * NR_NB_SC_PER_RB - neg_length;
memcpy(rxF_ext, &rxF[start_re], neg_length * sizeof(int32_t));
memcpy(&rxF_ext[neg_length], rxF, pos_length * sizeof(int32_t));
}
}
memcpy(dl_ch0_ext, dl_ch0, nb_rb * NR_NB_SC_PER_RB * sizeof(int32_t));
} else {
int j = 0;
int k = start_re;
for (int rb = start_rb; rb < start_rb + nb_rb; rb++) {
uint32_t overlap_map = rb % 2 ? dmrs_csi_overlap_odd : dmrs_csi_overlap_even;
for (int re = 0; re < NR_NB_SC_PER_RB; re++) {
if (((overlap_map >> re) & 0x01) == 0) {
// DATA RE
if (l == 0)
rxF_ext[j] = rxF[k];
dl_ch0_ext[j] = dl_ch0[re];
j++;
}
k++;
if (k >= fp->ofdm_symbol_size)
k -= fp->ofdm_symbol_size;
}
dl_ch0 += 12;
}
}
}
}
}
}
static void nr_dlsch_detection_mrc(uint32_t rx_size_symbol,
short nl,
short n_rx,
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
int ***rho,
c16_t dl_ch_mag[][n_rx][rx_size_symbol],
c16_t dl_ch_magb[][n_rx][rx_size_symbol],
c16_t dl_ch_magr[][n_rx][rx_size_symbol],
unsigned char symbol,
int length)
{
simde__m128i *rxdataF_comp128_0,*rxdataF_comp128_1,*dl_ch_mag128_0,*dl_ch_mag128_1,*dl_ch_mag128_0b,*dl_ch_mag128_1b,*dl_ch_mag128_0r,*dl_ch_mag128_1r;
uint32_t nb_rb_0 = length / 12 + ((length % 12) ? 1 : 0);
if (n_rx > 1) {
for (int l = 0; l < nl; l++) {
rxdataF_comp128_0 = (simde__m128i *)(rxdataF_comp[symbol][l][0]);
dl_ch_mag128_0 = (simde__m128i *)dl_ch_mag[l][0];
dl_ch_mag128_0b = (simde__m128i *)dl_ch_magb[l][0];
dl_ch_mag128_0r = (simde__m128i *)dl_ch_magr[l][0];
for (int aarx = 1; aarx < n_rx; aarx++) {
rxdataF_comp128_1 = (simde__m128i *)(rxdataF_comp[symbol][l][aarx]);
dl_ch_mag128_1 = (simde__m128i *)dl_ch_mag[l][aarx];
dl_ch_mag128_1b = (simde__m128i *)dl_ch_magb[l][aarx];
dl_ch_mag128_1r = (simde__m128i *)dl_ch_magr[l][aarx];
// MRC on each re of rb, both on MF output and magnitude (for 16QAM/64QAM/256 llr computation)
for (int i = 0; i < nb_rb_0 * 3; i++) {
rxdataF_comp128_0[i] = simde_mm_adds_epi16(rxdataF_comp128_0[i],rxdataF_comp128_1[i]);
dl_ch_mag128_0[i] = simde_mm_adds_epi16(dl_ch_mag128_0[i],dl_ch_mag128_1[i]);
dl_ch_mag128_0b[i] = simde_mm_adds_epi16(dl_ch_mag128_0b[i],dl_ch_mag128_1b[i]);
dl_ch_mag128_0r[i] = simde_mm_adds_epi16(dl_ch_mag128_0r[i],dl_ch_mag128_1r[i]);
}
}
}
#ifdef DEBUG_DLSCH_DEMOD
for (int i = 0; i < nb_rb_0 * 3; i++) {
printf("symbol%d RB %d\n", symbol, i / 3);
rxdataF_comp128_0 = (simde__m128i *)(rxdataF_comp[0][0] + symbol * rx_size_symbol);
rxdataF_comp128_1 = (simde__m128i *)(rxdataF_comp[0][n_rx] + symbol * rx_size_symbol);
print_shorts("tx 1 mrc_re/mrc_Im:",(int16_t*)&rxdataF_comp128_0[i]);
print_shorts("tx 2 mrc_re/mrc_Im:",(int16_t*)&rxdataF_comp128_1[i]);
// printf("mrc mag0 = %d = %d \n",((int16_t*)&dl_ch_mag128_0[0])[0],((int16_t*)&dl_ch_mag128_0[0])[1]);
// printf("mrc mag0b = %d = %d \n",((int16_t*)&dl_ch_mag128_0b[0])[0],((int16_t*)&dl_ch_mag128_0b[0])[1]);
}
#endif
if (rho) {
/*rho128_0 = (simde__m128i *) &rho[0][symbol*frame_parms->N_RB_DL*12];
rho128_1 = (simde__m128i *) &rho[1][symbol*frame_parms->N_RB_DL*12];
for (i=0; i<nb_rb_0*3; i++) {
// print_shorts("mrc rho0:",&rho128_0[i]);
// print_shorts("mrc rho1:",&rho128_1[i]);
rho128_0[i] = simde_mm_adds_epi16(simde_mm_srai_epi16(rho128_0[i],1),simde_mm_srai_epi16(rho128_1[i],1));
}*/
}
}
}
/* Zero Forcing Rx function: nr_a_sum_b()
* Compute the complex addition x=x+y
*
* */
void nr_a_sum_b(c16_t *input_x, c16_t *input_y, unsigned short nb_rb)
{
unsigned short rb;
simde__m128i *x = (simde__m128i *)input_x;
simde__m128i *y = (simde__m128i *)input_y;
for (rb=0; rb<nb_rb; rb++) {
x[0] = simde_mm_adds_epi16(x[0], y[0]);
x[1] = simde_mm_adds_epi16(x[1], y[1]);
x[2] = simde_mm_adds_epi16(x[2], y[2]);
x += 3;
y += 3;
}
}
/* Zero Forcing Rx function: nr_element_sign()
* Compute b=sign*a */
static inline void nr_element_sign(c16_t *a, c16_t *b, unsigned short nb_rb, int32_t sign)
{
const int16_t nr_sign[8] __attribute__((aligned(16))) = {-1, -1, -1, -1, -1, -1, -1, -1};
simde__m128i *a_128,*b_128;
a_128 = (simde__m128i *)a;
b_128 = (simde__m128i *)b;
for (int rb = 0; rb < 3 * nb_rb; rb++) {
if (sign < 0)
b_128[rb] = simde_mm_sign_epi16(a_128[rb], ((simde__m128i *)nr_sign)[0]);
else
b_128[rb] = a_128[rb];
#ifdef DEBUG_DLSCH_DEMOD
print_shorts("b:", (int16_t *)b_128);
#endif
}
}
/* Zero Forcing Rx function: nr_det_4x4()
* Compute the matrix determinant for 4x4 Matrix
*
* */
static void nr_determin(int size,
c16_t *a44[][size], //
c16_t *ad_bc, // ad-bc
unsigned short nb_rb,
int32_t sign,
int32_t shift0)
{
AssertFatal(size > 0, "");
if(size==1) {
nr_element_sign(a44[0][0], // a
ad_bc, // b
nb_rb,
sign);
} else {
int16_t k, rr[size - 1], cc[size - 1];
c16_t outtemp[12 * nb_rb] __attribute__((aligned(32)));
c16_t outtemp1[12 * nb_rb] __attribute__((aligned(32)));
c16_t *sub_matrix[size - 1][size - 1];
for (int rtx=0;rtx<size;rtx++) {//row calculation for determin
int ctx=0;
//find the submatrix row and column indices
k=0;
for(int rrtx=0;rrtx<size;rrtx++)
if(rrtx != rtx) rr[k++] = rrtx;
k=0;
for(int cctx=0;cctx<size;cctx++)
if(cctx != ctx) cc[k++] = cctx;
// fill out the sub matrix corresponds to this element
for (int ridx = 0; ridx < (size - 1); ridx++)
for (int cidx = 0; cidx < (size - 1); cidx++)
sub_matrix[cidx][ridx] = a44[cc[cidx]][rr[ridx]];
nr_determin(size - 1,
sub_matrix, // a33
outtemp,
nb_rb,
((rtx & 1) == 1 ? -1 : 1) * ((ctx & 1) == 1 ? -1 : 1) * sign,
shift0);
mult_complex_vectors(a44[ctx][rtx], outtemp, rtx == 0 ? ad_bc : outtemp1, sizeofArray(outtemp1), shift0);
if (rtx != 0)
nr_a_sum_b(ad_bc, outtemp1, nb_rb);
}
}
}
static double complex nr_determin_cpx(int32_t size, // size
double complex a44_cpx[][size], //
int32_t sign)
{
double complex outtemp, outtemp1;
//Allocate the submatrix elements
DevAssert(size > 0);
if(size==1) {
return (a44_cpx[0][0] * sign);
}else {
double complex sub_matrix[size - 1][size - 1];
int16_t k, rr[size - 1], cc[size - 1];
outtemp1 = 0;
for (int rtx=0;rtx<size;rtx++) {//row calculation for determin
int ctx=0;
//find the submatrix row and column indices
k=0;
for(int rrtx=0;rrtx<size;rrtx++)
if(rrtx != rtx) rr[k++] = rrtx;
k=0;
for(int cctx=0;cctx<size;cctx++)
if(cctx != ctx) cc[k++] = cctx;
//fill out the sub matrix corresponds to this element
for (int ridx=0;ridx<(size-1);ridx++)
for (int cidx=0;cidx<(size-1);cidx++)
sub_matrix[cidx][ridx] = a44_cpx[cc[cidx]][rr[ridx]];
outtemp = nr_determin_cpx(size - 1,
sub_matrix, // a33
((rtx & 1) == 1 ? -1 : 1) * ((ctx & 1) == 1 ? -1 : 1) * sign);
outtemp1 += a44_cpx[ctx][rtx] * outtemp;
}
return((double complex)outtemp1);
}
}
/* Zero Forcing Rx function: nr_matrix_inverse()
* Compute the matrix inverse and determinant up to 4x4 Matrix
*
* */
uint8_t nr_matrix_inverse(int32_t size,
c16_t *a44[][size], // Input matrix//conjH_H_elements[0]
c16_t *inv_H_h_H[][size], // Inverse
c16_t *ad_bc, // determin
unsigned short nb_rb,
int32_t flag, // fixed point or floating flag
int32_t shift0)
{
DevAssert(size > 1);
int16_t k,rr[size-1],cc[size-1];
if(flag) {//fixed point SIMD calc.
//Allocate the submatrix elements
c16_t *sub_matrix[size - 1][size - 1];
//Compute Matrix determinant
nr_determin(size,
a44, //
ad_bc, // determinant
nb_rb,
+1,
shift0);
//print_shorts("nr_det_",(int16_t*)&ad_bc[0]);
//Compute Inversion of the H^*H matrix
/* For 2x2 MIMO matrix, we compute
* * |(conj_H_00xH_00+conj_H_10xH_10) (conj_H_00xH_01+conj_H_10xH_11)|
* * H_h_H= | |
* * |(conj_H_01xH_00+conj_H_11xH_10) (conj_H_01xH_01+conj_H_11xH_11)|
* *
* *inv(H_h_H) =(1/det)*[d -b
* * -c a]
* **************************************************************************/
for (int rtx=0;rtx<size;rtx++) {//row
k=0;
for(int rrtx=0;rrtx<size;rrtx++)
if(rrtx != rtx) rr[k++] = rrtx;
for (int ctx=0;ctx<size;ctx++) {//column
k=0;
for(int cctx=0;cctx<size;cctx++)
if(cctx != ctx) cc[k++] = cctx;
//fill out the sub matrix corresponds to this element
for (int ridx=0;ridx<(size-1);ridx++)
for (int cidx=0;cidx<(size-1);cidx++)
// To verify
sub_matrix[cidx][ridx]=a44[cc[cidx]][rr[ridx]];
nr_determin(size - 1, // size
sub_matrix,
inv_H_h_H[rtx][ctx], // out transpose
nb_rb,
((rtx & 1) == 1 ? -1 : 1) * ((ctx & 1) == 1 ? -1 : 1),
shift0);
}
}
}
else {//floating point calc.
//Allocate the submatrix elements
double complex sub_matrix_cpx[size - 1][size - 1];
//Convert the IQ samples (in Q15 format) to float complex
double complex a44_cpx[size][size];
double complex inv_H_h_H_cpx[size][size];
double complex determin_cpx;
for (int i=0; i<12*nb_rb; i++) {
//Convert Q15 to floating point
for (int rtx=0;rtx<size;rtx++) {//row
for (int ctx=0;ctx<size;ctx++) {//column
a44_cpx[ctx][rtx] =
((double)(a44[ctx][rtx])[i].r) / (1 << (shift0 - 1)) + I * ((double)(a44[ctx][rtx])[i].i) / (1 << (shift0 - 1));
}
}
//Compute Matrix determinant (copy real value only)
determin_cpx = nr_determin_cpx(size,
a44_cpx, //
+1);
//if (i<4) printf("order %d nr_det_cpx = %lf+j%lf \n",log2_approx(creal(determin_cpx)),creal(determin_cpx),cimag(determin_cpx));
//Round and convert to Q15 (Out in the same format as Fixed point).
if (creal(determin_cpx)>0) {//determin of the symmetric matrix is real part only
((short *)ad_bc)[i << 1] = (short)((creal(determin_cpx) * (1 << (shift0))) + 0.5); //
} else {
((short *)ad_bc)[i << 1] = (short)((creal(determin_cpx) * (1 << (shift0))) - 0.5); //
}
//Compute Inversion of the H^*H matrix (normalized output divide by determinant)
for (int rtx=0;rtx<size;rtx++) {//row
k=0;
for(int rrtx=0;rrtx<size;rrtx++)
if(rrtx != rtx) rr[k++] = rrtx;
for (int ctx=0;ctx<size;ctx++) {//column
k=0;
for(int cctx=0;cctx<size;cctx++)
if(cctx != ctx) cc[k++] = cctx;
//fill out the sub matrix corresponds to this element
for (int ridx=0;ridx<(size-1);ridx++)
for (int cidx=0;cidx<(size-1);cidx++)
sub_matrix_cpx[cidx][ridx] = a44_cpx[cc[cidx]][rr[ridx]];
inv_H_h_H_cpx[rtx][ctx] = nr_determin_cpx(size - 1, // size,
sub_matrix_cpx, //
((rtx & 1) == 1 ? -1 : 1) * ((ctx & 1) == 1 ? -1 : 1));
//if (i==0) printf("H_h_H(r%d,c%d)=%lf+j%lf --> inv_H_h_H(%d,%d) = %lf+j%lf \n",rtx,ctx,creal(a44_cpx[ctx*size+rtx]),cimag(a44_cpx[ctx*size+rtx]),ctx,rtx,creal(inv_H_h_H_cpx[rtx*size+ctx]),cimag(inv_H_h_H_cpx[rtx*size+ctx]));
if (creal(inv_H_h_H_cpx[rtx][ctx]) > 0)
inv_H_h_H[rtx][ctx][i].r = (short)((creal(inv_H_h_H_cpx[rtx][ctx]) * (1 << (shift0 - 1))) + 0.5); // Convert to Q 18
else
inv_H_h_H[rtx][ctx][i].r = (short)((creal(inv_H_h_H_cpx[rtx][ctx]) * (1 << (shift0 - 1))) - 0.5); //
if (cimag(inv_H_h_H_cpx[rtx][ctx]) > 0)
inv_H_h_H[rtx][ctx][i].i = (short)((cimag(inv_H_h_H_cpx[rtx][ctx]) * (1 << (shift0 - 1))) + 0.5); //
else
inv_H_h_H[rtx][ctx][i].i = (short)((cimag(inv_H_h_H_cpx[rtx][ctx]) * (1 << (shift0 - 1))) - 0.5); //
//if (i<4) printf("inv_H_h_H_FP(%d,%d)= %d+j%d \n",ctx,rtx, ((short *) inv_H_h_H[rtx*size+ctx])[i<<1],((short *) inv_H_h_H[rtx*size+ctx])[(i<<1)+1]);
}
}
}
}
return(0);
}
/* Zero Forcing Rx function: nr_conjch0_mult_ch1()
*
*
* */
// TODO: This function is just a wrapper, can be removed.
void nr_conjch0_mult_ch1(c16_t *ch0, c16_t *ch1, c16_t *ch0conj_ch1, unsigned short nb_rb, unsigned char output_shift0)
{
//This function is used to compute multiplications in H_hermitian * H matrix
mult_cpx_conj_vector(ch0, ch1, ch0conj_ch1, 12 * nb_rb, output_shift0);
}
/*
* MMSE Rx function: up to 4 layers
*/
static void nr_dlsch_mmse(uint32_t rx_size_symbol,
unsigned char n_rx,
unsigned char nl, // number of layer
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
c16_t dl_ch_mag[][n_rx][rx_size_symbol],
c16_t dl_ch_magb[][n_rx][rx_size_symbol],
c16_t dl_ch_magr[][n_rx][rx_size_symbol],
int32_t dl_ch_estimates_ext[][rx_size_symbol],
unsigned char mod_order,
int shift,
unsigned char symbol,
int length,
uint32_t noise_var)
{
uint32_t nb_rb_0 = (length + 11) / 12;
c16_t determ_fin[12 * nb_rb_0] __attribute__((aligned(32)));
///Allocate H^*H matrix elements and sub elements
c16_t conjH_H_elements_data[n_rx][nl][nl][12 * nb_rb_0];
memset(conjH_H_elements_data, 0, sizeof(conjH_H_elements_data));
c16_t *conjH_H_elements[n_rx][nl][nl];
for (int aarx = 0; aarx < n_rx; aarx++)
for (int rtx = 0; rtx < nl; rtx++)
for (int ctx = 0; ctx < nl; ctx++)
conjH_H_elements[aarx][rtx][ctx] = conjH_H_elements_data[aarx][rtx][ctx];
//Compute H^*H matrix elements and sub elements:(1/2^log2_maxh)*conjH_H_elements
for (int rtx = 0; rtx < nl; rtx++) {//row
for (int ctx = 0; ctx < nl; ctx++) {//column
for (int aarx = 0; aarx < n_rx; aarx++) {
c16_t *ch0r = (c16_t *)dl_ch_estimates_ext[rtx * n_rx + aarx];
c16_t *ch0c = (c16_t *)dl_ch_estimates_ext[ctx * n_rx + aarx];
nr_conjch0_mult_ch1(ch0r,
ch0c,
conjH_H_elements[aarx][ctx][rtx], // sic
nb_rb_0,
shift);
if (aarx != 0)
nr_a_sum_b(conjH_H_elements[0][ctx][rtx], conjH_H_elements[aarx][ctx][rtx], nb_rb_0);
}
}
}
// Add noise_var such that: H^h * H + noise_var * I
if (noise_var != 0) {
simde__m128i nvar_128i = simde_mm_set1_epi32(noise_var >> 3);
for (int p = 0; p < nl; p++) {
simde__m128i *conjH_H_128i = (simde__m128i *)conjH_H_elements[0][p][p];
for (int k = 0; k < 3 * nb_rb_0; k++) {
conjH_H_128i[0] = simde_mm_add_epi32(conjH_H_128i[0], nvar_128i);
conjH_H_128i++;
}
}
}
//Compute the inverse and determinant of the H^*H matrix
//Allocate the inverse matrix
c16_t *inv_H_h_H[nl][nl];
c16_t inv_H_h_H_data[nl][nl][12 * nb_rb_0];
memset(inv_H_h_H_data, 0, sizeof(inv_H_h_H_data));
for (int rtx = 0; rtx < nl; rtx++)
for (int ctx = 0; ctx < nl; ctx++)
inv_H_h_H[ctx][rtx] = inv_H_h_H_data[ctx][rtx];
int fp_flag = 1;//0: float point calc 1: Fixed point calc
nr_matrix_inverse(nl,
conjH_H_elements[0], // Input matrix
inv_H_h_H, // Inverse
determ_fin, // determin
nb_rb_0,
fp_flag, // fixed point flag
shift - (fp_flag == 1 ? 1 : 0)); // the out put is Q15
// multiply Matrix inversion pf H_h_H by the rx signal vector
c16_t outtemp[12 * nb_rb_0] __attribute__((aligned(32)));
//Allocate rxdataF for zforcing out
c16_t rxdataF_zforcing[nl][12 * nb_rb_0];
memset(rxdataF_zforcing, 0, sizeof(rxdataF_zforcing));
for (int rtx = 0; rtx < nl; rtx++) {//Output Layers row
// loop over Layers rtx=0,...,N_Layers-1
for (int ctx = 0; ctx < nl; ctx++) { // column multi
// printf("Computing r_%d c_%d\n",rtx,ctx);
// print_shorts(" H_h_H=",(int16_t*)&conjH_H_elements[ctx*nl+rtx][0][0]);
// print_shorts(" Inv_H_h_H=",(int16_t*)&inv_H_h_H[ctx*nl+rtx][0]);
mult_complex_vectors(inv_H_h_H[ctx][rtx],
rxdataF_comp[symbol][ctx][0],
outtemp,
sizeofArray(outtemp),
shift - (fp_flag == 1 ? 1 : 0));
nr_a_sum_b(rxdataF_zforcing[rtx], outtemp, nb_rb_0); // a = a + b
}
#ifdef DEBUG_DLSCH_DEMOD
printf("Computing layer_%d \n", rtx);
print_shorts(" Rx signal:=", (int16_t*)&rxdataF_zforcing[rtx][0]);
print_shorts(" Rx signal:=", (int16_t*)&rxdataF_zforcing[rtx][4]);
print_shorts(" Rx signal:=", (int16_t*)&rxdataF_zforcing[rtx][8]);
#endif
}
//Copy zero_forcing out to output array
for (int rtx = 0; rtx < nl; rtx++)
nr_element_sign(rxdataF_zforcing[rtx], rxdataF_comp[symbol][rtx][0], nb_rb_0, +1);
//Update LLR thresholds with the Matrix determinant
simde__m128i *dl_ch_mag128_0=NULL,*dl_ch_mag128b_0=NULL,*dl_ch_mag128r_0=NULL,*determ_fin_128;
simde__m128i mmtmpD2,mmtmpD3;
simde__m128i QAM_amp128={0},QAM_amp128b={0},QAM_amp128r={0};
short nr_realpart[8]__attribute__((aligned(16))) = {1,0,1,0,1,0,1,0};
determ_fin_128 = (simde__m128i *)&determ_fin[0];
if (mod_order > 2) {
if (mod_order == 4) {
QAM_amp128 = simde_mm_set1_epi16(QAM16_n1); //2/sqrt(10)
QAM_amp128b = simde_mm_setzero_si128();
QAM_amp128r = simde_mm_setzero_si128();
} else if (mod_order == 6) {
QAM_amp128 = simde_mm_set1_epi16(QAM64_n1); //4/sqrt{42}
QAM_amp128b = simde_mm_set1_epi16(QAM64_n2); //2/sqrt{42}
QAM_amp128r = simde_mm_setzero_si128();
} else if (mod_order == 8) {
QAM_amp128 = simde_mm_set1_epi16(QAM256_n1); //8/sqrt{170}
QAM_amp128b = simde_mm_set1_epi16(QAM256_n2);//4/sqrt{170}
QAM_amp128r = simde_mm_set1_epi16(QAM256_n3);//2/sqrt{170}
}
dl_ch_mag128_0 = (simde__m128i *)dl_ch_mag[0][0];
dl_ch_mag128b_0 = (simde__m128i *)dl_ch_magb[0][0];
dl_ch_mag128r_0 = (simde__m128i *)dl_ch_magr[0][0];
for (int rb = 0; rb < 3 * nb_rb_0; rb++) {
//for symmetric H_h_H matrix, the determinant is only real values
mmtmpD2 = simde_mm_sign_epi16(determ_fin_128[0],*(simde__m128i*)&nr_realpart[0]);//set imag part to 0
mmtmpD3 = simde_mm_shufflelo_epi16(mmtmpD2,SIMDE_MM_SHUFFLE(2,3,0,1));
mmtmpD3 = simde_mm_shufflehi_epi16(mmtmpD3,SIMDE_MM_SHUFFLE(2,3,0,1));
mmtmpD2 = simde_mm_add_epi16(mmtmpD2,mmtmpD3);
dl_ch_mag128_0[0] = mmtmpD2;
dl_ch_mag128b_0[0] = mmtmpD2;
dl_ch_mag128r_0[0] = mmtmpD2;
dl_ch_mag128_0[0] = simde_mm_mulhrs_epi16(dl_ch_mag128_0[0], QAM_amp128);
dl_ch_mag128b_0[0] = simde_mm_mulhrs_epi16(dl_ch_mag128b_0[0],QAM_amp128b);
dl_ch_mag128r_0[0] = simde_mm_mulhrs_epi16(dl_ch_mag128r_0[0],QAM_amp128r);
determ_fin_128 += 1;
dl_ch_mag128_0 += 1;
dl_ch_mag128b_0 += 1;
dl_ch_mag128r_0 += 1;
}
}
}
/* 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_reference(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 first_re,
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 = first_re; re < first_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;
}
typedef enum {
NR_HIGH_RANK_CHOLESKY,
NR_HIGH_RANK_REFERENCE,
} nr_high_rank_detector_t;
typedef struct {
nr_high_rank_detector_t detector;
int re_group;
} nr_high_rank_detector_config_t;
static pthread_once_t high_rank_detector_once = PTHREAD_ONCE_INIT;
static nr_high_rank_detector_config_t high_rank_detector_config = {
.detector = NR_HIGH_RANK_CHOLESKY,
.re_group = 1,
};
static void configure_high_rank_detector(void)
{
const char *detector = getenv("OAI_HIGH_RANK_DETECTOR");
if (detector != NULL && strcmp(detector, "reference") == 0)
high_rank_detector_config.detector = NR_HIGH_RANK_REFERENCE;
else if (detector != NULL && strcmp(detector, "cholesky") != 0)
LOG_W(NR_PHY,
"Unknown OAI_HIGH_RANK_DETECTOR=%s; using cholesky\n",
detector);
const char *group = getenv("OAI_HIGH_RANK_GROUP");
if (group != NULL) {
const long parsed = strtol(group, NULL, 10);
if (parsed >= 1 && parsed <= NR_NB_SC_PER_RB)
high_rank_detector_config.re_group = parsed;
else
LOG_W(NR_PHY,
"OAI_HIGH_RANK_GROUP must be in [1,%d]; using 1\n",
NR_NB_SC_PER_RB);
}
LOG_I(NR_PHY,
"[HIGH_RANK_DETECTOR] mode=%s re_group=%d\n",
high_rank_detector_config.detector == NR_HIGH_RANK_REFERENCE ? "reference" : "cholesky",
high_rank_detector_config.re_group);
}
static const nr_high_rank_detector_config_t *get_high_rank_detector_config(void)
{
pthread_once(&high_rank_detector_once, configure_high_rank_detector);
return &high_rank_detector_config;
}
/* Factor A = L L^H. A is Hermitian positive definite after MMSE
* regularization, so Cholesky is both cheaper and more stable than forming an
* explicit inverse. */
static bool nr_high_rank_cholesky(uint8_t nl,
const float complex A[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS],
float complex L[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS])
{
memset(L, 0, sizeof(float complex) * NR_MAX_NB_LAYERS * NR_MAX_NB_LAYERS);
for (int row = 0; row < nl; row++) {
for (int col = 0; col <= row; col++) {
float complex value = A[row][col];
for (int k = 0; k < col; k++)
value -= L[row][k] * conjf(L[col][k]);
if (row == col) {
const float diagonal = crealf(value);
if (!isfinite(diagonal) || diagonal <= 1e-12f)
return false;
L[row][col] = sqrtf(diagonal);
} else {
L[row][col] = value / crealf(L[col][col]);
}
}
}
return true;
}
static void nr_high_rank_cholesky_solve(uint8_t nl,
const float complex L[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS],
const float complex b[NR_MAX_NB_LAYERS],
float complex x[NR_MAX_NB_LAYERS])
{
float complex z[NR_MAX_NB_LAYERS] = {0};
for (int row = 0; row < nl; row++) {
float complex value = b[row];
for (int col = 0; col < row; col++)
value -= L[row][col] * z[col];
z[row] = value / crealf(L[row][row]);
}
for (int row = nl - 1; row >= 0; row--) {
float complex value = z[row];
for (int col = row + 1; col < nl; col++)
value -= conjf(L[col][row]) * x[col];
x[row] = value / crealf(L[row][row]);
}
}
static void nr_high_rank_inverse_diagonal(uint8_t nl,
const float complex L[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS],
float diagonal[NR_MAX_NB_LAYERS])
{
for (int basis = 0; basis < nl; basis++) {
float complex column[NR_MAX_NB_LAYERS] = {0};
for (int row = 0; row < nl; row++) {
float complex value = row == basis ? 1.0f : 0.0f;
for (int col = 0; col < row; col++)
value -= L[row][col] * column[col];
column[row] = value / crealf(L[row][row]);
}
diagonal[basis] = 0.0f;
for (int row = basis; row < nl; row++)
diagonal[basis] += crealf(column[row] * conjf(column[row]));
}
}
/* A permutation-diagonal channel does not need an NxN matrix inversion. This
* is the common RF-simulator case (and also occurs with sufficiently isolated
* physical paths): every layer has one distinct, dominant receive antenna.
* Keep the decision data-driven so correlated channels automatically use the
* full joint detector below. */
static bool nr_high_rank_permutation_diagonal(uint8_t n_rx,
uint8_t nl,
const float complex h[MAX_ANT][NR_MAX_NB_LAYERS],
int dominant_rx[NR_MAX_NB_LAYERS])
{
bool rx_claimed[MAX_ANT] = {false};
for (int layer = 0; layer < nl; layer++) {
float total_power = 0.0f;
float dominant_power = 0.0f;
int dominant = -1;
for (int aarx = 0; aarx < n_rx; aarx++) {
const float power = crealf(h[aarx][layer] * conjf(h[aarx][layer]));
total_power += power;
if (power > dominant_power) {
dominant_power = power;
dominant = aarx;
}
}
if (dominant < 0 || dominant_power <= 1e-12f || rx_claimed[dominant])
return false;
/* Bound the aggregate ignored energy to -20 dB. PDSCH channel
* estimation leaves small fixed-point residue on otherwise diagonal
* RF-simulator/isolated channels; a -30 dB total bound was brittle and
* needlessly selected the full 8x8 solver. At or above one percent,
* retain the exact joint detector. */
const float leakage_power = fmaxf(0.0f, total_power - dominant_power);
if (leakage_power > dominant_power * 1e-2f)
return false;
dominant_rx[layer] = dominant;
rx_claimed[dominant] = true;
}
return true;
}
static void nr_high_rank_equalize_permutation_diagonal(
uint32_t rx_size_symbol,
uint8_t n_rx,
uint8_t nl,
const c16_t rxdataF_ext[][rx_size_symbol],
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
uint8_t symbol,
int first_re,
int group_length,
const float complex h[MAX_ANT][NR_MAX_NB_LAYERS],
const int dominant_rx[NR_MAX_NB_LAYERS])
{
float complex coefficient[NR_MAX_NB_LAYERS] = {0};
for (int layer = 0; layer < nl; layer++) {
const float complex dominant_h = h[dominant_rx[layer]][layer];
const float power = crealf(dominant_h * conjf(dominant_h));
coefficient[layer] = 0.7071067811865475244f * conjf(dominant_h) / power;
}
for (int re = first_re; re < first_re + group_length; re++) {
for (int layer = 0; layer < nl; layer++) {
const c16_t y = rxdataF_ext[dominant_rx[layer]][re];
const float complex detected = coefficient[layer] * ((float)y.r + I * (float)y.i);
const long real = lroundf(crealf(detected));
const long imag = lroundf(cimagf(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);
}
}
}
/* Unitary precoders produce dense effective channel matrices whose columns
* are nevertheless orthogonal. In that case H^H H is diagonal and the MMSE
* solution (after removing its scalar bias) is exactly a normalized matched
* filter; no Cholesky factorization is required. */
static bool nr_high_rank_orthogonal(uint8_t nl,
const float complex gram[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS])
{
for (int row = 0; row < nl; row++) {
const float row_power = crealf(gram[row][row]);
if (!isfinite(row_power) || row_power <= 1e-12f)
return false;
float normalized_interference = 0.0f;
for (int col = 0; col < nl; col++) {
if (col == row)
continue;
const float col_power = crealf(gram[col][col]);
if (!isfinite(col_power) || col_power <= 1e-12f)
return false;
const float cross_power = crealf(gram[row][col] * conjf(gram[row][col]));
normalized_interference += cross_power / (row_power * col_power);
}
/* The normalized matched-filter output sees this sum as residual
* inter-layer interference. A one-percent aggregate limit guarantees
* at least 20 dB spatial isolation; less isolated channels use the full
* Cholesky detector. */
if (!isfinite(normalized_interference) || normalized_interference > 1e-2f)
return false;
}
return true;
}
static void nr_high_rank_log_path_once(unsigned int path, uint8_t nl, int re_group)
{
static bool logged[3] = {false};
if (!__atomic_exchange_n(&logged[path], true, __ATOMIC_RELAXED)) {
static const char *const name[3] = {"permutation-diagonal", "orthogonal", "full-cholesky"};
LOG_I(NR_PHY,
"[HIGH_RANK_PATH] layers=%u re_group=%d detector=%s\n",
nl,
re_group,
name[path]);
}
}
static void nr_high_rank_equalize_orthogonal(
uint32_t rx_size_symbol,
uint8_t n_rx,
uint8_t nl,
const c16_t rxdataF_ext[][rx_size_symbol],
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
uint8_t symbol,
int first_re,
int group_length,
const float complex h[MAX_ANT][NR_MAX_NB_LAYERS],
const float complex gram[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS])
{
float complex coefficient[NR_MAX_NB_LAYERS][MAX_ANT] = {{0}};
for (int layer = 0; layer < nl; layer++) {
const float normalization = 0.7071067811865475244f / crealf(gram[layer][layer]);
for (int aarx = 0; aarx < n_rx; aarx++)
coefficient[layer][aarx] = normalization * conjf(h[aarx][layer]);
}
for (int re = first_re; re < first_re + group_length; re++) {
for (int layer = 0; layer < nl; layer++) {
float complex detected = 0.0f;
for (int aarx = 0; aarx < n_rx; aarx++) {
const c16_t y = rxdataF_ext[aarx][re];
detected += coefficient[layer][aarx] * ((float)y.r + I * (float)y.i);
}
const long real = lroundf(crealf(detected));
const long imag = lroundf(cimagf(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);
}
}
}
static int nr_dlsch_mmse_high_rank_cholesky(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,
int re_group)
{
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 float inverse_q15 = 1.0f / 32768.0f;
const float normalized_noise = (float)noise_var * inverse_q15 * inverse_q15;
for (int first_re = 0; first_re < length; first_re += re_group) {
const int group_length = min(re_group, length - first_re);
const int representative_re = first_re + group_length / 2;
float complex h[MAX_ANT][NR_MAX_NB_LAYERS] = {{0}};
for (int aarx = 0; aarx < n_rx; aarx++) {
for (int layer = 0; layer < nl; layer++) {
const c16_t *ch = (const c16_t *)dl_ch_estimates_ext[layer * n_rx + aarx];
h[aarx][layer] = (float)ch[representative_re].r * inverse_q15
+ I * ((float)ch[representative_re].i * inverse_q15);
}
}
int dominant_rx[NR_MAX_NB_LAYERS] = {0};
if (nr_high_rank_permutation_diagonal(n_rx, nl, h, dominant_rx)) {
nr_high_rank_log_path_once(0, nl, re_group);
nr_high_rank_equalize_permutation_diagonal(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
rxdataF_comp,
symbol,
first_re,
group_length,
h,
dominant_rx);
continue;
}
/* The common identity/permutation-diagonal path needs no Gram matrix.
* Build H^H H only after that data-driven check, avoiding O(N^3) work
* while preserving the exact fallback for coupled channels. */
float complex gram[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS] = {{0}};
for (int row = 0; row < nl; row++)
for (int col = 0; col <= row; col++) {
for (int aarx = 0; aarx < n_rx; aarx++)
gram[row][col] += conjf(h[aarx][row]) * h[aarx][col];
gram[col][row] = conjf(gram[row][col]);
}
if (nr_high_rank_orthogonal(nl, gram)) {
nr_high_rank_log_path_once(1, nl, re_group);
nr_high_rank_equalize_orthogonal(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
rxdataF_comp,
symbol,
first_re,
group_length,
h,
gram);
continue;
}
nr_high_rank_log_path_once(2, nl, re_group);
float trace = 0.0f;
for (int layer = 0; layer < nl; layer++)
trace += crealf(gram[layer][layer]);
const float average_gain = trace / nl;
const float ridge_floor = fmaxf(average_gain * 1e-7f, 1e-12f);
const float ridge = fmaxf(ridge_floor, fminf(normalized_noise, average_gain * 0.25f));
for (int layer = 0; layer < nl; layer++)
gram[layer][layer] += ridge;
float complex L[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS];
if (!nr_high_rank_cholesky(nl, gram, L)) {
LOG_W(NR_PHY,
"Rank-%u Cholesky factorization failed at RE %d; using reference detector\n",
nl,
representative_re);
if (nr_dlsch_mmse_high_rank_reference(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
dl_ch_estimates_ext,
rxdataF_comp,
symbol,
first_re,
group_length,
noise_var)
< 0)
return -1;
continue;
}
/* For the exact one-RE mode, solve A*x=H^H*y directly. This avoids
* constructing all n_rx columns of W just to multiply them by one y. */
if (group_length == 1) {
float complex rhs[NR_MAX_NB_LAYERS] = {0};
float complex detected[NR_MAX_NB_LAYERS] = {0};
for (int layer = 0; layer < nl; layer++)
for (int aarx = 0; aarx < n_rx; aarx++) {
const c16_t y = rxdataF_ext[aarx][first_re];
rhs[layer] += conjf(h[aarx][layer]) * ((float)y.r + I * (float)y.i);
}
nr_high_rank_cholesky_solve(nl, L, rhs, detected);
float inverse_diagonal[NR_MAX_NB_LAYERS] = {0};
nr_high_rank_inverse_diagonal(nl, L, inverse_diagonal);
for (int layer = 0; layer < nl; layer++) {
const float gain = fmaxf(0.05f, 1.0f - ridge * inverse_diagonal[layer]);
detected[layer] *= 0.7071067811865475244f / gain;
const long real = lroundf(crealf(detected[layer]));
const long imag = lroundf(cimagf(detected[layer]));
rxdataF_comp[symbol][layer][0][first_re].r = min(max(real, -32767), 32767);
rxdataF_comp[symbol][layer][0][first_re].i = min(max(imag, -32767), 32767);
}
continue;
}
/* W=(H^H H + lambda I)^-1 H^H. Reusing W across a configurable
* frequency group is an optional approximation; group=1 is exact. */
float complex weights[NR_MAX_NB_LAYERS][MAX_ANT] = {{0}};
for (int aarx = 0; aarx < n_rx; aarx++) {
float complex b[NR_MAX_NB_LAYERS] = {0};
float complex x[NR_MAX_NB_LAYERS] = {0};
for (int layer = 0; layer < nl; layer++)
b[layer] = conjf(h[aarx][layer]);
nr_high_rank_cholesky_solve(nl, L, b, x);
for (int layer = 0; layer < nl; layer++)
weights[layer][aarx] = x[layer];
}
float gain[NR_MAX_NB_LAYERS] = {0};
for (int layer = 0; layer < nl; layer++) {
float complex diagonal = 0.0f;
for (int aarx = 0; aarx < n_rx; aarx++)
diagonal += weights[layer][aarx] * h[aarx][layer];
gain[layer] = fmaxf(0.05f, crealf(diagonal));
}
for (int re = first_re; re < first_re + group_length; re++) {
for (int layer = 0; layer < nl; layer++) {
float complex detected = 0.0f;
for (int aarx = 0; aarx < n_rx; aarx++) {
const c16_t y = rxdataF_ext[aarx][re];
detected += weights[layer][aarx] * ((float)y.r + I * (float)y.i);
}
detected *= 0.7071067811865475244f / gain[layer];
const long real = lroundf(crealf(detected));
const long imag = lroundf(cimagf(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 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)
{
const nr_high_rank_detector_config_t *config = get_high_rank_detector_config();
if (config->detector == NR_HIGH_RANK_REFERENCE)
return nr_dlsch_mmse_high_rank_reference(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
dl_ch_estimates_ext,
rxdataF_comp,
symbol,
0,
length,
noise_var);
return nr_dlsch_mmse_high_rank_cholesky(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
dl_ch_estimates_ext,
rxdataF_comp,
symbol,
length,
noise_var,
config->re_group);
}
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,
const uint8_t mod_order,
const int llrLayerSize,
const int16_t llr_layers[NR_SYMBOLS_PER_SLOT][Nl][llrLayerSize],
const fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
const uint32_t re_len[NR_SYMBOLS_PER_SLOT],
int16_t *llr)
{
const int s0 = dlsch_config->start_symbol;
const int s1 = dlsch_config->number_symbols;
int k = 0;
switch (Nl) {
case 1:
for (int i = s0; i < (s0 + s1); i++) {
memcpy(llr + k, llr_layers[i][0], re_len[i] * mod_order * sizeof(int16_t));
k += re_len[i] * mod_order;
}
break;
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
for (int i = s0; i < (s0 + s1); i++) {
int m = 0;
for (int j = 0; j < re_len[i]; j++) {
for (int l = 0; l < Nl; l++) {
memcpy(llr + k, llr_layers[i][l] + m * mod_order, sizeof(int16_t) * mod_order);
k += mod_order;
// if (i<4) printf("length%d: llr_layers[l%d][m%d]=%d: \n",length,l,m,llr_layers[l][i*mod_order+m]);
}
m++;
}
}
break;
default:
AssertFatal(0, "Not supported number of layers %d\n", Nl);
}
}
/* Computes LLRs from compensated PDSCH signal per OFDM symbol for all layers */
static int nr_dlsch_llr(const NR_UE_DLSCH_t *dlsch,
const int len,
const int rx_size_symbol,
const c16_t dl_ch_mag[rx_size_symbol],
const c16_t dl_ch_magb[rx_size_symbol],
const c16_t dl_ch_magr[rx_size_symbol],
const int nb_antennas_rx,
const c16_t rxdataF_comp[dlsch->cw_info.Nl][nb_antennas_rx][rx_size_symbol],
const int llrSize,
int16_t layer_llr[dlsch->cw_info.Nl][llrSize])
{
switch (dlsch->cw_info.qamModOrder) {
case 2 :
for (int l = 0; l < dlsch->cw_info.Nl; l++)
nr_qpsk_llr(rxdataF_comp[l][0], layer_llr[l], len);
break;
case 4 :
for (int l = 0; l < dlsch->cw_info.Nl; l++)
nr_16qam_llr(rxdataF_comp[l][0], dl_ch_mag, layer_llr[l], len);
break;
case 6 :
for(int l=0; l < dlsch->cw_info.Nl; l++)
nr_64qam_llr(rxdataF_comp[l][0], dl_ch_mag, dl_ch_magb, layer_llr[l], len);
break;
case 8:
for(int l=0; l < dlsch->cw_info.Nl; l++)
nr_256qam_llr(rxdataF_comp[l][0],
dl_ch_mag,
dl_ch_magb,
dl_ch_magr,
layer_llr[l],
len);
break;
default:
AssertFatal(false, "Unknown mod_order!!!!\n");
break;
}
return 0;
}
//==============================================================================================
/* Main Function */
int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t *dlsch,
const freq_alloc_bitmap_t *freq_alloc,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
NR_DL_UE_HARQ_t *dlsch_harq,
unsigned char symbol,
bool first_symbol_flag,
unsigned char harq_pid,
uint32_t pdsch_est_size,
int32_t dl_ch_estimates[][pdsch_est_size],
int16_t *llr,
uint32_t dl_valid_re[NR_SYMBOLS_PER_SLOT],
c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP],
int32_t *log2_maxh,
int rx_size_symbol,
int nbRx,
c16_t rxdataF_comp[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t dl_ch_mag[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magb[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magr[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t ptrs_phase_per_slot[][NR_SYMBOLS_PER_SLOT],
int32_t ptrs_re_per_slot[][NR_SYMBOLS_PER_SLOT],
int G,
uint32_t nvar,
pdsch_scope_req_t *scope_req)
{
NR_DL_FRAME_PARMS *fp = &ue->frame_parms;
const int nl = dlsch->cw_info.Nl;
const int n_rx = fp->nb_antennas_rx;
const int matrixSz = n_rx * nl;
__attribute__((aligned(32))) int32_t dl_ch_estimates_ext[matrixSz][rx_size_symbol];
memset(dl_ch_estimates_ext, 0, sizeof(dl_ch_estimates_ext));
NR_UE_COMMON *common_vars = &ue->common_vars;
const int frame = proc->frame_rx;
const int nr_slot_rx = proc->nr_slot_rx;
const int gNB_id = proc->gNB_id;
uint8_t slot = 0;
uint32_t nb_re_pdsch = -1;
DevAssert(dlsch_harq);
if (gNB_id > 2) {
LOG_E(PHY, "Illegal gNB_id %d\n", gNB_id);
return(-1);
}
if (!common_vars) {
LOG_E(PHY, "dlsch_demodulation.c: Null common_vars\n");
return(-1);
}
if(symbol > fp->symbols_per_slot >> 1)
slot = 1;
uint8_t pilots = (dlsch_config->dlDmrsSymbPos >> symbol) & 1;
uint8_t config_type = dlsch_config->dmrsConfigType;
//----------------------------------------------------------
//--------------------- RBs extraction ---------------------
//----------------------------------------------------------
const bool meas_enabled = cpumeas(CPUMEAS_GETSTATE);
int nb_rb_pdsch = freq_alloc->num_rbs;
{
start_meas_nr_ue_phy(ue, DLSCH_EXTRACT_RBS_STATS);
__attribute__((aligned(32))) c16_t rxdataF_ext[nbRx][rx_size_symbol];
memset(rxdataF_ext, 0, sizeof(rxdataF_ext));
uint32_t csi_res_bitmap = build_csi_overlap_bitmap(dlsch_config, symbol);
LOG_D(PHY, "%d.%d symbol %d csi overlap bitmap %d\n", frame, nr_slot_rx, symbol, csi_res_bitmap);
nr_dlsch_extract_rbs(fp->samples_per_slot_wCP,
rxdataF,
rx_size_symbol,
pdsch_est_size,
dl_ch_estimates,
rxdataF_ext,
dl_ch_estimates_ext,
symbol,
pilots,
dlsch_config,
freq_alloc,
nl,
fp,
csi_res_bitmap,
nl > 4 ? 1 : ue->chest_time);
stop_meas_nr_ue_phy(ue, DLSCH_EXTRACT_RBS_STATS);
if (scope_req->copy_chanest_to_scope) {
size_t size = sizeof(c16_t) * nb_rb_pdsch * NR_NB_SC_PER_RB;
int copy_index = symbol - dlsch_config->start_symbol;
int offset = copy_index * size;
UEscopeCopyUnsafe(ue, pdschChanEstimates, dl_ch_estimates_ext[0], size, offset, copy_index);
}
if (meas_enabled) {
LOG_D(PHY,
"[AbsSFN %u.%d] Slot%d Symbol %d: Pilot/Data extraction %5.2f \n",
frame,
nr_slot_rx,
slot,
symbol,
ue->phy_cpu_stats.cpu_time_stats[DLSCH_EXTRACT_RBS_STATS].p_time / (cpuf * 1000.0));
}
if (ue->phy_sim_pdsch_rxdataF_ext)
memcpy(ue->phy_sim_pdsch_rxdataF_ext + symbol * sizeof(rxdataF_ext), rxdataF_ext, sizeof(rxdataF_ext));
nb_re_pdsch = (pilots == 1) ? ((config_type == NFAPI_NR_DMRS_TYPE1) ? nb_rb_pdsch * (12 - 6 * dlsch_config->n_dmrs_cdm_groups)
: nb_rb_pdsch * (12 - 4 * dlsch_config->n_dmrs_cdm_groups))
: (nb_rb_pdsch * 12);
// Subtract CSI-RS REs from PDSCH RE count
if (csi_res_bitmap != 0) {
uint32_t csi_re_count = 0;
uint32_t csi_res_even = csi_res_bitmap & 0xfff;
uint32_t csi_res_odd = (csi_res_bitmap >> 16) & 0xfff;
uint32_t count_even = count_bits(&csi_res_even, 1);
uint32_t count_odd = count_bits(&csi_res_odd, 1);
int start = freq_alloc->first_rb + dlsch_config->BWPStart;
int end = freq_alloc->last_rb + 1;
for (int rb = start; rb < end; rb++) {
if ((freq_alloc->bitmap[rb / 32] >> (rb % 32)) & 0x01)
csi_re_count += (rb % 2 == 0) ? count_even : count_odd;
}
nb_re_pdsch = (nb_re_pdsch > csi_re_count) ? (nb_re_pdsch - csi_re_count) : 0;
if (csi_re_count > 0) {
LOG_D(NR_PHY,
"[CSI OVERLAP] Frame/Slot %d.%d Symbol %d: CSI-RS overlapping PDSCH - %d CSI-RS REs skipped, %d data REs extracted\n",
frame,
nr_slot_rx,
symbol,
csi_re_count,
nb_re_pdsch);
}
}
if (scope_req->copy_rxdataF_to_scope) {
size_t size = sizeof(c16_t) * nb_re_pdsch;
int copy_index = symbol - dlsch_config->start_symbol;
UEscopeCopyUnsafe(ue, pdschRxdataF, rxdataF_ext[0], size, scope_req->scope_rxdataF_offset, copy_index);
scope_req->scope_rxdataF_offset += size;
}
//----------------------------------------------------------
//--------------------- Channel Scaling --------------------
//----------------------------------------------------------
start_meas_nr_ue_phy(ue, DLSCH_CHANNEL_SCALE_STATS);
nr_scale_channel(rx_size_symbol, dl_ch_estimates_ext, 0, nb_re_pdsch, nl, n_rx, 0);
stop_meas_nr_ue_phy(ue, DLSCH_CHANNEL_SCALE_STATS);
if (meas_enabled) {
LOG_D(PHY,
"[AbsSFN %u.%d] Slot%d Symbol %d: Channel Scale %5.2f \n",
frame,
nr_slot_rx,
slot,
symbol,
ue->phy_cpu_stats.cpu_time_stats[DLSCH_CHANNEL_SCALE_STATS].p_time / (cpuf * 1000.0));
}
//----------------------------------------------------------
//--------------------- Channel Level Calc. ----------------
//----------------------------------------------------------
start_meas_nr_ue_phy(ue, DLSCH_CHANNEL_LEVEL_STATS);
if (first_symbol_flag) {
int32_t avg[nl * n_rx];
if (nb_re_pdsch)
nr_channel_level(0, rx_size_symbol, (c16_t (*)[rx_size_symbol])dl_ch_estimates_ext, n_rx, nl, avg, nb_re_pdsch);
else
LOG_E(NR_PHY, "Average channel level is 0: nb_rb_pdsch = %d, nb_re_pdsch = %d\n", nb_rb_pdsch, nb_re_pdsch);
int avgs = 0;
int32_t median[MAX_ANT][MAX_ANT];
for (int l = 0; l < nl; l++)
for (int aarx = 0; aarx < n_rx; aarx++) {
avgs = cmax(avgs, avg[l * n_rx + aarx]);
LOG_D(PHY, "nb_rb %d avg_%d_%d Power per SC is %d\n", nb_rb_pdsch, aarx, l, avg[l * n_rx + aarx]);
LOG_D(PHY, "avgs Power per SC is %d\n", avgs);
median[l][aarx] = avg[l * n_rx + aarx];
}
if (nl > 1) {
nr_dlsch_channel_level_median(rx_size_symbol, dl_ch_estimates_ext, median, nl, n_rx, nb_re_pdsch);
for (int l = 0; l < nl; l++) {
for (int aarx = 0; aarx < n_rx; aarx++) {
avgs = cmax(avgs, median[l][aarx]);
}
}
}
*log2_maxh = (log2_approx(avgs) / 2) + 1;
LOG_D(PHY, "[DLSCH] AbsSubframe %d.%d log2_maxh = %d (%d)\n", frame % 1024, nr_slot_rx, *log2_maxh, avgs);
#if T_TRACER
T(T_UE_PHY_PDSCH_ENERGY,
T_INT(gNB_id),
T_INT(frame % 1024),
T_INT(nr_slot_rx),
T_INT(avg[0]), // layer 0, antenna 0
T_INT(n_rx > 1 ? avg[1] : 0), // layer 0, antenna 1
T_INT(nl > 1 ? avg[n_rx] : 0), // layer 1, antenna 0
T_INT(nl > 1 && n_rx > 1 ? avg[n_rx + 1] : 0)); // layer 1, antenna 1
#endif
}
stop_meas_nr_ue_phy(ue, DLSCH_CHANNEL_LEVEL_STATS);
if (meas_enabled) {
LOG_D(PHY,
"[AbsSFN %u.%d] Slot%d Symbol %d first_symbol_flag %d: Channel Level %5.2f \n",
frame,
nr_slot_rx,
slot,
symbol,
first_symbol_flag,
ue->phy_cpu_stats.cpu_time_stats[DLSCH_CHANNEL_LEVEL_STATS].p_time / (cpuf * 1000.0));
}
//----------------------------------------------------------
//--------------------- channel compensation ---------------
//----------------------------------------------------------
// Disable correlation measurement for optimizing UE
start_meas_nr_ue_phy(ue, DLSCH_CHANNEL_COMPENSATION_STATS);
if (nl > 4) {
if (nb_re_pdsch
&& nr_dlsch_mmse_high_rank(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
dl_ch_estimates_ext,
rxdataF_comp,
symbol,
nb_re_pdsch,
nvar)
< 0)
return -1;
set_high_rank_qam_thresholds(dlsch->cw_info.qamModOrder,
nb_re_pdsch,
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);
if (meas_enabled) {
LOG_D(PHY,
"[AbsSFN %u.%d] Slot%d Symbol %d log2_maxh %d Channel Comp %5.2f \n",
frame,
nr_slot_rx,
slot,
symbol,
*log2_maxh,
ue->phy_cpu_stats.cpu_time_stats[DLSCH_CHANNEL_COMPENSATION_STATS].p_time / (cpuf * 1000.0));
}
// Please keep it: useful for debugging
#ifdef DEBUG_PDSCH_RX
char filename[50];
snprintf(filename, 50, "rxdataF0_symb_%d_nr_slot_rx_%d.m", symbol, nr_slot_rx);
write_output(filename, "rxdataF0", &rxdataF[0][symbol * fp->ofdm_symbol_size], fp->ofdm_symbol_size, 1, 1);
snprintf(filename, 50, "dl_ch_estimates0_symb_%d_nr_slot_rx_%d.m", symbol, nr_slot_rx);
write_output(filename, "dl_ch_estimates0", &dl_ch_estimates[0][symbol * fp->ofdm_symbol_size], fp->ofdm_symbol_size, 1, 1);
snprintf(filename, 50, "rxdataF_ext0_symb_%d_nr_slot_rx_%d.m", symbol, nr_slot_rx);
write_output(filename, "rxdataF_ext0", &rxdataF_ext[0][0], rx_size_symbol, 1, 1);
snprintf(filename, 50, "dl_ch_estimates_ext0_symb_%d_nr_slot_rx_%d.m", symbol, nr_slot_rx);
write_output(filename, "dl_ch_estimates_ext0", &dl_ch_estimates_ext[0][0], rx_size_symbol, 1, 1);
snprintf(filename, 50, "rxdataF_comp00_symb_%d_nr_slot_rx_%d.m", symbol, nr_slot_rx);
write_output(filename, "rxdataF_comp00", &rxdataF_comp[0][0][symbol * rx_size_symbol], rx_size_symbol, 1, 1);
#endif
}
start_meas_nr_ue_phy(ue, DLSCH_MRC_MMSE_STATS);
if (nl <= 4 && n_rx > 1) {
nr_dlsch_detection_mrc(rx_size_symbol,
nl,
n_rx,
rxdataF_comp,
NULL,
dl_ch_mag[symbol],
dl_ch_magb[symbol],
dl_ch_magr[symbol],
symbol,
nb_re_pdsch);
if (nl >= 2) // Apply MMSE for 2, 3, and 4 Tx layers
if (nb_re_pdsch)
nr_dlsch_mmse(rx_size_symbol,
n_rx,
nl,
rxdataF_comp,
dl_ch_mag[symbol],
dl_ch_magb[symbol],
dl_ch_magr[symbol],
dl_ch_estimates_ext,
dlsch->cw_info.qamModOrder,
*log2_maxh,
symbol,
nb_re_pdsch,
nvar);
}
stop_meas_nr_ue_phy(ue, DLSCH_MRC_MMSE_STATS);
if (meas_enabled) {
LOG_D(PHY,
"[AbsSFN %u.%d] Slot%d Symbol %d: Channel Combine and MMSE %5.2f \n",
frame,
nr_slot_rx,
slot,
symbol,
ue->phy_cpu_stats.cpu_time_stats[DLSCH_MRC_MMSE_STATS].p_time / (cpuf * 1000.0));
}
/* Store the valid DL RE's */
dl_valid_re[symbol] = nb_re_pdsch;
int startSymbIdx = 0;
int nbSymb = 0;
int pduBitmap = 0;
if(dlsch_harq->status == NR_ACTIVE) {
startSymbIdx = dlsch_config->start_symbol;
nbSymb = dlsch_config->number_symbols;
pduBitmap = dlsch_config->pduBitmap;
}
/* Check for PTRS bitmap and process it respectively */
if((pduBitmap & 0x1) && (dlsch->rnti_type == TYPE_C_RNTI_)) {
nr_pdsch_ptrs_processing(nbRx,
ptrs_phase_per_slot,
ptrs_re_per_slot,
rx_size_symbol,
nl,
rxdataF_comp,
fp,
dlsch_config,
nr_slot_rx,
symbol,
freq_alloc->num_rbs,
dlsch->rnti,
&dlsch->ptrs_symbols,
&dlsch->ptrs_symbol_index);
dl_valid_re[symbol] -= ptrs_re_per_slot[0][symbol];
}
/* at last symbol in a slot calculate LLR's for whole slot */
if (symbol == (startSymbIdx + nbSymb - 1)) {
/* create LLR layer buffer */
int max_symb_re = 0;
GET_ARRAY_MAX(dl_valid_re, NR_SYMBOLS_PER_SLOT, max_symb_re);
const int llr_per_symbol = max_symb_re * dlsch->cw_info.qamModOrder;
__attribute__((aligned(32))) int16_t layer_llr[NR_SYMBOLS_PER_SLOT][nl][llr_per_symbol];
// Generate LLR from PTRS compensated signal
start_meas_nr_ue_phy(ue, DLSCH_LLR_STATS);
for (int llr_sym = startSymbIdx; llr_sym < startSymbIdx + nbSymb; llr_sym++) {
nr_dlsch_llr(dlsch,
dl_valid_re[llr_sym],
rx_size_symbol,
dl_ch_mag[llr_sym][0][0],
dl_ch_magb[llr_sym][0][0],
dl_ch_magr[llr_sym][0][0],
n_rx,
rxdataF_comp[llr_sym],
llr_per_symbol,
layer_llr[llr_sym]);
}
stop_meas_nr_ue_phy(ue, DLSCH_LLR_STATS);
start_meas_nr_ue_phy(ue, DLSCH_LAYER_DEMAPPING);
nr_dlsch_layer_demapping(nl, dlsch->cw_info.qamModOrder, llr_per_symbol, layer_llr, dlsch_config, dl_valid_re, llr);
stop_meas_nr_ue_phy(ue, DLSCH_LAYER_DEMAPPING);
if (UEScopeHasTryLock(ue)) {
metadata mt = {.frame = proc->frame_rx, .slot = proc->nr_slot_rx };
int total_valid_res = 0;
for (int i = startSymbIdx; i < startSymbIdx + nbSymb; i++) {
total_valid_res += dl_valid_re[i];
}
if (UETryLockScopeData(ue, pdschRxdataF_comp, sizeof(c16_t), 1, total_valid_res, &mt)) {
size_t offset = 0;
for (int i = startSymbIdx; i < startSymbIdx + nbSymb; i++) {
size_t data_size = sizeof(c16_t) * dl_valid_re[i];
UEscopeCopyUnsafe(ue, pdschRxdataF_comp, &rxdataF_comp[i][0][0][0], data_size, offset, i);
offset += data_size;
}
UEunlockScopeData(ue, pdschRxdataF_comp)
}
} else {
UEscopeCopy(ue, pdschRxdataF_comp, rxdataF_comp[0], sizeof(c16_t), nbRx, rx_size_symbol, 0);
}
}
if (meas_enabled) {
LOG_D(PHY,
"[AbsSFN %u.%d] Slot%d Symbol %d: LLR Computation %5.2f \n",
frame,
nr_slot_rx,
slot,
symbol,
ue->phy_cpu_stats.cpu_time_stats[DLSCH_LLR_STATS].p_time / (cpuf * 1000.0));
}
#if T_TRACER
T(T_UE_PHY_PDSCH_IQ,
T_INT(gNB_id),
T_INT(frame % 1024),
T_INT(nr_slot_rx),
T_INT(nb_rb_pdsch),
T_INT(fp->N_RB_UL),
T_INT(fp->symbols_per_slot),
T_BUFFER(&rxdataF_comp[gNB_id][0], 2 * fp->N_RB_UL * 12 * fp->symbols_per_slot * 2));
#endif
if (ue->phy_sim_pdsch_rxdataF_comp)
for (int a = 0; a < nbRx; a++) {
for (int l = 0; l < nl; l++) {
int offset = (void *)rxdataF_comp[symbol][l][a] - (void *)rxdataF_comp[0];
memcpy(ue->phy_sim_pdsch_rxdataF_comp + offset, rxdataF_comp[symbol][l][a], sizeof(c16_t) * rx_size_symbol);
}
memcpy((c16_t *)ue->phy_sim_pdsch_dl_ch_estimates + pdsch_est_size * a, dl_ch_estimates, pdsch_est_size * sizeof(c16_t));
}
if (ue->phy_sim_pdsch_dl_ch_estimates_ext)
memcpy(ue->phy_sim_pdsch_dl_ch_estimates_ext + symbol * sizeof(dl_ch_estimates_ext),
dl_ch_estimates_ext,
sizeof(dl_ch_estimates_ext));
return 0;
}