mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
Merge remote-tracking branch 'origin/l1-rx-use-queues' into integration_2026_w16 (!3991)
L1 RX: use queues instead of arrays and linear search for PUCCH, PUSCH, SRS, PRACH
This MR is an attempt to reduce the time L1 RX searches in array for the
next job to process, and instead use a queue. This queue is a FIFO,
because the various jobs (FAPI messages) to process come in order, and
need to be processed in order. The MR (hopefully, to be measured)
reduces the amount of time spent searching for the next UE (because the
next job is always at the beginning of the queue), and should scale
better for many UEs.
It does the following:
- introduce two helper libraries for (1) Frame.Slot calculation (sfn_t),
already introduced in !3521 (merged) commit 3102068e, and (2) a ring
buffer with fixed size
- use sfn_t and ring buffer (gNB->pucch_queue) to remove the linear
array for PUCCH (gNB->pucch)
- use sfn_t and ring buffer (gNB->pusch_queue) for some PUSCH lookups.
Because we need to still store PUSCH contexts, gNB->pusch is still
there
- use sfn_t and ring buffer (gNB->srs_queue) to remove the linear array
for SRS (gNB->srs)
- use sfn_t and ring buffers (gNB->prach_ru_queue and
gNB->prach_l1rx_queue) to remove the linear array for PRACH
(gNB->prach_list)
- some minor cleanups, e.g., additional loops over the PUSCH array,
using const, using pointers instead of indices, etc
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -6,6 +6,7 @@ add_library(ds OBJECT
|
||||
seq_arr.c
|
||||
hashtable.c
|
||||
obj_hashtable.c
|
||||
spsc_q.c
|
||||
)
|
||||
|
||||
target_include_directories(ds PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
@@ -10,36 +10,6 @@
|
||||
#include "assertions.h"
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------------------------------
|
||||
char *hashtable_rc_code2string(hashtable_rc_t rcP)
|
||||
//-------------------------------------------------------------------------------------------------------------------------------
|
||||
{
|
||||
switch (rcP) {
|
||||
case HASH_TABLE_OK:
|
||||
return "HASH_TABLE_OK";
|
||||
break;
|
||||
|
||||
case HASH_TABLE_INSERT_OVERWRITTEN_DATA:
|
||||
return "HASH_TABLE_INSERT_OVERWRITTEN_DATA";
|
||||
break;
|
||||
|
||||
case HASH_TABLE_KEY_NOT_EXISTS:
|
||||
return "HASH_TABLE_KEY_NOT_EXISTS";
|
||||
break;
|
||||
|
||||
case HASH_TABLE_KEY_ALREADY_EXISTS:
|
||||
return "HASH_TABLE_KEY_ALREADY_EXISTS";
|
||||
break;
|
||||
|
||||
case HASH_TABLE_BAD_PARAMETER_HASHTABLE:
|
||||
return "HASH_TABLE_BAD_PARAMETER_HASHTABLE";
|
||||
break;
|
||||
|
||||
default:
|
||||
return "UNKNOWN hashtable_rc_t";
|
||||
}
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------------------
|
||||
/*
|
||||
* free int function
|
||||
* hash_free_int_func() is used when this hashtable is used to store int values as data (pointer = value).
|
||||
@@ -147,41 +117,6 @@ hashtable_rc_t hashtable_is_key_exists (const hash_table_t *const hashtblP, cons
|
||||
|
||||
return HASH_TABLE_KEY_NOT_EXISTS;
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------------------
|
||||
hashtable_rc_t hashtable_dump_content (const hash_table_t *const hashtblP, char *const buffer_pP, int *const remaining_bytes_in_buffer_pP )
|
||||
//-------------------------------------------------------------------------------------------------------------------------------
|
||||
{
|
||||
hash_node_t *node = NULL;
|
||||
unsigned int i = 0;
|
||||
|
||||
if (hashtblP == NULL) {
|
||||
*remaining_bytes_in_buffer_pP = snprintf(
|
||||
buffer_pP,
|
||||
*remaining_bytes_in_buffer_pP,
|
||||
"HASH_TABLE_BAD_PARAMETER_HASHTABLE");
|
||||
return HASH_TABLE_BAD_PARAMETER_HASHTABLE;
|
||||
}
|
||||
|
||||
while ((i < hashtblP->size) && (*remaining_bytes_in_buffer_pP > 0)) {
|
||||
if (hashtblP->nodes[i] != NULL) {
|
||||
node=hashtblP->nodes[i];
|
||||
|
||||
while(node) {
|
||||
*remaining_bytes_in_buffer_pP = snprintf(
|
||||
buffer_pP,
|
||||
*remaining_bytes_in_buffer_pP,
|
||||
"Key 0x%"PRIx64" Element %p\n",
|
||||
node->key,
|
||||
node->data);
|
||||
node=node->next;
|
||||
}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
return HASH_TABLE_OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------------------------------
|
||||
/*
|
||||
|
||||
@@ -44,12 +44,10 @@ typedef struct hash_table_iterator_s {
|
||||
const hash_table_t* const hashtbl;
|
||||
} hash_table_iterator_s;
|
||||
|
||||
char *hashtable_rc_code2string(hashtable_rc_t rcP);
|
||||
void hash_free_int_func(void *memoryP);
|
||||
hash_table_t *hashtable_create (const hash_size_t size, hash_size_t (*hashfunc)(const hash_key_t ), void (*freefunc)(void *));
|
||||
hashtable_rc_t hashtable_destroy(hash_table_t **hashtbl);
|
||||
hashtable_rc_t hashtable_is_key_exists (const hash_table_t *const hashtbl, const uint64_t key);
|
||||
hashtable_rc_t hashtable_dump_content (const hash_table_t *const hashtblP, char *const buffer_pP, int *const remaining_bytes_in_buffer_pP );
|
||||
hashtable_rc_t hashtable_insert (hash_table_t *const hashtbl, const hash_key_t key, void *data);
|
||||
hashtable_rc_t hashtable_remove (hash_table_t *const hashtbl, const hash_key_t key);
|
||||
hashtable_rc_t hashtable_get (const hash_table_t *const hashtbl, const hash_key_t key, void **dataP);
|
||||
|
||||
90
common/utils/ds/spsc_q.c
Normal file
90
common/utils/ds/spsc_q.c
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "spsc_q.h"
|
||||
|
||||
spsc_q_t spsc_q_alloc(size_t cnt, size_t elsiz)
|
||||
{
|
||||
/* internally, use one element more: the ringbuffer is full if
|
||||
* (write_idx + 1) % cnt == read_idx, so it's full if one element is still
|
||||
* free (because otherwise write_idx == read_idx, but this is the empty
|
||||
* condition) */
|
||||
cnt += 1;
|
||||
spsc_q_t rb = {.cnt = cnt, .elsiz = elsiz};
|
||||
rb.buf = calloc(cnt, elsiz);
|
||||
return rb;
|
||||
}
|
||||
|
||||
void spsc_q_free(spsc_q_t *rb)
|
||||
{
|
||||
free(rb->buf);
|
||||
memset(rb, 0, sizeof(*rb));
|
||||
}
|
||||
|
||||
bool spsc_q_put(spsc_q_t *rb, const void *src, size_t elsiz)
|
||||
{
|
||||
assert(elsiz == rb->elsiz);
|
||||
size_t w = atomic_load_explicit(&rb->write_idx, memory_order_relaxed);
|
||||
size_t r = atomic_load_explicit(&rb->read_idx, memory_order_acquire);
|
||||
if ((w + 1) % rb->cnt == r) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t *bufpos = &rb->buf[w * rb->elsiz];
|
||||
memcpy(bufpos, src, elsiz);
|
||||
atomic_store_explicit(&rb->write_idx, (w + 1) % rb->cnt, memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool spsc_q_get_if(spsc_q_t *rb, pred p, void *user, void *dest, size_t elsiz)
|
||||
{
|
||||
assert(elsiz == rb->elsiz);
|
||||
size_t w = atomic_load_explicit(&rb->write_idx, memory_order_acquire);
|
||||
size_t r = atomic_load_explicit(&rb->read_idx, memory_order_relaxed);
|
||||
if (w == r) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t *bufpos = &rb->buf[rb->read_idx * rb->elsiz];
|
||||
if (!p(bufpos, user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dest)
|
||||
memcpy(dest, bufpos, elsiz);
|
||||
atomic_store_explicit(&rb->read_idx, (r + 1) % rb->cnt, memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool always(const void *data, void *user)
|
||||
{
|
||||
(void) data;
|
||||
(void) user;
|
||||
return true;
|
||||
}
|
||||
bool spsc_q_get(spsc_q_t *rb, void *dest, size_t elsiz)
|
||||
{
|
||||
return spsc_q_get_if(rb, always, NULL, dest, elsiz);
|
||||
}
|
||||
|
||||
int spsc_q_get_while(spsc_q_t *rb, pred p, void *user, void *dest, size_t elsiz, size_t max_len)
|
||||
{
|
||||
assert(elsiz == rb->elsiz);
|
||||
size_t count = 0;
|
||||
while (count < max_len && spsc_q_get_if(rb, p, user, dest + elsiz * count, elsiz))
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
int spsc_q_drop_while(spsc_q_t *rb, pred p, void *user)
|
||||
{
|
||||
size_t dropped = 0;
|
||||
while (spsc_q_get_if(rb, p, user, NULL, rb->elsiz))
|
||||
dropped++;
|
||||
return dropped;
|
||||
}
|
||||
37
common/utils/ds/spsc_q.h
Normal file
37
common/utils/ds/spsc_q.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
||||
*/
|
||||
|
||||
#ifndef SPSC_Q_H_
|
||||
#define SPSC_Q_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
// use atomic_size_t to help interworking with C++
|
||||
using std::atomic_size_t;
|
||||
#endif
|
||||
|
||||
typedef struct spsc_q {
|
||||
uint8_t *buf;
|
||||
size_t elsiz;
|
||||
size_t cnt;
|
||||
|
||||
atomic_size_t write_idx;
|
||||
atomic_size_t read_idx;
|
||||
} spsc_q_t;
|
||||
|
||||
spsc_q_t spsc_q_alloc(size_t cnt, size_t elsiz);
|
||||
void spsc_q_free(spsc_q_t *rb);
|
||||
|
||||
bool spsc_q_put(spsc_q_t *rb, const void *src, size_t elsiz);
|
||||
bool spsc_q_get(spsc_q_t *rb, void *dest, size_t elsiz);
|
||||
|
||||
typedef bool (*pred)(const void *data, void *user);
|
||||
bool spsc_q_get_if(spsc_q_t *rb, pred p, void *user, void *dest, size_t elsiz);
|
||||
int spsc_q_drop_while(spsc_q_t *rb, pred p, void *user);
|
||||
int spsc_q_get_while(spsc_q_t *rb, pred p, void *user, void *dest, size_t elsiz, size_t max_len);
|
||||
|
||||
#endif /* SPSC_Q_H_ */
|
||||
@@ -8,5 +8,11 @@ add_test(NAME test_seq_arr COMMAND test_seq_arr) # no options required
|
||||
add_executable(test_hashtable test_hashtable.cpp)
|
||||
add_dependencies(tests test_hashtable)
|
||||
target_link_libraries(test_hashtable PRIVATE ds GTest::gtest)
|
||||
add_test(NAME test_hashtable
|
||||
COMMAND ./test_hashtable)
|
||||
add_test(NAME test_hashtable COMMAND ./test_hashtable)
|
||||
|
||||
add_executable(test_spsc_q test_spsc_q.cpp)
|
||||
target_link_libraries(test_spsc_q ds GTest::gtest)
|
||||
add_dependencies(tests test_spsc_q)
|
||||
add_test(NAME test_spsc_q COMMAND test_spsc_q)
|
||||
add_executable(test_spsc_q_perf test_spsc_q_perf.cpp)
|
||||
target_link_libraries(test_spsc_q_perf PRIVATE ds benchmark::benchmark)
|
||||
|
||||
135
common/utils/ds/tests/test_spsc_q.cpp
Normal file
135
common/utils/ds/tests/test_spsc_q.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
extern "C" {
|
||||
#include "spsc_q.h"
|
||||
}
|
||||
|
||||
TEST(spsc_q, basic_test) {
|
||||
spsc_q_t rb = spsc_q_alloc(2, sizeof(int));
|
||||
|
||||
int a = 1;
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
|
||||
EXPECT_FALSE(spsc_q_put(&rb, &a, sizeof(a)));
|
||||
|
||||
int b;
|
||||
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
|
||||
EXPECT_EQ(a, b);
|
||||
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
|
||||
EXPECT_EQ(a, b);
|
||||
EXPECT_FALSE(spsc_q_get(&rb, &b, sizeof(b)));
|
||||
|
||||
spsc_q_free(&rb);
|
||||
}
|
||||
|
||||
TEST(spsc_q, cont_write) {
|
||||
spsc_q_t rb = spsc_q_alloc(10, sizeof(int));
|
||||
|
||||
int a = 1;
|
||||
for (int i = 0; i < 1111; ++i) {
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
|
||||
a++;
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
|
||||
a++;
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
|
||||
a++;
|
||||
|
||||
int b;
|
||||
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
|
||||
EXPECT_EQ(b, a - 3);
|
||||
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
|
||||
EXPECT_EQ(b, a - 2);
|
||||
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
|
||||
EXPECT_EQ(b, a - 1);
|
||||
}
|
||||
|
||||
spsc_q_free(&rb);
|
||||
}
|
||||
|
||||
bool count(const void *data, void *user)
|
||||
{
|
||||
EXPECT_TRUE(data != NULL);
|
||||
int *c = (int *)user;
|
||||
(*c)++;
|
||||
return true; /* count all */
|
||||
}
|
||||
TEST(spsc_q, iterator) {
|
||||
spsc_q_t rb = spsc_q_alloc(10, sizeof(int));
|
||||
int n = 8;
|
||||
for (int i = 0; i < n; ++i)
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &i, sizeof(i)));
|
||||
|
||||
int c = 0;
|
||||
int results[n];
|
||||
int ret = spsc_q_get_while(&rb, count, &c, results, sizeof(*results), n);
|
||||
EXPECT_EQ(c, n);
|
||||
EXPECT_EQ(ret, n);
|
||||
for (int i = 0; i < n; ++i)
|
||||
EXPECT_EQ(results[i], i);
|
||||
|
||||
int b;
|
||||
EXPECT_FALSE(spsc_q_get(&rb, &b, sizeof(b))); /* empty! */
|
||||
|
||||
spsc_q_free(&rb);
|
||||
}
|
||||
|
||||
bool drop(const void *data, void *user)
|
||||
{
|
||||
const int *e = (const int *)data;
|
||||
int *c = (int *)user;
|
||||
EXPECT_EQ(*e, *c);
|
||||
(*c)++;
|
||||
return true; /* drop all */
|
||||
}
|
||||
TEST(spsc_q, drop) {
|
||||
spsc_q_t rb = spsc_q_alloc(10, sizeof(int));
|
||||
int n = 8;
|
||||
for (int i = 0; i < n; ++i)
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &i, sizeof(i)));
|
||||
|
||||
int c = 0;
|
||||
int ret = spsc_q_drop_while(&rb, drop, &c);
|
||||
EXPECT_EQ(c, n);
|
||||
EXPECT_EQ(ret, n);
|
||||
|
||||
int b;
|
||||
EXPECT_FALSE(spsc_q_get(&rb, &b, sizeof(b))); /* empty! */
|
||||
|
||||
spsc_q_free(&rb);
|
||||
}
|
||||
|
||||
|
||||
TEST(spsc_q, full) {
|
||||
int n = 3;
|
||||
spsc_q_t rb = spsc_q_alloc(n, sizeof(int));
|
||||
int a = 1;
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
|
||||
a++;
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
|
||||
a++;
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
|
||||
a++;
|
||||
EXPECT_FALSE(spsc_q_put(&rb, &a, sizeof(a))); /* full! */
|
||||
|
||||
int b;
|
||||
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
|
||||
EXPECT_EQ(b, a - n);
|
||||
|
||||
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
|
||||
a++;
|
||||
EXPECT_FALSE(spsc_q_put(&rb, &a, sizeof(a))); /* full! */
|
||||
|
||||
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
|
||||
EXPECT_EQ(b, a - n);
|
||||
|
||||
spsc_q_free(&rb);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
75
common/utils/ds/tests/test_spsc_q_perf.cpp
Normal file
75
common/utils/ds/tests/test_spsc_q_perf.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "stdio.h"
|
||||
#include "assert.h"
|
||||
#include "pthread.h"
|
||||
|
||||
extern "C" {
|
||||
#include "spsc_q.h"
|
||||
}
|
||||
|
||||
typedef struct q_args {
|
||||
spsc_q_t q;
|
||||
int range;
|
||||
} q_args_t;
|
||||
|
||||
static void *producer(void *arg)
|
||||
{
|
||||
q_args_t *a = (q_args_t *) arg;
|
||||
int i = 0;
|
||||
while (i <= a->range) {
|
||||
if (spsc_q_put(&a->q, &i, sizeof(i)))
|
||||
i++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *consumer(void *arg)
|
||||
{
|
||||
q_args_t *a = (q_args_t *) arg;
|
||||
int i = 0;
|
||||
while (i != a->range) {
|
||||
int r;
|
||||
if (spsc_q_get(&a->q, &r, sizeof(r))) {
|
||||
assert(r == i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void run_test(int range, int q_size)
|
||||
{
|
||||
q_args_t args = {
|
||||
.q = spsc_q_alloc(q_size, sizeof(int)),
|
||||
.range = range,
|
||||
};
|
||||
|
||||
pthread_t p, c;
|
||||
int ret;
|
||||
// start
|
||||
ret = pthread_create(&p, NULL, producer, &args);
|
||||
assert(ret == 0);
|
||||
ret = pthread_create(&c, NULL, consumer, &args);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = pthread_join(p, NULL);
|
||||
assert(ret == 0);
|
||||
ret = pthread_join(c, NULL);
|
||||
assert(ret == 0);
|
||||
// stop
|
||||
// data/time => throughput
|
||||
|
||||
spsc_q_free(&args.q);
|
||||
}
|
||||
|
||||
static void BM_spsc_q(benchmark::State &state)
|
||||
{
|
||||
int range = 500000;
|
||||
int q_size = state.range(0);
|
||||
for (auto _ : state)
|
||||
run_test(range, q_size);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_spsc_q)->RangeMultiplier(2)->Range(10, 160);
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
55
common/utils/fsn.c
Normal file
55
common/utils/fsn.c
Normal 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
common/utils/fsn.h
Normal file
36
common/utils/fsn.h
Normal 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_ */
|
||||
@@ -2,3 +2,10 @@
|
||||
|
||||
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_test(NAME test_tpool_vs_actors COMMAND ./test_tpool_vs_actors)
|
||||
add_dependencies(tests test_tpool_vs_actors)
|
||||
|
||||
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
common/utils/tests/test_fsn.cpp
Normal file
84
common/utils/tests/test_fsn.cpp
Normal 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();
|
||||
}
|
||||
Reference in New Issue
Block a user