Add fsn lib to simplify frame/slot handling

Refactor existing code, and harmonize interface. Add a couple of tests.

Signed-off-by: Robert Schmidt <robert.schmidt@openairinterface.org>
This commit is contained in:
Robert Schmidt
2026-04-15 10:26:53 +02:00
parent b81442de91
commit 449910dbc7
8 changed files with 189 additions and 43 deletions
+1 -1
View File
@@ -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)
+55
View File
@@ -0,0 +1,55 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "fsn.h"
#include <assert.h>
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;
}
+36
View File
@@ -0,0 +1,36 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef FSN_H_
#define FSN_H_
#include <stdint.h>
#include <stdbool.h>
/// 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_ */
+5
View File
@@ -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)
+84
View File
@@ -0,0 +1,84 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <gtest/gtest.h>
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();
}
@@ -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);
}
}
+1
View File
@@ -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);
+1 -8
View File
@@ -16,6 +16,7 @@
#include <string.h>
#include <complex.h>
#include <pthread.h>
#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