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
+3
View File
@@ -5,3 +5,6 @@ target_link_libraries(test_nr_common PRIVATE nr_common GTest::gtest minimal_lib)
add_dependencies(tests test_nr_common)
add_test(NAME test_nr_common
COMMAND ./test_nr_common)
add_executable(nr_rank_selector_benchmark nr_rank_selector_benchmark.cpp)
target_link_libraries(nr_rank_selector_benchmark PRIVATE nr_common minimal_lib)
@@ -0,0 +1,63 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <algorithm>
#include <cmath>
#include <cstdio>
extern "C" {
#include "nr_common.h"
}
struct rank_scenario_t {
const char *name;
float eigenvalues[NR_MAX_NB_LAYERS];
float noise_power;
};
static uint8_t legacy_rank(const float eigenvalues[NR_MAX_NB_LAYERS])
{
uint8_t rank = 1;
for (int layer = 1; layer < NR_MAX_NB_LAYERS; layer++) {
if (eigenvalues[layer] <= 0.0f
|| 10.0f * std::log10(eigenvalues[0] / eigenvalues[layer]) >= 8.0f)
break;
rank++;
}
return rank;
}
int main()
{
const rank_scenario_t scenarios[] = {
{"dominant_one", {100.0f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f}, 1.0f},
{"four_modes", {100.0f, 80.0f, 60.0f, 40.0f, 0.01f, 0.01f, 0.01f, 0.01f}, 1.0f},
{"eight_modes", {100.0f, 95.0f, 90.0f, 85.0f, 80.0f, 75.0f, 70.0f, 65.0f}, 1.0f},
{"balanced_low_snr", {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}, 10.0f},
{"legacy_false_high_rank", {1.0f, 0.16f, 0.16f, 0.16f, 0.16f, 0.16f, 0.16f, 0.16f}, 10.0f},
{"weak_eighth", {100.0f, 90.0f, 80.0f, 70.0f, 60.0f, 50.0f, 40.0f, 5.0f}, 1.0f},
};
std::puts("scenario,policy,selected_rank,selected_capacity_bps_hz");
for (const rank_scenario_t &scenario : scenarios) {
float scores[NR_MAX_NB_LAYERS] = {};
nr_pdsch_rank_capacity_scores(scenario.eigenvalues,
NR_MAX_NB_LAYERS,
scenario.noise_power,
scores);
const uint8_t capacity_rank = nr_pdsch_select_rank(scores, NR_MAX_NB_LAYERS, 0.05f);
const uint8_t legacy = legacy_rank(scenario.eigenvalues);
const uint8_t policies[] = {capacity_rank, legacy, 1, 4, 8};
const char *policy_names[] = {"capacity", "legacy", "fixed1", "fixed4", "fixed8"};
for (int policy = 0; policy < 5; policy++) {
const uint8_t rank = std::min<uint8_t>(policies[policy], NR_MAX_NB_LAYERS);
std::printf("%s,%s,%u,%.6f\n",
scenario.name,
policy_names[policy],
rank,
scores[rank - 1]);
}
}
return 0;
}
+108
View File
@@ -2,6 +2,7 @@
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <climits>
#include <gtest/gtest.h>
extern "C" {
#include "nr_common.h"
@@ -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)
{