mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
Compare commits
6 Commits
83ef99d17d
...
rfsim_exte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd83253ab2 | ||
|
|
69a9007da7 | ||
|
|
010d4f0c77 | ||
|
|
f0bc998057 | ||
|
|
ba24fb0913 | ||
|
|
5d8282fb7a |
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
354
radio/vrtsim/cirdb_provider.c
Normal file
354
radio/vrtsim/cirdb_provider.c
Normal 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;
|
||||
}
|
||||
63
radio/vrtsim/cirdb_provider.h
Normal file
63
radio/vrtsim/cirdb_provider.h
Normal 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
103
radio/vrtsim/cirdb_yaml.cpp
Normal 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
61
radio/vrtsim/cirdb_yaml.h
Normal 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
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
Reference in New Issue
Block a user