From 449910dbc75bc8a2c9db66cbbdce05704551e2c7 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 17 Mar 2026 08:06:16 +0100 Subject: [PATCH] Add fsn lib to simplify frame/slot handling Refactor existing code, and harmonize interface. Add a couple of tests. Signed-off-by: Robert Schmidt --- common/utils/CMakeLists.txt | 2 +- common/utils/fsn.c | 55 ++++++++++++ common/utils/fsn.h | 36 ++++++++ common/utils/tests/CMakeLists.txt | 5 ++ common/utils/tests/test_fsn.cpp | 84 +++++++++++++++++++ .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 40 ++------- openair2/LAYER2/NR_MAC_gNB/main.c | 1 + openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 9 +- 8 files changed, 189 insertions(+), 43 deletions(-) create mode 100644 common/utils/fsn.c create mode 100644 common/utils/fsn.h create mode 100644 common/utils/tests/test_fsn.cpp diff --git a/common/utils/CMakeLists.txt b/common/utils/CMakeLists.txt index 3196b39868..fc113ffa46 100644 --- a/common/utils/CMakeLists.txt +++ b/common/utils/CMakeLists.txt @@ -16,7 +16,7 @@ add_subdirectory(nr) add_subdirectory(LOG) add_subdirectory(threadPool) add_subdirectory(time_manager) -add_library(utils utils.c system.c time_meas.c time_stat.c tuntap_if.c reverse_bits.c) +add_library(utils utils.c system.c time_meas.c time_stat.c tuntap_if.c reverse_bits.c fsn.c) target_include_directories(utils PUBLIC .) target_link_libraries(utils PRIVATE ${T_LIB}) add_subdirectory(barrier) diff --git a/common/utils/fsn.c b/common/utils/fsn.c new file mode 100644 index 0000000000..e8566a2da9 --- /dev/null +++ b/common/utils/fsn.c @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#include "fsn.h" + +#include + +static int num_slots[5] = {10, 20, 40, 80, 160}; + +fsn_t fsn_add_delta(fsn_t fsn, uint32_t delta) +{ + const int slots_frame = num_slots[fsn.mu]; + int next_f = fsn.f + delta / slots_frame; + int next_s = fsn.s + delta % slots_frame; + fsn_t res = { + .f = (next_f + next_s / slots_frame) % 1024, + .s = next_s % slots_frame, + .mu = fsn.mu, + }; + return res; +} + +int fsn_get_diff(fsn_t a, fsn_t b) +{ + assert(a.mu == b.mu); + const int slots_frame = num_slots[a.mu]; + int diff = (a.f * slots_frame + a.s) - (b.f * slots_frame + b.s); + if (diff < -512 * slots_frame) + diff += 1024 * slots_frame; + else if (diff > 512 * slots_frame) + diff -= 1024 * slots_frame; + return diff; +} + +fsn_t fsn_get_max(fsn_t a, fsn_t b) +{ + if (fsn_get_diff(a, b) <= 0) { + return b; + } else { + return a; + } +} + +bool fsn_equal(fsn_t a, fsn_t b) +{ + assert(a.mu == b.mu); + return a.f == b.f && a.s == b.s && a.mu == b.mu; +} + +bool fsn_in_the_past(fsn_t old, fsn_t new) +{ + int diff = fsn_get_diff(old, new); + return diff < 0; +} diff --git a/common/utils/fsn.h b/common/utils/fsn.h new file mode 100644 index 0000000000..dfa126bc85 --- /dev/null +++ b/common/utils/fsn.h @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#ifndef FSN_H_ +#define FSN_H_ + +#include +#include + +/// helper type to encapsulate a frame/slot combination in a single type. +typedef struct fsn { + uint16_t f; /// frame + uint16_t s; /// slot + uint8_t mu; /// numerology +} fsn_t; + +/* \brief Add delta slots to fsn. Note that this depends also on the stored + * numerology inside fsn. */ +fsn_t fsn_add_delta(fsn_t fsn, uint32_t delta); + +/* \brief Get difference of slots for a and b. + * \note asserts if numerology not the same. */ +int fsn_get_diff(fsn_t a, fsn_t b); + +/* \brief Gets the larger of two fsns */ +fsn_t fsn_get_max(fsn_t a, fsn_t b); + +/* \brief Compare if both fsn_t's are equal */ +bool fsn_equal(fsn_t a, fsn_t b); + +/* \brief Determine if told is in the past of tnew. The threshold is 512 + * frames. */ +bool fsn_in_the_past(fsn_t told, fsn_t tnew); + +#endif /* FSN_H_ */ diff --git a/common/utils/tests/CMakeLists.txt b/common/utils/tests/CMakeLists.txt index e3c310fed9..7401e2d1ea 100644 --- a/common/utils/tests/CMakeLists.txt +++ b/common/utils/tests/CMakeLists.txt @@ -2,3 +2,8 @@ add_executable(test_tpool_vs_actors test_tpool_vs_actors.c) target_link_libraries(test_tpool_vs_actors PRIVATE thread-pool pthread LOG minimal_lib actor) + +add_executable(test_fsn test_fsn.cpp) +add_dependencies(tests test_fsn) +target_link_libraries(test_fsn PRIVATE utils GTest::gtest) +add_test(NAME test_fsn COMMAND ./test_fsn) diff --git a/common/utils/tests/test_fsn.cpp b/common/utils/tests/test_fsn.cpp new file mode 100644 index 0000000000..3cbe34dfb2 --- /dev/null +++ b/common/utils/tests/test_fsn.cpp @@ -0,0 +1,84 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#include +extern "C" { +#include "fsn.h" +} + +TEST(fsn, test_mu0_fsn_add) { + fsn_t current = {123, 5, 0}; + fsn_t next = current; + + next = fsn_add_delta(next, 12); + EXPECT_EQ(next.f, 124); + EXPECT_EQ(next.s, 7); + EXPECT_EQ(next.mu, 0); + + next = fsn_add_delta(next, 10); + EXPECT_EQ(next.f, 125); + EXPECT_EQ(next.s, 7); + EXPECT_EQ(next.mu, 0); + + next = fsn_add_delta(next, 10 * 1024); + EXPECT_EQ(next.f, 125); + EXPECT_EQ(next.s, 7); + EXPECT_EQ(next.mu, 0); +} + +TEST(fsn, test_mu0_get_diff) { + fsn_t current = {123, 5, 0}; + fsn_t next = fsn_add_delta(current, 12); + int diff; + + diff = fsn_get_diff(current, next); + EXPECT_EQ(diff, -12); + + diff = fsn_get_diff(next, current); + EXPECT_EQ(diff, 12); +} + +TEST(fsn, test_mu0_get_max) { + fsn_t current = {123, 5, 0}; + fsn_t next = {125, 7, 0}; + + fsn_t max = fsn_get_max(current, next); + EXPECT_EQ(max.f, next.f); + EXPECT_EQ(max.s, next.s); + EXPECT_EQ(max.mu, next.mu); +} + +TEST(fsn, test_mu0_equal) { + fsn_t a = {123, 5, 0}; + fsn_t b = a; + fsn_t c = {123, 6, 0}; + + EXPECT_TRUE(fsn_equal(a, b)); + EXPECT_FALSE(fsn_equal(a, c)); +} + +TEST(fsn, test_mu0_in_the_past) { + fsn_t a = {0, 0, 0}; + fsn_t n = {100, 9, 0}; + fsn_t b_bef = {511, 9, 0}; + fsn_t b_ex = {512, 0, 0}; + fsn_t b_aft = {512, 1, 0}; + + // a is in the past of n + EXPECT_TRUE(fsn_in_the_past(a, n)); + // similarly, a is in the past of b_bef + EXPECT_TRUE(fsn_in_the_past(a, b_bef)); + // difference "to both sides" is 512 frames, so we consider a still in the + // past of b_ex + EXPECT_TRUE(fsn_in_the_past(a, b_ex)); + // (b_aft - a) < (a - b_aft) => b_aft is in the past of a because of + // wrap-around + EXPECT_TRUE(fsn_in_the_past(b_aft, a)); +} + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index be869d9de9..db35776944 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -2528,34 +2528,6 @@ void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE nr_mac->cset0_bwp_size); } -fsn_t fs_add_delta(const frame_structure_t *fs, uint32_t delta, fsn_t fsn) -{ - const int slots_frame = fs->numb_slots_frame; - fsn_t res = { - .f = (fsn.f + (fsn.s + delta) / slots_frame) % MAX_FRAME_NUMBER, - .s = (fsn.s + delta) % slots_frame, - }; - return res; -} -int fs_get_diff(const frame_structure_t *fs, fsn_t a, fsn_t b) -{ - const int slots_frame = fs->numb_slots_frame; - int diff = (a.f * slots_frame + a.s) - (b.f * slots_frame + b.s); - if (diff < -512 * slots_frame) - diff += 1024 * slots_frame; - else if (diff > 512 * slots_frame) - diff -= 1024 * slots_frame; - return diff; -} -fsn_t fs_get_max(const frame_structure_t *fs, fsn_t a, fsn_t b) -{ - if (fs_get_diff(fs, a, b) <= 0) { - return b; - } else { - return a; - } -} - static void nr_ulsch_preprocessor(gNB_MAC_INST *nr_mac, post_process_pusch_t *pp_pusch) { int frame = pp_pusch->frame; @@ -2579,8 +2551,8 @@ static void nr_ulsch_preprocessor(gNB_MAC_INST *nr_mac, post_process_pusch_t *pp // FAPI cannot handle more than MAX_DCI_CORESET DCIs max_dci = min(max_dci, MAX_DCI_CORESET); - fsn_t current = {frame, slot}; - fsn_t min_next = fs_add_delta(fs, min_rxtx, current); + fsn_t current = {frame, slot, *scc->ssbSubcarrierSpacing}; + fsn_t min_next = fsn_add_delta(current, min_rxtx); /* if it's the last DL slot, we should try all TDAs to make sure that the * scheduler can reach e.g. the next mixed slot. Otherwise, if we don't, we * might starve HARQ processes that need a retransmission in a specific slot @@ -2589,16 +2561,16 @@ static void nr_ulsch_preprocessor(gNB_MAC_INST *nr_mac, post_process_pusch_t *pp fsn_t *next = &nr_mac->ul_next; while (max_dci > 0) { /* go to the next UL slot, skipping DL if necessary */ - *next = fs_get_max(fs, *next, min_next); + *next = fsn_get_max(*next, min_next); while (!is_ul_slot(next->s, fs)) - *next = fs_add_delta(fs, 1, *next); + *next = fsn_add_delta(*next, 1); if (!is_dl_slot(current.s, fs)) // if current slot is not DL, nothing to do break; /* get a TDA so that next UL scheduling slot can be reached from current, * and exit if there is no such TDA. Remove koffset, as it is * "cell-specific", i.e., the UE will add it on the computation. */ - int k2 = fs_get_diff(fs, *next, current) - koffset; + int k2 = fsn_get_diff(*next, current) - koffset; DevAssert(k2 > 0); // we assume that all beams have the same symbol utilization in all RBs for // simplification (but this might not be true). Otherwise, we would need to @@ -2631,7 +2603,7 @@ static void nr_ulsch_preprocessor(gNB_MAC_INST *nr_mac, post_process_pusch_t *pp break; max_dci -= sched; - *next = fs_add_delta(fs, 1, *next); + *next = fsn_add_delta(*next, 1); } } diff --git a/openair2/LAYER2/NR_MAC_gNB/main.c b/openair2/LAYER2/NR_MAC_gNB/main.c index ca12c48c48..1f619308c2 100644 --- a/openair2/LAYER2/NR_MAC_gNB/main.c +++ b/openair2/LAYER2/NR_MAC_gNB/main.c @@ -293,6 +293,7 @@ void mac_top_init_gNB(ngran_node_t node_type, RC.nrmac[i]->cset0_bwp_start = 0; RC.nrmac[i]->cset0_bwp_size = 0; + RC.nrmac[i]->ul_next = (fsn_t) {.mu = *scc->ssbSubcarrierSpacing}; RC.nrmac[i]->print_ue_stats = true; pthread_mutex_init(&RC.nrmac[i]->sched_lock, NULL); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index f7ea466a94..391bfeabe8 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -16,6 +16,7 @@ #include #include #include +#include "fsn.h" #include "common/utils/ds/seq_arr.h" #include "common/utils/nr/nr_common.h" #include "common/utils/ds/byte_array.h" @@ -878,14 +879,6 @@ typedef struct { NR_ControlResourceSet_t coreset; } NR_sched_ctrl_sib1_t; -/// helper type to encapsulate a frame/slot combination in a single type. -/// Currently only used in the UL preprocessor. Note: if you use this type -/// further, please refactor it into a common type first. -typedef struct fsn { - frame_t f; - slot_t s; -} fsn_t; - typedef struct NR_du_stats { /// cell-wide wide-band CQI distribution, see 28.552 5.1.1.11.1; /// 0-15 CQI, 1-8 RI, 1-3 CQI table