radio/vrtsim: Add CIRDB support for precomputed channel taps

Integrate Channel Impulse Response Database (CIRDB) to enable playback
of precomputed realistic channel taps in vrtsim, with runtime selection
via YAML metadata.

Features:
- Read precomputed channel taps from binary database with YAML sidecar
- Runtime enable via --vrtsim.cirdb with optional paths (cirdb_yaml, cirdb_file, cirdb_path)
- Channel parameter configuration: model_id (TDL-A..E), delay spread (ds_ns), UE speed (speed_mps)
- Fallback to existing channel model options when disabled
- Time-driven snapshot progression based on elapsed simulation time

Fixes:
- RNG initialization: Add randominit() call before gaussZiggurat()
- FlatBuffers linking: Handle both 'FlatBuffers' and 'flatbuffers' package names when working with external channel emmulator
- Address review comments: use pthread directly, update calloc_or_fail, simplify CMake
This commit is contained in:
Merkebu Girmay
2026-02-09 23:22:40 -08:00
parent 9777d23173
commit ccf5021072
7 changed files with 745 additions and 10 deletions
+27 -3
View File
@@ -1,13 +1,36 @@
# 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)
# vrtsim module
add_library(vrtsim MODULE
vrtsim.c
cirdb_provider.c
cirdb_yaml.cpp
)
set_target_properties(vrtsim PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
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
pthread
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,6 +44,7 @@ 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})
+348
View File
@@ -0,0 +1,348 @@
/*
* 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 "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
static inline double dabs(double x)
{
return x < 0.0 ? -x : x;
}
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;
/* Bookkeeping of last step computed from elapsed time */
int64_t last_step_applied; /* -1 until first update */
} cirdb_g;
static cirdb_g G;
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, size_t 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,
size_t L_full,
size_t L_out)
{
for (int aatx = 0; aatx < num_tx; aatx++) {
for (int aarx = 0; aarx < num_rx; aarx++) {
size_t link = aarx + num_rx * aatx;
const struct complexf *src = &src_full[link * L_full];
struct complexf *dst_link = &dst[link * 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 * G.n_rx * 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, G.L_out);
point_channel_desc(buf->ch, (struct complexf *)buf->taps_blob, G.n_tx, G.n_rx, 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;
/* Require explicit paths in cirdb_select_opts_t */
AssertFatal(sel != NULL, "cirdb_select_opts_t must not be NULL\n");
AssertFatal(sel->bin_path && sel->bin_path[0], "CIRDB requires explicit bin_path in cirdb_select_opts_t\n");
AssertFatal(sel->yaml_path && sel->yaml_path[0], "CIRDB requires explicit yaml_path in cirdb_select_opts_t\n");
const char *use_bin = sel->bin_path;
const char *sidecar = sel->yaml_path;
LOG_I(HW, "CIRDB: using database %s with metadata %s\n", use_bin, sidecar);
G.fh = fopen(use_bin, "rb");
AssertFatal(G.fh != NULL, "Cannot open CIRDB database file '%s': %s\n", use_bin, strerror(errno));
/* Selection request */
int want_model_id = sel->want_model_id;
AssertFatal(want_model_id >= 0 && want_model_id <= 4,
"Invalid model_id=%d (valid: 0..4)\n",
want_model_id);
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;
LOG_I(HW,
"CIRDB: Searching for entry with model_id=%d, antennas=%dx%d, DS=%.1fns, speed=%.1fm/s\n",
want_model_id,
G.num_tx,
G.num_rx,
want_ds,
want_speed);
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 = 0,
.w_ds = 1.0f,
.w_speed = 0.2f,
.yaml_path = sidecar};
int ok = cirdb_yaml_select(&req, &m);
// STRICT validation: fail if no match found
AssertFatal(ok > 0,
"No suitable CIR entry found in YAML %s\n"
" Requested: model_id=%d, antennas=%dx%d, DS=%.1fns, speed=%.1fm/s\n"
" Possible causes:\n"
" - No entry with model_id=%d in database\n"
" - No entry with antenna configuration %dx%d in database\n"
" Check the YAML file for available entries\n",
sidecar,
want_model_id,
G.num_tx,
G.num_rx,
want_ds,
want_speed,
want_model_id,
G.num_tx,
G.num_rx);
// STRICT validation: antenna dimensions must match exactly
AssertFatal(m.n_tx == G.num_tx && m.n_rx == G.num_rx,
"CIRDB antenna mismatch: requested %dx%d but selected entry has %dx%d\n"
" This should never happen - indicates a bug in selection logic\n",
G.num_tx,
G.num_rx,
m.n_tx,
m.n_rx);
// STRICT validation: model_id must match if specified
if (want_model_id >= 0) {
AssertFatal(m.model_id == want_model_id,
"CIRDB model_id mismatch: requested %d but selected entry has %d\n"
" This should never happen - indicates a bug in selection logic\n",
want_model_id,
m.model_id);
}
// FLEXIBLE: warn if delay spread doesn't match exactly (using closest available)
if (want_ds > 0 && dabs(m.ds_ns - want_ds) > 0.1) {
LOG_W(HW, "CIRDB: Requested delay spread %.1f ns not available in database, using closest match: %.1f ns\n", want_ds, m.ds_ns);
}
// FLEXIBLE: warn if speed doesn't match exactly (using closest available)
if (want_speed > 0 && dabs(m.speed_mps - want_speed) > 0.1) {
LOG_W(HW,
"CIRDB: Requested speed %.1f m/s not available in database, using closest match: %.1f m/s\n",
want_speed,
m.speed_mps);
}
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 * G.n_rx * G.L_full * sizeof(struct complexf);
G.snapshot_tmp = calloc_or_fail(1, G.snapshot_tmp_bytes);
for (int i = 0; i < NUM_TAPS_BUFFERS; i++) {
size_t blob_cf = (size_t)G.n_tx * G.n_rx * G.L_out;
G.bufs[i].taps_blob = calloc_or_fail(blob_cf, sizeof(struct complexf));
G.bufs[i].ch = calloc_or_fail(1, sizeof(channel_desc_t));
G.bufs[i].ch->ch_ps = calloc_or_fail(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: Selected entry - model=%d DS=%.3fns shape=%ux%u L=%u/%u S=%u fs=%.0f dt=%.6fs speed=%.3fm/s\n",
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;
}
+60
View File
@@ -0,0 +1,60 @@
/*
* 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);
#endif /* OAI_VRTSIM_CIRDB_PROVIDER_H */
+152
View File
@@ -0,0 +1,152 @@
/*
* 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 <cmath>
#include <string>
extern "C" {
#include "common/utils/LOG/log.h"
}
static inline double dabs(double x)
{
return x < 0 ? -x : x;
}
int cirdb_yaml_scan(const char *yaml_path)
{
YAML::Node root;
try {
root = YAML::LoadFile(yaml_path);
} catch (const YAML::Exception &e) {
LOG_E(HW, "CIRDB YAML parse error in %s: %s\n", yaml_path, e.what());
return -1;
}
auto ents = root["entries"];
if (!ents || !ents.IsSequence()) {
LOG_E(HW, "CIRDB YAML: missing or invalid 'entries' in %s\n", yaml_path);
return -1;
}
int n = static_cast<int>(ents.size());
LOG_I(HW, "CIRDB YAML: %d entries in %s\n", n, yaml_path);
return n;
}
int cirdb_yaml_select(const cirdb_select_req_t *req, cirdb_entry_meta_t *out)
{
if (!req || !out || !req->yaml_path) {
LOG_E(HW, "CIRDB YAML: invalid arguments to cirdb_yaml_select\n");
return -1;
}
YAML::Node root;
try {
root = YAML::LoadFile(req->yaml_path);
} catch (const YAML::Exception &e) {
LOG_E(HW, "CIRDB YAML parse error in %s: %s\n", req->yaml_path, e.what());
return -1;
}
auto ents = root["entries"];
if (!ents || !ents.IsSequence()) {
LOG_E(HW, "CIRDB YAML: missing or invalid 'entries' in %s\n", req->yaml_path);
return -1;
}
bool found = false;
double best_cost = 1e300;
cirdb_entry_meta_t best{};
// Determine if specific parameters are required
bool want_specific_model = (req->want_model_id >= 0);
bool want_specific_antennas = (req->want_tx > 0 && req->want_rx > 0);
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);
// STRICT: model_id must match if specified
if (want_specific_model && 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);
// STRICT: antenna dimensions must match exactly if specified
bool shape_ok = true;
if (want_specific_antennas) {
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;
shape_ok = shape_ok || shape_swap_ok;
if (!shape_ok)
continue; // Skip entries with wrong antenna configuration
}
// STRICT: antenna dimensions must match exactly if specified
if (want_specific_antennas) {
if (n_tx != req->want_tx || n_rx != req->want_rx)
continue; // Skip entries with wrong antenna configuration
}
float ds_ns = e["ds_ns"].as<float>(0.0f);
float sp = e["speed_mps"].as<float>(0.0f);
// FLEXIBLE: delay spread and speed - find closest match using cost function
double cds = 0.0;
double csp = 0.0;
if (req->want_ds_ns > 0)
cds = dabs((double)ds_ns - (double)req->want_ds_ns);
if (req->want_speed_mps > 0)
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;
}
+61
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
+85 -7
View File
@@ -51,6 +51,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;
@@ -79,6 +81,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
@@ -126,6 +137,14 @@ typedef struct {
char *taps_socket;
int client_num_rx_antennas;
struct timespec start_ts;
/* 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;
static void histogram_add(histogram_t *histogram, double diff)
@@ -166,7 +185,7 @@ static void load_channel_model(vrtsim_state_t *vrtsim_state)
char *model_name = vrtsim_state->role == ROLE_CLIENT ? "client_tx_channel_model" : "server_tx_channel_model";
vrtsim_state->channel_desc = find_channel_desc_fromname(model_name);
AssertFatal(vrtsim_state->channel_desc != NULL,
"Could not find model name %s. Make sure it is present in the config file",
"Could not find model name %s. Make sure it is present in the config file\n",
model_name);
LOG_I(HW,
"Channel model %s parameters: path_loss_dB=%.2f, nb_tx=%d, nb_rx=%d, channel_length=%d\n",
@@ -201,6 +220,9 @@ static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state)
AssertFatal(false, "Invalid configuration: Build with OAI_VRTSIM_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)
@@ -305,7 +327,7 @@ static int vrtsim_connect(openair0_device_t *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);
@@ -316,6 +338,53 @@ static int vrtsim_connect(openair0_device_t *device)
device->openair0_cfg[0].tx_num_channels,
vrtsim_state->peer_info.num_rx_antennas,
&vrtsim_state->channel_desc);
} 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;
AssertFatal(vrtsim_state->cirdb_model_id >= 0 && vrtsim_state->cirdb_model_id <= 4,
"Invalid cirdb_model_id=%d (valid: 0..4)\n",
vrtsim_state->cirdb_model_id);
sel.want_model_id = vrtsim_state->cirdb_model_id;
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);
}
@@ -392,9 +461,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++) {
@@ -543,7 +618,7 @@ static int vrtsim_write(openair0_device_t *device,
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);
}
@@ -609,7 +684,7 @@ static void vrtsim_end(openair0_device_t *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]);
}
@@ -623,7 +698,9 @@ static void vrtsim_end(openair0_device_t *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();
}
}
@@ -711,8 +788,9 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device_t *de
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();
randominit();
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));
LOG_A(HW, "VRTSIM: Noise power %d sample value\n", noise_power);