|
|
|
@@ -32,6 +32,7 @@
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
|
|
#include "executables/nr-softmodem-common.h"
|
|
|
|
|
#include "nr_transport_proto_ue.h"
|
|
|
|
@@ -118,6 +119,100 @@ void nr_numer_2x2(int32_t *a_00_sq,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute eigenvalues of an NxN Hermitian matrix (N <= 4) using the Jacobi method.
|
|
|
|
|
// The matrix is provided as separate real and imaginary float arrays.
|
|
|
|
|
// Eigenvalues are returned sorted in descending order.
|
|
|
|
|
static void nr_hermitian_eigenvalues(const int N, float A_re[4][4], float A_im[4][4], float eigenvalues[4])
|
|
|
|
|
{
|
|
|
|
|
for (int sweep = 0; sweep < 30; sweep++) {
|
|
|
|
|
float off_norm = 0;
|
|
|
|
|
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)
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// Phase elimination: make A[p][q] real by rotating column/row q
|
|
|
|
|
float cos_phi = re_pq / mag;
|
|
|
|
|
float sin_phi = im_pq / mag;
|
|
|
|
|
|
|
|
|
|
// Multiply column q by e^{-j*phi} and row q by e^{+j*phi}
|
|
|
|
|
for (int k = 0; k < N; k++) {
|
|
|
|
|
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];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now A[p][q] is real and positive (= mag). Apply 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;
|
|
|
|
|
|
|
|
|
|
// Update diagonal
|
|
|
|
|
A_re[p][p] -= t * mag;
|
|
|
|
|
A_re[q][q] += t * mag;
|
|
|
|
|
|
|
|
|
|
// Zero out (p,q) and (q,p)
|
|
|
|
|
A_re[p][q] = 0;
|
|
|
|
|
A_im[p][q] = 0;
|
|
|
|
|
A_re[q][p] = 0;
|
|
|
|
|
A_im[q][p] = 0;
|
|
|
|
|
|
|
|
|
|
// Update remaining off-diagonal elements
|
|
|
|
|
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];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < N; i++)
|
|
|
|
|
eigenvalues[i] = A_re[i][i];
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool is_csi_rs_in_symbol(const fapi_nr_dl_config_csirs_pdu_rel15_t csirs_config_pdu, const int symbol) {
|
|
|
|
|
|
|
|
|
|
bool ret = false;
|
|
|
|
@@ -450,25 +545,37 @@ int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
|
|
|
|
|
|
|
|
|
|
if (ue->frame_parms.nb_antennas_rx == 1 || N_ports == 1) {
|
|
|
|
|
return 0;
|
|
|
|
|
} else if( !(ue->frame_parms.nb_antennas_rx == 2 && N_ports == 2) ) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int max_rank = N_ports < frame_parms->nb_antennas_rx ? N_ports : frame_parms->nb_antennas_rx;
|
|
|
|
|
if (max_rank < 2) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (N_ports > 4 || frame_parms->nb_antennas_rx > 4) {
|
|
|
|
|
LOG_W(NR_PHY, "Rank indicator computation is not implemented for %i x %i system\n",
|
|
|
|
|
ue->frame_parms.nb_antennas_rx, N_ports);
|
|
|
|
|
frame_parms->nb_antennas_rx, N_ports);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Example 2x2: Hh x H =
|
|
|
|
|
* | 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 |
|
|
|
|
|
*/
|
|
|
|
|
/* Hh x H = Gram matrix computation (general for any N_ports x nb_antennas_rx) */
|
|
|
|
|
|
|
|
|
|
c16_t csi_rs_estimated_conjch_ch[frame_parms->nb_antennas_rx][N_ports][frame_parms->nb_antennas_rx][N_ports]
|
|
|
|
|
[frame_parms->ofdm_symbol_size + FILTER_MARGIN] __attribute__((aligned(32)));
|
|
|
|
|
int32_t csi_rs_estimated_A_MF[N_ports][N_ports][frame_parms->ofdm_symbol_size + FILTER_MARGIN] __attribute__((aligned(32)));
|
|
|
|
|
|
|
|
|
|
// Arrays only needed for the 2x2 fast path
|
|
|
|
|
int32_t csi_rs_estimated_A_MF_sq[N_ports][N_ports][frame_parms->ofdm_symbol_size + FILTER_MARGIN] __attribute__((aligned(32)));
|
|
|
|
|
int32_t csi_rs_estimated_determ_fin[frame_parms->ofdm_symbol_size + FILTER_MARGIN] __attribute__((aligned(32)));
|
|
|
|
|
int32_t csi_rs_estimated_numer_fin[frame_parms->ofdm_symbol_size + FILTER_MARGIN] __attribute__((aligned(32)));
|
|
|
|
|
|
|
|
|
|
// For 4x4: eigenvalue-based rank voting. rank_count[r] = number of subcarriers supporting rank >= r+1
|
|
|
|
|
int rank_count[4] = {0};
|
|
|
|
|
// For 2x2: keep existing condition number approach
|
|
|
|
|
const uint8_t sum_shift = 1; // log2(2x2) = 2, which is a shift of 1 bit
|
|
|
|
|
|
|
|
|
|
// Eigenvalue ratio threshold in dB (approx. equivalent to cond_dB_threshold=5 for 2x2)
|
|
|
|
|
const float eig_ratio_dB_threshold = 8.0f;
|
|
|
|
|
|
|
|
|
|
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)) {
|
|
|
|
@@ -477,6 +584,7 @@ int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
|
|
|
|
|
uint16_t k = (frame_parms->first_carrier_offset + rb*NR_NB_SC_PER_RB) % frame_parms->ofdm_symbol_size;
|
|
|
|
|
uint16_t k_offset = k + mem_offset;
|
|
|
|
|
|
|
|
|
|
// Compute Gram matrix A = H^H * H (general for any N_ports)
|
|
|
|
|
for (int ant_rx_conjch = 0; ant_rx_conjch < frame_parms->nb_antennas_rx; ant_rx_conjch++) {
|
|
|
|
|
for(uint16_t port_tx_conjch = 0; port_tx_conjch < N_ports; port_tx_conjch++) {
|
|
|
|
|
for (int ant_rx_ch = 0; ant_rx_ch < frame_parms->nb_antennas_rx; ant_rx_ch++) {
|
|
|
|
@@ -499,68 +607,108 @@ int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// compute the determinant of A_MF (denominator)
|
|
|
|
|
nr_det_A_MF_2x2(&csi_rs_estimated_A_MF[0][0][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF[0][1][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF[1][0][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF[1][1][k_offset],
|
|
|
|
|
&csi_rs_estimated_determ_fin[k_offset],
|
|
|
|
|
1);
|
|
|
|
|
if (N_ports == 2) {
|
|
|
|
|
// ---- 2x2 fast path: existing SIMD-based condition number approach ----
|
|
|
|
|
nr_det_A_MF_2x2(&csi_rs_estimated_A_MF[0][0][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF[0][1][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF[1][0][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF[1][1][k_offset],
|
|
|
|
|
&csi_rs_estimated_determ_fin[k_offset],
|
|
|
|
|
1);
|
|
|
|
|
|
|
|
|
|
// compute the square of A_MF (numerator)
|
|
|
|
|
nr_squared_matrix_element(&csi_rs_estimated_A_MF[0][0][k_offset], &csi_rs_estimated_A_MF_sq[0][0][k_offset], 1);
|
|
|
|
|
nr_squared_matrix_element(&csi_rs_estimated_A_MF[0][1][k_offset], &csi_rs_estimated_A_MF_sq[0][1][k_offset], 1);
|
|
|
|
|
nr_squared_matrix_element(&csi_rs_estimated_A_MF[1][0][k_offset], &csi_rs_estimated_A_MF_sq[1][0][k_offset], 1);
|
|
|
|
|
nr_squared_matrix_element(&csi_rs_estimated_A_MF[1][1][k_offset], &csi_rs_estimated_A_MF_sq[1][1][k_offset], 1);
|
|
|
|
|
nr_numer_2x2(&csi_rs_estimated_A_MF_sq[0][0][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF_sq[0][1][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF_sq[1][0][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF_sq[1][1][k_offset],
|
|
|
|
|
&csi_rs_estimated_numer_fin[k_offset],
|
|
|
|
|
1);
|
|
|
|
|
nr_squared_matrix_element(&csi_rs_estimated_A_MF[0][0][k_offset], &csi_rs_estimated_A_MF_sq[0][0][k_offset], 1);
|
|
|
|
|
nr_squared_matrix_element(&csi_rs_estimated_A_MF[0][1][k_offset], &csi_rs_estimated_A_MF_sq[0][1][k_offset], 1);
|
|
|
|
|
nr_squared_matrix_element(&csi_rs_estimated_A_MF[1][0][k_offset], &csi_rs_estimated_A_MF_sq[1][0][k_offset], 1);
|
|
|
|
|
nr_squared_matrix_element(&csi_rs_estimated_A_MF[1][1][k_offset], &csi_rs_estimated_A_MF_sq[1][1][k_offset], 1);
|
|
|
|
|
nr_numer_2x2(&csi_rs_estimated_A_MF_sq[0][0][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF_sq[0][1][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF_sq[1][0][k_offset],
|
|
|
|
|
&csi_rs_estimated_A_MF_sq[1][1][k_offset],
|
|
|
|
|
&csi_rs_estimated_numer_fin[k_offset],
|
|
|
|
|
1);
|
|
|
|
|
|
|
|
|
|
#ifdef NR_CSIRS_DEBUG
|
|
|
|
|
for(uint16_t port_tx_conjch = 0; port_tx_conjch < N_ports; port_tx_conjch++) {
|
|
|
|
|
for(uint16_t port_tx_ch = 0; port_tx_ch < N_ports; port_tx_ch++) {
|
|
|
|
|
c16_t *csi_rs_estimated_A_MF_k = (c16_t *) &csi_rs_estimated_A_MF[port_tx_conjch][port_tx_ch][k_offset];
|
|
|
|
|
LOG_I(NR_PHY, "(%i) csi_rs_estimated_A_MF[%i][%i] = (%i, %i)\n",
|
|
|
|
|
k, port_tx_conjch, port_tx_ch, csi_rs_estimated_A_MF_k->r, csi_rs_estimated_A_MF_k->i);
|
|
|
|
|
c16_t *csi_rs_estimated_A_MF_sq_k = (c16_t *) &csi_rs_estimated_A_MF_sq[port_tx_conjch][port_tx_ch][k_offset];
|
|
|
|
|
LOG_I(NR_PHY, "(%i) csi_rs_estimated_A_MF_sq[%i][%i] = (%i, %i)\n",
|
|
|
|
|
k, port_tx_conjch, port_tx_ch, csi_rs_estimated_A_MF_sq_k->r, csi_rs_estimated_A_MF_sq_k->i);
|
|
|
|
|
for(uint16_t port_tx_conjch = 0; port_tx_conjch < N_ports; port_tx_conjch++) {
|
|
|
|
|
for(uint16_t port_tx_ch = 0; port_tx_ch < N_ports; port_tx_ch++) {
|
|
|
|
|
c16_t *csi_rs_estimated_A_MF_k = (c16_t *) &csi_rs_estimated_A_MF[port_tx_conjch][port_tx_ch][k_offset];
|
|
|
|
|
LOG_I(NR_PHY, "(%i) csi_rs_estimated_A_MF[%i][%i] = (%i, %i)\n",
|
|
|
|
|
k, port_tx_conjch, port_tx_ch, csi_rs_estimated_A_MF_k->r, csi_rs_estimated_A_MF_k->i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
LOG_I(NR_PHY, "(%i) csi_rs_estimated_determ_fin = %i\n", k, csi_rs_estimated_determ_fin[k_offset]);
|
|
|
|
|
LOG_I(NR_PHY, "(%i) csi_rs_estimated_numer_fin = %i\n", k, csi_rs_estimated_numer_fin[k_offset]>>sum_shift);
|
|
|
|
|
LOG_I(NR_PHY, "(%i) csi_rs_estimated_determ_fin = %i\n", k, csi_rs_estimated_determ_fin[k_offset]);
|
|
|
|
|
LOG_I(NR_PHY, "(%i) csi_rs_estimated_numer_fin = %i\n", k, csi_rs_estimated_numer_fin[k_offset]>>sum_shift);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// compute the conditional number
|
|
|
|
|
for (int sc_idx=0; sc_idx < NR_NB_SC_PER_RB; sc_idx++) {
|
|
|
|
|
int8_t csi_rs_estimated_denum_db = dB_fixed(csi_rs_estimated_determ_fin[k_offset + sc_idx]);
|
|
|
|
|
int8_t csi_rs_estimated_numer_db = dB_fixed(csi_rs_estimated_numer_fin[k_offset + sc_idx]>>sum_shift);
|
|
|
|
|
int8_t cond_db = csi_rs_estimated_numer_db - csi_rs_estimated_denum_db;
|
|
|
|
|
for (int sc_idx=0; sc_idx < NR_NB_SC_PER_RB; sc_idx++) {
|
|
|
|
|
int8_t csi_rs_estimated_denum_db = dB_fixed(csi_rs_estimated_determ_fin[k_offset + sc_idx]);
|
|
|
|
|
int8_t csi_rs_estimated_numer_db = dB_fixed(csi_rs_estimated_numer_fin[k_offset + sc_idx]>>sum_shift);
|
|
|
|
|
int8_t cond_db = csi_rs_estimated_numer_db - csi_rs_estimated_denum_db;
|
|
|
|
|
|
|
|
|
|
if (cond_db < cond_dB_threshold) {
|
|
|
|
|
count++;
|
|
|
|
|
} else {
|
|
|
|
|
count--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// ---- General path (N_ports > 2): eigenvalue-based rank estimation ----
|
|
|
|
|
for (int sc_idx = 0; sc_idx < NR_NB_SC_PER_RB; sc_idx++) {
|
|
|
|
|
float A_re[4][4] = {{0}}, A_im[4][4] = {{0}};
|
|
|
|
|
for (int i = 0; i < N_ports; i++) {
|
|
|
|
|
for (int j = 0; j < N_ports; j++) {
|
|
|
|
|
c16_t val = *((c16_t *)&csi_rs_estimated_A_MF[i][j][k_offset + sc_idx]);
|
|
|
|
|
A_re[i][j] = (float)val.r;
|
|
|
|
|
A_im[i][j] = (float)val.i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float eig[4] = {0};
|
|
|
|
|
nr_hermitian_eigenvalues(N_ports, A_re, A_im, eig);
|
|
|
|
|
|
|
|
|
|
if (eig[0] < 1e-6f)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Determine max supported rank at this subcarrier
|
|
|
|
|
rank_count[0]++; // at least rank 1
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef NR_CSIRS_DEBUG
|
|
|
|
|
LOG_I(NR_PHY, "csi_rs_estimated_denum_db = %i\n", csi_rs_estimated_denum_db);
|
|
|
|
|
LOG_I(NR_PHY, "csi_rs_estimated_numer_db = %i\n", csi_rs_estimated_numer_db);
|
|
|
|
|
LOG_I(NR_PHY, "cond_db = %i\n", cond_db);
|
|
|
|
|
LOG_I(NR_PHY, "(%i) sc %d eigenvalues: %.1f %.1f %.1f %.1f\n",
|
|
|
|
|
k, sc_idx, eig[0], eig[1], eig[2], eig[3]);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (cond_db < cond_dB_threshold) {
|
|
|
|
|
count++;
|
|
|
|
|
} else {
|
|
|
|
|
count--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// conditional number is lower than cond_dB_threshold in half on more REs
|
|
|
|
|
if (count > 0) {
|
|
|
|
|
*rank_indicator = 1;
|
|
|
|
|
if (N_ports == 2) {
|
|
|
|
|
// 2x2 decision: existing logic
|
|
|
|
|
if (count > 0) {
|
|
|
|
|
*rank_indicator = 1;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// General decision: choose highest rank supported by majority of subcarriers
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef NR_CSIRS_DEBUG
|
|
|
|
|
LOG_I(NR_PHY, "count = %i\n", count);
|
|
|
|
|
if (N_ports == 2)
|
|
|
|
|
LOG_I(NR_PHY, "count = %i\n", count);
|
|
|
|
|
else
|
|
|
|
|
LOG_I(NR_PHY, "rank_count = [%i, %i, %i, %i]\n", rank_count[0], rank_count[1], rank_count[2], rank_count[3]);
|
|
|
|
|
LOG_I(NR_PHY, "rank = %i\n", (*rank_indicator)+1);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
@@ -621,70 +769,212 @@ int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(rank_indicator == 0 || rank_indicator == 1) {
|
|
|
|
|
c64_t sum[4] = {0};
|
|
|
|
|
c64_t sum2[4] = {0};
|
|
|
|
|
int64_t tested_precoded_sinr[4] = {0};
|
|
|
|
|
if (N_ports == 2) {
|
|
|
|
|
// ---- 2-port PMI: TS 38.214 Table 5.2.2.2.1-1 ----
|
|
|
|
|
if(rank_indicator == 0 || rank_indicator == 1) {
|
|
|
|
|
c64_t sum[4] = {0};
|
|
|
|
|
c64_t sum2[4] = {0};
|
|
|
|
|
int64_t tested_precoded_sinr[4] = {0};
|
|
|
|
|
|
|
|
|
|
for (int rb = csirs_config_pdu->start_rb; rb < (csirs_config_pdu->start_rb+csirs_config_pdu->nr_of_rbs); rb++) {
|
|
|
|
|
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 = (frame_parms->first_carrier_offset + rb * NR_NB_SC_PER_RB) % frame_parms->ofdm_symbol_size;
|
|
|
|
|
uint16_t k_offset = k + mem_offset;
|
|
|
|
|
for (int ant_rx = 0; ant_rx < frame_parms->nb_antennas_rx; ant_rx++) {
|
|
|
|
|
const c16_t p0 = csi_rs_estimated_channel_freq[ant_rx][0][k_offset];
|
|
|
|
|
const c16_t p1 = csi_rs_estimated_channel_freq[ant_rx][1][k_offset];
|
|
|
|
|
if (csirs_config_pdu->freq_density <= 1 && csirs_config_pdu->freq_density != (rb % 2)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
uint16_t k = (frame_parms->first_carrier_offset + rb * NR_NB_SC_PER_RB) % frame_parms->ofdm_symbol_size;
|
|
|
|
|
uint16_t k_offset = k + mem_offset;
|
|
|
|
|
for (int ant_rx = 0; ant_rx < frame_parms->nb_antennas_rx; ant_rx++) {
|
|
|
|
|
const c16_t p0 = csi_rs_estimated_channel_freq[ant_rx][0][k_offset];
|
|
|
|
|
const c16_t p1 = csi_rs_estimated_channel_freq[ant_rx][1][k_offset];
|
|
|
|
|
|
|
|
|
|
// H_p0 + 1*H_p1 = (H_p0_re + H_p1_re) + 1j*(H_p0_im + H_p1_im)
|
|
|
|
|
sum[0].r += (p0.r + p1.r);
|
|
|
|
|
sum[0].i += (p0.i + p1.i);
|
|
|
|
|
sum2[0].r += (sum[0].r * sum[0].r) >> log2_re;
|
|
|
|
|
sum2[0].i += (sum[0].i * sum[0].i) >> log2_re;
|
|
|
|
|
// H_p0 + 1*H_p1 = (H_p0_re + H_p1_re) + 1j*(H_p0_im + H_p1_im)
|
|
|
|
|
sum[0].r += (p0.r + p1.r);
|
|
|
|
|
sum[0].i += (p0.i + p1.i);
|
|
|
|
|
sum2[0].r += (sum[0].r * sum[0].r) >> log2_re;
|
|
|
|
|
sum2[0].i += (sum[0].i * sum[0].i) >> log2_re;
|
|
|
|
|
|
|
|
|
|
// H_p0 + 1j*H_p1 = (H_p0_re - H_p1_im) + 1j*(H_p0_im + H_p1_re)
|
|
|
|
|
sum[1].r += (p0.r - p1.i);
|
|
|
|
|
sum[1].i += (p0.i + p1.r);
|
|
|
|
|
sum2[1].r += (sum[1].r * sum[1].r) >> log2_re;
|
|
|
|
|
sum2[1].i += (sum[1].i * sum[1].i) >> log2_re;
|
|
|
|
|
// H_p0 + 1j*H_p1 = (H_p0_re - H_p1_im) + 1j*(H_p0_im + H_p1_re)
|
|
|
|
|
sum[1].r += (p0.r - p1.i);
|
|
|
|
|
sum[1].i += (p0.i + p1.r);
|
|
|
|
|
sum2[1].r += (sum[1].r * sum[1].r) >> log2_re;
|
|
|
|
|
sum2[1].i += (sum[1].i * sum[1].i) >> log2_re;
|
|
|
|
|
|
|
|
|
|
// H_p0 - 1*H_p1 = (H_p0_re - H_p1_re) + 1j*(H_p0_im - H_p1_im)
|
|
|
|
|
sum[2].r += (p0.r - p1.r);
|
|
|
|
|
sum[2].i += (p0.i - p1.i);
|
|
|
|
|
sum2[2].r += (sum[2].r * sum[2].r) >> log2_re;
|
|
|
|
|
sum2[2].i += (sum[2].i * sum[2].i) >> log2_re;
|
|
|
|
|
// H_p0 - 1*H_p1 = (H_p0_re - H_p1_re) + 1j*(H_p0_im - H_p1_im)
|
|
|
|
|
sum[2].r += (p0.r - p1.r);
|
|
|
|
|
sum[2].i += (p0.i - p1.i);
|
|
|
|
|
sum2[2].r += (sum[2].r * sum[2].r) >> log2_re;
|
|
|
|
|
sum2[2].i += (sum[2].i * sum[2].i) >> log2_re;
|
|
|
|
|
|
|
|
|
|
// H_p0 - 1j*H_p1 = (H_p0_re + H_p1_im) + 1j*(H_p0_im - H_p1_re)
|
|
|
|
|
sum[3].r += (p0.r + p1.i);
|
|
|
|
|
sum[3].i += (p0.i - p1.r);
|
|
|
|
|
sum2[3].r += (sum[3].r * sum[3].r) >> log2_re;
|
|
|
|
|
sum2[3].i += (sum[3].i * sum[3].i) >> log2_re;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We should perform >>nr_csi_info->log2_re here for all terms, but since sum2_re and sum2_im can be high values,
|
|
|
|
|
// we performed this above.
|
|
|
|
|
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_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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(rank_indicator == 0) {
|
|
|
|
|
for(int tested_i2 = 0; tested_i2 < 4; tested_i2++) {
|
|
|
|
|
if(tested_precoded_sinr[tested_i2] > tested_precoded_sinr[i2[0]]) {
|
|
|
|
|
i2[0] = tested_i2;
|
|
|
|
|
// H_p0 - 1j*H_p1 = (H_p0_re + H_p1_im) + 1j*(H_p0_im - H_p1_re)
|
|
|
|
|
sum[3].r += (p0.r + p1.i);
|
|
|
|
|
sum[3].i += (p0.i - p1.r);
|
|
|
|
|
sum2[3].r += (sum[3].r * sum[3].r) >> log2_re;
|
|
|
|
|
sum2[3].i += (sum[3].i * sum[3].i) >> log2_re;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*precoded_sinr_dB = dB_fixed(tested_precoded_sinr[i2[0]]);
|
|
|
|
|
} else {
|
|
|
|
|
i2[0] = tested_precoded_sinr[0]+tested_precoded_sinr[2] > tested_precoded_sinr[1]+tested_precoded_sinr[3] ? 0 : 1;
|
|
|
|
|
*precoded_sinr_dB = dB_fixed((tested_precoded_sinr[i2[0]] + tested_precoded_sinr[i2[0]+2])>>1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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_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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(rank_indicator == 0) {
|
|
|
|
|
for(int tested_i2 = 0; tested_i2 < 4; tested_i2++) {
|
|
|
|
|
if(tested_precoded_sinr[tested_i2] > tested_precoded_sinr[i2[0]]) {
|
|
|
|
|
i2[0] = tested_i2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*precoded_sinr_dB = dB_fixed(tested_precoded_sinr[i2[0]]);
|
|
|
|
|
} else {
|
|
|
|
|
i2[0] = tested_precoded_sinr[0]+tested_precoded_sinr[2] > tested_precoded_sinr[1]+tested_precoded_sinr[3] ? 0 : 1;
|
|
|
|
|
*precoded_sinr_dB = dB_fixed((tested_precoded_sinr[i2[0]] + tested_precoded_sinr[i2[0]+2])>>1);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LOG_W(NR_PHY, "PMI computation is not implemented for rank indicator %i with 2 ports\n", rank_indicator+1);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
} else if (N_ports == 4) {
|
|
|
|
|
// ---- 4-port PMI: Type1SinglePanel codebook, TS 38.214 Table 5.2.2.2.1-5 ----
|
|
|
|
|
// N1=2, N2=1, O1=4, O2=1
|
|
|
|
|
// Rank 1 precoder: W(l,n) = 1/2 * [1; w1_l; w2_n; w1_l*w2_n]^T
|
|
|
|
|
// where w1_l = e^{j*pi*l/4} (l = i11 in {0,...,7}), w2_n = e^{j*pi*n/2} (n = i2 in {0,1,2,3})
|
|
|
|
|
// Rank 2 precoder: W(l,n) = 1/(2*sqrt(2)) * [v_l v_l; w2_n*v_l -w2_n*v_l]
|
|
|
|
|
// where v_l = [1; w1_l]^T, n = i2 in {0,1}
|
|
|
|
|
|
|
|
|
|
// Precompute DFT beam phases w1_l = e^{j*pi*l/4} for l=0..7
|
|
|
|
|
// Stored as (cos, sin) pairs scaled to fixed-point is not needed; we use float for the search.
|
|
|
|
|
const int N_beams = 8; // N1 * O1 = 2 * 4
|
|
|
|
|
float w1_re[8], w1_im[8];
|
|
|
|
|
for (int l = 0; l < N_beams; l++) {
|
|
|
|
|
w1_re[l] = cosf((float)M_PI * l / 4.0f);
|
|
|
|
|
w1_im[l] = sinf((float)M_PI * l / 4.0f);
|
|
|
|
|
}
|
|
|
|
|
// Co-phasing factors w2_n = e^{j*pi*n/2} for n=0..3
|
|
|
|
|
const float w2_re[4] = {1.0f, 0.0f, -1.0f, 0.0f};
|
|
|
|
|
const float w2_im[4] = {0.0f, 1.0f, 0.0f, -1.0f};
|
|
|
|
|
|
|
|
|
|
if (rank_indicator == 0) {
|
|
|
|
|
// Rank 1: search over i11 (beam l) and i2 (co-phase n)
|
|
|
|
|
// Effective channel: h_eff = H[rx][0] + w1*H[rx][1] + w2*H[rx][2] + w1*w2*H[rx][3]
|
|
|
|
|
// = (H[rx][0] + w1*H[rx][1]) + w2*(H[rx][2] + w1*H[rx][3])
|
|
|
|
|
int64_t best_sinr = -1;
|
|
|
|
|
uint8_t best_l = 0, best_n = 0;
|
|
|
|
|
|
|
|
|
|
for (int l = 0; l < N_beams; l++) {
|
|
|
|
|
// For this beam l, accumulate effective channel power for each co-phase n
|
|
|
|
|
int64_t power[4] = {0};
|
|
|
|
|
int rb_count = 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 = (frame_parms->first_carrier_offset + rb * NR_NB_SC_PER_RB) % frame_parms->ofdm_symbol_size;
|
|
|
|
|
uint16_t k_offset = k + mem_offset;
|
|
|
|
|
rb_count++;
|
|
|
|
|
|
|
|
|
|
for (int ant_rx = 0; ant_rx < frame_parms->nb_antennas_rx; ant_rx++) {
|
|
|
|
|
const c16_t h0 = csi_rs_estimated_channel_freq[ant_rx][0][k_offset];
|
|
|
|
|
const c16_t h1 = csi_rs_estimated_channel_freq[ant_rx][1][k_offset];
|
|
|
|
|
const c16_t h2 = csi_rs_estimated_channel_freq[ant_rx][2][k_offset];
|
|
|
|
|
const c16_t h3 = csi_rs_estimated_channel_freq[ant_rx][3][k_offset];
|
|
|
|
|
|
|
|
|
|
// A = h0 + w1*h1 (complex multiply)
|
|
|
|
|
float A_re = (float)h0.r + w1_re[l] * h1.r - w1_im[l] * h1.i;
|
|
|
|
|
float A_im = (float)h0.i + w1_re[l] * h1.i + w1_im[l] * h1.r;
|
|
|
|
|
// B = h2 + w1*h3
|
|
|
|
|
float B_re = (float)h2.r + w1_re[l] * h3.r - w1_im[l] * h3.i;
|
|
|
|
|
float B_im = (float)h2.i + w1_re[l] * h3.i + w1_im[l] * h3.r;
|
|
|
|
|
|
|
|
|
|
// h_eff(n) = A + w2_n * B for each co-phase n
|
|
|
|
|
for (int n = 0; n < 4; n++) {
|
|
|
|
|
float eff_re = A_re + w2_re[n] * B_re - w2_im[n] * B_im;
|
|
|
|
|
float eff_im = A_im + w2_re[n] * B_im + w2_im[n] * B_re;
|
|
|
|
|
power[n] += (int64_t)(eff_re * eff_re + eff_im * eff_im);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Select best co-phase for this beam
|
|
|
|
|
for (int n = 0; n < 4; n++) {
|
|
|
|
|
if (power[n] > best_sinr) {
|
|
|
|
|
best_sinr = power[n];
|
|
|
|
|
best_l = l;
|
|
|
|
|
best_n = n;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i1[0] = best_l; // i11
|
|
|
|
|
i1[1] = 0; // i12 (not applicable for N2=1)
|
|
|
|
|
i1[2] = 0; // i13 (not applicable for rank 1)
|
|
|
|
|
i2[0] = best_n;
|
|
|
|
|
if (best_sinr > 0)
|
|
|
|
|
*precoded_sinr_dB = dB_fixed(best_sinr / interference_plus_noise_power);
|
|
|
|
|
|
|
|
|
|
} else if (rank_indicator == 1) {
|
|
|
|
|
// Rank 2: two-layer precoder
|
|
|
|
|
// W(l,n) = 1/(2*sqrt(2)) * [v_l v_l; phi_n*v_l -phi_n*v_l]
|
|
|
|
|
// Column 0: [1; w1_l; w2_n; w1_l*w2_n]^T (same as rank 1)
|
|
|
|
|
// Column 1: [1; w1_l; -w2_n; -w1_l*w2_n]^T
|
|
|
|
|
// SINR ~ sum of per-layer powers, co-phase n in {0,1}
|
|
|
|
|
int64_t best_sinr = -1;
|
|
|
|
|
uint8_t best_l = 0, best_n = 0;
|
|
|
|
|
|
|
|
|
|
for (int l = 0; l < N_beams; l++) {
|
|
|
|
|
int64_t power[2] = {0}; // 2 co-phase hypotheses for rank 2
|
|
|
|
|
|
|
|
|
|
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 = (frame_parms->first_carrier_offset + rb * NR_NB_SC_PER_RB) % frame_parms->ofdm_symbol_size;
|
|
|
|
|
uint16_t k_offset = k + mem_offset;
|
|
|
|
|
|
|
|
|
|
for (int ant_rx = 0; ant_rx < frame_parms->nb_antennas_rx; ant_rx++) {
|
|
|
|
|
const c16_t h0 = csi_rs_estimated_channel_freq[ant_rx][0][k_offset];
|
|
|
|
|
const c16_t h1 = csi_rs_estimated_channel_freq[ant_rx][1][k_offset];
|
|
|
|
|
const c16_t h2 = csi_rs_estimated_channel_freq[ant_rx][2][k_offset];
|
|
|
|
|
const c16_t h3 = csi_rs_estimated_channel_freq[ant_rx][3][k_offset];
|
|
|
|
|
|
|
|
|
|
float A_re = (float)h0.r + w1_re[l] * h1.r - w1_im[l] * h1.i;
|
|
|
|
|
float A_im = (float)h0.i + w1_re[l] * h1.i + w1_im[l] * h1.r;
|
|
|
|
|
float B_re = (float)h2.r + w1_re[l] * h3.r - w1_im[l] * h3.i;
|
|
|
|
|
float B_im = (float)h2.i + w1_re[l] * h3.i + w1_im[l] * h3.r;
|
|
|
|
|
|
|
|
|
|
for (int n = 0; n < 2; n++) {
|
|
|
|
|
// Layer 0: A + w2_n * B
|
|
|
|
|
float l0_re = A_re + w2_re[n] * B_re - w2_im[n] * B_im;
|
|
|
|
|
float l0_im = A_im + w2_re[n] * B_im + w2_im[n] * B_re;
|
|
|
|
|
// Layer 1: A - w2_n * B
|
|
|
|
|
float l1_re = A_re - w2_re[n] * B_re + w2_im[n] * B_im;
|
|
|
|
|
float l1_im = A_im - w2_re[n] * B_im - w2_im[n] * B_re;
|
|
|
|
|
power[n] += (int64_t)(l0_re * l0_re + l0_im * l0_im + l1_re * l1_re + l1_im * l1_im);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int n = 0; n < 2; n++) {
|
|
|
|
|
if (power[n] > best_sinr) {
|
|
|
|
|
best_sinr = power[n];
|
|
|
|
|
best_l = l;
|
|
|
|
|
best_n = n;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i1[0] = best_l;
|
|
|
|
|
i1[1] = 0;
|
|
|
|
|
i1[2] = 0;
|
|
|
|
|
i2[0] = best_n;
|
|
|
|
|
if (best_sinr > 0)
|
|
|
|
|
*precoded_sinr_dB = dB_fixed((best_sinr / 2) / interference_plus_noise_power);
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
LOG_W(NR_PHY, "PMI computation is not implemented for rank indicator %i with 4 ports\n", rank_indicator+1);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
} 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 %i ports\n", N_ports);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|