This commit is contained in:
2026-09-19 23:28:08 +09:00
parent c50fe1af53
commit 67717ed5c1
43 changed files with 3145 additions and 294 deletions
+189
View File
@@ -13,10 +13,199 @@
#include "nr_common.h" #include "nr_common.h"
#include <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
#include <string.h>
#define C_SRS_NUMBER (64) #define C_SRS_NUMBER (64)
#define B_SRS_NUMBER (4) #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 */ /* 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] = { 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 */ /* B_SRS = 0 B_SRS = 1 B_SRS = 2 B_SRS = 3 */
+58
View File
@@ -9,6 +9,7 @@
#ifndef __COMMON_UTILS_NR_NR_COMMON__H__ #ifndef __COMMON_UTILS_NR_NR_COMMON__H__
#define __COMMON_UTILS_NR_NR_COMMON__H__ #define __COMMON_UTILS_NR_NR_COMMON__H__
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include "assertions.h" #include "assertions.h"
@@ -93,6 +94,63 @@ static inline uint8_t nr_pdsch_layers_per_codeword(uint8_t layers, uint8_t codew
return layers / 2 + (codeword == 1 ? layers % 2 : 0); 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 // Since the IQ samples are represented by SQ15 R+I (see https://en.wikipedia.org/wiki/Q_(number_format)) we need to compensate when
// calcualting signal energy. Instead of shifting each sample right by 15, we can normalize the result in dB scale once its // calcualting signal energy. Instead of shifting each sample right by 15, we can normalize the result in dB scale once its
// calcualted. Signal energy is calculated using RMS^2, where each sample is squared before taking the average of the sum, therefore // calcualted. Signal energy is calculated using RMS^2, where each sample is squared before taking the average of the sum, therefore
+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_dependencies(tests test_nr_common)
add_test(NAME test_nr_common add_test(NAME test_nr_common
COMMAND ./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;
}
+108
View File
@@ -2,6 +2,7 @@
* SPDX-License-Identifier: LicenseRef-CSSL-1.0 * SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/ */
#include <climits>
#include <gtest/gtest.h> #include <gtest/gtest.h>
extern "C" { extern "C" {
#include "nr_common.h" #include "nr_common.h"
@@ -37,6 +38,113 @@ TEST(nr_common, pdsch_codeword_layer_split)
} }
} }
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) int main(int argc, char **argv)
{ {
+67
View File
@@ -0,0 +1,67 @@
FROM ran-base:latest AS ran-build
ARG BUILD_OPTION
RUN rm -Rf /oai-ran
WORKDIR /oai-ran
COPY . .
# build all targets so final targets can be created afterwards
RUN /bin/sh oaienv && \
cd cmake_targets && \
./build_oai --ninja \
--gNB --RU --nrUE -w SIMU -w USRP --build-lib telnetsrv \
--build-e2 --cmake-opt -DKPM_VERSION=KPM_V3_00 --cmake-opt -DE2AP_VERSION=E2AP_V3 \
--cmake-opt -DCMAKE_C_FLAGS="-Werror" --cmake-opt -DCMAKE_CXX_FLAGS="-Werror" $BUILD_OPTION && \
echo "---- ldd on executables ----" && \
ldd ran_build/build/*softmodem* ran_build/build/nr-cuup ran_build/build/oairu && \
echo "---- ldd on shared libraries ----" && \
ldd ran_build/build/*.so
RUN set -eux; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl wget iproute2 iputils-ping dnsutils jq \
iperf iperf3 ffmpeg gdb\
nghttp2-client || true; \
if ! command -v h2load >/dev/null 2>&1; then \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nghttp2 || true; \
fi; \
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).
# 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 && \
cp /oai-ran/openair2/E2AP/flexric/flexric.conf /usr/local/etc/flexric/ 2>/dev/null || true && \
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 && \
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
RUN apt-get update && apt-get install -y --no-install-recommends \
netcat-openbsd python3-pip \
&& pip3 install --break-system-packages paramiko \
&& rm -rf /var/lib/apt/lists/*
# O-RU M-Plane NETCONF server (o-ran-uplane-conf antenna mask control)
# Listens on port 831, translates NETCONF edit-config to internal antenna control
COPY mplane/mplane_server.py /opt/mplane/mplane_server.py
COPY mplane/wg4-capability.json /opt/mplane/wg4-capability.json
COPY mplane/gnb-mplane-entrypoint.sh /opt/mplane/gnb-mplane-entrypoint.sh
RUN chmod +x /opt/mplane/gnb-mplane-entrypoint.sh
EXPOSE 831
ENTRYPOINT ["/bin/bash"]
+6 -1
View File
@@ -246,6 +246,12 @@ int main(int argc, char **argv)
softmodem_verify_mode(get_softmodem_params()); 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 #if T_TRACER
T_Config_Init(); T_Config_Init();
#endif #endif
@@ -490,4 +496,3 @@ int main(int argc, char **argv)
printf("Bye.\n"); printf("Bye.\n");
return 0; return 0;
} }
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# gNB entrypoint with M-Plane NETCONF server
#
# Starts the M-Plane server alongside the gNB process.
# The M-Plane server acts as an O-RU NETCONF endpoint,
# translating o-ran-uplane-conf edit-config to internal antenna control.
#
# Usage: gnb-mplane-entrypoint.sh <gnb-command-args...>
# Example: gnb-mplane-entrypoint.sh /oai-ran/.../nr-softmodem -O /opt/.../gnb.conf --rfsim
set -e
echo "[gnb-mplane] Starting M-Plane NETCONF server (port ${MPLANE_PORT:-831})..."
python3 /opt/mplane/mplane_server.py &
MPLANE_PID=$!
# Give the M-Plane server a moment to start
sleep 1
if ! kill -0 $MPLANE_PID 2>/dev/null; then
echo "[gnb-mplane] WARNING: M-Plane server failed to start, continuing without it"
fi
# Run the gNB command (passed as arguments)
echo "[gnb-mplane] Starting gNB: $@"
exec "$@"
+622
View File
@@ -0,0 +1,622 @@
#!/usr/bin/env python3
"""
O-RU M-Plane NETCONF Server for gNB Antenna Control
Lightweight NETCONF/SSH server implementing the O-RAN WG4 M-Plane interface
for antenna mask configuration (o-ran-uplane-conf.yang).
The gNB acts as an O-RU for antenna control purposes:
- Advertises mplane-supported-trx-control-masks (capability report)
- Accepts tx-array-antenna-mask-config via NETCONF edit-config
- Translates received mask to internal antenna control (localhost:9999)
- Sends mplane-trx-control-ant-mask-update notification (logged)
Protocol:
SSH transport (paramiko) -> NETCONF base:1.0 framing -> XML RPC processing
Architecture:
o1-adapter (O-DU) --NETCONF:831--> mplane_server.py (O-RU) --localhost:9999--> nr_antenna_control.c
"""
import base64
import json
import logging
import os
import re
import signal
import socket
import sys
import threading
import time
import xml.etree.ElementTree as ET
import paramiko
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
MPLANE_PORT = 831
MPLANE_USER = MPLANE_PASS = "netconf"
GNB_CTRL_HOST = "127.0.0.1"
GNB_CTRL_PORT = 9999
WG4_CAPABILITY_FILE = "/opt/mplane/wg4-capability.json"
SSH_HOST_KEY = "/opt/mplane/ssh_host_rsa_key"
NS_NC = "urn:ietf:params:xml:ns:netconf:base:1.0"
NS_UPLANE = "urn:o-ran:uplane-conf:1.0"
NETCONF_END = "]]>]]>" # https://www.rfc-editor.org/rfc/rfc4742 NETCONF base:1.0 message delimiter
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
stream=sys.stdout,
)
log = logging.getLogger("mplane-server")
# WG4 Capability (O-RU supported masks)
def b64_to_mask(b64_str):
return int.from_bytes(base64.b64decode(b64_str), byteorder="big")
def mask_to_b64(mask_int):
byte_len = max(1, (mask_int.bit_length() + 7) // 8)
return base64.b64encode(mask_int.to_bytes(byte_len, byteorder="big")).decode()
class Capability:
"""O-RU capability report: tx-arrays with supported masks."""
def __init__(self):
self.tx_arrays = {} # name -> {"masks": [{index, b64, int}], ...}
self.current_config = {} # array-name -> {"index": i, "mask_int": m}
def load(self, path):
if not (path and os.path.exists(path)):
# Fallback: generate for 4 antennas
masks = []
for n in range(4, 0, -1):
m = (1 << n) - 1
masks.append({"index": 4 - n, "b64": mask_to_b64(m), "int": m})
self.tx_arrays["tx-array-0"] = {"masks": masks, "columns": 4, "layers": 4}
self.current_config["tx-array-0"] = {"index": 0, "mask_int": 0x0F}
log.info("Generated fallback capability (4 antennas)")
return
with open(path) as f:
data = json.load(f) # returns a dict
for arr in data.get("tx-arrays", []): # blank list for fallback
name = arr["name"]
cap = arr.get("mplane-trx-control-txarr-capability-info", {})
masks = []
for m in cap.get("mplane-supported-trx-control-masks", []):
masks.append({
"index": m["index"],
"b64": m["antenna-mask"],
"int": b64_to_mask(m["antenna-mask"]),
})
self.tx_arrays[name] = {
"masks": masks,
"columns": arr.get("number-of-columns", 4),
"layers": arr.get("number-of-array-layers", 4),
}
# Default: all active (index 0)
if masks:
self.current_config[name] = {
"index": masks[0]["index"],
"mask_int": masks[0]["int"],
}
log.info("Loaded capability: %d tx-arrays from %s", len(self.tx_arrays), path)
def resolve_index(self, array_name, index):
arr = self.tx_arrays.get(array_name)
if not arr:
return None, f"unknown tx-array '{array_name}'"
for m in arr["masks"]:
if m["index"] == index:
return m, ""
return None, f"index {index} not in supported masks"
def resolve_mask(self, array_name, mask_int):
arr = self.tx_arrays.get(array_name)
if not arr:
return None, f"unknown tx-array '{array_name}'"
for m in arr["masks"]:
if m["int"] == mask_int:
return m, ""
return None, f"mask 0x{mask_int:02X} not in supported masks"
def get_capability_xml(self):
"""Generate o-ran-uplane-conf operational data XML for <get> response."""
parts = [f'<user-plane-configuration xmlns="{NS_UPLANE}">']
for name, arr in self.tx_arrays.items():
parts.append(f"<tx-arrays><name>{name}</name>")
parts.append(f"<number-of-columns>{arr['columns']}</number-of-columns>")
parts.append(f"<number-of-array-layers>{arr['layers']}</number-of-array-layers>")
parts.append("<mplane-trx-control-txarr-capability-info>")
for m in arr["masks"]:
parts.append("<mplane-supported-trx-control-masks>")
parts.append(f"<index>{m['index']}</index>")
parts.append(f"<antenna-mask>{m['b64']}</antenna-mask>")
parts.append("</mplane-supported-trx-control-masks>")
parts.append("</mplane-trx-control-txarr-capability-info>")
parts.append("</tx-arrays>")
# Current config
for aname, cfg in self.current_config.items():
parts.append("<tx-array-antenna-mask-config>")
parts.append(f"<array-name>{aname}</array-name>")
parts.append(f"<antenna-bitmask-index>{cfg['index']}</antenna-bitmask-index>")
parts.append("</tx-array-antenna-mask-config>")
parts.append("</user-plane-configuration>")
return "".join(parts)
# ---------------------------------------------------------------------------
# gNB internal antenna control client (localhost:9999)
# ---------------------------------------------------------------------------
class GnbCtrlClient:
"""Connects to the gNB internal test socket to apply antenna masks."""
def __init__(self, host, port):
self.host = host
self.port = port
self.sock = None
def connect(self):
for attempt in range(30):
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(5.0)
self.sock.connect((self.host, self.port))
self._recv() # consume welcome
log.info("Connected to gNB control at %s:%d", self.host, self.port)
return True
except (socket.error, ConnectionRefusedError) as e:
log.debug("gNB ctrl connect attempt %d/30: %s", attempt + 1, e)
if self.sock:
self.sock.close()
self.sock = None
time.sleep(2)
log.warning("Cannot connect to gNB control at %s:%d", self.host, self.port)
return False
def send_command(self, cmd):
if not self.sock:
if not self.connect():
return "ERROR: not connected"
try:
self.sock.sendall((cmd + "\n").encode())
time.sleep(0.1)
return self._recv()
except (socket.error, BrokenPipeError):
log.warning("gNB ctrl connection lost, reconnecting...")
self.sock = None
if not self.connect():
return "ERROR: reconnect failed"
self.sock.sendall((cmd + "\n").encode())
time.sleep(0.1)
return self._recv()
def _recv(self):
self.sock.settimeout(0.5)
data = b""
try:
while True:
chunk = self.sock.recv(4096)
if not chunk:
break
data += chunk
except socket.timeout:
pass
return data.decode("utf-8", errors="ignore")
def close(self):
if self.sock:
try:
self.sock.sendall(b"QUIT\n")
except Exception:
pass
self.sock.close()
self.sock = None
# ---------------------------------------------------------------------------
# NETCONF Session Handler
# ---------------------------------------------------------------------------
class NetconfSession:
"""Handle a single NETCONF session on an SSH channel."""
def __init__(self, channel, capability, gnb_ctrl):
self.channel = channel
self.cap = capability
self.gnb = gnb_ctrl
self.session_id = 1
self.running = True
def run(self):
try:
self._send_hello()
hello_msg = self._recv_message()
if hello_msg is None:
log.warning("Client did not send hello")
return
log.info("NETCONF hello exchange complete")
while self.running:
msg = self._recv_message()
if msg is None:
break
self._process_rpc(msg)
except Exception as e:
log.exception("Session error: %s", e)
finally:
try:
self.channel.close()
except Exception:
pass
log.info("NETCONF session closed")
def _send_hello(self):
hello = (
'<?xml version="1.0" encoding="UTF-8"?>'
f'<hello xmlns="{NS_NC}">'
"<capabilities>"
"<capability>urn:ietf:params:netconf:base:1.0</capability>"
"<capability>urn:ietf:params:netconf:capability:writable-running:1.0</capability>"
"</capabilities>"
f"<session-id>{self.session_id}</session-id>"
"</hello>"
)
self._send_message(hello)
def _send_message(self, xml_str):
data = (xml_str + NETCONF_END).encode("utf-8")
self.channel.sendall(data)
def _recv_message(self):
buf = b""
while self.running:
try:
self.channel.settimeout(300)
chunk = self.channel.recv(65536)
if not chunk:
return None
buf += chunk
text = buf.decode("utf-8", errors="ignore")
if NETCONF_END in text:
return text.split(NETCONF_END)[0].strip()
except socket.timeout:
continue
except Exception:
return None
return None
def _process_rpc(self, xml_str):
# Strip XML declaration if present
xml_str = re.sub(r"<\?xml[^?]*\?>", "", xml_str).strip()
try:
root = ET.fromstring(xml_str)
except ET.ParseError as e:
log.warning("Malformed RPC: %s", e)
self._send_error("0", "malformed-message", "rpc")
return
msg_id = root.get("message-id", "0")
# Find the operation element
for child in root:
local_tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag
if local_tag == "edit-config":
self._handle_edit_config(msg_id, child)
elif local_tag == "get":
self._handle_get(msg_id, child)
elif local_tag == "get-config":
self._handle_get_config(msg_id, child)
elif local_tag == "close-session":
self._send_ok(msg_id)
self.running = False
elif local_tag == "lock" or local_tag == "unlock":
self._send_ok(msg_id)
else:
self._send_error(msg_id, "operation-not-supported", "protocol")
return # Only process first operation
def _handle_edit_config(self, msg_id, edit_elem):
"""Handle edit-config for tx-array-antenna-mask-config."""
# Find <config> element
config_elem = None
for child in edit_elem:
local = child.tag.split("}")[-1] if "}" in child.tag else child.tag
if local == "config":
config_elem = child
break
if config_elem is None:
self._send_error(msg_id, "missing-element", "protocol",
"Missing <config> element")
return
# Find tx-array-antenna-mask-config under user-plane-configuration
mask_config = None
for elem in config_elem.iter():
local = elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag
if local == "tx-array-antenna-mask-config":
mask_config = elem
break
if mask_config is None:
# No antenna mask config in this edit - accept silently
self._send_ok(msg_id)
return
# Extract array-name, antenna-bitmask-index or antenna-bitmask
array_name = None
bitmask_index = None
bitmask_b64 = None
for child in mask_config:
local = child.tag.split("}")[-1] if "}" in child.tag else child.tag
if local == "array-name":
array_name = child.text.strip() if child.text else None
elif local == "antenna-bitmask-index":
bitmask_index = int(child.text.strip()) if child.text else None
elif local == "antenna-bitmask":
bitmask_b64 = child.text.strip() if child.text else None
if not array_name:
self._send_error(msg_id, "missing-element", "application",
"Missing array-name in tx-array-antenna-mask-config")
return
# Resolve the mask
resolved = None
if bitmask_index is not None:
resolved, err = self.cap.resolve_index(array_name, bitmask_index)
if not resolved:
self._send_error(msg_id, "invalid-value", "application", err)
return
log.info("M-Plane edit-config: array=%s, antenna-bitmask-index=%d",
array_name, bitmask_index)
elif bitmask_b64 is not None:
mask_int = b64_to_mask(bitmask_b64)
resolved, err = self.cap.resolve_mask(array_name, mask_int)
if not resolved:
self._send_error(msg_id, "invalid-value", "application", err)
return
log.info("M-Plane edit-config: array=%s, antenna-bitmask=%s (0x%02X)",
array_name, bitmask_b64, mask_int)
else:
self._send_error(msg_id, "missing-element", "application",
"Need antenna-bitmask or antenna-bitmask-index")
return
# 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}")
resp_clean = (resp or "").strip()
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: "
"array-name='%s', array-type=TX-ARRAY, "
"updated-antenna-mask-index=%d, updated-antenna-mask=%s",
array_name, resolved["index"], resolved["b64"])
self._send_ok(msg_id)
def _handle_get(self, msg_id, get_elem):
"""Handle <get> - return operational data (capability + current config)."""
data_xml = self.cap.get_capability_xml()
reply = (
f'<rpc-reply xmlns="{NS_NC}" message-id="{msg_id}">'
f"<data>{data_xml}</data>"
"</rpc-reply>"
)
self._send_message(reply)
def _handle_get_config(self, msg_id, get_elem):
"""Handle <get-config> - return running config (current mask config)."""
parts = [f'<user-plane-configuration xmlns="{NS_UPLANE}">']
for aname, cfg in self.cap.current_config.items():
parts.append("<tx-array-antenna-mask-config>")
parts.append(f"<array-name>{aname}</array-name>")
parts.append(f"<antenna-bitmask-index>{cfg['index']}</antenna-bitmask-index>")
parts.append("</tx-array-antenna-mask-config>")
parts.append("</user-plane-configuration>")
reply = (
f'<rpc-reply xmlns="{NS_NC}" message-id="{msg_id}">'
f'<data>{"".join(parts)}</data>'
"</rpc-reply>"
)
self._send_message(reply)
def _send_ok(self, msg_id):
reply = (
f'<rpc-reply xmlns="{NS_NC}" message-id="{msg_id}">'
"<ok/>"
"</rpc-reply>"
)
self._send_message(reply)
def _send_error(self, msg_id, error_tag, error_type, error_message=""):
msg_part = ""
if error_message:
msg_part = f"<error-message>{error_message}</error-message>"
reply = (
f'<rpc-reply xmlns="{NS_NC}" message-id="{msg_id}">'
"<rpc-error>"
f"<error-type>{error_type}</error-type>"
f"<error-tag>{error_tag}</error-tag>"
"<error-severity>error</error-severity>"
f"{msg_part}"
"</rpc-error>"
"</rpc-reply>"
)
self._send_message(reply)
# ---------------------------------------------------------------------------
# SSH Server Interface
# ---------------------------------------------------------------------------
class MplaneSSHServer(paramiko.ServerInterface):
def __init__(self):
self.subsystem_event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == "session":
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def get_allowed_auths(self, username):
return "password"
def check_auth_password(self, username, password):
if username == MPLANE_USER and password == MPLANE_PASS:
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def check_channel_subsystem_request(self, channel, name):
if name == "netconf":
self.subsystem_event.set()
return True
return False
# ---------------------------------------------------------------------------
# Main Server
# ---------------------------------------------------------------------------
def handle_connection(client_sock, addr, host_key, capability, gnb_ctrl):
"""Handle a single SSH/NETCONF client connection."""
log.info("SSH connection from %s:%d", addr[0], addr[1])
transport = None
try:
transport = paramiko.Transport(client_sock)
transport.add_server_key(host_key)
server = MplaneSSHServer()
transport.start_server(server=server)
channel = transport.accept(30)
if channel is None:
log.warning("No channel opened by client")
return
# Wait for NETCONF subsystem request
if not server.subsystem_event.wait(10):
log.warning("Client did not request netconf subsystem")
channel.close()
return
session = NetconfSession(channel, capability, gnb_ctrl)
session.run()
except paramiko.SSHException as e:
log.warning("SSH error from %s: %s", addr[0], e)
except Exception as e:
log.exception("Connection error from %s: %s", addr[0], e)
finally:
if transport:
try:
transport.close()
except Exception:
pass
def main():
log.info("O-RU M-Plane NETCONF server starting")
log.info(" Port: %d", MPLANE_PORT)
log.info(" gNB control: %s:%d (internal)", GNB_CTRL_HOST, GNB_CTRL_PORT)
# Load/generate SSH host key
if os.path.exists(SSH_HOST_KEY):
host_key = paramiko.RSAKey.from_private_key_file(SSH_HOST_KEY)
log.info("Loaded SSH host key from %s", SSH_HOST_KEY)
else:
host_key = paramiko.RSAKey.generate(2048)
os.makedirs(os.path.dirname(SSH_HOST_KEY), exist_ok=True)
host_key.write_private_key_file(SSH_HOST_KEY)
log.info("Generated SSH host key at %s", SSH_HOST_KEY)
# Load WG4 capability
capability = Capability()
capability.load(WG4_CAPABILITY_FILE)
for name, arr in capability.tx_arrays.items():
log.info(" tx-array '%s': %d supported masks", name, len(arr["masks"]))
for m in arr["masks"]:
log.info(" index=%d: %s (0x%02X, %d active)",
m["index"], m["b64"], m["int"], bin(m["int"]).count("1"))
# Connect to gNB internal control
gnb_ctrl = GnbCtrlClient(GNB_CTRL_HOST, GNB_CTRL_PORT)
gnb_ctrl.connect()
# Create server socket
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(("0.0.0.0", MPLANE_PORT))
server_sock.listen(5)
server_sock.settimeout(1.0)
shutdown = threading.Event()
def handle_signal(signum, frame):
log.info("Shutdown signal received")
shutdown.set()
signal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)
log.info("M-Plane NETCONF server ready on port %d", MPLANE_PORT)
while not shutdown.is_set():
try:
client_sock, addr = server_sock.accept()
t = threading.Thread(
target=handle_connection,
args=(client_sock, addr, host_key, capability, gnb_ctrl),
daemon=True,
)
t.start()
except socket.timeout:
continue
except Exception as e:
if not shutdown.is_set():
log.exception("Accept error: %s", e)
log.info("Shutting down...")
server_sock.close()
gnb_ctrl.close()
if __name__ == "__main__":
main()
+17
View File
@@ -0,0 +1,17 @@
{
"tx-arrays": [
{
"name": "tx-array-0",
"number-of-columns": 4,
"number-of-array-layers": 4,
"mplane-trx-control-txarr-capability-info": {
"mplane-supported-trx-control-masks": [
{"index": 0, "antenna-mask": "Dw=="},
{"index": 1, "antenna-mask": "Bw=="},
{"index": 2, "antenna-mask": "Aw=="},
{"index": 3, "antenna-mask": "AQ=="}
]
}
}
]
}
@@ -463,6 +463,11 @@ 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; // 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 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 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; } 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 enum{vrb_to_prb_mapping_non_interleaved = 0, vrb_to_prb_mapping_interleaved = 1} vrb_to_prb_mapping_t;
@@ -748,4 +753,3 @@ typedef struct {
} fapi_nr_config_request_t; } fapi_nr_config_request_t;
#endif #endif
@@ -246,10 +246,31 @@ void nr_polar_info_bit_pattern(uint8_t *ibp,
} }
if (n_PC > 0) { 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++) { for (int n = 0; n < n_PC - n_pc_wm; n++) {
Q_PC_N[n] = Q_I_N[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 // 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++) { for (int n = 0; n <= N - 1; n++) {
ibp[n] = 0; ibp[n] = 0;
for (int m = 0; m <= (K + n_PC) - 1; m++) { 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; 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]) { if (n == Q_PC_N[m]) {
pcbp[n] = 1; pcbp[n] = 1;
break; break;
+98 -14
View File
@@ -56,6 +56,10 @@ static struct {
int port; int port;
nr_antenna_ctrl_backend_t backend; nr_antenna_ctrl_backend_t backend;
int64_t tx_lock_until_ms[NR_MAX_ANTENNAS]; 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 #ifdef OAI_MPLANE
ru_session_t *ru_session; ru_session_t *ru_session;
#else #else
@@ -191,8 +195,8 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
bool force = fields >= 3 && strcmp(option, "FORCE") == 0; bool force = fields >= 3 && strcmp(option, "FORCE") == 0;
uint8_t valid_mask = (1u << g_ctrl.ant_state->num_tx_antennas) - 1u; uint8_t valid_mask = (1u << g_ctrl.ant_state->num_tx_antennas) - 1u;
uint8_t requested = (uint8_t)mask & valid_mask; uint8_t requested = (uint8_t)mask & valid_mask;
uint8_t old = nr_get_tx_antenna_bitmask(g_ctrl.ant_state); uint8_t old_requested = nr_get_requested_tx_antenna_bitmask(g_ctrl.ant_state);
uint8_t waking = requested & ~old; uint8_t waking = requested & ~old_requested;
for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) { 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) { if ((waking & (1u << ant_id)) && tx_lock_remaining_ms(ant_id) > 0 && !force) {
snprintf(response, resp_size, snprintf(response, resp_size,
@@ -201,8 +205,8 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
} }
} }
nr_set_tx_antenna_bitmask(g_ctrl.ant_state, requested); nr_request_tx_antenna_bitmask(g_ctrl.ant_state, requested);
uint8_t muted = old & ~requested; uint8_t muted = old_requested & ~requested;
for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) { for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) {
uint8_t bit = 1u << ant_id; uint8_t bit = 1u << ant_id;
if (muted & bit) if (muted & bit)
@@ -210,9 +214,13 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
if (waking & bit) if (waking & bit)
set_tx_lock(ant_id, 0); 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, snprintf(response, resp_size,
"OK: TX mask 0x%02X -> 0x%02X (TSM=%ds%s)\n", "OK: TX mask 0x%02X -> 0x%02X (active=0x%02X%s, TSM=%ds%s)\n",
old, requested, duration, force ? ", emergency override" : ""); old_requested, requested, active,
pending ? ", HARQ drain pending" : "",
duration, force ? ", emergency override" : "");
} }
else if (strncmp(p, "SET RX MASK ", 12) == 0) { else if (strncmp(p, "SET RX MASK ", 12) == 0) {
unsigned int mask; unsigned int mask;
@@ -225,11 +233,15 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
} }
else if (strcmp(p, "STATUS") == 0) { else if (strcmp(p, "STATUS") == 0) {
bool in_recovery = nr_is_in_recovery_mode(g_ctrl.ant_state); 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, snprintf(response, resp_size,
"=== Antenna Status ===\n" "=== Antenna Status ===\n"
"TX Array: %s\n" "TX Array: %s\n"
" Antennas: %d configured, %d active\n" " Antennas: %d configured, %d active\n"
" Bitmask: 0x%02X\n" " Bitmask: 0x%02X\n"
" Requested bitmask: 0x%02X\n"
" HARQ drain pending: %s\n"
" Antenna 0 protected: %s\n" " Antenna 0 protected: %s\n"
"RX Array: %s\n" "RX Array: %s\n"
" Antennas: %d configured, %d active\n" " Antennas: %d configured, %d active\n"
@@ -240,6 +252,8 @@ static void process_command(const char *cmd, char *response, size_t resp_size)
g_ctrl.ant_state->num_tx_antennas, g_ctrl.ant_state->num_tx_antennas,
nr_get_active_tx_antenna_count(g_ctrl.ant_state), nr_get_active_tx_antenna_count(g_ctrl.ant_state),
nr_get_tx_antenna_bitmask(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->config.protect_antenna0 ? "YES" : "NO",
g_ctrl.ant_state->rx_array_name, g_ctrl.ant_state->rx_array_name,
g_ctrl.ant_state->num_rx_antennas, g_ctrl.ant_state->num_rx_antennas,
@@ -332,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 // Handle a single client connection
static void handle_client(int client_fd) static void handle_client(int client_fd)
{ {
@@ -344,24 +365,28 @@ static void handle_client(int client_fd)
"WARNING: This is a TEST interface, NOT O-RAN compliant!\n" "WARNING: This is a TEST interface, NOT O-RAN compliant!\n"
"For production, use NETCONF via radio/fhi_72/mplane/\n" "For production, use NETCONF via radio/fhi_72/mplane/\n"
"Type HELP for commands.\n\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) { while (g_ctrl.running) {
// Send prompt
send(client_fd, "> ", 2, 0);
// Receive command // Receive command
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0); 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'; 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)); process_command(buffer, response, sizeof(response));
pthread_mutex_unlock(&g_ctrl.command_lock);
// Send response // Send response
if (strlen(response) > 0) { if (strlen(response) > 0) {
send(client_fd, response, strlen(response), 0); send(client_fd, response, strlen(response), MSG_NOSIGNAL);
} }
// Check for quit // Check for quit
@@ -369,12 +394,26 @@ static void handle_client(int client_fd)
strstr(buffer, "EXIT") || strstr(buffer, "exit")) { strstr(buffer, "EXIT") || strstr(buffer, "exit")) {
break; break;
} }
send_client_prompt(client_fd);
} }
close(client_fd); close(client_fd);
LOG_I(PHY, "[ANTENNA_CTRL] Client disconnected\n"); 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 // Server thread function
static void *server_thread(void *arg) static void *server_thread(void *arg)
{ {
@@ -402,7 +441,26 @@ static void *server_thread(void *arg)
LOG_I(PHY, "[ANTENNA_CTRL] Client connected from %s:%d\n", LOG_I(PHY, "[ANTENNA_CTRL] Client connected from %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); 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; return NULL;
@@ -469,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.ant_state = ant_state;
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_TEST_PORT; g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_TEST_PORT;
g_ctrl.backend = ANTENNA_CTRL_BACKEND_TEST_SOCKET; 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 // Create socket
g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0); g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (g_ctrl.server_fd < 0) { if (g_ctrl.server_fd < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create socket: %s\n", strerror(errno)); 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; return false;
} }
@@ -491,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) { 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)); LOG_E(PHY, "[ANTENNA_CTRL] Failed to bind port %d: %s\n", g_ctrl.port, strerror(errno));
close(g_ctrl.server_fd); 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; return false;
} }
@@ -498,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) { if (listen(g_ctrl.server_fd, 1) < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to listen: %s\n", strerror(errno)); LOG_E(PHY, "[ANTENNA_CTRL] Failed to listen: %s\n", strerror(errno));
close(g_ctrl.server_fd); 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; return false;
} }
@@ -507,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"); LOG_E(PHY, "[ANTENNA_CTRL] Failed to create server thread\n");
g_ctrl.running = false; g_ctrl.running = false;
close(g_ctrl.server_fd); 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; return false;
} }
@@ -540,6 +614,16 @@ void nr_antenna_control_stop(void)
// Wait for thread // Wait for thread
pthread_join(g_ctrl.thread, NULL); 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; g_ctrl.backend = ANTENNA_CTRL_BACKEND_NONE;
+102 -32
View File
@@ -36,6 +36,8 @@ void nr_init_antenna_state(nr_antenna_state_t *state,
// Initialize with all configured antennas active // Initialize with all configured antennas active
// Create bitmask with num_tx/num_rx lowest bits set // Create bitmask with num_tx/num_rx lowest bits set
state->tx_antenna_bitmask = (1 << state->num_tx_antennas) - 1; 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; state->rx_antenna_bitmask = (1 << state->num_rx_antennas) - 1;
// Set O-RAN array names // Set O-RAN array names
@@ -76,33 +78,103 @@ void nr_free_antenna_state(nr_antenna_state_t *state)
pthread_mutex_destroy(&state->lock); 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) uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
{ {
if (state == NULL) return 0; if (state == NULL) return 0;
pthread_mutex_lock(&state->lock); pthread_mutex_lock(&state->lock);
const uint8_t old_bitmask = state->tx_antenna_bitmask;
uint8_t old_bitmask = state->tx_antenna_bitmask; state->requested_tx_antenna_bitmask = bitmask & valid_tx_mask(state);
state->tx_mask_update_pending = false;
// Mask to only valid antenna bits set_tx_antenna_bitmask_locked(state, state->requested_tx_antenna_bitmask);
uint8_t valid_mask = (1 << state->num_tx_antennas) - 1;
state->tx_antenna_bitmask = bitmask & valid_mask;
// Update statistics
if (state->tx_antenna_bitmask != old_bitmask) {
state->stats.tx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] TX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
old_bitmask, state->tx_antenna_bitmask, state->tx_array_name,
popcount8(state->tx_antenna_bitmask), state->num_tx_antennas);
}
pthread_mutex_unlock(&state->lock); pthread_mutex_unlock(&state->lock);
return old_bitmask; 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) uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
{ {
if (state == NULL) return 0; if (state == NULL) return 0;
@@ -183,16 +255,16 @@ void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
return; return;
} }
uint8_t new_mask = state->tx_antenna_bitmask & ~(1 << ant_id); uint8_t new_mask = nr_get_requested_tx_antenna_bitmask(state) & ~(1 << ant_id);
nr_set_tx_antenna_bitmask(state, new_mask); nr_request_tx_antenna_bitmask(state, new_mask);
} }
void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id) void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
{ {
if (state == NULL || ant_id >= state->num_tx_antennas) return; if (state == NULL || ant_id >= state->num_tx_antennas) return;
uint8_t new_mask = state->tx_antenna_bitmask | (1 << ant_id); uint8_t new_mask = nr_get_requested_tx_antenna_bitmask(state) | (1 << ant_id);
nr_set_tx_antenna_bitmask(state, new_mask); nr_request_tx_antenna_bitmask(state, new_mask);
} }
void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id) 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) if (state == NULL)
return requested_layers; return requested_layers;
// Reduce MIMO layers to match the number of active TX antennas. /* Cap new transmissions to the target immediately. During a downshift the
// When antennas are muted, the RU blanks the corresponding TX buffers. * physical mask can remain wider for a few HARQ rounds, which is safe; the
// If the scheduler still uses full layer count, the precoded signal is * reverse ordering would erase layers from buffered retransmissions. */
// distributed across all antenna ports (including muted ones), which const int target = popcount8(nr_get_requested_tx_antenna_bitmask(state));
// corrupts the UE's MIMO reception. By reducing layers to match active if (target > 0 && target < requested_layers)
// antennas, the PMI fallback to identity precoding maps layers 1:1 to return target;
// active ports, avoiding signal loss on muted ports.
int active = nr_get_active_tx_antenna_count(state);
if (active > 0 && active < requested_layers)
return active;
return requested_layers; return requested_layers;
} }
@@ -296,6 +364,8 @@ void nr_unmute_all_tx_antennas(nr_antenna_state_t *state)
uint8_t old_mask = state->tx_antenna_bitmask; uint8_t old_mask = state->tx_antenna_bitmask;
uint8_t all_on = (1 << state->num_tx_antennas) - 1; 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; state->tx_antenna_bitmask = all_on;
// Enter recovery mode if we were muted // Enter recovery mode if we were muted
+48 -2
View File
@@ -20,6 +20,12 @@ typedef struct {
uint8_t tx_antenna_bitmask; uint8_t tx_antenna_bitmask;
uint8_t rx_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 /// Total number of configured antennas
uint8_t num_tx_antennas; uint8_t num_tx_antennas;
uint8_t num_rx_antennas; uint8_t num_rx_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); 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) * @brief Set RX antenna bitmask (O-RAN O1 interface equivalent)
* @param state Pointer to antenna state structure * @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 * @brief Get effective number of MIMO layers based on active antennas
* *
* For MIMO, the number of layers is limited by active antennas. * For MIMO, the number of layers is limited by the requested antenna mask.
* This function returns min(requested_layers, active_antennas). * 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 state Pointer to antenna state structure
* @param requested_layers Originally requested number of layers * @param requested_layers Originally requested number of layers
+263 -22
View File
@@ -16,6 +16,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#include <limits.h>
#include <pthread.h>
#include "executables/nr-softmodem-common.h" #include "executables/nr-softmodem-common.h"
#include "nr_transport_proto_ue.h" #include "nr_transport_proto_ue.h"
@@ -24,9 +26,8 @@
#include "PHY/NR_UE_ESTIMATION/filt16a_32.h" #include "PHY/NR_UE_ESTIMATION/filt16a_32.h"
// nakaolab CSI muting study: float Gram matrix + Jacobi eigenvalues for RI and // nakaolab CSI muting study: float Gram matrix + Jacobi eigenvalues for RI and
// spatial-spectrum telemetry. MAX_ANT is eight on this platform; the UE still // spatial-spectrum telemetry. MAX_ANT and the local PDSCH rank ceiling are
// reports at most NR_MAX_NB_LAYERS (four) because that is the supported PDSCH // eight on this branch.
// decoder/scheduler ceiling.
// Sorts eigenvalues in descending order on output. // Sorts eigenvalues in descending order on output.
static void nr_hermitian_eigenvalues(const int N, static void nr_hermitian_eigenvalues(const int N,
float A_re[MAX_ANT][MAX_ANT], float A_re[MAX_ANT][MAX_ANT],
@@ -165,12 +166,91 @@ typedef struct {
int samples; int samples;
int dimensions; int dimensions;
float eig_rel_db[NR_MAX_NB_LAYERS]; 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, // 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 // relative to all configured CSI-RS ports. This is an offline feature for
// testing whether CSI adds value to antenna-muting decisions. // testing whether CSI adds value to antenna-muting decisions.
float prefix_rel_db[4]; float prefix_rel_db[4];
} nr_csi_muting_summary_t; } 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, void nr_squared_matrix_element(int32_t *a,
int32_t *a_sq, int32_t *a_sq,
const unsigned short nb_rb) { const unsigned short nb_rb) {
@@ -527,10 +607,12 @@ static int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
const uint8_t N_ports, const uint8_t N_ports,
c16_t csi_rs_estimated_channel_freq[][N_ports][ue->frame_parms.ofdm_symbol_size], c16_t csi_rs_estimated_channel_freq[][N_ports][ue->frame_parms.ofdm_symbol_size],
const int16_t log2_maxh, const int16_t log2_maxh,
const uint32_t noise_power,
uint8_t *rank_indicator, uint8_t *rank_indicator,
nr_csi_muting_summary_t *muting_summary) nr_csi_muting_summary_t *muting_summary)
{ {
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms; const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
const nr_csi_rank_config_t *rank_config = get_csi_rank_config();
const int16_t cond_dB_threshold = 5; const int16_t cond_dB_threshold = 5;
int count = 0; int count = 0;
*rank_indicator = 0; *rank_indicator = 0;
@@ -555,12 +637,15 @@ static int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
return 0; return 0;
} }
// 2x2 keeps the existing condition-number path below. // Legacy 2x2 keeps the existing condition-number path below. Capacity and
// For >2 ports, use Patches 1-3: float Gram matrix + Jacobi eigenvalues + rank voting. // fixed policies use the same eigen-spectrum path for every MIMO size.
if (!(ue->frame_parms.nb_antennas_rx == 2 && N_ports == 2)) { 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}; int rank_count[NR_MAX_NB_LAYERS] = {0};
double eig_sum[MAX_ANT] = {0}; double eig_sum[MAX_ANT] = {0};
double port_power[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 float eig_ratio_dB_threshold = 8.0f;
const int sc_stride = physical_rank > 4 ? 4 : 1; 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++) { for (int rb = csirs_config_pdu->start_rb; rb < (csirs_config_pdu->start_rb + csirs_config_pdu->nr_of_rbs); rb++) {
@@ -587,6 +672,10 @@ static int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
float eig[MAX_ANT] = {0}; float eig[MAX_ANT] = {0};
nr_hermitian_eigenvalues(N_ports, A_re, A_im, eig); nr_hermitian_eigenvalues(N_ports, A_re, A_im, eig);
if (eig[0] < 1e-6f) continue; 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]++; rank_count[0]++;
for (int r = 0; r < N_ports; r++) for (int r = 0; r < N_ports; r++)
eig_sum[r] += eig[r]; eig_sum[r] += eig[r];
@@ -601,12 +690,24 @@ static int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
} }
int total_sc = rank_count[0]; int total_sc = rank_count[0];
if (total_sc > 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--) { for (int r = max_rank - 1; r >= 1; r--) {
if (rank_count[r] > total_sc / 2) { if (rank_count[r] > total_sc / 2) {
*rank_indicator = r; *rank_indicator = r;
break; 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) { if (muting_summary != NULL) {
muting_summary->samples = total_sc; muting_summary->samples = total_sc;
muting_summary->dimensions = physical_rank; muting_summary->dimensions = physical_rank;
@@ -615,6 +716,7 @@ static int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
r < physical_rank && eig_sum[r] > 0.0 && eig_sum[0] > 0.0 r < physical_rank && eig_sum[r] > 0.0 && eig_sum[0] > 0.0
? 10.0f * log10f((float)(eig_sum[r] / eig_sum[0])) ? 10.0f * log10f((float)(eig_sum[r] / eig_sum[0]))
: -INFINITY; : -INFINITY;
muting_summary->rank_capacity[r] = r < max_rank ? mean_capacity[r] : 0.0f;
} }
double full_power = 0.0; double full_power = 0.0;
for (int p = 0; p < N_ports; p++) for (int p = 0; p < N_ports; p++)
@@ -753,13 +855,16 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
const uint32_t interference_plus_noise_power, const uint32_t interference_plus_noise_power,
const uint8_t rank_indicator, const uint8_t rank_indicator,
const int16_t log2_re, const int16_t log2_re,
uint16_t *pmi_x1,
uint8_t *i2, uint8_t *i2,
int32_t *precoded_sinr_dB) int32_t *precoded_sinr_dB)
{ {
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms; const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
// RF simulators can report exactly zero interference/noise. Keep every PMI // RF simulators can report exactly zero interference/noise. Keep every PMI
// rank path finite, including the local rank-3/4 extension below. // 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; 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'. // i1 is a three-element vector in the form of [i11 i12 i13], when CodebookType is specified as 'Type1SinglePanel'.
// Note that i13 is not applicable when the number of transmission layers is one of {1, 5, 6, 7, 8}. // Note that i13 is not applicable when the number of transmission layers is one of {1, 5, 6, 7, 8}.
@@ -796,7 +901,7 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
return 0; 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 sum[4] = {0};
c64_t sum2[4] = {0}; c64_t sum2[4] = {0};
int64_t tested_precoded_sinr[4] = {0}; int64_t tested_precoded_sinr[4] = {0};
@@ -857,12 +962,131 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
*precoded_sinr_dB = dB_fixed((tested_precoded_sinr[i2[0]] + tested_precoded_sinr[i2[0]+2])>>1); *precoded_sinr_dB = dB_fixed((tested_precoded_sinr[i2[0]] + tested_precoded_sinr[i2[0]+2])>>1);
} }
} else if (rank_indicator == 2 || rank_indicator == 3) { } else if (rank_indicator < NR_MAX_NB_LAYERS && rank_indicator < N_ports && N_ports == 8
// RI=3 or RI=4 (4-port): Use identity-like precoder (i2=0). && rank_indicator != 2 && rank_indicator != 3
// gNB uses identity_precoding_matrix=1 anyway, so feedback isn't acted on && csirs_config_pdu->codebook_n1 == 4
// beyond the SINR-derived CQI. Without this branch, CQI is reported as 0 && csirs_config_pdu->codebook_n2 == 1
// and the gNB conservatively backs off rank/MCS. && csirs_config_pdu->codebook_mode == 1) {
// Restored from commit 3117df91cb (lost in upstream csi_rx.c refactor). 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; int nlayers = rank_indicator + 1;
int64_t total_power = 0; int64_t total_power = 0;
for (int rb = csirs_config_pdu->start_rb; for (int rb = csirs_config_pdu->start_rb;
@@ -877,7 +1101,6 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
} }
} }
} }
i2[0] = 0;
if (total_power > 0) if (total_power > 0)
*precoded_sinr_dB = dB_fixed((total_power / nlayers) / non_zero_noise_power); *precoded_sinr_dB = dB_fixed((total_power / nlayers) / non_zero_noise_power);
} else { } else {
@@ -1128,11 +1351,12 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
mapping_parms.ports, mapping_parms.ports,
csi_rs_estimated_channel_freq, csi_rs_estimated_channel_freq,
log2_maxh, log2_maxh,
noise_power,
&rank_indicator, &rank_indicator,
&muting_summary); &muting_summary);
} }
uint8_t i1[3] = {0}; uint16_t i1 = 0;
uint8_t i2[1] = {0}; uint8_t i2[1] = {0};
uint8_t cqi = 0; uint8_t cqi = 0;
int32_t precoded_sinr_dB = 0; int32_t precoded_sinr_dB = 0;
@@ -1145,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, csi_info->csi_im_meas_computed ? csi_info->interference_plus_noise_power : noise_power,
rank_indicator, rank_indicator,
log2_re, log2_re,
&i1,
i2, i2,
&precoded_sinr_dB); &precoded_sinr_dB);
@@ -1155,9 +1380,12 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
const char *muting_telemetry = getenv("OAI_CSI_MUTING_TELEMETRY"); const char *muting_telemetry = getenv("OAI_CSI_MUTING_TELEMETRY");
if (muting_summary.samples > 0 && muting_telemetry != NULL && strcmp(muting_telemetry, "1") == 0) { 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, LOG_I(NR_PHY,
"[CSI_MUTING] frame=%d slot=%d ue=%d rx=%u ports=%u samples=%d " "[CSI_MUTING] frame=%d slot=%d ue=%d rx=%u ports=%u samples=%d "
"ri=%u cqi=%u sinr_db=%d eig_rel_db=%.2f,%.2f,%.2f,%.2f " "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", "prefix_rel_db=%.2f,%.2f,%.2f,%.2f\n",
proc->frame_rx, proc->frame_rx,
proc->nr_slot_rx, proc->nr_slot_rx,
@@ -1165,6 +1393,7 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
frame_parms->nb_antennas_rx, frame_parms->nb_antennas_rx,
mapping_parms.ports, mapping_parms.ports,
muting_summary.samples, muting_summary.samples,
nr_csi_rank_policy_name(rank_config),
rank_indicator + 1, rank_indicator + 1,
cqi, cqi,
precoded_sinr_dB, precoded_sinr_dB,
@@ -1172,6 +1401,18 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
muting_summary.eig_rel_db[1], muting_summary.eig_rel_db[1],
muting_summary.eig_rel_db[2], muting_summary.eig_rel_db[2],
muting_summary.eig_rel_db[3], 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[0],
muting_summary.prefix_rel_db[1], muting_summary.prefix_rel_db[1],
muting_summary.prefix_rel_db[2], muting_summary.prefix_rel_db[2],
@@ -1183,12 +1424,12 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
LOG_I(NR_PHY, "[UE %d] RSRP = %i dBm\n", ue->Mod_id, rsrp_dBm); LOG_I(NR_PHY, "[UE %d] RSRP = %i dBm\n", ue->Mod_id, rsrp_dBm);
break; break;
case 26 : case 26 :
LOG_I(NR_PHY, "RI = %i i1 = %i.%i.%i, i2 = %i, SINR = %i dB, CQI = %i\n", LOG_I(NR_PHY, "RI = %i i1 = %u, i2 = %i, SINR = %i dB, CQI = %i\n",
rank_indicator + 1, i1[0], i1[1], i1[2], i2[0], precoded_sinr_dB, cqi); rank_indicator + 1, i1, i2[0], precoded_sinr_dB, cqi);
break; break;
case 27 : case 27 :
LOG_I(NR_PHY, "RSRP = %i dBm, RI = %i i1 = %i.%i.%i, i2 = %i, SINR = %i dB, CQI = %i\n", 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[0], i1[1], i1[2], i2[0], precoded_sinr_dB, cqi); rsrp_dBm, rank_indicator + 1, i1, i2[0], precoded_sinr_dB, cqi);
break; break;
default : default :
AssertFatal(false, "Not supported measurement configuration\n"); AssertFatal(false, "Not supported measurement configuration\n");
@@ -1205,7 +1446,7 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
.is_neighboring_cell = false, .is_neighboring_cell = false,
.rsrp_dBm = rsrp_dBm, .rsrp_dBm = rsrp_dBm,
.rank_indicator = rank_indicator, .rank_indicator = rank_indicator,
.i1 = *i1, .i1 = i1,
.i2 = *i2, .i2 = *i2,
.cqi = cqi, .cqi = cqi,
.radiolink_monitoring = RLM_no_monitoring, // TODO do be activated in case of RLM based on CSI-RS .radiolink_monitoring = RLM_no_monitoring, // TODO do be activated in case of RLM based on CSI-RS
@@ -18,6 +18,7 @@
#include "PHY/NR_REFSIG/dmrs_nr.h" #include "PHY/NR_REFSIG/dmrs_nr.h"
#include "common/utils/nr/nr_common.h" #include "common/utils/nr/nr_common.h"
#include <complex.h> #include <complex.h>
#include <pthread.h>
#include "openair1/PHY/TOOLS/phy_scope_interface.h" #include "openair1/PHY/TOOLS/phy_scope_interface.h"
#include "nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface.h" #include "nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface.h"
@@ -863,13 +864,14 @@ static void nr_dlsch_mmse(uint32_t rx_size_symbol,
* augmented matrix is retained to remove the MMSE bias before the existing * augmented matrix is retained to remove the MMSE bias before the existing
* QAM soft demappers are used. * QAM soft demappers are used.
*/ */
static int nr_dlsch_mmse_high_rank(uint32_t rx_size_symbol, static int nr_dlsch_mmse_high_rank_reference(uint32_t rx_size_symbol,
uint8_t n_rx, uint8_t n_rx,
uint8_t nl, uint8_t nl,
const c16_t rxdataF_ext[][rx_size_symbol], const c16_t rxdataF_ext[][rx_size_symbol],
int32_t dl_ch_estimates_ext[][rx_size_symbol], int32_t dl_ch_estimates_ext[][rx_size_symbol],
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol], c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
uint8_t symbol, uint8_t symbol,
int first_re,
int length, int length,
uint32_t noise_var) uint32_t noise_var)
{ {
@@ -882,7 +884,7 @@ static int nr_dlsch_mmse_high_rank(uint32_t rx_size_symbol,
const double q15 = 32768.0; const double q15 = 32768.0;
const double normalized_noise = (double)noise_var / (q15 * q15); const double normalized_noise = (double)noise_var / (q15 * q15);
for (int re = 0; re < length; re++) { for (int re = first_re; re < first_re + length; re++) {
double complex gram[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS] = {{0}}; double complex gram[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS] = {{0}};
double complex rhs[NR_MAX_NB_LAYERS] = {0}; double complex rhs[NR_MAX_NB_LAYERS] = {0};
@@ -978,6 +980,469 @@ static int nr_dlsch_mmse_high_rank(uint32_t rx_size_symbol,
return 0; 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, static void set_high_rank_qam_thresholds(uint8_t mod_order,
int length, int length,
c16_t *mag, c16_t *mag,
+100 -53
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, 359, 398, 447, 501, 562, 631, 708, 794, 891, 1000, 1122,
1258, 1412, 1585, 1778, 1995, 2239, 2512, 2818, 3162}; 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); 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, void nr_fill_dl_indication(nr_downlink_indication_t *dl_ind,
@@ -517,6 +555,18 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
int harq_pid = dlschCfg->harq_process_nbr; int harq_pid = dlschCfg->harq_process_nbr;
/* 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, 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 " "[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 "
"%d, DMRS mask " "%d, DMRS mask "
@@ -535,8 +585,14 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
dlsch->cw_info.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; 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; fourDimArray_t *pdsch_dl_ch_estimates_view =
allocCast2D(pdsch_dl_ch_estimates, int32_t, toFree, ue->frame_parms.nb_antennas_rx * dlsch->cw_info.Nl, pdsch_est_size, false); 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]; 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)); memset(ptrs_phase_per_slot, 0, sizeof(ptrs_phase_per_slot));
@@ -545,15 +601,13 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
memset(ptrs_re_per_slot, 0, sizeof(ptrs_re_per_slot)); 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; const uint32_t rx_size_symbol = (freq_alloc->num_rbs * NR_NB_SC_PER_RB + 15) & ~15;
fourDimArray_t *toFree2 = NULL; fourDimArray_t *rxdataF_comp_view = nr_ue_reuse_array(&rxdataF_comp_storage,
allocCast4D(rxdataF_comp, sizeof(c16_t),
c16_t,
toFree2,
ue->frame_parms.symbols_per_slot, ue->frame_parms.symbols_per_slot,
dlsch->cw_info.Nl, dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx, ue->frame_parms.nb_antennas_rx,
rx_size_symbol, rx_size_symbol);
false); cast4Darray(rxdataF_comp, c16_t, rxdataF_comp_view);
uint32_t nvar = 0; uint32_t nvar = 0;
@@ -644,33 +698,27 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
freq_alloc->num_rbs * NR_NB_SC_PER_RB * dlschCfg->number_symbols, freq_alloc->num_rbs * NR_NB_SC_PER_RB * dlschCfg->number_symbols,
&mt); &mt);
} }
fourDimArray_t *toFree3 = NULL; fourDimArray_t *dl_ch_mag_view = nr_ue_reuse_array(&dl_ch_mag_storage,
allocCast4D(dl_ch_mag, sizeof(c16_t),
c16_t,
toFree3,
NR_SYMBOLS_PER_SLOT, NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl, dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx, ue->frame_parms.nb_antennas_rx,
rx_size_symbol, rx_size_symbol);
false); cast4Darray(dl_ch_mag, c16_t, dl_ch_mag_view);
fourDimArray_t *toFree4 = NULL; fourDimArray_t *dl_ch_magb_view = nr_ue_reuse_array(&dl_ch_magb_storage,
allocCast4D(dl_ch_magb, sizeof(c16_t),
c16_t,
toFree4,
NR_SYMBOLS_PER_SLOT, NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl, dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx, ue->frame_parms.nb_antennas_rx,
rx_size_symbol, rx_size_symbol);
false); cast4Darray(dl_ch_magb, c16_t, dl_ch_magb_view);
fourDimArray_t *toFree5 = NULL; fourDimArray_t *dl_ch_magr_view = nr_ue_reuse_array(&dl_ch_magr_storage,
allocCast4D(dl_ch_magr, sizeof(c16_t),
c16_t,
toFree5,
NR_SYMBOLS_PER_SLOT, NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl, dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx, ue->frame_parms.nb_antennas_rx,
rx_size_symbol, rx_size_symbol);
false); cast4Darray(dl_ch_magr, c16_t, dl_ch_magr_view);
int pdsch_status = 0; int pdsch_status = 0;
for (int m = dlschCfg->start_symbol; m < (dlschCfg->number_symbols + dlschCfg->start_symbol); m++) { for (int m = dlschCfg->start_symbol; m < (dlschCfg->number_symbols + dlschCfg->start_symbol); m++) {
bool first_symbol_flag = false; bool first_symbol_flag = false;
@@ -717,11 +765,6 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
if (scope_req.copy_rxdataF_to_scope) { if (scope_req.copy_rxdataF_to_scope) {
UEunlockScopeData(ue, pdschRxdataF); UEunlockScopeData(ue, pdschRxdataF);
} }
free(toFree);
free(toFree2);
free(toFree3);
free(toFree4);
free(toFree5);
return pdsch_status; return pdsch_status;
} }
@@ -798,16 +841,11 @@ static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
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); 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);
NR_DL_FRAME_PARMS *fp = &ue->frame_parms;
// exit dlsch procedures as there are no active dlsch // exit dlsch procedures as there are no active dlsch
NR_DL_UE_HARQ_t *dl_harq = &ue->dl_harq_processes[cw_idx][harq_pid]; NR_DL_UE_HARQ_t *dl_harq = &ue->dl_harq_processes[cw_idx][harq_pid];
if (dl_harq->status != NR_ACTIVE) { if (dl_harq->status != NR_ACTIVE) {
// don't wait anymore // 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); 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 (config->k1_feedback) {
const int ack_nack_slot_and_frame = (proc->nr_slot_rx + config->k1_feedback) + proc->frame_rx * fp->slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
}
return; return;
} }
@@ -855,12 +893,6 @@ static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
ue->if_inst->dl_indication(&dl_indication); 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->rnti_type == TYPE_C_RNTI_ || dlsch->rnti_type == TYPE_RA_RNTI_) && config->k1_feedback) {
const int ack_nack_slot_and_frame = (proc->nr_slot_rx + config->k1_feedback) + proc->frame_rx * fp->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 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) { if (freq_alloc->num_rbs != 273) {
a_segments = a_segments * freq_alloc->num_rbs; a_segments = a_segments * freq_alloc->num_rbs;
@@ -1218,6 +1250,8 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
unav_res += compute_csi_rm_unav_res(dlsch_config); unav_res += compute_csi_rm_unav_res(dlsch_config);
int G[NR_MAX_NB_CODEWORDS] = {0}; int G[NR_MAX_NB_CODEWORDS] = {0};
uint32_t rx_llr_buf_sz[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++) { for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c]; NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
G[c] = nr_get_G(freq_alloc.num_rbs, G[c] = nr_get_G(freq_alloc.num_rbs,
@@ -1228,7 +1262,9 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
dlsch->cw_info.qamModOrder, dlsch->cw_info.qamModOrder,
dlsch->cw_info.Nl); dlsch->cw_info.Nl);
rx_llr_buf_sz[c] = ALIGNARRAYSIZE(G[c], 32); // each LLR is 2 bytes hence 64 byte aligned rx_llr_buf_sz[c] = ALIGNARRAYSIZE(G[c], 32); // each LLR is 2 bytes hence 64 byte aligned
llr[c] = (int16_t *)malloc16_clear(rx_llr_buf_sz[c] * sizeof(int16_t)); 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;
} }
int ret_pdsch = 0; int ret_pdsch = 0;
@@ -1256,7 +1292,9 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
joint_dlsch.cw_info.Nl = total_layers; joint_dlsch.cw_info.Nl = total_layers;
const int joint_G = G[0] + G[1]; const int joint_G = G[0] + G[1];
const uint32_t joint_buf_sz = ALIGNARRAYSIZE(joint_G, 32); const uint32_t joint_buf_sz = ALIGNARRAYSIZE(joint_G, 32);
int16_t *joint_llr = (int16_t *)malloc16_clear(joint_buf_sz * sizeof(*joint_llr)); 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]; NR_DL_UE_HARQ_t joint_harq = ue->dl_harq_processes[0][dlsch_config->harq_process_nbr];
joint_harq.status = NR_ACTIVE; joint_harq.status = NR_ACTIVE;
ret_pdsch = nr_ue_pdsch_procedures( ret_pdsch = nr_ue_pdsch_procedures(
@@ -1286,7 +1324,6 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
G[0], G[0],
G[1]); G[1]);
} }
free(joint_llr);
} }
} else { } else {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[0]; NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[0];
@@ -1307,11 +1344,6 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
nr_ue_dlsch_procedures(ue, proc, dlsch, c, G[c], &freq_alloc, dlsch_config, llr[c]); nr_ue_dlsch_procedures(ue, proc, dlsch, c, G[c], &freq_alloc, dlsch_config, llr[c]);
} else if (dlsch->active) { } else if (dlsch->active) {
LOG_E(NR_PHY, "PDSCH demodulation failed for CW%d\n", c); LOG_E(NR_PHY, "PDSCH demodulation failed for CW%d\n", c);
if (dlsch_config->k1_feedback) {
const int ack_nack_slot_and_frame =
proc->nr_slot_rx + dlsch_config->k1_feedback + proc->frame_rx * ue->frame_parms.slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
}
LOG_W(NR_PHY, "nr_ue_pdsch_procedures failed in slot %d\n", proc->nr_slot_rx); 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); stop_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
@@ -1322,6 +1354,23 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
ue->phy_cpu_stats.cpu_time_stats[DLSCH_PROCEDURES_STATS].p_time / (cpuf * 1000.0)); 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) if (ue->phy_sim_rxdataF)
memcpy(ue->phy_sim_rxdataF + start_symb_sch * ue->frame_parms.ofdm_symbol_size * sizeof(c16_t), memcpy(ue->phy_sim_rxdataF + start_symb_sch * ue->frame_parms.ofdm_symbol_size * sizeof(c16_t),
&rxdataF[0][start_symb_sch * ue->frame_parms.ofdm_symbol_size], &rxdataF[0][start_symb_sch * ue->frame_parms.ofdm_symbol_size],
@@ -1335,8 +1384,6 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
} }
} }
for (int c = 0; c < phy_data->n_dlsch_codewords; c++)
free(llr[c]);
} }
if (nr_slot_rx==9) { if (nr_slot_rx==9) {
+12
View File
@@ -745,6 +745,7 @@ int main(int argc, char **argv)
.num_dlharq = 16, .num_dlharq = 16,
.num_ulharq = 16, .num_ulharq = 16,
.maxMIMO_layers = g_nrOfLayers, .maxMIMO_layers = g_nrOfLayers,
.pdsch_dmrs_additional_position = 1,
.force_256qam_off = false, .force_256qam_off = false,
.timer_config.sr_ProhibitTimer = 0, .timer_config.sr_ProhibitTimer = 0,
.timer_config.sr_TransMax = 64, .timer_config.sr_TransMax = 64,
@@ -1076,6 +1077,10 @@ int main(int argc, char **argv)
reset_sched_response(Sched_INFO, frame, slot, 0, 0); reset_sched_response(Sched_INFO, frame, slot, 0, 0);
clear_nr_nfapi_information(RC.nrmac[0], 0, frame, slot); clear_nr_nfapi_information(RC.nrmac[0], 0, frame, slot);
UE_info->UE_sched_ctrl.harq_processes[harq_pid].ndi = !(trial&1); UE_info->UE_sched_ctrl.harq_processes[harq_pid].ndi = !(trial&1);
/* 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; UE_info->UE_sched_ctrl.harq_processes[harq_pid].round = round;
// nr_schedule_ue_spec() requires the mutex to be locked // nr_schedule_ue_spec() requires the mutex to be locked
@@ -1702,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; 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 (add_pos != 2) { // pos0,pos1,pos3
if (dmrs_config->dmrs_AdditionalPosition == NULL) { if (dmrs_config->dmrs_AdditionalPosition == NULL) {
dmrs_config->dmrs_AdditionalPosition = calloc(1,sizeof(*dmrs_MappingtypeA->choice.setup->dmrs_AdditionalPosition)); dmrs_config->dmrs_AdditionalPosition = calloc(1,sizeof(*dmrs_MappingtypeA->choice.setup->dmrs_AdditionalPosition));
@@ -17,6 +17,11 @@ bool read_mac_sm(void* data)
//fill_mac_ind_data(mac); //fill_mac_ind_data(mac);
mac->msg.tstamp = time_now_us(); 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; NR_UEs_t *UE_info = &RC.nrmac[mod_id]->UE_info;
size_t num_ues = 0; size_t num_ues = 0;
@@ -33,6 +38,10 @@ bool read_mac_sm(void* data)
size_t i = 0; //TODO size_t i = 0; //TODO
UE_iterator(UE_info->connected_ue_list, UE) { 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; const NR_UE_sched_ctrl_t* sched_ctrl = &UE->UE_sched_ctrl;
mac_ue_stats_impl_t* rd = &mac->msg.ue_stats[i]; 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->dl_aggr_retx_prb = UE->mac_stats.dl.total_rbs_retx;
rd->ul_aggr_retx_prb = UE->mac_stats.ul.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]; // The service-model field is aggregate SDU bytes. Using LCID 3 silently
rd->ul_aggr_bytes_sdus = UE->mac_stats.ul.lc_bytes[3]; // 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->dl_aggr_sdus = UE->mac_stats.dl.num_mac_sdu;
rd->ul_aggr_sdus = UE->mac_stats.ul.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->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->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_mcs1 = sched_ctrl->dl_bler_stats.mcs;
rd->dl_bler = sched_ctrl->dl_bler_stats.bler; rd->dl_bler = sched_ctrl->dl_bler_stats.bler;
rd->ul_mcs1 = sched_ctrl->ul_bler_stats.mcs; 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}; sm_ag_if_ans_t ans = {0};
return ans; return ans;
} }
@@ -25,7 +25,7 @@
#endif #endif
#if defined (NGRAN_GNB_CUUP) #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; size_t j = 0;
for (size_t i = 0; i < num_ues; i++) { 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; 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++]; pdcp_radio_bearer_stats_t *rd = &rb[j++];
rd->txpdu_pkts = rb_pdcp.txpdu_pkts; /* aggregated number of tx packets */ 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 // Filling Radio Bearer Statistics
#if defined (NGRAN_GNB_CUUP) #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 #endif
return true; return true;
} }
@@ -83,6 +83,11 @@ bool read_rlc_sm(void* data)
const int srb_flag = 0; const int srb_flag = 0;
const bool rc = nr_rlc_get_statistics(rnti, srb_flag, rb_id, &rb_rlc); const bool rc = nr_rlc_get_statistics(rnti, srb_flag, rb_id, &rb_rlc);
if(!rc) continue; 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]; rlc_radio_bearer_stats_t* sm_rb = &rlc->msg.rb[i];
/* TX */ /* TX */
@@ -143,6 +148,7 @@ bool read_rlc_sm(void* data)
++i; ++i;
} }
} }
done_iter:
return act_rb > 0; return act_rb > 0;
} }
@@ -86,8 +86,42 @@ static meas_record_lst_t fill_DRB_PdcpSduVolumeUL(__attribute__((unused)) const
} }
#if defined (NGRAN_GNB_DU) #if defined (NGRAN_GNB_DU)
static uldlcounter_t last_rlc_pdu_total_bytes[MAX_MOBILES_PER_GNB] = {0}; typedef struct ue_uldlcounter {
static uldlcounter_t last_total_prbs[MAX_MOBILES_PER_GNB] = {0}; 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) 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; meas_record.value = REAL_MEAS_VALUE;
// Get the value of sojourn time at the RLC buffer // Report the windowed-average RLC SDU sojourn (mean per-packet wait over the
meas_record.real_val = rlc.txsdu_avg_time_to_tx; // [μs] // 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; 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, static meas_record_lst_t fill_DRB_UEThpDl(__attribute__((unused)) const label_info_lst_t label,
uint32_t gran_period_ms, uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info, 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) __attribute__((unused))e2_node_level_stats_t* node_stats)
{ {
meas_record_lst_t meas_record = {0}; 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; meas_record.value = REAL_MEAS_VALUE;
// Calculate DL Thp // Calculate DL Thp
meas_record.real_val = (double)(rlc.txpdu_bytes - last_rlc_pdu_total_bytes[ue_idx].dl)*8/gran_period_ms; // [kbps] ue_uldlcounter_t *counter = get_ue_counter(last_rlc_pdu_total_bytes, ue_info);
last_rlc_pdu_total_bytes[ue_idx].dl = rlc.txpdu_bytes; 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; 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, static meas_record_lst_t fill_DRB_UEThpUl(__attribute__((unused)) const label_info_lst_t label,
uint32_t gran_period_ms, uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info, 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) __attribute__((unused))e2_node_level_stats_t* node_stats)
{ {
meas_record_lst_t meas_record = {0}; 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; meas_record.value = REAL_MEAS_VALUE;
// Calculate UL Thp // Calculate UL Thp
meas_record.real_val = (double)(rlc.rxpdu_bytes - last_rlc_pdu_total_bytes[ue_idx].ul)*8/gran_period_ms; // [kbps] ue_uldlcounter_t *counter = get_ue_counter(last_rlc_pdu_total_bytes, ue_info);
last_rlc_pdu_total_bytes[ue_idx].ul = rlc.rxpdu_bytes; 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; 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, static meas_record_lst_t fill_RRU_PrbTotDl(__attribute__((unused)) const label_info_lst_t label,
__attribute__((unused)) uint32_t gran_period_ms, __attribute__((unused)) uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info, 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) e2_node_level_stats_t* node_stats)
{ {
meas_record_lst_t meas_record = {0}; 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; meas_record.value = INTEGER_MEAS_VALUE;
// Get the number of DL PRBs // 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); // [%] ue_uldlcounter_t *counter = get_ue_counter(last_total_prbs, ue_info);
last_total_prbs[ue_idx].dl = ue_info.ue->mac_stats.dl.total_rbs; 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; 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, static meas_record_lst_t fill_RRU_PrbTotUl(__attribute__((unused)) const label_info_lst_t label,
__attribute__((unused)) uint32_t gran_period_ms, __attribute__((unused)) uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info, 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) e2_node_level_stats_t* node_stats)
{ {
meas_record_lst_t meas_record = {0}; 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; meas_record.value = INTEGER_MEAS_VALUE;
// Get the number of UL PRBs // 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); // [%] ue_uldlcounter_t *counter = get_ue_counter(last_total_prbs, ue_info);
last_total_prbs[ue_idx].ul = ue_info.ue->mac_stats.ul.total_rbs; 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; return meas_record;
} }
+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_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_FORCE256QAMOFF_IDX, GNB_CONFIG_STRING_FORCE256QAMOFF);
verify_gnb_param_notset(gnbp, GNB_MAXMIMOLAYERS_IDX, GNB_CONFIG_STRING_MAXMIMOLAYERS); 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_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_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); 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.force_UL256qam_off = *GNBParamList.paramarray[0][GNB_FORCEUL256QAMOFF_IDX].iptr;
config.use_deltaMCS = *GNBParamList.paramarray[0][GNB_USE_DELTA_MCS_IDX].iptr != 0; config.use_deltaMCS = *GNBParamList.paramarray[0][GNB_USE_DELTA_MCS_IDX].iptr != 0;
config.maxMIMO_layers = *GNBParamList.paramarray[0][GNB_MAXMIMOLAYERS_IDX].iptr; 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.disable_harq = *GNBParamList.paramarray[0][GNB_DISABLE_HARQ_IDX].iptr;
config.num_dlharq = *GNBParamList.paramarray[0][GNB_NUM_DL_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; 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); config.num_ulharq);
int tot_ant = config.pdsch_AntennaPorts.N1 * config.pdsch_AntennaPorts.N2 * config.pdsch_AntennaPorts.XP; 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); 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.redcap = get_redcap_config(0);
config.ptrs = get_ptrs_config(); 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_CONFIG_REP "CSI_report_type"
#define GNB_CONFIG_STRING_1ST_ACTIVE_BWP "first_active_bwp" #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_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_STRING_ENABLE_SDAP "enable the SDAP layer\n"
#define GNB_CONFIG_HLP_FORCE256QAMOFF "suppress activation of 256 QAM despite UE support" #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_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_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_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_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_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_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 // clang-format on
@@ -207,6 +210,7 @@ typedef enum {
#define GNB_CONFIG_REP_IDX 37 #define GNB_CONFIG_REP_IDX 37
#define GNB_1ST_ACTIVE_BWP_IDX 38 #define GNB_1ST_ACTIVE_BWP_IDX 38
#define GNB_LIMIT_RSRP_REPORT_IDX 39 #define GNB_LIMIT_RSRP_REPORT_IDX 39
#define GNB_PDSCH_DMRS_ADDITIONAL_POSITION_IDX 40
#define TRACKING_AREA_CODE_OKRANGE {0x0001,0xFFFD} #define TRACKING_AREA_CODE_OKRANGE {0x0001,0xFFFD}
#define NUM_DL_HARQ_OKVALUES {2,4,6,8,10,12,16,32} #define NUM_DL_HARQ_OKVALUES {2,4,6,8,10,12,16,32}
@@ -256,6 +260,7 @@ typedef enum {
3 } }, \ 3 } }, \
{ .s5 = { NULL } }, \ { .s5 = { NULL } }, \
{ .s5 = { NULL } }, \ { .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
} }
/*-------------------------------------------------------------------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------------------------------------------------------------------*/
+13 -4
View File
@@ -4133,12 +4133,19 @@ uint16_t compute_pucch_prb_size(uint8_t format,
nr_prbs); nr_prbs);
if (format==2){ 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 // 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 // compute code rate factor for next prb value
int next_prb_factor = (i - 1) * n_symb * Qm * n_re_ctrl * r; int next_prb_factor = (i - 1) * n_symb * Qm * n_re_ctrl * r;
// if it does not sa // if it does not sa
if (O_tot > next_prb_factor) if (i == min_prbs || O_tot > next_prb_factor)
return i; return i;
} }
} }
@@ -4552,8 +4559,10 @@ static void set_bitlen_size_singlepanel(CSI_Meas_bitlen_t *csi_bitlen, int n1, i
case 7: case 7:
case 8: case 8:
if(n1 == 4 && n2 == 1) { if(n1 == 4 && n2 == 1) {
csi_bitlen->pmi_i11_bitlen[i] = ceil(log2(n1 * o1)); // TS 38.212 Table 6.3.1.1.2-1: only half of the horizontal
csi_bitlen->pmi_i12_bitlen[i] = ceil(log2(n2 * o2 / 2)); // 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_i13_bitlen[i] = 0;
csi_bitlen->pmi_x2_bitlen[i] = 1; csi_bitlen->pmi_x2_bitlen[i] = 1;
} }
+9 -3
View File
@@ -2371,7 +2371,8 @@ bool get_downlink_ack(NR_UE_MAC_INST_t *mac, frame_t frame, int slot, PUCCH_sche
if (current_DL_BWP if (current_DL_BWP
&& current_DL_BWP->pdsch_Config && current_DL_BWP->pdsch_Config
&& current_DL_BWP->pdsch_Config->maxNrofCodeWordsScheduledByDCI && 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; two_transport_blocks = true;
number_of_code_word = 2; number_of_code_word = 2;
} }
@@ -2964,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_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 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]; 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; int padding_bitlen = 0;
// TODO: Improvements will be needed to cri_bitlen>0 and pmi_x1_bitlen>0 // TODO: Improvements will be needed to cri_bitlen>0 and pmi_x1_bitlen>0
if (mapping_type == ON_PUSCH) { if (mapping_type == ON_PUSCH) {
@@ -2971,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; p2_bits = pmi_x1_bitlen + pmi_x2_bitlen;
temp_payload_1 = (0/*mac->csi_measurements.cri*/ << (cqi_bitlen + ri_bitlen)) | temp_payload_1 = (0/*mac->csi_measurements.cri*/ << (cqi_bitlen + ri_bitlen)) |
(mac->csirs_measurements.ri << cqi_bitlen) | (mac->csirs_measurements.ri << cqi_bitlen) |
(mac->csirs_measurements.cqi); cqi_payload;
temp_payload_2 = (mac->csirs_measurements.i1 << pmi_x2_bitlen) | temp_payload_2 = (mac->csirs_measurements.i1 << pmi_x2_bitlen) |
mac->csirs_measurements.i2; mac->csirs_measurements.i2;
} }
@@ -2982,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.ri << (cqi_bitlen + pmi_x2_bitlen + pmi_x1_bitlen + padding_bitlen)) |
(mac->csirs_measurements.i1 << (cqi_bitlen + pmi_x2_bitlen)) | (mac->csirs_measurements.i1 << (cqi_bitlen + pmi_x2_bitlen)) |
(mac->csirs_measurements.i2 << (cqi_bitlen)) | (mac->csirs_measurements.i2 << (cqi_bitlen)) |
(mac->csirs_measurements.cqi); cqi_payload;
} }
temp_payload_1 = reverse_bits(temp_payload_1, p1_bits); temp_payload_1 = reverse_bits(temp_payload_1, p1_bits);
+24 -2
View File
@@ -1154,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 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; 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) if (csi_res_id > NR_maxNrofCSI_ResourceConfigurations)
return meas_bitmap; // CSI-RS for tracking return meas_bitmap; // CSI-RS for tracking
for(int i = 0; i < csi_measconfig->csi_ReportConfigToAddModList->list.count; i++) { 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]; NR_CSI_ReportConfig_t *report_config = csi_measconfig->csi_ReportConfigToAddModList->list.array[i];
if(report_config->resourcesForChannelMeasurement != csi_res_id) if(report_config->resourcesForChannelMeasurement != csi_res_id)
continue; 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 // bit 0 RSRP bit 1 RI bit 2 LI bit 3 PMI bit 4 CQI bit 5 i1
switch (report_config->reportQuantity.present) { switch (report_config->reportQuantity.present) {
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_PMI_CQI: case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_PMI_CQI:
@@ -1220,7 +1241,8 @@ static void nr_schedule_csirs_reception(NR_UE_MAC_INST_t *mac, int frame, int sl
continue; continue;
LOG_D(MAC,"Scheduling reception of CSI-RS in frame %d slot %d\n", frame, slot); 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; 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->subcarrier_spacing = mu;
csirs_config_pdu->cyclic_prefix = current_DL_BWP->cyclicprefix ? *current_DL_BWP->cyclicprefix : 0; csirs_config_pdu->cyclic_prefix = current_DL_BWP->cyclicprefix ? *current_DL_BWP->cyclicprefix : 0;
+48 -6
View File
@@ -186,6 +186,28 @@ int precoding_weigths_generation(nfapi_nr_pm_list_t *mat,
return pmiq; 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) 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; int num_antenna_ports = antenna_ports.N1 * antenna_ports.N2 * antenna_ports.XP;
@@ -208,20 +230,32 @@ nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPort
int O1 = num_antenna_ports > 2 ? 4 : 1; //Horizontal beam oversampling (1 or 4) int O1 = num_antenna_ports > 2 ? 4 : 1; //Horizontal beam oversampling (1 or 4)
const int max_mimo_layers = min(num_antenna_ports, NR_MAX_NB_LAYERS); const int max_mimo_layers = min(num_antenna_ports, NR_MAX_NB_LAYERS);
/* Type-I codebook generation below currently covers ranks 1--4. Rank const int low_codebook_layers = min(max_mimo_layers, 4);
* 5--8 is an explicit identity-PMI path and therefore needs no generated const bool high_rank_4x1 = num_antenna_ports == 8 && antenna_ports.XP == 2 && N1 == 4 && N2 == 1;
* codebook entries. */
const int codebook_layers = min(max_mimo_layers, 4);
AssertFatal(num_antenna_ports < 16, "Max number of antenna ports supported is currently 16\n"); AssertFatal(num_antenna_ports < 16, "Max number of antenna ports supported is currently 16\n");
int K1 = 0, K2 = 0; int K1 = 0, K2 = 0;
nfapi_nr_pm_list_t mat = {.num_pm_idx = 0}; nfapi_nr_pm_list_t mat = {.num_pm_idx = 0};
for (int i = 0; i < codebook_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); get_K1_K2(N1, N2, &K1, &K2, i + 1);
int i2_size = i == 0 ? 4 : 2; int i2_size = i == 0 ? 4 : 2;
gNB->precoding_matrix_size[i] = i2_size * N1 * O1 * N2 * O2 * K1 * K2; gNB->precoding_matrix_size[i] = i2_size * N1 * O1 * N2 * O2 * K1 * K2;
mat.num_pm_idx += gNB->precoding_matrix_size[i]; 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)); mat.pmi_pdu = malloc16(mat.num_pm_idx * sizeof(*mat.pmi_pdu));
AssertFatal(mat.pmi_pdu != NULL, "out of memory\n"); AssertFatal(mat.pmi_pdu != NULL, "out of memory\n");
@@ -277,8 +311,16 @@ nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPort
} }
int pmiq = 0; int pmiq = 0;
for (int layers = 1; layers <= codebook_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); 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; 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]; 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) 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}; 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_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
NR_SCHED_LOCK(&gNB->sched_lock); NR_SCHED_LOCK(&gNB->sched_lock);
apply_pending_tx_mask_if_harq_safe(gNB);
int slots_frame = gNB->frame_structure.numb_slots_frame; int slots_frame = gNB->frame_structure.numb_slots_frame;
clear_beam_information(&gNB->beam_info, frame, slot, slots_frame); clear_beam_information(&gNB->beam_info, frame, slot, slots_frame);
+125 -20
View File
@@ -13,6 +13,9 @@
#include "LAYER2/NR_MAC_gNB/mac_proto.h" #include "LAYER2/NR_MAC_gNB/mac_proto.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h" #include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
#include <stdlib.h>
#include <string.h>
/*TAG*/ /*TAG*/
#include "NR_TAG-Id.h" #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]; NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid];
harq->ndi ^= 1; harq->ndi ^= 1;
if (harq->sched_pdsch.nrOfLayers > 4)
harq->ndi2 ^= 1;
harq->pending_cw_mask = 0;
harq->round = 0; harq->round = 0;
add_tail_nr_list(&sched_ctrl->available_dl_harq, harq_pid); add_tail_nr_list(&sched_ctrl->available_dl_harq, harq_pid);
@@ -451,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_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP; NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch; NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch;
int layers = get_dl_nrOfLayers(nr_mac, sched_ctrl, dl_bwp->dci_format); const int layers = new_sched.nrOfLayers;
int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
// If the RI changed between current rtx and a previous transmission /* HARQ retransmission must retain the original layer mapping, codeword
// we need to verify if it is not decreased * count, and TBS. Recomputing it from the current RI/TX target can turn a
// othwise it wouldn't be possible to transmit the same TBS * rank-8 two-codeword HARQ process into a one-codeword transmission and
layers = (layers < new_sched.nrOfLayers) ? layers : new_sched.nrOfLayers; * silently discard TB2. Normally a staged mask downshift keeps enough
pm_index = (layers < new_sched.nrOfLayers) ? pm_index : new_sched.pm_index; * 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 coresetid = sched_ctrl->coreset->controlResourceSetId;
const int tda = get_dl_tda(nr_mac, slot); const int tda = get_dl_tda(nr_mac, slot);
@@ -556,7 +571,6 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
new_sched.rbSize = new_rbSize; new_sched.rbSize = new_rbSize;
new_sched.time_domain_allocation = tda; new_sched.time_domain_allocation = tda;
new_sched.nrOfLayers = layers; new_sched.nrOfLayers = layers;
new_sched.pm_index = pm_index;
new_sched.dmrs_parms = temp_dmrs; new_sched.dmrs_parms = temp_dmrs;
new_sched.tda_info = temp_tda; new_sched.tda_info = temp_tda;
update_sched_pdsch_codeword_tbs(&new_sched); update_sched_pdsch_codeword_tbs(&new_sched);
@@ -586,11 +600,8 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
if (!get_FeedbackDisabled(UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, current_harq_pid)) { if (!get_FeedbackDisabled(UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, current_harq_pid)) {
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex); int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
alloc = nr_acknack_scheduling(nr_mac, UE, frame, slot, UE->UE_beam_index, r_pucch, 0); alloc = nr_acknack_scheduling(nr_mac, UE, frame, slot, UE->UE_beam_index, r_pucch, 0);
if (alloc >= 0 && nr_pdsch_num_codewords(new_sched.nrOfLayers) == 2) { /* DAI counts PDSCH assignments, not transport blocks. A two-codeword
const int second_alloc = nr_acknack_scheduling(nr_mac, UE, frame, slot, UE->UE_beam_index, r_pucch, 0); * PDSCH uses one PUCCH allocation carrying two HARQ-ACK bits. */
if (second_alloc != alloc)
alloc = -1;
}
if (alloc < 0) { if (alloc < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI retransmission\n", UE->rnti, frame, slot); LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI retransmission\n", UE->rnti, frame, slot);
return false; return false;
@@ -659,6 +670,24 @@ static int comparator(const void *p, const void *q)
return 0; 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, static void pf_dl(gNB_MAC_INST *mac,
post_process_pdsch_t *pp_pdsch, post_process_pdsch_t *pp_pdsch,
NR_UE_info_t **UE_list, NR_UE_info_t **UE_list,
@@ -744,7 +773,13 @@ static void pf_dl(gNB_MAC_INST *mac,
/* Calculate coeff */ /* Calculate coeff */
const NR_bler_options_t *bo = &mac->dl_bler; const NR_bler_options_t *bo = &mac->dl_bler;
const int max_mcs_table = current_BWP->mcsTableIdx == 1 ? 27 : 28; 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; int selected_mcs;
if (bo->harq_round_max == 1) { if (bo->harq_round_max == 1) {
int new_mcs = min(bo->max_mcs, max_mcs); int new_mcs = min(bo->max_mcs, max_mcs);
@@ -816,6 +851,16 @@ static void pf_dl(gNB_MAC_INST *mac,
continue; 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 */ /* MCS has been set above */
int tda = get_dl_tda(mac, slot); int tda = get_dl_tda(mac, slot);
AssertFatal(tda >= 0,"Unable to find PDSCH time domain allocation in list\n"); AssertFatal(tda >= 0,"Unable to find PDSCH time domain allocation in list\n");
@@ -852,6 +897,15 @@ static void pf_dl(gNB_MAC_INST *mac,
iterator++; iterator++;
continue; 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, int CCEIndex = get_cce_index(mac,
CC_id, CC_id,
@@ -879,12 +933,8 @@ static void pf_dl(gNB_MAC_INST *mac,
if (!get_FeedbackDisabled(iterator->UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, sched_ctrl->available_dl_harq.head)) { if (!get_FeedbackDisabled(iterator->UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, sched_ctrl->available_dl_harq.head)) {
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex); int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
alloc = nr_acknack_scheduling(mac, iterator->UE, frame, slot, iterator->UE->UE_beam_index, r_pucch, 0); alloc = nr_acknack_scheduling(mac, iterator->UE, frame, slot, iterator->UE->UE_beam_index, r_pucch, 0);
if (alloc >= 0 && nr_pdsch_num_codewords(l) == 2) { /* One PDSCH assignment has one DAI increment even when it carries two
const int second_alloc = * codewords; the UE multiplexes both ACK bits on this allocation. */
nr_acknack_scheduling(mac, iterator->UE, frame, slot, iterator->UE->UE_beam_index, r_pucch, 0);
if (second_alloc != alloc)
alloc = -1;
}
if (alloc < 0) { if (alloc < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI\n", rnti, frame, slot); LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI\n", rnti, frame, slot);
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam); reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
@@ -1151,6 +1201,13 @@ static dlsch_fill_result_t fill_dlsch_transport_block(gNB_MAC_INST *mac,
return result; 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) void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE_info_t *UE, NR_sched_pdsch_t *sched_pdsch)
{ {
int CC_id = 0; int CC_id = 0;
@@ -1191,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_tda_info_t *tda_info = &sched_pdsch->tda_info;
NR_pdsch_dmrs_t *dmrs_parms = &sched_pdsch->dmrs_parms; NR_pdsch_dmrs_t *dmrs_parms = &sched_pdsch->dmrs_parms;
NR_UE_harq_t *harq = &sched_ctrl->harq_processes[current_harq_pid]; 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; NR_sched_pucch_t *pucch = NULL;
DevAssert(!harq->is_waiting); DevAssert(!harq->is_waiting);
if (sched_pdsch->pucch_allocation >= 0) { if (sched_pdsch->pucch_allocation >= 0) {
@@ -1202,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]++; UE->mac_stats.dl.rounds[harq->round]++;
int tpc = nr_mac_get_tpc(&sched_ctrl->pucch_pc); 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, 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", "%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, frame,
@@ -1279,6 +1371,10 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
fapi_beam, fapi_beam,
nl_tbslbrm, nl_tbslbrm,
pduindex); 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); LOG_D(NR_MAC,"Configuring DCI/PDCCH in %d.%d at CCE %d, rnti %x\n", frame,slot,sched_ctrl->cce_index,rnti);
@@ -1375,6 +1471,15 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
"Mismatch between scheduled TBS and buffered TB for HARQ PID %d. No RTX control in phy-test mode. " "Mismatch between scheduled TBS and buffered TB for HARQ PID %d. No RTX control in phy-test mode. "
"Possible causes: presence of CSI-RS or DLSCH scheduled in the mixed slot.\n", "Possible causes: presence of CSI-RS or DLSCH scheduled in the mixed slot.\n",
current_harq_pid); 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(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)); 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; UE->mac_stats.dl.total_rbs_retx += sched_pdsch->rbSize;
@@ -164,8 +164,10 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
int layers, int layers,
int xp_pdsch_antenna_ports) int xp_pdsch_antenna_ports)
{ {
if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1 || layers > 4) 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) 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; 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; 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"); LOG_D(NR_MAC, "[ANTENNA_MUTE] Recovery mode active, using identity matrix\n");
return 0; return 0;
} }
// Don't fall back to identity matrix for muted antennas. const int active_tx = nr_get_active_tx_antenna_count(ant_state);
// Antenna muting is handled at RU level (nr_feptx zeros muted antenna buffers). if (active_tx < ant_state->num_tx_antennas) {
// Using proper precoding ensures data reaches all active 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; 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 N2 = csi_report->N2;
const int antenna_ports = (N1 * N2) << 1; const int antenna_ports = (N1 * N2) << 1;
if (antenna_ports < 2) return 0; // single antenna port 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; 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; 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]; bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
const int i11 = x1 & ((1 << bitlen) - 1); const int i11 = x1 & ((1 << bitlen) - 1);
const int i2 = x2; 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; int lay_index = 0;
max_i2 = layers == 1 ? 4 : 2; 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; int K1, K2;
get_K1_K2(N1, N2, &K1, &K2, layers); get_K1_K2(N1, N2, &K1, &K2, layers);
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB // 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); 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; pm_index = 1 + prev_layers_size + lay_index;
} }
int max_valid_pm = prev_layers_size + nrmac->precoding_matrix_size[layers - 1]; int max_valid_pm = prev_layers_size + nrmac->precoding_matrix_size[layers - 1];
@@ -860,7 +898,12 @@ int get_mcs_from_bler(const NR_bler_options_t *bler_options,
if (diff < 0) // wrap around if (diff < 0) // wrap around
diff += 1024; 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); const uint8_t old_mcs = min(bler_stats->mcs, max_mcs);
if (diff < BLER_UPDATE_FRAME) if (diff < BLER_UPDATE_FRAME)
return old_mcs; // no update return old_mcs; // no update
@@ -971,8 +1014,17 @@ dci_pdu_rel15_t prepare_dci_dl_payload(const gNB_MAC_INST *gNB_mac,
dci_payload.ndi = harq->ndi; dci_payload.ndi = harq->ndi;
if (pdsch_pdu->NrOfCodewords == 2) { if (pdsch_pdu->NrOfCodewords == 2) {
dci_payload.mcs2.val = sched_pdsch->mcs; dci_payload.mcs2.val = sched_pdsch->mcs;
dci_payload.ndi2.val = harq->ndi; dci_payload.ndi2.val = harq->ndi2;
dci_payload.rv2.val = pdsch_pdu->rvIndex[1]; 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.pucch_resource_indicator = pucch ? pucch->resource_indicator : 0;
dci_payload.pdsch_to_harq_feedback_timing_indicator.val = pucch ? pucch->timing_indicator : 0; // PDSCH to HARQ TI dci_payload.pdsch_to_harq_feedback_timing_indicator.val = pucch ? pucch->timing_indicator : 0; // PDSCH to HARQ TI
+190 -33
View File
@@ -15,6 +15,24 @@
#include "common/ran_context.h" #include "common/ran_context.h"
#include "common/utils/nr/nr_common.h" #include "common/utils/nr/nr_common.h"
#include "nfapi/oai_integration/vendor_ext.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) 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; future_ul_tti_req->n_pdus += 1;
LOG_D(NR_MAC, LOG_D(NR_MAC,
"%s %4d.%2d Scheduling pucch reception in %4d.%2d: bits SR %d, DAI %d, CSI %d on res %d\n", "%s %4d.%2d Scheduling pucch reception in %4d.%2d: bits SR %d, DAI %d, HARQ-ACK %d, CSI %d on res %d\n",
pucch->dai_c>0 ? "pucch_acknack" : "", pucch->harq_ack_bits > 0 ? "pucch_acknack" : "",
frame, frame,
slot, slot,
pucch->frame, pucch->frame,
pucch->ul_slot, pucch->ul_slot,
pucch->sr_flag, pucch->sr_flag,
pucch->dai_c, pucch->dai_c,
pucch->harq_ack_bits,
pucch->csi_bits, pucch->csi_bits,
pucch->resource_indicator); pucch->resource_indicator);
NR_COMMON_channels_t * common_ch=nrmac->common_channels; 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, UE,
pucch->resource_indicator, pucch->resource_indicator,
pucch->csi_bits, pucch->csi_bits,
pucch->dai_c, pucch->harq_ack_bits,
pucch->sr_flag, pucch->sr_flag,
pucch->r_pucch, pucch->r_pucch,
nrmac->beam_info.beam_mode); 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; 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 uint16_t O_csi = curr_pucch->csi_bits;
const uint8_t O_sr = curr_pucch->sr_flag; const uint8_t O_sr = curr_pucch->sr_flag;
LOG_D(NR_MAC, 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; 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_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid]; 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->feedback_slot = -1;
harq->is_waiting = false; harq->is_waiting = false;
if (success) { 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; 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); LOG_D(MAC,"Wide-band CQI for the first TB %d\n", temp_cqi);
if (cqi_bitlen > 4) { 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; 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); LOG_D(MAC,"Wide-band CQI for the second TB %d\n", temp_cqi);
} }
@@ -886,7 +910,7 @@ static NR_UE_harq_t *find_harq(frame_t frame, slot_t slot, NR_UE_info_t * UE, in
frame, frame,
slot); slot);
remove_front_nr_list(&sched_ctrl->feedback_dl_harq); 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; pid = sched_ctrl->feedback_dl_harq.head;
if (pid < 0) if (pid < 0)
return NULL; return NULL;
@@ -936,15 +960,28 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
break; break;
} }
DevAssert(harq->is_waiting); DevAssert(harq->is_waiting);
const int n_ack_bits = harq->sched_pdsch.nrOfLayers > 4 ? 2 : 1; const 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; const int8_t pid = sched_ctrl->feedback_dl_harq.head;
remove_front_nr_list(&sched_ctrl->feedback_dl_harq); remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
bool success = harq_confidence == 0 && harq_bit + n_ack_bits <= uci_01->harq.num_harq; 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++) { 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; const uint8_t harq_value = uci_01->harq.harq_list[harq_bit + q].harq_value;
LOG_D(NR_MAC, "%4d.%2d bit %d pid %d cw %d ack/nack %d\n", frame, slot, harq_bit + q, pid, q, harq_value); LOG_D(NR_MAC, "%4d.%2d bit %d pid %d cw %d active %d ack/nack %d\n",
success &= harq_value == 0; 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); nr_mac_update_pdcch_closed_loop_adjust(sched_ctrl, harq_confidence != 0);
// TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133) // TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133)
if (success && harq->start_tci_timer) { if (success && harq->start_tci_timer) {
@@ -953,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); nr_timer_start(&sched_ctrl->tci_beam_switch);
harq->start_tci_timer = false; harq->start_tci_timer = false;
} }
handle_dl_harq(nrmac, UE, pid, success, nrmac->dl_bler.harq_round_max); handle_dl_harq(nrmac, UE, pid, ack_mask, nrmac->dl_bler.harq_round_max);
if (is_ra) { if (is_ra) {
bool ue_rejected = nr_check_Msg4_MsgB_Ack(mod_id, frame, slot, UE, success); bool ue_rejected = nr_check_Msg4_MsgB_Ack(mod_id, frame, slot, UE, success);
if (ue_rejected) { if (ue_rejected) {
@@ -1042,16 +1079,34 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c
break; break;
} }
DevAssert(harq->is_waiting); DevAssert(harq->is_waiting);
const int n_ack_bits = harq->sched_pdsch.nrOfLayers > 4 ? 2 : 1; 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; const int8_t pid = sched_ctrl->feedback_dl_harq.head;
remove_front_nr_list(&sched_ctrl->feedback_dl_harq); remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
bool success = uci_234->harq.harq_crc != 1 && harq_bit + n_ack_bits <= uci_234->harq.harq_bit_len; 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++) { for (int q = 0; q < n_ack_bits && harq_bit + q < uci_234->harq.harq_bit_len; q++) {
const int bit = harq_bit + q; const int logical_bit = harq_bit + q;
const int acknack = (uci_234->harq.harq_payload[bit >> 3] >> (bit & 7)) & 0x01; /* get_downlink_ack() reverses the Type-2 codebook before PUCCH
LOG_D(NR_MAC, "%4d.%2d bit %d pid %d cw %d ack/nack %d\n", frame, slot, bit, pid, q, acknack); * 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; 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) // TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133)
if (success && harq->start_tci_timer) { if (success && harq->start_tci_timer) {
int slots = 3 * nrmac->frame_structure.numb_slots_frame / 10; int slots = 3 * nrmac->frame_structure.numb_slots_frame / 10;
@@ -1059,7 +1114,7 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c
nr_timer_start(&sched_ctrl->tci_beam_switch); nr_timer_start(&sched_ctrl->tci_beam_switch);
harq->start_tci_timer = false; harq->start_tci_timer = false;
} }
handle_dl_harq(nrmac, UE, pid, success, nrmac->dl_bler.harq_round_max); handle_dl_harq(nrmac, UE, pid, ack_mask, nrmac->dl_bler.harq_round_max);
harq_bit += n_ack_bits; harq_bit += n_ack_bits;
} }
free(uci_234->harq.harq_payload); free(uci_234->harq.harq_payload);
@@ -1137,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) 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); int resource_id = get_pucch_resourceid(pucch_Config, O_uci, pucch_resource);
@@ -1182,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"); 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 // this function returns an index to NR_sched_pucch structure
// if the function returns -1 it was not possible to schedule acknack // if the function returns -1 it was not possible to schedule acknack
int nr_acknack_scheduling(gNB_MAC_INST *mac, int nr_acknack_scheduling(gNB_MAC_INST *mac,
@@ -1203,6 +1345,7 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
const NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP; const NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
const frame_structure_t *fs = &mac->frame_structure; const frame_structure_t *fs = &mac->frame_structure;
const int n_slots_frame = fs->numb_slots_frame; 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_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_PUCCH_Config_t *pucch_Config = ul_bwp->pucch_Config; NR_PUCCH_Config_t *pucch_Config = ul_bwp->pucch_Config;
@@ -1238,11 +1381,28 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
if (curr_pucch->active && if (curr_pucch->active &&
curr_pucch->frame == pucch_frame && curr_pucch->frame == pucch_frame &&
curr_pucch->ul_slot == pucch_slot) { // if there is already a PUCCH in given frame and slot 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", 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->csi_bits); frame, slot, pucch_frame, pucch_slot, curr_pucch->dai_c, curr_pucch->harq_ack_bits, curr_pucch->csi_bits);
// we can't schedule if short pucch is already full const int old_uci_bits = curr_pucch->csi_bits + curr_pucch->harq_ack_bits + curr_pucch->sr_flag;
if (curr_pucch->csi_bits == 0 && const int new_uci_bits = old_uci_bits + n_ack_bits;
curr_pucch->dai_c == 2) /* 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; continue;
// if there is CSI but simultaneous HARQ+CSI is disable we can't schedule // if there is CSI but simultaneous HARQ+CSI is disable we can't schedule
if (curr_pucch->csi_bits > 0 && !curr_pucch->simultaneous_harqcsi) if (curr_pucch->csi_bits > 0 && !curr_pucch->simultaneous_harqcsi)
@@ -1252,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) // the number of bits in the check need to include possible SR (1 bit)
// and the ack/nack bit to be scheduled (1 bit) // and the ack/nack bit to be scheduled (1 bit)
// so the number of bits already scheduled in current pucch + 2 // 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) // 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; continue;
// otherwise we can schedule in this active PUCCH // otherwise we can schedule in this active PUCCH
// no need to check VRB occupation because already done when PUCCH has been activated // no need to check VRB occupation because already done when PUCCH has been activated
curr_pucch->timing_indicator = f; curr_pucch->timing_indicator = f;
curr_pucch->dai_c++; 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", curr_pucch->harq_ack_bits += n_ack_bits;
frame,slot,curr_pucch->frame,curr_pucch->ul_slot,pucch_index,f,curr_pucch->dai_c,curr_pucch->csi_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 return pucch_index; // index of current PUCCH structure
} }
else if (curr_pucch->active) { else if (curr_pucch->active) {
@@ -1310,11 +1466,12 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
curr_pucch->ul_slot = pucch_slot; curr_pucch->ul_slot = pucch_slot;
curr_pucch->timing_indicator = f; // index in the list of timing indicators curr_pucch->timing_indicator = f; // index in the list of timing indicators
curr_pucch->dai_c++; 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->resource_indicator = 0; // each UE has dedicated PUCCH resources
curr_pucch->r_pucch=r_pucch; 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", 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); 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 // blocking resources for current PUCCH in VRB map
set_pucch0_vrb_occupation(curr_pucch, vrb_map_UL, bwp_start); set_pucch0_vrb_occupation(curr_pucch, vrb_map_UL, bwp_start);
+6
View File
@@ -188,6 +188,8 @@ typedef struct nr_mac_config_s {
bool force_UL256qam_off; bool force_UL256qam_off;
bool use_deltaMCS; bool use_deltaMCS;
int maxMIMO_layers; int maxMIMO_layers;
/// PDSCH dmrs-AdditionalPosition: 0 for pos0, 1 for pos1.
int pdsch_dmrs_additional_position;
bool disable_harq; bool disable_harq;
nr_power_config_t pusch; nr_power_config_t pusch;
/// SNR threshold needed to put or not a PRB in the black list /// 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; bool sr_flag;
int csi_bits; int csi_bits;
bool simultaneous_harqcsi; bool simultaneous_harqcsi;
/// Number of PDSCH assignments represented by the DAI field.
uint8_t dai_c; 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 timing_indicator;
uint8_t resource_indicator; uint8_t resource_indicator;
int r_pucch; int r_pucch;
@@ -508,6 +512,8 @@ typedef struct NR_sched_pdsch {
typedef struct NR_UE_harq { typedef struct NR_UE_harq {
bool is_waiting; bool is_waiting;
uint8_t ndi; 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; uint8_t round;
uint16_t feedback_frame; uint16_t feedback_frame;
uint16_t feedback_slot; uint16_t feedback_slot;
+7 -2
View File
@@ -1734,8 +1734,13 @@ static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap,
dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID0 = NULL; dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID0 = NULL;
dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID1 = NULL; dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID1 = NULL;
dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = calloc(1, sizeof(*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition)); dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = calloc(1, sizeof(*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition));
// TODO possible improvement is to select based on some input additional position AssertFatal(configuration->pdsch_dmrs_additional_position == 0 || configuration->pdsch_dmrs_additional_position == 1,
*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = NR_DMRS_DownlinkConfig__dmrs_AdditionalPosition_pos1; "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->dataScramblingIdentityPDSCH = NULL;
pdsch_Config->resourceAllocation = NR_PDSCH_Config__resourceAllocation_resourceAllocationType1; pdsch_Config->resourceAllocation = NR_PDSCH_Config__resourceAllocation_resourceAllocationType1;
+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 /* let's take average over the last 100 milliseconds
* initial_size of 1024 (packets) is arbitrary * 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)); 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 /* let's take average over the last 100 milliseconds
* initial_size of 1024 is arbitrary * 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; 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 /* let's take average over the last 100 milliseconds
* initial_size of 1024 is arbitrary * 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; 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) { if (rb != NULL) {
rb->avg_time_is_on = is_on; // 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); time_average_reset(rb->txsdu_avg_time_to_tx);
rb->avg_time_is_on = is_on;
} else { } else {
LOG_E(RLC, "Radio Bearer (channel ID %d) is NULL for UE %d\n", channel_id, ue_id); LOG_E(RLC, "Radio Bearer (channel ID %d) is NULL for UE %d\n", channel_id, ue_id);
} }
+7
View File
@@ -84,6 +84,10 @@
#ifdef E2_AGENT #ifdef E2_AGENT
#include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h" #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) \ #define E2_AGENT_SIGNAL_DL_DCCH_RRC_MSG(BUF, LEN, ID) \
do { \ do { \
byte_array_t buffer_ba = {.len = LEN}; \ byte_array_t buffer_ba = {.len = LEN}; \
@@ -2735,10 +2739,13 @@ void rrc_remove_ue(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context_p)
LOG_I(NR_RRC, "removed UE CU UE ID %u/RNTI %04x \n", UE->rrc_ue_id, UE->rnti); LOG_I(NR_RRC, "removed UE CU UE ID %u/RNTI %04x \n", UE->rrc_ue_id, UE->rnti);
rrc_delete_ue_data(UE); rrc_delete_ue_data(UE);
#ifdef E2_AGENT #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 */ rc_teardown_lock(); /* block in-flight RC indications during teardown */
#endif #endif
rrc_gNB_remove_ue_context(rrc, ue_context_p); rrc_gNB_remove_ue_context(rrc, ue_context_p);
#ifdef E2_AGENT #ifdef E2_AGENT
if (rc_lock_available)
rc_teardown_unlock(); rc_teardown_unlock();
#endif #endif
} }
+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]; double Doppler_phase_cur = channelDesc->Doppler_phase_cur[rxAnt];
Doppler_phase_cur -= 2 * M_PI * round(Doppler_phase_cur / (2 * M_PI)); 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++) { for (int i = 0; i < nbSamples; i++) {
cf_t *out_ptr = after_channel_sig + i; cf_t *out_ptr = after_channel_sig + i;
struct complexd rx_tmp = {0}; struct complexd rx_tmp = {0};
+125 -10
View File
@@ -56,6 +56,14 @@ extern int get_currentchannels_type(const char *buf,
#define SEND_BUFF_SIZE 100000000 // Socket buffer size #define SEND_BUFF_SIZE 100000000 // Socket buffer size
#define MAX_BEAMS 64 #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 // Simulator role
typedef enum { SIMU_ROLE_SERVER = 1, SIMU_ROLE_CLIENT } simuRole; 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 \ #define RFSIM_CONFIG_HELP_OPTIONS \
" list of comma separated options to enable rf simulator functionalities. Available options: \n" \ " list of comma separated options to enable rf simulator functionalities. Available options: \n" \
" chanmod: enable channel modelisation\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 simOpt PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED
#define simBool PARAMFLAG_BOOL | 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; rfsim_packet_t *packet_ptr;
size_t payload_sz; size_t payload_sz;
size_t remainToTransferBeam; size_t remainToTransferBeam;
bool packet_iq8;
std::queue<rfsim_packet_t *> received_packets; std::queue<rfsim_packet_t *> received_packets;
} buffer_t; } buffer_t;
@@ -191,6 +201,12 @@ typedef struct {
double rx_freq; double rx_freq;
double tx_bw; double tx_bw;
int channelmod; 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_pathloss;
double chan_forgetfact; double chan_forgetfact;
uint64_t chan_offset; uint64_t chan_offset;
@@ -463,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) static float get_rx_gain_db(rfsimulator_state_t *rfsimulator, uint rx_beam, uint tx_beam)
{ {
if (!rfsimulator->beam_ctrl->enable_beams) { if (!rfsimulator->beam_ctrl->enable_beams) {
@@ -584,6 +643,10 @@ static void rfsimulator_readconfig(rfsimulator_state_t *rfsimulator)
rfsimulator->rx_freq, rfsimulator->rx_freq,
rfsimulator->tx_bw); rfsimulator->tx_bw);
rfsimulator->channelmod = true; 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 { } else {
fprintf(stderr, "unknown rfsimulator option: %s\n", rfsimuParam[p].strlistptr[i]); fprintf(stderr, "unknown rfsimulator option: %s\n", rfsimuParam[p].strlistptr[i]);
exit(-1); exit(-1);
@@ -975,19 +1038,53 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t,
mutexlock(t->Sockmutex); mutexlock(t->Sockmutex);
LOG_D(HW, "Sending %d samples at time: %ld, nbAnt %d\n", nsamps, timestamp, nbAnt); LOG_D(HW, "Sending %d samples at time: %ld, nbAnt %d\n", nsamps, timestamp, nbAnt);
for (int i = 0; i < MAX_FD_RFSIMU; i++) { const int num_beams = tx_beams.size();
buffer_t *b = &t->buf[i]; AssertFatal(num_beams > 0, "Must set at least one bit in beam_map\n");
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)};
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. // 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::vector<size_t> indices(tx_beams.size());
std::iota(indices.begin(), indices.end(), 0); 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]; }); 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"); 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,
use_iq8 ? RFSIM_OPTION_IQ8 : 0,
beams_to_beam_map(tx_beams)};
fullwrite(b->conn_sock, &header, sizeof(header), 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 beam = 0; beam < num_beams; beam++) {
for (int a = 0; a < nbAnt; a++) { for (int a = 0; a < nbAnt; a++) {
sample_t *in = (sample_t *)samplesVoid[indices[beam]][a]; sample_t *in = (sample_t *)samplesVoid[indices[beam]][a];
@@ -996,6 +1093,7 @@ static int rfsimulator_write_internal(rfsimulator_state_t *t,
} }
} }
} }
}
if (t->lastWroteTS > timestamp) if (t->lastWroteTS > timestamp)
LOG_W(HW, "Not supported to send Tx out of order %lu, %lu\n", t->lastWroteTS, timestamp); LOG_W(HW, "Not supported to send Tx out of order %lu, %lu\n", t->lastWroteTS, timestamp);
@@ -1106,6 +1204,9 @@ static void process_recv_header(rfsimulator_state_t *t, buffer_t *b, bool first_
{ {
b->headerMode = false; // We got the header b->headerMode = false; // We got the header
AssertFatal(b->th.nbAnt != 0, "Number of antennas not set\n"); 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) { if (b->nbAnt != b->th.nbAnt) {
LOG_A(HW, "RFsim: Number of antennas changed from %d to %d\n", 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; b->nbAnt = b->th.nbAnt;
@@ -1132,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 = static_cast<rfsim_packet_t *>(calloc_or_fail(1, payload_sz + sizeof(samplesBlockHeader_t)));
b->packet_ptr->header = b->th; b->packet_ptr->header = b->th;
b->transferPtr = b->packet_ptr->payload; 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; return;
} }
@@ -1274,6 +1377,17 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time)
b->remainToTransfer = sizeof(samplesBlockHeader_t); b->remainToTransfer = sizeof(samplesBlockHeader_t);
if (!b->trashingPacket) { 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; b->lastReceivedTS = b->th.timestamp + b->th.size;
LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS); LOG_D(HW, "UEsock: %d Set b->lastReceivedTS %ld\n", b->conn_sock, b->lastReceivedTS);
b->received_packets.emplace(b->packet_ptr); b->received_packets.emplace(b->packet_ptr);
@@ -1281,6 +1395,7 @@ static bool flushInput(rfsimulator_state_t *t, int timeout, bool first_time)
free(b->packet_ptr); free(b->packet_ptr);
} }
b->packet_ptr = NULL; b->packet_ptr = NULL;
b->packet_iq8 = false;
b->trashingPacket = false; b->trashingPacket = false;
} }
} }
@@ -369,7 +369,7 @@
<supportedBandwidthDL> <supportedBandwidthDL>
<fr1><mhz100/></fr1> <fr1><mhz100/></fr1>
</supportedBandwidthDL> </supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH> <maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL> <supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC> </FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC> <FeatureSetDownlinkPerCC>
@@ -378,7 +378,7 @@
<fr1><mhz100/></fr1> <fr1><mhz100/></fr1>
</supportedBandwidthDL> </supportedBandwidthDL>
<channelBW-90mhz><supported/></channelBW-90mhz> <channelBW-90mhz><supported/></channelBW-90mhz>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH> <maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL> <supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC> </FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC> <FeatureSetDownlinkPerCC>
@@ -394,7 +394,7 @@
<supportedBandwidthDL> <supportedBandwidthDL>
<fr1><mhz40/></fr1> <fr1><mhz40/></fr1>
</supportedBandwidthDL> </supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH> <maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL> <supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC> </FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC> <FeatureSetDownlinkPerCC>
@@ -402,7 +402,7 @@
<supportedBandwidthDL> <supportedBandwidthDL>
<fr1><mhz40/></fr1> <fr1><mhz40/></fr1>
</supportedBandwidthDL> </supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH> <maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL> <supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC> </FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC> <FeatureSetDownlinkPerCC>
@@ -410,7 +410,7 @@
<supportedBandwidthDL> <supportedBandwidthDL>
<fr1><mhz80/></fr1> <fr1><mhz80/></fr1>
</supportedBandwidthDL> </supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH> <maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL> <supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC> </FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC> <FeatureSetDownlinkPerCC>
@@ -418,7 +418,7 @@
<supportedBandwidthDL> <supportedBandwidthDL>
<fr1><mhz20/></fr1> <fr1><mhz20/></fr1>
</supportedBandwidthDL> </supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH> <maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL> <supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC> </FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC> <FeatureSetDownlinkPerCC>
@@ -434,7 +434,7 @@
<supportedBandwidthDL> <supportedBandwidthDL>
<fr1><mhz20/></fr1> <fr1><mhz20/></fr1>
</supportedBandwidthDL> </supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH> <maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL> <supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC> </FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC> <FeatureSetDownlinkPerCC>
@@ -450,7 +450,7 @@
<supportedBandwidthDL> <supportedBandwidthDL>
<fr1><mhz30/></fr1> <fr1><mhz30/></fr1>
</supportedBandwidthDL> </supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH> <maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL> <supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC> </FeatureSetDownlinkPerCC>
<FeatureSetDownlinkPerCC> <FeatureSetDownlinkPerCC>
@@ -458,7 +458,7 @@
<supportedBandwidthDL> <supportedBandwidthDL>
<fr1><mhz60/></fr1> <fr1><mhz60/></fr1>
</supportedBandwidthDL> </supportedBandwidthDL>
<maxNumberMIMO-LayersPDSCH><fourLayers/></maxNumberMIMO-LayersPDSCH> <maxNumberMIMO-LayersPDSCH><eightLayers/></maxNumberMIMO-LayersPDSCH>
<supportedModulationOrderDL><qam256/></supportedModulationOrderDL> <supportedModulationOrderDL><qam256/></supportedModulationOrderDL>
</FeatureSetDownlinkPerCC> </FeatureSetDownlinkPerCC>
</featureSetsDownlinkPerCC> </featureSetsDownlinkPerCC>