From 5874aa1fe7337e35c7d57f9fa15668bfe1faee60 Mon Sep 17 00:00:00 2001 From: maki Date: Wed, 4 Feb 2026 17:20:40 +0900 Subject: [PATCH] update --- ci-scripts/conf_files/nrue.uicc.conf | 7 + docker/Dockerfile.nakaolab.test | 4 +- openair1/PHY/NR_TRANSPORT/nr_antenna_state.c | 20 +- openair1/PHY/NR_UE_TRANSPORT/csi_rx.c | 504 ++++++++++++++---- openair1/SCHED_NR/nr_ru_procedures.c | 57 +- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 10 +- tools/scripts/traffic_runner.sh | 2 +- 7 files changed, 450 insertions(+), 154 deletions(-) diff --git a/ci-scripts/conf_files/nrue.uicc.conf b/ci-scripts/conf_files/nrue.uicc.conf index 5424abe30f..7e74382580 100644 --- a/ci-scripts/conf_files/nrue.uicc.conf +++ b/ci-scripts/conf_files/nrue.uicc.conf @@ -14,6 +14,13 @@ position0 = { thread-pool = "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" +rfsimulator : +{ + serveraddr = "192.168.71.140"; // gNB IP address + serverport = 4043; + options = ("chanmod"); // Enable channel modeling +}; + #/* configuration for channel modelisation */ #/* To be included in main config file when */ #/* channel modelisation is used (rfsimulator with chanmod options enabled) */ diff --git a/docker/Dockerfile.nakaolab.test b/docker/Dockerfile.nakaolab.test index d51e58fdba..3216fa2b96 100644 --- a/docker/Dockerfile.nakaolab.test +++ b/docker/Dockerfile.nakaolab.test @@ -8,10 +8,8 @@ COPY . . # build all targets so final targets can be created afterwards RUN /bin/sh oaienv && \ cd cmake_targets && \ - mkdir -p log && \ ./build_oai --ninja \ - --gNB --RU --nrUE -w SIMU \ - -w USRP -t Ethernet \ + --gNB --RU --nrUE -w SIMU -w USRP --build-lib telnetsrv \ --build-e2 --cmake-opt -DKPM_VERSION=KPM_V3_00 --cmake-opt -DE2AP_VERSION=E2AP_V3 \ --cmake-opt -DCMAKE_C_FLAGS="-Werror" --cmake-opt -DCMAKE_CXX_FLAGS="-Werror" $BUILD_OPTION && \ echo "---- ldd on executables ----" && \ diff --git a/openair1/PHY/NR_TRANSPORT/nr_antenna_state.c b/openair1/PHY/NR_TRANSPORT/nr_antenna_state.c index 3ffed1c615..56e874d206 100644 --- a/openair1/PHY/NR_TRANSPORT/nr_antenna_state.c +++ b/openair1/PHY/NR_TRANSPORT/nr_antenna_state.c @@ -213,19 +213,13 @@ void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id) int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers) { - if (state == NULL) return requested_layers; - - int active_tx = nr_get_active_tx_antenna_count(state); - - // MIMO layers cannot exceed active TX antennas - int effective = (requested_layers < active_tx) ? requested_layers : active_tx; - - // Must have at least 1 layer if any antenna is active - if (effective < 1 && active_tx > 0) { - effective = 1; - } - - return effective; + // Don't reduce MIMO layers based on muted antennas. + // Antenna muting happens at the RU level (in nr_feptx) after precoding, + // so we keep using full layer count to maintain proper precoding matrices. + // This avoids the identity matrix fallback which doesn't account for + // which specific antennas are muted. + (void)state; // unused - muting is handled at RU level + return requested_layers; } void nr_log_antenna_state(const nr_antenna_state_t *state) diff --git a/openair1/PHY/NR_UE_TRANSPORT/csi_rx.c b/openair1/PHY/NR_UE_TRANSPORT/csi_rx.c index b2e43b3c16..99ef532cf3 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/csi_rx.c +++ b/openair1/PHY/NR_UE_TRANSPORT/csi_rx.c @@ -32,6 +32,7 @@ #include #include #include +#include #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; } diff --git a/openair1/SCHED_NR/nr_ru_procedures.c b/openair1/SCHED_NR/nr_ru_procedures.c index 9540385244..063916c2ee 100644 --- a/openair1/SCHED_NR/nr_ru_procedures.c +++ b/openair1/SCHED_NR/nr_ru_procedures.c @@ -46,11 +46,6 @@ #include -// Rate-limit TX power logs (every N slots per antenna task, startSymbol==0 only) -#ifndef TXF_PWR_LOG_PERIOD_FRAME -#define TXF_PWR_LOG_PERIOD_FRAME 100 -#endif - static inline int count_nnz_c16(const c16_t *v, int n) { int nnz = 0; @@ -216,12 +211,21 @@ void nr_feptx_prec(RU_t *ru, int frame_tx, int slot_tx) int Ptx = cfg->carrier_config.num_tx_ant.value; // If there is no digital beamforming we just need to copy the data to RU if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) { + const nr_antenna_state_t *ant_state = &gNB->antenna_state; for (int b = 0; b < ru->num_beams_period; b++) { for (int i = 0; i < Ptx; ++i) { int tx_idx = i + b * ru->nb_tx; - memcpy((void*)ru->common.txdataF_BF[tx_idx], - (void*)&gNB->common_vars.txdataF[b][i][txdataF_offset], - fp->samples_per_slot_wCP * sizeof(int32_t)); + // Check antenna muting state for energy saving + if (!nr_is_tx_antenna_active(ant_state, i)) { + // Antenna is muted - zero out the buffer (transmission blanking) + memset((void*)ru->common.txdataF_BF[tx_idx], 0, fp->samples_per_slot_wCP * sizeof(int32_t)); + LOG_D(PHY, "[ANTENNA_MUTE] TX antenna %d muted in nr_feptx_prec, blanking slot %d\n", i, slot_tx); + } else { + // Antenna is active - copy data normally + memcpy((void*)ru->common.txdataF_BF[tx_idx], + (void*)&gNB->common_vars.txdataF[b][i][txdataF_offset], + fp->samples_per_slot_wCP * sizeof(int32_t)); + } } } } else { @@ -260,29 +264,36 @@ void nr_feptx(void *arg) // If there is no digital beamforming we just need to copy the data to RU if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) { - memcpy((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset], - (void*)&ru->gNB_list[0]->common_vars.txdataF[bb][aa][txdataF_offset], - numSamples * sizeof(int32_t)); + // Check antenna muting state for energy saving + const nr_antenna_state_t *ant_state = &ru->gNB_list[0]->antenna_state; + bool is_active = nr_is_tx_antenna_active(ant_state, aa); + if (!is_active) { + // Antenna is muted - zero out the buffer (transmission blanking) + memset((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset], 0, numSamples * sizeof(int32_t)); + if (startSymbol == 0) { + LOG_I(PHY, "[ANTENNA_MUTE] TX ant %d muted (mask=0x%02X, num_tx=%d), blanking slot %d\n", + aa, nr_get_tx_antenna_bitmask(ant_state), ant_state->num_tx_antennas, slot); + } + } else { + // Antenna is active - copy data normally + memcpy((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset], + (void*)&ru->gNB_list[0]->common_vars.txdataF[bb][aa][txdataF_offset], + numSamples * sizeof(int32_t)); + } } else { AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n"); } - if (startSymbol == 0 && ((ru->proc.frame_tx % TXF_PWR_LOG_PERIOD_FRAME) == 0)) { - const c16_t *src = (const c16_t *)&ru->gNB_list[0]->common_vars.txdataF[bb][aa][txdataF_offset]; - const c16_t *dst = (const c16_t *)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset]; - - // signal_energy_nodc() in OAI expects "2*N" for c16_t buffers (see existing usage in nr_feptx_ofdm()). - const int src_pwr_db = dB_fixed(signal_energy_nodc((c16_t *)src, 2 * numSamples)); + const c16_t *dst = (const c16_t *)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset]; + const int dst_nnz = count_nnz_c16(dst, numSamples); + if (startSymbol == 0 && ru->proc.frame_tx % 10 == 0 && dst_nnz != 0) { const int dst_pwr_db = dB_fixed(signal_energy_nodc((c16_t *)dst, 2 * numSamples)); - const int src_nnz = count_nnz_c16(src, numSamples); - const int dst_nnz = count_nnz_c16(dst, numSamples); LOG_I(PHY, - "[TXF_PWR] frame=%d slot=%d beam=%d aa=%d tx_idx=%d sym0 len=%d: " - "SRC pwr=%d dB nnz=%d | DST pwr=%d dB nnz=%d\n", - ru->proc.frame_tx, slot, bb, aa, tx_idx, numSamples, - src_pwr_db, src_nnz, dst_pwr_db, dst_nnz); + "[TXF_PWR] frame=%d slot=%d beam=%d aa=%d tx_idx=%d " + "DST pwr=%d dB nnz=%d\n", + ru->proc.frame_tx, slot, bb, aa, tx_idx, dst_pwr_db, dst_nnz); } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 95794a9542..814e7aa3af 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -202,17 +202,13 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac, } nr_antenna_state_t* ant_state = nrmac->antenna_state; if (ant_state) { - int active_tx = nr_get_active_tx_antenna_count(ant_state); - int configured_tx = ant_state->num_tx_antennas; if (nr_is_in_recovery_mode(ant_state)) { LOG_D(NR_MAC, "[ANTENNA_MUTE] Recovery mode active, using identity matrix\n"); return 0; } - if (active_tx < configured_tx && layers < configured_tx) { - LOG_D(NR_MAC, "[ANTENNA_MUTE] Using identity matrix: %d/%d TX antennas active, %d layers\n", - active_tx, configured_tx, layers); - return 0; - } + // Don't fall back to identity matrix for muted antennas. + // Antenna muting is handled at RU level (nr_feptx zeros muted antenna buffers). + // Using proper precoding ensures data reaches all active antennas. } const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id; diff --git a/tools/scripts/traffic_runner.sh b/tools/scripts/traffic_runner.sh index d564e72a10..bf7f0c06ca 100755 --- a/tools/scripts/traffic_runner.sh +++ b/tools/scripts/traffic_runner.sh @@ -41,7 +41,7 @@ iperf_udp_ul_forever() { ( iperf -u -c "$IPERF_HOST_UDP" -B "$OAITUN_IP" -p "$IP # ---------------- profiles ---------------- do_boring() { - iperf_udp_dl_forever 15M + iperf_udp_dl_forever 1M wait # iperf -c "$IPERF_HOST" -p "$IPERF_PORT" -B "$OAITUN_IP" -u -b 25M -t "$LONG_T" }