Author SHA1 Message Date
maki 67717ed5c1 update 2026-09-19 23:28:08 +09:00
maki c50fe1af53 feat(nr): implement rank-8 PDSCH 2026-09-15 00:36:01 +09:00
francescomani 04ee4ea8e5 restructuring of UE DLSCH code to handle 2 codewords
even if 2 codewords are currently not supported and not tested, the way UE handled the possibility of having 2 codewords was messy, in between support and non-support
the intention of this MR is to cleanup the code more than extend the support

Signed-off-by: Francesco Mani <email@francescomani.it>
2026-09-14 22:56:26 +09:00
makiandClaude Opus 4.7 08349f246b RC SM: fix heap corruption on UE teardown after rrcRelease event
When xApp subscribes to RC SM Style 1 (Message Copy), the gNB's
signal_rrc_msg() path on rrcRelease handed an async indication to the
E2 agent thread, then the calling code proceeded to UE-context
teardown while the encoder was still walking the indication structure.
Three issues observed in production:

1. malloc_or_fail() leaves union/pointer fields in rc_ind_data and its
   sub-structures uninitialized; the encoder dereferences whatever
   garbage was in the unused union member and corrupts the heap.
   Fix: switch malloc_or_fail(sizeof(...)) to
   calloc_or_fail(1, sizeof(...)) in fill_rrc_msg_copy, fill_ue_id,
   and the rc_ind_data_t allocations.

2. The async indication path runs concurrently with UE-context
   removal. Add rc_teardown_lock/rc_teardown_unlock helpers (public
   wrappers around rc_mutex) so the RRC layer can serialize teardown
   against any in-flight signal_rrc_msg() critical section.

3. Wrap rrc_gNB_remove_ue_context() in rrc_gNB.c with
   rc_teardown_lock/unlock under E2_AGENT.

With these three fixes plus the deployment-side MALLOC_CHECK_=3
backstop, a 20-minute challenging-schedule run completes with 22
RRC_RELEASE events processed cleanly and zero crashes (vs. SIGSEGV
within 10-30 minutes previously).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:55:45 +09:00
80 changed files with 4867 additions and 1990 deletions
+2 -2
View File
@@ -919,7 +919,6 @@ set(PHY_SRC_RU
${OPENAIR1_DIR}/PHY/MODULATION/gen_75KHz.cpp
${OPENAIR1_DIR}/PHY/MODULATION/beamforming.c
${OPENAIR1_DIR}/PHY/MODULATION/compute_bf_weights.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_state.c
${OPENAIR1_DIR}/PHY/INIT/lte_init_ru.c
${OPENAIR1_DIR}/PHY/LTE_TRANSPORT/prach.c
)
@@ -979,6 +978,8 @@ set(PHY_SRC_UE
${OPENAIR1_DIR}/PHY/nr_phy_common/src/nr_phy_common_srs.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_scrambling.c
${OPENAIR1_DIR}/PHY/NR_REFSIG/refsig.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_state.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_control.c
)
set(PHY_NR_SRC
@@ -999,7 +1000,6 @@ set(PHY_SRC_UE
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_prach.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch_llr_computation.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_control.c
${OPENAIR1_DIR}/PHY/NR_REFSIG/ul_ref_seq_nr.c
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_dmrs_rx.c
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_gen_mod_table.c
+189
View File
@@ -13,10 +13,199 @@
#include "nr_common.h"
#include <limits.h>
#include <math.h>
#include <string.h>
#define C_SRS_NUMBER (64)
#define B_SRS_NUMBER (4)
void nr_pdsch_rank_capacity_scores(const float eigenvalues[NR_MAX_NB_LAYERS],
uint8_t max_rank,
float noise_power,
float scores[NR_MAX_NB_LAYERS])
{
AssertFatal(max_rank >= 1 && max_rank <= NR_MAX_NB_LAYERS,
"Invalid maximum PDSCH rank %u\n",
max_rank);
const float finite_noise = isfinite(noise_power) && noise_power > 0.0f ? noise_power : 1.0f;
for (int rank = 1; rank <= NR_MAX_NB_LAYERS; rank++) {
scores[rank - 1] = 0.0f;
if (rank > max_rank)
continue;
for (int layer = 0; layer < rank; layer++) {
const float eigenvalue = isfinite(eigenvalues[layer]) && eigenvalues[layer] > 0.0f
? eigenvalues[layer]
: 0.0f;
scores[rank - 1] += log2f(1.0f + eigenvalue / (rank * finite_noise));
}
}
}
uint8_t nr_pdsch_select_rank(const float scores[NR_MAX_NB_LAYERS],
uint8_t max_rank,
float relative_margin)
{
AssertFatal(max_rank >= 1 && max_rank <= NR_MAX_NB_LAYERS,
"Invalid maximum PDSCH rank %u\n",
max_rank);
const float margin = isfinite(relative_margin) && relative_margin > 0.0f ? relative_margin : 0.0f;
uint8_t best_rank = 1;
float best_score = isfinite(scores[0]) ? scores[0] : 0.0f;
for (uint8_t rank = 2; rank <= max_rank; rank++) {
const float candidate = isfinite(scores[rank - 1]) ? scores[rank - 1] : 0.0f;
if (candidate > best_score * (1.0f + margin)) {
best_rank = rank;
best_score = candidate;
}
}
return best_rank;
}
uint8_t nr_type1_sp_4x1_i11_count(uint8_t layers)
{
switch (layers) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
return 16;
case 7:
case 8:
return 8;
default:
return 0;
}
}
uint8_t nr_type1_sp_4x1_i13_count(uint8_t layers)
{
switch (layers) {
case 1:
case 5:
case 6:
case 7:
case 8:
return 1;
case 2:
return 4;
case 3:
case 4:
return 3;
default:
return 0;
}
}
uint8_t nr_type1_sp_4x1_i2_count(uint8_t layers)
{
if (layers == 1)
return 4;
return layers >= 2 && layers <= 8 ? 2 : 0;
}
bool nr_type1_sp_4x1_weights(uint8_t layers,
uint8_t i11,
uint8_t i13,
uint8_t i2,
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS])
{
memset(weights, 0, NR_MAX_NB_LAYERS * NR_MAX_CSI_PORTS * sizeof(**weights));
const uint8_t i11_count = nr_type1_sp_4x1_i11_count(layers);
const uint8_t i13_count = nr_type1_sp_4x1_i13_count(layers);
const uint8_t i2_count = nr_type1_sp_4x1_i2_count(layers);
if (i11_count == 0 || i11 >= i11_count || i13 >= i13_count || i2 >= i2_count)
return false;
/* TS 38.214 Tables 5.2.2.2.1-5--12, specialized to the one-dimensional
* N1=4 panel. O1=4, so adjacent orthogonal DFT beams are four indices
* apart. Values 0..3 below encode 1, -1, phi, and -phi respectively. */
uint8_t beam_offset[NR_MAX_NB_LAYERS] = {0};
uint8_t polarization[NR_MAX_NB_LAYERS] = {0};
switch (layers) {
case 1:
polarization[0] = 2;
break;
case 2:
beam_offset[1] = 4 * i13;
polarization[0] = 2;
polarization[1] = 3;
break;
case 3:
case 4: {
const uint8_t offset = 4 * (i13 + 1);
beam_offset[1] = offset;
beam_offset[3] = offset;
polarization[0] = 2;
polarization[1] = 2;
polarization[2] = 3;
polarization[3] = 3;
break;
}
case 5: {
const uint8_t offsets[] = {0, 0, 4, 4, 8};
const uint8_t polarizations[] = {2, 3, 0, 1, 0};
memcpy(beam_offset, offsets, sizeof(offsets));
memcpy(polarization, polarizations, sizeof(polarizations));
break;
}
case 6: {
const uint8_t offsets[] = {0, 0, 4, 4, 8, 8};
const uint8_t polarizations[] = {2, 3, 2, 3, 0, 1};
memcpy(beam_offset, offsets, sizeof(offsets));
memcpy(polarization, polarizations, sizeof(polarizations));
break;
}
case 7: {
const uint8_t offsets[] = {0, 0, 4, 8, 8, 12, 12};
const uint8_t polarizations[] = {2, 3, 2, 0, 1, 0, 1};
memcpy(beam_offset, offsets, sizeof(offsets));
memcpy(polarization, polarizations, sizeof(polarizations));
break;
}
case 8: {
const uint8_t offsets[] = {0, 0, 4, 4, 8, 8, 12, 12};
const uint8_t polarizations[] = {2, 3, 2, 3, 0, 1, 0, 1};
memcpy(beam_offset, offsets, sizeof(offsets));
memcpy(polarization, polarizations, sizeof(polarizations));
break;
}
}
const float scale = 1.0f / sqrtf((float)layers);
const float phi_re = cosf((float)M_PI * i2 / 2.0f);
const float phi_im = sinf((float)M_PI * i2 / 2.0f);
for (int layer = 0; layer < layers; layer++) {
const int beam = i11 + beam_offset[layer];
float pol_re = 1.0f;
float pol_im = 0.0f;
switch (polarization[layer]) {
case 1:
pol_re = -1.0f;
break;
case 2:
pol_re = phi_re;
pol_im = phi_im;
break;
case 3:
pol_re = -phi_re;
pol_im = -phi_im;
break;
}
for (int element = 0; element < 4; element++) {
const float angle = 2.0f * (float)M_PI * element * beam / 16.0f;
const float beam_re = cosf(angle);
const float beam_im = sinf(angle);
weights[layer][element].r = lrintf(SHRT_MAX * scale * beam_re);
weights[layer][element].i = lrintf(SHRT_MAX * scale * beam_im);
weights[layer][element + 4].r = lrintf(SHRT_MAX * scale * (pol_re * beam_re - pol_im * beam_im));
weights[layer][element + 4].i = lrintf(SHRT_MAX * scale * (pol_re * beam_im + pol_im * beam_re));
}
}
return true;
}
/* TS 38.211 Table 6.4.1.4.3-1: SRS bandwidth configuration */
static const unsigned short srs_bandwidth_config[C_SRS_NUMBER][B_SRS_NUMBER][2] = {
/* B_SRS = 0 B_SRS = 1 B_SRS = 2 B_SRS = 3 */
+78 -1
View File
@@ -9,6 +9,7 @@
#ifndef __COMMON_UTILS_NR_NR_COMMON__H__
#define __COMMON_UTILS_NR_NR_COMMON__H__
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "assertions.h"
@@ -71,9 +72,85 @@ static inline const char *rnti_types(nr_rnti_type_t rr)
#define MAX_GSCN_BAND 620 // n78 has the highest GSCN range of 619
#define NR_SYMBOLS_PER_SLOT 14
#define NR_SYMBOLS_PER_SLOT_EXTENDED_CP 12
#define NR_MAX_NB_LAYERS 4 // 8
#define NR_MAX_NB_LAYERS 8
#define NR_MAX_NB_CODEWORDS 2
#define NR_MAX_CSI_PORTS 12
/* TS 38.211 Table 7.3.1.3-1: PDSCH uses one codeword up to rank four and
* two codewords from rank five onward. Keep this split in one shared helper
* so MAC and PHY cannot silently disagree about the 4+4 rank-eight mapping. */
static inline uint8_t nr_pdsch_num_codewords(uint8_t layers)
{
AssertFatal(layers >= 1 && layers <= NR_MAX_NB_LAYERS, "Invalid PDSCH layer count %u\n", layers);
return layers > 4 ? 2 : 1;
}
static inline uint8_t nr_pdsch_layers_per_codeword(uint8_t layers, uint8_t codeword)
{
const uint8_t n_codewords = nr_pdsch_num_codewords(layers);
AssertFatal(codeword < n_codewords, "Invalid codeword %u for %u PDSCH layers\n", codeword, layers);
if (n_codewords == 1)
return layers;
return layers / 2 + (codeword == 1 ? layers % 2 : 0);
}
/* A two-TB PDSCH has two separate HARQ-ACK decisions even though both TBs
* share one HARQ process. ACKs for padding-only replacement codewords must
* not clear the other TB's pending retransmission. */
static inline uint8_t nr_pdsch_pending_after_ack(uint8_t pending_mask,
uint8_t active_mask,
uint8_t ack_mask)
{
AssertFatal(active_mask == 1 || active_mask == 3, "Invalid active codeword mask %u\n", active_mask);
const uint8_t pending = pending_mask ? pending_mask : active_mask;
return pending & ~(ack_mask & active_mask);
}
/** Compute equal-power spatial-capacity scores for candidate PDSCH ranks.
*
* eigenvalues contains the descending eigenvalues of H^H H. The score for
* rank r is sum_i log2(1 + eigenvalue_i / (r * noise_power)): total TX power
* is held constant and divided equally among the r layers. Scores estimate
* the useful spatial multiplexing opportunity; MCS, coding, and scheduling
* overhead are intentionally left to MAC.
*/
void nr_pdsch_rank_capacity_scores(const float eigenvalues[NR_MAX_NB_LAYERS],
uint8_t max_rank,
float noise_power,
float scores[NR_MAX_NB_LAYERS]);
/** Select the lowest rank that is materially better than all lower ranks.
* relative_margin is expressed as a fraction (0.05 means a five-percent
* required improvement), which avoids switching rank for negligible model
* gains. Returns a one-based rank.
*/
uint8_t nr_pdsch_select_rank(const float scores[NR_MAX_NB_LAYERS],
uint8_t max_rank,
float relative_margin);
/** Number of valid i_1_1 values for the rank-1--8 Type-I single-panel
* codebooks with (N1,N2,O1,O2)=(4,1,4,1). This is the eight-port geometry
* used by the experimental rank-8 branch. Returns zero for invalid ranks.
*/
uint8_t nr_type1_sp_4x1_i11_count(uint8_t layers);
/** Number of valid i_1_3 and i_2 values in the same codebook. */
uint8_t nr_type1_sp_4x1_i13_count(uint8_t layers);
uint8_t nr_type1_sp_4x1_i2_count(uint8_t layers);
/** Build one TS 38.214 Type-I single-panel rank-1--8 precoder for the
* eight-port (N1,N2,O1,O2)=(4,1,4,1), codebookMode=1 geometry.
*
* weights is indexed [layer][antenna port]. Coefficients use OAI's existing
* Q15 codebook convention (1/sqrt(layers) scaling); unused entries are zero.
* Returns false when layers, i11, i13, or i2 is outside this codebook.
*/
bool nr_type1_sp_4x1_weights(uint8_t layers,
uint8_t i11,
uint8_t i13,
uint8_t i2,
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS]);
// Since the IQ samples are represented by SQ15 R+I (see https://en.wikipedia.org/wiki/Q_(number_format)) we need to compensate when
// calcualting signal energy. Instead of shifting each sample right by 15, we can normalize the result in dB scale once its
// calcualted. Signal energy is calculated using RMS^2, where each sample is squared before taking the average of the sum, therefore
+3
View File
@@ -5,3 +5,6 @@ target_link_libraries(test_nr_common PRIVATE nr_common GTest::gtest minimal_lib)
add_dependencies(tests test_nr_common)
add_test(NAME test_nr_common
COMMAND ./test_nr_common)
add_executable(nr_rank_selector_benchmark nr_rank_selector_benchmark.cpp)
target_link_libraries(nr_rank_selector_benchmark PRIVATE nr_common minimal_lib)
@@ -0,0 +1,63 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <algorithm>
#include <cmath>
#include <cstdio>
extern "C" {
#include "nr_common.h"
}
struct rank_scenario_t {
const char *name;
float eigenvalues[NR_MAX_NB_LAYERS];
float noise_power;
};
static uint8_t legacy_rank(const float eigenvalues[NR_MAX_NB_LAYERS])
{
uint8_t rank = 1;
for (int layer = 1; layer < NR_MAX_NB_LAYERS; layer++) {
if (eigenvalues[layer] <= 0.0f
|| 10.0f * std::log10(eigenvalues[0] / eigenvalues[layer]) >= 8.0f)
break;
rank++;
}
return rank;
}
int main()
{
const rank_scenario_t scenarios[] = {
{"dominant_one", {100.0f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f}, 1.0f},
{"four_modes", {100.0f, 80.0f, 60.0f, 40.0f, 0.01f, 0.01f, 0.01f, 0.01f}, 1.0f},
{"eight_modes", {100.0f, 95.0f, 90.0f, 85.0f, 80.0f, 75.0f, 70.0f, 65.0f}, 1.0f},
{"balanced_low_snr", {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}, 10.0f},
{"legacy_false_high_rank", {1.0f, 0.16f, 0.16f, 0.16f, 0.16f, 0.16f, 0.16f, 0.16f}, 10.0f},
{"weak_eighth", {100.0f, 90.0f, 80.0f, 70.0f, 60.0f, 50.0f, 40.0f, 5.0f}, 1.0f},
};
std::puts("scenario,policy,selected_rank,selected_capacity_bps_hz");
for (const rank_scenario_t &scenario : scenarios) {
float scores[NR_MAX_NB_LAYERS] = {};
nr_pdsch_rank_capacity_scores(scenario.eigenvalues,
NR_MAX_NB_LAYERS,
scenario.noise_power,
scores);
const uint8_t capacity_rank = nr_pdsch_select_rank(scores, NR_MAX_NB_LAYERS, 0.05f);
const uint8_t legacy = legacy_rank(scenario.eigenvalues);
const uint8_t policies[] = {capacity_rank, legacy, 1, 4, 8};
const char *policy_names[] = {"capacity", "legacy", "fixed1", "fixed4", "fixed8"};
for (int policy = 0; policy < 5; policy++) {
const uint8_t rank = std::min<uint8_t>(policies[policy], NR_MAX_NB_LAYERS);
std::printf("%s,%s,%u,%.6f\n",
scenario.name,
policy_names[policy],
rank,
scores[rank - 1]);
}
}
return 0;
}
+124
View File
@@ -2,6 +2,7 @@
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <climits>
#include <gtest/gtest.h>
extern "C" {
#include "nr_common.h"
@@ -21,6 +22,129 @@ TEST(nr_common, nr_timer) {
EXPECT_TRUE(nr_timer_expired(&timer));
}
TEST(nr_common, pdsch_codeword_layer_split)
{
constexpr uint8_t expected_codewords[] = {1, 1, 1, 1, 2, 2, 2, 2};
constexpr uint8_t expected_cw0_layers[] = {1, 2, 3, 4, 2, 3, 3, 4};
constexpr uint8_t expected_cw1_layers[] = {0, 0, 0, 0, 3, 3, 4, 4};
for (uint8_t layers = 1; layers <= NR_MAX_NB_LAYERS; layers++) {
const int index = layers - 1;
EXPECT_EQ(nr_pdsch_num_codewords(layers), expected_codewords[index]);
EXPECT_EQ(nr_pdsch_layers_per_codeword(layers, 0), expected_cw0_layers[index]);
if (expected_codewords[index] == 2) {
EXPECT_EQ(nr_pdsch_layers_per_codeword(layers, 1), expected_cw1_layers[index]);
}
}
}
TEST(nr_common, pdsch_two_codeword_ack_tracks_only_pending_transport_blocks)
{
EXPECT_EQ(nr_pdsch_pending_after_ack(0, 3, 0), 3); // both NACK or DTX
EXPECT_EQ(nr_pdsch_pending_after_ack(0, 3, 1), 2); // CW0 ACK, CW1 NACK
EXPECT_EQ(nr_pdsch_pending_after_ack(0, 3, 2), 1); // CW0 NACK, CW1 ACK
EXPECT_EQ(nr_pdsch_pending_after_ack(0, 3, 3), 0); // both ACK
EXPECT_EQ(nr_pdsch_pending_after_ack(2, 3, 1), 2); // padding CW0 ACK is irrelevant
EXPECT_EQ(nr_pdsch_pending_after_ack(2, 3, 2), 0); // pending CW1 ACK finishes
EXPECT_EQ(nr_pdsch_pending_after_ack(1, 3, 2), 1); // padding CW1 ACK is irrelevant
EXPECT_EQ(nr_pdsch_pending_after_ack(1, 3, 1), 0); // pending CW0 ACK finishes
EXPECT_EQ(nr_pdsch_pending_after_ack(0, 1, 1), 0); // one-codeword legacy HARQ
}
TEST(nr_common, pdsch_rank_capacity_prefers_independent_modes)
{
float eigenvalues[NR_MAX_NB_LAYERS];
for (float &eigenvalue : eigenvalues)
eigenvalue = 100.0f;
float scores[NR_MAX_NB_LAYERS] = {};
nr_pdsch_rank_capacity_scores(eigenvalues, NR_MAX_NB_LAYERS, 1.0f, scores);
for (int rank = 1; rank < NR_MAX_NB_LAYERS; rank++)
EXPECT_GT(scores[rank], scores[rank - 1]);
EXPECT_EQ(nr_pdsch_select_rank(scores, NR_MAX_NB_LAYERS, 0.05f), 8);
}
TEST(nr_common, pdsch_rank_capacity_rejects_weak_modes)
{
float eigenvalues[NR_MAX_NB_LAYERS] = {100.0f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f};
float scores[NR_MAX_NB_LAYERS] = {};
nr_pdsch_rank_capacity_scores(eigenvalues, NR_MAX_NB_LAYERS, 1.0f, scores);
EXPECT_EQ(nr_pdsch_select_rank(scores, NR_MAX_NB_LAYERS, 0.05f), 1);
}
TEST(nr_common, pdsch_rank_capacity_respects_rank_and_gain_margin)
{
const float scores[NR_MAX_NB_LAYERS] = {10.0f, 10.4f, 10.6f, 20.0f, 30.0f, 40.0f, 50.0f, 60.0f};
EXPECT_EQ(nr_pdsch_select_rank(scores, 3, 0.05f), 3);
EXPECT_EQ(nr_pdsch_select_rank(scores, 2, 0.05f), 1);
}
TEST(nr_common, type1_sp_4x1_parameter_ranges)
{
constexpr uint8_t expected_i11[] = {16, 16, 16, 16, 16, 16, 8, 8};
constexpr uint8_t expected_i13[] = {1, 4, 3, 3, 1, 1, 1, 1};
constexpr uint8_t expected_i2[] = {4, 2, 2, 2, 2, 2, 2, 2};
for (uint8_t layers = 1; layers <= NR_MAX_NB_LAYERS; layers++) {
EXPECT_EQ(nr_type1_sp_4x1_i11_count(layers), expected_i11[layers - 1]);
EXPECT_EQ(nr_type1_sp_4x1_i13_count(layers), expected_i13[layers - 1]);
EXPECT_EQ(nr_type1_sp_4x1_i2_count(layers), expected_i2[layers - 1]);
}
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS];
EXPECT_FALSE(nr_type1_sp_4x1_weights(0, 0, 0, 0, weights));
EXPECT_FALSE(nr_type1_sp_4x1_weights(8, 8, 0, 0, weights));
EXPECT_FALSE(nr_type1_sp_4x1_weights(8, 0, 1, 0, weights));
EXPECT_FALSE(nr_type1_sp_4x1_weights(8, 0, 0, 2, weights));
}
TEST(nr_common, type1_sp_4x1_precoders_are_orthogonal)
{
for (uint8_t layers = 1; layers <= NR_MAX_NB_LAYERS; layers++) {
const uint8_t i11_count = nr_type1_sp_4x1_i11_count(layers);
for (uint8_t i11 = 0; i11 < i11_count; i11++) {
for (uint8_t i13 = 0; i13 < nr_type1_sp_4x1_i13_count(layers); i13++) {
for (uint8_t i2 = 0; i2 < nr_type1_sp_4x1_i2_count(layers); i2++) {
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS];
ASSERT_TRUE(nr_type1_sp_4x1_weights(layers, i11, i13, i2, weights));
const int64_t expected_norm = 8LL * SHRT_MAX * SHRT_MAX / layers;
const int64_t tolerance = expected_norm / 1000;
for (int a = 0; a < layers; a++) {
int64_t diagonal = 0;
for (int port = 0; port < 8; port++)
diagonal += (int64_t)weights[a][port].r * weights[a][port].r
+ (int64_t)weights[a][port].i * weights[a][port].i;
EXPECT_NEAR((double)diagonal, (double)expected_norm, (double)tolerance);
for (int b = a + 1; b < layers; b++) {
int64_t inner_re = 0;
int64_t inner_im = 0;
for (int port = 0; port < 8; port++) {
inner_re += (int64_t)weights[a][port].r * weights[b][port].r
+ (int64_t)weights[a][port].i * weights[b][port].i;
inner_im += (int64_t)weights[a][port].r * weights[b][port].i
- (int64_t)weights[a][port].i * weights[b][port].r;
}
EXPECT_NEAR((double)inner_re, 0.0, (double)tolerance);
EXPECT_NEAR((double)inner_im, 0.0, (double)tolerance);
}
}
}
}
}
}
}
TEST(nr_common, type1_sp_4x1_i2_rotates_cross_polarization)
{
c16_t i2_zero[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS];
c16_t i2_one[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS];
ASSERT_TRUE(nr_type1_sp_4x1_weights(8, 0, 0, 0, i2_zero));
ASSERT_TRUE(nr_type1_sp_4x1_weights(8, 0, 0, 1, i2_one));
EXPECT_EQ(i2_zero[0][4].i, 0);
EXPECT_EQ(i2_one[0][4].r, 0);
EXPECT_EQ(i2_zero[0][4].r, i2_one[0][4].i);
}
int main(int argc, char **argv)
{
+14 -7
View File
@@ -29,17 +29,24 @@ RUN set -eux; \
wget -O /usr/local/bin/sipp https://github.com/SIPp/sipp/releases/download/v3.7.7/sipp && chmod +x /usr/local/bin/sipp; \
rm -rf /var/lib/apt/lists/*
# FlexRIC libraries (built by --build-e2) - only copy service model libraries (*_sm.so)
# Also rebuild xapp_nakaolab (not built by default in build_oai)
# FlexRIC libraries (built by --build-e2) - only copy service model libraries (*_sm.so).
# The source tree can carry a pre-existing FlexRIC build directory whose object-file
# timestamps are newer than freshly copied headers. Force the MAC service model and
# xApp to rebuild so mac_ind_msg_t has one ABI in the gNB, plugin, and xApp. Install
# plugins only after that rebuild.
RUN mkdir -p /usr/local/etc/flexric /usr/local/lib/flexric && \
find /oai-ran/openair2/E2AP/flexric/build -name "*_sm.so" -exec cp {} /usr/local/lib/flexric/ \; && \
cp /oai-ran/openair2/E2AP/flexric/flexric.conf /usr/local/etc/flexric/ 2>/dev/null || true && \
echo "/usr/local/lib/flexric" > /etc/ld.so.conf.d/flexric.conf && ldconfig && \
cd /oai-ran/openair2/E2AP/flexric/build && \
sed -i 's|/home/[^/]*/openairinterface5g|/oai-ran|g' CMakeCache.txt && \
find . -name 'Makefile' -o -name '*.cmake' | xargs sed -i 's|/home/[^/]*/openairinterface5g|/oai-ran|g' 2>/dev/null || true && \
rm -f examples/xApp/c/monitor/CMakeFiles/xapp_nakaolab.dir/xapp_nakaolab.c.o && \
cmake --build . --target xapp_nakaolab 2>&1 | tail -5 && \
find src/sm/mac_sm/CMakeFiles/mac_sm.dir src/sm/mac_sm/CMakeFiles/mac_sm_static.dir \
-type f \( -name '*.o' -o -name '*.o.d' \) -delete && \
rm -f src/sm/mac_sm/libmac_sm.so src/sm/mac_sm/libmac_sm_static.a \
examples/xApp/c/monitor/CMakeFiles/xapp_nakaolab.dir/xapp_nakaolab.c.o \
examples/xApp/c/monitor/CMakeFiles/xapp_nakaolab.dir/xapp_nakaolab.c.o.d && \
cmake --build . --target mac_sm mac_sm_static xapp_nakaolab -j2 && \
find /oai-ran/openair2/E2AP/flexric/build -name "*_sm.so" -exec cp {} /usr/local/lib/flexric/ \; && \
echo "/usr/local/lib/flexric" > /etc/ld.so.conf.d/flexric.conf && ldconfig && \
ls -la /usr/local/lib/flexric/
# Install netcat for antenna control testing + paramiko for M-Plane NETCONF server
@@ -57,4 +64,4 @@ RUN chmod +x /opt/mplane/gnb-mplane-entrypoint.sh
EXPOSE 831
ENTRYPOINT ["/bin/bash"]
ENTRYPOINT ["/bin/bash"]
+2 -2
View File
@@ -302,8 +302,8 @@ void nrue_init_openair0(void)
cfg->ru_id = ru_id;
cfg->num_rb_dl = frame_parms->N_RB_DL;
cfg->tx_num_channels = min(4, frame_parms->nb_antennas_tx);
cfg->rx_num_channels = min(4, frame_parms->nb_antennas_rx);
cfg->tx_num_channels = min(MAX_ANT, frame_parms->nb_antennas_tx);
cfg->rx_num_channels = min(MAX_ANT, frame_parms->nb_antennas_rx);
LOG_I(PHY,
"HW: Configuring ru_id %d, sample_rate %f, tx/rx num_channels %d/%d, duplex_mode %s\n",
+5 -4
View File
@@ -558,8 +558,8 @@ static int UE_dl_preprocessing(PHY_VARS_NR_UE *UE,
if (phy_data->dlsch[0].active
&& (phy_data->dlsch[0].rnti_type == TYPE_C_RNTI_ || phy_data->dlsch[0].rnti_type == TYPE_RA_RNTI_)) {
// indicate to tx thread to wait for DLSCH decoding
if (phy_data->dlsch[0].dlsch_config.k1_feedback) { // if feedback is 0 there is no HARQ associated with this DLSCH
const int ack_nack_slot = (proc->nr_slot_rx + phy_data->dlsch[0].dlsch_config.k1_feedback) % fp->slots_per_frame;
if (phy_data->dlsch_config.k1_feedback) { // if feedback is 0 there is no HARQ associated with this DLSCH
const int ack_nack_slot = (proc->nr_slot_rx + phy_data->dlsch_config.k1_feedback) % fp->slots_per_frame;
tx_wait_for_dlsch[ack_nack_slot]++;
}
}
@@ -763,8 +763,9 @@ void *UE_thread(void *arg)
if (get_softmodem_params()->sync_ref && UE->sl_mode == 2) {
UE->is_synchronized = 1;
} else {
//warm up the RF board
} else if (!IS_SOFTMODEM_RFSIM) {
// Warm up physical RF hardware. RFsim has no RF board to settle, and
// discarding 100 simulated frames only delays initial synchronization.
openair0_timestamp_t tmp;
for (int i = 0; i < 50; i++)
readFrame(UE, &tmp, duration_rx_to_tx, true);
+6 -1
View File
@@ -246,6 +246,12 @@ int main(int argc, char **argv)
softmodem_verify_mode(get_softmodem_params());
const char *cpu_meas_env = getenv("OAI_CPU_MEAS");
if (cpu_meas_env != NULL && strcmp(cpu_meas_env, "1") == 0) {
cpumeas(CPUMEAS_ENABLE);
LOG_I(UTIL, "UE PHY CPU measurements enabled by OAI_CPU_MEAS=1\n");
}
#if T_TRACER
T_Config_Init();
#endif
@@ -490,4 +496,3 @@ int main(int argc, char **argv)
printf("Bye.\n");
return 0;
}
Binary file not shown.
+23 -3
View File
@@ -394,16 +394,36 @@ class NetconfSession:
"Need antenna-bitmask or antenna-bitmask-index")
return
# Apply to gNB via internal control socket
# Apply to gNB via internal control socket and verify it succeeded.
# The gNB returns "OK: TX mask 0xAA -> 0xBB" on success; the socket
# wrapper returns "ERROR: ..." on transport failure and the gNB itself
# returns "ERROR: ..." for invalid input. Returning <ok/> when the
# underlying control failed would let the M-Plane state and the gNB
# state diverge.
mask_int = resolved["int"]
resp = self.gnb.send_command(f"SET TX MASK 0x{mask_int:02X}")
log.info("Applied mask 0x%02X to gNB: %s", mask_int, resp.strip())
resp_clean = (resp or "").strip()
# Update current config
if not resp_clean.upper().startswith("OK"):
log.warning("gNB rejected mask 0x%02X: %r", mask_int, resp_clean)
self._send_error(
msg_id,
"operation-failed",
"application",
f"gNB control did not confirm mask change: {resp_clean or 'no response'}",
)
return
log.info("Applied mask 0x%02X to gNB: %s", mask_int, resp_clean)
# Update current config only after the gNB confirmed.
self.cap.current_config[array_name] = {
"index": resolved["index"],
"mask_int": mask_int,
}
# Note: the production O1 path used by this paper runs through
# docker/o1-adapter/o1_antenna_bridge.py; this legacy gNB-side
# M-Plane server keeps the same OK-required check for parity.
# Log WG4 mplane-trx-control-ant-mask-update notification
log.info("WG4 mplane-trx-control-ant-mask-update: "
@@ -118,6 +118,7 @@ typedef struct {
typedef struct {
uint8_t harq_pid;
uint8_t cw_idx;
uint8_t ack_nack;
uint32_t pdu_length;
uint8_t* pdu;
@@ -462,10 +463,26 @@ typedef struct {
uint8_t power_control_offset; // Ratio of PDSCH EPRE to NZP CSI-RSEPRE [3GPP TS 38.214, sec 5.2.2.3.1], Value: 0->23 representing -8 to 15 dB in 1dB steps; 255: L1 is configured with ProfileSSS
uint8_t power_control_offset_ss; // Ratio of NZP CSI-RS EPRE to SSB/PBCH block EPRE [3GPP TS 38.214, sec 5.2.2.3.1], Values: 0: -3dB; 1: 0dB; 2: 3dB; 3: 6dB; 255: L1 is configured with ProfileSSS
uint8_t measurement_bitmap; // bit 0 RSRP, bit 1 RI, bit 2 LI, bit 3 PMI, bit 4 CQI, bit 5 i1
// Internal UE MAC-to-PHY context for Type-I single-panel PMI search. Zero
// means that no associated codebook report was found for this CSI-RS.
uint8_t codebook_n1;
uint8_t codebook_n2;
uint8_t codebook_mode;
} fapi_nr_dl_config_csirs_pdu_rel15_t;
typedef enum{vrb_to_prb_mapping_non_interleaved = 0, vrb_to_prb_mapping_interleaved = 1} vrb_to_prb_mapping_t;
typedef struct {
uint8_t mcs;
bool new_data_indicator;
uint8_t rv;
uint16_t targetCodeRate;
uint8_t qamModOrder;
uint32_t TBS;
uint8_t ldpcBaseGraph;
uint8_t Nl;
} fapi_nr_dl_cw_info_t;
typedef struct {
uint16_t BWPSize;
uint16_t BWPStart;
@@ -482,15 +499,8 @@ typedef struct {
uint8_t prb_bundling_size_ind;
uint8_t rate_matching_ind;
uint8_t zp_csi_rs_trigger;
uint8_t mcs;
bool new_data_indicator;
uint8_t rv;
uint16_t targetCodeRate;
uint8_t qamModOrder;
uint32_t TBS;
uint8_t tb2_mcs;
bool tb2_new_data_indicator;
uint8_t tb2_rv;
uint8_t n_codewords;
fapi_nr_dl_cw_info_t cw_info[2];
uint8_t harq_process_nbr;
vrb_to_prb_mapping_t vrb_to_prb_mapping;
uint8_t dai;
@@ -523,7 +533,6 @@ typedef struct {
uint16_t dlDataScramblingId;
uint16_t pduBitmap;
uint32_t k1_feedback;
uint8_t ldpcBaseGraph;
uint8_t numCsiRsForRateMatching;
fapi_nr_dl_config_csirs_pdu_rel15_t csiRsForRateMatching[NFAPI_MAX_NUM_CSI_RATEMATCH];
} fapi_nr_dl_config_dlsch_pdu_rel15_t;
@@ -744,4 +753,3 @@ typedef struct {
} fapi_nr_config_request_t;
#endif
@@ -246,10 +246,31 @@ void nr_polar_info_bit_pattern(uint8_t *ibp,
}
if (n_PC > 0) {
AssertFatal(n_pc_wm == 0, "Q_PC_N computation for n_pc_wm = %i is not implemented yet!\n", n_pc_wm);
AssertFatal(n_pc_wm <= 1, "n_pc_wm = %i is not valid (only 0 and 1 allowed)\n", n_pc_wm);
// As per 5.3.1.2 of 38.212, denote the set of bit indices for parity check bits as Q_PC_N
// A number of (n_PC - n_pc_wm) parity check bits are placed in the (n_PC - n_pc_wm) least reliable bit indices
for (int n = 0; n < n_PC - n_pc_wm; n++) {
Q_PC_N[n] = Q_I_N[n];
}
// A number of n_pc_wm other parity check bits are placed in the bit indices of minimum row weight of most reliable bit indices
if (n_pc_wm == 1) {
// tilde{Q_I}^N = K most reliable indices of bar{Q_I}^N = Q_I_N[n_PC .. K+n_PC-1].
// Find minimum row weight over this range; on ties, keep the most reliable
// (later position, since Q_I_N is ascending by reliability).
int min_weight = INT_MAX;
int best_pos = -1;
for (int n = n_PC; n <= (K + n_PC) - 1; n++) {
const int w = __builtin_popcount((unsigned)Q_I_N[n]);
if (w <= min_weight) {
min_weight = w;
best_pos = n;
}
}
AssertFatal(best_pos >= 0, "No candidate found in tilde{Q_I}^N for n_pc_wm = 1 (K=%u, n_PC=%u)", K, n_PC);
Q_PC_N[n_PC - 1] = Q_I_N[best_pos];
}
}
// Q_F_N = Q_0_N-1 \ Q_I_N
@@ -266,7 +287,7 @@ void nr_polar_info_bit_pattern(uint8_t *ibp,
}
}
// Information and Parity Chack Bit Pattern
// Information and Parity Check Bit Pattern
for (int n = 0; n <= N - 1; n++) {
ibp[n] = 0;
for (int m = 0; m <= (K + n_PC) - 1; m++) {
@@ -277,7 +298,7 @@ void nr_polar_info_bit_pattern(uint8_t *ibp,
}
pcbp[n] = 0;
for (int m = 0; m < n_PC - n_pc_wm; m++) {
for (int m = 0; m < n_PC; m++) {
if (n == Q_PC_N[m]) {
pcbp[n] = 1;
break;
+7 -3
View File
@@ -160,8 +160,12 @@ int init_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB)
LOG_I(PHY, "Initializing UE vars for gNB TXant %u, UE RXant %u\n", fp->nb_antennas_tx, fp->nb_antennas_rx);
phy_init_nr_top(ue);
// many memory allocation sizes are hard coded
AssertFatal( fp->nb_antennas_rx <= 4, "hard coded allocation for ue_common_vars->dl_ch_estimates[gNB_id]" );
// NR UE receive buffers are dimensioned dynamically. Keep the explicit
// platform bound so an invalid RF configuration fails before allocation.
AssertFatal(fp->nb_antennas_rx > 0 && fp->nb_antennas_rx <= MAX_ANT,
"UE RX antenna count %u exceeds platform maximum %u",
fp->nb_antennas_rx,
MAX_ANT);
AssertFatal( nb_connected_gNB <= NUMBER_OF_CONNECTED_gNB_MAX, "n_connected_gNB is too large" );
// init phy_vars_ue
@@ -170,7 +174,7 @@ int init_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB)
ue->measurements.ssb_sinr_dB[i] = INT_MIN;
}
for (int i = 0; i < 4; i++) {
for (int i = 0; i < fp->nb_antennas_rx; i++) {
ue->rx_gain_max[i] = 135;
ue->rx_gain_med[i] = 128;
ue->rx_gain_byp[i] = 120;
+41 -57
View File
@@ -216,15 +216,19 @@ void nr_modulation(const uint32_t *in, uint32_t length, uint16_t mod_order, int1
}
void nr_layer_mapping(int nbCodes,
int encoded_len,
c16_t mod_symbs[nbCodes][encoded_len],
const c16_t *const mod_symbs[NR_MAX_NB_CODEWORDS],
const uint32_t n_symbs_per_codeword[NR_MAX_NB_CODEWORDS],
uint8_t n_layers,
int layerSz,
uint32_t n_symbs,
c16_t tx_layers[][layerSz])
{
const uint32_t n_symbs = n_symbs_per_codeword[0];
LOG_D(PHY, "Doing layer mapping for %d layers, %d symbols\n", n_layers, n_symbs);
c16_t *mod = mod_symbs[0];
const c16_t *mod = mod_symbs[0];
AssertFatal(nbCodes == nr_pdsch_num_codewords(n_layers),
"Layer mapping got %d codewords for %u layers\n",
nbCodes,
n_layers);
switch (n_layers) {
case 1:
memcpy(tx_layers[0], mod, n_symbs * sizeof(**mod_symbs));
@@ -472,58 +476,26 @@ void nr_layer_mapping(int nbCodes,
case 5:
case 6:
case 7:
case 8:
/*
// Layer 0,1
for (int i = 0; i < n_symbs; i += 2) {
const int txIdx = i / 2;
tx_layer[0][txIdx] = mod_symbs[0][i];
tx_layer[1][txIdx] = mod_symbs[0][i + 1];
case 8: {
int first_layer = 0;
for (int q = 0; q < nbCodes; q++) {
const int cw_layers = nr_pdsch_layers_per_codeword(n_layers, q);
AssertFatal(n_symbs_per_codeword[q] % cw_layers == 0,
"Codeword %d has %u symbols, not divisible by %d layers\n",
q,
n_symbs_per_codeword[q],
cw_layers);
const uint32_t symbols_per_layer = n_symbs_per_codeword[q] / cw_layers;
AssertFatal(symbols_per_layer <= layerSz,
"Layer buffer too small: need %u, have %d symbols\n",
symbols_per_layer,
layerSz);
for (uint32_t i = 0; i < symbols_per_layer; i++)
for (int l = 0; l < cw_layers; l++)
tx_layers[first_layer + l][i] = mod_symbs[q][i * cw_layers + l];
first_layer += cw_layers;
}
// layers 2,3,4
else
for (int i = 0; i < n_symbs; i += 3) {
const int txIdx = i / 3;
tx_layer[2][txIdx] = mod_symbs[1][i + 2];
tx_layer[3][txIdx] = mod_symbs[1][i + 3];
tx_layer[4][txIdx] = mod_symbs[1][i + 4];
}
break;
case 6:
for (int q=0; q<2; q++)
for (int i = 0; i < n_symbs; i += 3) {
const int txIdx = i / 3;
tx_layer[0][txIdx] = mod_symbs[q][i + layer];
tx_layer[1][txIdx] = mod_symbs[q][i + layer];
tx_layer[2][txIdx] = mod_symbs[q][i + layer];
tx_layer[3][txIdx] = mod_symbs[q][i + layer];
tx_layer[4][txIdx] = mod_symbs[q][i + layer];
tx_layer[5][txIdx] = mod_symbs[q][i + layer];
}
break;
case 7:
if (layer < 3)
for (int i = 0; i < n_symbs; i += 3) {
const int txIdx = i / 3;
tx_layer[txIdx] = mod_symbs[1][i + layer];
}
else
for (int i = 0; i < n_symbs; i += 4) {
const int txIdx = i / 4;
tx_layer[txIdx] = mod_symbs[0][i + layer];
}
break;
case 8:
for (int q=0; q<2; q++)
for (int i = 0; i < n_symbs; i += 4) {
const int txIdx = i / 4;
tx_layer[txIdx] = mod_symbs[q][i + layer];
}
break;
*/
} break;
default:
AssertFatal(0, "Invalid number of layers %d\n", n_layers);
}
@@ -687,7 +659,7 @@ c16_t nr_layer_precoder(int sz, c16_t datatx_F_precoding[][sz], const char *prec
c16_t nr_layer_precoder_cm(int n_layers,
int symSz,
c16_t datatx_F_precoding[n_layers][symSz],
const c16_t datatx_F_precoding[n_layers][symSz],
int ap,
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
int offset)
@@ -819,7 +791,19 @@ void nr_layer_precoder_simd(const int n_layers,
{
// For x86, use 256 SIMD for every 8 RE and 128 SIMD for last 4 RE
// For aarch64, use 128 SIMD for every 4 RE
AssertFatal(n_layers > 0 && n_layers <= 4, "Shouldn't get here, n_layers %d\n", n_layers);
AssertFatal(n_layers > 0 && n_layers <= NR_MAX_NB_LAYERS, "Invalid PDSCH layer count %d\n", n_layers);
/* The hand-vectorized kernels below remain the fast path for ranks 1--4.
* Rank 5--8 is uncommon and uses the same full complex matrix operation via
* the scalar reference kernel. Unlike antenna fan-out, this combines every
* distinct layer with its own precoder weight and is therefore a genuine
* high-rank precoder. */
if (n_layers > 4) {
for (int re = 0; re < re_cnt; re++)
txdataF_precoded[sc_offset + re] =
nr_layer_precoder_cm(n_layers, symSz, txdataF_res_mapped, ant, weights, sc_offset + re);
return;
}
// 512/256 SIMD: Do 16/8 RE in one iteration, 3 iterations for 2 RB
c16_t *beginning = txdataF_precoded + sc_offset;
+4 -4
View File
@@ -7,6 +7,7 @@
#include <stdint.h>
#include "PHY/defs_nr_common.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
#define DMRS_MOD_ORDER 2
/*Precoding matices: W[pmi][antenna_port][layer]*/
@@ -36,11 +37,10 @@ void nr_modulation(const uint32_t *in,
*/
void nr_layer_mapping(int nbCodes,
int encoded_len,
c16_t mod_symbs[nbCodes][encoded_len],
const c16_t *const mod_symbs[NR_MAX_NB_CODEWORDS],
const uint32_t n_symbs_per_codeword[NR_MAX_NB_CODEWORDS],
uint8_t n_layers,
int layerSz,
uint32_t n_symbs,
c16_t tx_layers[][layerSz]);
/*! \brief Perform NR layer mapping. TS 38.211 V15.4.0 subclause 7.3.1.3
@@ -151,7 +151,7 @@ c16_t nr_layer_precoder(int sz, c16_t datatx_F_precoding[][sz], const char *prec
c16_t nr_layer_precoder_cm(int n_layers,
int symSz,
c16_t datatx_F_precoding[n_layers][symSz],
const c16_t datatx_F_precoding[n_layers][symSz],
int ap,
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
int offset);
@@ -16,13 +16,14 @@ extern "C" {
#include "openair1/PHY/TOOLS/tools_defs.h" // c16_t
#include "nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h" // fapi_nr_dl_config_dci_dl_pdu_rel15_t
#include "common/utils/LOG/log.h"
//#include "openair1/PHY/MODULATION/nr_modulation.h" // nr_layer_precoder_simd_[cm, simd]
// Forward declare the function but hide the Variable-Length Array (VLA) feature from C not available in C++
c16_t nr_layer_precoder_cm(int n_layers,
int symbol_size,
c16_t (*dataF_in)[/*symbol_size*/], // c16_t dataF_in[n_layers][symbol_size]
const c16_t (*dataF_in)[/*symbol_size*/], // c16_t dataF_in[n_layers][symbol_size]
int ant,
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
int offset);
@@ -37,6 +38,14 @@ void nr_layer_precoder_simd(int n_layers,
int re_cnt,
c16_t *dataF_out);
// Forward declare the layer mapper while hiding its C VLA dimension.
void nr_layer_mapping(int nb_codewords,
const c16_t *const mod_symbols[2],
const uint32_t n_symbols_per_codeword[2],
uint8_t n_layers,
int layer_size,
c16_t (*tx_layers)[/*layer_size*/]);
#ifdef __cplusplus
}
#endif
@@ -185,11 +194,50 @@ TEST(NrLayerPrecoderTest, Compare_CM_SIMD)
}
}
TEST(NrLayerMappingTest, Rank8UsesTwoCodewordsAcrossEightDistinctLayers)
{
constexpr int n_codewords = 2;
constexpr int n_layers = 8;
constexpr int layers_per_codeword = 4;
constexpr int symbols_per_layer = 7;
constexpr int symbols_per_codeword = layers_per_codeword * symbols_per_layer;
std::vector<c16_t> cw0(symbols_per_codeword);
std::vector<c16_t> cw1(symbols_per_codeword);
for (int i = 0; i < symbols_per_codeword; ++i) {
cw0[i] = {static_cast<int16_t>(100 + i), static_cast<int16_t>(-100 - i)};
cw1[i] = {static_cast<int16_t>(1000 + i), static_cast<int16_t>(-1000 - i)};
}
const c16_t *mod_symbols[2] = {cw0.data(), cw1.data()};
const uint32_t n_symbols_per_codeword[2] = {symbols_per_codeword, symbols_per_codeword};
std::vector<c16_t> layer_storage(n_layers * symbols_per_layer, c16_t{SHRT_MIN, SHRT_MIN});
c16_t(*tx_layers)[symbols_per_layer] = reinterpret_cast<c16_t(*)[symbols_per_layer]>(layer_storage.data());
nr_layer_mapping(n_codewords,
mod_symbols,
n_symbols_per_codeword,
n_layers,
symbols_per_layer,
tx_layers);
for (int re = 0; re < symbols_per_layer; ++re) {
for (int layer = 0; layer < n_layers; ++layer) {
const int cw = layer / layers_per_codeword;
const int cw_layer = layer % layers_per_codeword;
const c16_t expected = mod_symbols[cw][re * layers_per_codeword + cw_layer];
EXPECT_EQ(tx_layers[layer][re].r, expected.r) << "real mismatch at layer " << layer << ", RE " << re;
EXPECT_EQ(tx_layers[layer][re].i, expected.i) << "imag mismatch at layer " << layer << ", RE " << re;
}
}
}
int main(int argc, char **argv)
{
// Initialize random seed
srand(time(0));
logInit();
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+223 -20
View File
@@ -39,6 +39,7 @@
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
// Try to include M-Plane header if available
#ifdef OAI_MPLANE
@@ -54,6 +55,11 @@ static struct {
volatile bool running;
int port;
nr_antenna_ctrl_backend_t backend;
int64_t tx_lock_until_ms[NR_MAX_ANTENNAS];
pthread_mutex_t command_lock;
pthread_mutex_t clients_lock;
pthread_cond_t clients_done;
unsigned int client_count;
#ifdef OAI_MPLANE
ru_session_t *ru_session;
#else
@@ -61,6 +67,44 @@ static struct {
#endif
} g_ctrl = {0};
static bool valid_tsm_duration(int seconds)
{
return seconds == 0 || seconds == 15 || seconds == 30 ||
seconds == 45 || seconds == 60;
}
static int64_t monotonic_ms(void)
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000;
}
static int64_t tx_lock_remaining_ms(int ant_id)
{
if (ant_id < 0 || ant_id >= NR_MAX_ANTENNAS)
return 0;
int64_t remaining = g_ctrl.tx_lock_until_ms[ant_id] - monotonic_ms();
if (remaining <= 0) {
g_ctrl.tx_lock_until_ms[ant_id] = 0;
return 0;
}
return remaining;
}
static void set_tx_lock(int ant_id, int seconds)
{
if (ant_id < 0 || ant_id >= NR_MAX_ANTENNAS)
return;
g_ctrl.tx_lock_until_ms[ant_id] =
seconds > 0 ? monotonic_ms() + (int64_t)seconds * 1000 : 0;
}
static void clear_all_tx_locks(void)
{
memset(g_ctrl.tx_lock_until_ms, 0, sizeof(g_ctrl.tx_lock_until_ms));
}
// ============================================================================
// Test Socket Server Implementation (Development/Testing Mode)
// ============================================================================
@@ -84,14 +128,44 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
while (end > p && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = '\0';
if (strncmp(p, "MUTE TX ", 8) == 0) {
int ant_id = atoi(p + 8);
int ant_id = -1;
int duration = 0;
int fields = sscanf(p + 8, "%d %d", &ant_id, &duration);
if (fields < 1 || ant_id < 0 || ant_id >= g_ctrl.ant_state->num_tx_antennas) {
snprintf(response, resp_size, "ERROR: Invalid TX antenna id\n");
return;
}
if (fields >= 2 && !valid_tsm_duration(duration)) {
snprintf(response, resp_size,
"ERROR: Unsupported TSM duration %d; use 15, 30, 45, or 60 seconds\n",
duration);
return;
}
nr_mute_tx_antenna(g_ctrl.ant_state, ant_id);
snprintf(response, resp_size, "OK: TX antenna %d muted\n", ant_id);
set_tx_lock(ant_id, duration);
snprintf(response, resp_size, "OK: TX antenna %d muted (TSM=%ds)\n",
ant_id, duration);
}
else if (strncmp(p, "UNMUTE TX ", 10) == 0) {
int ant_id = atoi(p + 10);
int ant_id = -1;
char option[16] = {0};
int fields = sscanf(p + 10, "%d %15s", &ant_id, option);
if (fields < 1 || ant_id < 0 || ant_id >= g_ctrl.ant_state->num_tx_antennas) {
snprintf(response, resp_size, "ERROR: Invalid TX antenna id\n");
return;
}
bool force = fields >= 2 && strcmp(option, "FORCE") == 0;
int64_t remaining = tx_lock_remaining_ms(ant_id);
if (remaining > 0 && !force) {
snprintf(response, resp_size,
"ERROR: TX antenna %d locked for %ld more ms\n",
ant_id, (long)remaining);
return;
}
set_tx_lock(ant_id, 0);
nr_unmute_tx_antenna(g_ctrl.ant_state, ant_id);
snprintf(response, resp_size, "OK: TX antenna %d unmuted\n", ant_id);
snprintf(response, resp_size, "OK: TX antenna %d unmuted%s\n",
ant_id, force ? " (emergency override)" : "");
}
else if (strncmp(p, "MUTE RX ", 8) == 0) {
int ant_id = atoi(p + 8);
@@ -105,12 +179,48 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
}
else if (strncmp(p, "SET TX MASK ", 12) == 0) {
unsigned int mask;
if (sscanf(p + 12, "%x", &mask) == 1 || sscanf(p + 12, "0X%x", &mask) == 1) {
uint8_t old = nr_set_tx_antenna_bitmask(g_ctrl.ant_state, (uint8_t)mask);
snprintf(response, resp_size, "OK: TX mask 0x%02X -> 0x%02X\n", old, (uint8_t)mask);
} else {
int duration = 0;
char option[16] = {0};
int fields = sscanf(p + 12, "%x %d %15s", &mask, &duration, option);
if (fields < 1) {
snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n");
return;
}
if (fields >= 2 && !valid_tsm_duration(duration)) {
snprintf(response, resp_size,
"ERROR: Unsupported TSM duration %d; use 15, 30, 45, or 60 seconds\n",
duration);
return;
}
bool force = fields >= 3 && strcmp(option, "FORCE") == 0;
uint8_t valid_mask = (1u << g_ctrl.ant_state->num_tx_antennas) - 1u;
uint8_t requested = (uint8_t)mask & valid_mask;
uint8_t old_requested = nr_get_requested_tx_antenna_bitmask(g_ctrl.ant_state);
uint8_t waking = requested & ~old_requested;
for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) {
if ((waking & (1u << ant_id)) && tx_lock_remaining_ms(ant_id) > 0 && !force) {
snprintf(response, resp_size,
"ERROR: TX antenna %d is still TSM-locked\n", ant_id);
return;
}
}
nr_request_tx_antenna_bitmask(g_ctrl.ant_state, requested);
uint8_t muted = old_requested & ~requested;
for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) {
uint8_t bit = 1u << ant_id;
if (muted & bit)
set_tx_lock(ant_id, duration);
if (waking & bit)
set_tx_lock(ant_id, 0);
}
bool pending = nr_get_pending_tx_antenna_bitmask(g_ctrl.ant_state, NULL);
uint8_t active = nr_get_tx_antenna_bitmask(g_ctrl.ant_state);
snprintf(response, resp_size,
"OK: TX mask 0x%02X -> 0x%02X (active=0x%02X%s, TSM=%ds%s)\n",
old_requested, requested, active,
pending ? ", HARQ drain pending" : "",
duration, force ? ", emergency override" : "");
}
else if (strncmp(p, "SET RX MASK ", 12) == 0) {
unsigned int mask;
@@ -123,11 +233,15 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
}
else if (strcmp(p, "STATUS") == 0) {
bool in_recovery = nr_is_in_recovery_mode(g_ctrl.ant_state);
uint8_t requested_mask = nr_get_requested_tx_antenna_bitmask(g_ctrl.ant_state);
bool pending = nr_get_pending_tx_antenna_bitmask(g_ctrl.ant_state, NULL);
snprintf(response, resp_size,
"=== Antenna Status ===\n"
"TX Array: %s\n"
" Antennas: %d configured, %d active\n"
" Bitmask: 0x%02X\n"
" Requested bitmask: 0x%02X\n"
" HARQ drain pending: %s\n"
" Antenna 0 protected: %s\n"
"RX Array: %s\n"
" Antennas: %d configured, %d active\n"
@@ -138,6 +252,8 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
g_ctrl.ant_state->num_tx_antennas,
nr_get_active_tx_antenna_count(g_ctrl.ant_state),
nr_get_tx_antenna_bitmask(g_ctrl.ant_state),
requested_mask,
pending ? "YES" : "NO",
g_ctrl.ant_state->config.protect_antenna0 ? "YES" : "NO",
g_ctrl.ant_state->rx_array_name,
g_ctrl.ant_state->num_rx_antennas,
@@ -147,6 +263,20 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
g_ctrl.backend == ANTENNA_CTRL_BACKEND_MPLANE ? "O-RAN M-Plane NETCONF" :
g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET ? "Test Socket (NOT O-RAN)" :
"None");
size_t used = strlen(response);
if (used < resp_size) {
used += snprintf(response + used, resp_size - used, "TSM locks:");
for (int ant_id = 0;
ant_id < g_ctrl.ant_state->num_tx_antennas && used < resp_size;
++ant_id) {
int64_t remaining = tx_lock_remaining_ms(ant_id);
if (remaining > 0)
used += snprintf(response + used, resp_size - used,
" TX%d=%lds", ant_id, (long)((remaining + 999) / 1000));
}
if (used < resp_size)
snprintf(response + used, resp_size - used, "\n");
}
}
else if (strcmp(p, "STATS") == 0) {
uint64_t tx_events, rx_events, muted_slots;
@@ -164,6 +294,7 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
}
else if (strcmp(p, "RESET") == 0 || strcmp(p, "UNMUTE ALL") == 0) {
// Reset all antennas to active state with recovery mode
clear_all_tx_locks();
nr_unmute_all_tx_antennas(g_ctrl.ant_state);
nr_unmute_all_rx_antennas(g_ctrl.ant_state);
snprintf(response, resp_size,
@@ -193,9 +324,11 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
else if (strcmp(p, "HELP") == 0) {
snprintf(response, resp_size,
"=== Antenna Control ===\n"
"MUTE TX/RX <id> - Mute antenna\n"
"UNMUTE TX/RX <id> - Unmute antenna\n"
"SET TX/RX MASK <hex> - Set bitmask\n"
"MUTE TX <id> [15|30|45|60] - Mute TX antenna with optional TSM lock\n"
"UNMUTE TX <id> [FORCE] - Unmute TX antenna\n"
"MUTE/UNMUTE RX <id> - Change RX antenna state\n"
"SET TX MASK <hex> [duration] [FORCE] - Set TX mask and TSM lock\n"
"SET RX MASK <hex> - Set RX bitmask\n"
"STATUS - Show state\n"
"STATS - Show statistics\n"
"RESET - Unmute all\n"
@@ -213,6 +346,13 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
}
}
static void send_client_prompt(int client_fd)
{
const char prompt[] = "> ";
if (send(client_fd, prompt, sizeof(prompt) - 1, MSG_NOSIGNAL) < 0)
LOG_D(PHY, "[ANTENNA_CTRL] Failed to send client prompt: %s\n", strerror(errno));
}
// Handle a single client connection
static void handle_client(int client_fd)
{
@@ -225,24 +365,28 @@ static void handle_client(int client_fd)
"WARNING: This is a TEST interface, NOT O-RAN compliant!\n"
"For production, use NETCONF via radio/fhi_72/mplane/\n"
"Type HELP for commands.\n\n";
send(client_fd, welcome, strlen(welcome), 0);
send(client_fd, welcome, strlen(welcome), MSG_NOSIGNAL);
send_client_prompt(client_fd);
while (g_ctrl.running) {
// Send prompt
send(client_fd, "> ", 2, 0);
// Receive command
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n <= 0) break;
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
continue;
if (n <= 0)
break;
buffer[n] = '\0';
// Process command
// Serialize state-changing commands while allowing multiple connected
// clients (for example the persistent O1 bridge and a diagnostic client).
pthread_mutex_lock(&g_ctrl.command_lock);
process_command(buffer, response, sizeof(response));
pthread_mutex_unlock(&g_ctrl.command_lock);
// Send response
if (strlen(response) > 0) {
send(client_fd, response, strlen(response), 0);
send(client_fd, response, strlen(response), MSG_NOSIGNAL);
}
// Check for quit
@@ -250,12 +394,26 @@ static void handle_client(int client_fd)
strstr(buffer, "EXIT") || strstr(buffer, "exit")) {
break;
}
send_client_prompt(client_fd);
}
close(client_fd);
LOG_I(PHY, "[ANTENNA_CTRL] Client disconnected\n");
}
static void *client_thread(void *arg)
{
const int client_fd = (int)(intptr_t)arg;
handle_client(client_fd);
pthread_mutex_lock(&g_ctrl.clients_lock);
AssertFatal(g_ctrl.client_count > 0, "Antenna-control client count underflow\n");
g_ctrl.client_count--;
pthread_cond_broadcast(&g_ctrl.clients_done);
pthread_mutex_unlock(&g_ctrl.clients_lock);
return NULL;
}
// Server thread function
static void *server_thread(void *arg)
{
@@ -282,8 +440,27 @@ static void *server_thread(void *arg)
LOG_I(PHY, "[ANTENNA_CTRL] Client connected from %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
handle_client(client_fd);
// Bound recv() so shutdown can wait for detached client workers without
// leaving a thread blocked forever on an idle persistent connection.
struct timeval client_timeout = {.tv_sec = 1, .tv_usec = 0};
setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &client_timeout, sizeof(client_timeout));
pthread_mutex_lock(&g_ctrl.clients_lock);
g_ctrl.client_count++;
pthread_mutex_unlock(&g_ctrl.clients_lock);
pthread_t client;
if (pthread_create(&client, NULL, client_thread, (void *)(intptr_t)client_fd) != 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create client thread\n");
close(client_fd);
pthread_mutex_lock(&g_ctrl.clients_lock);
g_ctrl.client_count--;
pthread_cond_broadcast(&g_ctrl.clients_done);
pthread_mutex_unlock(&g_ctrl.clients_lock);
continue;
}
pthread_detach(client);
}
return NULL;
@@ -350,11 +527,18 @@ bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int por
g_ctrl.ant_state = ant_state;
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_TEST_PORT;
g_ctrl.backend = ANTENNA_CTRL_BACKEND_TEST_SOCKET;
pthread_mutex_init(&g_ctrl.command_lock, NULL);
pthread_mutex_init(&g_ctrl.clients_lock, NULL);
pthread_cond_init(&g_ctrl.clients_done, NULL);
g_ctrl.client_count = 0;
// Create socket
g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (g_ctrl.server_fd < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create socket: %s\n", strerror(errno));
pthread_cond_destroy(&g_ctrl.clients_done);
pthread_mutex_destroy(&g_ctrl.clients_lock);
pthread_mutex_destroy(&g_ctrl.command_lock);
return false;
}
@@ -372,6 +556,9 @@ bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int por
if (bind(g_ctrl.server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to bind port %d: %s\n", g_ctrl.port, strerror(errno));
close(g_ctrl.server_fd);
pthread_cond_destroy(&g_ctrl.clients_done);
pthread_mutex_destroy(&g_ctrl.clients_lock);
pthread_mutex_destroy(&g_ctrl.command_lock);
return false;
}
@@ -379,6 +566,9 @@ bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int por
if (listen(g_ctrl.server_fd, 1) < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to listen: %s\n", strerror(errno));
close(g_ctrl.server_fd);
pthread_cond_destroy(&g_ctrl.clients_done);
pthread_mutex_destroy(&g_ctrl.clients_lock);
pthread_mutex_destroy(&g_ctrl.command_lock);
return false;
}
@@ -388,6 +578,9 @@ bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int por
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create server thread\n");
g_ctrl.running = false;
close(g_ctrl.server_fd);
pthread_cond_destroy(&g_ctrl.clients_done);
pthread_mutex_destroy(&g_ctrl.clients_lock);
pthread_mutex_destroy(&g_ctrl.command_lock);
return false;
}
@@ -421,6 +614,16 @@ void nr_antenna_control_stop(void)
// Wait for thread
pthread_join(g_ctrl.thread, NULL);
// Client workers wake from recv() at least once per second and observe
// running=false. Keep ant_state valid until every worker has exited.
pthread_mutex_lock(&g_ctrl.clients_lock);
while (g_ctrl.client_count > 0)
pthread_cond_wait(&g_ctrl.clients_done, &g_ctrl.clients_lock);
pthread_mutex_unlock(&g_ctrl.clients_lock);
pthread_cond_destroy(&g_ctrl.clients_done);
pthread_mutex_destroy(&g_ctrl.clients_lock);
pthread_mutex_destroy(&g_ctrl.command_lock);
}
g_ctrl.backend = ANTENNA_CTRL_BACKEND_NONE;
@@ -44,11 +44,11 @@ bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state,
* NETCONF infrastructure. This is NOT O-RAN compliant.
*
* Commands supported (for backwards compatibility):
* MUTE TX <ant_id> - Mute TX antenna
* UNMUTE TX <ant_id> - Unmute TX antenna
* MUTE TX <ant_id> [15|30|45|60] - Mute TX antenna with optional TSM lock
* UNMUTE TX <ant_id> [FORCE] - Unmute TX antenna; FORCE breaks lock
* MUTE RX <ant_id> - Mute RX antenna
* UNMUTE RX <ant_id> - Unmute RX antenna
* SET TX MASK <hex> - Set TX antenna bitmask
* SET TX MASK <hex> [duration] [FORCE] - Set TX mask and per-chain TSM lock
* SET RX MASK <hex> - Set RX antenna bitmask
* STATUS - Get current antenna state
* STATS - Get energy saving statistics
+103 -33
View File
@@ -36,6 +36,8 @@ void nr_init_antenna_state(nr_antenna_state_t *state,
// Initialize with all configured antennas active
// Create bitmask with num_tx/num_rx lowest bits set
state->tx_antenna_bitmask = (1 << state->num_tx_antennas) - 1;
state->requested_tx_antenna_bitmask = state->tx_antenna_bitmask;
state->tx_mask_update_pending = false;
state->rx_antenna_bitmask = (1 << state->num_rx_antennas) - 1;
// Set O-RAN array names
@@ -76,33 +78,103 @@ void nr_free_antenna_state(nr_antenna_state_t *state)
pthread_mutex_destroy(&state->lock);
}
static uint8_t valid_tx_mask(const nr_antenna_state_t *state)
{
return (uint8_t)((1u << state->num_tx_antennas) - 1u);
}
/* Caller holds state->lock. */
static void set_tx_antenna_bitmask_locked(nr_antenna_state_t *state, uint8_t bitmask)
{
const uint8_t old_bitmask = state->tx_antenna_bitmask;
state->tx_antenna_bitmask = bitmask & valid_tx_mask(state);
if (state->tx_antenna_bitmask == old_bitmask)
return;
state->stats.tx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] TX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
old_bitmask,
state->tx_antenna_bitmask,
state->tx_array_name,
popcount8(state->tx_antenna_bitmask),
state->num_tx_antennas);
}
uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
{
if (state == NULL) return 0;
pthread_mutex_lock(&state->lock);
uint8_t old_bitmask = state->tx_antenna_bitmask;
// Mask to only valid antenna bits
uint8_t valid_mask = (1 << state->num_tx_antennas) - 1;
state->tx_antenna_bitmask = bitmask & valid_mask;
// Update statistics
if (state->tx_antenna_bitmask != old_bitmask) {
state->stats.tx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] TX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
old_bitmask, state->tx_antenna_bitmask, state->tx_array_name,
popcount8(state->tx_antenna_bitmask), state->num_tx_antennas);
}
const uint8_t old_bitmask = state->tx_antenna_bitmask;
state->requested_tx_antenna_bitmask = bitmask & valid_tx_mask(state);
state->tx_mask_update_pending = false;
set_tx_antenna_bitmask_locked(state, state->requested_tx_antenna_bitmask);
pthread_mutex_unlock(&state->lock);
return old_bitmask;
}
uint8_t nr_request_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
{
if (state == NULL) return 0;
pthread_mutex_lock(&state->lock);
const uint8_t old_requested = state->requested_tx_antenna_bitmask;
const uint8_t requested = bitmask & valid_tx_mask(state);
state->requested_tx_antenna_bitmask = requested;
/* Adding chains cannot invalidate an in-flight PDSCH. Removing a chain can,
* so leave the physical mask unchanged until MAC has drained HARQ processes
* whose original layer count exceeds the new target. */
if ((state->tx_antenna_bitmask & (uint8_t)~requested) == 0) {
state->tx_mask_update_pending = false;
set_tx_antenna_bitmask_locked(state, requested);
} else {
state->tx_mask_update_pending = true;
LOG_I(PHY,
"[ANTENNA_STATE] TX downshift requested: active=0x%02X target=0x%02X; waiting for DL HARQ drain\n",
state->tx_antenna_bitmask,
requested);
}
pthread_mutex_unlock(&state->lock);
return old_requested;
}
uint8_t nr_get_requested_tx_antenna_bitmask(const nr_antenna_state_t *state)
{
if (state == NULL) return 0xFF;
pthread_mutex_lock((pthread_mutex_t *)&state->lock);
const uint8_t requested = state->requested_tx_antenna_bitmask;
pthread_mutex_unlock((pthread_mutex_t *)&state->lock);
return requested;
}
bool nr_get_pending_tx_antenna_bitmask(const nr_antenna_state_t *state, uint8_t *bitmask)
{
if (state == NULL) return false;
pthread_mutex_lock((pthread_mutex_t *)&state->lock);
const bool pending = state->tx_mask_update_pending;
if (pending && bitmask != NULL)
*bitmask = state->requested_tx_antenna_bitmask;
pthread_mutex_unlock((pthread_mutex_t *)&state->lock);
return pending;
}
bool nr_apply_requested_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t expected_bitmask)
{
if (state == NULL) return false;
pthread_mutex_lock(&state->lock);
const bool matches = state->tx_mask_update_pending
&& state->requested_tx_antenna_bitmask == expected_bitmask;
if (matches) {
set_tx_antenna_bitmask_locked(state, expected_bitmask);
state->tx_mask_update_pending = false;
}
pthread_mutex_unlock(&state->lock);
return matches;
}
uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
{
if (state == NULL) return 0;
@@ -183,16 +255,16 @@ void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
return;
}
uint8_t new_mask = state->tx_antenna_bitmask & ~(1 << ant_id);
nr_set_tx_antenna_bitmask(state, new_mask);
uint8_t new_mask = nr_get_requested_tx_antenna_bitmask(state) & ~(1 << ant_id);
nr_request_tx_antenna_bitmask(state, new_mask);
}
void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_tx_antennas) return;
uint8_t new_mask = state->tx_antenna_bitmask | (1 << ant_id);
nr_set_tx_antenna_bitmask(state, new_mask);
uint8_t new_mask = nr_get_requested_tx_antenna_bitmask(state) | (1 << ant_id);
nr_request_tx_antenna_bitmask(state, new_mask);
}
void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
@@ -216,16 +288,12 @@ int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_
if (state == NULL)
return requested_layers;
// Reduce MIMO layers to match the number of active TX antennas.
// When antennas are muted, the RU blanks the corresponding TX buffers.
// If the scheduler still uses full layer count, the precoded signal is
// distributed across all antenna ports (including muted ones), which
// corrupts the UE's MIMO reception. By reducing layers to match active
// antennas, the PMI fallback to identity precoding maps layers 1:1 to
// active ports, avoiding signal loss on muted ports.
int active = nr_get_active_tx_antenna_count(state);
if (active > 0 && active < requested_layers)
return active;
/* Cap new transmissions to the target immediately. During a downshift the
* physical mask can remain wider for a few HARQ rounds, which is safe; the
* reverse ordering would erase layers from buffered retransmissions. */
const int target = popcount8(nr_get_requested_tx_antenna_bitmask(state));
if (target > 0 && target < requested_layers)
return target;
return requested_layers;
}
@@ -296,8 +364,10 @@ void nr_unmute_all_tx_antennas(nr_antenna_state_t *state)
uint8_t old_mask = state->tx_antenna_bitmask;
uint8_t all_on = (1 << state->num_tx_antennas) - 1;
state->requested_tx_antenna_bitmask = all_on;
state->tx_mask_update_pending = false;
state->tx_antenna_bitmask = all_on;
// Enter recovery mode if we were muted
if (old_mask != all_on) {
state->recovery.in_recovery = true;
+48 -2
View File
@@ -19,6 +19,12 @@ typedef struct {
/// Corresponds to o-ran-uplane-conf:antenna-bitmask
uint8_t tx_antenna_bitmask;
uint8_t rx_antenna_bitmask;
/// Target mask used for new scheduling while an unsafe TX downshift drains.
uint8_t requested_tx_antenna_bitmask;
/// True when requested_tx_antenna_bitmask removes physical TX chains that
/// cannot be muted until incompatible in-flight DL HARQ processes complete.
bool tx_mask_update_pending;
/// Total number of configured antennas
uint8_t num_tx_antennas;
@@ -86,6 +92,45 @@ void nr_free_antenna_state(nr_antenna_state_t *state);
*/
uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
/**
* @brief Request a TX mask, staging physical downshifts until MAC HARQ is safe
*
* New scheduling immediately observes the requested mask. Pure upshifts are
* applied physically at once; a request that removes active TX chains remains
* pending until the MAC scheduler explicitly commits it.
*
* @param state Pointer to antenna state structure
* @param bitmask Requested antenna bitmask
* @return Previous requested bitmask
*/
uint8_t nr_request_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
/**
* @brief Read the requested TX mask
* @return Requested bitmask, or 0xFF when state is NULL
*/
uint8_t nr_get_requested_tx_antenna_bitmask(const nr_antenna_state_t *state);
/**
* @brief Atomically snapshot a pending TX-mask request
* @param state Pointer to antenna state structure
* @param bitmask Output requested mask when pending
* @return true when a physical mask update is pending
*/
bool nr_get_pending_tx_antenna_bitmask(const nr_antenna_state_t *state, uint8_t *bitmask);
/**
* @brief Commit a previously checked pending TX mask
*
* The compare against expected_bitmask prevents a concurrent control request
* from being applied using a stale HARQ-safety decision.
*
* @param state Pointer to antenna state structure
* @param expected_bitmask Mask for which the scheduler completed its check
* @return true when that exact pending request was committed
*/
bool nr_apply_requested_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t expected_bitmask);
/**
* @brief Set RX antenna bitmask (O-RAN O1 interface equivalent)
* @param state Pointer to antenna state structure
@@ -169,8 +214,9 @@ void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Get effective number of MIMO layers based on active antennas
*
* For MIMO, the number of layers is limited by active antennas.
* This function returns min(requested_layers, active_antennas).
* For MIMO, the number of layers is limited by the requested antenna mask.
* During a staged downshift this stops new high-rank HARQ traffic before the
* physical antenna mask changes.
*
* @param state Pointer to antenna state structure
* @param requested_layers Originally requested number of layers
+151 -59
View File
@@ -17,9 +17,55 @@
#include "executables/softmodem-common.h"
#include "SCHED_NR/sched_nr.h"
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <simde/x86/avx512.h>
#define USE128BIT
static pthread_once_t tx_layer_fanout_once = PTHREAD_ONCE_INIT;
static bool tx_layer_fanout = false;
static void configure_tx_layer_fanout(void)
{
const char *value = getenv("OAI_TX_LAYER_FANOUT");
tx_layer_fanout = value != NULL && strcmp(value, "1") == 0;
if (tx_layer_fanout)
LOG_I(PHY,
"[8TX_FANOUT] Fixed identity-layer fan-out enabled; "
"all active physical TX ports carry PDSCH\n");
}
static bool tx_layer_fanout_enabled(void)
{
pthread_once(&tx_layer_fanout_once, configure_tx_layer_fanout);
return tx_layer_fanout;
}
static void copy_normalized_layer(c16_t *dst,
const c16_t *src,
int count,
int replicas)
{
// Q15 approximations of 1/sqrt(n), n=1..8. Normalizing each duplicated
// layer keeps total frequency-domain energy comparable with the 4TX
// identity mapping instead of giving 8TX an artificial power advantage.
static const int16_t gain_q15[] = {
32767, 23170, 18919, 16384, 14654, 13377, 12384, 11585};
AssertFatal(replicas >= 1 && replicas <= 8,
"Invalid 8TX layer replica count %d\n",
replicas);
if (replicas == 1) {
memcpy(dst, src, count * sizeof(*dst));
return;
}
const int32_t gain = gain_q15[replicas - 1];
for (int i = 0; i < count; ++i) {
dst[i].r = ((int32_t)src[i].r * gain) >> 15;
dst[i].i = ((int32_t)src[i].i * gain) >> 15;
}
}
static void nr_pdsch_codeword_scrambling(uint8_t *in, uint32_t size, uint8_t q, uint32_t Nid, uint32_t n_RNTI, uint32_t *out)
{
nr_codeword_scrambling(in, size, q, Nid, n_RNTI, out);
@@ -436,6 +482,49 @@ static inline void do_txdataF(c16_t **txdataF,
int txdataF_offset_per_symbol)
{
NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms;
// nakaolab Patch 8: identity-precoding layer-to-antenna remapping.
// When antennas are muted, layer N must NOT be sent to muted antenna N
// (the layer's data would be lost on the blanked port). Instead, find
// this antenna's rank among active antennas — that's the layer index it
// should receive. Muted antennas get zeros.
const nr_antenna_state_t *ast = &gNB->antenna_state;
int identity_layer;
int identity_replicas = 1;
int active_count = nr_get_active_tx_antenna_count(ast);
const bool fanout = frame_parms->nb_antennas_tx == 8
&& tx_layer_fanout_enabled();
if (active_count <= 0 || !nr_is_tx_antenna_active(ast, ant)) {
identity_layer = -1;
} else if (fanout) {
// If physical TX ports outnumber the scheduled layers, map active ports
// cyclically and normalize replicas (for example, 8TX/4 layers). At
// rank 8 this naturally becomes a one-to-one mapping: every port carries
// distinct layer data and no fan-out occurs.
int active_rank = 0;
for (int a = 0; a < ant; ++a)
if (nr_is_tx_antenna_active(ast, a))
active_rank++;
identity_layer = rel15->nrOfLayers > 0
? active_rank % rel15->nrOfLayers
: -1;
if (identity_layer >= 0)
identity_replicas = (active_count + rel15->nrOfLayers - 1
- identity_layer) / rel15->nrOfLayers;
} else if (active_count < frame_parms->nb_antennas_tx) {
if (!nr_is_tx_antenna_active(ast, ant)) {
identity_layer = -1; // muted antenna → zero output
} else {
// count active antennas with index < ant
identity_layer = 0;
for (int a = 0; a < ant; ++a)
if (nr_is_tx_antenna_active(ast, a))
identity_layer++;
if (identity_layer >= rel15->nrOfLayers)
identity_layer = -1;
}
} else {
identity_layer = (ant < rel15->nrOfLayers) ? ant : -1;
}
int rb = 0;
uint16_t subCarrier = get_block_start_sc(rb_start, rel15->BWPStart, symbol_sz);
const nfapi_nr_tx_precoding_and_beamforming_t *pb = &rel15->precodingAndBeamforming;
@@ -452,10 +541,13 @@ static inline void do_txdataF(c16_t **txdataF,
const int rb_step = rb_step0 == 2 && pmi3 == pmi && pmi4 == pmi ? 4 : rb_step0;
const int re_cnt = NR_NB_SC_PER_RB * rb_step;
if (pmi == 0) { // unitary Precoding
if (ant < rel15->nrOfLayers)
memcpy(&txdataF[ant][txdataF_offset_per_symbol + subCarrier],
&txdataF_precoding[ant][subCarrier],
re_cnt * sizeof(**txdataF));
if (identity_layer >= 0) {
copy_normalized_layer(
&txdataF[ant][txdataF_offset_per_symbol + subCarrier],
&txdataF_precoding[identity_layer][subCarrier],
re_cnt,
fanout ? identity_replicas : 1);
}
else
memset(&txdataF[ant][txdataF_offset_per_symbol + subCarrier], 0, re_cnt * sizeof(**txdataF));
subCarrier += re_cnt;
@@ -501,7 +593,7 @@ typedef struct pdschSymbolProc_s {
unsigned int n_ptrs;
unsigned int beam_nb;
unsigned int re_beginning_of_symbol[14];
c16_t *tx_layers[4];
c16_t *tx_layers[NR_MAX_NB_LAYERS];
time_stats_t dlsch_resource_mapping_stats;
time_stats_t dlsch_precoding_stats;
} pdschSymbolProc_t;
@@ -528,20 +620,10 @@ static void nr_pdsch_symbol_processing(void *arg)
for (int l_symbol = rdata->startSymbol; l_symbol < rdata->startSymbol + rdata->numSymbols; l_symbol++) {
start_meas(&rdata->dlsch_resource_mapping_stats);
int l_prime = 0; // single symbol layer 0
int l_overline = get_l0(rel15->dlDmrsSymbPos);
const int l_prime = get_pdsch_dmrs_l_prime(rel15->dlDmrsSymbPos, l_symbol);
/// DMRS QPSK modulation
if ((rel15->dlDmrsSymbPos & (1 << l_symbol))) { // DMRS time occasion
// The reference point for is subcarrier -1 of the lowest-numbered resource block in CORESET 0 if the corresponding
// PDCCH is associated with CORESET -1 and Type0-PDCCH common search space and is addressed to SI-RNTI
// 2GPP TS 38.211 V15.8.0 Section 7.4.1.1.2 Mapping to physical resources
if (l_symbol == (l_overline + 1)) // take into account the double DMRS symbols
l_prime = 1;
else if (l_symbol > (l_overline + 1)) { // new DMRS pair
l_overline = l_symbol;
l_prime = 0;
}
const uint32_t *gold = nr_gold_pdsch(frame_parms->N_RB_DL,
frame_parms->symbols_per_slot,
rel15->dlDmrsScramblingId,
@@ -612,12 +694,11 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
const int layerSz = frame_parms->N_RB_DL * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB;
const int nb_re_dmrs = rel15->numDmrsCdmGrpsNoData * (rel15->dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ? 6 : 4);
const int n_dmrs = freq_alloc->num_rbs * nb_re_dmrs;
const int xOverhead = 0;
const int nb_re =
(12 * rel15->NrOfSymbols - nb_re_dmrs * get_num_dmrs(rel15->dlDmrsSymbPos) - xOverhead) * freq_alloc->num_rbs * rel15->nrOfLayers;
const int Qm = rel15->qamModOrder[0];
const int encoded_length = nb_re * Qm;
uint32_t encoded_length[NR_MAX_NB_CODEWORDS] = {0};
uint32_t n_mod_symbols[NR_MAX_NB_CODEWORDS] = {0};
const c16_t *mod_symbs[NR_MAX_NB_CODEWORDS] = {0};
c16_t *mod_symbs_storage[NR_MAX_NB_CODEWORDS] = {0};
size_t encoded_offset = 0;
/* PTRS */
uint16_t dlPtrsSymPos = 0;
@@ -633,34 +714,51 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
#ifdef DEBUG_DLSCH
printf("PDSCH encoding:\nPayload:\n");
for (int i = 0; i < (dlsch->B >> 3); i += 16) {
for (int j = 0; j < 16; j++)
printf("0x%02x\t", dlsch->pdu[i + j]);
printf("\n");
}
printf("\nEncoded payload:\n");
for (int i = 0; i < encoded_length; i += 8) {
for (int j = 0; j < 8; j++)
printf("%d", (input_ptr[i >> 3] >> j) & 1);
printf("\t");
for (int q = 0; q < rel15->NrOfCodewords; q++) {
printf("codeword %d:\n", q);
for (int i = 0; i < rel15->TBSize[q]; i++)
printf("0x%02x%s", dlsch->pdu[q][i], (i & 15) == 15 ? "\n" : "\t");
}
printf("\n");
#endif
if (IS_SOFTMODEM_DLSIM)
memcpy(dlsch->f, input_ptr, (encoded_length + 7) >> 3);
c16_t mod_symbs[rel15->NrOfCodewords][encoded_length] __attribute__((aligned(64)));
for (int codeWord = 0; codeWord < rel15->NrOfCodewords; codeWord++) {
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, codeWord);
const int Qm = rel15->qamModOrder[codeWord];
encoded_length[codeWord] = nr_get_G(freq_alloc->num_rbs,
rel15->NrOfSymbols,
nb_re_dmrs,
get_num_dmrs(rel15->dlDmrsSymbPos),
dlsch->unav_res,
Qm,
cw_layers);
n_mod_symbols[codeWord] = encoded_length[codeWord] / Qm;
unsigned char *codeword_input = input_ptr + encoded_offset;
const size_t max_encoded_bits =
freq_alloc->num_rbs * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * Qm * cw_layers;
encoded_offset += ceil_mod(max_encoded_bits, 8 * 64) >> 3;
if (IS_SOFTMODEM_DLSIM)
memcpy(dlsch->f[codeWord], codeword_input, (encoded_length[codeWord] + 7) >> 3);
mod_symbs_storage[codeWord] = malloc16(n_mod_symbols[codeWord] * sizeof(c16_t));
AssertFatal(mod_symbs_storage[codeWord], "Cannot allocate codeword %d modulation buffer\n", codeWord);
mod_symbs[codeWord] = mod_symbs_storage[codeWord];
/// scrambling
start_meas(dlsch_scrambling_stats);
uint32_t scrambled_output[(encoded_length >> 5) + 4]; // modulator acces by 4 bytes in some cases
uint32_t scrambled_output[(encoded_length[codeWord] >> 5) + 4]; // modulator accesses four-byte groups
memset(scrambled_output, 0, sizeof(scrambled_output));
nr_pdsch_codeword_scrambling(input_ptr, encoded_length, codeWord, rel15->dataScramblingId, rel15->rnti, scrambled_output);
nr_pdsch_codeword_scrambling(codeword_input,
encoded_length[codeWord],
codeWord,
rel15->dataScramblingId,
rel15->rnti,
scrambled_output);
#ifdef DEBUG_DLSCH
printf("PDSCH scrambling:\n");
for (int i = 0; i < encoded_length >> 8; i++) {
for (int i = 0; i < encoded_length[codeWord] >> 8; i++) {
for (int j = 0; j < 8; j++)
printf("0x%08x\t", scrambled_output[(i << 3) + j]);
printf("\n");
@@ -670,16 +768,10 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
stop_meas(dlsch_scrambling_stats);
/// Modulation
start_meas(dlsch_modulation_stats);
nr_modulation(scrambled_output, encoded_length, Qm, (int16_t *)mod_symbs[codeWord]);
nr_modulation(scrambled_output, encoded_length[codeWord], Qm, (int16_t *)mod_symbs_storage[codeWord]);
stop_meas(dlsch_modulation_stats);
#ifdef DEBUG_DLSCH
printf("PDSCH Modulation: Qm %d(%d)\n", Qm, nb_re);
for (int i = 0; i < nb_re; i += 8) {
for (int j = 0; j < 8; j++) {
printf("%d %d\t", mod_symbs[codeWord][i + j].r, mod_symbs[codeWord][i + j].i);
}
printf("\n");
}
printf("PDSCH codeword %d modulation: Qm %d, %u symbols\n", codeWord, Qm, n_mod_symbols[codeWord]);
#endif
}
@@ -694,7 +786,7 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
int layerSz2 = (layerSz + 63) & ~63;
c16_t tx_layers[rel15->nrOfLayers][layerSz2] __attribute__((aligned(64)));
memset(tx_layers, 0, sizeof(tx_layers));
nr_layer_mapping(rel15->NrOfCodewords, encoded_length, mod_symbs, rel15->nrOfLayers, layerSz2, nb_re, tx_layers);
nr_layer_mapping(rel15->NrOfCodewords, mod_symbs, n_mod_symbols, rel15->nrOfLayers, layerSz2, tx_layers);
/// Layer Precoding and Antenna port mapping
// tx_layers 1-8 are mapped on antenna ports 1000-1007
@@ -777,12 +869,13 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
merge_meas(&gNB->dlsch_resource_mapping_stats, &arr[i].dlsch_resource_mapping_stats);
merge_meas(&gNB->dlsch_precoding_stats, &arr[i].dlsch_precoding_stats);
}
for (int q = 0; q < rel15->NrOfCodewords; q++)
free16(mod_symbs_storage[q], n_mod_symbols[q] * sizeof(c16_t));
stop_meas(&gNB->dlsch_pdsch_generation_stats);
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
* should remain a multiple of 8 * 64 with enough offset to fit each dlsch
*/
uint32_t size_output_tb = freq_alloc->num_rbs * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * Qm * rel15->nrOfLayers;
return ((size_output_tb + 511) >> 9) << 6;
return encoded_offset;
}
void nr_generate_pdsch(PHY_VARS_gNB *gNB, int n_dlsch, NR_gNB_DLSCH_t *dlsch_array, int frame, int slot)
@@ -816,8 +909,6 @@ void nr_generate_pdsch(PHY_VARS_gNB *gNB, int n_dlsch, NR_gNB_DLSCH_t *dlsch_arr
dlsch->freq_alloc.last_rb,
dlsch->freq_alloc.num_rbs);
const int Qm = rel15->qamModOrder[0];
/* PTRS */
uint16_t dlPtrsSymPos = 0;
int n_ptrs = 0;
@@ -834,13 +925,14 @@ void nr_generate_pdsch(PHY_VARS_gNB *gNB, int n_dlsch, NR_gNB_DLSCH_t *dlsch_arr
dlsch->unav_res = ptrsSymbPerSlot * n_ptrs;
/// CRC, coding, interleaving and rate matching
AssertFatal(dlsch->pdu != NULL, "%4d.%2d no PDU for PDSCH generation\n", frame, slot);
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
* => size_output is a sum of parts sizes rounded up to a multiple of 8 * 64
*/
size_t size_output_tb = dlsch->freq_alloc.num_rbs * NR_SYMBOLS_PER_SLOT * NR_NB_SC_PER_RB * Qm * rel15->nrOfLayers;
size_output += ceil_mod(size_output_tb, 8 * 64);
for (int q = 0; q < rel15->NrOfCodewords; q++) {
AssertFatal(dlsch->pdu[q] != NULL, "%4d.%2d no PDU for PDSCH codeword %d generation\n", frame, slot, q);
/* Every LDPC output starts at a 64-byte boundary. */
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, q);
const size_t size_output_tb =
dlsch->freq_alloc.num_rbs * NR_SYMBOLS_PER_SLOT * NR_NB_SC_PER_RB * rel15->qamModOrder[q] * cw_layers;
size_output += ceil_mod(size_output_tb, 8 * 64);
}
}
unsigned char output[size_output >> 3] __attribute__((aligned(64)));
+155 -116
View File
@@ -23,35 +23,53 @@
// #define DEBUG_DLSCH_CODING
// #define DEBUG_DLSCH_FREE 1
/* Keep the PHY encoder independent of the MAC helper with the same rule from
* TS 38.212 section 5.3.2. A dual-codeword PDSCH may have a different TBS
* (and therefore a different base graph) for each codeword. */
static uint8_t dlsch_base_graph(uint32_t A, uint16_t R)
{
const float code_rate = (float)R / 10240.0f;
return (A <= 292 || (A <= NR_MAX_PDSCH_TBS && code_rate <= 0.6667f) || code_rate <= 0.25f) ? 2 : 1;
}
void free_gNB_dlsch(NR_gNB_DLSCH_t *dlsch, uint16_t N_RB, const NR_DL_FRAME_PARMS *frame_parms)
{
int max_layers = (frame_parms->nb_antennas_tx < NR_MAX_NB_LAYERS) ? frame_parms->nb_antennas_tx : NR_MAX_NB_LAYERS;
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_layers;
const int max_cw_layers = min(max_layers, 4);
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_cw_layers;
if (N_RB != 273) {
a_segments = a_segments * N_RB;
a_segments = a_segments / 273 + 1;
}
if (dlsch->b) {
free16(dlsch->b, a_segments * 1056);
dlsch->b = NULL;
const size_t f_bytes = N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * max_cw_layers;
(void)f_bytes; /* free16 ignores its size argument on the supported allocators. */
for (int q = 0; q < NR_MAX_NB_CODEWORDS; q++) {
if (dlsch->b[q]) {
free16(dlsch->b[q], a_segments * 1056);
dlsch->b[q] = NULL;
}
if (dlsch->f[q]) {
free16(dlsch->f[q], f_bytes);
dlsch->f[q] = NULL;
}
if (!dlsch->c[q])
continue;
for (int r = 0; r < a_segments; r++) {
free(dlsch->c[q][r]);
dlsch->c[q][r] = NULL;
}
free(dlsch->c[q]);
dlsch->c[q] = NULL;
}
if (dlsch->f) {
free16(dlsch->f, N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * NR_MAX_NB_LAYERS);
dlsch->f = NULL;
}
for (int r = 0; r < a_segments; r++) {
free(dlsch->c[r]);
dlsch->c[r] = NULL;
}
free(dlsch->c);
}
NR_gNB_DLSCH_t new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, uint16_t N_RB)
{
int max_layers = (frame_parms->nb_antennas_tx < NR_MAX_NB_LAYERS) ? frame_parms->nb_antennas_tx : NR_MAX_NB_LAYERS;
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_layers; // number of segments to be allocated
const int max_cw_layers = min(max_layers, 4);
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_cw_layers; // segments allocated per codeword
if (N_RB != 273) {
a_segments = a_segments * N_RB;
@@ -59,28 +77,29 @@ NR_gNB_DLSCH_t new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, uint16_t N_RB)
}
LOG_D(PHY, "Allocating %d segments (MAX %d, N_PRB %d)\n", a_segments, MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER, N_RB);
uint32_t dlsch_bytes = a_segments * 1056; // allocated bytes per segment
uint32_t dlsch_bytes = a_segments * 1056;
NR_gNB_DLSCH_t dlsch = {0};
const size_t f_bytes = N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * max_cw_layers;
dlsch.b = malloc16(dlsch_bytes);
AssertFatal(dlsch.b, "cannot allocate memory for dlsch.b\n");
bzero(dlsch.b, dlsch_bytes);
for (int q = 0; q < NR_MAX_NB_CODEWORDS; q++) {
dlsch.b[q] = malloc16(dlsch_bytes);
AssertFatal(dlsch.b[q], "cannot allocate memory for dlsch.b[%d]\n", q);
bzero(dlsch.b[q], dlsch_bytes);
dlsch.c = (uint8_t **)malloc16(a_segments * sizeof(uint8_t *));
for (int r = 0; r < a_segments; r++) {
// account for filler in first segment and CRCs for multiple segment case
// [hna] 8448 is the maximum CB size in NR
// 68*348 = 68*(maximum size of Zc)
// In section 5.3.2 in 38.212, the for loop is up to N + 2*Zc (maximum size of N is 66*Zc, therefore 68*Zc)
dlsch.c[r] = malloc16(8448);
AssertFatal(dlsch.c[r], "cannot allocate dlsch.c[%d]\n", r);
bzero(dlsch.c[r], 8448);
dlsch.c[q] = (uint8_t **)malloc16(a_segments * sizeof(uint8_t *));
AssertFatal(dlsch.c[q], "cannot allocate memory for dlsch.c[%d]\n", q);
for (int r = 0; r < a_segments; r++) {
// 8448 is the maximum NR code-block size, including filler and CRC.
dlsch.c[q][r] = malloc16(8448);
AssertFatal(dlsch.c[q][r], "cannot allocate dlsch.c[%d][%d]\n", q, r);
bzero(dlsch.c[q][r], 8448);
}
dlsch.f[q] = malloc16(f_bytes);
AssertFatal(dlsch.f[q], "cannot allocate dlsch.f[%d]\n", q);
bzero(dlsch.f[q], f_bytes);
}
dlsch.f = malloc16(N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * NR_MAX_NB_LAYERS);
AssertFatal(dlsch.f, "cannot allocate dlsch->f\n");
bzero(dlsch.f, N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * NR_MAX_NB_LAYERS);
return (dlsch);
}
@@ -98,92 +117,104 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
time_stats_t *dlsch_interleaving_stats,
time_stats_t *dlsch_segmentation_stats)
{
nrLDPC_TB_encoding_parameters_t TBs[n_dlsch];
int n_tbs = 0;
for (int i = 0; i < n_dlsch; i++) {
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch_array[i].pdsch_pdu->pdsch_pdu_rel15;
AssertFatal(rel15->NrOfCodewords == nr_pdsch_num_codewords(rel15->nrOfLayers),
"PDSCH has %u codewords for %u layers\n",
rel15->NrOfCodewords,
rel15->nrOfLayers);
n_tbs += rel15->NrOfCodewords;
}
nrLDPC_TB_encoding_parameters_t TBs[n_tbs];
memset(TBs, 0, sizeof(TBs));
NR_gNB_DLSCH_t *tb_dlsch[n_tbs];
uint8_t tb_cw[n_tbs];
int num_segments = 0;
int tb_index = 0;
for (int i = 0; i < n_dlsch; i++) {
NR_gNB_DLSCH_t *dlsch = &dlsch_array[i];
unsigned int crc = 1;
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch->pdsch_pdu->pdsch_pdu_rel15;
uint32_t A = rel15->TBSize[0] << 3;
unsigned char *a = dlsch->pdu;
if (rel15->rnti != SI_RNTI) {
ws_trace_t tmp = {.nr = true,
.direction = DIRECTION_DOWNLINK,
.pdu_buffer = a,
.pdu_buffer_size = rel15->TBSize[0],
.ueid = 0,
.rntiType = WS_C_RNTI,
.rnti = rel15->rnti,
.sysFrame = frame,
.subframe = slot,
.harq_pid = 0, // difficult to find the harq pid here
.oob_event = 0,
.oob_event_value = 0};
trace_pdu(&tmp);
}
NR_gNB_PHY_STATS_t *phy_stats = NULL;
if (rel15->rnti != 0xFFFF)
phy_stats = get_phy_stats(gNB, rel15->rnti);
if (phy_stats) {
phy_stats->frame = frame;
phy_stats->dlsch_stats.total_bytes_tx += rel15->TBSize[0];
phy_stats->dlsch_stats.current_RI = rel15->nrOfLayers;
phy_stats->dlsch_stats.current_Qm = rel15->qamModOrder[0];
}
int max_bytes = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * rel15->nrOfLayers * 1056;
int B;
if (A > NR_MAX_PDSCH_TBS) {
// Add 24-bit crc (polynomial A) to payload
crc = crc24a(a, A) >> 8;
a[A >> 3] = ((uint8_t *)&crc)[2];
a[1 + (A >> 3)] = ((uint8_t *)&crc)[1];
a[2 + (A >> 3)] = ((uint8_t *)&crc)[0];
// printf("CRC %x (A %d)\n",crc,A);
// printf("a0 %d a1 %d a2 %d\n", a[A>>3], a[1+(A>>3)], a[2+(A>>3)]);
B = A + 24;
AssertFatal((A / 8) + 4 <= max_bytes, "A %d is too big (A/8+4 = %d > %d)\n", A, (A / 8) + 4, max_bytes);
memcpy(dlsch->b, a, (A / 8) + 4); // why is this +4 if the CRC is only 3 bytes?
} else {
// Add 16-bit crc (polynomial A) to payload
crc = crc16(a, A) >> 16;
a[A >> 3] = ((uint8_t *)&crc)[1];
a[1 + (A >> 3)] = ((uint8_t *)&crc)[0];
// printf("CRC %x (A %d)\n",crc,A);
// printf("a0 %d a1 %d \n", a[A>>3], a[1+(A>>3)]);
B = A + 16;
AssertFatal((A / 8) + 3 <= max_bytes, "A %d is too big (A/8+3 = %d > %d)\n", A, (A / 8) + 3, max_bytes);
memcpy(dlsch->b, a, (A / 8) + 3); // using 3 bytes to mimic the case of 24 bit crc
for (int q = 0; q < rel15->NrOfCodewords; q++, tb_index++) {
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, q);
const uint32_t A = rel15->TBSize[q] << 3;
unsigned char *a = dlsch->pdu[q];
AssertFatal(a != NULL, "Missing PDSCH transport block for codeword %d\n", q);
if (rel15->rnti != SI_RNTI) {
ws_trace_t tmp = {.nr = true,
.direction = DIRECTION_DOWNLINK,
.pdu_buffer = a,
.pdu_buffer_size = rel15->TBSize[q],
.ueid = 0,
.rntiType = WS_C_RNTI,
.rnti = rel15->rnti,
.sysFrame = frame,
.subframe = slot,
.harq_pid = 0,
.oob_event = 0,
.oob_event_value = 0};
trace_pdu(&tmp);
}
if (phy_stats)
phy_stats->dlsch_stats.total_bytes_tx += rel15->TBSize[q];
unsigned int crc = 1;
const int max_bytes = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * cw_layers * 1056;
int B;
if (A > NR_MAX_PDSCH_TBS) {
crc = crc24a(a, A) >> 8;
a[A >> 3] = ((uint8_t *)&crc)[2];
a[1 + (A >> 3)] = ((uint8_t *)&crc)[1];
a[2 + (A >> 3)] = ((uint8_t *)&crc)[0];
B = A + 24;
AssertFatal((A / 8) + 4 <= max_bytes, "A %u is too big (%u > %d)\n", A, (A / 8) + 4, max_bytes);
memcpy(dlsch->b[q], a, (A / 8) + 4);
} else {
crc = crc16(a, A) >> 16;
a[A >> 3] = ((uint8_t *)&crc)[1];
a[1 + (A >> 3)] = ((uint8_t *)&crc)[0];
B = A + 16;
AssertFatal((A / 8) + 3 <= max_bytes, "A %u is too big (%u > %d)\n", A, (A / 8) + 3, max_bytes);
memcpy(dlsch->b[q], a, (A / 8) + 3);
}
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[tb_index];
tb_dlsch[tb_index] = dlsch;
tb_cw[tb_index] = q;
// A HARQ PID is not globally unique, so use the flattened TB index.
TB_parameters->harq_unique_pid = tb_index;
TB_parameters->BG = dlsch_base_graph(A, rel15->targetCodeRate[q]);
TB_parameters->A = A;
start_meas(dlsch_segmentation_stats);
TB_parameters->Kb = nr_segmentation(dlsch->b[q],
dlsch->c[q],
B,
&TB_parameters->C,
&TB_parameters->K,
&TB_parameters->Z,
&TB_parameters->F,
TB_parameters->BG);
stop_meas(dlsch_segmentation_stats);
if (TB_parameters->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * cw_layers) {
LOG_E(PHY, "nr_segmentation.c: too many segments %d, B %d, codeword layers %d\n", TB_parameters->C, B, cw_layers);
return -1;
}
num_segments += TB_parameters->C;
}
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
// The harq_pid is not unique among the active HARQ processes in the instance so we use i instead
TB_parameters->harq_unique_pid = i;
TB_parameters->BG = rel15->maintenance_parms_v3.ldpcBaseGraph;
TB_parameters->A = A;
start_meas(dlsch_segmentation_stats);
TB_parameters->Kb = nr_segmentation(dlsch->b,
dlsch->c,
B,
&TB_parameters->C,
&TB_parameters->K,
&TB_parameters->Z,
&TB_parameters->F,
TB_parameters->BG);
stop_meas(dlsch_segmentation_stats);
if (TB_parameters->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * rel15->nrOfLayers) {
LOG_E(PHY, "nr_segmentation.c: too many segments %d, B %d\n", TB_parameters->C, B);
return (-1);
}
num_segments += TB_parameters->C;
}
nrLDPC_segment_encoding_parameters_t segments[num_segments];
@@ -191,19 +222,26 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
size_t segments_offset = 0;
size_t dlsch_offset = 0;
for (int i = 0; i < n_dlsch; i++) {
NR_gNB_DLSCH_t *dlsch = &dlsch_array[i];
for (int i = 0; i < n_tbs; i++) {
NR_gNB_DLSCH_t *dlsch = tb_dlsch[i];
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch->pdsch_pdu->pdsch_pdu_rel15;
const int q = tb_cw[i];
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, q);
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
#ifdef DEBUG_DLSCH_CODING
for (int r = 0; r < TB_parameters->C; r++) {
LOG_D(PHY, "Encoder: B %d F %d \n", dlsch->B, TB_parameters->F);
LOG_D(PHY, "start ldpc encoder segment %d/%d\n", r, TB_parameters->C);
LOG_D(PHY, "input %d %d %d %d %d \n", dlsch->c[r][0], dlsch->c[r][1], dlsch->c[r][2], dlsch->c[r][3], dlsch->c[r][4]);
LOG_D(PHY,
"input %d %d %d %d %d \n",
dlsch->c[q][r][0],
dlsch->c[q][r][1],
dlsch->c[q][r][2],
dlsch->c[q][r][3],
dlsch->c[q][r][4]);
for (int cnt = 0; cnt < 22 * (TB_parameters->Z) / 8; cnt++) {
LOG_D(PHY, "%d ", dlsch->c[r][cnt]);
LOG_D(PHY, "%d ", dlsch->c[q][r][cnt]);
}
LOG_D(PHY, "\n");
}
@@ -211,10 +249,10 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
int rbsize = dlsch->freq_alloc.num_rbs;
TB_parameters->nb_rb = rbsize;
TB_parameters->Qm = rel15->qamModOrder[0];
TB_parameters->mcs = rel15->mcsIndex[0];
TB_parameters->nb_layers = rel15->nrOfLayers;
TB_parameters->rv_index = rel15->rvIndex[0];
TB_parameters->Qm = rel15->qamModOrder[q];
TB_parameters->mcs = rel15->mcsIndex[q];
TB_parameters->nb_layers = cw_layers;
TB_parameters->rv_index = rel15->rvIndex[q];
int nb_re_dmrs =
(rel15->dmrsConfigType == NFAPI_NR_DMRS_TYPE1) ? (6 * rel15->numDmrsCdmGrpsNoData) : (4 * rel15->numDmrsCdmGrpsNoData);
@@ -223,8 +261,8 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
nb_re_dmrs,
get_num_dmrs(rel15->dlDmrsSymbPos),
dlsch->unav_res,
rel15->qamModOrder[0],
rel15->nrOfLayers);
rel15->qamModOrder[q],
cw_layers);
TB_parameters->tbslbrm = rel15->maintenance_parms_v3.tbSizeLbrmBytes;
TB_parameters->output = &output[dlsch_offset >> 3];
@@ -232,8 +270,8 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
for (int r = 0; r < TB_parameters->C; r++) {
nrLDPC_segment_encoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
segment_parameters->c = dlsch->c[r];
segment_parameters->E = nr_get_E(TB_parameters->G, TB_parameters->C, TB_parameters->Qm, rel15->nrOfLayers, r);
segment_parameters->c = dlsch->c[q][r];
segment_parameters->E = nr_get_E(TB_parameters->G, TB_parameters->C, TB_parameters->Qm, cw_layers, r);
reset_meas(&segment_parameters->ts_interleave);
reset_meas(&segment_parameters->ts_rate_match);
@@ -245,13 +283,14 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
* => dlsch_offset should remain a multiple of 8 * 64 with enough offset to fit each dlsch
*/
const size_t dlsch_size = rbsize * gNB->frame_parms.symbols_per_slot * NR_NB_SC_PER_RB * rel15->qamModOrder[0] * rel15->nrOfLayers;
const size_t dlsch_size =
rbsize * gNB->frame_parms.symbols_per_slot * NR_NB_SC_PER_RB * rel15->qamModOrder[q] * cw_layers;
dlsch_offset += ceil_mod(dlsch_size, 8 * 64);
}
nrLDPC_slot_encoding_parameters_t slot_parameters = {.frame = frame,
.slot = slot,
.nb_TBs = n_dlsch,
.nb_TBs = n_tbs,
.threadPool = &gNB->threadPool,
.tinput = tinput,
.tprep = tprep,
@@ -260,7 +299,7 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
.TBs = TBs};
gNB->nrLDPC_coding_interface.nrLDPC_coding_encoder(&slot_parameters);
for (int i = 0; i < n_dlsch; i++) {
for (int i = 0; i < n_tbs; i++) {
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
for (int r = 0; r < TB_parameters->C; r++) {
nrLDPC_segment_encoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
+8
View File
@@ -19,4 +19,12 @@ uint16_t get_dmrs_freq_idx(uint16_t n, uint8_t k_prime, uint8_t delta, uint8_t d
uint8_t get_l0(uint16_t dlDmrsSymbPos);
/* For a double-symbol PDSCH DMRS occasion, l'=1 on the second symbol of
* each adjacent pair and l'=0 on the first. Single-symbol occasions always
* use l'=0. */
static inline uint8_t get_pdsch_dmrs_l_prime(uint16_t dmrs_symbol_mask, uint8_t symbol)
{
return symbol > 0 && ((dmrs_symbol_mask >> (symbol - 1)) & 0x1);
}
#endif
@@ -1282,7 +1282,8 @@ void nr_pdsch_channel_estimation(PHY_VARS_NR_UE *ue,
float beta_dmrs_pdsch = get_beta_dmrs(dlsch->n_dmrs_cdm_groups, config_type == NFAPI_NR_DMRS_TYPE2);
int16_t dmrs_scaling = (int16_t)((1 / beta_dmrs_pdsch) * (1 << 14));
const uint32_t *gold = nr_gold_pdsch(fp->N_RB_DL, fp->symbols_per_slot, dlsch->dlDmrsScramblingId, dlsch->nscid, slot, symbol);
nr_pdsch_dmrs_rx(fp->Ncp, gold, pilot, 1000 + p, 0, nb_rb_pdsch + rb_offset, config_type, dmrs_scaling);
const uint8_t l_prime = get_pdsch_dmrs_l_prime(dlsch->dlDmrsSymbPos, symbol);
nr_pdsch_dmrs_rx(fp->Ncp, gold, pilot, 1000 + p, l_prime, nb_rb_pdsch + rb_offset, config_type, dmrs_scaling);
delay_t delay = {0};
@@ -1374,127 +1375,94 @@ void nr_pdsch_ptrs_processing(int nbRx,
int nl,
c16_t rxdataF_comp[][nl][nbRx][rx_size_symbol],
NR_DL_FRAME_PARMS *frame_parms,
NR_DL_UE_HARQ_t *dlsch0_harq,
NR_DL_UE_HARQ_t *dlsch1_harq,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
uint8_t nr_slot_rx,
unsigned char symbol,
int nb_rb,
uint16_t rnti,
NR_UE_DLSCH_t dlsch[2])
uint16_t *ptrsSymbPos,
uint8_t *ptrsSymbIdx)
{
//#define DEBUG_DL_PTRS 1
int32_t *ptrs_re_symbol = NULL;
int8_t ret = 0;
/* harq specific variables */
uint8_t symbInSlot = 0;
uint16_t *startSymbIndex = NULL;
uint16_t *nbSymb = NULL;
uint8_t *L_ptrs = NULL;
uint8_t *K_ptrs = NULL;
uint16_t *dmrsSymbPos = NULL;
uint16_t *ptrsSymbPos = NULL;
uint8_t *ptrsSymbIdx = NULL;
uint8_t *ptrsReOffset = NULL;
int nscid = 0;
int symbInSlot = dlsch_config->start_symbol + dlsch_config->number_symbols;
int startSymbIndex = dlsch_config->start_symbol;
int nbSymb = dlsch_config->number_symbols;
int L_ptrs = dlsch_config->PTRSTimeDensity;
int K_ptrs = dlsch_config->PTRSFreqDensity;
int dmrsSymbPos = dlsch_config->dlDmrsSymbPos;
int ptrsReOffset = dlsch_config->PTRSReOffset;
int nscid = dlsch_config->nscid;
if(dlsch0_harq->status == NR_ACTIVE) {
symbInSlot = dlsch[0].dlsch_config.start_symbol + dlsch[0].dlsch_config.number_symbols;
startSymbIndex = &dlsch[0].dlsch_config.start_symbol;
nbSymb = &dlsch[0].dlsch_config.number_symbols;
L_ptrs = &dlsch[0].dlsch_config.PTRSTimeDensity;
K_ptrs = &dlsch[0].dlsch_config.PTRSFreqDensity;
dmrsSymbPos = &dlsch[0].dlsch_config.dlDmrsSymbPos;
ptrsReOffset = &dlsch[0].dlsch_config.PTRSReOffset;
ptrsSymbPos = &dlsch[0].ptrs_symbols;
ptrsSymbIdx = &dlsch[0].ptrs_symbol_index;
nscid = dlsch[0].dlsch_config.nscid;
}
if(dlsch1_harq) {
symbInSlot = dlsch[1].dlsch_config.start_symbol + dlsch[1].dlsch_config.number_symbols;
startSymbIndex = &dlsch[1].dlsch_config.start_symbol;
nbSymb = &dlsch[1].dlsch_config.number_symbols;
L_ptrs = &dlsch[1].dlsch_config.PTRSTimeDensity;
K_ptrs = &dlsch[1].dlsch_config.PTRSFreqDensity;
dmrsSymbPos = &dlsch[1].dlsch_config.dlDmrsSymbPos;
ptrsReOffset = &dlsch[1].dlsch_config.PTRSReOffset;
ptrsSymbPos = &dlsch[1].ptrs_symbols;
ptrsSymbIdx = &dlsch[1].ptrs_symbol_index;
nscid = dlsch[1].dlsch_config.nscid;
}
/* loop over antennas */
for (int aarx=0; aarx<frame_parms->nb_antennas_rx; aarx++) {
for (int aarx = 0; aarx < frame_parms->nb_antennas_rx; aarx++) {
c16_t *phase_per_symbol = (c16_t*)ptrs_phase_per_slot[aarx];
ptrs_re_symbol = (int32_t*)ptrs_re_per_slot[aarx];
ptrs_re_symbol[symbol] = 0;
phase_per_symbol[symbol].i = 0; // Imag
/* set DMRS estimates to 0 angle with magnitude 1 */
if(is_dmrs_symbol(symbol,*dmrsSymbPos)) {
if(is_dmrs_symbol(symbol, dmrsSymbPos)) {
/* set DMRS real estimation to 32767 */
phase_per_symbol[symbol].r=INT16_MAX; // 32767
#ifdef DEBUG_DL_PTRS
printf("[PHY][PTRS]: DMRS Symbol %d -> %4d + j*%4d\n", symbol, phase_per_symbol[symbol].r,phase_per_symbol[symbol].i);
#endif
}
else { // real ptrs value is set to 0
} else { // real ptrs value is set to 0
phase_per_symbol[symbol].r = 0; // Real
}
if(dlsch0_harq->status == NR_ACTIVE) {
if(symbol == *startSymbIndex) {
*ptrsSymbPos = 0;
set_ptrs_symb_idx(ptrsSymbPos,
*nbSymb,
*startSymbIndex,
1<< *L_ptrs,
*dmrsSymbPos);
}
/* if not PTRS symbol set current ptrs symbol index to zero*/
*ptrsSymbIdx = 0;
/* Check if current symbol contains PTRS */
if(is_ptrs_symbol(symbol, *ptrsSymbPos)) {
*ptrsSymbIdx = symbol;
/*------------------------------------------------------------------------------------------------------- */
/* 1) Estimate common phase error per PTRS symbol */
/*------------------------------------------------------------------------------------------------------- */
const uint32_t *gold =
if (symbol == startSymbIndex) {
*ptrsSymbPos = 0;
set_ptrs_symb_idx(ptrsSymbPos, nbSymb, startSymbIndex, 1 << L_ptrs, dmrsSymbPos);
}
/* if not PTRS symbol set current ptrs symbol index to zero*/
*ptrsSymbIdx = 0;
/* Check if current symbol contains PTRS */
if (is_ptrs_symbol(symbol, *ptrsSymbPos)) {
*ptrsSymbIdx = symbol;
/*------------------------------------------------------------------------------------------------------- */
/* 1) Estimate common phase error per PTRS symbol */
/*------------------------------------------------------------------------------------------------------- */
const uint32_t *gold =
nr_gold_pdsch(frame_parms->N_RB_DL, frame_parms->symbols_per_slot, frame_parms->Nid_cell, nscid, nr_slot_rx, symbol);
nr_ptrs_cpe_estimation(*K_ptrs,
*ptrsReOffset,
nb_rb,
rnti,
frame_parms->ofdm_symbol_size,
rxdataF_comp[symbol][0][aarx],
gold,
(int16_t *)&phase_per_symbol[symbol],
&ptrs_re_symbol[symbol]);
}
}// HARQ 0
nr_ptrs_cpe_estimation(K_ptrs,
ptrsReOffset,
nb_rb,
rnti,
frame_parms->ofdm_symbol_size,
rxdataF_comp[symbol][0][aarx],
gold,
(int16_t *)&phase_per_symbol[symbol],
&ptrs_re_symbol[symbol]);
}
/* For last OFDM symbol at each antenna perform interpolation and compensation for the slot*/
if(symbol == (symbInSlot -1)) {
if (symbol == (symbInSlot -1)) {
/*------------------------------------------------------------------------------------------------------- */
/* 2) Interpolate PTRS estimated value in TD */
/*------------------------------------------------------------------------------------------------------- */
/* If L-PTRS is > 0 then we need interpolation */
if(*L_ptrs > 0) {
ret = nr_ptrs_process_slot(*dmrsSymbPos, *ptrsSymbPos, (int16_t*)phase_per_symbol, *startSymbIndex, *nbSymb);
if (L_ptrs > 0) {
ret = nr_ptrs_process_slot(dmrsSymbPos, *ptrsSymbPos, (int16_t*)phase_per_symbol, startSymbIndex, nbSymb);
if(ret != 0) {
LOG_W(PHY,"[PTRS] Compensation is skipped due to error in PTRS slot processing !!\n");
}
}
#ifdef DEBUG_DL_PTRS
LOG_M("ptrsEst.m","est",ptrs_phase_per_slot[aarx],frame_parms->symbols_per_slot,1,1 );
LOG_M("rxdataF_bf_ptrs_comp.m", "bf_ptrs_cmp", rxdataF_comp[0][aarx] + (*startSymbIndex) * rx_size_symbol, rx_size_symbol * (*nbSymb), 1, 1);
LOG_M("ptrsEst.m","est", ptrs_phase_per_slot[aarx], frame_parms->symbols_per_slot, 1, 1);
LOG_M("rxdataF_bf_ptrs_comp.m", "bf_ptrs_cmp", rxdataF_comp[0][aarx] + startSymbIndex * rx_size_symbol, rx_size_symbol * nbSymb, 1, 1);
#endif
/*------------------------------------------------------------------------------------------------------- */
/* 3) Compensated DMRS based estimated signal with PTRS estimation */
/*--------------------------------------------------------------------------------------------------------*/
for(uint8_t i = *startSymbIndex; i < symbInSlot; i++) {
for(uint8_t i = startSymbIndex; i < symbInSlot; i++) {
/* DMRS Symbol has 0 phase so no need to rotate the respective symbol */
/* Skip rotation if the slot processing is wrong */
if((!is_dmrs_symbol(i, *dmrsSymbPos)) && (ret == 0)) {
if((!is_dmrs_symbol(i, dmrsSymbPos)) && (ret == 0)) {
#ifdef DEBUG_DL_PTRS
printf("[PHY][DL][PTRS]: Rotate Symbol %2d with %d + j* %d\n", i, phase_per_symbol[i].r,phase_per_symbol[i].i);
printf("[PHY][DL][PTRS]: Rotate Symbol %2d with %d + j* %d\n", i, phase_per_symbol[i].r, phase_per_symbol[i].i);
#endif
rotate_cpx_vector(rxdataF_comp[i][0][aarx], &phase_per_symbol[i], rxdataF_comp[i][0][aarx], nb_rb * NR_NB_SC_PER_RB, 15);
}// if not DMRS Symbol
@@ -126,13 +126,13 @@ void nr_pdsch_ptrs_processing(int nbRx,
int nl,
c16_t rxdataF_comp[][nl][nbRx][rx_size_symbol],
NR_DL_FRAME_PARMS *frame_parms,
NR_DL_UE_HARQ_t *dlsch0_harq,
NR_DL_UE_HARQ_t *dlsch1_harq,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
uint8_t nr_slot_rx,
unsigned char symbol,
int nb_rb,
uint16_t rnti,
NR_UE_DLSCH_t dlsch[2]);
uint16_t *ptrsSymbPos,
uint8_t *ptrsSymbIdx);
int nr_sl_psbch_rsrp_measurements(PHY_VARS_NR_UE *ue,
sl_nr_ue_phy_params_t *sl_phy_params,
@@ -170,7 +170,7 @@ static void send_ssb_rsrp_meas(PHY_VARS_NR_UE *ue,
nr_downlink_indication_t dl_indication = {0};
fapi_nr_rx_indication_t rx_ind = {0};
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, NULL, NULL, 1, proc, &l1_measurements, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, 0, 0, NULL, 1, proc, &l1_measurements, NULL);
ue->if_inst->dl_indication(&dl_indication);
}
+508 -15
View File
@@ -15,6 +15,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include <pthread.h>
#include "executables/nr-softmodem-common.h"
#include "nr_transport_proto_ue.h"
@@ -22,6 +25,100 @@
#include "common/utils/nr/nr_common.h"
#include "PHY/NR_UE_ESTIMATION/filt16a_32.h"
// nakaolab CSI muting study: float Gram matrix + Jacobi eigenvalues for RI and
// spatial-spectrum telemetry. MAX_ANT and the local PDSCH rank ceiling are
// eight on this branch.
// Sorts eigenvalues in descending order on output.
static void nr_hermitian_eigenvalues(const int N,
float A_re[MAX_ANT][MAX_ANT],
float A_im[MAX_ANT][MAX_ANT],
float eigenvalues[MAX_ANT])
{
for (int sweep = 0; sweep < 64; sweep++) {
float off_norm = 0;
float diag_norm = 0;
for (int i = 0; i < N; i++)
diag_norm += A_re[i][i] * A_re[i][i];
for (int i = 0; i < N; i++)
for (int j = i + 1; j < N; j++)
off_norm += A_re[i][j] * A_re[i][j] + A_im[i][j] * A_im[i][j];
if (off_norm <= 1e-10f * (diag_norm + 1.0f))
break;
for (int p = 0; p < N - 1; p++) {
for (int q = p + 1; q < N; q++) {
float re_pq = A_re[p][q], im_pq = A_im[p][q];
float mag = sqrtf(re_pq * re_pq + im_pq * im_pq);
if (mag < 1e-12f)
continue;
float cos_phi = re_pq / mag;
float sin_phi = im_pq / mag;
// Phase elimination: rotate column/row q so A[p][q] is real.
// Patch 1: skip k==q (was a bug — corrupted diagonal A[q][q]).
for (int k = 0; k < N; k++) {
if (k == q) continue;
float old_re = A_re[k][q], old_im = A_im[k][q];
A_re[k][q] = old_re * cos_phi + old_im * sin_phi;
A_im[k][q] = -old_re * sin_phi + old_im * cos_phi;
}
for (int k = 0; k < N; k++) {
if (k == q) continue;
A_re[q][k] = A_re[k][q];
A_im[q][k] = -A_im[k][q];
}
// Real Jacobi rotation
float d = A_re[q][q] - A_re[p][p];
float t;
if (fabsf(d) < 1e-12f) {
t = 1.0f;
} else {
float tau = d / (2.0f * mag);
t = ((tau >= 0) ? 1.0f : -1.0f) / (fabsf(tau) + sqrtf(1.0f + tau * tau));
}
float c = 1.0f / sqrtf(1.0f + t * t);
float s = t * c;
A_re[p][p] -= t * mag;
A_re[q][q] += t * mag;
A_re[p][q] = 0;
A_im[p][q] = 0;
A_re[q][p] = 0;
A_im[q][p] = 0;
for (int r = 0; r < N; r++) {
if (r == p || r == q) continue;
float rp_re = A_re[r][p], rp_im = A_im[r][p];
float rq_re = A_re[r][q], rq_im = A_im[r][q];
A_re[r][p] = c * rp_re - s * rq_re;
A_im[r][p] = c * rp_im - s * rq_im;
A_re[p][r] = A_re[r][p];
A_im[p][r] = -A_im[r][p];
A_re[r][q] = s * rp_re + c * rq_re;
A_im[r][q] = s * rp_im + c * rq_im;
A_re[q][r] = A_re[r][q];
A_im[q][r] = -A_im[r][q];
}
}
}
}
// Patch 2: clamp eigenvalues to >=0 (numerical errors can produce negative values)
for (int i = 0; i < N; i++)
eigenvalues[i] = A_re[i][i] > 0.0f ? A_re[i][i] : 0.0f;
// Sort descending
for (int i = 0; i < N - 1; i++)
for (int j = i + 1; j < N; j++)
if (eigenvalues[j] > eigenvalues[i]) {
float tmp = eigenvalues[i];
eigenvalues[i] = eigenvalues[j];
eigenvalues[j] = tmp;
}
}
//#define NR_CSIRS_DEBUG
//#define NR_CSIIM_DEBUG
@@ -65,6 +162,95 @@ void nr_det_A_MF_2x2(int32_t *a_mf_00,
}
}
typedef struct {
int samples;
int dimensions;
float eig_rel_db[NR_MAX_NB_LAYERS];
float rank_capacity[NR_MAX_NB_LAYERS];
// Retained diagonal Gram-matrix energy for active prefix sizes 1/2/4/8,
// relative to all configured CSI-RS ports. This is an offline feature for
// testing whether CSI adds value to antenna-muting decisions.
float prefix_rel_db[4];
} nr_csi_muting_summary_t;
typedef enum {
NR_CSI_RANK_CAPACITY,
NR_CSI_RANK_LEGACY,
NR_CSI_RANK_FIXED,
} nr_csi_rank_policy_t;
typedef struct {
nr_csi_rank_policy_t policy;
uint8_t fixed_rank;
float capacity_margin;
} nr_csi_rank_config_t;
static pthread_once_t csi_rank_config_once = PTHREAD_ONCE_INIT;
static nr_csi_rank_config_t csi_rank_config = {
.policy = NR_CSI_RANK_CAPACITY,
.fixed_rank = 1,
.capacity_margin = 0.05f,
};
static const char *nr_csi_rank_policy_name(const nr_csi_rank_config_t *config)
{
if (config->policy == NR_CSI_RANK_LEGACY)
return "legacy";
if (config->policy == NR_CSI_RANK_FIXED)
return "fixed";
return "capacity";
}
static void configure_csi_rank_policy(void)
{
const char *value = getenv("OAI_CSI_RANK_POLICY");
if (value != NULL && strcmp(value, "legacy") == 0) {
csi_rank_config.policy = NR_CSI_RANK_LEGACY;
} else if (value == NULL || strcmp(value, "capacity") == 0) {
csi_rank_config.policy = NR_CSI_RANK_CAPACITY;
} else if (strncmp(value, "fixed", 5) == 0) {
const char *rank_text = value + 5;
if (*rank_text == ':')
rank_text++;
const long rank = strtol(rank_text, NULL, 10);
if (rank >= 1 && rank <= NR_MAX_NB_LAYERS) {
csi_rank_config.policy = NR_CSI_RANK_FIXED;
csi_rank_config.fixed_rank = rank;
} else {
LOG_W(NR_PHY,
"OAI_CSI_RANK_POLICY=%s has invalid fixed rank; using capacity\n",
value);
}
} else {
LOG_W(NR_PHY,
"Unknown OAI_CSI_RANK_POLICY=%s; using capacity\n",
value);
}
const char *margin_text = getenv("OAI_CSI_RANK_MARGIN");
if (margin_text != NULL) {
const float margin = strtof(margin_text, NULL);
if (isfinite(margin) && margin >= 0.0f && margin <= 1.0f)
csi_rank_config.capacity_margin = margin;
else
LOG_W(NR_PHY,
"OAI_CSI_RANK_MARGIN must be in [0,1]; using %.2f\n",
csi_rank_config.capacity_margin);
}
LOG_I(NR_PHY,
"[CSI_RANK] policy=%s fixed_rank=%u capacity_margin=%.3f\n",
nr_csi_rank_policy_name(&csi_rank_config),
csi_rank_config.fixed_rank,
csi_rank_config.capacity_margin);
}
static const nr_csi_rank_config_t *get_csi_rank_config(void)
{
pthread_once(&csi_rank_config_once, configure_csi_rank_policy);
return &csi_rank_config;
}
void nr_squared_matrix_element(int32_t *a,
int32_t *a_sq,
const unsigned short nb_rb) {
@@ -421,21 +607,136 @@ static int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
const uint8_t N_ports,
c16_t csi_rs_estimated_channel_freq[][N_ports][ue->frame_parms.ofdm_symbol_size],
const int16_t log2_maxh,
uint8_t *rank_indicator)
const uint32_t noise_power,
uint8_t *rank_indicator,
nr_csi_muting_summary_t *muting_summary)
{
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
const nr_csi_rank_config_t *rank_config = get_csi_rank_config();
const int16_t cond_dB_threshold = 5;
int count = 0;
*rank_indicator = 0;
if (muting_summary != NULL)
memset(muting_summary, 0, sizeof(*muting_summary));
if (ue->frame_parms.nb_antennas_rx == 1 || N_ports == 1) {
return 0;
} else if( !(ue->frame_parms.nb_antennas_rx == 2 && N_ports == 2) ) {
LOG_W(NR_PHY, "Rank indicator computation is not implemented for %i x %i system\n",
ue->frame_parms.nb_antennas_rx, N_ports);
}
if (N_ports > MAX_ANT || frame_parms->nb_antennas_rx > MAX_ANT) {
LOG_W(NR_PHY,
"CSI spatial-spectrum computation exceeds platform bound for %i x %i system\n",
frame_parms->nb_antennas_rx,
N_ports);
return -1;
}
const int physical_rank = N_ports < frame_parms->nb_antennas_rx ? N_ports : frame_parms->nb_antennas_rx;
const int max_rank = physical_rank < NR_MAX_NB_LAYERS ? physical_rank : NR_MAX_NB_LAYERS;
if (max_rank < 2) {
return 0;
}
// Legacy 2x2 keeps the existing condition-number path below. Capacity and
// fixed policies use the same eigen-spectrum path for every MIMO size.
const bool legacy_2x2 = rank_config->policy == NR_CSI_RANK_LEGACY
&& ue->frame_parms.nb_antennas_rx == 2 && N_ports == 2;
if (!legacy_2x2) {
int rank_count[NR_MAX_NB_LAYERS] = {0};
double eig_sum[MAX_ANT] = {0};
double port_power[MAX_ANT] = {0};
double capacity_sum[NR_MAX_NB_LAYERS] = {0};
const float eig_ratio_dB_threshold = 8.0f;
const int sc_stride = physical_rank > 4 ? 4 : 1;
for (int rb = csirs_config_pdu->start_rb; rb < (csirs_config_pdu->start_rb + csirs_config_pdu->nr_of_rbs); rb++) {
if (csirs_config_pdu->freq_density <= 1 && csirs_config_pdu->freq_density != (rb % 2)) continue;
uint16_t k = rb * NR_NB_SC_PER_RB;
for (int sc_idx = 0; sc_idx < NR_NB_SC_PER_RB; sc_idx += sc_stride) {
// Patch 3: float Gram matrix A = H^H H computed directly from channel estimates
// (avoids int16 saturation that capped RI at 2 in 4-RX setups).
float A_re[MAX_ANT][MAX_ANT] = {{0}}, A_im[MAX_ANT][MAX_ANT] = {{0}};
for (int rx = 0; rx < frame_parms->nb_antennas_rx; rx++) {
for (int p1 = 0; p1 < N_ports; p1++) {
c16_t h1 = csi_rs_estimated_channel_freq[rx][p1][k + sc_idx];
float h1_re = (float)h1.r, h1_im = (float)h1.i;
for (int p2 = 0; p2 < N_ports; p2++) {
c16_t h2 = csi_rs_estimated_channel_freq[rx][p2][k + sc_idx];
float h2_re = (float)h2.r, h2_im = (float)h2.i;
A_re[p1][p2] += h1_re * h2_re + h1_im * h2_im;
A_im[p1][p2] += -h1_im * h2_re + h1_re * h2_im;
}
}
}
for (int p = 0; p < N_ports; p++)
port_power[p] += A_re[p][p];
float eig[MAX_ANT] = {0};
nr_hermitian_eigenvalues(N_ports, A_re, A_im, eig);
if (eig[0] < 1e-6f) continue;
float rank_capacity[NR_MAX_NB_LAYERS] = {0};
nr_pdsch_rank_capacity_scores(eig, max_rank, (float)noise_power, rank_capacity);
for (int r = 0; r < max_rank; r++)
capacity_sum[r] += rank_capacity[r];
rank_count[0]++;
for (int r = 0; r < N_ports; r++)
eig_sum[r] += eig[r];
for (int r = 1; r < max_rank; r++) {
if (eig[r] > 1e-6f && 10.0f * log10f(eig[0] / eig[r]) < eig_ratio_dB_threshold) {
rank_count[r]++;
} else {
break;
}
}
}
}
int total_sc = rank_count[0];
if (total_sc > 0) {
float mean_capacity[NR_MAX_NB_LAYERS] = {0};
for (int r = 0; r < max_rank; r++)
mean_capacity[r] = capacity_sum[r] / total_sc;
if (rank_config->policy == NR_CSI_RANK_LEGACY) {
for (int r = max_rank - 1; r >= 1; r--) {
if (rank_count[r] > total_sc / 2) {
*rank_indicator = r;
break;
}
}
} else if (rank_config->policy == NR_CSI_RANK_FIXED) {
*rank_indicator = min(rank_config->fixed_rank, max_rank) - 1;
} else {
*rank_indicator = nr_pdsch_select_rank(mean_capacity,
max_rank,
rank_config->capacity_margin)
- 1;
}
if (muting_summary != NULL) {
muting_summary->samples = total_sc;
muting_summary->dimensions = physical_rank;
for (int r = 0; r < NR_MAX_NB_LAYERS; r++) {
muting_summary->eig_rel_db[r] =
r < physical_rank && eig_sum[r] > 0.0 && eig_sum[0] > 0.0
? 10.0f * log10f((float)(eig_sum[r] / eig_sum[0]))
: -INFINITY;
muting_summary->rank_capacity[r] = r < max_rank ? mean_capacity[r] : 0.0f;
}
double full_power = 0.0;
for (int p = 0; p < N_ports; p++)
full_power += port_power[p];
const int prefix_size[4] = {1, 2, 4, 8};
for (int candidate = 0; candidate < 4; candidate++) {
const int keep = prefix_size[candidate] < N_ports ? prefix_size[candidate] : N_ports;
double prefix_power = 0.0;
for (int p = 0; p < keep; p++)
prefix_power += port_power[p];
muting_summary->prefix_rel_db[candidate] =
prefix_power > 0.0 && full_power > 0.0
? 10.0f * log10f((float)(prefix_power / full_power))
: -INFINITY;
}
}
}
return 0;
}
/* Example 2x2: Hh x H =
* | 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 |
@@ -554,10 +855,16 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
const uint32_t interference_plus_noise_power,
const uint8_t rank_indicator,
const int16_t log2_re,
uint16_t *pmi_x1,
uint8_t *i2,
int32_t *precoded_sinr_dB)
{
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
// RF simulators can report exactly zero interference/noise. Keep every PMI
// rank path finite, including the eight-port codebook search below.
const uint32_t non_zero_noise_power = interference_plus_noise_power == 0 ? 1 : interference_plus_noise_power;
*pmi_x1 = 0;
i2[0] = 0;
// i1 is a three-element vector in the form of [i11 i12 i13], when CodebookType is specified as 'Type1SinglePanel'.
// Note that i13 is not applicable when the number of transmission layers is one of {1, 5, 6, 7, 8}.
@@ -587,14 +894,14 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
if (count > 0) {
const int64_t avg_signal_power = signal_power / count;
// Non RF devices like ZMQ has virtually zero noise. So here we make noise as 1 to return maximum sinr.
const uint32_t sinr = avg_signal_power / ((interference_plus_noise_power == 0) ? 1 : interference_plus_noise_power);
const uint32_t sinr = avg_signal_power / non_zero_noise_power;
*precoded_sinr_dB = dB_fixed(sinr);
}
return 0;
}
if(rank_indicator == 0 || rank_indicator == 1) {
if ((rank_indicator == 0 || rank_indicator == 1) && N_ports != 8) {
c64_t sum[4] = {0};
c64_t sum2[4] = {0};
int64_t tested_precoded_sinr[4] = {0};
@@ -640,7 +947,7 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
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;
tested_precoded_sinr[p] = (power_re + power_im) / non_zero_noise_power;
}
if(rank_indicator == 0) {
@@ -655,6 +962,147 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
*precoded_sinr_dB = dB_fixed((tested_precoded_sinr[i2[0]] + tested_precoded_sinr[i2[0]+2])>>1);
}
} else if (rank_indicator < NR_MAX_NB_LAYERS && rank_indicator < N_ports && N_ports == 8
&& rank_indicator != 2 && rank_indicator != 3
&& csirs_config_pdu->codebook_n1 == 4
&& csirs_config_pdu->codebook_n2 == 1
&& csirs_config_pdu->codebook_mode == 1) {
const int nlayers = rank_indicator + 1;
float gram_re[MAX_ANT][MAX_ANT] = {{0}};
float gram_im[MAX_ANT][MAX_ANT] = {{0}};
int samples = 0;
const int sc_stride = 4;
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;
const uint16_t k = rb * NR_NB_SC_PER_RB;
for (int sc = 0; sc < NR_NB_SC_PER_RB; sc += sc_stride) {
for (int rx = 0; rx < frame_parms->nb_antennas_rx; rx++) {
for (int p = 0; p < N_ports; p++) {
const c16_t hp = csi_rs_estimated_channel_freq[rx][p][k + sc];
for (int q = 0; q < N_ports; q++) {
const c16_t hq = csi_rs_estimated_channel_freq[rx][q][k + sc];
gram_re[p][q] += (float)hp.r * hq.r + (float)hp.i * hq.i;
gram_im[p][q] += -(float)hp.i * hq.r + (float)hp.r * hq.i;
}
}
}
samples++;
}
}
float best_score = -INFINITY;
uint8_t best_i11 = 0;
uint8_t best_i13 = 0;
uint8_t best_i2 = 0;
const uint8_t i11_count = nr_type1_sp_4x1_i11_count(nlayers);
const uint8_t i13_count = nr_type1_sp_4x1_i13_count(nlayers);
const uint8_t i2_count = nr_type1_sp_4x1_i2_count(nlayers);
for (uint8_t candidate_i11 = 0; candidate_i11 < i11_count; candidate_i11++) {
for (uint8_t candidate_i13 = 0; candidate_i13 < i13_count; candidate_i13++) {
for (uint8_t candidate_i2 = 0; candidate_i2 < i2_count; candidate_i2++) {
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS];
AssertFatal(nr_type1_sp_4x1_weights(nlayers, candidate_i11, candidate_i13, candidate_i2, weights),
"Invalid rank-%d PMI candidate i11=%u i13=%u i2=%u\n",
nlayers,
candidate_i11,
candidate_i13,
candidate_i2);
float precoded_re[MAX_ANT][MAX_ANT] = {{0}};
float precoded_im[MAX_ANT][MAX_ANT] = {{0}};
for (int a = 0; a < nlayers; a++) {
for (int b = 0; b < nlayers; b++) {
double value_re = 0.0;
double value_im = 0.0;
for (int p = 0; p < N_ports; p++) {
const double wa_re = weights[a][p].r / (double)SHRT_MAX;
const double wa_im = weights[a][p].i / (double)SHRT_MAX;
for (int q = 0; q < N_ports; q++) {
const double wb_re = weights[b][q].r / (double)SHRT_MAX;
const double wb_im = weights[b][q].i / (double)SHRT_MAX;
const double left_re = wa_re * gram_re[p][q] + wa_im * gram_im[p][q];
const double left_im = wa_re * gram_im[p][q] - wa_im * gram_re[p][q];
value_re += left_re * wb_re - left_im * wb_im;
value_im += left_re * wb_im + left_im * wb_re;
}
}
precoded_re[a][b] = value_re;
precoded_im[a][b] = value_im;
}
}
// Remove accumulated floating-point asymmetry before Jacobi sweeps.
for (int a = 0; a < nlayers; a++) {
precoded_im[a][a] = 0.0f;
for (int b = a + 1; b < nlayers; b++) {
const float avg_re = 0.5f * (precoded_re[a][b] + precoded_re[b][a]);
const float avg_im = 0.5f * (precoded_im[a][b] - precoded_im[b][a]);
precoded_re[a][b] = precoded_re[b][a] = avg_re;
precoded_im[a][b] = avg_im;
precoded_im[b][a] = -avg_im;
}
}
float eigenvalues[MAX_ANT] = {0};
nr_hermitian_eigenvalues(nlayers, precoded_re, precoded_im, eigenvalues);
float score = 0.0f;
if (samples > 0)
for (int layer = 0; layer < nlayers; layer++)
score += log2f(1.0f + eigenvalues[layer] / (samples * (float)non_zero_noise_power));
const float tie_tolerance = fmaxf(1e-4f, fabsf(best_score) * 1e-5f);
if (!isfinite(best_score) || score > best_score + tie_tolerance) {
best_score = score;
best_i11 = candidate_i11;
best_i13 = candidate_i13;
best_i2 = candidate_i2;
}
}
}
}
// x1 is packed i11|i12|i13. N2=1 makes i12 zero length; i13 is
// two bits for ranks 2--4 and absent for the other ranks.
const uint8_t i13_bitlen = i13_count > 1 ? 2 : 0;
*pmi_x1 = (best_i11 << i13_bitlen) | best_i13;
i2[0] = best_i2;
const float equivalent_sinr = best_score > 0.0f ? exp2f(best_score / nlayers) - 1.0f : 0.0f;
const float equivalent_sinr_dB = equivalent_sinr > 0.0f ? 10.0f * log10f(equivalent_sinr) : 0.0f;
*precoded_sinr_dB = lroundf(fmaxf(0.0f, fminf(equivalent_sinr_dB, INT_MAX)));
const char *pmi_telemetry = getenv("OAI_CSI_PMI_TELEMETRY");
if (pmi_telemetry != NULL && strcmp(pmi_telemetry, "1") == 0)
LOG_I(NR_PHY,
"[CSI_PMI] ports=8 geometry=4x1 mode=1 rank=%d i11=%u i13=%u i2=%u candidates=%u "
"capacity=%.4f equivalent_sinr_db=%.2f samples=%d\n",
nlayers,
best_i11,
best_i13,
best_i2,
i11_count * i13_count * i2_count,
best_score,
equivalent_sinr_dB,
samples);
} else if (rank_indicator >= 2 && rank_indicator < NR_MAX_NB_LAYERS
&& rank_indicator < N_ports) {
// Unsupported geometries retain the prior identity-like CQI fallback.
// The eight-port rank-3/4 receive path also remains identity: OAI's
// legacy detector cannot yet decode a non-identity 8-port rank-3/4 PDSCH.
int nlayers = rank_indicator + 1;
int64_t total_power = 0;
for (int rb = csirs_config_pdu->start_rb;
rb < (csirs_config_pdu->start_rb + csirs_config_pdu->nr_of_rbs); rb++) {
if (csirs_config_pdu->freq_density <= 1 && csirs_config_pdu->freq_density != (rb % 2))
continue;
uint16_t k = rb * NR_NB_SC_PER_RB;
for (int ant_rx = 0; ant_rx < frame_parms->nb_antennas_rx; ant_rx++) {
for (int p = 0; p < nlayers; p++) {
const c16_t h = csi_rs_estimated_channel_freq[ant_rx][p][k];
total_power += (int64_t)h.r * h.r + (int64_t)h.i * h.i;
}
}
}
if (total_power > 0)
*precoded_sinr_dB = dB_fixed((total_power / nlayers) / non_zero_noise_power);
} else {
LOG_W(NR_PHY, "PMI computation is not implemented for rank indicator %i\n", rank_indicator+1);
return -1;
@@ -895,6 +1343,7 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
&noise_power);
uint8_t rank_indicator = 0;
nr_csi_muting_summary_t muting_summary = {0};
// bit 1 in bitmap to indicate RI measurment
if (csirs_config_pdu->measurement_bitmap & 2) {
nr_csi_rs_ri_estimation(ue,
@@ -902,10 +1351,12 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
mapping_parms.ports,
csi_rs_estimated_channel_freq,
log2_maxh,
&rank_indicator);
noise_power,
&rank_indicator,
&muting_summary);
}
uint8_t i1[3] = {0};
uint16_t i1 = 0;
uint8_t i2[1] = {0};
uint8_t cqi = 0;
int32_t precoded_sinr_dB = 0;
@@ -918,6 +1369,7 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
csi_info->csi_im_meas_computed ? csi_info->interference_plus_noise_power : noise_power,
rank_indicator,
log2_re,
&i1,
i2,
&precoded_sinr_dB);
@@ -926,17 +1378,58 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
nr_csi_rs_cqi_estimation(precoded_sinr_dB, &cqi);
}
const char *muting_telemetry = getenv("OAI_CSI_MUTING_TELEMETRY");
if (muting_summary.samples > 0 && muting_telemetry != NULL && strcmp(muting_telemetry, "1") == 0) {
const nr_csi_rank_config_t *rank_config = get_csi_rank_config();
LOG_I(NR_PHY,
"[CSI_MUTING] frame=%d slot=%d ue=%d rx=%u ports=%u samples=%d "
"rank_policy=%s ri=%u cqi=%u sinr_db=%d "
"eig_rel_db=%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f "
"rank_capacity=%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f "
"prefix_rel_db=%.2f,%.2f,%.2f,%.2f\n",
proc->frame_rx,
proc->nr_slot_rx,
ue->Mod_id,
frame_parms->nb_antennas_rx,
mapping_parms.ports,
muting_summary.samples,
nr_csi_rank_policy_name(rank_config),
rank_indicator + 1,
cqi,
precoded_sinr_dB,
muting_summary.eig_rel_db[0],
muting_summary.eig_rel_db[1],
muting_summary.eig_rel_db[2],
muting_summary.eig_rel_db[3],
muting_summary.eig_rel_db[4],
muting_summary.eig_rel_db[5],
muting_summary.eig_rel_db[6],
muting_summary.eig_rel_db[7],
muting_summary.rank_capacity[0],
muting_summary.rank_capacity[1],
muting_summary.rank_capacity[2],
muting_summary.rank_capacity[3],
muting_summary.rank_capacity[4],
muting_summary.rank_capacity[5],
muting_summary.rank_capacity[6],
muting_summary.rank_capacity[7],
muting_summary.prefix_rel_db[0],
muting_summary.prefix_rel_db[1],
muting_summary.prefix_rel_db[2],
muting_summary.prefix_rel_db[3]);
}
switch (csirs_config_pdu->measurement_bitmap) {
case 1 :
LOG_I(NR_PHY, "[UE %d] RSRP = %i dBm\n", ue->Mod_id, rsrp_dBm);
break;
case 26 :
LOG_I(NR_PHY, "RI = %i i1 = %i.%i.%i, i2 = %i, SINR = %i dB, CQI = %i\n",
rank_indicator + 1, i1[0], i1[1], i1[2], i2[0], precoded_sinr_dB, cqi);
LOG_I(NR_PHY, "RI = %i i1 = %u, i2 = %i, SINR = %i dB, CQI = %i\n",
rank_indicator + 1, i1, i2[0], precoded_sinr_dB, cqi);
break;
case 27 :
LOG_I(NR_PHY, "RSRP = %i dBm, RI = %i i1 = %i.%i.%i, i2 = %i, SINR = %i dB, CQI = %i\n",
rsrp_dBm, rank_indicator + 1, i1[0], i1[1], i1[2], i2[0], precoded_sinr_dB, cqi);
LOG_I(NR_PHY, "RSRP = %i dBm, RI = %i i1 = %u, i2 = %i, SINR = %i dB, CQI = %i\n",
rsrp_dBm, rank_indicator + 1, i1, i2[0], precoded_sinr_dB, cqi);
break;
default :
AssertFatal(false, "Not supported measurement configuration\n");
@@ -953,7 +1446,7 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
.is_neighboring_cell = false,
.rsrp_dBm = rsrp_dBm,
.rank_indicator = rank_indicator,
.i1 = *i1,
.i1 = i1,
.i2 = *i2,
.cqi = cqi,
.radiolink_monitoring = RLM_no_monitoring, // TODO do be activated in case of RLM based on CSI-RS
@@ -961,6 +1454,6 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
nr_downlink_indication_t dl_indication;
fapi_nr_rx_indication_t rx_ind = {0};
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, NULL, NULL, 1, proc, (void *)&l1_measurements, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, 0, 0, NULL, 1, proc, (void *)&l1_measurements, NULL);
ue->if_inst->dl_indication(&dl_indication);
}
+12 -7
View File
@@ -290,14 +290,19 @@ static void nr_pdcch_channel_compensation(int arraySz,
static void nr_pdcch_detection_mrc(int nb_ant, int sz, c16_t rxdataF_comp[][sz])
{
c16_t *rx0 = rxdataF_comp[0];
// MRC on each re of rb
// input always aligned and accepting tail padding to process all actual samples
for (int a = 1; a < nb_ant; a++) {
c16_t *rx = rxdataF_comp[a];
for (int i = 0; i < sz; i += 4) {
*(simde__m128i *)(rx0 + i) = simde_mm_adds_epi16(simde_mm_srai_epi16(*(simde__m128i *)(rx0 + i), 1),
simde_mm_srai_epi16(*(simde__m128i *)(rx + i), 1));
/* Accumulate every matched-filter branch with equal weight. The former
* pairwise right shift repeatedly attenuated the existing accumulator, so
* with eight RX antennas branch 0 was divided by 128 and PDCCH decoding
* failed even at high SNR. Use a wide accumulator and saturate once. */
for (int i = 0; i < sz; i++) {
int32_t real = 0;
int32_t imag = 0;
for (int a = 0; a < nb_ant; a++) {
real += rxdataF_comp[a][i].r;
imag += rxdataF_comp[a][i].i;
}
rx0[i].r = min(max(real, INT16_MIN), INT16_MAX);
rx0[i].i = min(max(imag, INT16_MIN), INT16_MAX);
}
}
+185 -244
View File
@@ -43,178 +43,143 @@ void nr_dlsch_unscrambling(int16_t *llr, uint32_t size, uint8_t q, uint32_t Nid,
void nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t *dlsch,
int16_t **dlsch_llr,
uint8_t **b,
int *G,
int nb_dlsch,
int cw_idx,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
int16_t *dlsch_llr,
uint8_t *b,
int number_rbs,
uint8_t *DLSCH_ids)
int G)
{
nrLDPC_TB_decoding_parameters_t TBs[nb_dlsch];
memset(TBs, 0, sizeof(TBs));
nrLDPC_TB_decoding_parameters_t TB_parameters = {0};
nrLDPC_slot_decoding_parameters_t slot_parameters = {
.frame = proc->frame_rx,
.slot = proc->nr_slot_rx,
.nb_TBs = nb_dlsch,
.nb_TBs = 1,
.threadPool = &get_nrUE_params()->Tpool,
.TBs = TBs
.TBs = &TB_parameters
};
int max_num_segments = 0;
uint8_t dmrs_Type = dlsch_config->dmrsConfigType;
int harq_pid = dlsch_config->harq_process_nbr;
AssertFatal(dmrs_Type == 0 || dmrs_Type == 1, "Illegal dmrs_type %d\n", dmrs_Type);
fapi_nr_dl_cw_info_t *cw_info = &dlsch->cw_info;
NR_DL_UE_HARQ_t *harq_process = &phy_vars_ue->dl_harq_processes[cw_idx][dlsch_config->harq_process_nbr];
LOG_D(PHY, "Round %d RV idx %d\n", harq_process->DLround, cw_info->rv);
uint8_t nb_re_dmrs;
if (dmrs_Type == NFAPI_NR_DMRS_TYPE1)
nb_re_dmrs = 6 * dlsch_config->n_dmrs_cdm_groups;
else
nb_re_dmrs = 4 * dlsch_config->n_dmrs_cdm_groups;
uint16_t dmrs_length = get_num_dmrs(dlsch_config->dlDmrsSymbPos);
for (uint8_t pdsch_id = 0; pdsch_id < nb_dlsch; pdsch_id++) {
uint8_t DLSCH_id = DLSCH_ids[pdsch_id];
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[DLSCH_id].dlsch_config;
uint8_t dmrs_Type = dlsch_config->dmrsConfigType;
int harq_pid = dlsch_config->harq_process_nbr;
NR_DL_UE_HARQ_t *harq_process = &phy_vars_ue->dl_harq_processes[DLSCH_id][harq_pid];
TB_parameters.harq_unique_pid = 2 * harq_pid + cw_idx;
TB_parameters.G = G;
TB_parameters.nb_rb = number_rbs;
TB_parameters.Qm = cw_info->qamModOrder;
TB_parameters.mcs = cw_info->mcs;
TB_parameters.nb_layers = cw_info->Nl;
TB_parameters.BG = cw_info->ldpcBaseGraph;
TB_parameters.A = cw_info->TBS;
TB_parameters.processedSegments = &harq_process->processedSegments;
AssertFatal(dmrs_Type == 0 || dmrs_Type == 1, "Illegal dmrs_type %d\n", dmrs_Type);
uint8_t nb_re_dmrs;
float Coderate = (float)cw_info->targetCodeRate / 10240.0f;
LOG_D(PHY, "Round %d RV idx %d\n", harq_process->DLround, dlsch->dlsch_config.rv);
if (dmrs_Type == NFAPI_NR_DMRS_TYPE1)
nb_re_dmrs = 6 * dlsch_config->n_dmrs_cdm_groups;
else
nb_re_dmrs = 4 * dlsch_config->n_dmrs_cdm_groups;
uint16_t dmrs_length = get_num_dmrs(dlsch_config->dlDmrsSymbPos);
if (!harq_process) {
LOG_E(PHY, "dlsch_decoding_slot.c: NULL harq_process pointer\n");
return;
}
nrLDPC_TB_decoding_parameters_t *TB_parameters = &TBs[pdsch_id];
/* Neither harq_pid nor DLSCH_id are unique in the instance
* but their combination is.
* Since DLSCH_id < 2
* then 2 * harq_pid + DLSCH_id is unique.
*/
TB_parameters->harq_unique_pid = 2 * harq_pid + DLSCH_id;
// ------------------------------------------------------------------
TB_parameters->G = G[DLSCH_id];
TB_parameters->nb_rb = number_rbs;
TB_parameters->Qm = dlsch_config->qamModOrder;
TB_parameters->mcs = dlsch_config->mcs;
TB_parameters->nb_layers = dlsch[DLSCH_id].Nl;
TB_parameters->BG = dlsch_config->ldpcBaseGraph;
TB_parameters->A = dlsch_config->TBS;
// ------------------------------------------------------------------
TB_parameters->processedSegments = &harq_process->processedSegments;
float Coderate = (float)dlsch->dlsch_config.targetCodeRate / 10240.0f;
LOG_D(
PHY,
LOG_D(PHY,
"%d.%d DLSCH %d Decoding, harq_pid %d TBS %d G %d nb_re_dmrs %d length dmrs %d mcs %d Nl %d nb_symb_sch %d nb_rb %d Qm %d "
"Coderate %f\n",
slot_parameters.frame,
slot_parameters.slot,
DLSCH_id,
cw_idx,
harq_pid,
dlsch_config->TBS,
TB_parameters->G,
cw_info->TBS,
G,
nb_re_dmrs,
dmrs_length,
TB_parameters->mcs,
TB_parameters->nb_layers,
TB_parameters.mcs,
TB_parameters.nb_layers,
dlsch_config->number_symbols,
TB_parameters->nb_rb,
TB_parameters->Qm,
TB_parameters.nb_rb,
TB_parameters.Qm,
Coderate);
if (harq_process->first_rx == 1) {
// This is a new packet, so compute quantities regarding segmentation
nr_segmentation(NULL,
NULL,
lenWithCrc(1, TB_parameters->A), // We give a max size in case of 1 segment
&TB_parameters->C,
&TB_parameters->K,
&TB_parameters->Z, // [hna] Z is Zc
&TB_parameters->F,
TB_parameters->BG);
harq_process->C = TB_parameters->C;
harq_process->K = TB_parameters->K;
harq_process->Z = TB_parameters->Z;
harq_process->F = TB_parameters->F;
if (harq_process->first_rx == 1) {
// This is a new packet, so compute quantities regarding segmentation
nr_segmentation(NULL,
NULL,
lenWithCrc(1, TB_parameters.A), // We give a max size in case of 1 segment
&TB_parameters.C,
&TB_parameters.K,
&TB_parameters.Z, // [hna] Z is Zc
&TB_parameters.F,
TB_parameters.BG);
harq_process->C = TB_parameters.C;
harq_process->K = TB_parameters.K;
harq_process->Z = TB_parameters.Z;
harq_process->F = TB_parameters.F;
if (harq_process->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * TB_parameters->nb_layers) {
LOG_E(PHY, "nr_segmentation.c: too many segments %d, A %d\n", harq_process->C, TB_parameters->A);
return;
}
if (LOG_DEBUGFLAG(DEBUG_DLSCH_DECOD) && (!slot_parameters.frame % 100))
LOG_I(PHY, "K %d C %d Z %d nl %d \n", harq_process->K, harq_process->C, harq_process->Z, TB_parameters->nb_layers);
// clear HARQ buffer
for (int i = 0; i < harq_process->C; i++)
memset(harq_process->d[i], 0, 5 * 8448 * sizeof(int16_t));
} else {
// This is not a new packet, so retrieve previously computed quantities regarding segmentation
TB_parameters->C = harq_process->C;
TB_parameters->K = harq_process->K;
TB_parameters->Z = harq_process->Z;
TB_parameters->F = harq_process->F;
if (harq_process->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * TB_parameters.nb_layers) {
LOG_E(PHY, "nr_segmentation.c: too many segments %d, A %d\n", harq_process->C, TB_parameters.A);
return;
}
max_num_segments = max(max_num_segments, TB_parameters->C);
if (LOG_DEBUGFLAG(DEBUG_DLSCH_DECOD))
LOG_I(PHY, "Segmentation: C %d, K %d\n", harq_process->C, harq_process->K);
TB_parameters->max_ldpc_iterations = dlsch[DLSCH_id].max_ldpc_iterations;
TB_parameters->rv_index = dlsch_config->rv;
TB_parameters->tbslbrm = dlsch_config->tbslbrm;
TB_parameters->abort_decode = &harq_process->abort_decode;
set_abort(&harq_process->abort_decode, false);
if (LOG_DEBUGFLAG(DEBUG_DLSCH_DECOD) && (!slot_parameters.frame % 100))
LOG_I(PHY, "K %d C %d Z %d nl %d \n", harq_process->K, harq_process->C, harq_process->Z, TB_parameters.nb_layers);
// clear HARQ buffer
for (int i = 0; i < harq_process->C; i++)
memset(harq_process->d[i], 0, 5 * 8448 * sizeof(int16_t));
} else {
// This is not a new packet, so retrieve previously computed quantities regarding segmentation
TB_parameters.C = harq_process->C;
TB_parameters.K = harq_process->K;
TB_parameters.Z = harq_process->Z;
TB_parameters.F = harq_process->F;
}
nrLDPC_segment_decoding_parameters_t segments[nb_dlsch][max_num_segments];
if (LOG_DEBUGFLAG(DEBUG_DLSCH_DECOD))
LOG_I(PHY, "Segmentation: C %d, K %d\n", harq_process->C, harq_process->K);
TB_parameters.max_ldpc_iterations = dlsch->max_ldpc_iterations;
TB_parameters.rv_index = cw_info->rv;
TB_parameters.tbslbrm = dlsch_config->tbslbrm;
TB_parameters.abort_decode = &harq_process->abort_decode;
set_abort(&harq_process->abort_decode, false);
nrLDPC_segment_decoding_parameters_t segments[TB_parameters.C];
memset(segments, 0, sizeof(segments));
bool d_to_be_cleared[nb_dlsch][max_num_segments];
bool d_to_be_cleared[TB_parameters.C];
memset(d_to_be_cleared, 0, sizeof(d_to_be_cleared));
for (uint8_t pdsch_id = 0; pdsch_id < nb_dlsch; pdsch_id++) {
uint8_t DLSCH_id = DLSCH_ids[pdsch_id];
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[DLSCH_id].dlsch_config;
int harq_pid = dlsch_config->harq_process_nbr;
NR_DL_UE_HARQ_t *harq_process = &phy_vars_ue->dl_harq_processes[DLSCH_id][harq_pid];
TB_parameters.segments = segments;
nrLDPC_TB_decoding_parameters_t *TB_parameters = &TBs[pdsch_id];
TB_parameters->segments = segments[pdsch_id];
uint32_t r_offset = 0;
for (int r = 0; r < TB_parameters.C; r++) {
if (harq_process->first_rx == 1)
d_to_be_cleared[r] = true;
else
d_to_be_cleared[r] = false;
nrLDPC_segment_decoding_parameters_t *segment_parameters = &TB_parameters.segments[r];
segment_parameters->E = nr_get_E(TB_parameters.G,
TB_parameters.C,
TB_parameters.Qm,
TB_parameters.nb_layers,
r);
segment_parameters->R = nr_get_R_ldpc_decoder(TB_parameters.rv_index,
segment_parameters->E,
TB_parameters.BG,
TB_parameters.Z,
&harq_process->llrLen,
harq_process->DLround);
segment_parameters->llr = dlsch_llr + r_offset;
segment_parameters->d = harq_process->d[r];
segment_parameters->d_to_be_cleared = &d_to_be_cleared[r];
segment_parameters->c = harq_process->c[r];
segment_parameters->decodeSuccess = false;
uint32_t r_offset = 0;
for (int r = 0; r < TB_parameters->C; r++) {
if (harq_process->first_rx == 1)
d_to_be_cleared[pdsch_id][r] = true;
else
d_to_be_cleared[pdsch_id][r] = false;
nrLDPC_segment_decoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
segment_parameters->E = nr_get_E(TB_parameters->G,
TB_parameters->C,
TB_parameters->Qm,
TB_parameters->nb_layers,
r);
segment_parameters->R = nr_get_R_ldpc_decoder(TB_parameters->rv_index,
segment_parameters->E,
TB_parameters->BG,
TB_parameters->Z,
&harq_process->llrLen,
harq_process->DLround);
segment_parameters->llr = dlsch_llr[DLSCH_id] + r_offset;
segment_parameters->d = harq_process->d[r];
segment_parameters->d_to_be_cleared = &d_to_be_cleared[pdsch_id][r];
segment_parameters->c = harq_process->c[r];
segment_parameters->decodeSuccess = false;
reset_meas(&segment_parameters->ts_deinterleave);
reset_meas(&segment_parameters->ts_rate_unmatch);
reset_meas(&segment_parameters->ts_ldpc_decode);
reset_meas(&segment_parameters->ts_deinterleave);
reset_meas(&segment_parameters->ts_rate_unmatch);
reset_meas(&segment_parameters->ts_ldpc_decode);
r_offset += segment_parameters->E;
}
r_offset += segment_parameters->E;
}
int ret_decoder = phy_vars_ue->nrLDPC_coding_interface.nrLDPC_coding_decoder(&slot_parameters);
@@ -224,114 +189,90 @@ void nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue,
return;
}
// post decode
for (uint8_t pdsch_id = 0; pdsch_id < nb_dlsch; pdsch_id++) {
uint8_t DLSCH_id = DLSCH_ids[pdsch_id];
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[DLSCH_id].dlsch_config;
int harq_pid = dlsch_config->harq_process_nbr;
NR_DL_UE_HARQ_t *harq_process = &phy_vars_ue->dl_harq_processes[DLSCH_id][harq_pid];
nrLDPC_TB_decoding_parameters_t *TB_parameters = &TBs[pdsch_id];
uint32_t offset = 0;
for (int r = 0; r < TB_parameters->C; r++) {
nrLDPC_segment_decoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
if (segment_parameters->decodeSuccess) {
memcpy(b[DLSCH_id] + offset,
harq_process->c[r],
(harq_process->K >> 3) - (harq_process->F >> 3) - ((harq_process->C > 1) ? 3 : 0));
} else {
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[DLSCH_id].dlsch_config;
LOG_D(PHY, "frame=%d, slot=%d, first_rx=%d, rv_index=%d\n", proc->frame_rx, proc->nr_slot_rx, harq_process->first_rx, dlsch_config->rv);
LOG_D(PHY, "downlink segment error %d/%d\n", r, harq_process->C);
LOG_D(PHY, "DLSCH %d in error\n", DLSCH_id);
}
offset += (harq_process->K >> 3) - (harq_process->F >> 3) - ((harq_process->C > 1) ? 3 : 0);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_DEINTERLEAVING_STATS], &segment_parameters->ts_deinterleave);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_RATE_UNMATCHING_STATS], &segment_parameters->ts_rate_unmatch);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_LDPC_DECODING_STATS], &segment_parameters->ts_ldpc_decode);
}
kpiStructure.nb_total++;
kpiStructure.blockSize = dlsch_config->TBS;
kpiStructure.dl_mcs = dlsch_config->mcs;
kpiStructure.nofRBs = number_rbs;
harq_process->decodeResult = harq_process->processedSegments == harq_process->C;
if (harq_process->decodeResult && harq_process->C > 1) {
/* check global CRC */
int A = dlsch->dlsch_config.TBS;
// we have regrouped the transport block
if (!check_crc(b[DLSCH_id], lenWithCrc(1, A), crcType(1, A))) {
LOG_E(PHY,
" Frame %d.%d LDPC global CRC fails, but individual LDPC CRC succeeded. %d segs\n",
proc->frame_rx,
proc->nr_slot_rx,
harq_process->C);
harq_process->decodeResult = false;
}
}
if (harq_process->decodeResult) {
// We search only a reccuring OAI error that propagates all 0 packets with a 0 CRC, so we
const int sz = dlsch->dlsch_config.TBS / 8;
if (b[DLSCH_id][sz] == 0 && b[DLSCH_id][sz + 1] == 0) {
// do the check only if the 2 first bytes of the CRC are 0 (it can be CRC16 or CRC24)
int i = 0;
while (b[DLSCH_id][i] == 0 && i < sz)
i++;
if (i == sz) {
LOG_E(PHY,
"received all 0 pdu, consider it false reception, even if the TS 38.212 7.2.1 says only we should attach the "
"corresponding CRC, and nothing prevents to have a all 0 packet\n");
harq_process->decodeResult = false;
}
}
}
if (harq_process->decodeResult) {
LOG_D(PHY, "DLSCH received ok \n");
harq_process->status = NR_SCH_IDLE;
dlsch->last_iteration_cnt = dlsch->max_ldpc_iterations - 1;
uint32_t offset = 0;
for (int r = 0; r < TB_parameters.C; r++) {
nrLDPC_segment_decoding_parameters_t *segment_parameters = &TB_parameters.segments[r];
if (segment_parameters->decodeSuccess) {
memcpy(b + offset, harq_process->c[r], (harq_process->K >> 3) - (harq_process->F >> 3) - ((harq_process->C > 1) ? 3 : 0));
} else {
LOG_D(PHY, "DLSCH received nok \n");
kpiStructure.nb_nack++;
dlsch->last_iteration_cnt = dlsch->max_ldpc_iterations;
UEdumpScopeData(phy_vars_ue, proc->nr_slot_rx, proc->frame_rx, "DLSCH_NACK");
LOG_D(PHY, "frame=%d, slot=%d, first_rx=%d, rv_index=%d\n", proc->frame_rx, proc->nr_slot_rx, harq_process->first_rx, cw_info->rv);
LOG_D(PHY, "downlink segment error %d/%d\n", r, harq_process->C);
}
offset += (harq_process->K >> 3) - (harq_process->F >> 3) - ((harq_process->C > 1) ? 3 : 0);
uint8_t dmrs_Type = dlsch_config->dmrsConfigType;
uint8_t nb_re_dmrs;
if (dmrs_Type == NFAPI_NR_DMRS_TYPE1)
nb_re_dmrs = 6 * dlsch_config->n_dmrs_cdm_groups;
else
nb_re_dmrs = 4 * dlsch_config->n_dmrs_cdm_groups;
uint16_t dmrs_length = get_num_dmrs(dlsch_config->dlDmrsSymbPos);
float Coderate = (float)dlsch->dlsch_config.targetCodeRate / 10240.0f;
LOG_D(PHY,
"%d.%d DLSCH Decoded, harq_pid %d, round %d, result: %d TBS %d (%d) G %d nb_re_dmrs %d length dmrs %d mcs %d Nl %d "
"nb_symb_sch %d "
"nb_rb %d Qm %d Coderate %f\n",
proc->frame_rx,
proc->nr_slot_rx,
harq_pid,
harq_process->DLround,
harq_process->decodeResult,
dlsch->dlsch_config.TBS,
dlsch->dlsch_config.TBS / 8,
G[DLSCH_id],
nb_re_dmrs,
dmrs_length,
dlsch->dlsch_config.mcs,
dlsch->Nl,
dlsch_config->number_symbols,
number_rbs,
dlsch_config->qamModOrder,
Coderate);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_DEINTERLEAVING_STATS], &segment_parameters->ts_deinterleave);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_RATE_UNMATCHING_STATS], &segment_parameters->ts_rate_unmatch);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_LDPC_DECODING_STATS], &segment_parameters->ts_ldpc_decode);
}
kpiStructure.nb_total++;
kpiStructure.blockSize = cw_info->TBS;
kpiStructure.dl_mcs = cw_info->mcs;
kpiStructure.nofRBs = number_rbs;
harq_process->decodeResult = harq_process->processedSegments == harq_process->C;
if (harq_process->decodeResult && harq_process->C > 1) {
/* check global CRC */
// we have regrouped the transport block
if (!check_crc(b, lenWithCrc(1, cw_info->TBS), crcType(1, cw_info->TBS))) {
LOG_E(PHY,
" Frame %d.%d LDPC global CRC fails, but individual LDPC CRC succeeded. %d segs\n",
proc->frame_rx,
proc->nr_slot_rx,
harq_process->C);
harq_process->decodeResult = false;
}
}
if (harq_process->decodeResult) {
// We search only a reccuring OAI error that propagates all 0 packets with a 0 CRC, so we
const int sz = cw_info->TBS / 8;
if (b[sz] == 0 && b[sz + 1] == 0) {
// do the check only if the 2 first bytes of the CRC are 0 (it can be CRC16 or CRC24)
int i = 0;
while (b[i] == 0 && i < sz)
i++;
if (i == sz) {
LOG_E(PHY,
"received all 0 pdu, consider it false reception, even if the TS 38.212 7.2.1 says only we should attach the "
"corresponding CRC, and nothing prevents to have a all 0 packet\n");
harq_process->decodeResult = false;
}
}
}
if (harq_process->decodeResult) {
LOG_D(PHY, "DLSCH received ok \n");
harq_process->status = NR_SCH_IDLE;
dlsch->last_iteration_cnt = dlsch->max_ldpc_iterations - 1;
} else {
LOG_D(PHY, "DLSCH received nok \n");
kpiStructure.nb_nack++;
dlsch->last_iteration_cnt = dlsch->max_ldpc_iterations;
UEdumpScopeData(phy_vars_ue, proc->nr_slot_rx, proc->frame_rx, "DLSCH_NACK");
}
LOG_D(PHY,
"%d.%d DLSCH Decoded, harq_pid %d, cw_idx %d round %d, result: %d TBS %d (%d) G %d nb_re_dmrs %d length dmrs %d mcs %d Nl %d "
"nb_symb_sch %d "
"nb_rb %d Qm %d Coderate %f\n",
proc->frame_rx,
proc->nr_slot_rx,
harq_pid,
cw_idx,
harq_process->DLround,
harq_process->decodeResult,
cw_info->TBS,
cw_info->TBS / 8,
G,
nb_re_dmrs,
dmrs_length,
cw_info->mcs,
cw_info->Nl,
dlsch_config->number_symbols,
number_rbs,
cw_info->qamModOrder,
Coderate);
}
@@ -18,6 +18,7 @@
#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"
@@ -852,16 +853,638 @@ static void nr_dlsch_mmse(uint32_t rx_size_symbol,
}
}
/* Joint linear MMSE detector for rank 5--8 PDSCH.
*
* The legacy fixed-point detector above constructs cofactors recursively and
* is deliberately limited to four layers. Two-codeword PDSCH still shares
* one physical MIMO channel, so detecting each four-layer codeword in
* isolation is incorrect. This path builds H^H H over all active layers for
* every data RE, inverts the Hermitian matrix with pivoted Gauss-Jordan
* elimination, and equalizes all layers together. The right half of the
* augmented matrix is retained to remove the MMSE bias before the existing
* QAM soft demappers are used.
*/
static int nr_dlsch_mmse_high_rank_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 NR_UE_DLSCH_t *dlsch,
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->dlsch_config.start_symbol;
const int s1 = dlsch->dlsch_config.number_symbols;
const int s0 = dlsch_config->start_symbol;
const int s1 = dlsch_config->number_symbols;
int k = 0;
switch (Nl) {
@@ -875,6 +1498,10 @@ static void nr_dlsch_layer_demapping(const uint8_t Nl,
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++) {
@@ -901,28 +1528,28 @@ static int nr_dlsch_llr(const NR_UE_DLSCH_t *dlsch,
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->Nl][nb_antennas_rx][rx_size_symbol],
const c16_t rxdataF_comp[dlsch->cw_info.Nl][nb_antennas_rx][rx_size_symbol],
const int llrSize,
int16_t layer_llr[dlsch->Nl][llrSize])
int16_t layer_llr[dlsch->cw_info.Nl][llrSize])
{
switch (dlsch->dlsch_config.qamModOrder) {
switch (dlsch->cw_info.qamModOrder) {
case 2 :
for (int l = 0; l < dlsch[0].Nl; l++)
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[0].Nl; l++)
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[0].Nl; l++)
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[0].Nl; l++)
for(int l=0; l < dlsch->cw_info.Nl; l++)
nr_256qam_llr(rxdataF_comp[l][0],
dl_ch_mag,
dl_ch_magb,
@@ -943,23 +1570,25 @@ static int nr_dlsch_llr(const NR_UE_DLSCH_t *dlsch,
/* Main Function */
int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t dlsch[2],
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[2],
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->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_mag[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magb[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magr[][dlsch->Nl][nbRx][rx_size_symbol],
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,
@@ -967,7 +1596,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
pdsch_scope_req_t *scope_req)
{
NR_DL_FRAME_PARMS *fp = &ue->frame_parms;
const int nl = dlsch[0].Nl;
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];
@@ -979,72 +1608,8 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
const int gNB_id = proc->gNB_id;
uint8_t slot = 0;
int32_t codeword_TB0 = -1;
int32_t codeword_TB1 = -1;
uint32_t nb_re_pdsch = -1;
NR_DL_UE_HARQ_t *dlsch0_harq, *dlsch1_harq = NULL;
dlsch0_harq = &ue->dl_harq_processes[0][harq_pid];
if (NR_MAX_NB_LAYERS>4)
dlsch1_harq = &ue->dl_harq_processes[1][harq_pid];
if (dlsch0_harq && dlsch1_harq){
LOG_D(PHY,
"AbsSubframe %d.%d / Sym %d harq_pid %d, harq status %d.%d \n",
frame,
nr_slot_rx,
symbol,
harq_pid,
dlsch0_harq->status,
dlsch1_harq->status);
if ((dlsch0_harq->status == NR_ACTIVE) && (dlsch1_harq->status == NR_ACTIVE)){
codeword_TB0 = dlsch0_harq->codeword; // SV: where is this set? revisit for DL MIMO.
codeword_TB1 = dlsch1_harq->codeword;
dlsch0_harq = &ue->dl_harq_processes[codeword_TB0][harq_pid];
dlsch1_harq = &ue->dl_harq_processes[codeword_TB1][harq_pid];
DEBUG_HARQ("[DEMOD] I am assuming both TBs are active, in cw0 %d and cw1 %d \n", codeword_TB0, codeword_TB1);
} else if ((dlsch0_harq->status == NR_ACTIVE) && (dlsch1_harq->status != NR_ACTIVE) ) {
codeword_TB0 = dlsch0_harq->codeword;
dlsch0_harq = &ue->dl_harq_processes[codeword_TB0][harq_pid];
dlsch1_harq = NULL;
DEBUG_HARQ("[DEMOD] I am assuming only TB0 is active, in cw %d \n", codeword_TB0);
} else if ((dlsch0_harq->status != NR_ACTIVE) && (dlsch1_harq->status == NR_ACTIVE)){
codeword_TB1 = dlsch1_harq->codeword;
dlsch0_harq = NULL;
dlsch1_harq = &ue->dl_harq_processes[codeword_TB1][harq_pid];
DEBUG_HARQ("[DEMOD] I am assuming only TB1 is active, it is in cw %d\n", codeword_TB1);
LOG_E(PHY, "[DEMOD] slot %d TB0 not active and TB1 active case is not supported\n", nr_slot_rx);
return -1;
} else {
LOG_E(PHY, "[DEMOD] slot %d: no active DLSCH (2 layers case)\n", nr_slot_rx);
return (-1);
}
} else if (dlsch0_harq) {
if (dlsch0_harq->status == NR_ACTIVE) {
codeword_TB0 = dlsch0_harq->codeword;
dlsch0_harq = &ue->dl_harq_processes[0][harq_pid];
DEBUG_HARQ("[DEMOD] I am assuming only TB0 is active\n");
} else {
LOG_E(PHY, "[DEMOD] slot %d nr_rx_pdsch no active DLSCH (one layer case)\n", nr_slot_rx);
return (-1);
}
} else {
LOG_E(PHY, "[DEMOD] slot %d Inconsistent call to nr_rx_pdsch (no layer 0)\n", nr_slot_rx);
return -1;
}
DEBUG_HARQ("[DEMOD] cw for TB0 = %d, cw for TB1 = %d\n", codeword_TB0, codeword_TB1);
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[0].dlsch_config;
DevAssert(dlsch0_harq);
DevAssert(dlsch_harq);
if (gNB_id > 2) {
LOG_E(PHY, "Illegal gNB_id %d\n", gNB_id);
@@ -1089,7 +1654,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
nl,
fp,
csi_res_bitmap,
ue->chest_time);
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;
@@ -1216,22 +1781,42 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
//----------------------------------------------------------
// Disable correlation measurement for optimizing UE
start_meas_nr_ue_phy(ue, DLSCH_CHANNEL_COMPENSATION_STATS);
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_config->qamModOrder,
nb_rb_pdsch,
*log2_maxh); // log2_maxh+I0_shift
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,
@@ -1265,7 +1850,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
}
start_meas_nr_ue_phy(ue, DLSCH_MRC_MMSE_STATS);
if (n_rx > 1) {
if (nl <= 4 && n_rx > 1) {
nr_dlsch_detection_mrc(rx_size_symbol,
nl,
n_rx,
@@ -1286,7 +1871,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
dl_ch_magb[symbol],
dl_ch_magr[symbol],
dl_ch_estimates_ext,
dlsch_config->qamModOrder,
dlsch->cw_info.qamModOrder,
*log2_maxh,
symbol,
nb_re_pdsch,
@@ -1312,14 +1897,14 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
int nbSymb = 0;
int pduBitmap = 0;
if(dlsch0_harq->status == NR_ACTIVE) {
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[0].rnti_type == TYPE_C_RNTI_)) {
if((pduBitmap & 0x1) && (dlsch->rnti_type == TYPE_C_RNTI_)) {
nr_pdsch_ptrs_processing(nbRx,
ptrs_phase_per_slot,
ptrs_re_per_slot,
@@ -1327,13 +1912,13 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
nl,
rxdataF_comp,
fp,
dlsch0_harq,
dlsch1_harq,
dlsch_config,
nr_slot_rx,
symbol,
freq_alloc->num_rbs,
dlsch[0].rnti,
dlsch);
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 */
@@ -1341,7 +1926,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
/* 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->dlsch_config.qamModOrder;
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
@@ -1360,14 +1945,9 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
}
stop_meas_nr_ue_phy(ue, DLSCH_LLR_STATS);
start_meas_nr_ue_phy(ue, DLSCH_LAYER_DEMAPPING);
nr_dlsch_layer_demapping(nl, dlsch_config->qamModOrder, llr_per_symbol, layer_llr, dlsch, dl_valid_re, llr[0]);
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);
/*
for (int i=0; i < 2; i++){
snprintf(filename, 50, "llr%d_symb_%d_nr_slot_rx_%d.m", i, symbol, nr_slot_rx);
write_output(filename,"llr", &llr[i][0], (NR_SYMBOLS_PER_SLOT*nb_rb_pdsch*NR_NB_SC_PER_RB*dlsch1_harq->Qm) - 4*(nb_rb_pdsch*4*dlsch1_harq->Qm), 1, 0);
}
*/
if (UEScopeHasTryLock(ue)) {
metadata mt = {.frame = proc->frame_rx, .slot = proc->nr_slot_rx };
int total_valid_res = 0;
@@ -446,7 +446,8 @@ nr_initial_sync_t nr_initial_sync(UE_nr_rxtx_proc_t *proc,
nr_fill_rx_indication(&rx_ind,
FAPI_NR_RX_PDU_TYPE_SSB,
ue,
NULL,
0,
0,
NULL,
number_pdus,
proc,
+2 -2
View File
@@ -404,7 +404,7 @@ int nr_rx_pbch(PHY_VARS_NR_UE *ue,
if (decoderState) {
if (ue) { // decoding failed in synced state
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, NULL, NULL, number_pdus, proc, NULL, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, 0, 0, NULL, number_pdus, proc, NULL, NULL);
if (ue->if_inst && ue->if_inst->dl_indication)
ue->if_inst->dl_indication(&dl_indication);
}
@@ -464,7 +464,7 @@ int nr_rx_pbch(PHY_VARS_NR_UE *ue,
if (ue) {
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, NULL, NULL, number_pdus, proc, (void *)result, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, 0, 0, NULL, number_pdus, proc, (void *)result, NULL);
if (ue->if_inst && ue->if_inst->dl_indication)
ue->if_inst->dl_indication(&dl_indication);
@@ -60,19 +60,17 @@ int get_max_pdcch_monOcc(const NR_UE_PDCCH_CONFIG *phy_pdcch_config, int nb_symb
@param[in] dlsch_llr Pointers to LLR values computed by dlsch_demodulation
@param[in] b
@param[in] G array of Gs
@param[in] nb_dlsch number of active downlink shared channels
@param[in] DLSCH_ids array of active downlink shared channels
@returns 0 on success, 1 on unsuccessful decoding
*/
void nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t *dlsch,
short **dlsch_llr,
uint8_t **b,
int *G,
int nb_dlsch,
int cw_idx,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
int16_t *dlsch_llr,
uint8_t *b,
int number_rbs,
uint8_t *DLSCH_ids);
int G);
int nr_ulsch_pre_encoding(PHY_VARS_NR_UE *ue,
const NR_UE_ULSCH_t *ulsch,
@@ -278,23 +276,25 @@ void nr_sl_rf_card_config_freq(PHY_VARS_NR_UE *ue,
*/
int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t dlsch[2],
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[2],
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->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_mag[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magb[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magr[][dlsch->Nl][nbRx][rx_size_symbol],
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,
@@ -97,16 +97,13 @@ typedef struct {
} NR_DL_UE_HARQ_t;
typedef struct {
fapi_nr_dl_cw_info_t cw_info;
/// RNTI
uint16_t rnti;
/// RNTI type
uint8_t rnti_type;
/// Active flag for DLSCH demodulation
bool active;
/// Structure to hold dlsch config from MAC
fapi_nr_dl_config_dlsch_pdu_rel15_t dlsch_config;
/// Number of MIMO layers (streams)
uint8_t Nl;
/// Maximum number of LDPC iterations
uint8_t max_ldpc_iterations;
/// number of iterations used in last turbo decoding
+4 -4
View File
@@ -106,13 +106,13 @@ typedef struct {
/// freq allocation information
freq_alloc_bitmap_t freq_alloc;
/// pointer to pdu from MAC interface (this is "a" in 36.212)
uint8_t *pdu;
uint8_t *pdu[NR_MAX_NB_CODEWORDS];
/// Pointer to the payload
uint8_t *b;
uint8_t *b[NR_MAX_NB_CODEWORDS];
/// Pointers to transport block segments
uint8_t **c;
uint8_t **c[NR_MAX_NB_CODEWORDS];
/// Interleaver outputs
uint8_t *f;
uint8_t *f[NR_MAX_NB_CODEWORDS];
/// REs unavailable for DLSCH (overlapping with PTRS, CSIRS etc.)
uint32_t unav_res;
} NR_gNB_DLSCH_t;
+6 -5
View File
@@ -302,11 +302,11 @@ typedef struct PHY_VARS_NR_UE_s {
/// \brief Total gain of the RX chain (antenna to baseband I/Q) This is a function of rx_gain_mode (and the corresponding gain) and the rx_gain of the card.
uint32_t rx_total_gain_dB;
/// \brief Total gains with maximum RF gain stage (ExpressMIMO2/Lime)
uint32_t rx_gain_max[4];
uint32_t rx_gain_max[MAX_ANT];
/// \brief Total gains with medium RF gain stage (ExpressMIMO2/Lime)
uint32_t rx_gain_med[4];
uint32_t rx_gain_med[MAX_ANT];
/// \brief Total gains with bypassed RF gain stage (ExpressMIMO2/Lime)
uint32_t rx_gain_byp[4];
uint32_t rx_gain_byp[MAX_ANT];
/// \brief Current transmit power
int16_t tx_power_dBm[NR_MAX_SLOTS_PER_FRAME];
/// \brief Total number of REs in current transmission
@@ -521,8 +521,9 @@ typedef struct nr_phy_data_tx_s {
typedef struct nr_phy_data_s {
NR_UE_PDCCH_CONFIG phy_pdcch_config;
fapi_nr_dl_config_dlsch_pdu_rel15_t dlsch_config;
NR_UE_DLSCH_t dlsch[2];
int n_dlsch_codewords;
// Sidelink Rx action decided by MAC
sl_nr_rx_config_type_enum_t sl_rx_action;
NR_UE_CSI_RS csirs_vars;
@@ -535,7 +536,7 @@ enum stream_status_e { STREAM_STATUS_UNSYNC, STREAM_STATUS_SYNCING, STREAM_STATU
*/
typedef struct nr_rxtx_thread_data_s {
UE_nr_rxtx_proc_t proc;
PHY_VARS_NR_UE *UE;
PHY_VARS_NR_UE *UE;
int writeBlockSize;
nr_phy_data_t phy_data;
dynamic_barrier_t* next_barrier;
+41 -11
View File
@@ -19,8 +19,28 @@
#include "assertions.h"
#include <pthread.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
static pthread_once_t tx_chain_telemetry_once = PTHREAD_ONCE_INIT;
static bool tx_chain_telemetry = false;
static atomic_uint tx_chain_proven_mask = ATOMIC_VAR_INIT(0);
static void configure_tx_chain_telemetry(void)
{
const char *value = getenv("OAI_TX_CHAIN_TELEMETRY");
tx_chain_telemetry = value != NULL && strcmp(value, "1") == 0;
}
static bool tx_chain_telemetry_enabled(void)
{
pthread_once(&tx_chain_telemetry_once, configure_tx_chain_telemetry);
return tx_chain_telemetry;
}
static inline int count_nnz_c16(const c16_t *v, int n)
{
int nnz = 0;
@@ -241,18 +261,28 @@ void nr_feptx(void *arg)
AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n");
}
/* 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)); */
// One-shot proof that an 8TX run exercises every physical frequency-domain
// chain. Check each generated symbol chunk until its chain first carries
// energy; normal runs leave this disabled to avoid counting overhead.
if (tx_chain_telemetry_enabled()) {
const int sample_count = numSymbols * fp->ofdm_symbol_size;
const int sample_offset = startSymbol * fp->ofdm_symbol_size;
const c16_t *dst = (const c16_t *)&ru->common.txdataF_BF[tx_idx][sample_offset];
const int nnz = count_nnz_c16(dst, sample_count);
if (nnz > 0) {
const unsigned bit = 1u << aa;
const unsigned seen = atomic_fetch_or_explicit(
&tx_chain_proven_mask, bit, memory_order_relaxed);
if ((seen & bit) == 0)
LOG_I(PHY,
"[TX_CHAIN_PROOF] frame=%d slot=%d ant=%d active=1 nnz=%d\n",
ru->proc.frame_tx,
slot,
aa,
nnz);
}
}
/* LOG_I(PHY, */
/* "[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); */
/* } */
if (tx_idx == 0)
stop_meas(&ru->precoding_stats);
+10 -1
View File
@@ -270,7 +270,16 @@ void phy_procedures_gNB_TX(PHY_VARS_gNB *gNB,
// reuse dlsch variables, as there are multiple very large memory
// buffers
gNB->dlsch[num_pdsch].pdsch_pdu = &dl_tti_pdu->pdsch_pdu;
gNB->dlsch[num_pdsch].pdu = (uint8_t *)TX_req->pdu_list[tx_data_idx].TLVs[0].value.direct;
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *pdsch = &dl_tti_pdu->pdsch_pdu.pdsch_pdu_rel15;
const nfapi_nr_pdu_t *tx_pdu = &TX_req->pdu_list[tx_data_idx];
AssertFatal(tx_pdu->num_TLV == pdsch->NrOfCodewords,
"%4d.%2d PDSCH has %u codewords but TX request has %u TLVs\n",
frame,
slot,
pdsch->NrOfCodewords,
tx_pdu->num_TLV);
for (int q = 0; q < NR_MAX_NB_CODEWORDS; q++)
gNB->dlsch[num_pdsch].pdu[q] = q < tx_pdu->num_TLV ? (uint8_t *)tx_pdu->TLVs[q].value.direct : NULL;
DevAssert(num_pdsch < gNB->max_nb_pdsch);
num_pdsch++;
} else {
+3 -2
View File
@@ -102,8 +102,9 @@ void nr_fill_dl_indication(nr_downlink_indication_t *dl_ind,
void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
uint8_t pdu_type,
PHY_VARS_NR_UE *ue,
NR_UE_DLSCH_t *dlsch0,
NR_UE_DLSCH_t *dlsch1,
int cw_idx,
int harq_pid,
NR_UE_DLSCH_t *dlsch,
uint16_t n_pdus,
const UE_nr_rxtx_proc_t *proc,
void *typeSpecific,
+35 -34
View File
@@ -21,48 +21,44 @@ extern PHY_VARS_NR_UE ***PHY_vars_UE_g;
const char *const dl_pdu_type[] = {"DCI", "DLSCH", "RA_DLSCH", "SI_DLSCH", "P_DLSCH", "CSI_RS", "CSI_IM", "TA"};
const char *const ul_pdu_type[] = {"PRACH", "PUCCH", "PUSCH", "SRS"};
static void configure_dlsch(NR_UE_DLSCH_t *dlsch0,
static void configure_dlsch(NR_UE_DLSCH_t *dlsch,
NR_DL_UE_HARQ_t *harq_list,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config_pdu,
NR_UE_MAC_INST_t *mac,
int cw_idx,
int rnti)
{
const uint8_t current_harq_pid = dlsch_config_pdu->harq_process_nbr;
dlsch0->active = true;
dlsch0->rnti = rnti;
dlsch->active = true;
dlsch->rnti = rnti;
LOG_D(PHY,"current_harq_pid = %d\n", current_harq_pid);
LOG_D(PHY,"Configure DLSCH for pid = %d\n", current_harq_pid);
NR_DL_UE_HARQ_t *dlsch0_harq = &harq_list[current_harq_pid];
NR_DL_UE_HARQ_t *dlsch_harq = &harq_list[current_harq_pid];
dlsch->cw_info = dlsch_config_pdu->cw_info[cw_idx];
//get nrOfLayers from DCI info
uint8_t Nl = 0;
for (int i = 0; i < 12; i++) { // max 12 ports
if ((dlsch_config_pdu->dmrs_ports >> i) & 0x01)
Nl += 1;
}
if (Nl == 0) {
LOG_E(PHY, "Invalid number of layers %d for DLSCH\n", Nl);
if (dlsch->cw_info.Nl == 0) {
LOG_E(PHY, "Invalid number of layers %d for DLSCH\n", dlsch->cw_info.Nl);
// setting NACK for this TB
dlsch0->active = false;
update_harq_status(mac, current_harq_pid, 0);
dlsch->active = false;
update_harq_status(mac, current_harq_pid, cw_idx, 0);
return;
}
dlsch0->Nl = Nl;
if (dlsch_config_pdu->new_data_indicator) {
dlsch0_harq->first_rx = true;
dlsch0_harq->DLround = 0;
if (dlsch->cw_info.new_data_indicator) {
dlsch_harq->first_rx = true;
dlsch_harq->DLround = 0;
} else {
dlsch0_harq->first_rx = false;
dlsch0_harq->DLround++;
dlsch_harq->first_rx = false;
dlsch_harq->DLround++;
}
downlink_harq_process(dlsch0_harq, current_harq_pid, dlsch_config_pdu->new_data_indicator, dlsch_config_pdu->rv, dlsch0->rnti_type);
if (dlsch0_harq->status != NR_ACTIVE) {
// dlsch0_harq->status not ACTIVE due to false retransmission
downlink_harq_process(dlsch_harq, current_harq_pid, dlsch->cw_info.new_data_indicator, dlsch->cw_info.rv, dlsch->rnti_type);
if (dlsch_harq->status != NR_ACTIVE) {
// dlsch_harq->status not ACTIVE due to false retransmission
// Reset the following flag to skip PDSCH procedures in that case and retrasmit harq status
dlsch0->active = false;
dlsch->active = false;
LOG_W(NR_MAC, "dlsch0_harq->status not ACTIVE due to false retransmission harq pid: %d\n", current_harq_pid);
update_harq_status(mac, current_harq_pid, dlsch0_harq->decodeResult);
update_harq_status(mac, current_harq_pid, cw_idx, dlsch_harq->decodeResult);
}
}
@@ -161,23 +157,28 @@ static void nr_ue_scheduled_response_dl(NR_UE_MAC_INST_t *mac,
case FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH: {
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config_pdu = &pdu->dlsch_config_pdu.dlsch_config_rel15;
NR_UE_DLSCH_t *dlsch0 = phy_data->dlsch + 0;
phy_data->n_dlsch_codewords = dlsch_config_pdu->n_codewords;
phy_data->dlsch_config = *dlsch_config_pdu;
dlsch0->rnti_type = TYPE_RA_RNTI_;
dlsch0->dlsch_config = *dlsch_config_pdu;
configure_dlsch(dlsch0, phy->dl_harq_processes[0], dlsch_config_pdu, mac, pdu->dlsch_config_pdu.rnti);
configure_dlsch(dlsch0, phy->dl_harq_processes[0], dlsch_config_pdu, mac, 0, pdu->dlsch_config_pdu.rnti);
} break;
case FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH: {
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config_pdu = &pdu->dlsch_config_pdu.dlsch_config_rel15;
NR_UE_DLSCH_t *dlsch0 = phy_data->dlsch + 0;
phy_data->n_dlsch_codewords = dlsch_config_pdu->n_codewords;
phy_data->dlsch_config = *dlsch_config_pdu;
dlsch0->rnti_type = TYPE_SI_RNTI_;
dlsch0->dlsch_config = *dlsch_config_pdu;
configure_dlsch(dlsch0, phy->dl_harq_processes[0], dlsch_config_pdu, mac, pdu->dlsch_config_pdu.rnti);
configure_dlsch(dlsch0, phy->dl_harq_processes[0], dlsch_config_pdu, mac, 0, pdu->dlsch_config_pdu.rnti);
} break;
case FAPI_NR_DL_CONFIG_TYPE_DLSCH: {
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config_pdu = &pdu->dlsch_config_pdu.dlsch_config_rel15;
NR_UE_DLSCH_t *dlsch0 = &phy_data->dlsch[0];
dlsch0->rnti_type = TYPE_C_RNTI_;
dlsch0->dlsch_config = *dlsch_config_pdu;
configure_dlsch(dlsch0, phy->dl_harq_processes[0], dlsch_config_pdu, mac, pdu->dlsch_config_pdu.rnti);
phy_data->n_dlsch_codewords = dlsch_config_pdu->n_codewords;
phy_data->dlsch_config = *dlsch_config_pdu;
for (int c = 0; c < dlsch_config_pdu->n_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
dlsch->rnti_type = TYPE_C_RNTI_;
configure_dlsch(dlsch, phy->dl_harq_processes[c], dlsch_config_pdu, mac, c, pdu->dlsch_config_pdu.rnti);
}
} break;
case FAPI_NR_CONFIG_TA_COMMAND:
configure_ta_command(phy, &pdu->ta_command_pdu);
+1 -1
View File
@@ -29,6 +29,6 @@ void nr_ue_sl_phy_config_request(nr_sl_phy_config_t *phy_config);
\param synch_request including target_Nid_cell*/
void nr_ue_synch_request(nr_synch_request_t *synch_request);
void update_harq_status(NR_UE_MAC_INST_t *mac, uint8_t harq_pid, uint8_t ack_nack);
void update_harq_status(NR_UE_MAC_INST_t *mac, uint8_t harq_pid, int cw_idx, uint8_t ack_nack);
#endif
+304 -188
View File
@@ -55,6 +55,44 @@ static const unsigned int gain_table[31] = {100, 112, 126, 141, 158, 178,
359, 398, 447, 501, 562, 631, 708, 794, 891, 1000, 1122,
1258, 1412, 1585, 1778, 1995, 2239, 2512, 2818, 3162};
typedef struct {
fourDimArray_t *array;
size_t capacity;
} nr_ue_reusable_array_t;
/* Return a correctly-strided view without shrinking an existing allocation.
* Rank and RB allocation vary from one PDSCH to the next; exact-dimension
* resize logic otherwise oscillates between small control grants and large
* rank-8 data grants, reintroducing allocation/zeroing on every slot. */
static fourDimArray_t *nr_ue_reuse_array(nr_ue_reusable_array_t *storage,
size_t element_size,
int dim1,
int dim2,
int dim3,
int dim4)
{
DevAssert(dim1 > 0);
size_t elements = dim1;
if (dim2 > 0)
elements *= dim2;
if (dim3 > 0)
elements *= dim3;
if (dim4 > 0)
elements *= dim4;
const size_t required = sizeof(fourDimArray_t) + elements * element_size;
if (required > storage->capacity) {
free(storage->array);
storage->array = malloc16_clear(required);
AssertFatal(storage->array != NULL, "Could not allocate %zu-byte UE PDSCH workspace\n", required);
storage->capacity = required;
}
storage->array->dim1 = dim1;
storage->array->dim2 = dim2;
storage->array->dim3 = dim3;
storage->array->dim4 = dim4;
return storage->array;
}
static void nr_ue_prach_procedures(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, c16_t **txData);
void nr_fill_dl_indication(nr_downlink_indication_t *dl_ind,
@@ -94,31 +132,35 @@ static uint32_t get_ssb_arfcn(NR_DL_FRAME_PARMS *frame_parms)
uint64_t ssb_freq = frame_parms->dl_CarrierFreq - (band_size_hz / 2) + frame_parms->subcarrier_spacing * ssb_center_sc;
return to_nrarfcn(ssb_freq);
}
void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
uint8_t pdu_type,
PHY_VARS_NR_UE *ue,
NR_UE_DLSCH_t *dlsch0,
NR_UE_DLSCH_t *dlsch1,
int cw_idx,
int harq_pid,
NR_UE_DLSCH_t *dlsch,
uint16_t n_pdus,
const UE_nr_rxtx_proc_t *proc,
void *typeSpecific,
uint8_t *b)
{
if (n_pdus > 1){
LOG_E(PHY, "In %s: multiple number of DL PDUs not supported yet...\n", __FUNCTION__);
if (n_pdus > 1) {
LOG_E(PHY, "Multiple number of DL PDUs not supported yet...\n");
return;
}
fapi_nr_rx_indication_body_t *rx = rx_ind->rx_indication_body + n_pdus - 1;
switch (pdu_type){
case FAPI_NR_RX_PDU_TYPE_SIB:
case FAPI_NR_RX_PDU_TYPE_RAR:
case FAPI_NR_RX_PDU_TYPE_DLSCH:
if(dlsch0) {
NR_DL_UE_HARQ_t *dl_harq0 = &ue->dl_harq_processes[0][dlsch0->dlsch_config.harq_process_nbr];
rx->pdsch_pdu.harq_pid = dlsch0->dlsch_config.harq_process_nbr;
rx->pdsch_pdu.ack_nack = dl_harq0->decodeResult;
if(dlsch) {
NR_DL_UE_HARQ_t *dl_harq = &ue->dl_harq_processes[cw_idx][harq_pid];
rx->pdsch_pdu.harq_pid = harq_pid;
rx->pdsch_pdu.cw_idx = cw_idx;
rx->pdsch_pdu.ack_nack = dl_harq->decodeResult;
rx->pdsch_pdu.pdu = b;
rx->pdsch_pdu.pdu_length = dlsch0->dlsch_config.TBS / 8;
if (dl_harq0->decodeResult) {
rx->pdsch_pdu.pdu_length = dlsch->cw_info.TBS / 8;
if (dl_harq->decodeResult) {
int t = WS_C_RNTI;
if (pdu_type == FAPI_NR_RX_PDU_TYPE_RAR)
t = WS_RA_RNTI;
@@ -130,16 +172,13 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
.pdu_buffer_size = rx->pdsch_pdu.pdu_length,
.ueid = 0,
.rntiType = t,
.rnti = dlsch0->rnti,
.rnti = dlsch->rnti,
.sysFrame = proc->frame_rx,
.subframe = proc->nr_slot_rx,
.harq_pid = dlsch0->dlsch_config.harq_process_nbr};
.harq_pid = harq_pid};
trace_pdu(&tmp);
}
}
if(dlsch1) {
AssertFatal(1==0,"Second codeword currently not supported\n");
}
break;
case FAPI_NR_RX_PDU_TYPE_SSB: {
if (typeSpecific) {
@@ -168,7 +207,6 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
rx->pdu_type = pdu_type;
rx_ind->number_pdus = n_pdus;
}
int get_tx_amp_prach(int power_dBm, int power_max_dBm, int N_RB_UL){
@@ -488,8 +526,10 @@ static int nr_ue_pbch_procedures(PHY_VARS_NR_UE *ue,
static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t dlsch[2],
int16_t *llr[2],
NR_UE_DLSCH_t *dlsch,
NR_DL_UE_HARQ_t *dlsch_harq,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlschCfg,
int16_t *llr,
c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP],
int G,
freq_alloc_bitmap_t *freq_alloc)
@@ -497,16 +537,35 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
int frame_rx = proc->frame_rx;
int nr_slot_rx = proc->nr_slot_rx;
// We handle only one CW now
if (NR_MAX_NB_LAYERS > 4) {
LOG_E(NR_PHY, "Handling of more than one CW not implemented\n");
if (dlsch->cw_info.Nl < 1 || dlsch->cw_info.Nl > NR_MAX_NB_LAYERS) {
LOG_E(NR_PHY, "Invalid PDSCH layer count %u\n", dlsch->cw_info.Nl);
return -1;
}
NR_UE_DLSCH_t *dlsch0 = &dlsch[0];
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlschCfg = &dlsch0->dlsch_config;
if (dlsch->cw_info.Nl > ue->frame_parms.nb_antennas_rx) {
LOG_E(NR_PHY,
"Cannot detect rank-%u PDSCH with only %u UE RX antennas\n",
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx);
return -1;
}
if (dlsch->cw_info.Nl > 4 && (dlschCfg->pduBitmap & 0x1)) {
LOG_E(NR_PHY, "PTRS is not supported by the rank-5--8 joint PDSCH detector\n");
return -1;
}
int harq_pid = dlschCfg->harq_process_nbr;
// dlsch0_harq contains the previous transmissions data for this harq pid
NR_DL_UE_HARQ_t *dlsch0_harq = &ue->dl_harq_processes[0][harq_pid];
/* These workspaces are several tens of megabytes at rank 8. Allocating and
* zeroing them for every PDSCH made the UE miss its HARQ-feedback slot even
* when the cheap permutation-diagonal detector was selected. Each DL actor
* owns one persistent thread, so thread-local storage is race-free and
* preserves the legacy zero-initialized first use. Every active RE is
* overwritten by channel estimation/equalization before it is consumed. */
static _Thread_local nr_ue_reusable_array_t pdsch_dl_ch_estimates_storage;
static _Thread_local nr_ue_reusable_array_t rxdataF_comp_storage;
static _Thread_local nr_ue_reusable_array_t dl_ch_mag_storage;
static _Thread_local nr_ue_reusable_array_t dl_ch_magb_storage;
static _Thread_local nr_ue_reusable_array_t dl_ch_magr_storage;
LOG_D(PHY,
"[UE %d] frame_rx %d, nr_slot_rx %d, harq_pid %d (%d), BWP start %d, start RB %d, end RB %d, symbol_start %d, nb_symbols "
@@ -516,18 +575,24 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
frame_rx,
nr_slot_rx,
harq_pid,
dlsch0_harq->status,
dlsch_harq->status,
dlschCfg->BWPStart,
freq_alloc->first_rb,
freq_alloc->last_rb,
dlschCfg->start_symbol,
dlschCfg->number_symbols,
dlschCfg->dlDmrsSymbPos,
dlsch0->Nl);
dlsch->cw_info.Nl);
const uint32_t pdsch_est_size = ((ue->frame_parms.symbols_per_slot * ue->frame_parms.ofdm_symbol_size + 15) / 16) * 16;
fourDimArray_t *toFree = NULL;
allocCast2D(pdsch_dl_ch_estimates, int32_t, toFree, ue->frame_parms.nb_antennas_rx * dlsch0->Nl, pdsch_est_size, false);
fourDimArray_t *pdsch_dl_ch_estimates_view =
nr_ue_reuse_array(&pdsch_dl_ch_estimates_storage,
sizeof(int32_t),
ue->frame_parms.nb_antennas_rx * dlsch->cw_info.Nl,
pdsch_est_size,
0,
0);
cast2Darray(pdsch_dl_ch_estimates, int32_t, pdsch_dl_ch_estimates_view);
c16_t ptrs_phase_per_slot[ue->frame_parms.nb_antennas_rx][NR_SYMBOLS_PER_SLOT];
memset(ptrs_phase_per_slot, 0, sizeof(ptrs_phase_per_slot));
@@ -536,22 +601,20 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
memset(ptrs_re_per_slot, 0, sizeof(ptrs_re_per_slot));
const uint32_t rx_size_symbol = (freq_alloc->num_rbs * NR_NB_SC_PER_RB + 15) & ~15;
fourDimArray_t *toFree2 = NULL;
allocCast4D(rxdataF_comp,
c16_t,
toFree2,
ue->frame_parms.symbols_per_slot,
dlsch[0].Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol,
false);
fourDimArray_t *rxdataF_comp_view = nr_ue_reuse_array(&rxdataF_comp_storage,
sizeof(c16_t),
ue->frame_parms.symbols_per_slot,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(rxdataF_comp, c16_t, rxdataF_comp_view);
uint32_t nvar = 0;
start_meas_nr_ue_phy(ue, DLSCH_CHANNEL_ESTIMATION_STATS);
for (int m = dlschCfg->start_symbol; m < (dlschCfg->start_symbol + dlschCfg->number_symbols); m++) {
if (dlschCfg->dlDmrsSymbPos & (1 << m)) {
for (int nl = 0; nl < dlsch0->Nl; nl++) { // for MIMO Config: it shall loop over no_layers
for (int nl = 0; nl < dlsch->cw_info.Nl; nl++) { // for MIMO Config: it shall loop over no_layers
LOG_D(PHY, "PDSCH Channel estimation layer %d, slot %d, symbol %d\n", nl, nr_slot_rx, m);
uint32_t nvar_tmp = 0;
nr_pdsch_channel_estimation(ue,
@@ -580,16 +643,26 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
}
}
stop_meas_nr_ue_phy(ue, DLSCH_CHANNEL_ESTIMATION_STATS);
nvar /= (dlschCfg->number_symbols * dlsch0->Nl * ue->frame_parms.nb_antennas_rx);
nvar /= (dlschCfg->number_symbols * dlsch->cw_info.Nl * ue->frame_parms.nb_antennas_rx);
nr_ue_measurement_procedures(2, ue, proc, freq_alloc->num_rbs, pdsch_est_size, pdsch_dl_ch_estimates);
if (ue->chest_time == 1) { // averaging time domain channel estimates
nr_chest_time_domain_avg(&ue->frame_parms,
(int32_t **)pdsch_dl_ch_estimates,
dlschCfg->number_symbols,
dlschCfg->start_symbol,
dlschCfg->dlDmrsSymbPos,
freq_alloc->num_rbs);
if (ue->chest_time == 1 || dlsch->cw_info.Nl > 4) {
/* Rank 5--8 uses maxLength=len2 DMRS. Ports 1004--1007 share
* frequency-domain OCC with ports 1000--1003 and are separated by the
* time-domain OCC across the two front-loaded symbols, so averaging is
* mandatory. Apply it to every layer, not only the first RX block. */
for (int layer = 0; layer < dlsch->cw_info.Nl; layer++) {
int32_t *layer_ch_estimates[ue->frame_parms.nb_antennas_rx];
for (int aarx = 0; aarx < ue->frame_parms.nb_antennas_rx; aarx++)
layer_ch_estimates[aarx] = pdsch_dl_ch_estimates[layer * ue->frame_parms.nb_antennas_rx + aarx];
nr_chest_time_domain_avg(&ue->frame_parms,
layer_ch_estimates,
dlschCfg->number_symbols,
dlschCfg->start_symbol,
dlschCfg->dlDmrsSymbPos,
freq_alloc->num_rbs);
}
}
uint16_t first_symbol_with_data = dlschCfg->start_symbol;
@@ -625,33 +698,28 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
freq_alloc->num_rbs * NR_NB_SC_PER_RB * dlschCfg->number_symbols,
&mt);
}
fourDimArray_t *toFree3 = NULL;
allocCast4D(dl_ch_mag,
c16_t,
toFree3,
NR_SYMBOLS_PER_SLOT,
dlsch[0].Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol,
false);
fourDimArray_t *toFree4 = NULL;
allocCast4D(dl_ch_magb,
c16_t,
toFree4,
NR_SYMBOLS_PER_SLOT,
dlsch[0].Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol,
false);
fourDimArray_t *toFree5 = NULL;
allocCast4D(dl_ch_magr,
c16_t,
toFree5,
NR_SYMBOLS_PER_SLOT,
dlsch[0].Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol,
false);
fourDimArray_t *dl_ch_mag_view = nr_ue_reuse_array(&dl_ch_mag_storage,
sizeof(c16_t),
NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(dl_ch_mag, c16_t, dl_ch_mag_view);
fourDimArray_t *dl_ch_magb_view = nr_ue_reuse_array(&dl_ch_magb_storage,
sizeof(c16_t),
NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(dl_ch_magb, c16_t, dl_ch_magb_view);
fourDimArray_t *dl_ch_magr_view = nr_ue_reuse_array(&dl_ch_magr_storage,
sizeof(c16_t),
NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(dl_ch_magr, c16_t, dl_ch_magr_view);
int pdsch_status = 0;
for (int m = dlschCfg->start_symbol; m < (dlschCfg->number_symbols + dlschCfg->start_symbol); m++) {
bool first_symbol_flag = false;
if (m == first_symbol_with_data)
@@ -663,6 +731,8 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
proc,
dlsch,
freq_alloc,
dlschCfg,
dlsch_harq,
m,
first_symbol_flag,
harq_pid,
@@ -684,13 +754,8 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
nvar,
&scope_req)
< 0) {
if (scope_req.copy_chanest_to_scope) {
UEunlockScopeData(ue, pdschChanEstimates);
}
if (scope_req.copy_rxdataF_to_scope) {
UEunlockScopeData(ue, pdschRxdataF);
}
return -1;
pdsch_status = -1;
break;
}
} // CRNTI active
stop_meas_nr_ue_phy(ue, RX_PDSCH_STATS);
@@ -700,12 +765,7 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
if (scope_req.copy_rxdataF_to_scope) {
UEunlockScopeData(ue, pdschRxdataF);
}
free(toFree);
free(toFree2);
free(toFree3);
free(toFree4);
free(toFree5);
return 0;
return pdsch_status;
}
static uint32_t compute_csi_rm_unav_res(fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config)
@@ -760,90 +820,63 @@ static uint32_t compute_csi_rm_unav_res(fapi_nr_dl_config_dlsch_pdu_rel15_t *dls
*/
static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t dlsch[2],
int16_t *llr[2],
int G[2],
freq_alloc_bitmap_t *freq_alloc)
NR_UE_DLSCH_t *dlsch,
int cw_idx,
int G,
freq_alloc_bitmap_t *freq_alloc,
fapi_nr_dl_config_dlsch_pdu_rel15_t *config,
int16_t *llr)
{
if (dlsch[0].active == false) {
if (dlsch->active == false) {
LOG_E(PHY, "DLSCH should be active when calling this function\n");
return;
}
int harq_pid = dlsch[0].dlsch_config.harq_process_nbr;
int harq_pid = config->harq_process_nbr;
int frame_rx = proc->frame_rx;
int nr_slot_rx = proc->nr_slot_rx;
NR_DL_UE_HARQ_t *dl_harq0 = &ue->dl_harq_processes[0][harq_pid];
NR_DL_UE_HARQ_t *dl_harq1 = &ue->dl_harq_processes[1][harq_pid];
nr_downlink_indication_t dl_indication;
fapi_nr_rx_indication_t rx_ind = {0};
uint16_t number_pdus = 1;
uint8_t is_cw0_active = dl_harq0->status;
uint8_t is_cw1_active = dl_harq1->status;
int nb_dlsch = 0;
nb_dlsch += (is_cw0_active == NR_ACTIVE) ? 1 : 0;
nb_dlsch += (is_cw1_active == NR_ACTIVE) ? 1 : 0;
LOG_D(PHY, "AbsSubframe %d.%d Start LDPC Decoder for CW0 [harq_pid %d] ? %d \n", frame_rx % 1024, nr_slot_rx, harq_pid, is_cw0_active);
LOG_D(PHY, "AbsSubframe %d.%d Start LDPC Decoder for CW1 [harq_pid %d] ? %d \n", frame_rx % 1024, nr_slot_rx, harq_pid, is_cw1_active);
LOG_D(PHY, "AbsSubframe %d.%d Start LDPC Decoder for CW%d [harq_pid %d]\n", frame_rx % 1024, nr_slot_rx, cw_idx, harq_pid);
// exit dlsch procedures as there are no active dlsch
if (is_cw0_active != ACTIVE && is_cw1_active != NR_ACTIVE) {
// don't wait anymore
NR_DL_UE_HARQ_t *dl_harq = &ue->dl_harq_processes[cw_idx][harq_pid];
if (dl_harq->status != NR_ACTIVE) {
// The slot-level caller releases the RX/TX barrier after all codewords.
LOG_E(NR_PHY, "Internal error nr_ue_dlsch_procedure() called but no active cw on slot %d, harq %d\n", nr_slot_rx, harq_pid);
if (dlsch[0].dlsch_config.k1_feedback) {
const int ack_nack_slot_and_frame =
(proc->nr_slot_rx + dlsch[0].dlsch_config.k1_feedback) + proc->frame_rx * ue->frame_parms.slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
}
return;
}
uint8_t DLSCH_ids[nb_dlsch];
int pdsch_id = 0;
uint8_t *p_b[2] = {NULL};
for (uint8_t DLSCH_id = 0; DLSCH_id < 2; DLSCH_id++) {
NR_DL_UE_HARQ_t *dl_harq = &ue->dl_harq_processes[DLSCH_id][harq_pid];
if (dl_harq->status != NR_ACTIVE)
continue;
DLSCH_ids[pdsch_id++] = DLSCH_id;
start_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
nr_dlsch_unscrambling(llr[DLSCH_id], G[DLSCH_id], 0, dlsch[DLSCH_id].dlsch_config.dlDataScramblingId, dlsch[DLSCH_id].rnti);
stop_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
p_b[DLSCH_id] = dl_harq->b;
}
start_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
nr_dlsch_unscrambling(llr, G, cw_idx, config->dlDataScramblingId, dlsch->rnti);
stop_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
start_meas_nr_ue_phy(ue, DLSCH_DECODING_STATS);
nr_dlsch_decoding(ue, proc, dlsch, llr, p_b, G, nb_dlsch, freq_alloc->num_rbs, DLSCH_ids);
nr_dlsch_decoding(ue, proc, dlsch, cw_idx, config, llr, dl_harq->b, freq_alloc->num_rbs, G);
stop_meas_nr_ue_phy(ue, DLSCH_DECODING_STATS);
int ind_type = -1;
switch (dlsch[0].rnti_type) {
switch (dlsch->rnti_type) {
case TYPE_RA_RNTI_:
ind_type = FAPI_NR_RX_PDU_TYPE_RAR;
break;
case TYPE_SI_RNTI_:
ind_type = FAPI_NR_RX_PDU_TYPE_SIB;
break;
case TYPE_C_RNTI_:
ind_type = FAPI_NR_RX_PDU_TYPE_DLSCH;
break;
default:
AssertFatal(true, "Invalid DLSCH type %d\n", dlsch[0].rnti_type);
AssertFatal(false, "Invalid DLSCH type %d\n", dlsch->rnti_type);
break;
}
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, ind_type, ue, &dlsch[0], NULL, number_pdus, proc, NULL, p_b[0]);
nr_fill_rx_indication(&rx_ind, ind_type, ue, cw_idx, harq_pid, dlsch, number_pdus, proc, NULL, dl_harq->b);
LOG_D(PHY, "DL PDU length in bits: %d, in bytes: %d \n", dlsch[0].dlsch_config.TBS, dlsch[0].dlsch_config.TBS / 8);
LOG_D(PHY, "DL PDU length in bits: %d, in bytes: %d \n", dlsch->cw_info.TBS, dlsch->cw_info.TBS / 8);
if (cpumeas(CPUMEAS_GETSTATE)) {
LOG_D(PHY,
" --> Unscrambling %5.3f\n",
@@ -860,13 +893,6 @@ static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
ue->if_inst->dl_indication(&dl_indication);
}
// DLSCH decoding finished! don't wait anymore in Tx process, we know if we should answer ACK/NACK PUCCH
if ((dlsch[0].rnti_type == TYPE_C_RNTI_ || dlsch[0].rnti_type == TYPE_RA_RNTI_) && dlsch[0].dlsch_config.k1_feedback) {
const int ack_nack_slot_and_frame =
(proc->nr_slot_rx + dlsch[0].dlsch_config.k1_feedback) + proc->frame_rx * ue->frame_parms.slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
}
int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * NR_MAX_NB_LAYERS; // number of segments to be allocated
if (freq_alloc->num_rbs != 273) {
a_segments = a_segments * freq_alloc->num_rbs;
@@ -874,11 +900,8 @@ static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
}
uint32_t dlsch_bytes = a_segments * 1056; // allocated bytes per segment
if (ue->phy_sim_dlsch_b && is_cw0_active == NR_ACTIVE)
memcpy(ue->phy_sim_dlsch_b, p_b[0], dlsch_bytes);
else if (ue->phy_sim_dlsch_b && is_cw1_active == NR_ACTIVE)
memcpy(ue->phy_sim_dlsch_b, p_b[1], dlsch_bytes);
if (ue->phy_sim_dlsch_b)
memcpy(ue->phy_sim_dlsch_b + cw_idx * dlsch_bytes, dl_harq->b, dlsch_bytes);
}
static bool check_meas_to_perform(PHY_VARS_NR_UE *ue, int nr_slot_rx)
@@ -1150,7 +1173,6 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
int nr_slot_rx = proc->nr_slot_rx;
int gNB_id = proc->gNB_id;
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[0];
// do procedures for C-RNTI
bool slot_fep_map[14] = {0};
@@ -1182,9 +1204,16 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
nr_ue_csi_rs_procedures(ue, proc, rxdataF, &phy_data->csirs_vars.csirs_config_pdu);
}
int G[2] = {0};
if (dlsch[0].active) {
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[0].dlsch_config;
int16_t *llr[NR_MAX_NB_CODEWORDS] = {0};
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &phy_data->dlsch_config;
AssertFatal(phy_data->n_dlsch_codewords >= 0 && phy_data->n_dlsch_codewords <= NR_MAX_NB_CODEWORDS,
"Invalid number of PDSCH codewords %d\n",
phy_data->n_dlsch_codewords);
bool has_active_dlsch = false;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++)
has_active_dlsch |= phy_data->dlsch[c].active;
if (has_active_dlsch) {
uint16_t nb_symb_sch = dlsch_config->number_symbols;
uint16_t start_symb_sch = dlsch_config->start_symbol;
@@ -1219,55 +1248,142 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
unav_res = n_ptrs * ptrsSymbPerSlot;
}
unav_res += compute_csi_rm_unav_res(dlsch_config);
G[0] = nr_get_G(freq_alloc.num_rbs,
dlsch_config->number_symbols,
nb_re_dmrs,
dmrs_len,
unav_res,
dlsch_config->qamModOrder,
dlsch[0].Nl);
const uint32_t rx_llr_buf_sz = ALIGNARRAYSIZE(G[0], 32); // each LLR is 2 bytes hence 64 byte aligned
const uint32_t nb_codewords = NR_MAX_NB_LAYERS > 4 ? 2 : 1;
int16_t* llr[2];
for (int i = 0; i < nb_codewords; i++)
llr[i] = (int16_t *)malloc16_clear(rx_llr_buf_sz * sizeof(int16_t));
// it returns -1 in case of internal failure, or 0 in case of normal result
int ret_pdsch = nr_ue_pdsch_procedures(ue, proc, dlsch, llr, rxdataF, G[0], &freq_alloc);
TracyCPlot("pdsch mcs", dlsch->dlsch_config.mcs);
UEscopeCopy(ue, pdschLlr, llr[0], sizeof(int16_t), 1, G[0], 0);
LOG_D(PHY, "DLSCH data reception at nr_slot_rx: %d\n", nr_slot_rx);
start_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
if (ret_pdsch >= 0) {
nr_ue_dlsch_procedures(ue, proc, dlsch, llr, G, &freq_alloc);
int G[NR_MAX_NB_CODEWORDS] = {0};
uint32_t rx_llr_buf_sz[NR_MAX_NB_CODEWORDS] = {0};
static _Thread_local nr_ue_reusable_array_t llr_storage[NR_MAX_NB_CODEWORDS];
static _Thread_local nr_ue_reusable_array_t joint_llr_storage;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
G[c] = nr_get_G(freq_alloc.num_rbs,
dlsch_config->number_symbols,
nb_re_dmrs,
dmrs_len,
unav_res,
dlsch->cw_info.qamModOrder,
dlsch->cw_info.Nl);
rx_llr_buf_sz[c] = ALIGNARRAYSIZE(G[c], 32); // each LLR is 2 bytes hence 64 byte aligned
fourDimArray_t *llr_view =
nr_ue_reuse_array(&llr_storage[c], sizeof(int16_t), rx_llr_buf_sz[c], 0, 0, 0);
llr[c] = (int16_t *)llr_view->data;
}
else {
LOG_E(NR_PHY, "Demodulation impossible, internal error\n");
if (dlsch_config->k1_feedback) {
const int ack_nack_slot_and_frame =
proc->nr_slot_rx + dlsch_config->k1_feedback + proc->frame_rx * ue->frame_parms.slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
int ret_pdsch = 0;
if (phy_data->n_dlsch_codewords == 2) {
NR_UE_DLSCH_t *cw0 = &phy_data->dlsch[0];
NR_UE_DLSCH_t *cw1 = &phy_data->dlsch[1];
const int total_layers = cw0->cw_info.Nl + cw1->cw_info.Nl;
if (total_layers <= 4 || total_layers > NR_MAX_NB_LAYERS) {
LOG_E(NR_PHY, "Two-codeword PDSCH has invalid aggregate rank %d\n", total_layers);
ret_pdsch = -1;
} else if (cw0->cw_info.qamModOrder != cw1->cw_info.qamModOrder) {
/* The joint detector is modulation-independent, but the current slot
* LLR layout uses one modulation order. The local gNB scheduler sends
* the same MCS on both TBs; reject unsupported external FAPI input
* explicitly rather than silently corrupting CW1. */
LOG_E(NR_PHY,
"Rank-%d PDSCH with different modulation orders per codeword (%u/%u) is not supported\n",
total_layers,
cw0->cw_info.qamModOrder,
cw1->cw_info.qamModOrder);
ret_pdsch = -1;
} else {
NR_UE_DLSCH_t joint_dlsch = *cw0;
joint_dlsch.active = true;
joint_dlsch.cw_info.Nl = total_layers;
const int joint_G = G[0] + G[1];
const uint32_t joint_buf_sz = ALIGNARRAYSIZE(joint_G, 32);
fourDimArray_t *joint_llr_view =
nr_ue_reuse_array(&joint_llr_storage, sizeof(int16_t), joint_buf_sz, 0, 0, 0);
int16_t *joint_llr = (int16_t *)joint_llr_view->data;
NR_DL_UE_HARQ_t joint_harq = ue->dl_harq_processes[0][dlsch_config->harq_process_nbr];
joint_harq.status = NR_ACTIVE;
ret_pdsch = nr_ue_pdsch_procedures(
ue, proc, &joint_dlsch, &joint_harq, dlsch_config, joint_llr, rxdataF, joint_G, &freq_alloc);
if (ret_pdsch >= 0) {
const int qm = cw0->cw_info.qamModOrder;
const int joint_stride = total_layers * qm;
AssertFatal(joint_G % joint_stride == 0,
"Rank-%d PDSCH G=%d is not divisible by the layer stride %d\n",
total_layers,
joint_G,
joint_stride);
int cw_offset[NR_MAX_NB_CODEWORDS] = {0};
for (int re = 0; re < joint_G / joint_stride; re++) {
const int16_t *joint_re = joint_llr + re * joint_stride;
const int cw0_bits = cw0->cw_info.Nl * qm;
const int cw1_bits = cw1->cw_info.Nl * qm;
memcpy(llr[0] + cw_offset[0], joint_re, cw0_bits * sizeof(*joint_re));
memcpy(llr[1] + cw_offset[1], joint_re + cw0_bits, cw1_bits * sizeof(*joint_re));
cw_offset[0] += cw0_bits;
cw_offset[1] += cw1_bits;
}
AssertFatal(cw_offset[0] == G[0] && cw_offset[1] == G[1],
"Dual-codeword LLR split mismatch: %d/%d expected %d/%d\n",
cw_offset[0],
cw_offset[1],
G[0],
G[1]);
}
}
LOG_W(NR_PHY, "nr_ue_pdsch_procedures failed in slot %d\n", proc->nr_slot_rx);
} else {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[0];
NR_DL_UE_HARQ_t *harq = &ue->dl_harq_processes[0][dlsch_config->harq_process_nbr];
ret_pdsch = nr_ue_pdsch_procedures(ue, proc, dlsch, harq, dlsch_config, llr[0], rxdataF, G[0], &freq_alloc);
}
stop_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
if (cpumeas(CPUMEAS_GETSTATE)) {
LOG_D(PHY, "[SFN %d] Slot0 Slot1: Dlsch Proc %5.2f\n",nr_slot_rx,ue->phy_cpu_stats.cpu_time_stats[DLSCH_PROCEDURES_STATS].p_time/(cpuf*1000.0));
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
TracyCPlot("pdsch mcs", dlsch->cw_info.mcs);
if (dlsch->active)
UEscopeCopy(ue, pdschLlr, llr[c], sizeof(int16_t), 1, G[c], 0);
LOG_D(PHY, "DLSCH CW%d data reception at nr_slot_rx: %d\n", c, nr_slot_rx);
start_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
if (ret_pdsch >= 0) {
if (dlsch->active)
nr_ue_dlsch_procedures(ue, proc, dlsch, c, G[c], &freq_alloc, dlsch_config, llr[c]);
} else if (dlsch->active) {
LOG_E(NR_PHY, "PDSCH demodulation failed for CW%d\n", c);
LOG_W(NR_PHY, "nr_ue_pdsch_procedures failed in slot %d\n", proc->nr_slot_rx);
}
stop_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
if (cpumeas(CPUMEAS_GETSTATE))
LOG_D(PHY,
"[SFN %d] Slot0 Slot1: Dlsch Proc %5.2f\n",
nr_slot_rx,
ue->phy_cpu_stats.cpu_time_stats[DLSCH_PROCEDURES_STATS].p_time / (cpuf * 1000.0));
}
/* One PDSCH occasion has one RX/TX synchronization point even when it
* carries two transport blocks. Joining this barrier in each codeword's
* decoder advances it twice: the second join waits for a TX participant
* in the next generation and deadlocks the UE after its first rank>4
* PDSCH. Publish both ACK/NACK results first, then release TX once. */
bool needs_ack_barrier = false;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
const NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
needs_ack_barrier |= dlsch->active
&& (dlsch->rnti_type == TYPE_C_RNTI_ || dlsch->rnti_type == TYPE_RA_RNTI_);
}
if (needs_ack_barrier && dlsch_config->k1_feedback) {
const int ack_nack_slot_and_frame =
proc->nr_slot_rx + dlsch_config->k1_feedback + proc->frame_rx * ue->frame_parms.slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
}
if (ue->phy_sim_rxdataF)
memcpy(ue->phy_sim_rxdataF + start_symb_sch * ue->frame_parms.ofdm_symbol_size * sizeof(c16_t),
&rxdataF[0][start_symb_sch * ue->frame_parms.ofdm_symbol_size],
sizeof(int32_t) * nb_symb_sch * ue->frame_parms.ofdm_symbol_size);
if (ue->phy_sim_pdsch_llr)
memcpy(ue->phy_sim_pdsch_llr, llr[0], sizeof(int16_t) * rx_llr_buf_sz);
if (ue->phy_sim_pdsch_llr) {
int16_t *sim_llr = ue->phy_sim_pdsch_llr;
size_t llr_offset = 0;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
memcpy(sim_llr + llr_offset, llr[c], sizeof(*sim_llr) * G[c]);
llr_offset += G[c];
}
}
for (int i=0; i<nb_codewords; i++)
free(llr[i]);
}
if (nr_slot_rx==9) {
+24 -27
View File
@@ -457,23 +457,25 @@ int main(int argc, char **argv)
NR_UE_DLSCH_t *dlsch0_ue = &dlsch_ue[0];
NR_DL_UE_HARQ_t *harq_process = &UE->dl_harq_processes[0][harq_pid];
harq_process->first_rx = 1;
dlsch0_ue->dlsch_config.mcs = Imcs;
dlsch0_ue->dlsch_config.mcs_table = mcs_table;
dlsch0_ue->Nl = Nl;
dlsch0_ue->dlsch_config.number_rbs = nb_rb;
dlsch0_ue->dlsch_config.qamModOrder = mod_order;
dlsch0_ue->dlsch_config.rv = rvidx;
dlsch0_ue->dlsch_config.targetCodeRate = rate;
dlsch0_ue->dlsch_config.TBS = TBS;
dlsch0_ue->dlsch_config.ldpcBaseGraph = get_BG(TBS, rate);
dlsch0_ue->dlsch_config.dmrsConfigType = NFAPI_NR_DMRS_TYPE1;
dlsch0_ue->dlsch_config.dlDmrsSymbPos = 4;
dlsch0_ue->dlsch_config.n_dmrs_cdm_groups = 1;
dlsch0_ue->dlsch_config.tbslbrm = Tbslbrm;
printf("harq process ue mcs = %d Qm = %d, symb %d\n", dlsch0_ue->dlsch_config.mcs, dlsch0_ue->dlsch_config.qamModOrder, nb_symb_sch);
fapi_nr_dl_config_dlsch_pdu_rel15_t dlsch_config;
dlsch_config.cw_info[0].mcs = Imcs;
dlsch_config.mcs_table = mcs_table;
dlsch_config.number_rbs = nb_rb;
dlsch_config.cw_info[0].Nl = Nl;
dlsch_config.cw_info[0].qamModOrder = mod_order;
dlsch_config.cw_info[0].rv = rvidx;
dlsch_config.cw_info[0].targetCodeRate = rate;
dlsch_config.cw_info[0].TBS = TBS;
dlsch_config.cw_info[0].ldpcBaseGraph = get_BG(TBS, rate);
dlsch_config.dmrsConfigType = NFAPI_NR_DMRS_TYPE1;
dlsch_config.dlDmrsSymbPos = 4;
dlsch_config.n_dmrs_cdm_groups = 1;
dlsch_config.tbslbrm = Tbslbrm;
dlsch0_ue->cw_info = dlsch_config.cw_info[0];
printf("harq process ue mcs = %d Qm = %d, symb %d\n", dlsch_config.cw_info[0].mcs, dlsch_config.cw_info[0].qamModOrder, nb_symb_sch);
uint8_t test_input[TBS / 8 + 4]; // + 3 for CRC + 1 additional byte, see nr_dlsch_encoding()
dlsch->pdu = test_input;
dlsch->pdu[0] = test_input;
//unsigned char test_input[TBS / 8] __attribute__ ((aligned(16)));
for (i = 0; i < TBS / 8; i++)
test_input[i] = (unsigned char) rand();
@@ -536,27 +538,23 @@ int main(int argc, char **argv)
exit(-1);
#endif
int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER*NR_MAX_NB_LAYERS; //number of segments to be allocated
int num_rb = dlsch0_ue->dlsch_config.number_rbs;
int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS; // number of segments to be allocated
int num_rb = dlsch_config.number_rbs;
if (num_rb != 273) {
a_segments = a_segments*num_rb;
a_segments = (a_segments/273)+1;
}
uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment
__attribute__ ((aligned(32))) uint8_t b[dlsch_bytes];
uint8_t DLSCH_ids[1] = {0};
short *p_channel_output_fixed = channel_output_fixed;
uint8_t *p_b = b;
int available_bits_array[1] = { available_bits };
nr_dlsch_decoding(UE,
&proc,
dlsch0_ue,
&p_channel_output_fixed,
&p_b,
available_bits_array,
1,
0,
&dlsch_config,
channel_output_fixed,
b,
num_rb,
DLSCH_ids);
available_bits);
if (dlsch0_ue->last_iteration_cnt > dlsch0_ue->max_ldpc_iterations)
n_errors++;
@@ -634,4 +632,3 @@ int main(int argc, char **argv)
return (n_errors);
}
+112 -64
View File
@@ -240,14 +240,7 @@ void nr_dlsim_preprocessor(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pdsch)
sched_pdsch.Qm = nr_get_Qm_dl(sched_pdsch.mcs, current_BWP->mcsTableIdx);
sched_pdsch.R = nr_get_code_rate_dl(sched_pdsch.mcs, current_BWP->mcsTableIdx);
sched_pdsch.tb_size = nr_compute_tbs(sched_pdsch.Qm,
sched_pdsch.R,
sched_pdsch.rbSize,
sched_pdsch.tda_info.nrOfSymbols,
sched_pdsch.dmrs_parms.N_PRB_DMRS * sched_pdsch.dmrs_parms.N_DMRS_SLOT,
0 /* N_PRB_oh, 0 for initialBWP */,
0 /* tb_scaling */,
sched_pdsch.nrOfLayers) >> 3;
update_sched_pdsch_codeword_tbs(&sched_pdsch);
/* the simulator assumes the HARQ PID is equal to the slot number */
sched_pdsch.dl_harq_pid = pp_pdsch->slot;
@@ -471,7 +464,7 @@ int main(int argc, char **argv)
case 'x':
g_nrOfLayers = atoi(optarg);
if ((g_nrOfLayers == 0) || (g_nrOfLayers > 4)) {
if ((g_nrOfLayers == 0) || (g_nrOfLayers > NR_MAX_NB_LAYERS)) {
printf("Unsupported nr Of Layers %d\n", g_nrOfLayers);
exit(-1);
}
@@ -490,7 +483,7 @@ int main(int argc, char **argv)
case 'y':
n_tx=atoi(optarg);
if ((n_tx==0) || (n_tx>4)) {//extend gNB to support n_tx = 4
if ((n_tx == 0) || (n_tx > NR_MAX_NB_LAYERS)) {
printf("Unsupported number of tx antennas %d\n",n_tx);
exit(-1);
}
@@ -500,7 +493,7 @@ int main(int argc, char **argv)
case 'z':
n_rx=atoi(optarg);
if ((n_rx==0) || (n_rx>4)) {//extend UE to support n_tx = 4
if ((n_rx == 0) || (n_rx > NR_MAX_NB_LAYERS)) {
printf("Unsupported number of rx antennas %d\n",n_rx);
exit(-1);
}
@@ -752,6 +745,7 @@ int main(int argc, char **argv)
.num_dlharq = 16,
.num_ulharq = 16,
.maxMIMO_layers = g_nrOfLayers,
.pdsch_dmrs_additional_position = 1,
.force_256qam_off = false,
.timer_config.sr_ProhibitTimer = 0,
.timer_config.sr_TransMax = 64,
@@ -940,8 +934,6 @@ int main(int argc, char **argv)
UE_mac->if_module = nr_ue_if_module_init(0);
unsigned int available_bits=0;
unsigned char *estimated_output_bit=NULL;
unsigned char *test_input_bit=NULL;
unsigned int errors_bit = 0;
initFloatingCoresTpool(dlsch_threads, &nrUE_params.Tpool, false, "UE-tpool");
@@ -987,7 +979,7 @@ int main(int argc, char **argv)
a_segments = (a_segments/273)+1;
}
uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment
UE->phy_sim_dlsch_b = calloc(1, dlsch_bytes);
UE->phy_sim_dlsch_b = calloc(NR_MAX_NB_CODEWORDS, dlsch_bytes);
// csv file
if (filename_csv != NULL) {
@@ -1069,20 +1061,26 @@ int main(int argc, char **argv)
UE_proc.nr_slot_rx = slot;
UE_proc.gNB_id = 0;
NR_UE_DLSCH_t *dlsch0 = &phy_data.dlsch[0];
int harq_pid = slot;
NR_DL_UE_HARQ_t *UE_harq_process = &UE->dl_harq_processes[0][harq_pid];
UE_harq_process->decodeResult = false;
NR_DL_UE_HARQ_t *UE_harq_process[NR_MAX_NB_CODEWORDS];
round = 0;
UE_harq_process->DLround = round;
UE_harq_process->first_rx = 1;
for (int c = 0; c < NR_MAX_NB_CODEWORDS; c++) {
UE_harq_process[c] = &UE->dl_harq_processes[c][harq_pid];
UE_harq_process[c]->decodeResult = false;
UE_harq_process[c]->DLround = round;
UE_harq_process[c]->first_rx = 1;
}
int n_codewords = 0;
bool all_codewords_decoded = false;
while (round < num_rounds && !UE_harq_process->decodeResult && !stop) {
while (round < num_rounds && !all_codewords_decoded && !stop) {
reset_sched_response(Sched_INFO, frame, slot, 0, 0);
clear_nr_nfapi_information(RC.nrmac[0], 0, frame, slot);
UE_info->UE_sched_ctrl.harq_processes[harq_pid].ndi = !(trial&1);
/* The high-rank PDSCH carries two independently HARQ-managed TBs.
* Advance both NDIs between independent trials; leaving CW1's NDI
* unchanged makes the UE treat every later trial as a retransmission. */
UE_info->UE_sched_ctrl.harq_processes[harq_pid].ndi2 = !(trial&1);
UE_info->UE_sched_ctrl.harq_processes[harq_pid].round = round;
// nr_schedule_ue_spec() requires the mutex to be locked
@@ -1302,57 +1300,97 @@ int main(int argc, char **argv)
//---------------------- count errors ----------------------
//----------------------------------------------------------
if (dlsch0->last_iteration_cnt >= dlsch0->max_ldpc_iterations)
n_codewords = phy_data.n_dlsch_codewords;
AssertFatal(n_codewords >= 1 && n_codewords <= NR_MAX_NB_CODEWORDS,
"Invalid number of decoded PDSCH codewords %d\n",
n_codewords);
bool block_error = false;
bool any_active_codeword = false;
all_codewords_decoded = true;
for (int c = 0; c < n_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data.dlsch[c];
any_active_codeword |= dlsch->active;
block_error |= dlsch->active && dlsch->last_iteration_cnt >= dlsch->max_ldpc_iterations;
all_codewords_decoded &= UE_harq_process[c]->decodeResult;
}
if (block_error)
n_errors[round]++;
if (dlsch0->active)
if (any_active_codeword)
round_trials[round]++;
int16_t *UE_llr = (int16_t*)UE->phy_sim_pdsch_llr;
TBS = dlsch0->dlsch_config.TBS;
uint16_t length_dmrs = get_num_dmrs(dlsch0->dlsch_config.dlDmrsSymbPos);
uint16_t nb_rb = dlsch0->dlsch_config.number_rbs;
uint8_t nb_re_dmrs = dlsch0->dlsch_config.dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ? 6*dlsch0->dlsch_config.n_dmrs_cdm_groups : 4*dlsch0->dlsch_config.n_dmrs_cdm_groups;
uint8_t mod_order = dlsch0->dlsch_config.qamModOrder;
uint8_t nb_symb_sch = dlsch0->dlsch_config.number_symbols;
TBS = 0;
uint16_t length_dmrs = get_num_dmrs(phy_data.dlsch_config.dlDmrsSymbPos);
uint16_t nb_rb = phy_data.dlsch_config.number_rbs;
uint8_t nb_re_dmrs = phy_data.dlsch_config.dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ?
6 * phy_data.dlsch_config.n_dmrs_cdm_groups :
4 * phy_data.dlsch_config.n_dmrs_cdm_groups;
uint8_t nb_symb_sch = phy_data.dlsch_config.number_symbols;
uint32_t unav_res = ptrsSymbPerSlot * ptrsRePerSymb;
available_bits = nr_get_G(nb_rb, nb_symb_sch, nb_re_dmrs, length_dmrs, unav_res, mod_order, pdsch_pdu_rel15->nrOfLayers);
available_bits = 0;
for (int c = 0; c < n_codewords; c++) {
const fapi_nr_dl_cw_info_t *cw_info = &phy_data.dlsch_config.cw_info[c];
TBS += cw_info->TBS;
available_bits += nr_get_G(nb_rb,
nb_symb_sch,
nb_re_dmrs,
length_dmrs,
unav_res,
cw_info->qamModOrder,
cw_info->Nl);
}
if (pdu_bit_map & 0x1) {
if (trial == 0 && round == 0) {
printf("[DLSIM][PTRS] Available bits are: %5u, removed PTRS bits are: %5u \n", available_bits, (ptrsSymbPerSlot * ptrsRePerSymb * pdsch_pdu_rel15->nrOfLayers * mod_order));
printf("[DLSIM][PTRS] Available bits are: %5u, removed PTRS REs are: %5u \n",
available_bits,
ptrsSymbPerSlot * ptrsRePerSymb * pdsch_pdu_rel15->nrOfLayers);
}
}
for (i = 0; i < available_bits; i++) {
const uint8_t current_bit = (gNB_dlsch->f[i / 8] & (1 << (i & 7))) >> (i & 7);
if (((current_bit == 0) && (UE_llr[i] <= 0)) || ((current_bit == 1) && (UE_llr[i] >= 0))) {
if (errors_scrambling[round] == 0) {
LOG_D(PHY,"First bit in error in unscrambling = %d\n",i);
uint32_t llr_offset = 0;
for (int c = 0; c < n_codewords; c++) {
const fapi_nr_dl_cw_info_t *cw_info = &phy_data.dlsch_config.cw_info[c];
const uint32_t codeword_bits = nr_get_G(nb_rb,
nb_symb_sch,
nb_re_dmrs,
length_dmrs,
unav_res,
cw_info->qamModOrder,
cw_info->Nl);
for (i = 0; i < codeword_bits; i++) {
const uint8_t current_bit = (gNB_dlsch->f[c][i / 8] & (1 << (i & 7))) >> (i & 7);
if (((current_bit == 0) && (UE_llr[llr_offset + i] <= 0))
|| ((current_bit == 1) && (UE_llr[llr_offset + i] >= 0))) {
if (errors_scrambling[round] == 0)
LOG_D(PHY, "First bit in error in unscrambling = CW%d bit %d\n", c, i);
errors_scrambling[round]++;
}
errors_scrambling[round]++;
}
llr_offset += codeword_bits;
}
AssertFatal(llr_offset == available_bits,
"PDSCH LLR accounting mismatch: %u of %u bits\n",
llr_offset,
available_bits);
//printf("dlsim round %d ends\n",round);
round++;
} // round
if (test_input_bit == NULL) {
test_input_bit = (unsigned char *)malloc16(8 * pdsch_pdu_rel15->TBSize[0]);
estimated_output_bit = (unsigned char *)malloc16(8 * pdsch_pdu_rel15->TBSize[0]);
}
for (i = 0; i < TBS; i++) {
for (int c = 0; c < n_codewords; c++) {
const uint32_t codeword_tbs = phy_data.dlsch_config.cw_info[c].TBS;
const uint8_t *decoded = UE->phy_sim_dlsch_b + c * dlsch_bytes;
for (i = 0; i < codeword_tbs; i++) {
const uint8_t estimated_output_bit = (decoded[i / 8] & (1 << (i & 7))) >> (i & 7);
const uint8_t test_input_bit = (gNB_dlsch->b[c][i / 8] & (1 << (i & 7))) >> (i & 7);
estimated_output_bit[i] = (UE->phy_sim_dlsch_b[i/8] & (1 << (i & 7))) >> (i & 7);
test_input_bit[i] = (gNB_dlsch->b[i / 8] & (1 << (i & 7))) >> (i & 7); // Further correct for multiple segments
if (estimated_output_bit[i] != test_input_bit[i]) {
if(errors_bit == 0)
LOG_D(PHY,"First bit in error in decoding = %d\n",i);
errors_bit++;
}
if (estimated_output_bit != test_input_bit) {
if (errors_bit == 0)
LOG_D(PHY, "First bit in error in decoding = CW%d bit %d\n", c, i);
errors_bit++;
}
}
}
////////////////////////////////////////////////////////////
@@ -1362,7 +1400,7 @@ int main(int argc, char **argv)
printf("errors_bit = %u (trial %d)\n", errors_bit, trial);
}
roundStats += round;
if (UE_harq_process->decodeResult)
if (all_codewords_decoded)
effRate += ((double)TBS) / round;
} // noise trials
@@ -1389,7 +1427,11 @@ int main(int argc, char **argv)
printf("), Channel BER (%e", berStats[0]);
for (int r = 1; r < num_rounds; r++)
printf(",%e", berStats[r]);
printf(") Avg round %.2f, Eff Rate %.4f bits/slot, Eff Throughput %.2f, TBS %u bits/slot\n", roundStats, effRate, effRate / (8 * pdsch_pdu_rel15->TBSize[0]) * 100, 8 * pdsch_pdu_rel15->TBSize[0]);
printf(") Avg round %.2f, Eff Rate %.4f bits/slot, Eff Throughput %.2f, TBS %u bits/slot\n",
roundStats,
effRate,
effRate / TBS * 100,
TBS);
printf("*****************************************\n");
printf("\n");
// writing to csv file
@@ -1397,7 +1439,7 @@ int main(int argc, char **argv)
fprintf(csv_file,"%f,%d/%d,",SNR,n_false_positive,n_trials);
for (int r = 0; r < num_rounds; r++)
fprintf(csv_file,"%d/%d,%u/%u,%f,%e,",n_errors[r], round_trials[r], errors_scrambling[r], available_bits * round_trials[r],blerStats[r],berStats[r]);
fprintf(csv_file,"%.2f,%.4f,%.2f,%u\n", roundStats, effRate, effRate / (8 * pdsch_pdu_rel15->TBSize[0]) * 100, 8 * pdsch_pdu_rel15->TBSize[0]);
fprintf(csv_file, "%.2f,%.4f,%.2f,%u\n", roundStats, effRate, effRate / TBS * 100, TBS);
}
if (print_perf==1) {
printf("\ngNB TX function statistics (per %d us slot, NPRB %d, mcs %d, C %d, block %d)\n",
@@ -1405,7 +1447,7 @@ int main(int argc, char **argv)
g_rbSize,
g_mcsIndex,
UE->dl_harq_processes[0][slot].C,
8 * pdsch_pdu_rel15->TBSize[0]);
TBS);
printDistribution(&gNB->phy_proc_tx,table_tx,"PHY proc tx");
printStatIndent2(&gNB->dci_generation_stats, "DCI encoding time");
printStatIndent2(&gNB->dlsch_encoding_stats,"DLSCH encoding time");
@@ -1457,10 +1499,10 @@ int main(int argc, char **argv)
const int s = pdsch_pdu_rel15->StartSymbolIndex;
const int n = pdsch_pdu_rel15->NrOfSymbols;
for (int i = s; i < s + n; i++) {
const uint32_t dmrsBitMap = phy_data.dlsch[0].dlsch_config.dlDmrsSymbPos;
const uint32_t dmrsCfg = phy_data.dlsch[0].dlsch_config.dmrsConfigType;
const uint32_t nrb = phy_data.dlsch[0].dlsch_config.number_rbs;
const uint32_t ncdmg = phy_data.dlsch[0].dlsch_config.n_dmrs_cdm_groups;
const uint32_t dmrsBitMap = phy_data.dlsch_config.dlDmrsSymbPos;
const uint32_t dmrsCfg = phy_data.dlsch_config.dmrsConfigType;
const uint32_t nrb = phy_data.dlsch_config.number_rbs;
const uint32_t ncdmg = phy_data.dlsch_config.n_dmrs_cdm_groups;
const uint32_t numValidReSym = ((dmrsBitMap >> i) & 1)
? ((dmrsCfg == NFAPI_NR_DMRS_TYPE1) ? nrb * (12 - 6 * ncdmg) : nrb * (12 - 4 * ncdmg))
: (nrb * 12);
@@ -1488,10 +1530,11 @@ int main(int argc, char **argv)
LOG_M("chestF0.m", "chF0", UE->phy_sim_pdsch_dl_ch_estimates, frame_parms->ofdm_symbol_size * 14, dec, op_format);
op_format = (op_format & (~1));
LOG_M("rxF_llr.m", "rxFllr", UE->phy_sim_pdsch_llr, available_bits, dec, op_format);
ret = n_errors[0] == 0 && n_false_positive == 0 ? 0 : 1;
break;
}
if (effRate >= (eff_tp_check * 8 * pdsch_pdu_rel15->TBSize[0])) {
if (effRate >= eff_tp_check * TBS) {
printf("PDSCH test OK\n");
ret = 0;
break;
@@ -1528,8 +1571,6 @@ int main(int argc, char **argv)
free(r_re);
free(r_im);
free(txdata);
free(test_input_bit);
free(estimated_output_bit);
free(UE->phy_sim_rxdataF);
free(UE->phy_sim_pdsch_llr);
free(UE->phy_sim_pdsch_rxdataF_ext);
@@ -1666,6 +1707,13 @@ void update_dmrs_config(NR_CellGroupConfig_t *scg, int8_t* dmrs_arg)
NR_DMRS_DownlinkConfig_t *dmrs_config = (mapping_type == typeA) ? dmrs_MappingtypeA->choice.setup : dmrs_MappingtypeB->choice.setup;
/* Ranks 5--8 need the two-symbol Type-1 DMRS that provides ports 1000--1007.
* Keep that mandatory maxLength setting when -U changes only the additional
* DMRS position; otherwise the high-rank scheduler correctly rejects the
* inconsistent single-symbol configuration. */
if (g_nrOfLayers > 4)
asn1cCallocOne(dmrs_config->maxLength, NR_DMRS_DownlinkConfig__maxLength_len2);
if (add_pos != 2) { // pos0,pos1,pos3
if (dmrs_config->dmrs_AdditionalPosition == NULL) {
dmrs_config->dmrs_AdditionalPosition = calloc(1,sizeof(*dmrs_MappingtypeA->choice.setup->dmrs_AdditionalPosition));
+3 -2
View File
@@ -83,8 +83,9 @@ void nr_fill_dl_indication(nr_downlink_indication_t *dl_ind,
void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
uint8_t pdu_type,
PHY_VARS_NR_UE *ue,
NR_UE_DLSCH_t *dlsch0,
NR_UE_DLSCH_t *dlsch1,
int cw_idx,
int harq_pid,
NR_UE_DLSCH_t *dlsch,
uint16_t n_pdus,
const UE_nr_rxtx_proc_t *proc,
void *typeSpecific,
@@ -2277,6 +2277,14 @@ void init_channelmod(void) {
int load_channellist(uint8_t nb_tx, uint8_t nb_rx, double sampling_rate, uint64_t center_freq, double channel_bandwidth) {
// nakaolab Fix 9: ensure RNG is initialized exactly once, before any
// random_channel call (TDL/EPA/Rayleigh need gaussZiggurat). Static guard
// prevents multiple init races on the global tableNor* arrays.
static bool nakaolab_rng_done = false;
if (!nakaolab_rng_done) {
randominit();
nakaolab_rng_done = true;
}
paramdef_t achannel_params[] = CHANNELMOD_MODEL_PARAMS_DESC;
paramlist_def_t channel_list;
memset(&channel_list,0,sizeof(paramlist_def_t));
@@ -17,6 +17,11 @@ bool read_mac_sm(void* data)
//fill_mac_ind_data(mac);
mac->msg.tstamp = time_now_us();
// Export the physical denominator beside the per-UE TBS/PRB counters. This
// avoids estimating PRB opportunity from a rounded, differently-windowed
// KPM percentage in the rApp.
mac->msg.dl_prb_opportunities = RC.nrmac[mod_id]->mac_stats.dl.total_prb_aggregate;
mac->msg.dl_used_prbs = RC.nrmac[mod_id]->mac_stats.dl.used_prb_aggregate;
NR_UEs_t *UE_info = &RC.nrmac[mod_id]->UE_info;
size_t num_ues = 0;
@@ -33,6 +38,10 @@ bool read_mac_sm(void* data)
size_t i = 0; //TODO
UE_iterator(UE_info->connected_ue_list, UE) {
// Bound the write against the calloc'd buffer size: a UE attach between
// the count loop above and this iterator can grow connected_ue_list and
// overflow mac->msg.ue_stats (corrupts the heap → later free() abort).
if (i >= num_ues) break;
const NR_UE_sched_ctrl_t* sched_ctrl = &UE->UE_sched_ctrl;
mac_ue_stats_impl_t* rd = &mac->msg.ue_stats[i];
@@ -58,8 +67,11 @@ bool read_mac_sm(void* data)
rd->dl_aggr_retx_prb = UE->mac_stats.dl.total_rbs_retx;
rd->ul_aggr_retx_prb = UE->mac_stats.ul.total_rbs_retx;
rd->dl_aggr_bytes_sdus = UE->mac_stats.dl.lc_bytes[3];
rd->ul_aggr_bytes_sdus = UE->mac_stats.ul.lc_bytes[3];
// The service-model field is aggregate SDU bytes. Using LCID 3 silently
// drops payload whenever the DRB is assigned another logical channel and
// corrupts the MAC payload/TBS fill ratio used by capacity estimation.
rd->dl_aggr_bytes_sdus = UE->mac_stats.dl.total_sdu_bytes;
rd->ul_aggr_bytes_sdus = UE->mac_stats.ul.total_sdu_bytes;
rd->dl_aggr_sdus = UE->mac_stats.dl.num_mac_sdu;
rd->ul_aggr_sdus = UE->mac_stats.ul.num_mac_sdu;
@@ -68,6 +80,16 @@ bool read_mac_sm(void* data)
rd->pucch_snr = sched_ctrl->pucch_pc.avg_snr; //: float = -64;
rd->wb_cqi = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_1tb;
rd->csi_valid = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.print_report;
rd->dl_ri = rd->csi_valid ? sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1 : 0;
rd->dl_layers = UE->mac_stats.dl.last_layers;
rd->pmi_x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
rd->pmi_x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
rd->csi_frame = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.report_frame;
rd->csi_slot = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.report_slot;
rd->active_tx_chains = RC.nrmac[mod_id]->antenna_state != NULL
? nr_get_active_tx_antenna_count(RC.nrmac[mod_id]->antenna_state)
: 0;
rd->dl_mcs1 = sched_ctrl->dl_bler_stats.mcs;
rd->dl_bler = sched_ctrl->dl_bler_stats.bler;
rd->ul_mcs1 = sched_ctrl->ul_bler_stats.mcs;
@@ -110,4 +132,3 @@ sm_ag_if_ans_t write_ctrl_mac_sm(void const* data)
sm_ag_if_ans_t ans = {0};
return ans;
}
@@ -25,7 +25,7 @@
#endif
#if defined (NGRAN_GNB_CUUP)
void fill_pdcp_stats_for_all_rbs(pdcp_radio_bearer_stats_t* rb, const size_t num_ues, const uint64_t* ue_id_list)
void fill_pdcp_stats_for_all_rbs(pdcp_radio_bearer_stats_t* rb, const size_t num_ues, const uint64_t* ue_id_list, const size_t rb_capacity)
{
size_t j = 0;
for (size_t i = 0; i < num_ues; i++) {
@@ -37,6 +37,10 @@ void fill_pdcp_stats_for_all_rbs(pdcp_radio_bearer_stats_t* rb, const size_t num
if (!rc) continue;
// Bound the write against the original capacity: an RB activating
// between the count loop in read_pdcp_sm and this fill can grow the
// observed count and overflow rb[] (corrupts the heap).
if (j >= rb_capacity) return;
pdcp_radio_bearer_stats_t *rd = &rb[j++];
rd->txpdu_pkts = rb_pdcp.txpdu_pkts; /* aggregated number of tx packets */
@@ -114,7 +118,7 @@ bool read_pdcp_sm(void* data)
// Filling Radio Bearer Statistics
#if defined (NGRAN_GNB_CUUP)
fill_pdcp_stats_for_all_rbs(pdcp->msg.rb, num_ues, ue_id_list);
fill_pdcp_stats_for_all_rbs(pdcp->msg.rb, num_ues, ue_id_list, pdcp->msg.len);
#endif
return true;
}
@@ -83,6 +83,11 @@ bool read_rlc_sm(void* data)
const int srb_flag = 0;
const bool rc = nr_rlc_get_statistics(rnti, srb_flag, rb_id, &rb_rlc);
if(!rc) continue;
// Bound the write against the originally-calloc'd buffer size. A UE
// attach between num_act_rb() above and this iterator can grow the
// connected-UE list; without this check we'd overflow rlc->msg.rb
// and corrupt the heap (crash later in free_rlc_ind_msg()).
if (i >= act_rb) goto done_iter;
rlc_radio_bearer_stats_t* sm_rb = &rlc->msg.rb[i];
/* TX */
@@ -143,6 +148,7 @@ bool read_rlc_sm(void* data)
++i;
}
}
done_iter:
return act_rb > 0;
}
@@ -86,8 +86,42 @@ static meas_record_lst_t fill_DRB_PdcpSduVolumeUL(__attribute__((unused)) const
}
#if defined (NGRAN_GNB_DU)
static uldlcounter_t last_rlc_pdu_total_bytes[MAX_MOBILES_PER_GNB] = {0};
static uldlcounter_t last_total_prbs[MAX_MOBILES_PER_GNB] = {0};
typedef struct ue_uldlcounter {
const NR_UE_info_t *ue;
rnti_t rnti;
uldlcounter_t value;
bool dl_valid;
bool ul_valid;
} ue_uldlcounter_t;
static ue_uldlcounter_t last_rlc_pdu_total_bytes[MAX_MOBILES_PER_GNB] = {0};
static ue_uldlcounter_t last_total_prbs[MAX_MOBILES_PER_GNB] = {0};
/* The connected UE list is compacted when a UE leaves, so its iteration index
* is not a stable identity. Keep baselines by the MAC-assigned UID instead and
* reset them when that UID is reused for another UE. */
static ue_uldlcounter_t *get_ue_counter(ue_uldlcounter_t counters[MAX_MOBILES_PER_GNB],
const cudu_ue_info_pair_t ue_info)
{
AssertFatal(ue_info.ue != NULL, "KPM UE measurement requires MAC UE information\n");
const uid_t uid = ue_info.ue->uid;
AssertFatal(uid < MAX_MOBILES_PER_GNB, "KPM UE UID %u exceeds maximum %u\n", uid, MAX_MOBILES_PER_GNB);
ue_uldlcounter_t *counter = &counters[uid];
if (counter->ue != ue_info.ue || counter->rnti != ue_info.ue->rnti)
*counter = (ue_uldlcounter_t){.ue = ue_info.ue, .rnti = ue_info.ue->rnti};
return counter;
}
/* A new/reused UE counter and a reset/wrap have no valid preceding sample.
* Establish a fresh baseline and report zero for that single period. */
static uint32_t counter_delta(uint32_t current, uint32_t *previous, bool *valid)
{
const uint32_t delta = *valid && current >= *previous ? current - *previous : 0;
*previous = current;
*valid = true;
return delta;
}
static nr_rlc_statistics_t get_rlc_stats_per_drb(const rnti_t rnti, const int rb_id)
{
@@ -120,8 +154,16 @@ static meas_record_lst_t fill_DRB_RlcSduDelayDl(__attribute__((unused)) const la
meas_record.value = REAL_MEAS_VALUE;
// Get the value of sojourn time at the RLC buffer
meas_record.real_val = rlc.txsdu_avg_time_to_tx; // [μs]
// Report the windowed-average RLC SDU sojourn (mean per-packet wait over the
// last 100 ms), not the instantaneous tx_list HOL snapshot. The snapshot
// (txsdu_wt_us) reads 0 whenever the tx_list is momentarily drained at the
// single sampling instant -- common with bursty DL even at high throughput --
// which systematically under-reports delay (era/load-dependent). The averaging
// accumulator is now activated once (reset guarded to off->on in
// nr_rlc_activate_avg_time_to_tx), so it accumulates continuously instead of
// being wiped on every read; it returns 0 only when no SDU was transmitted in
// the window (genuine idle/starvation).
meas_record.real_val = (double)rlc.txsdu_avg_time_to_tx; // [us] avg RLC SDU sojourn
return meas_record;
}
@@ -132,7 +174,7 @@ static meas_record_lst_t fill_DRB_RlcSduDelayDl(__attribute__((unused)) const la
static meas_record_lst_t fill_DRB_UEThpDl(__attribute__((unused)) const label_info_lst_t label,
uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info,
const size_t ue_idx,
__attribute__((unused)) const size_t ue_idx,
__attribute__((unused))e2_node_level_stats_t* node_stats)
{
meas_record_lst_t meas_record = {0};
@@ -142,8 +184,9 @@ static meas_record_lst_t fill_DRB_UEThpDl(__attribute__((unused)) const label_in
meas_record.value = REAL_MEAS_VALUE;
// Calculate DL Thp
meas_record.real_val = (double)(rlc.txpdu_bytes - last_rlc_pdu_total_bytes[ue_idx].dl)*8/gran_period_ms; // [kbps]
last_rlc_pdu_total_bytes[ue_idx].dl = rlc.txpdu_bytes;
ue_uldlcounter_t *counter = get_ue_counter(last_rlc_pdu_total_bytes, ue_info);
const uint32_t bytes = counter_delta(rlc.txpdu_bytes, &counter->value.dl, &counter->dl_valid);
meas_record.real_val = gran_period_ms == 0 ? 0 : (double)bytes * 8 / gran_period_ms; // [kbps]
return meas_record;
}
@@ -154,7 +197,7 @@ static meas_record_lst_t fill_DRB_UEThpDl(__attribute__((unused)) const label_in
static meas_record_lst_t fill_DRB_UEThpUl(__attribute__((unused)) const label_info_lst_t label,
uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info,
const size_t ue_idx,
__attribute__((unused)) const size_t ue_idx,
__attribute__((unused))e2_node_level_stats_t* node_stats)
{
meas_record_lst_t meas_record = {0};
@@ -166,8 +209,9 @@ static meas_record_lst_t fill_DRB_UEThpUl(__attribute__((unused)) const label_in
meas_record.value = REAL_MEAS_VALUE;
// Calculate UL Thp
meas_record.real_val = (double)(rlc.rxpdu_bytes - last_rlc_pdu_total_bytes[ue_idx].ul)*8/gran_period_ms; // [kbps]
last_rlc_pdu_total_bytes[ue_idx].ul = rlc.rxpdu_bytes;
ue_uldlcounter_t *counter = get_ue_counter(last_rlc_pdu_total_bytes, ue_info);
const uint32_t bytes = counter_delta(rlc.rxpdu_bytes, &counter->value.ul, &counter->ul_valid);
meas_record.real_val = gran_period_ms == 0 ? 0 : (double)bytes * 8 / gran_period_ms; // [kbps]
return meas_record;
}
@@ -177,7 +221,7 @@ static meas_record_lst_t fill_DRB_UEThpUl(__attribute__((unused)) const label_in
static meas_record_lst_t fill_RRU_PrbTotDl(__attribute__((unused)) const label_info_lst_t label,
__attribute__((unused)) uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info,
const size_t ue_idx,
__attribute__((unused)) const size_t ue_idx,
e2_node_level_stats_t* node_stats)
{
meas_record_lst_t meas_record = {0};
@@ -185,8 +229,13 @@ static meas_record_lst_t fill_RRU_PrbTotDl(__attribute__((unused)) const label_i
meas_record.value = INTEGER_MEAS_VALUE;
// Get the number of DL PRBs
meas_record.int_val = (ue_info.ue->mac_stats.dl.total_rbs - last_total_prbs[ue_idx].dl) * 100 / (node_stats[1].mac_stats.dl.total_prb_aggregate - node_stats[0].mac_stats.dl.total_prb_aggregate); // [%]
last_total_prbs[ue_idx].dl = ue_info.ue->mac_stats.dl.total_rbs;
ue_uldlcounter_t *counter = get_ue_counter(last_total_prbs, ue_info);
const uint32_t ue_delta = counter_delta(ue_info.ue->mac_stats.dl.total_rbs, &counter->value.dl, &counter->dl_valid);
const uint64_t current = node_stats[1].mac_stats.dl.total_prb_aggregate;
const uint64_t previous = node_stats[0].mac_stats.dl.total_prb_aggregate;
const uint64_t total_delta = current >= previous ? current - previous : 0;
const uint64_t percentage = total_delta == 0 ? 0 : (uint64_t)ue_delta * 100 / total_delta;
meas_record.int_val = percentage > 100 ? 100 : percentage; // [%]
return meas_record;
}
@@ -196,7 +245,7 @@ static meas_record_lst_t fill_RRU_PrbTotDl(__attribute__((unused)) const label_i
static meas_record_lst_t fill_RRU_PrbTotUl(__attribute__((unused)) const label_info_lst_t label,
__attribute__((unused)) uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info,
const size_t ue_idx,
__attribute__((unused)) const size_t ue_idx,
e2_node_level_stats_t* node_stats)
{
meas_record_lst_t meas_record = {0};
@@ -204,8 +253,13 @@ static meas_record_lst_t fill_RRU_PrbTotUl(__attribute__((unused)) const label_i
meas_record.value = INTEGER_MEAS_VALUE;
// Get the number of UL PRBs
meas_record.int_val = (ue_info.ue->mac_stats.ul.total_rbs - last_total_prbs[ue_idx].ul) * 100 / (node_stats[1].mac_stats.ul.total_prb_aggregate - node_stats[0].mac_stats.ul.total_prb_aggregate); // [%]
last_total_prbs[ue_idx].ul = ue_info.ue->mac_stats.ul.total_rbs;
ue_uldlcounter_t *counter = get_ue_counter(last_total_prbs, ue_info);
const uint32_t ue_delta = counter_delta(ue_info.ue->mac_stats.ul.total_rbs, &counter->value.ul, &counter->ul_valid);
const uint64_t current = node_stats[1].mac_stats.ul.total_prb_aggregate;
const uint64_t previous = node_stats[0].mac_stats.ul.total_prb_aggregate;
const uint64_t total_delta = current >= previous ? current - previous : 0;
const uint64_t percentage = total_delta == 0 ? 0 : (uint64_t)ue_delta * 100 / total_delta;
meas_record.int_val = percentage > 100 ? 100 : percentage; // [%]
return meas_record;
}
+12 -7
View File
@@ -592,7 +592,7 @@ static rc_ind_data_t* fill_ue_rrc_state_change(const gNB_RRC_UE_t *rrc_ue_contex
// Generate Indication Header
rc_ind->hdr.format = FORMAT_1_E2SM_RC_IND_HDR;
rc_ind->hdr.frmt_1.ev_trigger_id = malloc_or_fail(sizeof(uint32_t));
rc_ind->hdr.frmt_1.ev_trigger_id = calloc_or_fail(1, sizeof(uint32_t));
*rc_ind->hdr.frmt_1.ev_trigger_id = cond_id;
// Generate Indication Message
@@ -629,11 +629,11 @@ static void send_aper_ric_ind(const uint32_t ric_req_id, rc_ind_data_t* rc_ind_d
static rc_ind_data_t* fill_ue_id(const gNB_RRC_UE_t *rrc_ue_context, const uint16_t cond_id)
{
rc_ind_data_t* rc_ind = malloc_or_fail(sizeof(rc_ind_data_t));
rc_ind_data_t* rc_ind = calloc_or_fail(1, sizeof(rc_ind_data_t));
// Generate Indication Header
rc_ind->hdr.format = FORMAT_1_E2SM_RC_IND_HDR;
rc_ind->hdr.frmt_1.ev_trigger_id = malloc_or_fail(sizeof(uint32_t));
rc_ind->hdr.frmt_1.ev_trigger_id = calloc_or_fail(1, sizeof(uint32_t));
*rc_ind->hdr.frmt_1.ev_trigger_id = cond_id;
// Generate Indication Message
@@ -646,7 +646,7 @@ static rc_ind_data_t* fill_ue_id(const gNB_RRC_UE_t *rrc_ue_context, const uint1
// Fill the RAN Parameter details for UE ID
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_id = E2SM_RC_RS1_UE_ID;
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.type = ELEMENT_KEY_FLAG_FALSE_RAN_PARAMETER_VAL_TYPE;
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = malloc_or_fail(sizeof(ran_parameter_value_t));
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = calloc_or_fail(1, sizeof(ran_parameter_value_t));
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false->type = OCTET_STRING_RAN_PARAMETER_VALUE;
const ngran_node_t node_type = get_e2_node_type();
@@ -734,11 +734,11 @@ void signal_rrc_state_changed_to(const gNB_RRC_UE_t *rrc_ue_context, const rrc_s
static rc_ind_data_t* fill_rrc_msg_copy(const byte_array_t rrc_ba, const uint16_t cond_id)
{
rc_ind_data_t* rc_ind = malloc_or_fail(sizeof(rc_ind_data_t));
rc_ind_data_t* rc_ind = calloc_or_fail(1, sizeof(rc_ind_data_t));
// Generate Indication Header
rc_ind->hdr.format = FORMAT_1_E2SM_RC_IND_HDR;
rc_ind->hdr.frmt_1.ev_trigger_id = malloc_or_fail(sizeof(uint32_t));
rc_ind->hdr.frmt_1.ev_trigger_id = calloc_or_fail(1, sizeof(uint32_t));
*rc_ind->hdr.frmt_1.ev_trigger_id = cond_id;
// Generate Indication Message
@@ -751,7 +751,7 @@ static rc_ind_data_t* fill_rrc_msg_copy(const byte_array_t rrc_ba, const uint16_
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_id = E2SM_RC_RS1_RRC_MESSAGE;
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.type = ELEMENT_KEY_FLAG_FALSE_RAN_PARAMETER_VAL_TYPE;
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = malloc_or_fail(sizeof(ran_parameter_value_t));
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = calloc_or_fail(1, sizeof(ran_parameter_value_t));
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false->type = OCTET_STRING_RAN_PARAMETER_VALUE;
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false->octet_str_ran = copy_byte_array(rrc_ba);
@@ -776,6 +776,11 @@ static void check_rrc_msg_copy(const nr_rrc_class_e nr_channel, const uint32_t r
}
}
/* Public lock/unlock used by RRC to serialize UE-context teardown against
* in-flight RC indications fired by signal_rrc_msg(). See header comment. */
void rc_teardown_lock(void) { pthread_mutex_lock(&rc_mutex); }
void rc_teardown_unlock(void) { pthread_mutex_unlock(&rc_mutex); }
void signal_rrc_msg(const nr_rrc_class_e nr_channel, const uint32_t rrc_msg_id, const byte_array_t rrc_ba)
{
pthread_mutex_lock(&rc_mutex);
@@ -16,4 +16,13 @@ void signal_ue_id(const gNB_RRC_UE_t *rrc_ue_context, const uint16_t class, cons
void signal_rrc_state_changed_to(const gNB_RRC_UE_t *rrc_ue_context, const rrc_state_e2sm_rc_e rrc_state);
/* Serialize UE-context teardown against in-flight RC indications.
* Without this, signal_rrc_msg(rrcRelease) can hand a malloc'd indication
* to the async E2 agent thread; if the gNB then frees the UE state while
* the agent is still encoding, glibc detects heap corruption ("double free
* or corruption (!prev)") and the process aborts.
* The teardown caller must wrap rrc_gNB_remove_ue_context() in these. */
void rc_teardown_lock(void);
void rc_teardown_unlock(void);
#endif
+12
View File
@@ -645,6 +645,9 @@ void RCconfig_verify(configmodule_interface_t *cfg, ngran_node_t node_type)
verify_gnb_param_notset(gnbp, GNB_DO_SRS_IDX, GNB_CONFIG_STRING_DOSRS);
verify_gnb_param_notset(gnbp, GNB_FORCE256QAMOFF_IDX, GNB_CONFIG_STRING_FORCE256QAMOFF);
verify_gnb_param_notset(gnbp, GNB_MAXMIMOLAYERS_IDX, GNB_CONFIG_STRING_MAXMIMOLAYERS);
verify_gnb_param_notset(gnbp,
GNB_PDSCH_DMRS_ADDITIONAL_POSITION_IDX,
GNB_CONFIG_STRING_PDSCH_DMRS_ADDITIONAL_POSITION);
verify_gnb_param_notset(gnbp, GNB_DISABLE_HARQ_IDX, GNB_CONFIG_STRING_DISABLE_HARQ);
verify_gnb_param_notset(gnbp, GNB_NUM_DL_HARQ_IDX, GNB_CONFIG_STRING_NUM_DL_HARQPROCESSES);
verify_gnb_param_notset(gnbp, GNB_NUM_UL_HARQ_IDX, GNB_CONFIG_STRING_NUM_UL_HARQPROCESSES);
@@ -1553,6 +1556,12 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg)
config.force_UL256qam_off = *GNBParamList.paramarray[0][GNB_FORCEUL256QAMOFF_IDX].iptr;
config.use_deltaMCS = *GNBParamList.paramarray[0][GNB_USE_DELTA_MCS_IDX].iptr != 0;
config.maxMIMO_layers = *GNBParamList.paramarray[0][GNB_MAXMIMOLAYERS_IDX].iptr;
config.pdsch_dmrs_additional_position =
*GNBParamList.paramarray[0][GNB_PDSCH_DMRS_ADDITIONAL_POSITION_IDX].iptr;
AssertFatal(config.pdsch_dmrs_additional_position == 0 || config.pdsch_dmrs_additional_position == 1,
"%s must be 0 (pos0) or 1 (pos1), got %d\n",
GNB_CONFIG_STRING_PDSCH_DMRS_ADDITIONAL_POSITION,
config.pdsch_dmrs_additional_position);
config.disable_harq = *GNBParamList.paramarray[0][GNB_DISABLE_HARQ_IDX].iptr;
config.num_dlharq = *GNBParamList.paramarray[0][GNB_NUM_DL_HARQ_IDX].iptr;
config.num_ulharq = *GNBParamList.paramarray[0][GNB_NUM_UL_HARQ_IDX].iptr;
@@ -1572,6 +1581,9 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg)
config.num_ulharq);
int tot_ant = config.pdsch_AntennaPorts.N1 * config.pdsch_AntennaPorts.N2 * config.pdsch_AntennaPorts.XP;
AssertFatal(config.maxMIMO_layers != 0 && config.maxMIMO_layers <= tot_ant, "Invalid maxMIMO_layers %d\n", config.maxMIMO_layers);
LOG_I(GNB_APP,
"PDSCH DMRS additional position pos%d\n",
config.pdsch_dmrs_additional_position);
config.redcap = get_redcap_config(0);
config.ptrs = get_ptrs_config();
+5
View File
@@ -101,6 +101,7 @@ typedef enum {
#define GNB_CONFIG_STRING_CONFIG_REP "CSI_report_type"
#define GNB_CONFIG_STRING_1ST_ACTIVE_BWP "first_active_bwp"
#define GNB_CONFIG_STRING_LIMIT_RSRP_REPORT "max_num_RSRP_reported"
#define GNB_CONFIG_STRING_PDSCH_DMRS_ADDITIONAL_POSITION "pdsch_dmrs_additional_position"
#define GNB_CONFIG_HLP_STRING_ENABLE_SDAP "enable the SDAP layer\n"
#define GNB_CONFIG_HLP_FORCE256QAMOFF "suppress activation of 256 QAM despite UE support"
@@ -114,6 +115,7 @@ typedef enum {
#define GNB_CONFIG_HLP_CU_SIBS "List of CU generated SIBs to be transmitted"
#define GNB_CONFIG_HLP_DU_SIBS "List of DU generated SIBs to be transmitted"
#define GNB_CONFIG_HLP_CONFIG_REP "Define quantity for CSI report (options: ssb_rsrp, ssb_sinr and cri_rsrp)"
#define GNB_CONFIG_HLP_PDSCH_DMRS_ADDITIONAL_POSITION "PDSCH DMRS additional position: 0=pos0, 1=pos1"
/*-----------------------------------------------------------------------------------------------------------------------------------------*/
@@ -163,6 +165,7 @@ typedef enum {
{GNB_CONFIG_STRING_CONFIG_REP, GNB_CONFIG_HLP_CONFIG_REP, 0, .strptr=NULL, .defstrval="ssb_rsrp", TYPE_STRING, 0}, \
{GNB_CONFIG_STRING_1ST_ACTIVE_BWP, NULL, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \
{GNB_CONFIG_STRING_LIMIT_RSRP_REPORT, NULL, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \
{GNB_CONFIG_STRING_PDSCH_DMRS_ADDITIONAL_POSITION, GNB_CONFIG_HLP_PDSCH_DMRS_ADDITIONAL_POSITION, 0, .iptr=NULL, .defintval=1, TYPE_INT, 0}, \
}
// clang-format on
@@ -207,6 +210,7 @@ typedef enum {
#define GNB_CONFIG_REP_IDX 37
#define GNB_1ST_ACTIVE_BWP_IDX 38
#define GNB_LIMIT_RSRP_REPORT_IDX 39
#define GNB_PDSCH_DMRS_ADDITIONAL_POSITION_IDX 40
#define TRACKING_AREA_CODE_OKRANGE {0x0001,0xFFFD}
#define NUM_DL_HARQ_OKVALUES {2,4,6,8,10,12,16,32}
@@ -256,6 +260,7 @@ typedef enum {
3 } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
}
/*-------------------------------------------------------------------------------------------------------------------------------------------------*/
+18 -6
View File
@@ -3033,8 +3033,11 @@ uint16_t nr_dci_size(const NR_UE_DL_BWP_t *DL_BWP,
size += 8;
// TB2
long *maxCWperDCI = pdsch_Config ? pdsch_Config->maxNrofCodeWordsScheduledByDCI : NULL;
if ((maxCWperDCI != NULL) && (*maxCWperDCI == 2)) {
size += 8;
if (maxCWperDCI && (*maxCWperDCI == NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2)) {
dci_pdu->mcs2.nbits = 5;
dci_pdu->ndi2.nbits = 1;
dci_pdu->rv2.nbits = 2;
size += dci_pdu->mcs2.nbits + dci_pdu->ndi2.nbits + dci_pdu->rv2.nbits;
}
// HARQ process number – 5 bits if higher layer parameter harq-ProcessNumberSizeDCI-1-1 is configured;
// otherwise 4 bits. Spec 38.212 Section 7.3.1.2.2
@@ -4130,12 +4133,19 @@ uint16_t compute_pucch_prb_size(uint8_t format,
nr_prbs);
if (format==2){
/* nr_decode_pucch2() requires at least two PRB-symbols. Preserve the
* one-symbol format-2 resource at symbol 13 (which does not overlap the
* usual PUSCH ending at symbol 12) and use frequency width to meet that
* decoder invariant. This common helper keeps UE TX and gNB RX aligned. */
const int min_prbs = n_symb == 1 ? 2 : 1;
AssertFatal(nr_prbs >= min_prbs,
"PUCCH format 2 needs at least %d PRBs for %d symbol(s)", min_prbs, n_symb);
// TODO fix this for multiple CSI reports
for (int i = nr_prbs; i > 0; i--) {
for (int i = nr_prbs; i >= min_prbs; i--) {
// compute code rate factor for next prb value
int next_prb_factor = (i - 1) * n_symb * Qm * n_re_ctrl * r;
// if it does not sa
if (O_tot > next_prb_factor)
if (i == min_prbs || O_tot > next_prb_factor)
return i;
}
}
@@ -4549,8 +4559,10 @@ static void set_bitlen_size_singlepanel(CSI_Meas_bitlen_t *csi_bitlen, int n1, i
case 7:
case 8:
if(n1 == 4 && n2 == 1) {
csi_bitlen->pmi_i11_bitlen[i] = ceil(log2(n1 * o1));
csi_bitlen->pmi_i12_bitlen[i] = ceil(log2(n2 * o2 / 2));
// TS 38.212 Table 6.3.1.1.2-1: only half of the horizontal
// oversampled beams are valid for rank 7/8 in the 4x1 geometry.
csi_bitlen->pmi_i11_bitlen[i] = ceil(log2(n1 * o1 / 2));
csi_bitlen->pmi_i12_bitlen[i] = 0;
csi_bitlen->pmi_i13_bitlen[i] = 0;
csi_bitlen->pmi_x2_bitlen[i] = 1;
}
+1 -1
View File
@@ -595,7 +595,7 @@ typedef struct NR_UE_MAC_INST_s {
// Defined for abstracted mode
nr_downlink_indication_t dl_info;
NR_UE_DL_HARQ_STATUS_t dl_harq_info[NR_MAX_HARQ_PROCESSES];
NR_UE_DL_HARQ_STATUS_t dl_harq_info[NR_MAX_HARQ_PROCESSES][2]; // one harq process for each codeword
NR_UE_UL_HARQ_INFO_t ul_harq_info[NR_MAX_HARQ_PROCESSES];
NR_TAG_Id_t tag_Id;
+8 -6
View File
@@ -200,12 +200,14 @@ void reset_mac_inst(NR_UE_MAC_INST_t *nr_mac)
nr_mac->scheduling_info.phr_info.phr_reporting = 0;
nr_mac->scheduling_info.phr_info.was_mac_reset = true;
// flush the soft buffers for all DL HARQ processes
memset(nr_mac->dl_harq_info, 0, sizeof(nr_mac->dl_harq_info));
// for each DL HARQ process, consider the next received transmission for a TB as the very first transmission
for (int k = 0; k < NR_MAX_HARQ_PROCESSES; k++)
nr_mac->dl_harq_info[k].last_ndi = -1; // initialize to invalid value
for (int c = 0; c < 2; c++) {
// flush the soft buffers for all DL HARQ processes
// for each DL HARQ process, consider the next received transmission for a TB as the very first transmission
for (int k = 0; k < NR_MAX_HARQ_PROCESSES; k++) {
memset(&nr_mac->dl_harq_info[k][c], 0, sizeof(NR_UE_DL_HARQ_STATUS_t));
nr_mac->dl_harq_info[k][c].last_ndi = -1; // initialize to invalid value
}
}
// release, if any, Temporary C-RNTI
nr_mac->ra.t_crnti = 0;
+1 -1
View File
@@ -1190,7 +1190,7 @@ void trigger_MAC_UE_RA(NR_UE_MAC_INST_t *mac, dci_pdu_rel15_t *pdcch_order)
void prepare_msg4_msgb_feedback(NR_UE_MAC_INST_t *mac, int pid, int ack_nack)
{
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[pid];
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[pid][0]; // single cw for MSG4
int sched_slot = current_harq->ul_slot;
int sched_frame = current_harq->ul_frame;
PUCCH_sched_t pucch = {.n_CCE = current_harq->n_CCE,
@@ -218,9 +218,11 @@ int get_sum_delta_pucch(NR_UE_MAC_INST_t *mac, int slot, frame_t frame)
const int num_dl_harq = get_nrofHARQ_ProcessesForPDSCH(&mac->sc_info);
for (int i = 0; i < num_dl_harq; i++) {
if (mac->dl_harq_info[i].active && mac->dl_harq_info[i].ul_slot == slot && mac->dl_harq_info[i].ul_frame == frame) {
delta_tpc_sum += mac->dl_harq_info[i].delta_pucch;
mac->dl_harq_info[i].delta_pucch = 0;
for (int c = 0; c < 2; c++) {
if (mac->dl_harq_info[i][c].active && mac->dl_harq_info[i][c].ul_slot == slot && mac->dl_harq_info[i][c].ul_frame == frame) {
delta_tpc_sum += mac->dl_harq_info[i][c].delta_pucch;
mac->dl_harq_info[i][c].delta_pucch = 0;
}
}
}
return delta_tpc_sum;
+258 -213
View File
@@ -435,6 +435,7 @@ int8_t nr_ue_process_dci_freq_dom_resource_assignment(nfapi_nr_ue_pusch_pdu_t *p
static void set_harq_status(NR_UE_MAC_INST_t *mac,
uint8_t pucch_id,
uint8_t harq_id,
int cw_id,
int8_t delta_pucch,
uint16_t data_toul_fb,
uint8_t dai,
@@ -443,7 +444,7 @@ static void set_harq_status(NR_UE_MAC_INST_t *mac,
frame_t frame,
int slot)
{
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[harq_id];
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[harq_id][cw_id];
current_harq->active = true;
current_harq->ack_received = false;
current_harq->pucch_resource_indicator = pucch_id;
@@ -469,13 +470,15 @@ static void set_harq_status(NR_UE_MAC_INST_t *mac,
// looking for other active HARQ processes with feedback in the same frame/slot
if (i == harq_id)
continue;
NR_UE_DL_HARQ_STATUS_t *harq = &mac->dl_harq_info[i];
if (harq->active &&
harq->ul_frame == current_harq->ul_frame &&
harq->ul_slot == current_harq->ul_slot) {
// highest_dai is the largest cumulative dai in the set of HARQ allocations for a given slot
if (harq->dai_cumul > highest_dai)
highest_dai = harq->dai_cumul - 1;
for (int c = 0; c < 2; c++) {
NR_UE_DL_HARQ_STATUS_t *harq = &mac->dl_harq_info[i][c];
if (harq->active &&
harq->ul_frame == current_harq->ul_frame &&
harq->ul_slot == current_harq->ul_slot) {
// highest_dai is the largest cumulative dai in the set of HARQ allocations for a given slot
if (harq->dai_cumul > highest_dai)
highest_dai = harq->dai_cumul - 1;
}
}
}
@@ -667,6 +670,99 @@ static int nr_ue_process_dci_ul_01(NR_UE_MAC_INST_t *mac,
return ret;
}
// Table 7.3.1.3-1 of 38.211
static int get_nl_for_cw(int Nl, int cw_idx)
{
AssertFatal(Nl >= 0 && Nl <= 8, "Invalid number of layers %d\n", Nl);
if (Nl < 5)
return Nl;
else {
if (cw_idx == 0)
return Nl / 2;
else
return Nl / 2 + Nl % 2;
}
}
static bool get_cw_info(NR_UE_DL_HARQ_STATUS_t *current_harq,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_pdu,
uint8_t pdu_type,
fapi_nr_dl_cw_info_t *cw_info,
int number_rbs,
int nb_re_dmrs,
int nb_rb_oh,
int rv,
int mcs,
int ndi,
int cw_idx)
{
uint8_t Nl = 0;
for (int i = 0; i < 12; i++) { // max 12 ports
if ((dlsch_pdu->dmrs_ports >> i) & 0x01)
Nl += 1;
}
cw_info->mcs = mcs;
/* RV for transport block */
cw_info->rv = rv;
/* NDI for transport block*/
if (pdu_type == FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH || pdu_type == FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH || ndi != current_harq->last_ndi) {
// new data
cw_info->new_data_indicator = true;
current_harq->R = 0;
current_harq->TBS = 0;
} else
cw_info->new_data_indicator = false;
if (pdu_type != FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH && pdu_type != FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH) {
current_harq->last_ndi = ndi;
if (cw_info->new_data_indicator)
current_harq->round = 0;
else
current_harq->round++;
}
cw_info->qamModOrder = nr_get_Qm_dl(cw_info->mcs, dlsch_pdu->mcs_table);
if (cw_info->qamModOrder == 0) {
LOG_W(NR_MAC, "Invalid code rate or Mod order, likely due to unexpected DL DCI\n");
return false;
}
cw_info->Nl = get_nl_for_cw(Nl, cw_idx);
int R = nr_get_code_rate_dl(cw_info->mcs, dlsch_pdu->mcs_table);
if (R > 0) {
cw_info->targetCodeRate = R;
cw_info->TBS = nr_compute_tbs(cw_info->qamModOrder,
R,
number_rbs,
dlsch_pdu->number_symbols,
nb_re_dmrs * get_num_dmrs(dlsch_pdu->dlDmrsSymbPos),
nb_rb_oh,
0,
cw_info->Nl);
// storing for possible retransmissions
if (!cw_info->new_data_indicator && current_harq->TBS != cw_info->TBS) {
LOG_W(NR_MAC,
"NDI indicates re-transmission but computed TBS %d doesn't match with what previously stored %d\n",
cw_info->TBS,
current_harq->TBS);
cw_info->new_data_indicator = true; // treated as new data
}
current_harq->R = cw_info->targetCodeRate;
current_harq->TBS = cw_info->TBS;
}
else {
cw_info->targetCodeRate = current_harq->R;
cw_info->TBS = current_harq->TBS;
}
if (cw_info->TBS == 0) {
LOG_E(MAC, "Invalid TBS = 0. Probably caused by missed detection of DCI\n");
return false;
}
cw_info->ldpcBaseGraph = get_BG(cw_info->TBS, cw_info->targetCodeRate);
return true;
}
static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
frame_t frame,
int slot,
@@ -789,7 +885,7 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
0,
dci->frequency_domain_assignment)
< 0) {
LOG_W(MAC, "[%d.%d] Invalid frequency_domain_assignment. Possibly due to false DCI. Ignoring DCI!\n", frame, slot);
LOG_W(NR_MAC, "[%d.%d] Invalid frequency_domain_assignment. Possibly due to false DCI. Ignoring DCI!\n", frame, slot);
return -1;
}
@@ -873,76 +969,28 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
NULL, // SPS not implemented,
false, // as above
NULL); // MCS-C-RNTI not implemented
/* MCS */
dlsch_pdu->mcs = dci->mcs;
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[dci->harq_pid.val];
/* NDI (only if CRC scrambled by C-RNTI or CS-RNTI or new-RNTI or TC-RNTI)*/
if (dl_conf_req->pdu_type == FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH ||
dl_conf_req->pdu_type == FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH ||
dci->ndi != current_harq->last_ndi) {
// new data
dlsch_pdu->new_data_indicator = true;
current_harq->R = 0;
current_harq->TBS = 0;
} else {
dlsch_pdu->new_data_indicator = false;
}
if (dl_conf_req->pdu_type != FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH &&
dl_conf_req->pdu_type != FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH) {
current_harq->last_ndi = dci->ndi;
}
dlsch_pdu->qamModOrder = nr_get_Qm_dl(dlsch_pdu->mcs, dlsch_pdu->mcs_table);
if (dlsch_pdu->qamModOrder == 0) {
LOG_W(MAC, "Invalid code rate or Mod order, likely due to unexpected DL DCI.\n");
int nb_re_dmrs = ((dlsch_pdu->dmrsConfigType == NFAPI_NR_DMRS_TYPE1) ? 6 : 4) * dlsch_pdu->n_dmrs_cdm_groups;
int nb_rb_oh = mac->sc_info.xOverhead_PDSCH ? nb_rb_oh = 6 * (1 + *mac->sc_info.xOverhead_PDSCH) : 0;
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[dci->harq_pid.val][0];
if (!get_cw_info(current_harq,
dlsch_pdu,
dl_conf_req->pdu_type,
&dlsch_pdu->cw_info[0],
dlsch_pdu->number_rbs,
nb_re_dmrs,
nb_rb_oh,
dci->rv,
dci->mcs,
dci->ndi,
0))
return -1;
}
int R = nr_get_code_rate_dl(dlsch_pdu->mcs, dlsch_pdu->mcs_table);
if (R > 0) {
dlsch_pdu->targetCodeRate = R;
int nb_rb_oh;
if (mac->sc_info.xOverhead_PDSCH)
nb_rb_oh = 6 * (1 + *mac->sc_info.xOverhead_PDSCH);
else
nb_rb_oh = 0;
int nb_re_dmrs = ((dlsch_pdu->dmrsConfigType == NFAPI_NR_DMRS_TYPE1) ? 6 : 4) * dlsch_pdu->n_dmrs_cdm_groups;
dlsch_pdu->TBS = nr_compute_tbs(dlsch_pdu->qamModOrder,
R,
dlsch_pdu->number_rbs,
dlsch_pdu->number_symbols,
nb_re_dmrs * get_num_dmrs(dlsch_pdu->dlDmrsSymbPos),
nb_rb_oh,
0,
1);
// storing for possible retransmissions
current_harq->R = dlsch_pdu->targetCodeRate;
if (!dlsch_pdu->new_data_indicator && current_harq->TBS != dlsch_pdu->TBS) {
LOG_W(NR_MAC, "NDI indicates re-transmission but computed TBS %d doesn't match with what previously stored %d\n",
dlsch_pdu->TBS, current_harq->TBS);
dlsch_pdu->new_data_indicator = true; // treated as new data
}
current_harq->TBS = dlsch_pdu->TBS;
}
else {
dlsch_pdu->targetCodeRate = current_harq->R;
dlsch_pdu->TBS = current_harq->TBS;
}
dlsch_pdu->ldpcBaseGraph = get_BG(dlsch_pdu->TBS, dlsch_pdu->targetCodeRate);
if (dlsch_pdu->TBS == 0) {
LOG_E(MAC, "Invalid TBS = 0. Probably caused by missed detection of DCI\n");
return -1;
}
dlsch_pdu->n_codewords = 1;
int bw_tbslbrm = current_DL_BWP ? mac->sc_info.dl_bw_tbslbrm : dlsch_pdu->BWPSize;
dlsch_pdu->tbslbrm = nr_compute_tbslbrm(dlsch_pdu->mcs_table, bw_tbslbrm, 1);
/* RV (only if CRC scrambled by C-RNTI or CS-RNTI or new-RNTI or TC-RNTI)*/
dlsch_pdu->rv = dci->rv;
/* HARQ_PROCESS_NUMBER (only if CRC scrambled by C-RNTI or CS-RNTI or new-RNTI or TC-RNTI)*/
dlsch_pdu->harq_process_nbr = dci->harq_pid.val;
/* TB_SCALING (only if CRC scrambled by P-RNTI or RA-RNTI) */
@@ -1009,6 +1057,7 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
set_harq_status(mac,
dci->pucch_resource_indicator,
dci->harq_pid.val,
0,
tpc[dci->tpc],
feedback_ti,
dci->dai[0].val,
@@ -1017,10 +1066,6 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
frame,
slot);
}
if (dlsch_pdu->new_data_indicator)
current_harq->round = 0;
else
current_harq->round++;
if (current_harq->round < sizeofArray(mac->stats.dl.rounds))
mac->stats.dl.rounds[current_harq->round]++;
}
@@ -1043,9 +1088,9 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
"(nr_ue_procedures.c) vrb_to_prb_mapping=%d \n>>> mcs=%d\n>>> ndi=%d\n>>> rv=%d\n>>> harq_process_nbr=%d\n>>> dai=%d\n>>> "
"scaling_factor_S=%f\n>>> tpc_pucch=%d\n>>> pucch_res_ind=%d\n>>> pdsch_to_harq_feedback_time_ind=%d\n",
dlsch_pdu->vrb_to_prb_mapping,
dlsch_pdu->mcs,
dlsch_pdu->new_data_indicator,
dlsch_pdu->rv,
dlsch_pdu->cw_info[0].mcs,
dlsch_pdu->cw_info[0].new_data_indicator,
dlsch_pdu->cw_info[0].rv,
dlsch_pdu->harq_process_nbr,
dci->dai[0].val,
dlsch_pdu->scaling_factor_S,
@@ -1055,7 +1100,7 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
dlsch_pdu->k1_feedback = feedback_ti;
LOG_D(MAC, "(nr_ue_procedures.c) pdu_type=%d\n\n", dl_conf_req->pdu_type);
LOG_D(MAC, "(nr_ue_procedures.c) pdu_type=%d\n", dl_conf_req->pdu_type);
// the prepared dci is valid, we add it in the list
dl_config->number_pdus++;
@@ -1213,9 +1258,6 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
dlsch_pdu->dmrsConfigType = dl_dmrs_config->dmrs_Type == NULL ? NFAPI_NR_DMRS_TYPE1 : NFAPI_NR_DMRS_TYPE2;
/* TODO: fix number of DM-RS CDM groups without data according to subclause 5.1.6.2 of 3GPP TS 38.214,
using tables 7.3.1.2.2-1, 7.3.1.2.2-2, 7.3.1.2.2-3, 7.3.1.2.2-4 of 3GPP TS 38.212 */
dlsch_pdu->n_dmrs_cdm_groups = 1;
/* VRB_TO_PRB_MAPPING */
if ((pdsch_Config->resourceAllocation == 1) && (pdsch_Config->vrb_ToPRB_Interleaver != NULL))
dlsch_pdu->vrb_to_prb_mapping =
@@ -1226,28 +1268,7 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
dlsch_pdu->rate_matching_ind = dci->rate_matching_indicator.val;
/* ZP_CSI_RS_TRIGGER */
dlsch_pdu->zp_csi_rs_trigger = dci->zp_csi_rs_trigger.val;
/* MCS (for transport block 1)*/
dlsch_pdu->mcs = dci->mcs;
/* NDI (for transport block 1)*/
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[dci->harq_pid.val];
if (dci->ndi != current_harq->last_ndi) {
// new data
dlsch_pdu->new_data_indicator = true;
current_harq->R = 0;
current_harq->TBS = 0;
}
else {
dlsch_pdu->new_data_indicator = false;
}
current_harq->last_ndi = dci->ndi;
/* RV (for transport block 1)*/
dlsch_pdu->rv = dci->rv;
/* MCS (for transport block 2)*/
dlsch_pdu->tb2_mcs = dci->mcs2.val;
/* NDI (for transport block 2)*/
dlsch_pdu->tb2_new_data_indicator = dci->ndi2.val;
/* RV (for transport block 2)*/
dlsch_pdu->tb2_rv = dci->rv2.val;
/* HARQ_PROCESS_NUMBER */
dlsch_pdu->harq_process_nbr = dci->harq_pid.val;
/* TPC_PUCCH */
@@ -1276,13 +1297,32 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
return -1;
}
NR_UE_ServingCell_Info_t *sc_info = &mac->sc_info;
int nb_rb_oh;
if (sc_info->xOverhead_PDSCH)
nb_rb_oh = 6 * (1 + *sc_info->xOverhead_PDSCH);
else
nb_rb_oh = 0;
/* ANTENNA_PORTS */
uint8_t n_codewords = 1; // FIXME!!!
long *max_length = dl_dmrs_config->maxLength;
long *dmrs_type = dl_dmrs_config->dmrs_Type;
// In case the higher layer parameter maxNrofCodeWordsScheduledByDCI indicates that two codeword transmission is enabled,
// then one of the two transport blocks is disabled by DCI format 1_1
// if IMCS = 26 and if rvid = 1 for the corresponding transport block
bool cw0 = true;
bool cw1 = true;
if (pdsch_Config->maxNrofCodeWordsScheduledByDCI
&& *pdsch_Config->maxNrofCodeWordsScheduledByDCI == NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2) {
cw0 = dci->rv != 1 || dci->mcs != 26;
cw1 = dci->rv2.val != 1 || dci->mcs2.val != 26;
} else
cw1 = false;
dlsch_pdu->n_front_load_symb = 1; // default value
set_antenna_port_parameters(dlsch_pdu, n_codewords, max_length, dmrs_type, dci->antenna_ports.val);
set_antenna_port_parameters(dlsch_pdu, cw0 + cw1, max_length, dmrs_type, dci->antenna_ports.val);
int nb_re_dmrs = ((dmrs_type == NULL) ? 6 : 4) * dlsch_pdu->n_dmrs_cdm_groups;
/* dmrs symbol positions*/
dlsch_pdu->dlDmrsSymbPos = fill_dmrs_mask(pdsch_Config,
@@ -1293,6 +1333,105 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
tda_info.mapping_type,
dlsch_pdu->n_front_load_symb);
dlsch_pdu->mcs_table = get_dlsch_mcs_table(NR_DL_DCI_FORMAT_1_1,
rnti_type,
dci_ind->ss_type,
pdsch_Config ? pdsch_Config->mcs_Table : NULL,
NULL, // SPS not implemented,
false, // as above
NULL); // MCS-C-RNTI not implemented
/* PDSCH_TO_HARQ_FEEDBACK_TIME_IND */
// according to TS 38.213 Table 9.2.3-1
const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing);
uint16_t feedback_ti = 0;
const int tpc[] = {-1, 0, 1, 3};
if (!get_FeedbackDisabled(mac->sc_info.downlinkHARQ_FeedbackDisabled_r17, dci->harq_pid.val)) {
feedback_ti = pucch_Config->dl_DataToUL_ACK->list.array[dci->pdsch_to_harq_feedback_timing_indicator.val][0] + ntn_ue_koffset;
AssertFatal(feedback_ti >= GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing),
"PDSCH to HARQ feedback time (%d) needs to be higher than DURATION_RX_TO_TX (%ld). Min feedback time set in config "
"file (min_rxtxtime).\n",
feedback_ti,
GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing));
}
int number_rbs;
if (dlsch_pdu->resource_alloc == 1) {
number_rbs = dlsch_pdu->number_rbs;
} else {
uint32_t temp_bitmap[9];
memcpy(temp_bitmap, dlsch_pdu->rb_bitmap, 36);
number_rbs = count_bits(temp_bitmap, 9);
}
int cw_idx = 0;
NR_UE_DL_HARQ_STATUS_t *current_harq = NULL;
if (cw0) {
current_harq = &mac->dl_harq_info[dci->harq_pid.val][0];
if (get_cw_info(current_harq,
dlsch_pdu,
dl_conf_req->pdu_type,
&dlsch_pdu->cw_info[0],
number_rbs,
nb_re_dmrs,
nb_rb_oh,
dci->rv,
dci->mcs,
dci->ndi,
cw_idx)) {
if (current_harq->round < sizeofArray(mac->stats.dl.rounds))
mac->stats.dl.rounds[current_harq->round]++;
// set the harq status at MAC for feedback
set_harq_status(mac,
dci->pucch_resource_indicator,
dci->harq_pid.val,
0,
tpc[dci->tpc],
feedback_ti,
dci->dai[0].val,
dci_ind->n_CCE,
dci_ind->N_CCE,
frame,
slot);
cw_idx++;
} else
return -1;
}
if (cw1) {
current_harq = &mac->dl_harq_info[dci->harq_pid.val][cw_idx];
if (get_cw_info(current_harq,
dlsch_pdu,
dl_conf_req->pdu_type,
&dlsch_pdu->cw_info[1],
number_rbs,
nb_re_dmrs,
nb_rb_oh,
dci->rv2.val,
dci->mcs2.val,
dci->ndi2.val,
cw_idx)) {
if (current_harq->round < sizeofArray(mac->stats.dl.rounds))
mac->stats.dl.rounds[current_harq->round]++;
// set the harq status at MAC for feedback
set_harq_status(mac,
dci->pucch_resource_indicator,
dci->harq_pid.val,
1,
tpc[dci->tpc],
feedback_ti,
dci->dai[0].val,
dci_ind->n_CCE,
dci_ind->N_CCE,
frame,
slot);
cw_idx++;
} else
return -1;
}
dlsch_pdu->n_codewords = cw_idx;
/* TCI */
if (dl_conf_req->dci_config_pdu.dci_config_rel15.coreset.tci_present_in_dci == 1) {
// 0 bit if higher layer parameter tci-PresentInDCI is not enabled
@@ -1311,107 +1450,9 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
/* DMRS_SEQ_INI */
// FIXME!!!
/* PDSCH_TO_HARQ_FEEDBACK_TIME_IND */
// according to TS 38.213 Table 9.2.3-1
const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing);
uint16_t feedback_ti = 0;
if (!get_FeedbackDisabled(mac->sc_info.downlinkHARQ_FeedbackDisabled_r17, dci->harq_pid.val)) {
feedback_ti = pucch_Config->dl_DataToUL_ACK->list.array[dci->pdsch_to_harq_feedback_timing_indicator.val][0] + ntn_ue_koffset;
AssertFatal(feedback_ti >= GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing),
"PDSCH to HARQ feedback time (%d) needs to be higher than DURATION_RX_TO_TX (%ld). Min feedback time set in config "
"file (min_rxtxtime).\n",
feedback_ti,
GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing));
// set the harq status at MAC for feedback
const int tpc[] = {-1, 0, 1, 3};
set_harq_status(mac,
dci->pucch_resource_indicator,
dci->harq_pid.val,
tpc[dci->tpc],
feedback_ti,
dci->dai[0].val,
dci_ind->n_CCE,
dci_ind->N_CCE,
frame,
slot);
}
if (dlsch_pdu->new_data_indicator)
current_harq->round = 0;
else
current_harq->round++;
if (current_harq->round < sizeofArray(mac->stats.dl.rounds))
mac->stats.dl.rounds[current_harq->round]++;
// send the ack/nack slot number to phy to indicate tx thread to wait for DLSCH decoding
dlsch_pdu->k1_feedback = feedback_ti;
dlsch_pdu->mcs_table = get_dlsch_mcs_table(NR_DL_DCI_FORMAT_1_1,
rnti_type,
dci_ind->ss_type,
pdsch_Config ? pdsch_Config->mcs_Table : NULL,
NULL, // SPS not implemented,
false, // as above
NULL); // MCS-C-RNTI not implemented
dlsch_pdu->qamModOrder = nr_get_Qm_dl(dlsch_pdu->mcs, dlsch_pdu->mcs_table);
if (dlsch_pdu->qamModOrder == 0) {
LOG_W(MAC, "Invalid code rate or Mod order, likely due to unexpected DL DCI.\n");
return -1;
}
uint8_t Nl = 0;
for (int i = 0; i < 12; i++) { // max 12 ports
if ((dlsch_pdu->dmrs_ports >> i) & 0x01)
Nl += 1;
}
NR_UE_ServingCell_Info_t *sc_info = &mac->sc_info;
int nb_rb_oh;
if (sc_info->xOverhead_PDSCH)
nb_rb_oh = 6 * (1 + *sc_info->xOverhead_PDSCH);
else
nb_rb_oh = 0;
int nb_re_dmrs = ((dmrs_type == NULL) ? 6 : 4) * dlsch_pdu->n_dmrs_cdm_groups;
int number_rbs;
if (dlsch_pdu->resource_alloc == 1) {
number_rbs = dlsch_pdu->number_rbs;
} else {
uint32_t temp_bitmap[9];
memcpy(temp_bitmap, dlsch_pdu->rb_bitmap, 36);
number_rbs = count_bits(temp_bitmap, 9);
}
int R = nr_get_code_rate_dl(dlsch_pdu->mcs, dlsch_pdu->mcs_table);
if (R > 0) {
dlsch_pdu->targetCodeRate = R;
dlsch_pdu->TBS = nr_compute_tbs(dlsch_pdu->qamModOrder,
R,
number_rbs,
dlsch_pdu->number_symbols,
nb_re_dmrs * get_num_dmrs(dlsch_pdu->dlDmrsSymbPos),
nb_rb_oh,
0,
Nl);
// storing for possible retransmissions
if (!dlsch_pdu->new_data_indicator && current_harq->TBS != dlsch_pdu->TBS) {
LOG_W(NR_MAC, "NDI indicates re-transmission but computed TBS %d doesn't match with what previously stored %d\n",
dlsch_pdu->TBS, current_harq->TBS);
dlsch_pdu->new_data_indicator = true; // treated as new data
}
current_harq->R = dlsch_pdu->targetCodeRate;
current_harq->TBS = dlsch_pdu->TBS;
}
else {
dlsch_pdu->targetCodeRate = current_harq->R;
dlsch_pdu->TBS = current_harq->TBS;
}
dlsch_pdu->ldpcBaseGraph = get_BG(dlsch_pdu->TBS, dlsch_pdu->targetCodeRate);
if (dlsch_pdu->TBS == 0) {
LOG_E(MAC, "Invalid TBS = 0. Probably caused by missed detection of DCI\n");
return -1;
}
// TBS_LBRM according to section 5.4.2.1 of 38.212
int max_mimo_layers = 0;
if (sc_info->maxMIMO_Layers_PDSCH)
@@ -1424,10 +1465,11 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
/*PTRS configuration */
dlsch_pdu->pduBitmap = 0;
if (dl_dmrs_config->phaseTrackingRS != NULL) {
AssertFatal(cw_idx == 1, "Cannot handle PTRS with 2 codewords\n");
bool valid_ptrs_setup =
set_dl_ptrs_values(dl_dmrs_config->phaseTrackingRS->choice.setup,
number_rbs,
dlsch_pdu->mcs,
dlsch_pdu->cw_info[0].mcs,
dlsch_pdu->mcs_table,
&dlsch_pdu->PTRSFreqDensity,
&dlsch_pdu->PTRSTimeDensity,
@@ -2329,7 +2371,8 @@ bool get_downlink_ack(NR_UE_MAC_INST_t *mac, frame_t frame, int slot, PUCCH_sche
if (current_DL_BWP
&& current_DL_BWP->pdsch_Config
&& current_DL_BWP->pdsch_Config->maxNrofCodeWordsScheduledByDCI
&& current_DL_BWP->pdsch_Config->maxNrofCodeWordsScheduledByDCI[0] == 2) {
&& *current_DL_BWP->pdsch_Config->maxNrofCodeWordsScheduledByDCI
== NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2) {
two_transport_blocks = true;
number_of_code_word = 2;
}
@@ -2339,11 +2382,8 @@ bool get_downlink_ack(NR_UE_MAC_INST_t *mac, frame_t frame, int slot, PUCCH_sche
/* look for dl acknowledgment which should be done on current uplink slot */
for (int code_word = 0; code_word < number_of_code_word; code_word++) {
for (int dl_harq_pid = 0; dl_harq_pid < num_dl_harq; dl_harq_pid++) {
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[dl_harq_pid];
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[dl_harq_pid][code_word];
if (current_harq->active) {
LOG_D(PHY, "HARQ pid %d is active for %d.%d\n",
dl_harq_pid, current_harq->ul_frame, current_harq->ul_slot);
@@ -2925,6 +2965,11 @@ static nfapi_nr_ue_csi_payload_t get_csirs_RI_PMI_CQI_payload(NR_UE_MAC_INST_t *
int pmi_x1_bitlen = csi_report->csi_meas_bitlen.pmi_x1_bitlen[mac->csirs_measurements.ri];
int pmi_x2_bitlen = csi_report->csi_meas_bitlen.pmi_x2_bitlen[mac->csirs_measurements.ri];
int cqi_bitlen = csi_report->csi_meas_bitlen.cqi_bitlen[mac->csirs_measurements.ri];
/* For ranks with two transport blocks, the report carries one
* wideband CQI per codeword. Until separate per-codeword estimates
* are available, report the measured wideband CQI for both. */
const uint16_t cqi = mac->csirs_measurements.cqi & 0x0f;
const uint16_t cqi_payload = cqi_bitlen > 4 ? (cqi << 4) | cqi : cqi;
int padding_bitlen = 0;
// TODO: Improvements will be needed to cri_bitlen>0 and pmi_x1_bitlen>0
if (mapping_type == ON_PUSCH) {
@@ -2932,7 +2977,7 @@ static nfapi_nr_ue_csi_payload_t get_csirs_RI_PMI_CQI_payload(NR_UE_MAC_INST_t *
p2_bits = pmi_x1_bitlen + pmi_x2_bitlen;
temp_payload_1 = (0/*mac->csi_measurements.cri*/ << (cqi_bitlen + ri_bitlen)) |
(mac->csirs_measurements.ri << cqi_bitlen) |
(mac->csirs_measurements.cqi);
cqi_payload;
temp_payload_2 = (mac->csirs_measurements.i1 << pmi_x2_bitlen) |
mac->csirs_measurements.i2;
}
@@ -2943,7 +2988,7 @@ static nfapi_nr_ue_csi_payload_t get_csirs_RI_PMI_CQI_payload(NR_UE_MAC_INST_t *
(mac->csirs_measurements.ri << (cqi_bitlen + pmi_x2_bitlen + pmi_x1_bitlen + padding_bitlen)) |
(mac->csirs_measurements.i1 << (cqi_bitlen + pmi_x2_bitlen)) |
(mac->csirs_measurements.i2 << (cqi_bitlen)) |
(mac->csirs_measurements.cqi);
cqi_payload;
}
temp_payload_1 = reverse_bits(temp_payload_1, p1_bits);
+38 -14
View File
@@ -116,15 +116,22 @@ static void trigger_regular_bsr(NR_UE_MAC_INST_t *mac, NR_LogicalChannelIdentity
nr_timer_stop(&mac->scheduling_info.sr_DelayTimer);
}
static void flush_harq_buffers(NR_UE_MAC_INST_t *mac)
{
for (int k = 0; k < NR_MAX_HARQ_PROCESSES; k++) {
for (int c = 0; c < 2; c++) {
memset(&mac->dl_harq_info[k][c], 0, sizeof(NR_UE_DL_HARQ_STATUS_t));
mac->dl_harq_info[k][c].last_ndi = -1; // initialize to invalid value
}
memset(&mac->ul_harq_info[k], 0, sizeof(*mac->ul_harq_info));
mac->ul_harq_info[k].last_ndi = -1; // initialize to invalid value
}
}
void handle_time_alignment_timer_expired(NR_UE_MAC_INST_t *mac)
{
// flush all HARQ buffers for all Serving Cells
for (int k = 0; k < NR_MAX_HARQ_PROCESSES; k++) {
memset(&mac->dl_harq_info[k], 0, sizeof(*mac->dl_harq_info));
memset(&mac->ul_harq_info[k], 0, sizeof(*mac->ul_harq_info));
mac->dl_harq_info[k].last_ndi = -1; // initialize to invalid value
mac->ul_harq_info[k].last_ndi = -1; // initialize to invalid value
}
flush_harq_buffers(mac);
// release PUCCH for all Serving Cells;
// release SRS for all Serving Cells;
release_PUCCH_SRS(mac);
@@ -142,12 +149,7 @@ void handle_time_alignment_timer_expired(NR_UE_MAC_INST_t *mac)
void handle_ulsync_loss(NR_UE_MAC_INST_t *mac)
{
// flush all HARQ buffers for all Serving Cells
for (int k = 0; k < NR_MAX_HARQ_PROCESSES; k++) {
memset(&mac->dl_harq_info[k], 0, sizeof(*mac->dl_harq_info));
memset(&mac->ul_harq_info[k], 0, sizeof(*mac->ul_harq_info));
mac->dl_harq_info[k].last_ndi = -1; // initialize to invalid value
mac->ul_harq_info[k].last_ndi = -1; // initialize to invalid value
}
flush_harq_buffers(mac);
// clear any configured downlink assignments and uplink grants;
if (mac->dl_config_request)
memset(mac->dl_config_request, 0, sizeof(*mac->dl_config_request));
@@ -1152,15 +1154,36 @@ static NR_CSI_ResourceConfigId_t find_CSI_resourceconfig(NR_CSI_MeasConfig_t *cs
return -1; // not found any CSI-resource in current DL BWP associated with this CSI-RS ID
}
static uint8_t set_csirs_measurement_bitmap(NR_CSI_MeasConfig_t *csi_measconfig, NR_CSI_ResourceConfigId_t csi_res_id)
static uint8_t set_csirs_measurement_bitmap(const NR_UE_MAC_INST_t *mac,
NR_CSI_MeasConfig_t *csi_measconfig,
NR_CSI_ResourceConfigId_t csi_res_id,
fapi_nr_dl_config_csirs_pdu_rel15_t *csirs_config_pdu)
{
uint8_t meas_bitmap = 0;
csirs_config_pdu->codebook_n1 = 0;
csirs_config_pdu->codebook_n2 = 0;
csirs_config_pdu->codebook_mode = 0;
if (csi_res_id > NR_maxNrofCSI_ResourceConfigurations)
return meas_bitmap; // CSI-RS for tracking
for(int i = 0; i < csi_measconfig->csi_ReportConfigToAddModList->list.count; i++) {
NR_CSI_ReportConfig_t *report_config = csi_measconfig->csi_ReportConfigToAddModList->list.array[i];
if(report_config->resourcesForChannelMeasurement != csi_res_id)
continue;
const nr_csi_report_t *report = &mac->csi_report_template[i];
if (report->reportConfigId == report_config->reportConfigId && report->N1 > 0 && report->N2 > 0) {
if (csirs_config_pdu->codebook_n1 != 0
&& (csirs_config_pdu->codebook_n1 != report->N1
|| csirs_config_pdu->codebook_n2 != report->N2
|| csirs_config_pdu->codebook_mode != report->codebook_mode)) {
LOG_W(NR_MAC,
"CSI-RS resource %ld is associated with conflicting PMI codebooks; retaining the first one\n",
csi_res_id);
} else {
csirs_config_pdu->codebook_n1 = report->N1;
csirs_config_pdu->codebook_n2 = report->N2;
csirs_config_pdu->codebook_mode = report->codebook_mode;
}
}
// bit 0 RSRP bit 1 RI bit 2 LI bit 3 PMI bit 4 CQI bit 5 i1
switch (report_config->reportQuantity.present) {
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_PMI_CQI:
@@ -1218,7 +1241,8 @@ static void nr_schedule_csirs_reception(NR_UE_MAC_INST_t *mac, int frame, int sl
continue;
LOG_D(MAC,"Scheduling reception of CSI-RS in frame %d slot %d\n", frame, slot);
fapi_nr_dl_config_csirs_pdu_rel15_t *csirs_config_pdu = &dl_config->dl_config_list[dl_config->number_pdus].csirs_config_pdu.csirs_config_rel15;
csirs_config_pdu->measurement_bitmap = set_csirs_measurement_bitmap(csi_measconfig, csi_res_id);
csirs_config_pdu->measurement_bitmap =
set_csirs_measurement_bitmap(mac, csi_measconfig, csi_res_id, csirs_config_pdu);
csirs_config_pdu->subcarrier_spacing = mu;
csirs_config_pdu->cyclic_prefix = current_DL_BWP->cyclicprefix ? *current_DL_BWP->cyclicprefix : 0;
+49 -4
View File
@@ -186,6 +186,28 @@ int precoding_weigths_generation(nfapi_nr_pm_list_t *mat,
return pmiq;
}
static int precoding_weights_generation_4x1_high_rank(nfapi_nr_pm_list_t *mat,
int pmiq,
int layers)
{
const uint8_t i11_count = nr_type1_sp_4x1_i11_count(layers);
for (uint8_t i11 = 0; i11 < i11_count; i11++) {
for (uint8_t i2 = 0; i2 < 2; i2++) {
nfapi_nr_pm_pdu_t *pdu = &mat->pmi_pdu[pmiq];
pdu->pm_idx = pmiq + 1; // index zero is the identity matrix
pdu->numLayers = layers;
pdu->num_ant_ports = 8;
AssertFatal(nr_type1_sp_4x1_weights(layers, i11, 0, i2, pdu->weights),
"Invalid rank-%d Type-I single-panel PMI i11=%u i2=%u\n",
layers,
i11,
i2);
pmiq++;
}
}
return pmiq;
}
nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPorts_t antenna_ports)
{
int num_antenna_ports = antenna_ports.N1 * antenna_ports.N2 * antenna_ports.XP;
@@ -207,18 +229,33 @@ nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPort
int O2 = N2 > 1 ? 4 : 1; //Vertical beam oversampling (1 or 4)
int O1 = num_antenna_ports > 2 ? 4 : 1; //Horizontal beam oversampling (1 or 4)
int max_mimo_layers = (num_antenna_ports < NR_MAX_NB_LAYERS) ? num_antenna_ports : NR_MAX_NB_LAYERS;
AssertFatal(max_mimo_layers <= 4, "Max number of layers supported is 4\n");
const int max_mimo_layers = min(num_antenna_ports, NR_MAX_NB_LAYERS);
const int low_codebook_layers = min(max_mimo_layers, 4);
const bool high_rank_4x1 = num_antenna_ports == 8 && antenna_ports.XP == 2 && N1 == 4 && N2 == 1;
AssertFatal(num_antenna_ports < 16, "Max number of antenna ports supported is currently 16\n");
int K1 = 0, K2 = 0;
nfapi_nr_pm_list_t mat = {.num_pm_idx = 0};
for (int i = 0; i < max_mimo_layers; i++) {
memset(gNB->precoding_matrix_size, 0, sizeof(gNB->precoding_matrix_size));
for (int i = 0; i < low_codebook_layers; i++) {
get_K1_K2(N1, N2, &K1, &K2, i + 1);
int i2_size = i == 0 ? 4 : 2;
gNB->precoding_matrix_size[i] = i2_size * N1 * O1 * N2 * O2 * K1 * K2;
mat.num_pm_idx += gNB->precoding_matrix_size[i];
}
if (max_mimo_layers > 4 && !high_rank_4x1) {
LOG_W(NR_MAC,
"Rank-5--8 PMI is only implemented for the eight-port Type-I single-panel 4x1 geometry; "
"unsupported geometry XP=%d N1=%d N2=%d will use identity precoding\n",
antenna_ports.XP,
N1,
N2);
} else if (high_rank_4x1) {
for (int layers = 5; layers <= max_mimo_layers; layers++) {
gNB->precoding_matrix_size[layers - 1] = 2 * nr_type1_sp_4x1_i11_count(layers);
mat.num_pm_idx += gNB->precoding_matrix_size[layers - 1];
}
}
mat.pmi_pdu = malloc16(mat.num_pm_idx * sizeof(*mat.pmi_pdu));
AssertFatal(mat.pmi_pdu != NULL, "out of memory\n");
@@ -274,8 +311,16 @@ nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPort
}
int pmiq = 0;
for (int layers = 1; layers <= max_mimo_layers; layers++)
for (int layers = 1; layers <= low_codebook_layers; layers++)
pmiq = precoding_weigths_generation(&mat, pmiq, layers, N1, N2, O1, O2, num_antenna_ports, theta_n, v_lm);
if (high_rank_4x1)
for (int layers = 5; layers <= max_mimo_layers; layers++)
pmiq = precoding_weights_generation_4x1_high_rank(&mat, pmiq, layers);
AssertFatal(pmiq == mat.num_pm_idx,
"Generated %d precoding matrices but allocated %d\n",
pmiq,
mat.num_pm_idx);
return mat;
}
@@ -115,6 +115,51 @@ static void copy_ul_tti_req(nfapi_nr_ul_tti_request_t *to, nfapi_nr_ul_tti_reque
to->groups_list[i] = from->groups_list[i];
}
static bool has_incompatible_dl_harq(gNB_MAC_INST *gNB, int target_layers)
{
UE_iterator(gNB->UE_info.connected_ue_list, UE) {
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
for (int pid = 0; pid < NR_MAX_HARQ_PROCESSES; ++pid) {
const NR_UE_harq_t *harq = &sched_ctrl->harq_processes[pid];
const bool in_flight = harq->is_waiting || harq->round > 0;
if (in_flight && harq->sched_pdsch.nrOfLayers > target_layers) {
LOG_D(NR_MAC,
"[UE %04x] defer TX-mask downshift: HARQ PID %d still uses %d layers (target %d)\n",
UE->rnti,
pid,
harq->sched_pdsch.nrOfLayers,
target_layers);
return true;
}
}
}
return false;
}
/* A physical downshift cannot occur while a buffered retransmission needs
* more layers (and possibly a second codeword) than the target supports.
* The control thread has already made the target visible to new scheduling,
* so the old high-rank HARQ set can only drain from this point onward. */
static void apply_pending_tx_mask_if_harq_safe(gNB_MAC_INST *gNB)
{
nr_antenna_state_t *state = gNB->antenna_state;
uint8_t target_mask = 0;
if (!nr_get_pending_tx_antenna_bitmask(state, &target_mask))
return;
int target_layers = 0;
for (uint8_t mask = target_mask; mask != 0; mask >>= 1)
target_layers += mask & 1u;
if (has_incompatible_dl_harq(gNB, target_layers))
return;
if (nr_apply_requested_tx_antenna_bitmask(state, target_mask))
LOG_I(NR_MAC,
"Applied HARQ-safe TX-mask downshift to 0x%02X (%d layers)\n",
target_mask,
target_layers);
}
void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slot, NR_Sched_Rsp_t *sched_info)
{
protocol_ctxt_t ctxt = {0};
@@ -125,6 +170,7 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slo
NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
NR_SCHED_LOCK(&gNB->sched_lock);
apply_pending_tx_mask_if_harq_safe(gNB);
int slots_frame = gNB->frame_structure.numb_slots_frame;
clear_beam_information(&gNB->beam_info, frame, slot, slots_frame);
+322 -362
View File
@@ -13,6 +13,9 @@
#include "LAYER2/NR_MAC_gNB/mac_proto.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
#include <stdlib.h>
#include <string.h>
/*TAG*/
#include "NR_TAG-Id.h"
@@ -348,6 +351,9 @@ void finish_nr_dl_harq(NR_UE_sched_ctrl_t *sched_ctrl, int harq_pid)
NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid];
harq->ndi ^= 1;
if (harq->sched_pdsch.nrOfLayers > 4)
harq->ndi2 ^= 1;
harq->pending_cw_mask = 0;
harq->round = 0;
add_tail_nr_list(&sched_ctrl->available_dl_harq, harq_pid);
@@ -404,6 +410,37 @@ bwp_info_t get_pdsch_bwp_start_size(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE)
return bwp_info;
}
static uint32_t compute_sched_pdsch_tbs(const NR_sched_pdsch_t *sched, uint16_t rb_size, uint8_t codeword)
{
const int cw_layers = nr_pdsch_layers_per_codeword(sched->nrOfLayers, codeword);
return nr_compute_tbs(sched->Qm,
sched->R,
rb_size,
sched->tda_info.nrOfSymbols,
sched->dmrs_parms.N_PRB_DMRS * sched->dmrs_parms.N_DMRS_SLOT,
0,
0,
cw_layers)
>> 3;
}
void update_sched_pdsch_codeword_tbs(NR_sched_pdsch_t *sched)
{
sched->tb_size = compute_sched_pdsch_tbs(sched, sched->rbSize, 0);
sched->tb_size2 = nr_pdsch_num_codewords(sched->nrOfLayers) == 2 ? compute_sched_pdsch_tbs(sched, sched->rbSize, 1) : 0;
}
static void fit_dual_codeword_tbs(NR_sched_pdsch_t *sched, uint32_t requested_bytes, uint16_t max_rb_size)
{
if (nr_pdsch_num_codewords(sched->nrOfLayers) == 1)
return;
update_sched_pdsch_codeword_tbs(sched);
while ((uint64_t)sched->tb_size + sched->tb_size2 < requested_bytes && sched->rbSize < max_rb_size) {
sched->rbSize++;
update_sched_pdsch_codeword_tbs(sched);
}
}
static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
post_process_pdsch_t *pp_pdsch,
int *n_rb_sched,
@@ -420,14 +457,23 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch;
int layers = get_dl_nrOfLayers(nr_mac, sched_ctrl, dl_bwp->dci_format);
int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
const int layers = new_sched.nrOfLayers;
// If the RI changed between current rtx and a previous transmission
// we need to verify if it is not decreased
// othwise it wouldn't be possible to transmit the same TBS
layers = (layers < new_sched.nrOfLayers) ? layers : new_sched.nrOfLayers;
pm_index = (layers < new_sched.nrOfLayers) ? pm_index : new_sched.pm_index;
/* HARQ retransmission must retain the original layer mapping, codeword
* count, and TBS. Recomputing it from the current RI/TX target can turn a
* rank-8 two-codeword HARQ process into a one-codeword transmission and
* silently discard TB2. Normally a staged mask downshift keeps enough
* physical chains active until this process drains; if that mechanism was
* bypassed, defer instead of emitting an invalid retransmission. */
if (nr_mac->antenna_state != NULL) {
const int active_tx = nr_get_active_tx_antenna_count(nr_mac->antenna_state);
if (active_tx < layers) {
LOG_W(NR_MAC,
"[UE %04x][%4d.%2d] defer DL HARQ PID %d: original rank %d exceeds %d active TX chains\n",
UE->rnti, frame, slot, current_harq_pid, layers, active_tx);
return false;
}
}
const int coresetid = sched_ctrl->coreset->controlResourceSetId;
const int tda = get_dl_tda(nr_mac, slot);
@@ -473,26 +519,43 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
* that we have enough resources */
NR_pdsch_dmrs_t temp_dmrs = get_dl_dmrs_params(scc, dl_bwp, &temp_tda, layers);
uint32_t new_tbs;
uint16_t new_rbSize;
bool success = nr_find_nb_rb(new_sched.Qm,
new_sched.R,
1, // no transform precoding for DL
layers,
temp_tda.nrOfSymbols,
temp_dmrs.N_PRB_DMRS * temp_dmrs.N_DMRS_SLOT,
new_sched.tb_size,
1, /* minimum of 1RB: need to find exact TBS, don't preclude any number */
bwp_size,
&new_tbs,
&new_rbSize);
uint16_t new_rbSize = 0;
if (nr_pdsch_num_codewords(layers) == 1) {
uint32_t new_tbs = 0;
bool success = nr_find_nb_rb(new_sched.Qm,
new_sched.R,
1, // no transform precoding for DL
layers,
temp_tda.nrOfSymbols,
temp_dmrs.N_PRB_DMRS * temp_dmrs.N_DMRS_SLOT,
new_sched.tb_size,
1, /* find an exact retransmission TBS */
bwp_size,
&new_tbs,
&new_rbSize);
if (!success || new_tbs != new_sched.tb_size)
new_rbSize = 0;
} else {
NR_sched_pdsch_t candidate = new_sched;
candidate.nrOfLayers = layers;
candidate.dmrs_parms = temp_dmrs;
candidate.tda_info = temp_tda;
for (uint16_t rb = 1; rb <= bwp_size; rb++) {
candidate.rbSize = rb;
update_sched_pdsch_codeword_tbs(&candidate);
if (candidate.tb_size == new_sched.tb_size && candidate.tb_size2 == new_sched.tb_size2) {
new_rbSize = rb;
break;
}
}
}
if (!success || new_tbs != new_sched.tb_size) {
if (new_rbSize == 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] allocation of DL retransmission failed: new TBS %d of new TDA does not match old TBS %d\n",
UE->rnti,
frame,
slot,
new_tbs,
0,
new_sched.tb_size);
return false; /* the maximum TBsize we might have is smaller than what we need */
}
@@ -505,13 +568,12 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
/* we can allocate it. Overwrite the time_domain_allocation, the number
* of RBs, and the new TB size. The rest is done below */
new_sched.tb_size = new_tbs;
new_sched.rbSize = new_rbSize;
new_sched.time_domain_allocation = tda;
new_sched.nrOfLayers = layers;
new_sched.pm_index = pm_index;
new_sched.dmrs_parms = temp_dmrs;
new_sched.tda_info = temp_tda;
update_sched_pdsch_codeword_tbs(&new_sched);
}
/* Find a free CCE */
@@ -538,6 +600,8 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
if (!get_FeedbackDisabled(UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, current_harq_pid)) {
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
alloc = nr_acknack_scheduling(nr_mac, UE, frame, slot, UE->UE_beam_index, r_pucch, 0);
/* DAI counts PDSCH assignments, not transport blocks. A two-codeword
* PDSCH uses one PUCCH allocation carrying two HARQ-ACK bits. */
if (alloc < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI retransmission\n", UE->rnti, frame, slot);
return false;
@@ -593,57 +657,8 @@ typedef struct UEsched_s {
float coef;
NR_UE_info_t * UE;
int selected_mcs;
bool mumimo_paired; /* nakaolab: already allocated as MU-MIMO partner; main loop must skip */
} UEsched_t;
/* nakaolab: cached MU-MIMO enable state.
* Read once via OAI_MU_MIMO env var. 0 = SU-MIMO (default), 1 = MU-MIMO co-scheduling. */
static int mumimo_enabled(void)
{
static int cached = -1;
if (cached < 0) {
const char *e = getenv("OAI_MU_MIMO");
cached = (e && atoi(e) > 0) ? 1 : 0;
if (cached)
LOG_W(NR_MAC, "[nakaolab] MU-MIMO downlink scheduling ENABLED (OAI_MU_MIMO=1)\n");
}
return cached;
}
/* nakaolab: orthogonal-PMI test for 4-port Type I rank-1 codebook (N1=2, N2=1).
*
* The 4 codewords sharing the same spatial beam (same pmi_x1) are constructed
* with mutually orthogonal co-phasings (theta_n in {1, j, -1, -j}):
* w_(x1, n) = (1/sqrt(4)) * [v_x1 ; j^n * v_x1]
* Pairwise inner product <w_(x1,a), w_(x1,b)> for any a != b is zero, so all
* 4 codewords with the same x1 are mutually orthogonal. Up to 4 UEs (one per
* x2 class) can be co-scheduled simultaneously.
*
* Two UEs with different x1 use different spatial beams. With OAI default
* oversampling (O1=4) the DFT vectors are not strictly orthogonal, so we
* conservatively don't pair across x1.
*
* OAI_MU_MIMO_RELAXED=1 accepts any rank-1 pair (used to validate the
* scheduler path on a symmetric rfsim channel where PMIs collapse). */
static bool pmi_is_orthogonal(const NR_UE_info_t *a, const NR_UE_info_t *b)
{
const struct CRI_RI_LI_PMI_CQI *ra = &a->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report;
const struct CRI_RI_LI_PMI_CQI *rb = &b->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report;
if (ra->ri != 0 || rb->ri != 0)
return false;
static int relaxed = -1;
if (relaxed < 0) {
const char *e = getenv("OAI_MU_MIMO_RELAXED");
relaxed = (e && atoi(e) > 0) ? 1 : 0;
}
if (relaxed)
return true;
if (ra->pmi_x1 != rb->pmi_x1)
return false;
/* Same beam, distinct co-phasing -> mutually orthogonal codewords. */
return ra->pmi_x2 != rb->pmi_x2;
}
static int comparator(const void *p, const void *q)
{
const UEsched_t *pp = p;
@@ -655,6 +670,24 @@ static int comparator(const void *p, const void *q)
return 0;
}
/* Keep latency bounded under multi-UE load by preventing the first PF-selected
* UE from consuming the complete contiguous PRB region. The ordinary OAI PF
* loop already supports several frequency-disjoint PDSCHs per slot, but a UE
* with an accumulated RLC burst can otherwise take every PRB and make the
* remaining UEs wait for later DL slots. This switch only divides resources
* when at least two eligible UEs remain; single-UE peak throughput is unchanged. */
static bool dl_fair_rb_cap_enabled(void)
{
static int enabled = -1;
if (enabled < 0) {
const char *setting = getenv("OAI_DL_FAIR_RB_CAP");
enabled = setting != NULL && atoi(setting) != 0;
if (enabled)
LOG_I(NR_MAC, "DL fair-RB cap enabled for multi-UE low-latency scheduling\n");
}
return enabled;
}
static void pf_dl(gNB_MAC_INST *mac,
post_process_pdsch_t *pp_pdsch,
NR_UE_info_t **UE_list,
@@ -740,7 +773,13 @@ static void pf_dl(gNB_MAC_INST *mac,
/* Calculate coeff */
const NR_bler_options_t *bo = &mac->dl_bler;
const int max_mcs_table = current_BWP->mcsTableIdx == 1 ? 27 : 28;
const int max_mcs = min(sched_ctrl->dl_max_mcs, max_mcs_table);
/* The experimental identity-precoded rank-8 path can make a Type-I
* codebook CQI pessimistic for the actual PDSCH precoder. Keep CQI as
* the default cap, but allow a bounded HARQ/BLER probe when explicitly
* requested. The configured min/max MCS still apply in either mode. */
const char *mcs_policy = getenv("OAI_DL_MCS_POLICY");
const bool bler_only = mcs_policy != NULL && strcmp(mcs_policy, "bler_only") == 0;
const int max_mcs = min(bler_only ? bo->max_mcs : sched_ctrl->dl_max_mcs, max_mcs_table);
int selected_mcs;
if (bo->harq_round_max == 1) {
int new_mcs = min(bo->max_mcs, max_mcs);
@@ -778,31 +817,12 @@ static void pf_dl(gNB_MAC_INST *mac,
qsort(UE_sched, numUE, sizeof(UEsched_t), comparator);
UEsched_t *iterator = UE_sched;
/* nakaolab: tag to mark UEs already paired in this pf_dl call. Increments each
* call so a UE's stored tag != current tag means "available for pairing". */
static uint64_t mumimo_pf_call_id = 0;
if (mumimo_enabled())
mumimo_pf_call_id++;
/* nakaolab: diagnostic — log numUE > 1 to understand MU-MIMO opportunity */
if (mumimo_enabled() && numUE >= 2) {
static int dump_count = 0;
if (dump_count++ < 20)
LOG_W(NR_MAC, "[nakaolab MU-MIMO] %4d.%2d numUE=%d (PF candidates)\n", frame, slot, numUE);
}
const int min_rbSize = 5;
/* Loop UE_sched to find max coeff and allocate transmission */
while (iterator->UE != NULL) {
/* nakaolab: skip UEs already allocated as a MU-MIMO partner of an earlier UE
* in this same pf_dl call (mumimo_pf_call_id matches). */
if (mumimo_enabled() &&
iterator->UE->UE_sched_ctrl.mumimo_paired_slot == (int64_t)mumimo_pf_call_id) {
iterator++;
continue;
}
NR_UE_sched_ctrl_t *sched_ctrl = &iterator->UE->UE_sched_ctrl;
const uint16_t rnti = iterator->UE->rnti;
@@ -831,6 +851,16 @@ static void pf_dl(gNB_MAC_INST *mac,
continue;
}
int eligible_ues_left = 0;
if (dl_fair_rb_cap_enabled()) {
for (UEsched_t *candidate = iterator; candidate->UE != NULL; candidate++) {
NR_UE_sched_ctrl_t *candidate_ctrl = &candidate->UE->UE_sched_ctrl;
if (nr_mac_ue_is_active(candidate->UE)
&& candidate_ctrl->available_dl_harq.head >= 0)
eligible_ues_left++;
}
}
/* MCS has been set above */
int tda = get_dl_tda(mac, slot);
AssertFatal(tda >= 0,"Unable to find PDSCH time domain allocation in list\n");
@@ -867,6 +897,15 @@ static void pf_dl(gNB_MAC_INST *mac,
iterator++;
continue;
}
if (eligible_ues_left > 1) {
const int fair_ues = min(remainUEs[beam.idx], eligible_ues_left);
if (fair_ues > 1) {
/* Recompute after every allocation: unused shares from a lightly
* loaded UE remain available to later UEs in this same slot. */
const int fair_rb_cap = (n_rb_sched[beam.idx] + fair_ues - 1) / fair_ues;
max_rbSize = min(max_rbSize, max(min_rbSize, fair_rb_cap));
}
}
int CCEIndex = get_cce_index(mac,
CC_id,
@@ -889,10 +928,13 @@ static void pf_dl(gNB_MAC_INST *mac,
/* Find PUCCH occasion: if it fails, undo CCE allocation (undoing PUCCH
* allocation after CCE alloc fail would be more complex) */
const int l = get_dl_nrOfLayers(mac, sched_ctrl, dl_bwp->dci_format);
int alloc = -1;
if (!get_FeedbackDisabled(iterator->UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, sched_ctrl->available_dl_harq.head)) {
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
alloc = nr_acknack_scheduling(mac, iterator->UE, frame, slot, iterator->UE->UE_beam_index, r_pucch, 0);
/* One PDSCH assignment has one DAI increment even when it carries two
* codewords; the UE multiplexes both ACK bits on this allocation. */
if (alloc < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI\n", rnti, frame, slot);
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
@@ -904,7 +946,6 @@ static void pf_dl(gNB_MAC_INST *mac,
sched_ctrl->cce_index = CCEIndex;
fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx);
int l = get_dl_nrOfLayers(mac, sched_ctrl, dl_bwp->dci_format);
NR_sched_pdsch_t sched_pdsch = {
// rbSize below
.rbStart = rbStart,
@@ -944,180 +985,9 @@ static void pf_dl(gNB_MAC_INST *mac,
max_rbSize,
&sched_pdsch.tb_size,
&sched_pdsch.rbSize);
/* nakaolab MU-MIMO: find up to MAX_MU_MIMO_PARTNERS orthogonal-PMI partner UEs
* that can ride on the same RB allocation. Each partner gets its own DCI/CCE,
* its own DMRS port (1000+layer_idx), and is forced to the leader's MCS/TBS.
* Up to 4 UEs total per slot (one per orthogonal x2 class). */
#define MAX_MU_MIMO_PARTNERS 3
NR_UE_info_t *partners[MAX_MU_MIMO_PARTNERS] = {NULL};
int partner_CCEs[MAX_MU_MIMO_PARTNERS] = {-1, -1, -1};
int partner_allocs[MAX_MU_MIMO_PARTNERS] = {-1, -1, -1};
int n_partners = 0;
/* nakaolab Phase 4: cap MU-MIMO group size to the active TX antenna count.
* When the antenna controller mutes some TX chains (e.g., 4->2 or 4->1), the
* effective number of orthogonal codewords in the codebook shrinks. Each
* co-scheduled UE consumes one spatial layer = one TX port. So:
* active_tx = 4 -> up to 4 UEs (3 partners), 4-way MU-MIMO
* active_tx = 2 -> up to 2 UEs (1 partner), 2-way MU-MIMO
* active_tx = 1 -> SU-MIMO only (0 partners) */
int max_partners_now = MAX_MU_MIMO_PARTNERS;
if (mac->antenna_state) {
const uint8_t tx_mask = nr_get_tx_antenna_bitmask(mac->antenna_state);
int active_tx = __builtin_popcount(tx_mask);
if (active_tx < 1) active_tx = 1;
if (active_tx - 1 < max_partners_now)
max_partners_now = active_tx - 1;
}
if (mumimo_enabled() && remainUEs[beam.idx] > 0 && max_partners_now > 0) {
const uint8_t leader_x2 = iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
uint8_t classes_used = 1 << leader_x2;
static int diag_count = 0;
bool log_diag = ((diag_count++ % 200) == 0);
if (log_diag) {
int n_cand = 0;
UE_iterator (UE_list, c) {
if (c != iterator->UE && nr_mac_ue_is_active(c) &&
c->UE_sched_ctrl.available_dl_harq.head >= 0)
n_cand++;
}
LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d ENTER partner-search after RNTI %04x leader_x2=%d; eligible_others=%d\n",
frame, slot, iterator->UE->rnti, leader_x2, n_cand);
}
/* Find up to max_partners_now partners, one per remaining x2 class.
* max_partners_now is capped by active TX antenna count (Phase 4). */
while (n_partners < max_partners_now && remainUEs[beam.idx] > 0) {
NR_UE_info_t *new_partner = NULL;
int new_CCE = -1;
int new_alloc = -1;
NR_beam_alloc_t new_pbeam = {0};
UE_iterator (UE_list, cand_ue) {
if (cand_ue == iterator->UE) continue;
if (cand_ue->UE_sched_ctrl.mumimo_paired_slot == (int64_t)mumimo_pf_call_id) continue;
/* Skip if already a partner of this leader. */
bool already_partner = false;
for (int i = 0; i < n_partners; i++)
if (partners[i] == cand_ue) { already_partner = true; break; }
if (already_partner) continue;
/* Skip if cand's class is already represented (leader or earlier partner). */
const uint8_t cand_x2 = cand_ue->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
if (classes_used & (1 << cand_x2)) continue;
if (!nr_mac_ue_is_active(cand_ue)) continue;
if (cand_ue->UE_sched_ctrl.available_dl_harq.head < 0) continue;
update_dlsch_buffer(pp_pdsch->frame, pp_pdsch->slot, cand_ue);
if (!dlsch_to_schedule(&cand_ue->UE_sched_ctrl)) continue;
if (!pmi_is_orthogonal(iterator->UE, cand_ue)) continue;
NR_beam_alloc_t pbeam = beam_allocation_procedure(&mac->beam_info, frame, slot,
cand_ue->UE_beam_index,
slots_per_frame);
if (pbeam.idx != beam.idx) {
reset_beam_status(&mac->beam_info, frame, slot, cand_ue->UE_beam_index,
slots_per_frame, pbeam.new_beam);
continue;
}
int p_CCE = get_cce_index(mac, CC_id, slot, cand_ue->rnti,
&cand_ue->UE_sched_ctrl.aggregation_level,
pbeam.idx,
cand_ue->UE_sched_ctrl.search_space,
cand_ue->UE_sched_ctrl.coreset,
&cand_ue->UE_sched_ctrl.sched_pdcch,
cand_ue->UE_sched_ctrl.pdcch_cl_adjust);
if (p_CCE < 0) {
reset_beam_status(&mac->beam_info, frame, slot, cand_ue->UE_beam_index,
slots_per_frame, pbeam.new_beam);
continue;
}
int p_alloc = -1;
if (!get_FeedbackDisabled(cand_ue->sc_info.downlinkHARQ_FeedbackDisabled_r17,
cand_ue->UE_sched_ctrl.available_dl_harq.head)) {
int p_pucch = nr_get_pucch_resource(cand_ue->UE_sched_ctrl.coreset,
cand_ue->current_UL_BWP.pucch_Config, p_CCE);
p_alloc = nr_acknack_scheduling(mac, cand_ue, frame, slot,
cand_ue->UE_beam_index, p_pucch, 0);
if (p_alloc < 0) {
reset_beam_status(&mac->beam_info, frame, slot, cand_ue->UE_beam_index,
slots_per_frame, pbeam.new_beam);
continue;
}
}
new_partner = cand_ue;
new_CCE = p_CCE;
new_alloc = p_alloc;
new_pbeam = pbeam;
break;
}
if (new_partner == NULL) break;
partners[n_partners] = new_partner;
partner_CCEs[n_partners] = new_CCE;
partner_allocs[n_partners] = new_alloc;
const uint8_t p_x2 = new_partner->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
classes_used |= 1 << p_x2;
n_partners++;
(void)new_pbeam; /* beam already taken via beam_allocation_procedure */
}
if (n_partners > 0) {
/* Compose pairing log: leader + partners */
char pair_str[256];
int off = snprintf(pair_str, sizeof(pair_str), "RNTI %04x (x2=%d)",
iterator->UE->rnti, leader_x2);
for (int i = 0; i < n_partners && off < (int)sizeof(pair_str) - 32; i++)
off += snprintf(pair_str + off, sizeof(pair_str) - off, " + RNTI %04x (x2=%d)",
partners[i]->rnti,
partners[i]->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2);
LOG_W(NR_MAC, "[nakaolab MU-MIMO] %4d.%2d %d-way pair %s on RBs [%d..%d]\n",
frame, slot, n_partners + 1, pair_str,
sched_pdsch.rbStart, sched_pdsch.rbStart + sched_pdsch.rbSize - 1);
/* MU-MIMO needs DMRS pilots from both CDM groups protected from data. */
sched_pdsch.dmrs_parms.numDmrsCdmGrpsNoData = 2;
}
}
fit_dual_codeword_tbs(&sched_pdsch, sched_ctrl->num_total_bytes + oh, max_rbSize);
post_process_dlsch(mac, pp_pdsch, iterator->UE, &sched_pdsch);
if (mumimo_enabled())
iterator->UE->UE_sched_ctrl.mumimo_paired_slot = (int64_t)mumimo_pf_call_id;
/* nakaolab: allocate each MU-MIMO partner on the same RBs.
* DMRS port assignment: leader = port 1000 (mask bit 0), partner_i = port
* 1001+i (mask bit i+1). For 4-way: 1000 (CDM group 0), 1001 (CDM group 0),
* 1002 (CDM group 1), 1003 (CDM group 1). */
for (int i = 0; i < n_partners; i++) {
NR_UE_info_t *p_ue = partners[i];
NR_UE_sched_ctrl_t *p_sched_ctrl = &p_ue->UE_sched_ctrl;
NR_UE_DL_BWP_t *p_dl_bwp = &p_ue->current_DL_BWP;
p_sched_ctrl->cce_index = partner_CCEs[i];
fill_pdcch_vrb_map(mac, CC_id, &p_sched_ctrl->sched_pdcch, partner_CCEs[i],
p_sched_ctrl->aggregation_level, beam.idx);
const int p_l = 1;
NR_sched_pdsch_t p_sched_pdsch = {
.rbStart = sched_pdsch.rbStart,
.rbSize = sched_pdsch.rbSize,
.mcs = sched_pdsch.mcs,
.R = sched_pdsch.R,
.Qm = sched_pdsch.Qm,
.tb_size = sched_pdsch.tb_size,
.dl_harq_pid = p_sched_ctrl->available_dl_harq.head,
.pucch_allocation = partner_allocs[i],
.pm_index = get_pm_index(mac, p_ue, p_dl_bwp->dci_format, p_l,
mac->radio_config.pdsch_AntennaPorts.XP),
.nrOfLayers = p_l,
.bwp_info = get_pdsch_bwp_start_size(mac, p_ue),
.dmrs_parms = get_dl_dmrs_params(scc, p_dl_bwp, &tda_info, p_l),
.time_domain_allocation = tda,
.tda_info = tda_info,
.action = NULL,
};
/* Partner i (i in 0..2) uses DMRS port 1001+i: mask bit (i+1). */
p_sched_pdsch.dmrs_parms.dmrs_ports_id = 1u << (i + 1);
p_sched_pdsch.dmrs_parms.numDmrsCdmGrpsNoData = 2;
post_process_dlsch(mac, pp_pdsch, p_ue, &p_sched_pdsch);
p_sched_ctrl->mumimo_paired_slot = (int64_t)mumimo_pf_call_id;
remainUEs[beam.idx]--;
}
/* transmissions: directly allocate */
n_rb_sched[beam.idx] -= sched_pdsch.rbSize;
@@ -1182,13 +1052,15 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
pdsch_pdu->SubcarrierSpacing = dl_bwp ? dl_bwp->scs : *scc->ssbSubcarrierSpacing;
pdsch_pdu->CyclicPrefix = dl_bwp && dl_bwp->cyclicprefix ? *dl_bwp->cyclicprefix : 0;
// Codeword information
pdsch_pdu->NrOfCodewords = 1;
pdsch_pdu->targetCodeRate[0] = sched_pdsch->R;
pdsch_pdu->qamModOrder[0] = sched_pdsch->Qm;
pdsch_pdu->mcsIndex[0] = sched_pdsch->mcs;
pdsch_pdu->mcsTable[0] = dl_bwp ? dl_bwp->mcsTableIdx : 0;
pdsch_pdu->rvIndex[0] = nr_get_rv(harq_round % 4);
pdsch_pdu->TBSize[0] = sched_pdsch->tb_size;
pdsch_pdu->NrOfCodewords = nr_pdsch_num_codewords(sched_pdsch->nrOfLayers);
for (int q = 0; q < pdsch_pdu->NrOfCodewords; q++) {
pdsch_pdu->targetCodeRate[q] = sched_pdsch->R;
pdsch_pdu->qamModOrder[q] = sched_pdsch->Qm;
pdsch_pdu->mcsIndex[q] = sched_pdsch->mcs;
pdsch_pdu->mcsTable[q] = dl_bwp ? dl_bwp->mcsTableIdx : 0;
pdsch_pdu->rvIndex[q] = nr_get_rv(harq_round % 4);
pdsch_pdu->TBSize[q] = q == 0 ? sched_pdsch->tb_size : sched_pdsch->tb_size2;
}
pdsch_pdu->dataScramblingId = pdsch_Config && pdsch_Config->dataScramblingIdentityPDSCH ? *pdsch_Config->dataScramblingIdentityPDSCH : *scc->physCellId;
pdsch_pdu->nrOfLayers = sched_pdsch->nrOfLayers;
pdsch_pdu->transmissionScheme = 0;
@@ -1200,7 +1072,7 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
pdsch_pdu->SCID = dmrs_parms->n_scid;
pdsch_pdu->dlDmrsScramblingId = dmrs_parms->scrambling_id;
pdsch_pdu->numDmrsCdmGrpsNoData = dmrs_parms->numDmrsCdmGrpsNoData;
pdsch_pdu->dmrsPorts = (1 << sched_pdsch->nrOfLayers) - 1; // FIXME with a better implementation
pdsch_pdu->dmrsPorts = dmrs_parms->dmrs_ports;
// Pdsch Allocation in frequency domain
pdsch_pdu->resourceAlloc = 1;
pdsch_pdu->rbStart = sched_pdsch->rbStart;
@@ -1237,6 +1109,105 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
return pdsch_pdu;
}
typedef struct {
int sdus;
int sdu_bytes;
} dlsch_fill_result_t;
static dlsch_fill_result_t fill_dlsch_transport_block(gNB_MAC_INST *mac,
NR_UE_info_t *UE,
uint8_t *tb,
uint32_t tbs,
frame_t frame,
slot_t slot,
bool include_ces)
{
const int module_id = mac->Mod_id;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
dlsch_fill_result_t result = {0};
uint8_t *buf = tb;
if (include_ces)
buf += nr_write_ce_dlsch_pdu(module_id, sched_ctrl, buf, 255, NULL);
DevAssert(buf < tb + tbs);
uint8_t *buf_end = tb + tbs;
start_meas(&mac->rlc_data_req);
if (sched_ctrl->num_total_bytes > 0) {
for (int i = 0; i < seq_arr_size(&sched_ctrl->lc_config); ++i) {
const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, i);
const int lcid = c->lcid;
if (sched_ctrl->rlc_status[lcid].bytes_in_buffer == 0)
continue;
tb_size_t pdu_siz[MAX_NUM_DATA_REQ];
int num = nr_mac_rlc_multi_data_req(module_id,
UE->rnti,
true,
lcid,
buf_end - buf,
(char *)buf,
pdu_siz,
sizeofArray(pdu_siz));
DevAssert(num <= sizeofArray(pdu_siz));
result.sdus += num;
int lcid_bytes = 0;
for (int j = 0; j < num; ++j) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *)buf;
header->R = 0;
header->F = 1;
header->LCID = lcid;
header->L = htons(pdu_siz[j]);
buf += pdu_siz[j] + sizeof(NR_MAC_SUBHEADER_LONG);
result.sdu_bytes += pdu_siz[j];
lcid_bytes += pdu_siz[j];
}
T(T_GNB_MAC_LCID_DL,
T_INT(UE->rnti),
T_INT(frame),
T_INT(slot),
T_INT(lcid),
T_INT(lcid_bytes),
T_INT(nr_rlc_tx_list_occupancy(UE->rnti, lcid)));
UE->mac_stats.dl.lc_bytes[lcid] += lcid_bytes;
}
} else if (get_softmodem_params()->phy_test || get_softmodem_params()->do_ra) {
LOG_D(NR_MAC, "Configuring DL_TX in %d.%d: TBS %d of random data\n", frame, slot, tbs);
if (buf_end - buf > sizeof(NR_MAC_SUBHEADER_LONG)) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *)buf;
header->R = 0;
header->F = 1;
header->LCID = DL_SCH_LCID_PADDING;
buf += sizeof(NR_MAC_SUBHEADER_LONG);
header->L = htons(buf_end - buf);
for (; ((intptr_t)buf) % 4; buf++)
*buf = lrand48() & 0xff;
for (; buf < buf_end - 3; buf += 4)
*(uint32_t *)buf = lrand48();
for (; buf < buf_end; buf++)
*buf = lrand48() & 0xff;
result.sdus++;
}
}
stop_meas(&mac->rlc_data_req);
if (buf_end - buf > 0) {
NR_MAC_SUBHEADER_FIXED *padding = (NR_MAC_SUBHEADER_FIXED *)buf;
padding->R = 0;
padding->LCID = DL_SCH_LCID_PADDING;
buf++;
memset(buf, 0, buf_end - buf);
}
return result;
}
static void fill_padding_only_transport_block(uint8_t *tb, uint32_t tbs)
{
DevAssert(tbs >= sizeof(NR_MAC_SUBHEADER_FIXED));
memset(tb, 0, tbs);
((NR_MAC_SUBHEADER_FIXED *)tb)->LCID = DL_SCH_LCID_PADDING;
}
void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE_info_t *UE, NR_sched_pdsch_t *sched_pdsch)
{
int CC_id = 0;
@@ -1253,6 +1224,7 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
/* POST processing */
const uint8_t nrOfLayers = sched_pdsch->nrOfLayers;
const uint32_t TBS = sched_pdsch->tb_size;
const uint32_t TBS2 = sched_pdsch->tb_size2;
int8_t current_harq_pid = sched_pdsch->dl_harq_pid;
if (current_harq_pid < 0) {
@@ -1276,6 +1248,19 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
NR_tda_info_t *tda_info = &sched_pdsch->tda_info;
NR_pdsch_dmrs_t *dmrs_parms = &sched_pdsch->dmrs_parms;
NR_UE_harq_t *harq = &sched_ctrl->harq_processes[current_harq_pid];
const int active_codewords = nr_pdsch_num_codewords(nrOfLayers);
if (harq->round == 0) {
harq->pending_cw_mask = (1U << active_codewords) - 1;
} else if (active_codewords == 2) {
DevAssert(harq->pending_cw_mask > 0 && harq->pending_cw_mask < 4);
/* A codeword already ACKed must not be sent again with its old NDI:
* the UE would otherwise deliver its MAC SDUs twice. Give its padding
* replacement a fresh NDI and RV0 while the peer TB is retransmitted. */
if (!(harq->pending_cw_mask & 1))
harq->ndi ^= 1;
if (!(harq->pending_cw_mask & 2))
harq->ndi2 ^= 1;
}
NR_sched_pucch_t *pucch = NULL;
DevAssert(!harq->is_waiting);
if (sched_pdsch->pucch_allocation >= 0) {
@@ -1287,6 +1272,28 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
}
UE->mac_stats.dl.rounds[harq->round]++;
int tpc = nr_mac_get_tpc(&sched_ctrl->pucch_pc);
if (nrOfLayers > 4 && sched_pdsch->rbSize >= 100) {
static bool logged_full_high_rank_shape[32] = {false};
if (!__atomic_exchange_n(&logged_full_high_rank_shape[sched_pdsch->mcs], true, __ATOMIC_RELAXED))
LOG_I(NR_MAC,
"[HIGH_RANK_SCHEDULE] layers=%u RB=%u symbols=%u start=%u "
"MCS=%u Qm=%u R=%u TBS=%u/%u total=%u DMRS_mask=0x%x "
"DMRS_RE_per_RB=%u DMRS_symbols=%u CDM_groups=%u\n",
nrOfLayers,
sched_pdsch->rbSize,
tda_info->nrOfSymbols,
tda_info->startSymbolIndex,
sched_pdsch->mcs,
sched_pdsch->Qm,
sched_pdsch->R,
TBS,
TBS2,
TBS + TBS2,
dmrs_parms->dl_dmrs_symb_pos,
dmrs_parms->N_PRB_DMRS,
dmrs_parms->N_DMRS_SLOT,
dmrs_parms->numDmrsCdmGrpsNoData);
}
LOG_D(NR_MAC,
"%4d.%2d [DLSCH/PDSCH/PUCCH] RNTI %04x DCI L %d start %3d RBs %3d startSymbol %2d nb_symbol %2d dmrspos %x MCS %2d nrOfLayers %d TBS %4d HARQ PID %2d round %d RV %d NDI %d dl_data_to_ULACK %d (%d.%d) PUCCH allocation %d TPC %d\n",
frame,
@@ -1364,6 +1371,10 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
fapi_beam,
nl_tbslbrm,
pduindex);
if (harq->round > 0 && active_codewords == 2)
for (int q = 0; q < active_codewords; q++)
if (!(harq->pending_cw_mask & (1U << q)))
pdsch_pdu->rvIndex[q] = 0;
LOG_D(NR_MAC,"Configuring DCI/PDCCH in %d.%d at CCE %d, rnti %x\n", frame,slot,sched_ctrl->cce_index,rnti);
@@ -1447,15 +1458,28 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
harq->round,
harq->ndi);
if (!get_softmodem_params()->phy_test)
AssertFatal(harq->sched_pdsch.tb_size == TBS,
"UE %04x mismatch between scheduled TBS and buffered TB for HARQ PID %d\n",
AssertFatal(harq->sched_pdsch.tb_size == TBS && harq->sched_pdsch.tb_size2 == TBS2,
"UE %04x mismatch between scheduled TBS (%u,%u) and buffered TB (%u,%u) for HARQ PID %d\n",
UE->rnti,
TBS,
TBS2,
harq->sched_pdsch.tb_size,
harq->sched_pdsch.tb_size2,
current_harq_pid);
else if (harq->sched_pdsch.tb_size != TBS)
else if (harq->sched_pdsch.tb_size != TBS || harq->sched_pdsch.tb_size2 != TBS2)
LOG_E(NR_MAC,
"Mismatch between scheduled TBS and buffered TB for HARQ PID %d. No RTX control in phy-test mode. "
"Possible causes: presence of CSI-RS or DLSCH scheduled in the mixed slot.\n",
current_harq_pid);
if (active_codewords == 2)
for (int q = 0; q < active_codewords; q++) {
if (harq->pending_cw_mask & (1U << q))
continue;
byte_array_t *transport_block = q == 0 ? &harq->transportBlock : &harq->transportBlock2;
const uint32_t cw_tbs = q == 0 ? TBS : TBS2;
uint8_t *buf = allocate_transportBlock_buffer(transport_block, cw_tbs);
fill_padding_only_transport_block(buf, cw_tbs);
}
T(T_GNB_MAC_RETRANSMISSION_DL_PDU_WITH_DATA, T_INT(module_id), T_INT(CC_id), T_INT(rnti),
T_INT(frame), T_INT(slot), T_INT(current_harq_pid), T_INT(harq->round), T_BUFFER(harq->transportBlock.buf, TBS));
UE->mac_stats.dl.total_rbs_retx += sched_pdsch->rbSize;
@@ -1464,94 +1488,24 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
LOG_D(NR_MAC, "Initial HARQ transmission in %d.%d\n", frame, slot);
// Flag HARQ process to start TCI timer at ACK
harq->start_tci_timer = sched_ctrl->UE_mac_ce_ctrl.tci_state_ind.is_scheduled;
uint8_t *buf = allocate_transportBlock_buffer(&harq->transportBlock, TBS);
/* first, write all CEs that might be there */
int written = nr_write_ce_dlsch_pdu(module_id,
sched_ctrl,
(unsigned char *)buf,
255, // no drx
NULL); // contention res id
buf += written;
uint8_t *bufEnd = buf + TBS - written;
DevAssert(TBS > written);
int dlsch_total_bytes = 0;
/* next, get RLC data */
start_meas(&nr_mac->rlc_data_req);
int sdus = 0;
if (sched_ctrl->num_total_bytes > 0) {
/* loop over all activated logical channels */
for (int i = 0; i < seq_arr_size(&sched_ctrl->lc_config); ++i) {
const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, i);
const int lcid = c->lcid;
if (sched_ctrl->rlc_status[lcid].bytes_in_buffer == 0)
continue; // no data for this LC tbs_size_t len = 0;
tb_size_t pdu_siz[MAX_NUM_DATA_REQ];
int num = nr_mac_rlc_multi_data_req(module_id, rnti, true, lcid, bufEnd - buf, (char *)buf, pdu_siz, sizeofArray(pdu_siz));
DevAssert(num <= sizeofArray(pdu_siz));
sdus += num;
int lcid_bytes = 0;
for (int j = 0; j < num; ++j) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *) buf;
header->R = 0;
header->F = 1;
header->LCID = lcid;
header->L = htons(pdu_siz[j]);
buf += pdu_siz[j] + sizeof(NR_MAC_SUBHEADER_LONG);
dlsch_total_bytes += pdu_siz[j];
lcid_bytes += pdu_siz[j];
}
T(T_GNB_MAC_LCID_DL, T_INT(rnti), T_INT(frame), T_INT(slot), T_INT(lcid), T_INT(lcid_bytes), T_INT(nr_rlc_tx_list_occupancy(rnti, lcid)));
UE->mac_stats.dl.lc_bytes[lcid] += lcid_bytes;
}
} else if (get_softmodem_params()->phy_test || get_softmodem_params()->do_ra) {
/* we will need the large header, phy-test typically allocates all
* resources and fills to the last byte below */
LOG_D(NR_MAC, "Configuring DL_TX in %d.%d: TBS %d of random data\n", frame, slot, TBS);
if (bufEnd-buf > sizeof(NR_MAC_SUBHEADER_LONG) ) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *) buf;
// fill dlsch_buffer with random data
header->R = 0;
header->F = 1;
header->LCID = DL_SCH_LCID_PADDING;
buf += sizeof(NR_MAC_SUBHEADER_LONG);
header->L = htons(bufEnd-buf);
for (; ((intptr_t)buf) % 4; buf++)
*buf = lrand48() & 0xff;
for (; buf < bufEnd - 3; buf += 4) {
uint32_t *buf32 = (uint32_t *)buf;
*buf32 = lrand48();
}
for (; buf < bufEnd; buf++)
*buf = lrand48() & 0xff;
sdus +=1;
}
for (int q = 0; q < nr_pdsch_num_codewords(nrOfLayers); q++) {
const uint32_t cw_tbs = q == 0 ? TBS : sched_pdsch->tb_size2;
byte_array_t *transport_block = q == 0 ? &harq->transportBlock : &harq->transportBlock2;
uint8_t *buf = allocate_transportBlock_buffer(transport_block, cw_tbs);
dlsch_fill_result_t fill = fill_dlsch_transport_block(nr_mac, UE, buf, cw_tbs, frame, slot, q == 0);
sdus += fill.sdus;
dlsch_total_bytes += fill.sdu_bytes;
}
stop_meas(&nr_mac->rlc_data_req);
// Add padding header and zero rest out if there is space left
if (bufEnd-buf > 0) {
NR_MAC_SUBHEADER_FIXED *padding = (NR_MAC_SUBHEADER_FIXED *) buf;
padding->R = 0;
padding->LCID = DL_SCH_LCID_PADDING;
buf += 1;
memset(buf,0,bufEnd-buf);
buf=bufEnd;
}
UE->mac_stats.dl.total_bytes += TBS;
UE->mac_stats.dl.current_bytes = TBS;
const uint32_t total_tbs = TBS + sched_pdsch->tb_size2;
UE->mac_stats.dl.total_bytes += total_tbs;
UE->mac_stats.dl.current_bytes = total_tbs;
UE->mac_stats.dl.total_rbs += sched_pdsch->rbSize;
UE->mac_stats.dl.num_mac_sdu += sdus;
UE->mac_stats.dl.current_rbs = sched_pdsch->rbSize;
UE->mac_stats.dl.last_layers = sched_pdsch->nrOfLayers;
UE->mac_stats.dl.total_sdu_bytes += dlsch_total_bytes;
nr_mac->mac_stats.dl.used_prb_aggregate += sched_pdsch->rbSize;
@@ -1588,10 +1542,16 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
const int ntx_req = pdsch->TX_req->Number_of_PDUs;
nfapi_nr_pdu_t *tx_req = &pdsch->TX_req->pdu_list[ntx_req];
tx_req->PDU_index = pduindex;
tx_req->num_TLV = 1;
tx_req->TLVs[0].length = TBS;
tx_req->PDU_length = compute_PDU_length(tx_req->num_TLV, tx_req->TLVs[0].length);
memcpy(tx_req->TLVs[0].value.direct, harq->transportBlock.buf, TBS);
tx_req->num_TLV = nr_pdsch_num_codewords(nrOfLayers);
uint32_t total_tlv_length = 0;
for (int q = 0; q < tx_req->num_TLV; q++) {
const uint32_t cw_tbs = q == 0 ? TBS : TBS2;
const byte_array_t *transport_block = q == 0 ? &harq->transportBlock : &harq->transportBlock2;
tx_req->TLVs[q].length = cw_tbs;
memcpy(tx_req->TLVs[q].value.direct, transport_block->buf, cw_tbs);
total_tlv_length += cw_tbs;
}
tx_req->PDU_length = compute_PDU_length(tx_req->num_TLV, total_tlv_length);
pdsch->TX_req->Number_of_PDUs++;
pdsch->TX_req->SFN = frame;
pdsch->TX_req->Slot = slot;
@@ -166,15 +166,7 @@ void nr_preprocessor_phytest(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch)
.tda_info = tda_info,
};
sched_ctrl->dl_bler_stats.mcs = target_dl_mcs; /* for logging output */
sched_pdsch.tb_size = nr_compute_tbs(sched_pdsch.Qm,
sched_pdsch.R,
sched_pdsch.rbSize,
tda_info.nrOfSymbols,
sched_pdsch.dmrs_parms.N_PRB_DMRS * sched_pdsch.dmrs_parms.N_DMRS_SLOT,
0 /* N_PRB_oh, 0 for initialBWP */,
0 /* tb_scaling */,
target_dl_Nl)
>> 3;
update_sched_pdsch_codeword_tbs(&sched_pdsch);
post_process_dlsch(mac, pp_pdsch, UE, &sched_pdsch);
@@ -166,6 +166,8 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
{
if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1)
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
if (layers < 1 || layers > NR_MAX_NB_LAYERS || nrmac->precoding_matrix_size[layers - 1] == 0)
return 0;
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
int reported_ri_layers = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
@@ -180,9 +182,17 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
LOG_D(NR_MAC, "[ANTENNA_MUTE] Recovery mode active, using identity matrix\n");
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 active_tx = nr_get_active_tx_antenna_count(ant_state);
if (active_tx < ant_state->num_tx_antennas) {
// A full-panel codebook spreads every layer over ports that the RU
// subsequently blanks. Identity mapping instead places the
// reduced layer count one-to-one on the remaining active ports.
LOG_D(NR_MAC,
"[ANTENNA_MUTE] %d/%u TX ports active, using identity matrix\n",
active_tx,
ant_state->num_tx_antennas);
return 0;
}
}
const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id;
@@ -191,6 +201,13 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
const int N2 = csi_report->N2;
const int antenna_ports = (N1 * N2) << 1;
if (antenna_ports < 2) return 0; // single antenna port
if (antenna_ports == 8 && (layers == 3 || layers == 4)) {
LOG_D(NR_MAC,
"Using identity matrix for rank-%d over eight ports: the legacy UE rank-3/4 detector "
"does not support non-identity eight-port precoding\n",
layers);
return 0;
}
int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
@@ -226,17 +243,38 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
const int i11 = x1 & ((1 << bitlen) - 1);
const int i2 = x2;
int k1, k2;
get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values)
const int O2 = N2 == 1 ? 1 : 4;
int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports
int max_i2 = 0;
int lay_index = 0;
max_i2 = layers == 1 ? 4 : 2;
int K1, K2;
get_K1_K2(N1, N2, &K1, &K2, layers);
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
if (layers > 4) {
const int i11_count = nr_type1_sp_4x1_i11_count(layers);
if (antenna_ports != 8 || N1 != 4 || N2 != 1 || i11_count == 0
|| i11 >= i11_count || i12 != 0 || i13 != 0 || i2 < 0 || i2 >= 2) {
LOG_W(NR_MAC,
"Unsupported rank-%d high-rank PMI for N1=%d N2=%d: i11=%d i12=%d i13=%d i2=%d; "
"using identity matrix\n",
layers,
N1,
N2,
i11,
i12,
i13,
i2);
return 0;
}
// High-rank matrices are generated in i11-major, i2-minor order.
lay_index = i11 * 2 + i2;
} else {
int k1, k2;
get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // indices for the generated PHY matrix
const int O2 = N2 == 1 ? 1 : 4;
const int O1 = 4; // horizontal beam oversampling for more than two antenna ports
const int max_i2 = layers == 1 ? 4 : 2;
int K1, K2;
get_K1_K2(N1, N2, &K1, &K2, layers);
// computing precoding matrix index according to init_DL_MIMO_codebook ordering
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1)
+ (k1 * max_i2 * N1 * O1 * N2 * O2)
+ (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
}
pm_index = 1 + prev_layers_size + lay_index;
}
int max_valid_pm = prev_layers_size + nrmac->precoding_matrix_size[layers - 1];
@@ -328,6 +366,11 @@ NR_pdsch_dmrs_t get_dl_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
{
NR_pdsch_dmrs_t dmrs = {0};
int frontloaded_symb = 1; // default value
NR_PDSCH_Config_t *pdsch_Config = dl_bwp ? dl_bwp->pdsch_Config : NULL;
NR_DMRS_DownlinkConfig_t *dmrs_Config = NULL;
if (pdsch_Config)
dmrs_Config = tda_info->mapping_type == typeB ? pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup
: pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
nr_dci_format_t dci_format = dl_bwp ? dl_bwp->dci_format : NR_DL_DCI_FORMAT_1_0;
if (dci_format == NR_DL_DCI_FORMAT_1_0) {
dmrs.numDmrsCdmGrpsNoData = tda_info->nrOfSymbols <= 2 ? 1 : 2;
@@ -362,19 +405,29 @@ NR_pdsch_dmrs_t get_dl_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
dmrs.numDmrsCdmGrpsNoData = 2;
frontloaded_symb = 1;
break;
case 5:
case 6:
case 7:
case 8:
AssertFatal(dmrs_Config && dmrs_Config->dmrs_Type == NULL && dmrs_Config->maxLength,
"Rank %d PDSCH requires DMRS type 1 with maxLength len2\n",
Layers);
/* TS 38.212 Table 7.3.1.2.2-2, two-codeword rows 0--3. */
dmrs.dmrs_ports_id = Layers - 5;
dmrs.numDmrsCdmGrpsNoData = 2;
frontloaded_symb = 2;
break;
default:
AssertFatal(1==0,"Number of layers %d\n not supported or not valid\n",Layers);
}
}
dmrs.n_scid = 0;
static const uint16_t two_cw_dmrs_ports[] = {0x1f, 0x5f, 0x7f, 0xff};
dmrs.dmrs_ports = Layers > 4 ? two_cw_dmrs_ports[Layers - 5] : (1U << Layers) - 1;
long *scramblingID = NULL;
NR_PDSCH_Config_t *pdsch_Config = dl_bwp ? dl_bwp->pdsch_Config : NULL;
if (pdsch_Config) {
NR_DMRS_DownlinkConfig_t *dmrs_Config = tda_info->mapping_type == typeB ?
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup :
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
dmrs.phaseTrackingRS = dmrs_Config->phaseTrackingRS ? dmrs_Config->phaseTrackingRS->choice.setup : NULL;
dmrs.phaseTrackingRS = Layers > 4 ? NULL : (dmrs_Config->phaseTrackingRS ? dmrs_Config->phaseTrackingRS->choice.setup : NULL);
dmrs.dmrsConfigType = dmrs_Config->dmrs_Type != NULL;
scramblingID = dmrs.n_scid ? dmrs_Config->scramblingID1 : dmrs_Config->scramblingID0;
} else {
@@ -845,7 +898,12 @@ int get_mcs_from_bler(const NR_bler_options_t *bler_options,
if (diff < 0) // wrap around
diff += 1024;
max_mcs = min(max_mcs, bler_options->max_mcs);
/* Keep the configured interval valid even when CSI supplies a lower cap.
* In particular, min_mcs == max_mcs is an explicit fixed-MCS request and
* must not alternate between the CQI fallback and the configured value at
* each BLER update boundary. */
max_mcs = max((int)bler_options->min_mcs,
min(max_mcs, (int)bler_options->max_mcs));
const uint8_t old_mcs = min(bler_stats->mcs, max_mcs);
if (diff < BLER_UPDATE_FRAME)
return old_mcs; // no update
@@ -954,6 +1012,20 @@ dci_pdu_rel15_t prepare_dci_dl_payload(const gNB_MAC_INST *gNB_mac,
AssertFatal(harq, "HARQ process should be available for DCI with RNTI %s\n", rnti_types(rnti_type));
dci_payload.harq_pid.val = harq_pid;
dci_payload.ndi = harq->ndi;
if (pdsch_pdu->NrOfCodewords == 2) {
dci_payload.mcs2.val = sched_pdsch->mcs;
dci_payload.ndi2.val = harq->ndi2;
dci_payload.rv2.val = pdsch_pdu->rvIndex[1];
} else {
/* maxNrofCodeWordsScheduledByDCI=n2 keeps the second-TB fields in DCI
* format 1_1 even for a one-codeword (rank <= 4) PDSCH. TS 38.214
* disables that transport block with I_MCS=26 and RV=1. Leaving the
* zero-initialized values here makes the UE treat CW1 as active and
* interpret the antenna-port row through the two-codeword DMRS table. */
dci_payload.mcs2.val = 26;
dci_payload.ndi2.val = 0;
dci_payload.rv2.val = 1;
}
dci_payload.pucch_resource_indicator = pucch ? pucch->resource_indicator : 0;
dci_payload.pdsch_to_harq_feedback_timing_indicator.val = pucch ? pucch->timing_indicator : 0; // PDSCH to HARQ TI
dci_payload.dai[0].val = pucch ? (pucch->dai_c - 1) & 3 : 0;
@@ -2542,8 +2614,10 @@ void delete_nr_ue_data(NR_UE_info_t *UE, uid_allocator_t *uia)
destroy_nr_list(&sched_ctrl->available_ul_harq);
destroy_nr_list(&sched_ctrl->feedback_ul_harq);
destroy_nr_list(&sched_ctrl->retrans_ul_harq);
for (int i = 0; i < NR_MAX_HARQ_PROCESSES; ++i)
for (int i = 0; i < NR_MAX_HARQ_PROCESSES; ++i) {
free_transportBlock_buffer(&sched_ctrl->harq_processes[i].transportBlock);
free_transportBlock_buffer(&sched_ctrl->harq_processes[i].transportBlock2);
}
free_sched_pucch_list(sched_ctrl);
uid_linear_allocator_free(uia, UE->uid);
LOG_I(NR_MAC, "Remove NR rnti 0x%04x\n", UE->rnti);
@@ -3638,9 +3712,16 @@ void nr_mac_update_timers(module_id_t module_id)
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
if (nr_mac_check_release(sched_ctrl)) {
// trigger release first as nr_mac_release_ue() invalidates UE ptr
nr_mac_trigger_release_complete(mac, UE->rnti);
nr_mac_release_ue(mac, UE->rnti);
// nakaolab UE release race fix:
// Save rnti before nr_mac_trigger_release_complete — that call drops
// the scheduler lock briefly (to send F1AP UE Context Release Complete),
// and another thread (e.g., RRC DL handler) can free this UE during
// that window, leaving UE->rnti pointing at freed memory.
rnti_t rnti = UE->rnti;
nr_mac_trigger_release_complete(mac, rnti);
// Re-validate after lock re-acquired: skip release if already gone.
if (find_nr_UE(&mac->UE_info, rnti) != NULL)
nr_mac_release_ue(mac, rnti);
// go back to examine the next UE, which is at the position the
// current UE was
UE--;
+208 -33
View File
@@ -15,6 +15,24 @@
#include "common/ran_context.h"
#include "common/utils/nr/nr_common.h"
#include "nfapi/oai_integration/vendor_ext.h"
/* DAI counts PDSCH assignments, while O_ACK counts HARQ-ACK bits. With
* maxNrofCodeWordsScheduledByDCI=n2, the type-2 codebook reserves two ACK
* bits per assignment even when DCI disables the second transport block.
* The reserved bit still occupies the UCI payload, but it must not turn a
* successfully decoded one-codeword PDSCH into a HARQ failure. */
static uint8_t harq_ack_bits_per_assignment(const NR_UE_info_t *UE, bool is_common)
{
if (is_common)
return 1;
const NR_PDSCH_Config_t *pdsch_config = UE->current_DL_BWP.pdsch_Config;
return pdsch_config != NULL && pdsch_config->maxNrofCodeWordsScheduledByDCI != NULL
&& *pdsch_config->maxNrofCodeWordsScheduledByDCI
== NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2
? 2
: 1;
}
static void nr_fill_nfapi_pucch(gNB_MAC_INST *nrmac, frame_t frame, slot_t slot, const NR_sched_pucch_t *pucch, NR_UE_info_t* UE)
{
@@ -48,14 +66,15 @@ static void nr_fill_nfapi_pucch(gNB_MAC_INST *nrmac, frame_t frame, slot_t slot,
future_ul_tti_req->n_pdus += 1;
LOG_D(NR_MAC,
"%s %4d.%2d Scheduling pucch reception in %4d.%2d: bits SR %d, DAI %d, CSI %d on res %d\n",
pucch->dai_c>0 ? "pucch_acknack" : "",
"%s %4d.%2d Scheduling pucch reception in %4d.%2d: bits SR %d, DAI %d, HARQ-ACK %d, CSI %d on res %d\n",
pucch->harq_ack_bits > 0 ? "pucch_acknack" : "",
frame,
slot,
pucch->frame,
pucch->ul_slot,
pucch->sr_flag,
pucch->dai_c,
pucch->harq_ack_bits,
pucch->csi_bits,
pucch->resource_indicator);
NR_COMMON_channels_t * common_ch=nrmac->common_channels;
@@ -75,7 +94,7 @@ static void nr_fill_nfapi_pucch(gNB_MAC_INST *nrmac, frame_t frame, slot_t slot,
UE,
pucch->resource_indicator,
pucch->csi_bits,
pucch->dai_c,
pucch->harq_ack_bits,
pucch->sr_flag,
pucch->r_pucch,
nrmac->beam_info.beam_mode);
@@ -151,7 +170,7 @@ static void schedule_pucch_core(gNB_MAC_INST *nrmac, NR_UE_info_t *UE, frame_t f
return;
}
const uint16_t O_ack = curr_pucch->dai_c;
const uint16_t O_ack = curr_pucch->harq_ack_bits;
const uint16_t O_csi = curr_pucch->csi_bits;
const uint8_t O_sr = curr_pucch->sr_flag;
LOG_D(NR_MAC,
@@ -359,10 +378,15 @@ int get_pucch_resourceid(NR_PUCCH_Config_t *pucch_Config, int O_uci, int pucch_r
return *resource_id;
}
static void handle_dl_harq(gNB_MAC_INST *mac, NR_UE_info_t * UE, int8_t harq_pid, bool success, int harq_round_max)
static void handle_dl_harq(gNB_MAC_INST *mac, NR_UE_info_t * UE, int8_t harq_pid, uint8_t ack_mask, int harq_round_max)
{
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid];
const uint8_t active_mask = harq->sched_pdsch.nrOfLayers > 4 ? 3 : 1;
harq->pending_cw_mask = nr_pdsch_pending_after_ack(harq->pending_cw_mask, active_mask, ack_mask);
LOG_D(NR_MAC, "DL HARQ UE %04x PID %d ACK mask 0x%x pending CW mask 0x%x round %u\n",
UE->rnti, harq_pid, ack_mask, harq->pending_cw_mask, harq->round);
const bool success = harq->pending_cw_mask == 0;
harq->feedback_slot = -1;
harq->is_waiting = false;
if (success) {
@@ -633,7 +657,7 @@ static void evaluate_cqi_report(uint8_t *payload,
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_1tb = temp_cqi;
LOG_D(MAC,"Wide-band CQI for the first TB %d\n", temp_cqi);
if (cqi_bitlen > 4) {
temp_cqi = pickandreverse_bits(payload, 4, cumul_bits);
temp_cqi = pickandreverse_bits(payload, 4, cumul_bits + 4);
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_2tb = temp_cqi;
LOG_D(MAC,"Wide-band CQI for the second TB %d\n", temp_cqi);
}
@@ -718,6 +742,7 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
int cumul_bits = 0;
int r_index = -1;
int new_bf_index = -1;
bool link_csi_decoded = false;
for (int csi_report_id = 0; csi_report_id < csi_MeasConfig->csi_ReportConfigToAddModList->list.count; csi_report_id++) {
nr_csi_report_t *csi_report = &UE->csi_report_template[csi_report_id];
csi_report->nb_of_csi_ssb_report = 0;
@@ -781,6 +806,7 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
if (r_index != -1)
skip_zero_padding(&cumul_bits, csi_report, r_index, bitlen);
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
link_csi_decoded |= r_index >= 0;
break;
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_PMI_CQI:
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.print_report = true;
@@ -810,6 +836,7 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id = csi_report_id;
cumul_bits += pmi_bitlen;
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
link_csi_decoded |= r_index >= 0;
break;
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_LI_PMI_CQI:
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.print_report = true;
@@ -841,6 +868,7 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id = csi_report_id;
cumul_bits += pmi_bitlen;
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
link_csi_decoded |= r_index >= 0;
break;
default:
AssertFatal(1 == 0, "Invalid or not supported CSI measurement report\n");
@@ -848,6 +876,10 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
}
}
}
if (link_csi_decoded) {
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.report_frame = frame;
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.report_slot = slot;
}
if ((new_bf_index !=-1) && !nrmac->radio_config.do_TCI)
// Trigger RRCReconfiguration. Need to be out of the for loop as it may modify csi_MeasConfig
beam_switching_procedure(nrmac, UE, new_bf_index);
@@ -878,7 +910,7 @@ static NR_UE_harq_t *find_harq(frame_t frame, slot_t slot, NR_UE_info_t * UE, in
frame,
slot);
remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
handle_dl_harq(NULL, UE, pid, false, harq_round_max);
handle_dl_harq(NULL, UE, pid, 0, harq_round_max);
pid = sched_ctrl->feedback_dl_harq.head;
if (pid < 0)
return NULL;
@@ -920,8 +952,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
if (((uci_01->pduBitmap >> 1) & 0x01)) {
// iterate over received harq bits
for (int harq_bit = 0; harq_bit < uci_01->harq.num_harq; harq_bit++) {
const uint8_t harq_value = uci_01->harq.harq_list[harq_bit].harq_value;
for (int harq_bit = 0; harq_bit < uci_01->harq.num_harq;) {
const uint8_t harq_confidence = uci_01->harq.harq_confidence_level;
NR_UE_harq_t *harq = find_harq(frame, slot, UE, nrmac->dl_bler.harq_round_max);
if (!harq) {
@@ -929,11 +960,29 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
break;
}
DevAssert(harq->is_waiting);
const int n_ack_bits = harq_ack_bits_per_assignment(UE, is_ra);
/* Msg4/common HARQ entries can carry the legacy zero layer sentinel.
* Preserve the scheduler's established 0--4 => one-codeword rule. */
const int active_codewords = harq->sched_pdsch.nrOfLayers > 4 ? 2 : 1;
const int8_t pid = sched_ctrl->feedback_dl_harq.head;
remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
LOG_D(NR_MAC,"%4d.%2d bit %d pid %d ack/nack %d\n",frame, slot, harq_bit,pid,harq_value);
const bool feedback_valid = harq_confidence == 0 && harq_bit + n_ack_bits <= uci_01->harq.num_harq;
bool success = feedback_valid;
uint8_t ack_mask = 0;
for (int q = 0; q < n_ack_bits && harq_bit + q < uci_01->harq.num_harq; q++) {
const uint8_t harq_value = uci_01->harq.harq_list[harq_bit + q].harq_value;
LOG_D(NR_MAC, "%4d.%2d bit %d pid %d cw %d active %d ack/nack %d\n",
frame, slot, harq_bit + q, pid, q, q < active_codewords, harq_value);
if (q < active_codewords) {
const bool ack = harq_value == 0;
success &= ack;
if (feedback_valid && ack)
ack_mask |= 1U << q;
}
}
if (active_codewords == 2)
success = feedback_valid && (harq->pending_cw_mask & ~ack_mask) == 0;
nr_mac_update_pdcch_closed_loop_adjust(sched_ctrl, harq_confidence != 0);
bool success = harq_value == 0 && harq_confidence == 0;
// TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133)
if (success && harq->start_tci_timer) {
int slots = 3 * nrmac->frame_structure.numb_slots_frame / 10;
@@ -941,7 +990,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
nr_timer_start(&sched_ctrl->tci_beam_switch);
harq->start_tci_timer = false;
}
handle_dl_harq(nrmac, UE, pid, success, nrmac->dl_bler.harq_round_max);
handle_dl_harq(nrmac, UE, pid, ack_mask, nrmac->dl_bler.harq_round_max);
if (is_ra) {
bool ue_rejected = nr_check_Msg4_MsgB_Ack(mod_id, frame, slot, UE, success);
if (ue_rejected) {
@@ -957,6 +1006,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
// DTX for each _bit_, but maybe once for PUCCH is enough? not sure
nr_mac_signal_dtx(&sched_ctrl->pucch_pc);
}
harq_bit += n_ack_bits;
}
// tpc (power control) only if we received AckNack
@@ -1022,26 +1072,50 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c
if ((uci_234->pduBitmap >> 1) & 0x01) {
// iterate over received harq bits
for (int harq_bit = 0; harq_bit < uci_234->harq.harq_bit_len; harq_bit++) {
const int acknack = ((uci_234->harq.harq_payload[harq_bit >> 3]) >> harq_bit) & 0x01;
for (int harq_bit = 0; harq_bit < uci_234->harq.harq_bit_len;) {
NR_UE_harq_t *harq = find_harq(frame, slot, UE, RC.nrmac[mod_id]->dl_bler.harq_round_max);
if (!harq) {
LOG_E(NR_MAC, "UE %04x: Could not find a HARQ process at %4d.%2d!\n", UE->rnti, frame, slot);
break;
}
DevAssert(harq->is_waiting);
const int n_ack_bits = harq_ack_bits_per_assignment(UE, false);
/* Msg4/common HARQ entries can carry the legacy zero layer sentinel.
* Preserve the scheduler's established 0--4 => one-codeword rule. */
const int active_codewords = harq->sched_pdsch.nrOfLayers > 4 ? 2 : 1;
const int8_t pid = sched_ctrl->feedback_dl_harq.head;
remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
LOG_D(NR_MAC,"%4d.%2d bit %d pid %d ack/nack %d\n",frame, slot, harq_bit, pid, acknack);
const bool feedback_valid = uci_234->harq.harq_crc != 1 && harq_bit + n_ack_bits <= uci_234->harq.harq_bit_len;
bool success = feedback_valid;
uint8_t ack_mask = 0;
for (int q = 0; q < n_ack_bits && harq_bit + q < uci_234->harq.harq_bit_len; q++) {
const int logical_bit = harq_bit + q;
/* get_downlink_ack() reverses the Type-2 codebook before PUCCH
* encoding. PUCCH formats 2/3/4 expose that wire order directly,
* unlike the format-0/1 harq_list. Convert it back before assigning
* bits to DAI/codeword positions. This matters when CW1 is disabled:
* the reserved NACK and the active CW0 ACK otherwise exchange roles. */
const int payload_bit = uci_234->harq.harq_bit_len - 1 - logical_bit;
const int acknack = (uci_234->harq.harq_payload[payload_bit >> 3] >> (payload_bit & 7)) & 0x01;
LOG_D(NR_MAC, "%4d.%2d logical bit %d payload bit %d pid %d cw %d active %d ack/nack %d\n",
frame, slot, logical_bit, payload_bit, pid, q, q < active_codewords, acknack);
if (q < active_codewords) {
success &= acknack;
if (feedback_valid && acknack)
ack_mask |= 1U << q;
}
}
if (active_codewords == 2)
success = feedback_valid && (harq->pending_cw_mask & ~ack_mask) == 0;
// TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133)
bool success = uci_234->harq.harq_crc != 1 && acknack;
if (success && harq->start_tci_timer) {
int slots = 3 * nrmac->frame_structure.numb_slots_frame / 10;
nr_timer_setup(&sched_ctrl->tci_beam_switch, slots, 1);
nr_timer_start(&sched_ctrl->tci_beam_switch);
harq->start_tci_timer = false;
}
handle_dl_harq(nrmac, UE, pid, success, nrmac->dl_bler.harq_round_max);
handle_dl_harq(nrmac, UE, pid, ack_mask, nrmac->dl_bler.harq_round_max);
harq_bit += n_ack_bits;
}
free(uci_234->harq.harq_payload);
}
@@ -1118,6 +1192,15 @@ static void set_pucch0_vrb_occupation(const NR_sched_pucch_t *pucch, uint16_t *v
}
}
static void clear_pucch0_vrb_occupation(const NR_sched_pucch_t *pucch, uint16_t *vrb_map_UL, const int bwp_start)
{
for (int l = 0; l < pucch->nr_of_symb; l++) {
const uint16_t symb = SL_to_bitmap(pucch->start_symb + l, 1);
const int prb = l == 1 && pucch->second_hop_prb != 0 ? pucch->second_hop_prb : pucch->prb_start;
vrb_map_UL[bwp_start + prb] &= ~symb;
}
}
bool check_bits_vs_coderate_limit(NR_PUCCH_Config_t *pucch_Config, int O_uci, int pucch_resource)
{
int resource_id = get_pucch_resourceid(pucch_Config, O_uci, pucch_resource);
@@ -1163,6 +1246,84 @@ bool check_bits_vs_coderate_limit(NR_PUCCH_Config_t *pucch_Config, int O_uci, in
AssertFatal(false, "No PUCCH resource found\n");
}
/* PUCCH resource set 0 is limited to two UCI bits. A rank-8 PDSCH consumes
* two HARQ-ACK bits by itself, so multiplexing a second assignment changes
* the selected resource to set 1 (format 2). Keep the scheduler's VRB map in
* sync with the resource that nr_configure_pucch() and the UE will select from
* the final payload size. Without this migration every feedback occasion
* accepts only one rank-8 assignment and halves the usable DL slots. */
static bool migrate_pucch_to_large_payload_resource(gNB_MAC_INST *mac,
NR_UE_info_t *UE,
NR_sched_pucch_t *pucch,
int old_uci_bits,
int new_uci_bits,
int ue_beam)
{
if (old_uci_bits > 2 || new_uci_bits <= 2 || pucch->r_pucch >= 0)
return true;
NR_PUCCH_Config_t *pucch_config = UE->current_UL_BWP.pucch_Config;
const int resource_id = get_pucch_resourceid(pucch_config, new_uci_bits, pucch->resource_indicator);
const NR_PUCCH_Resource_t *resource = NULL;
for (int i = 0; i < pucch_config->resourceToAddModList->list.count; i++) {
const NR_PUCCH_Resource_t *candidate = pucch_config->resourceToAddModList->list.array[i];
if (candidate->pucch_ResourceId == resource_id) {
resource = candidate;
break;
}
}
if (resource == NULL || resource->format.present != NR_PUCCH_Resource__format_PR_format2
|| resource->intraSlotFrequencyHopping != NULL || resource->secondHopPRB != NULL) {
LOG_W(NR_MAC,
"UE %04x cannot migrate %d-bit HARQ feedback to the configured non-hopping PUCCH format-2 resource\n",
UE->rnti,
new_uci_bits);
return false;
}
const int n_slots_frame = mac->frame_structure.numb_slots_frame;
NR_beam_alloc_t beam =
beam_allocation_procedure(&mac->beam_info, pucch->frame, pucch->ul_slot, ue_beam, n_slots_frame);
if (beam.idx < 0)
return false;
const int index = ul_buffer_index(pucch->frame, pucch->ul_slot, n_slots_frame, mac->vrb_map_UL_size);
const int bwp_start = UE->current_UL_BWP.BWPStart;
uint16_t *vrb_map_UL = &mac->common_channels[0].vrb_map_UL[beam.idx][index * MAX_BWP_SIZE];
/* Release the format-0 reservation before testing format 2 so overlapping
* resource layouts do not conflict with themselves. Restore it if the
* larger footprint is occupied by another UL allocation. */
clear_pucch0_vrb_occupation(pucch, vrb_map_UL, bwp_start);
const int start_prb = resource->startingPRB;
const int n_prbs = resource->format.choice.format2->nrofPRBs;
const int start_symbol = resource->format.choice.format2->startingSymbolIndex;
const int n_symbols = resource->format.choice.format2->nrofSymbols;
const uint16_t symbol_mask = SL_to_bitmap(start_symbol, n_symbols);
bool available = true;
for (int prb = start_prb; prb < start_prb + n_prbs; prb++) {
if ((vrb_map_UL[bwp_start + prb] & symbol_mask) != 0) {
available = false;
break;
}
}
if (!available) {
set_pucch0_vrb_occupation(pucch, vrb_map_UL, bwp_start);
return false;
}
for (int prb = start_prb; prb < start_prb + n_prbs; prb++)
vrb_map_UL[bwp_start + prb] |= symbol_mask;
pucch->prb_start = start_prb;
pucch->second_hop_prb = 0;
pucch->nr_of_symb = n_symbols;
pucch->start_symb = start_symbol;
LOG_D(NR_MAC,
"UE %04x migrated HARQ feedback from PUCCH format 0 to format 2 for %d UCI bits\n",
UE->rnti,
new_uci_bits);
return true;
}
// this function returns an index to NR_sched_pucch structure
// if the function returns -1 it was not possible to schedule acknack
int nr_acknack_scheduling(gNB_MAC_INST *mac,
@@ -1184,6 +1345,7 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
const NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
const frame_structure_t *fs = &mac->frame_structure;
const int n_slots_frame = fs->numb_slots_frame;
const uint8_t n_ack_bits = harq_ack_bits_per_assignment(UE, is_common);
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_PUCCH_Config_t *pucch_Config = ul_bwp->pucch_Config;
@@ -1219,11 +1381,28 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
if (curr_pucch->active &&
curr_pucch->frame == pucch_frame &&
curr_pucch->ul_slot == pucch_slot) { // if there is already a PUCCH in given frame and slot
LOG_D(NR_MAC, "pucch_acknack DL %4d.%2d, UL_ACK %4d.%2d Bits already in current PUCCH: DAI_C %d CSI %d\n",
frame, slot, pucch_frame, pucch_slot, curr_pucch->dai_c, curr_pucch->csi_bits);
// we can't schedule if short pucch is already full
if (curr_pucch->csi_bits == 0 &&
curr_pucch->dai_c == 2)
LOG_D(NR_MAC, "pucch_acknack DL %4d.%2d, UL_ACK %4d.%2d Bits already in current PUCCH: DAI_C %d ACK %d CSI %d\n",
frame, slot, pucch_frame, pucch_slot, curr_pucch->dai_c, curr_pucch->harq_ack_bits, curr_pucch->csi_bits);
const int old_uci_bits = curr_pucch->csi_bits + curr_pucch->harq_ack_bits + curr_pucch->sr_flag;
const int new_uci_bits = old_uci_bits + n_ack_bits;
/* Preserve the proven two-PDSCH processing budget per feedback
* occasion. Rank-8 needs format 2 because those two assignments
* produce four HARQ-ACK bits, but accepting more assignments here
* can leave the UE insufficient time to decode every transport block
* before their common feedback deadline. The scheduler will try the
* next configured feedback occasion for additional assignments. */
const int max_pdsch_assignments_per_feedback = 2;
if (curr_pucch->dai_c >= max_pdsch_assignments_per_feedback)
continue;
if (new_uci_bits > 2
&& !check_bits_vs_coderate_limit(pucch_Config, new_uci_bits, curr_pucch->resource_indicator))
continue;
if (!migrate_pucch_to_large_payload_resource(mac,
UE,
curr_pucch,
old_uci_bits,
new_uci_bits,
ue_beam))
continue;
// if there is CSI but simultaneous HARQ+CSI is disable we can't schedule
if (curr_pucch->csi_bits > 0 && !curr_pucch->simultaneous_harqcsi)
@@ -1233,21 +1412,17 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
// the number of bits in the check need to include possible SR (1 bit)
// and the ack/nack bit to be scheduled (1 bit)
// so the number of bits already scheduled in current pucch + 2
if (curr_pucch->csi_bits > 0
&& !check_bits_vs_coderate_limit(pucch_Config,
curr_pucch->csi_bits + curr_pucch->dai_c + 2,
curr_pucch->resource_indicator))
continue;
// TODO temporarily limit ack/nak to 3 bits because of performances of polar for PUCCH (required for > 11 bits)
if (curr_pucch->csi_bits > 0 && curr_pucch->dai_c >= 3)
if (curr_pucch->csi_bits > 0 && curr_pucch->harq_ack_bits + n_ack_bits > 3)
continue;
// otherwise we can schedule in this active PUCCH
// no need to check VRB occupation because already done when PUCCH has been activated
curr_pucch->timing_indicator = f;
curr_pucch->dai_c++;
LOG_D(NR_MAC, "DL %4d.%2d, UL_ACK %4d.%2d Scheduling ACK/NACK in PUCCH %d with timing indicator %d DAI %d CSI %d\n",
frame,slot,curr_pucch->frame,curr_pucch->ul_slot,pucch_index,f,curr_pucch->dai_c,curr_pucch->csi_bits);
curr_pucch->harq_ack_bits += n_ack_bits;
LOG_D(NR_MAC, "DL %4d.%2d, UL_ACK %4d.%2d Scheduling ACK/NACK in PUCCH %d with timing indicator %d DAI %d ACK %d CSI %d\n",
frame,slot,curr_pucch->frame,curr_pucch->ul_slot,pucch_index,f,curr_pucch->dai_c,curr_pucch->harq_ack_bits,curr_pucch->csi_bits);
return pucch_index; // index of current PUCCH structure
}
else if (curr_pucch->active) {
@@ -1291,11 +1466,12 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
curr_pucch->ul_slot = pucch_slot;
curr_pucch->timing_indicator = f; // index in the list of timing indicators
curr_pucch->dai_c++;
curr_pucch->harq_ack_bits += n_ack_bits;
curr_pucch->resource_indicator = 0; // each UE has dedicated PUCCH resources
curr_pucch->r_pucch=r_pucch;
LOG_D(NR_MAC, "DL %4d.%2d, UL_ACK %4d.%2d Scheduling ACK/NACK in PUCCH %d with timing indicator %d DAI %d\n",
frame, slot, curr_pucch->frame, curr_pucch->ul_slot, pucch_index, f, curr_pucch->dai_c);
LOG_D(NR_MAC, "DL %4d.%2d, UL_ACK %4d.%2d Scheduling ACK/NACK in PUCCH %d with timing indicator %d DAI %d ACK %d\n",
frame, slot, curr_pucch->frame, curr_pucch->ul_slot, pucch_index, f, curr_pucch->dai_c, curr_pucch->harq_ack_bits);
// blocking resources for current PUCCH in VRB map
set_pucch0_vrb_occupation(curr_pucch, vrb_map_UL, bwp_start);
@@ -1391,4 +1567,3 @@ void nr_sr_reporting(gNB_MAC_INST *nrmac, frame_t SFN, slot_t slot)
}
}
}
+1
View File
@@ -464,6 +464,7 @@ bool prepare_initial_ul_rrc_message(gNB_MAC_INST *mac, NR_UE_info_t *UE);
void send_initial_ul_rrc_message(int rnti, const uint8_t *sdu, sdu_size_t sdu_len, void *data);
void finish_nr_dl_harq(NR_UE_sched_ctrl_t *sched_ctrl, int harq_pid);
void update_sched_pdsch_codeword_tbs(NR_sched_pdsch_t *sched);
void abort_nr_dl_harq(NR_UE_info_t* UE, int8_t harq_pid);
void nr_mac_trigger_release_timer(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpacing_t subcarrier_spacing);
+16 -5
View File
@@ -188,6 +188,8 @@ typedef struct nr_mac_config_s {
bool force_UL256qam_off;
bool use_deltaMCS;
int maxMIMO_layers;
/// PDSCH dmrs-AdditionalPosition: 0 for pos0, 1 for pos1.
int pdsch_dmrs_additional_position;
bool disable_harq;
nr_power_config_t pusch;
/// SNR threshold needed to put or not a PRB in the black list
@@ -403,7 +405,9 @@ typedef struct NR_sched_pucch {
bool sr_flag;
int csi_bits;
bool simultaneous_harqcsi;
/// Number of PDSCH assignments represented by the DAI field.
uint8_t dai_c;
uint8_t harq_ack_bits; ///< Actual O_ACK payload width (one or two bits per assignment).
uint8_t timing_indicator;
uint8_t resource_indicator;
int r_pucch;
@@ -460,6 +464,7 @@ typedef struct NR_sched_pusch {
typedef struct NR_pdsch_dmrs {
uint8_t dmrs_ports_id;
uint16_t dmrs_ports;
uint8_t N_PRB_DMRS;
uint8_t N_DMRS_SLOT;
uint16_t dl_dmrs_symb_pos;
@@ -485,6 +490,8 @@ typedef struct NR_sched_pdsch {
uint16_t R;
uint8_t Qm;
uint32_t tb_size;
/// Second transport block for rank 5--8 (same MCS/R/Qm for bundled HARQ)
uint32_t tb_size2;
/// DL HARQ PID to use for this UE, or -1 for "any new"
int8_t dl_harq_pid;
@@ -505,12 +512,15 @@ typedef struct NR_sched_pdsch {
typedef struct NR_UE_harq {
bool is_waiting;
uint8_t ndi;
uint8_t ndi2; /* independent NDI for the second transport block */
uint8_t pending_cw_mask; /* original TBs awaiting ACK; padding CWs are not pending */
uint8_t round;
uint16_t feedback_frame;
uint16_t feedback_slot;
/* Transport block to be sent using this HARQ process */
byte_array_t transportBlock;
byte_array_t transportBlock2;
uint32_t tb_size; // size of currently stored TB
bool start_tci_timer;
/// sched_pdsch keeps information on MCS etc used for the initial transmission
@@ -547,6 +557,8 @@ struct CRI_RI_LI_PMI_CQI {
uint8_t wb_cqi_2tb;
uint8_t cqi_table;
uint8_t csi_report_id;
uint16_t report_frame;
uint16_t report_slot;
bool print_report;
};
@@ -664,10 +676,6 @@ typedef struct {
int release_timer;
CSI_report_t CSI_report;
bool SR;
/// nakaolab MU-MIMO: encoded frame*1000+slot when this UE was last allocated as
/// a MU-MIMO partner (or leader). -1 = never. Lets pf_dl skip UEs already paired
/// in the current slot when scanning connected_ue_list for partners.
int64_t mumimo_paired_slot;
/// information about every HARQ process
NR_UE_harq_t harq_processes[NR_MAX_HARQ_PROCESSES];
/// HARQ processes that are free
@@ -719,6 +727,9 @@ typedef struct NR_mac_dir_stats {
uint32_t total_rbs_retx;
uint32_t num_mac_sdu;
uint32_t current_rbs;
// Most recently scheduled spatial layer count. Unlike current_rbs this is
// intentionally persistent so asynchronous E2 sampling does not miss it.
uint8_t last_layers;
} NR_mac_dir_stats_t;
typedef struct NR_mac_stats {
@@ -970,7 +981,7 @@ typedef struct gNB_MAC_INST_s {
nr_mac_config_t radio_config;
nr_rlc_configuration_t rlc_config;
/// Pointer to PHY antenna state (port mute mask) — set in scheduler init,
/// Pointer to PHY antenna state (port mute mask) — set in nr-gnb init,
/// used by get_dl_nrOfLayers to cap MIMO rank to active TX port count.
nr_antenna_state_t *antenna_state;
+44 -15
View File
@@ -32,7 +32,7 @@
#include "utils.h"
#include "xer_encoder.h"
#define NR_MAX_SUPPORTED_DL_LAYERS 4
#define NR_MAX_SUPPORTED_DL_LAYERS NR_MAX_NB_LAYERS
/* Default values for measurement gap configuration */
#define DEFAULT_MGRP NR_GapConfig__mgrp_ms160
@@ -655,10 +655,18 @@ static void set_dl_maxmimolayers(NR_PDSCH_ServingCellConfig_t *pdsch_servingcell
pdsch_servingcellconfig->ext1->maxMIMO_Layers = calloc(1, sizeof(*pdsch_servingcellconfig->ext1->maxMIMO_Layers));
long l = ue_supported_dl_layers(scc, uecap);
long ue_supported_layers = l > 1 ? l : 2; // min UE supported layers = 2
if (maxMIMO_layers == -1)
// nakaolab Patch 6: when called with NULL uecap (initial config before
// capability exchange), ue_supported_dl_layers returns -1. Fall back to
// maxMIMO_layers from config directly instead of clamping to 2.
long ue_supported_layers;
if (l < 1) {
ue_supported_layers = (maxMIMO_layers > 0) ? maxMIMO_layers : NR_MAX_SUPPORTED_DL_LAYERS;
} else {
ue_supported_layers = l;
}
if (maxMIMO_layers == -1)
*pdsch_servingcellconfig->ext1->maxMIMO_Layers = min(NR_MAX_SUPPORTED_DL_LAYERS, ue_supported_layers);
else
else
*pdsch_servingcellconfig->ext1->maxMIMO_Layers = min(maxMIMO_layers, ue_supported_layers);
}
@@ -1282,7 +1290,9 @@ static void config_pucch_resset1(const NR_ServingCellConfigCommon_t *scc,
pucchfmt2->interslotFrequencyHopping = NULL;
pucchfmt2->additionalDMRS = NULL;
pucchfmt2->maxCodeRate = calloc(1,sizeof(*pucchfmt2->maxCodeRate));
*pucchfmt2->maxCodeRate = NR_PUCCH_MaxCodeRate_zeroDot15;
// nakaolab Patch 5: bump from zeroDot15 (19.2 bit capacity) to zeroDot25
// (32 bit capacity) so 4-port CSI report (20 bits) fits in PUCCH.
*pucchfmt2->maxCodeRate = NR_PUCCH_MaxCodeRate_zeroDot25;
pucchfmt2->nrofSlots = NULL;
pucchfmt2->pi2BPSK = NULL;
@@ -1691,8 +1701,14 @@ static NR_PTRS_DownlinkConfig_t *config_dlptrs(const nr_ptrs_config_t *ptrs)
return ptrs_config;
}
static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap, int bwp_Id, bool do_TCI, nr_ptrs_config_t *ptrs)
static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap,
int bwp_Id,
const nr_mac_config_t *configuration)
{
const int antenna_ports = configuration->pdsch_AntennaPorts.N1 * configuration->pdsch_AntennaPorts.N2
* configuration->pdsch_AntennaPorts.XP;
const int configured_layers = configuration->maxMIMO_layers > 0 ? configuration->maxMIMO_layers : antenna_ports;
const bool two_codewords = min(configured_layers, NR_MAX_SUPPORTED_DL_LAYERS) > 4;
NR_SetupRelease_PDSCH_Config_t *setup_pdsch_Config = calloc(1,sizeof(*setup_pdsch_Config));
setup_pdsch_Config->present = NR_SetupRelease_PDSCH_Config_PR_setup;
NR_PDSCH_Config_t *pdsch_Config = calloc(1, sizeof(*pdsch_Config));
@@ -1701,30 +1717,43 @@ static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap, int bwp
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->present = NR_SetupRelease_DMRS_DownlinkConfig_PR_setup;
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup = calloc(1, sizeof(*pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup));
NR_DMRS_DownlinkConfig_t *dmrs_DownlinkForPDSCH_MappingTypeA = pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
if (ptrs) {
NR_PTRS_DownlinkConfig_t *ptrs_config = config_dlptrs(ptrs);
if (configuration->ptrs && !two_codewords) {
NR_PTRS_DownlinkConfig_t *ptrs_config = config_dlptrs(configuration->ptrs);
NR_SetupRelease_PTRS_DownlinkConfig_t *phaseTrackingRS = calloc(1, sizeof(*phaseTrackingRS));
phaseTrackingRS->present = NR_SetupRelease_PTRS_DownlinkConfig_PR_setup;
phaseTrackingRS->choice.setup = ptrs_config;
dmrs_DownlinkForPDSCH_MappingTypeA->phaseTrackingRS = phaseTrackingRS;
}
if (configuration->ptrs && two_codewords)
LOG_W(NR_MAC, "PDSCH PTRS is disabled for rank 5--8 until two-codeword PTRS is supported\n");
dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_Type = NULL;
dmrs_DownlinkForPDSCH_MappingTypeA->maxLength = NULL;
if (two_codewords)
asn1cCallocOne(dmrs_DownlinkForPDSCH_MappingTypeA->maxLength, NR_DMRS_DownlinkConfig__maxLength_len2);
else
dmrs_DownlinkForPDSCH_MappingTypeA->maxLength = NULL;
dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID0 = NULL;
dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID1 = NULL;
dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = calloc(1, sizeof(*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition));
// TODO possible improvement is to select based on some input additional position
*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = NR_DMRS_DownlinkConfig__dmrs_AdditionalPosition_pos1;
AssertFatal(configuration->pdsch_dmrs_additional_position == 0 || configuration->pdsch_dmrs_additional_position == 1,
"Invalid PDSCH DMRS additional position %d\n",
configuration->pdsch_dmrs_additional_position);
*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition =
configuration->pdsch_dmrs_additional_position == 0
? NR_DMRS_DownlinkConfig__dmrs_AdditionalPosition_pos0
: NR_DMRS_DownlinkConfig__dmrs_AdditionalPosition_pos1;
pdsch_Config->dataScramblingIdentityPDSCH = NULL;
pdsch_Config->resourceAllocation = NR_PDSCH_Config__resourceAllocation_resourceAllocationType1;
if (two_codewords)
asn1cCallocOne(pdsch_Config->maxNrofCodeWordsScheduledByDCI,
NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2);
pdsch_Config->prb_BundlingType.present = NR_PDSCH_Config__prb_BundlingType_PR_staticBundling;
pdsch_Config->prb_BundlingType.choice.staticBundling = calloc(1, sizeof(*pdsch_Config->prb_BundlingType.choice.staticBundling));
pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize = calloc(1, sizeof(*pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize));
*pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize = NR_PDSCH_Config__prb_BundlingType__staticBundling__bundleSize_wideband;
if (do_TCI) {
if (configuration->do_TCI) {
int n_ssb = 0;
if (!pdsch_Config->tci_StatesToAddModList)
pdsch_Config->tci_StatesToAddModList=calloc(1,sizeof(*pdsch_Config->tci_StatesToAddModList));
@@ -1840,7 +1869,7 @@ static NR_BWP_Downlink_t *config_downlinkBWP(const NR_ServingCellConfigCommon_t
asn1cSeqAdd(&bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList->list, ss3);
bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToReleaseList = NULL;
bwp->bwp_Dedicated->pdsch_Config = config_pdsch(ssb_bitmap, bwp->bwp_Id, configuration->do_TCI, configuration->ptrs);
bwp->bwp_Dedicated->pdsch_Config = config_pdsch(ssb_bitmap, bwp->bwp_Id, configuration);
set_dl_mcs_table(bwp->bwp_Common->genericParameters.subcarrierSpacing,
force_256qam_off ? NULL : uecap,
@@ -3389,7 +3418,7 @@ static NR_BWP_DownlinkDedicated_t *configure_initial_dl_bwp(const NR_ServingCell
asn1cSeqAdd(&pdcch_Config->searchSpacesToAddModList->list, ss2);
bwp_Dedicated->pdcch_Config->choice.setup = pdcch_Config;
bwp_Dedicated->pdsch_Config = config_pdsch(bitmap, 0, configuration->do_TCI, configuration->ptrs);
bwp_Dedicated->pdsch_Config = config_pdsch(bitmap, 0, configuration);
// we might call configuration of initial BWP for BWP switch when we already have UE capabilities
set_dl_mcs_table(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.subcarrierSpacing,
configuration->force_256qam_off ? NULL : uecap,
@@ -4062,7 +4091,7 @@ NR_CellGroupConfig_t *get_default_secondaryCellGroup(const NR_ServingCellConfigC
configDedicated->initialDownlinkBWP = calloc(1, sizeof(*configDedicated->initialDownlinkBWP));
configDedicated->initialDownlinkBWP->pdcch_Config = NULL;
configDedicated->initialDownlinkBWP->pdsch_Config = config_pdsch(bitmap, 0, configuration->do_TCI, configuration->ptrs);
configDedicated->initialDownlinkBWP->pdsch_Config = config_pdsch(bitmap, 0, configuration);
configDedicated->initialDownlinkBWP->sps_Config = NULL; // calloc(1,sizeof(struct NR_SetupRelease_SPS_Config));
configDedicated->initialDownlinkBWP->radioLinkMonitoringConfig = NULL;
+3 -3
View File
@@ -123,7 +123,7 @@ nr_rlc_entity_t *new_nr_rlc_entity_am(int rx_maxsize,
/* let's take average over the last 100 milliseconds
* initial_size of 1024 (packets) is arbitrary
*/
ret->common.txsdu_avg_time_to_tx = time_average_new(100 * 1000, 1024);
ret->common.txsdu_avg_time_to_tx = time_average_new(1000 * 1000, 1024);
ret->rx = nr_rlc_new_rx_manager(1 << (sn_field_length - 1));
@@ -176,7 +176,7 @@ nr_rlc_entity_t *new_nr_rlc_entity_um(int rx_maxsize,
/* let's take average over the last 100 milliseconds
* initial_size of 1024 is arbitrary
*/
ret->common.txsdu_avg_time_to_tx = time_average_new(100 * 1000, 1024);
ret->common.txsdu_avg_time_to_tx = time_average_new(1000 * 1000, 1024);
return (nr_rlc_entity_t *)ret;
}
@@ -215,7 +215,7 @@ nr_rlc_entity_t *new_nr_rlc_entity_tm(int tx_maxsize,
/* let's take average over the last 100 milliseconds
* initial_size of 1024 is arbitrary
*/
ret->common.txsdu_avg_time_to_tx = time_average_new(100 * 1000, 1024);
ret->common.txsdu_avg_time_to_tx = time_average_new(1000 * 1000, 1024);
return (nr_rlc_entity_t *)ret;
}
+8 -1
View File
@@ -1023,8 +1023,15 @@ void nr_rlc_activate_avg_time_to_tx(const int ue_id,
}
if (rb != NULL) {
// Reset the accumulator only on an off->on transition, NOT on every call.
// KPM measurement fills call this once per report period (after reading the
// stats); resetting on every call wiped the sliding-window average before it
// could accumulate, forcing a fallback to the noisy instantaneous HOL
// snapshot. The window self-expires old samples, so no periodic reset is
// needed -- activate once and let it accumulate continuously.
if (is_on && !rb->avg_time_is_on)
time_average_reset(rb->txsdu_avg_time_to_tx);
rb->avg_time_is_on = is_on;
time_average_reset(rb->txsdu_avg_time_to_tx);
} else {
LOG_E(RLC, "Radio Bearer (channel ID %d) is NULL for UE %d\n", channel_id, ue_id);
}
+3 -2
View File
@@ -145,6 +145,7 @@ static int8_t handle_dlsch(NR_UE_MAC_INST_t *mac, nr_downlink_indication_t *dl_i
if (mac->ra.ra_state != nrRA_WAIT_RAR) // no HARQ for MSG2
update_harq_status(mac,
dl_info->rx_ind->rx_indication_body[pdu_id].pdsch_pdu.harq_pid,
dl_info->rx_ind->rx_indication_body[pdu_id].pdsch_pdu.cw_idx,
dl_info->rx_ind->rx_indication_body[pdu_id].pdsch_pdu.ack_nack);
if(dl_info->rx_ind->rx_indication_body[pdu_id].pdsch_pdu.ack_nack)
nr_ue_send_sdu(mac, dl_info, pdu_id);
@@ -167,9 +168,9 @@ static int8_t handle_l1_measurements(NR_UE_MAC_INST_t *mac, frame_t frame, int s
return 0;
}
void update_harq_status(NR_UE_MAC_INST_t *mac, uint8_t harq_pid, uint8_t ack_nack)
void update_harq_status(NR_UE_MAC_INST_t *mac, uint8_t harq_pid, int cw_idx, uint8_t ack_nack)
{
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[harq_pid];
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[harq_pid][cw_idx];
if (current_harq->active) {
LOG_D(PHY,"Updating harq_status for harq_id %d, ack/nak %d\n", harq_pid, current_harq->ack);
+13
View File
@@ -84,6 +84,10 @@
#ifdef E2_AGENT
#include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h"
/* PHY simulators link RRC with E2 enabled, but do not link the RC service.
* Keep RC's real mutex when present and avoid a hard simulator dependency. */
extern void rc_teardown_lock(void) __attribute__((weak));
extern void rc_teardown_unlock(void) __attribute__((weak));
#define E2_AGENT_SIGNAL_DL_DCCH_RRC_MSG(BUF, LEN, ID) \
do { \
byte_array_t buffer_ba = {.len = LEN}; \
@@ -2734,7 +2738,16 @@ void rrc_remove_ue(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context_p)
nr_pdcp_remove_UE(UE->rrc_ue_id);
LOG_I(NR_RRC, "removed UE CU UE ID %u/RNTI %04x \n", UE->rrc_ue_id, UE->rnti);
rrc_delete_ue_data(UE);
#ifdef E2_AGENT
const bool rc_lock_available = rc_teardown_lock != NULL && rc_teardown_unlock != NULL;
if (rc_lock_available)
rc_teardown_lock(); /* block in-flight RC indications during teardown */
#endif
rrc_gNB_remove_ue_context(rrc, ue_context_p);
#ifdef E2_AGENT
if (rc_lock_available)
rc_teardown_unlock();
#endif
}
static void rrc_CU_process_ue_context_release_complete(MessageDef *msg_p)
+22
View File
@@ -227,6 +227,28 @@ void rxAddInput(c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_d
double Doppler_phase_cur = channelDesc->Doppler_phase_cur[rxAnt];
Doppler_phase_cur -= 2 * M_PI * round(Doppler_phase_cur / (2 * M_PI));
/* random_channel() defines AWGN as a one-tap identity matrix. The generic
* convolution below nevertheless scans every TX branch for every RX sample,
* turning an 8x8 RFsim link into 64 complex multiplies per sample. Select
* the sole non-zero branch directly. This is the exact same channel model;
* it only removes multiplication by the known zeros. */
if (channelDesc->modelid == AWGN && channelDesc->channel_length == 1
&& channelDesc->Doppler_phase_inc == 0.0) {
const int txAnt = rxAnt % nbTx;
const struct complexd channel = channelDesc->ch[rxAnt + (txAnt * channelDesc->nb_rx)][0];
const bool noise_rounds_to_zero = noise_per_sample < 1e-6;
for (int i = 0; i < nbSamples; i++) {
const struct complex16 tx = input_sig[txAnt][i];
const double rx_r = tx.r * channel.r - tx.i * channel.i;
const double rx_i = tx.i * channel.r + tx.r * channel.i;
after_channel_sig[i].r += rx_r * pathLossLinear
+ (noise_rounds_to_zero ? 0.0 : noise_per_sample * gaussZiggurat(0.0, 1.0));
after_channel_sig[i].i += rx_i * pathLossLinear
+ (noise_rounds_to_zero ? 0.0 : noise_per_sample * gaussZiggurat(0.0, 1.0));
}
return;
}
for (int i = 0; i < nbSamples; i++) {
cf_t *out_ptr = after_channel_sig + i;
struct complexd rx_tmp = {0};
+180 -96
View File
@@ -56,6 +56,14 @@ extern int get_currentchannels_type(const char *buf,
#define SEND_BUFF_SIZE 100000000 // Socket buffer size
#define MAX_BEAMS 64
// The RF simulator normally transports c16_t samples verbatim. At 106 PRB,
// eight TX ports exceed a 10-Gbit/s link before TCP overhead. IQ8 is an
// explicitly enabled wire encoding: a block is packed only when every I/Q
// component is exactly representable as int8_t, otherwise that block is sent
// in the legacy c16_t format. The magic value avoids confusing this with any
// future use of the otherwise-unused option fields in samplesBlockHeader_t.
static constexpr uint32_t RFSIM_OPTION_IQ8 = 0x49513831U; // "IQ81"
// Simulator role
typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole;
@@ -81,7 +89,8 @@ typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole;
#define RFSIM_CONFIG_HELP_OPTIONS \
" list of comma separated options to enable rf simulator functionalities. Available options: \n" \
" chanmod: enable channel modelisation\n" \
" saviq: enable saving written iqs to a file\n"
" saviq: enable saving written iqs to a file\n" \
" iq8: losslessly pack int8-representable IQ blocks on the wire\n"
#define simOpt PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED
#define simBool PARAMFLAG_BOOL | PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED
@@ -161,6 +170,7 @@ typedef struct buffer_s {
rfsim_packet_t *packet_ptr;
size_t payload_sz;
size_t remainToTransferBeam;
bool packet_iq8;
std::queue<rfsim_packet_t *> received_packets;
} buffer_t;
@@ -191,6 +201,12 @@ typedef struct {
double rx_freq;
double tx_bw;
int channelmod;
bool iq8;
bool iq8_tx_logged;
bool iq8_fallback_logged;
bool iq8_rx_logged;
uint64_t iq8_tx_blocks;
uint64_t iq8_raw_blocks;
double chan_pathloss;
double chan_forgetfact;
uint64_t chan_offset;
@@ -362,6 +378,15 @@ static buffer_t *allocCirBuf(rfsimulator_state_t *bridge, int sock)
set_channeldesc_owner(ptr->channel_model, RFSIMU_MODULEID);
set_channeldesc_direction(ptr->channel_model, bridge->role == SIMU_ROLE_SERVER);
// nakaolab Fix 9: ensure RNG is initialized before random_channel runs in this thread
{
static thread_local bool rng_initialized = false;
if (!rng_initialized) {
randominit();
set_taus_seed((unsigned int)(uintptr_t)pthread_self());
rng_initialized = true;
}
}
random_channel(ptr->channel_model, false);
LOG_I(HW, "Random channel %s in rfsimulator activated\n", modelname);
}
@@ -454,6 +479,49 @@ static void fullwrite(int fd, void *_buf, ssize_t count, rfsimulator_state_t *t)
}
}
static bool pack_iq8_block(void ***samplesVoid,
const std::vector<size_t> &beam_indices,
int nbAnt,
int nsamps,
std::vector<int8_t> &packed,
int *max_abs)
{
const size_t complex_samples = (size_t)beam_indices.size() * nbAnt * nsamps;
packed.resize(complex_samples * 2);
size_t out = 0;
*max_abs = 0;
for (size_t beam : beam_indices) {
for (int ant = 0; ant < nbAnt; ant++) {
const sample_t *in = static_cast<const sample_t *>(samplesVoid[beam][ant]);
for (int sample = 0; sample < nsamps; sample++) {
const int real = in[sample].r;
const int imag = in[sample].i;
*max_abs = std::max(*max_abs, std::max(std::abs(real), std::abs(imag)));
if (real < INT8_MIN || real > INT8_MAX || imag < INT8_MIN || imag > INT8_MAX) {
packed.clear();
return false;
}
packed[out++] = static_cast<int8_t>(real);
packed[out++] = static_cast<int8_t>(imag);
}
}
}
return true;
}
static void unpack_iq8_block_in_place(char *payload, size_t complex_samples)
{
int8_t *packed = reinterpret_cast<int8_t *>(payload);
sample_t *unpacked = reinterpret_cast<sample_t *>(payload);
// Expanding from the end keeps unread packed bytes intact.
for (size_t sample = complex_samples; sample-- > 0;) {
const int16_t real = packed[2 * sample];
const int16_t imag = packed[2 * sample + 1];
unpacked[sample].r = real;
unpacked[sample].i = imag;
}
}
static float get_rx_gain_db(rfsimulator_state_t *rfsimulator, uint rx_beam, uint tx_beam)
{
if (!rfsimulator->beam_ctrl->enable_beams) {
@@ -565,12 +633,20 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator)
break;
} else if (strcmp(rfsimuParam[p].strlistptr[i], "chanmod") == 0) {
init_channelmod();
load_channellist(rfsimulator->tx_num_channels,
// Channel model nb_tx must cover the peer's TX count, not just ours.
// E.g., UE has tx=1, rx=4, but gNB sends 4 TX streams. Without this fix,
// the UE-side channel model is (1,4) and only 1 TX stream is processed,
// dropping 3 of the 4 layers — RI gets capped at 1 instead of 4.
load_channellist(std::max(rfsimulator->tx_num_channels, rfsimulator->rx_num_channels),
rfsimulator->rx_num_channels,
rfsimulator->sample_rate,
rfsimulator->rx_freq,
rfsimulator->tx_bw);
rfsimulator->channelmod = true;
} else if (strcmp(rfsimuParam[p].strlistptr[i], "iq8") == 0) {
rfsimulator->iq8 = true;
LOG_I(HW,
"RFsim IQ8 wire packing enabled (exact int8 blocks, automatic c16 fallback)\n");
} else {
fprintf(stderr, "unknown rfsimulator option: %s\n", rfsimuParam[p].strlistptr[i]);
exit(-1);
@@ -671,7 +747,7 @@ static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt,
if (b->channel_model->model_name == NULL)
continue;
if (b->conn_sock >= 0 && (strcmp(b->channel_model->model_name, modelname) == 0)) {
channel_desc_t *newmodel = new_channel_desc_scm(t->tx_num_channels,
channel_desc_t *newmodel = new_channel_desc_scm(std::max(t->tx_num_channels, t->rx_num_channels),
t->rx_num_channels,
static_cast<SCM_t>(channelmod),
t->sample_rate,
@@ -962,23 +1038,58 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t,
mutexlock(t->Sockmutex);
LOG_D(HW, "Sending %d samples at time: %ld, nbAnt %d\n", nsamps, timestamp, nbAnt);
const int num_beams = tx_beams.size();
AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n");
// Send beams in order of beam index. This is required for beam_map to work correctly on the receiver side.
std::vector<size_t> indices(tx_beams.size());
std::iota(indices.begin(), indices.end(), 0);
std::sort(indices.begin(), indices.end(), [&](size_t i, size_t j) { return tx_beams[i] < tx_beams[j]; });
static thread_local std::vector<int8_t> iq8_payload;
int max_abs = 0;
const bool use_iq8 = t->iq8 && pack_iq8_block(samplesVoid, indices, nbAnt, nsamps, iq8_payload, &max_abs);
if (use_iq8) {
t->iq8_tx_blocks++;
if (!t->iq8_tx_logged) {
LOG_I(HW,
"RFsim IQ8 first packed TX block: samples=%d antennas=%d beams=%d max_abs=%d wire=%zu/raw=%zu bytes\n",
nsamps,
nbAnt,
num_beams,
max_abs,
iq8_payload.size(),
sampleToByte(nsamps, nbAnt) * (size_t)num_beams);
t->iq8_tx_logged = true;
}
} else if (t->iq8) {
t->iq8_raw_blocks++;
if (!t->iq8_fallback_logged) {
LOG_W(HW,
"RFsim IQ8 raw fallback: an IQ component is outside [-128,127] (observed_abs=%d)\n",
max_abs);
t->iq8_fallback_logged = true;
}
}
for (int i = 0; i < MAX_FD_RFSIMU; i++) {
buffer_t *b = &t->buf[i];
if (b->conn_sock >= 0) {
samplesBlockHeader_t header = {(uint32_t)nsamps, (uint32_t)nbAnt, (uint64_t)timestamp, 0, 0, beams_to_beam_map(tx_beams)};
samplesBlockHeader_t header = {(uint32_t)nsamps,
(uint32_t)nbAnt,
(uint64_t)timestamp,
0,
use_iq8 ? RFSIM_OPTION_IQ8 : 0,
beams_to_beam_map(tx_beams)};
fullwrite(b->conn_sock, &header, sizeof(header), t);
int num_beams = tx_beams.size();
// Send beams in order of beam index. This is required for beam_map to work correctly on the receiver side.
std::vector<size_t> indices(tx_beams.size());
std::iota(indices.begin(), indices.end(), 0);
std::sort(indices.begin(), indices.end(), [&](size_t i, size_t j) { return tx_beams[i] < tx_beams[j]; });
AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n");
for (int beam = 0; beam < num_beams; beam++) {
for (int a = 0; a < nbAnt; a++) {
sample_t *in = (sample_t *)samplesVoid[indices[beam]][a];
fullwrite(b->conn_sock, (void *)in, sampleToByte(nsamps, 1), t);
if (use_iq8) {
fullwrite(b->conn_sock, iq8_payload.data(), iq8_payload.size(), t);
} else {
for (int beam = 0; beam < num_beams; beam++) {
for (int a = 0; a < nbAnt; a++) {
sample_t *in = (sample_t *)samplesVoid[indices[beam]][a];
fullwrite(b->conn_sock, (void *)in, sampleToByte(nsamps, 1), t);
}
}
}
}
@@ -1093,6 +1204,9 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_
{
b->headerMode = false; // We got the header
AssertFatal(b->th.nbAnt != 0, "Number of antennas not set\n");
AssertFatal(b->th.option_flag == 0 || b->th.option_flag == RFSIM_OPTION_IQ8,
"Unsupported RFsim sample encoding flag 0x%08x\n",
b->th.option_flag);
if (b->nbAnt != b->th.nbAnt) {
LOG_A(HW, "RFsim: Number of antennas changed from %d to %d\n", b->nbAnt, b->th.nbAnt);
b->nbAnt = b->th.nbAnt;
@@ -1119,7 +1233,9 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_
b->packet_ptr = static_cast<rfsim_packet_t *>(calloc_or_fail(1, payload_sz + sizeof(samplesBlockHeader_t)));
b->packet_ptr->header = b->th;
b->transferPtr = b->packet_ptr->payload;
b->remainToTransfer = payload_sz;
b->payload_sz = payload_sz;
b->packet_iq8 = b->th.option_flag == RFSIM_OPTION_IQ8;
b->remainToTransfer = b->packet_iq8 ? payload_sz / 2 : payload_sz;
return;
}
@@ -1261,6 +1377,17 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time)
b->remainToTransfer = sizeof(samplesBlockHeader_t);
if (!b->trashingPacket) {
if (b->packet_iq8) {
const size_t complex_samples = b->payload_sz / sizeof(sample_t);
unpack_iq8_block_in_place(b->packet_ptr->payload, complex_samples);
if (!t->iq8_rx_logged) {
LOG_I(HW,
"RFsim IQ8 first received block: wire=%zu/raw=%zu bytes, restored exactly to c16\n",
b->payload_sz / 2,
b->payload_sz);
t->iq8_rx_logged = true;
}
}
b->lastReceivedTS = b->th.timestamp + b->th.size;
LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS);
b->received_packets.emplace(b->packet_ptr);
@@ -1268,6 +1395,7 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time)
free(b->packet_ptr);
}
b->packet_ptr = NULL;
b->packet_iq8 = false;
b->trashingPacket = false;
}
}
@@ -1283,6 +1411,15 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t,
int rx_beam_id,
bool is_first_beam)
{
// nakaolab Fix 9: Thread-local RNG init.
// random_channel/gaussZiggurat may run in this thread; randominit() uses
// thread_local storage, so each new RX thread needs its own init.
static thread_local bool rng_initialized = false;
if (!rng_initialized) {
randominit();
set_taus_seed((unsigned int)(uintptr_t)pthread_self());
rng_initialized = true;
}
cf_t temp_array[nbAnt][nsamps];
bool channel_modelling = false;
// Add all input nodes signal in the output buffer
@@ -1306,7 +1443,23 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t,
}
const uint64_t channel_offset = ptr->channel_model->channel_offset;
const uint64_t channel_length = ptr->channel_model->channel_length;
std::vector<std::vector<c16_t>> ant_buffers(ptr->nbAnt, std::vector<c16_t>(nsamps + channel_length - 1, {0, 0}));
// The UE-side channel descriptor is provisioned for the largest peer
// width it may receive (for example 8 TX when the UE has 8 RX). A
// matched experiment can still connect a narrower 4-TX gNB. Keep an
// input buffer for every descriptor TX port and leave absent peer
// ports zero; rxAddInput() iterates channel_model->nb_tx and therefore
// must not be handed the shorter peer-width pointer array.
const uint model_tx = ptr->channel_model->nb_tx;
AssertFatal(ptr->nbAnt <= model_tx,
"RFsim peer has %u TX ports but channel model supports only %u\n",
ptr->nbAnt,
model_tx);
AssertFatal(nbAnt <= ptr->channel_model->nb_rx,
"RFsim receiver has %d RX ports but channel model supports only %u\n",
nbAnt,
ptr->channel_model->nb_rx);
std::vector<std::vector<c16_t>> ant_buffers(model_tx,
std::vector<c16_t>(nsamps + channel_length - 1, {0, 0}));
c16_t *input[ant_buffers.size()];
for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) {
input[aatx] = ant_buffers[aatx].data();
@@ -1324,29 +1477,7 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t,
rxAddInput(input, temp_array[aarx], aarx, ptr->channel_model, nsamps);
}
} else {
/* nakaolab MU-MIMO: when OAI_UE_CHANNEL_CLASS is set in the env, the UE
* applies a deterministic rank-1 channel whose dominant right-singular-vector
* matches Type I 4-port codebook entry w_class. This makes the UE report a
* stable PMI = class, giving the gNB scheduler natural PMI diversity across
* UEs assigned to different classes. Four classes give four mutually-orthogonal
* codewords, supporting up to 4-way MU-MIMO.
*
* Codeword w_k[a] for 4-port rank-1 (Type I, N1=2, N2=1):
* w_0 = [1, 1, 1, 1]/2
* w_1 = [1, j, -1, -j]/2
* w_2 = [1, -1, 1, -1]/2
* w_3 = [1, -j, -1, j]/2
* The 4x4 channel H^(k) is rank-1: H^(k)[r,a] = w_k[a]* (per-row constant), so
* the dominant right singular vector is w_k. */
static int ue_class_cached = -2;
if (ue_class_cached == -2) {
const char *env = getenv("OAI_UE_CHANNEL_CLASS");
ue_class_cached = (env && atoi(env) >= 0 && atoi(env) < 4) ? atoi(env) : -1;
if (ue_class_cached >= 0)
LOG_W(HW, "[nakaolab MU-MIMO] rfsim DL channel: orthogonal class %d\n", ue_class_cached);
}
if (is_first_beam && is_first_peer && (ptr->nbAnt == 1 || nbAnt == 1) && ue_class_cached < 0) {
if (is_first_beam && is_first_peer && (ptr->nbAnt == 1 || nbAnt == 1)) {
// optimization: The buffer is uninitialized so samples can be written directly in the buffer
combine_received_beams(t, ptr->received_packets, timestamp - t->chan_offset, 1, nsamps, rx_beam_id, samples);
} else {
@@ -1356,64 +1487,17 @@ static void rfsimulator_read_internal(rfsimulator_state_t *t,
input[aatx] = ant_buffers[aatx].data();
}
combine_received_beams(t, ptr->received_packets, timestamp - t->chan_offset, ptr->nbAnt, nsamps, rx_beam_id, input);
if (ue_class_cached >= 0 && ant_buffers.size() == 4) {
/* nakaolab MU-MIMO: per-UE channel that combines the default symmetric
* rfsim kernel with a class-specific TX-antenna weighting w_k[a]:
*
* H^(k)[r, a] = H_orig[r,a] * w_k[a]
*
* The default kernel H_orig[r,a] = (a==r ? 1.0 : 0.2/|a-r|) is full-rank,
* preserving SSB and reference-signal sync (signal reaches all RX). The
* class-specific weighting rotates each TX antenna's contribution
* differently per class, so the optimal PMI for class k is exactly
* codeword w_k from the Type I 4-port codebook (modulo diagonal scaling).
*
* w_0 = [1, 1, 1, 1]/2
* w_1 = [1, j, -1, -j]/2
* w_2 = [1, -1, 1, -1]/2
* w_3 = [1, -j, -1, j]/2 */
static const double W_RE[4][4] = {
{ 0.5, 0.5, 0.5, 0.5 },
{ 0.5, 0.0, -0.5, 0.0 },
{ 0.5, -0.5, 0.5, -0.5 },
{ 0.5, 0.0, -0.5, 0.0 }
};
static const double W_IM[4][4] = {
{ 0.0, 0.0, 0.0, 0.0 },
{ 0.0, 0.5, 0.0, -0.5 },
{ 0.0, 0.0, 0.0, 0.0 },
{ 0.0, -0.5, 0.0, 0.5 }
};
const int k = ue_class_cached;
for (int aarx = 0; aarx < nbAnt; aarx++) {
for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) {
const uint32_t ant_diff = (uint32_t)std::abs(aatx - aarx);
const double h_orig = ant_diff ? (0.2 / ant_diff) : 1.0;
/* Effective channel coeff: h_orig * w_k[a] (complex multiply). */
const double hre = h_orig * W_RE[k][aatx];
const double him = h_orig * W_IM[k][aatx];
for (int i = 0; i < nsamps; i++) {
const double xr = ant_buffers[aatx][i].r;
const double xi = ant_buffers[aatx][i].i;
samples[aarx][i].r += (int16_t)(hre * xr - him * xi);
samples[aarx][i].i += (int16_t)(hre * xi + him * xr);
}
}
for (int aarx = 0; aarx < nbAnt; aarx++) {
double H_awgn_mimo_coeff[ant_buffers.size()];
for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) {
uint32_t ant_diff = std::abs(aatx - aarx);
H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0;
}
} else {
for (int aarx = 0; aarx < nbAnt; aarx++) {
double H_awgn_mimo_coeff[ant_buffers.size()];
for (int aatx = 0; aatx < (int)ant_buffers.size(); aatx++) {
uint32_t ant_diff = std::abs(aatx - aarx);
H_awgn_mimo_coeff[aatx] = ant_diff ? (0.2 / ant_diff) : 1.0;
}
for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) {
for (int i = 0; i < nsamps; i++) {
samples[aarx][i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx];
samples[aarx][i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx];
}
for (uint aatx = 0; aatx < ant_buffers.size(); aatx++) {
for (int i = 0; i < nsamps; i++) {
samples[aarx][i].r += ant_buffers[aatx][i].r * H_awgn_mimo_coeff[aatx];
samples[aarx][i].i += ant_buffers[aatx][i].i * H_awgn_mimo_coeff[aatx];
}
}
}
@@ -369,7 +369,7 @@
<supportedBandwidthDL>
<fr1><mhz100/></fr1>
</supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH>
<maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC>
@@ -378,7 +378,7 @@
<fr1><mhz100/></fr1>
</supportedBandwidthDL>
<channelBW-90mhz><supported/></channelBW-90mhz>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH>
<maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC>
@@ -394,7 +394,7 @@
<supportedBandwidthDL>
<fr1><mhz40/></fr1>
</supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH>
<maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC>
@@ -402,7 +402,7 @@
<supportedBandwidthDL>
<fr1><mhz40/></fr1>
</supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH>
<maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC>
@@ -410,7 +410,7 @@
<supportedBandwidthDL>
<fr1><mhz80/></fr1>
</supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH>
<maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC>
@@ -418,7 +418,7 @@
<supportedBandwidthDL>
<fr1><mhz20/></fr1>
</supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH>
<maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC>
@@ -434,7 +434,7 @@
<supportedBandwidthDL>
<fr1><mhz20/></fr1>
</supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH>
<maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC>
@@ -450,7 +450,7 @@
<supportedBandwidthDL>
<fr1><mhz30/></fr1>
</supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH>
<maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC>
@@ -458,7 +458,7 @@
<supportedBandwidthDL>
<fr1><mhz60/></fr1>
</supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH>
<maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC>
</featureSetsDownlinkPerCC>