Compare commits

...

6 Commits

Author SHA1 Message Date
Merkebu Girmay
bd83253ab2 Add external taps client support to rfsim
- Add OAI_RFSIM_TAPS_CLIENT CMake option for external channel emulation
- Integrate taps_client library (reuses VRTSim's taps_client.cpp)
- Add --rfsimulator.taps-socket CLI parameter
- Support FlatBuffers-based tap reception over TCP/nanomsg
- Enable external 3GPP TDL channel models from raytracing-channel-emulator

Changes:
- radio/rfsimulator/CMakeLists.txt: Add taps client build support
- radio/rfsimulator/simulator.c: Integrate taps_client functions
2025-11-25 23:56:38 -08:00
Merkebu Girmay
69a9007da7 FlatBuffers package name mismatch resolved 2025-11-18 10:48:53 -08:00
Merkebu Girmay
010d4f0c77 minimize CIR DB ifdef usage
- removed ifdef of the CIR DB reader, it is built always

- removed the unused CIR_DB_PATH
2025-11-14 20:37:52 -08:00
Merkebu Girmay
f0bc998057 vrtsim: integrate CIR DB taps with YAML-driven selection
Summary:
- Read precomputed channel taps from a CIR DB binary with metadata in a YAML sidecar
- Enable at runtime with --vrtsim.cirdb 1, optional cirdb_yaml and cirdb_file or a base cirdb_path
- Selection knobs supported: cirdb_model_id, cirdb_ds_ns, cirdb_speed_mps
- Falls back to the existing channel model when cirdb=0
- CIR DB is generated using the steps here:
  https://gitlab.eurecom.fr/oai/raytracing-channel-emulator/-/tree/develop/server/external_taps?ref_type=heads

Build:
  cmake ../.. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DOAI_VRTSIM_CIRDB=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
  cmake --build . --target vrtsim nr-softmodem nr-uesoftmodem ldpc params_libconfig -j"\$(nproc)"

Run (gNB):
  sudo ./nr-softmodem \
    -O ./../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf \
    --device.name vrtsim --vrtsim.role server --gNBs.[0].min_rxtxtime 3 \
    --vrtsim.cirdb 1 --vrtsim.cirdb_yaml <path>/cir_db.yaml --vrtsim.cirdb_file <path>/cir_db.bin

Run (UE):
  sudo ./nr-uesoftmodem -C 3619200000 -r 106 --numerology 1 --ssb 516 --band 78 \
    --device.name vrtsim --vrtsim.role client

Expected log:
  CIRDB: model=X DS=... shape=TxRx L=... S=... fs=... dt=... speed=...

Example config block:
  vrtsim : {
    #role = "server";
    #client-num-rx-antennas = 1;
    #cirdb = 1;
    #cirdb_yaml = "/path/to/vrtsim.yaml";
    #cirdb_file = "/path/to/cir_db.bin";
    cirdb_model_id = 0;    # 0..4 for 3GPP TDL-A..E
    cirdb_ds_ns = 10.0;    # RMS delay spread in ns
    cirdb_speed_mps = 1.5; # UE speed in m/s
  }
2025-11-11 01:53:47 -08:00
Merkebu Girmay
ba24fb0913 vrtsim: Use cirdb and cirdb-path runtime opts. Use calloc_or_fail Update CMake with OAI_VRTSIM_CIRDB. 2025-10-31 19:24:53 -07:00
Merkebu Girmay
5d8282fb7a vrtsim.c: comments reverted, env variables removed 2025-10-30 10:02:41 -07:00
9 changed files with 863 additions and 13 deletions

View File

@@ -5,6 +5,48 @@ add_library(rfsimulator MODULE
)
target_link_libraries(rfsimulator PRIVATE SIMU log_headers)
set_target_properties(rfsimulator PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
add_executable(replay_node stored_node.c)
target_link_libraries (replay_node minimal_lib)
# External taps client for channel emulation via socket interface
add_boolean_option(OAI_RFSIM_TAPS_CLIENT OFF "Enable external taps client for RFSim" ON)
if (OAI_RFSIM_TAPS_CLIENT)
target_compile_definitions(rfsimulator PRIVATE ENABLE_TAPS_CLIENT=1)
# Download FlatBuffers schema for taps interface
file(DOWNLOAD
"https://gitlab.eurecom.fr/oai/raytracing-channel-emulator/-/raw/59589ad2a534881810b2aa7dd81e63066245bb8d/server/api/taps.fbs?inline=false"
${CMAKE_SOURCE_DIR}/taps.fbs
)
set(TAPS_IF_PATH ${CMAKE_SOURCE_DIR}/taps.fbs)
set(TAPS_API_HEADER taps_generated.h)
# Generate FlatBuffers C++ header from schema
add_custom_command(OUTPUT ${TAPS_API_HEADER}
COMMAND flatc -c ${TAPS_IF_PATH}
DEPENDS ${TAPS_IF_PATH}
COMMENT "Generating flatbuffers API from ${TAPS_IF_PATH}"
)
add_library(taps_api INTERFACE)
target_include_directories(taps_api INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
# Build taps client library
add_library(rfsim_taps_client ../vrtsim/taps_client.cpp ${TAPS_API_HEADER})
target_include_directories(rfsim_taps_client PUBLIC ../vrtsim)
# Handle different FlatBuffers package naming conventions across distributions
find_package(FlatBuffers QUIET)
if(NOT FlatBuffers_FOUND)
find_package(Flatbuffers REQUIRED)
set(FLATBUFFERS_TARGET flatbuffers)
else()
set(FLATBUFFERS_TARGET flatbuffers::flatbuffers)
endif()
target_link_libraries(rfsim_taps_client PUBLIC ${FLATBUFFERS_TARGET} taps_api SIMU nanomsg)
target_link_libraries(rfsimulator PRIVATE rfsim_taps_client)
message(STATUS "RFSim: External taps client support enabled")
endif()

View File

@@ -50,6 +50,10 @@
#include <openair1/SIMULATION/TOOLS/sim.h>
#include "rfsimulator.h"
#ifdef ENABLE_TAPS_CLIENT
#include "../vrtsim/taps_client.h"
#endif
#define PORT 4043 // default TCP port for this simulator
//
// CirSize defines the number of samples inquired for a read cycle
@@ -138,6 +142,84 @@ typedef struct buffer_s {
channel_desc_t *channel_model;
} buffer_t;
typedef struct {
int listen_sock, epollfd;
pthread_mutex_t Sockmutex;
unsigned int nb_cnx;
openair0_timestamp nextRxTstamp;
openair0_timestamp lastWroteTS;
simuRole role;
char *ip;
uint16_t port;
int saveIQfile;
buffer_t buf[MAX_FD_RFSIMU];
int next_buf;
int rx_num_channels;
int tx_num_channels;
double sample_rate;
double rx_freq;
double tx_bw;
int channelmod;
double chan_pathloss;
double chan_forgetfact;
uint64_t chan_offset;
void *telnetcmd_qid;
poll_telnetcmdq_func_t poll_telnetcmdq;
int wait_timeout;
double prop_delay_ms;
char *taps_socket;
channel_desc_t *ext_channel_desc;
} rfsimulator_state_t;
#define RFSIMULATOR_PARAMS_DESC { \
{"serveraddr", "<ip address to connect to>\n", simOpt, .strptr=&rfsimulator->ip, .defstrval="127.0.0.1", TYPE_STRING, 0 },\
{"serverport", "<port to connect to>\n", simOpt, .u16ptr = &(rfsimulator->port), .defuintval = PORT, TYPE_UINT16, 0}, \
{RFSIMU_OPTIONS_PARAMNAME, RFSIM_CONFIG_HELP_OPTIONS, 0, .strlistptr = NULL, .defstrlistval = NULL, TYPE_STRINGLIST, 0}, \
{"IQfile", "<file path to use when saving IQs>\n",simOpt, .strptr=&saveF, .defstrval="/tmp/rfsimulator.iqs",TYPE_STRING, 0 },\
{"modelname", "<channel model name>\n", simOpt, .strptr = &modelname, .defstrval = "AWGN", TYPE_STRING, 0}, \
{"ploss", "<channel path loss in dB>\n", simOpt, .dblptr = &(rfsimulator->chan_pathloss), .defdblval = 0, TYPE_DOUBLE, 0}, \
{"forgetfact", "<channel forget factor ((0 to 1)>\n", simOpt, .dblptr=&(rfsimulator->chan_forgetfact),.defdblval=0, TYPE_DOUBLE, 0 },\
{"offset", "<channel offset in samps>\n", simOpt, .u64ptr = &(rfsimulator->chan_offset), .defint64val = 0, TYPE_UINT64, 0}, \
{"prop_delay", "<propagation delay in ms>\n", simOpt, .dblptr=&(rfsimulator->prop_delay_ms), .defdblval=0.0, TYPE_DOUBLE, 0 },\
{"wait_timeout", "<wait timeout if no UE connected>\n", simOpt, .iptr=&(rfsimulator->wait_timeout), .defintval=1, TYPE_INT, 0 },\
{"taps-socket", "<TCP address for external taps emitter>\n", simOpt, .strptr=&(rfsimulator->taps_socket), .defstrval=NULL, TYPE_STRING, 0 },\
};
// clang-format on
static void getset_currentchannels_type(char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt);
extern int get_currentchannels_type(char *buf, int debug, webdatadef_t *tdata, telnet_printfunc_t prnt); // in random_channel.c
static int rfsimu_setchanmod_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg);
static int rfsimu_setdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg);
static int rfsimu_getdistance_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg);
static int rfsimu_vtime_cmd(char *buff, int debug, telnet_printfunc_t prnt, void *arg);
// clang-format off
static telnetshell_cmddef_t rfsimu_cmdarray[] = {
{"show models", "", (cmdfunc_t)rfsimu_setchanmod_cmd, {(webfunc_t)getset_currentchannels_type}, TELNETSRV_CMDFLAG_WEBSRVONLY | TELNETSRV_CMDFLAG_GETWEBTBLDATA, NULL},
{"setmodel", "<model name> <model type>", (cmdfunc_t)rfsimu_setchanmod_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ | TELNETSRV_CMDFLAG_TELNETONLY, NULL},
{"setdistance", "<model name> <distance>", (cmdfunc_t)rfsimu_setdistance_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ | TELNETSRV_CMDFLAG_NEEDPARAM },
{"getdistance", "<model name>", (cmdfunc_t)rfsimu_getdistance_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ},
{"vtime", "", (cmdfunc_t)rfsimu_vtime_cmd, {NULL}, TELNETSRV_CMDFLAG_PUSHINTPOOLQ | TELNETSRV_CMDFLAG_AUTOUPDATE},
{"", "", NULL},
};
// clang-format on
static telnetshell_cmddef_t *setmodel_cmddef = &(rfsimu_cmdarray[1]);
static telnetshell_vardef_t rfsimu_vardef[] = {{"", 0, 0, NULL}};
static uint64_t CirSize = minCirSize;
typedef c16_t sample_t; // 2*16 bits complex number
typedef struct buffer_s {
int conn_sock;
openair0_timestamp lastReceivedTS;
bool headerMode;
bool trashingPacket;
samplesBlockHeader_t th;
char *transferPtr;
uint64_t remainToTransfer;
char *circularBufEnd;
sample_t *circularBuf;
channel_desc_t *channel_model;
} buffer_t;
typedef struct {
int listen_sock, epollfd;
pthread_mutex_t Sockmutex;
@@ -989,10 +1071,18 @@ static int rfsimulator_read(openair0_device *device, openair0_timestamp *ptimest
memset(temp_array, 0, sizeof(temp_array));
}
num_chanmod_channels++;
channel_desc_t *active_channel = ptr->channel_model;
#ifdef ENABLE_TAPS_CLIENT
if (t->ext_channel_desc != NULL) {
active_channel = t->ext_channel_desc;
}
#endif
rxAddInput(ptr->circularBuf,
temp_array[a_rx],
a_rx,
ptr->channel_model,
active_channel,
nsamps,
t->nextRxTstamp,
CirSize,
@@ -1084,6 +1174,15 @@ static int rfsimulator_reset_stats(openair0_device *device) {
}
static void rfsimulator_end(openair0_device *device) {
rfsimulator_state_t *s = device->priv;
#ifdef ENABLE_TAPS_CLIENT
if (s->ext_channel_desc != NULL) {
LOG_I(HW, "RFSim: Stopping external taps client\n");
taps_client_stop();
s->ext_channel_desc = NULL;
}
#endif
for (int i = 0; i < MAX_FD_RFSIMU; i++) {
buffer_t *b = &s->buf[i];
if (b->conn_sock >= 0)
@@ -1137,6 +1236,24 @@ int device_init(openair0_device *device, openair0_config_t *openair0_cfg) {
rfsimulator->prop_delay_ms = rfsimulator->chan_offset * 1000 / rfsimulator->sample_rate;
LOG_I(HW, "propagation delay %f ms, %lu samples\n", rfsimulator->prop_delay_ms, rfsimulator->chan_offset);
}
#ifdef ENABLE_TAPS_CLIENT
if (rfsimulator->taps_socket != NULL && rfsimulator->taps_socket[0] != '\0') {
LOG_I(HW, "RFSim: Connecting to external taps emitter at %s\n", rfsimulator->taps_socket);
taps_client_connect(0,
rfsimulator->taps_socket,
openair0_cfg->tx_num_channels,
openair0_cfg->rx_num_channels,
&rfsimulator->ext_channel_desc);
LOG_I(HW, "RFSim: External taps client initialized (TX=%d, RX=%d)\n",
openair0_cfg->tx_num_channels, openair0_cfg->rx_num_channels);
} else {
rfsimulator->ext_channel_desc = NULL;
}
#else
rfsimulator->ext_channel_desc = NULL;
#endif
mutexinit(rfsimulator->Sockmutex);
LOG_I(HW,
"Running as %s\n",

View File

@@ -1,13 +1,35 @@
# Noise device static lib
add_library(noise_device STATIC noise_device.c)
target_link_libraries(noise_device PRIVATE log_headers)
add_library(vrtsim MODULE vrtsim.c)
set_target_properties(vrtsim PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
target_link_libraries(vrtsim PRIVATE SIMU shm_td_iq_channel actor noise_device log_headers)
# Collect vrtsim sources - always include CIR DB sources
set(VRTSIM_SOURCES vrtsim.c cirdb_provider.c cirdb_yaml.cpp)
# vrtsim module
add_library(vrtsim MODULE ${VRTSIM_SOURCES})
set_target_properties(vrtsim PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
find_package(Threads REQUIRED)
find_package(yaml-cpp REQUIRED)
# Link libraries - always link yaml-cpp
target_link_libraries(vrtsim
PRIVATE
SIMU
shm_td_iq_channel
actor
noise_device
log_headers
Threads::Threads
yaml-cpp
)
# Optional taps client
add_boolean_option(OAI_VRTSIM_TAPS_CLIENT OFF "Enable taps client" ON)
if (OAI_VRTSIM_TAPS_CLIENT)
target_compile_definitions(vrtsim PRIVATE ENABLE_TAPS_CLIENT=1)
target_link_libraries(vrtsim PRIVATE taps_client)
file(DOWNLOAD
"https://gitlab.eurecom.fr/oai/raytracing-channel-emulator/-/raw/59589ad2a534881810b2aa7dd81e63066245bb8d/server/api/taps.fbs?inline=false"
${CMAKE_SOURCE_DIR}/taps.fbs
@@ -21,11 +43,20 @@ if (OAI_VRTSIM_TAPS_CLIENT)
DEPENDS ${TAPS_IF_PATH}
COMMENT "Generating flatbuffers API from ${TAPS_IF_PATH}"
)
add_library(taps_api INTERFACE)
target_include_directories(taps_api INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
add_library(taps_client taps_client.cpp ${TAPS_API_HEADER})
target_include_directories(taps_client PUBLIC .)
find_package(Flatbuffers REQUIRED)
#find_package(Flatbuffers REQUIRED)
find_package(FlatBuffers QUIET)
if(NOT FlatBuffers_FOUND)
# Fall back to lowercase (Ubuntu 22.04)
find_package(Flatbuffers REQUIRED)
set(FLATBUFFERS_TARGET flatbuffers)
else()
set(FLATBUFFERS_TARGET flatbuffers::flatbuffers)
endif()
target_link_libraries(taps_client PUBLIC flatbuffers taps_api SIMU nanomsg)
endif()

View File

@@ -0,0 +1,354 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <limits.h>
#include <unistd.h>
#include "common/utils/LOG/log.h"
#include "common/utils/assertions.h"
#include "SIMULATION/TOOLS/sim.h"
#include "cirdb_provider.h"
#include "cirdb_yaml.h"
#define NUM_TAPS_BUFFERS 4
#define MAX_L_PUBLISH 8
typedef struct {
void *taps_blob; /* contiguous complexf for all links */
channel_desc_t *ch; /* ch_ps points into taps_blob */
} cirdb_buffer_t;
typedef struct {
/* I/O */
FILE *fh;
/* Selected entry meta */
int model_id;
int n_tx;
int n_rx;
int L_full;
int S;
double fs_hz;
double snapshot_dt_s;
float ds_ns;
float speed_mps;
uint64_t sel_offset;
uint64_t sel_nbytes;
/* Publication control */
uint32_t L_out;
uint32_t snap_idx;
/* Shape in publication */
int num_tx;
int num_rx;
/* Double buffering for readers */
cirdb_buffer_t bufs[NUM_TAPS_BUFFERS];
int cur;
/* Temporary read buffer for one snapshot of full L_full taps */
void *snapshot_tmp;
size_t snapshot_tmp_bytes;
/* Published pointer location owned by caller */
channel_desc_t **channel_desc_out;
/* Path resolution helper */
char path_override[PATH_MAX];
/* Bookkeeping of last step computed from elapsed time */
int64_t last_step_applied; /* -1 until first update */
} cirdb_g;
static cirdb_g G;
/* returns 1 if file exists and is readable */
static int file_readable(const char *p) { return p && p[0] && access(p, R_OK) == 0; }
static int get_exe_dir(char out[PATH_MAX]) {
char exe[PATH_MAX] = {0};
ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
if (n <= 0) return 0;
exe[n] = 0;
char tmp[PATH_MAX];
if (snprintf(tmp, sizeof(tmp), "%s", exe) >= (int)sizeof(tmp)) return 0;
char *dir = dirname(tmp);
if (!dir || !dir[0]) return 0;
if (snprintf(out, PATH_MAX, "%s", dir) >= (int)PATH_MAX) return 0;
return 1;
}
static int path_join(char out[PATH_MAX], const char *dir, const char *leaf) {
if (!dir || !leaf) return 0;
size_t ld = strnlen(dir, PATH_MAX);
size_t ll = strnlen(leaf, PATH_MAX);
if (ld + 1 + ll + 1 > PATH_MAX) return 0;
int n = snprintf(out, PATH_MAX, "%s/%s", dir, leaf);
return n > 0 && n < (int)PATH_MAX;
}
/* Optional public override helper kept for backward compatibility */
void cirdb_set_path_override(const char *path) {
if (!path) { G.path_override[0] = '\0'; return; }
size_t n = strnlen(path, PATH_MAX - 1);
memcpy(G.path_override, path, n);
G.path_override[n] = '\0';
}
/* Resolve cir_db.bin path */
static const char *resolve_db_path(char out[PATH_MAX]) {
if (file_readable(G.path_override)) {
snprintf(out, PATH_MAX, "%s", G.path_override);
return out;
}
char exe_dir[PATH_MAX];
if (get_exe_dir(exe_dir)) {
if (path_join(out, exe_dir, "cir_db.bin") && file_readable(out)) return out;
if (path_join(out, exe_dir, "radio/vrtsim/cir_db.bin") && file_readable(out)) return out;
}
snprintf(out, PATH_MAX, "%s", "cir_db.bin");
if (file_readable(out)) return out;
snprintf(out, PATH_MAX, "%s", "radio/vrtsim/cir_db.bin");
if (file_readable(out)) return out;
snprintf(out, PATH_MAX, "%s", "cir_db.bin");
return out;
}
static inline void fread_exact(void *dst, size_t sz, FILE *fh) {
size_t r = fread(dst, 1, sz, fh);
if (r != sz) {
LOG_E(HW, "CIRDB short read: expected %zu got %zu errno=%d\n", sz, r, errno);
abort();
}
}
static inline size_t cf_bytes(size_t n) { return n * sizeof(struct complexf); }
/* Point channel_desc ch_ps into a compacted taps buffer */
static void point_channel_desc(channel_desc_t *ch,
struct complexf *base,
int num_tx, int num_rx, int L_out) {
for (int aarx = 0; aarx < num_rx; aarx++) {
for (int aatx = 0; aatx < num_tx; aatx++) {
int link = aarx + num_rx * aatx;
ch->ch_ps[link] = &base[link * L_out];
}
}
ch->channel_length = L_out;
ch->path_loss_dB = 0;
ch->nb_tx = num_tx;
ch->nb_rx = num_rx;
}
/* Copy first L_out taps per link from a full L_full snapshot */
static void compact_L_out(struct complexf *dst,
const struct complexf *src_full,
int num_tx, int num_rx,
int L_full, int L_out) {
for (int aatx = 0; aatx < num_tx; aatx++) {
for (int aarx = 0; aarx < num_rx; aarx++) {
int link = aarx + num_rx * aatx;
const struct complexf *src = &src_full[(size_t)link * (size_t)L_full];
struct complexf *dst_link = &dst[(size_t)link * (size_t)L_out];
memcpy(dst_link, src, cf_bytes(L_out));
}
}
}
/* Load snapshot index s into the next publication buffer and flip */
static void load_snapshot_and_publish(uint32_t s)
{
uint64_t stride = (uint64_t)G.n_tx * (uint64_t)G.n_rx *
(uint64_t)G.L_full * sizeof(struct complexf);
uint64_t off = G.sel_offset + (uint64_t)s * stride;
if (fseeko(G.fh, (off_t)off, SEEK_SET) != 0) {
LOG_E(HW, "CIRDB fseoko failed errno=%d\n", errno);
abort();
}
fread_exact(G.snapshot_tmp, (size_t)stride, G.fh);
int next = (G.cur + 1) % NUM_TAPS_BUFFERS;
cirdb_buffer_t *buf = &G.bufs[next];
compact_L_out((struct complexf *)buf->taps_blob,
(const struct complexf *)G.snapshot_tmp,
G.n_tx, G.n_rx, G.L_full, (int)G.L_out);
point_channel_desc(buf->ch,
(struct complexf *)buf->taps_blob,
G.n_tx, G.n_rx, (int)G.L_out);
*G.channel_desc_out = buf->ch;
G.cur = next;
}
void cirdb_connect(int id,
int num_tx_antennas,
int num_rx_antennas,
const cirdb_select_opts_t *sel,
channel_desc_t **channel_desc_out)
{
(void)id;
memset(&G, 0, sizeof(G));
G.channel_desc_out = channel_desc_out;
G.num_tx = num_tx_antennas;
G.num_rx = num_rx_antennas;
G.cur = 0;
G.last_step_applied = -1;
/* Resolve binary path with priority: sel->bin_path, optional override, exe-dir fallbacks, CWD fallbacks */
char binpath[PATH_MAX];
const char *use_bin = NULL;
if (sel && sel->bin_path && sel->bin_path[0]) {
use_bin = sel->bin_path;
} else {
use_bin = resolve_db_path(binpath);
}
G.fh = fopen(use_bin, "rb");
AssertFatal(G.fh != NULL, "open %s failed, errno=%d", use_bin, errno);
/* Resolve YAML sidecar path with priority: sel->yaml_path, then derive from bin */
char sidecar[PATH_MAX];
if (sel && sel->yaml_path && sel->yaml_path[0]) {
snprintf(sidecar, PATH_MAX, "%s", sel->yaml_path);
} else {
size_t n = strnlen(use_bin, PATH_MAX - 6);
snprintf(sidecar, PATH_MAX, "%s", use_bin);
if (n >= 4 && strcmp(&use_bin[n-4], ".bin") == 0) {
sidecar[n] = '\0';
snprintf(sidecar + n, PATH_MAX - n, "%s", ".yaml");
} else {
snprintf(sidecar, PATH_MAX, "%s.yaml", use_bin);
}
}
/* Selection request */
int want_model_id = (sel && sel->want_model_id > 0) ? sel->want_model_id : -1;
float want_ds = (sel && sel->want_ds_ns > 0) ? sel->want_ds_ns : -1.0f;
float want_speed = (sel && sel->want_speed_mps > 0) ? sel->want_speed_mps : -1.0f;
cirdb_entry_meta_t m = (cirdb_entry_meta_t){0};
cirdb_select_req_t req = {
.want_model_id = want_model_id,
.want_tx = G.num_tx,
.want_rx = G.num_rx,
.want_ds_ns = want_ds,
.want_speed_mps = want_speed,
.allow_shape_swap = 1,
.w_ds = 1.0f,
.w_speed = 0.2f,
.yaml_path = sidecar
};
int ok = cirdb_yaml_select(&req, &m);
if (ok <= 0) {
cirdb_select_req_t req_relaxed = req;
req_relaxed.want_tx = 0;
req_relaxed.want_rx = 0;
ok = cirdb_yaml_select(&req_relaxed, &m);
}
AssertFatal(ok > 0, "No suitable CIR entry found in YAML %s", sidecar);
G.model_id = m.model_id;
G.n_tx = m.n_tx;
G.n_rx = m.n_rx;
G.L_full = m.L;
G.S = m.S;
G.fs_hz = m.fs_hz;
G.snapshot_dt_s = m.snapshot_dt_s;
G.ds_ns = m.ds_ns;
G.speed_mps = m.speed_mps;
G.sel_offset = m.offset_bytes;
G.sel_nbytes = m.nbytes;
G.L_out = (m.L <= MAX_L_PUBLISH ? (uint32_t)m.L : MAX_L_PUBLISH);
G.snap_idx = 0;
G.snapshot_tmp_bytes = (size_t)G.n_tx * (size_t)G.n_rx *
(size_t)G.L_full * sizeof(struct complexf);
G.snapshot_tmp = calloc(1, G.snapshot_tmp_bytes);
AssertFatal(G.snapshot_tmp != NULL, "Alloc snapshot_tmp failed");
for (int i = 0; i < NUM_TAPS_BUFFERS; i++) {
size_t blob_cf = (size_t)G.n_tx * (size_t)G.n_rx * (size_t)G.L_out;
G.bufs[i].taps_blob = calloc(1, blob_cf * sizeof(struct complexf));
AssertFatal(G.bufs[i].taps_blob != NULL, "Alloc taps_blob failed");
G.bufs[i].ch = (channel_desc_t *)calloc(1, sizeof(channel_desc_t));
G.bufs[i].ch->ch_ps = (struct complexf **)calloc(G.n_rx * G.n_tx, sizeof(struct complexf *));
G.bufs[i].ch->nb_tx = G.n_tx;
G.bufs[i].ch->nb_rx = G.n_rx;
}
if (G.S > 0) {
load_snapshot_and_publish(0);
}
LOG_I(HW, "CIRDB: model=%d DS=%.3fns shape=%ux%u L=%u/%u S=%u fs=%.0f dt=%.6fs speed=%.3fm/s",
G.model_id, G.ds_ns, G.n_tx, G.n_rx, G.L_out, G.L_full,
G.S, G.fs_hz, G.snapshot_dt_s, G.speed_mps);
}
void cirdb_update(uint64_t ns_since_start)
{
if (!G.channel_desc_out || !*G.channel_desc_out) return;
if (G.S <= 0) return;
double dt_s = (G.snapshot_dt_s > 0.0 ? G.snapshot_dt_s : 0.5);
if (dt_s <= 0.0) dt_s = 0.5;
double steps_f = (ns_since_start * 1e-9) / dt_s;
int64_t step = (int64_t)(steps_f >= 0.0 ? steps_f : 0.0);
if (step != G.last_step_applied) {
uint32_t s = (uint32_t)(step % G.S);
load_snapshot_and_publish(s);
G.snap_idx = s;
G.last_step_applied = step;
}
}
void cirdb_stop(void)
{
for (int i = 0; i < NUM_TAPS_BUFFERS; i++) {
free(G.bufs[i].taps_blob);
if (G.bufs[i].ch) {
free(G.bufs[i].ch->ch_ps);
free(G.bufs[i].ch);
}
}
free(G.snapshot_tmp);
if (G.fh) fclose(G.fh);
memset(&G, 0, sizeof(G));
G.last_step_applied = -1;
}

View File

@@ -0,0 +1,63 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
#ifndef OAI_VRTSIM_CIRDB_PROVIDER_H
#define OAI_VRTSIM_CIRDB_PROVIDER_H
#include <stdint.h>
#include "SIMULATION/TOOLS/sim.h"
/*
Selection and timing options:
want_model_id: TDL family index. Allowed: 0 (TDL-A), 1 (TDL-B), 2 (TDL-C), 3 (TDL-D), 4 (TDL-E).
Set < 0 to express no preference.
want_ds_ns: RMS delay spread in ns. Typical values: 30, 100, 300. Nonnegative float.
Set < 0 to express no preference.
want_speed_mps: UE speed in m/s. Typical: walking ≈ 1.5, urban 3..10, vehicular 10..30.
Set < 0 to express no preference.
*/
typedef struct {
const char *yaml_path; /* optional absolute path to vrtsim.yaml */
const char *bin_path; /* optional absolute path to cir_db.bin */
int want_model_id; /* 0..4, or <0 for no preference */
float want_ds_ns; /* nonnegative, or <0 for no preference */
float want_speed_mps; /* nonnegative, or <0 for no preference */
} cirdb_select_opts_t;
/* Initialize provider and publish snapshot 0 through channel_desc_out. */
void cirdb_connect(int id,
int num_tx_antennas,
int num_rx_antennas,
const cirdb_select_opts_t *sel,
channel_desc_t **channel_desc_out);
/* Advance snapshot selection based on elapsed nanoseconds since the start. */
void cirdb_update(uint64_t ns_since_start);
/* Tear down and free resources. */
void cirdb_stop(void);
/* Optional public override helper kept for backward compatibility */
void cirdb_set_path_override(const char *path);
#endif /* OAI_VRTSIM_CIRDB_PROVIDER_H */

103
radio/vrtsim/cirdb_yaml.cpp Normal file
View File

@@ -0,0 +1,103 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
#include "cirdb_yaml.h"
#include <yaml-cpp/yaml.h>
#include <cstdio>
#include <cmath>
#include <string>
static inline double dabs(double x){ return x < 0 ? -x : x; }
int cirdb_yaml_scan(const char *yaml_path) {
try {
YAML::Node root = YAML::LoadFile(yaml_path);
auto ents = root["entries"];
if (!ents || !ents.IsSequence()) return -1;
int n = static_cast<int>(ents.size());
std::fprintf(stderr, "[cirdb_yaml] %d entries in %s\n", n, yaml_path);
return n;
} catch (const YAML::Exception &e) {
std::fprintf(stderr, "[cirdb_yaml] parse error: %s\n", e.what());
return -2;
}
}
int cirdb_yaml_select(const cirdb_select_req_t *req, cirdb_entry_meta_t *out) {
if (!req || !out || !req->yaml_path) return -3;
try {
YAML::Node root = YAML::LoadFile(req->yaml_path);
auto ents = root["entries"];
if (!ents || !ents.IsSequence()) return -4;
bool found = false;
double best_cost = 1e300;
cirdb_entry_meta_t best{};
for (std::size_t i = 0; i < ents.size(); ++i) {
const YAML::Node e = ents[i];
int pair_order = e["pair_order"].as<int>(0);
int model_id = e["model_id"].as<int>(-1);
if (req->want_model_id >= 0 && model_id != req->want_model_id) continue;
int n_tx = e["n_tx"].as<int>(0);
int n_rx = e["n_rx"].as<int>(0);
bool shape_ok = (n_tx == req->want_tx && n_rx == req->want_rx);
bool shape_swap_ok = req->allow_shape_swap ? (n_tx == req->want_rx && n_rx == req->want_tx) : false;
if (!shape_ok && !shape_swap_ok) continue;
float ds_ns = e["ds_ns"].as<float>(0.0f);
float sp = e["speed_mps"].as<float>(0.0f);
double cds = dabs((double)ds_ns - (double)req->want_ds_ns);
double csp = dabs((double)sp - (double)req->want_speed_mps);
double cost = (req->w_ds > 0 ? req->w_ds : 1.0) * cds
+ (req->w_speed > 0 ? req->w_speed : 0.2) * csp;
if (!found || cost < best_cost) {
best_cost = cost;
found = true;
best.model_id = model_id;
best.n_tx = n_tx;
best.n_rx = n_rx;
best.L = e["L"].as<int>(0);
best.S = e["S"].as<int>(0);
best.fs_hz = e["fs_hz"].as<double>(0.0);
best.snapshot_dt_s = e["snapshot_dt_s"].as<double>(0.0);
best.ds_ns = ds_ns;
best.speed_mps= sp;
best.pair_order = pair_order;
best.offset_bytes = e["offset_bytes"].as<uint64_t>(0);
best.nbytes = e["nbytes"].as<uint64_t>(0);
}
}
if (!found) return 0;
*out = best;
return 1;
} catch (const YAML::Exception &e) {
std::fprintf(stderr, "[cirdb_yaml] parse error: %s\n", e.what());
return -2;
}
}

61
radio/vrtsim/cirdb_yaml.h Normal file
View File

@@ -0,0 +1,61 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
int want_model_id; // 0..4 for TDL-A..E, or -1 for any
int want_tx; // exact match
int want_rx; // exact match
float want_ds_ns; // nearest
float want_speed_mps; // nearest
int allow_shape_swap; // allow TX/RX swap when matching
float w_ds; // DS distance weight
float w_speed; // speed distance weight
const char *yaml_path; // sidecar path
} cirdb_select_req_t;
typedef struct {
int model_id;
int n_tx;
int n_rx;
int L; // taps per link in file
int S; // snapshots
double fs_hz;
double snapshot_dt_s;
float ds_ns;
float speed_mps;
int pair_order;
uint64_t offset_bytes;
uint64_t nbytes;
} cirdb_entry_meta_t;
int cirdb_yaml_select(const cirdb_select_req_t *req, cirdb_entry_meta_t *out);
int cirdb_yaml_scan(const char *yaml_path);
#ifdef __cplusplus
}
#endif

View File

@@ -49,6 +49,8 @@
#include "noise_device.h"
#include "simde/x86/avx512.h"
#include "taps_client.h"
#include "cirdb_provider.h"
#include "cirdb_yaml.h"
// Simulator role
typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
@@ -77,6 +79,15 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
{"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \
{"taps-socket", TAPS_SOCKET_HLP, 0, .strptr = &vrtsim_state->taps_socket, .defstrval = NULL, TYPE_STRING, 0}, \
{"client-num-rx-antennas", CLIENT_NUM_RX_HLP, 0, .iptr = &vrtsim_state->client_num_rx_antennas, .defintval = 1, TYPE_INT, 0}, \
/* CIR DB enable and paths */ \
{"cirdb", "Use CIR database for channel taps (1 yes, 0 no)", 0, .iptr = &vrtsim_state->use_cirdb, .defintval = 0, TYPE_INT, 0}, \
{"cirdb-path", "Directory that holds vrtsim.yaml and cir_db.bin", 0, .strptr = &vrtsim_state->cirdb_path, .defstrval = NULL, TYPE_STRING, 0}, \
{"cirdb_yaml", "Absolute path to CIR DB YAML file (optional, overrides cirdb-path)", 0, .strptr = &vrtsim_state->cirdb_yaml, .defstrval = NULL, TYPE_STRING, 0}, \
{"cirdb_file", "Absolute path to CIR DB binary file (optional, overrides cirdb-path)", 0, .strptr = &vrtsim_state->cirdb_file, .defstrval = NULL, TYPE_STRING, 0}, \
/* CIR DB selection knobs */ \
{"cirdb_model_id", "Preferred TDL model id 0..4", 0, .iptr = &vrtsim_state->cirdb_model_id, .defintval = 0, TYPE_INT, 0}, \
{"cirdb_ds_ns", "Desired RMS delay spread in ns", 0, .dblptr = &vrtsim_state->cirdb_ds_ns, .defdblval = 10.0, TYPE_DOUBLE, 0}, \
{"cirdb_speed_mps", "Desired speed in m/s", 0, .dblptr = &vrtsim_state->cirdb_speed_mps, .defdblval = 1.5, TYPE_DOUBLE, 0}, \
};
// clang-format on
@@ -123,6 +134,14 @@ typedef struct {
Actor_t *channel_modelling_actors;
char *taps_socket;
int client_num_rx_antennas;
/* CIR DB state */
int use_cirdb;
char *cirdb_path;
char *cirdb_yaml;
char *cirdb_file;
int cirdb_model_id;
double cirdb_ds_ns;
double cirdb_speed_mps;
} vrtsim_state_t;
// Sample history for channel impulse response
@@ -201,6 +220,9 @@ static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state)
AssertFatal(false, "Invalid configuration: Build with ENABLE_TAPS_CLIENT to use taps socket\n");
}
#endif
if (vrtsim_state->use_cirdb) {
LOG_A(HW, "VRTSIM: CIR DB is enabled at runtime\n");
}
}
static void *vrtsim_timing_job(void *arg)
@@ -313,7 +335,7 @@ static int vrtsim_connect(openair0_device *device)
// Handle channel modelling after number of RX antennas are known
int num_tx_stats = 1;
if (vrtsim_state->chanmod || vrtsim_state->taps_socket) {
if (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb) {
vrtsim_state->channel_modelling_actors = calloc_or_fail(vrtsim_state->peer_info.num_rx_antennas, sizeof(Actor_t));
for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) {
init_actor(&vrtsim_state->channel_modelling_actors[i], "chanmod", -1);
@@ -324,7 +346,44 @@ static int vrtsim_connect(openair0_device *device)
device->openair0_cfg[0].tx_num_channels,
vrtsim_state->peer_info.num_rx_antennas,
&vrtsim_state->channel_desc);
} else {
}
else if (vrtsim_state->use_cirdb) {
const char *yaml_path = NULL;
const char *bin_path = NULL;
if (vrtsim_state->cirdb_yaml && vrtsim_state->cirdb_yaml[0]) yaml_path = vrtsim_state->cirdb_yaml;
if (vrtsim_state->cirdb_file && vrtsim_state->cirdb_file[0]) bin_path = vrtsim_state->cirdb_file;
char yaml_buf[PATH_MAX];
char bin_buf[PATH_MAX];
if (!yaml_path || !bin_path) {
const char *base = vrtsim_state->cirdb_path;
if (base && base[0]) {
if (!yaml_path) { snprintf(yaml_buf, sizeof(yaml_buf), "%s/%s", base, "vrtsim.yaml"); yaml_path = yaml_buf; }
if (!bin_path) { snprintf(bin_buf, sizeof(bin_buf), "%s/%s", base, "cir_db.bin"); bin_path = bin_buf; }
}
}
cirdb_select_opts_t sel = (cirdb_select_opts_t){0};
sel.yaml_path = yaml_path;
sel.bin_path = bin_path;
sel.want_model_id = vrtsim_state->cirdb_model_id > 0 ? vrtsim_state->cirdb_model_id : -1;
sel.want_ds_ns = (float)(vrtsim_state->cirdb_ds_ns > 0.0 ? vrtsim_state->cirdb_ds_ns : -1.0);
sel.want_speed_mps = (float)(vrtsim_state->cirdb_speed_mps > 0.0 ? vrtsim_state->cirdb_speed_mps : -1.0);
LOG_A(HW, "VRTSIM: CIR DB select yaml='%s' bin='%s'\n",
sel.yaml_path ? sel.yaml_path : "(auto)",
sel.bin_path ? sel.bin_path : "(auto)");
cirdb_connect(0,
device->openair0_cfg[0].tx_num_channels,
vrtsim_state->peer_info.num_rx_antennas,
&sel,
&vrtsim_state->channel_desc);
LOG_A(HW, "VRTSIM: channel taps via CIR DB\n");
}
else {
load_channel_model(vrtsim_state);
}
num_tx_stats = vrtsim_state->peer_info.num_rx_antennas;
@@ -397,9 +456,15 @@ static void perform_channel_modelling(void *arg)
return;
}
if (vrtsim_state->use_cirdb) {
double seconds = (double)channel_modelling_args->timestamp / vrtsim_state->sample_rate;
uint64_t elapsed_ns = (uint64_t)(seconds * 1e9 + 0.5);
cirdb_update(elapsed_ns);
}
cf_t channel_impulse_response[nb_tx_ant][channel_desc->channel_length];
cf_t *channel_impulse_response_p[nb_tx_ant];
if (!vrtsim_state->taps_socket) {
if (!vrtsim_state->taps_socket && !vrtsim_state->use_cirdb) {
const float pathloss_linear = powf(10, channel_desc->path_loss_dB / 20.0);
// Convert channel impulse response to float + apply pathloss
for (int aatx = 0; aatx < nb_tx_ant; aatx++) {
@@ -514,7 +579,7 @@ static int vrtsim_write(openair0_device *device, openair0_timestamp timestamp, v
AssertFatal(timestamp >= 0, "Timestamp must be non-negative, got %ld\n", timestamp);
timestamp -= device->openair0_cfg->command_line_sample_advance;
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
bool channel_modelling = vrtsim_state->chanmod || vrtsim_state->taps_socket;
bool channel_modelling = vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb;
return channel_modelling ? vrtsim_write_with_chanmod(vrtsim_state, timestamp, samplesVoid, nsamps, nbAnt, flags)
: vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0);
}
@@ -568,7 +633,7 @@ static void vrtsim_end(openair0_device *device)
}
tx_timing_t *tx_timing = vrtsim_state->tx_timing;
if (vrtsim_state->chanmod || vrtsim_state->taps_socket) {
if (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb) {
for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) {
shutdown_actor(&vrtsim_state->channel_modelling_actors[i]);
}
@@ -582,7 +647,9 @@ static void vrtsim_end(openair0_device *device)
}
tx_timing->average_tx_budget /= vrtsim_state->peer_info.num_rx_antennas;
free_noise_device();
if (vrtsim_state->taps_socket) {
if (vrtsim_state->use_cirdb) {
cirdb_stop();
} else if (vrtsim_state->taps_socket) {
taps_client_stop();
}
}
@@ -655,7 +722,7 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device *devi
vrtsim_state->tx_num_channels = openair0_cfg->tx_num_channels;
vrtsim_state->rx_num_channels = openair0_cfg->rx_num_channels;
if (vrtsim_state->chanmod || vrtsim_state->taps_socket) {
if (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb) {
init_channelmod();
int noise_power_dBFS = get_noise_power_dBFS();
int16_t noise_power = noise_power_dBFS == INVALID_DBFS_VALUE ? 0 : (int16_t)(32767.0 / powf(10.0, .05 * -noise_power_dBFS));

View File

@@ -248,3 +248,15 @@ e2_agent = {
#sm_dir = "/path/where/the/SMs/are/located/"
sm_dir = "/usr/local/lib/flexric/"
};
vrtsim :
{
#role = "server"; # "server" or "client"
#client-num-rx-antennas = 1; # publish expected client RX count
#cirdb = 1; # Enable CIR DB taps
# cirdb_yaml = "/path/to/vrtsim.yaml";
# cirdb_file = "/path/to/cir_db.bin";
cirdb_model_id = 0; # 0..4 for 3GPP TDL-A..E
cirdb_ds_ns = 10.0; # RMS delay spread in ns (10, 30)
cirdb_speed_mps = 1.5; # UE speed in m/s (1.5, 30)
};