Merge remote-tracking branch 'origin/vrtsim_cirdb_read' into integration_2026_w07 (!3734)

vrtsim: add CIR DB playback with CLI flags

VRTSIM: CIR DB playback via CLI

This change adds an optional in-process CIR DB path to VRTSIM. When
enabled with --vrtsim.cirdb and optional --vrtsim.cirdb-path, VRTSIM
reads precomputed taps from cir_db.bin. Default behavior is unchanged
when the flag is not set.

Build

    cmake ../../ -GNinja -DOAI_VRTSIM_CIRDB=ON -DOAI_VRTSIM_TAPS_CLIENT=OFF
    cmake --build . --target vrtsim

How to 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-path /home/merkeb/oai-cir-emitter/cir_db.bin

UE

    sudo ./nr-uesoftmodem \
      -C 3619200000 -r 106 --numerology 1 --ssb 516 --band 78 \
      --device.name vrtsim

Expected logs

- VRTSIM prints that channel modelling is active and uses DB backed taps
- CIRDB ready: model=..., shape=Tx x Rx, L=..., S=..., fs=..., dt=...,
  speed=...

CLI behavior and fallbacks

- TAPS client and chanmod paths are unchanged
- If --vrtsim.cirdb is not set or equals 0, VRTSIM uses the existing
  channel model path
This commit is contained in:
Robert Schmidt
2026-02-11 10:20:28 +01:00
9 changed files with 1084 additions and 32 deletions
@@ -0,0 +1,130 @@
<!--
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
-->
<testCaseList>
<htmlTabRef>vrtsim-5gnr-cirdb</htmlTabRef>
<htmlTabName>Monolithic gNB vrtsim + CIRDB</htmlTabName>
<htmlTabIcon>wrench</htmlTabIcon>
<testCase>
<class>Pull_Local_Registry</class>
<desc>Pull Images from Local Registry</desc>
<node>localhost</node>
<images>oai-gnb oai-nr-ue</images>
<tag_prefix>arm_</tag_prefix>
</testCase>
<testCase>
<class>Create_Workspace</class>
<desc>Create new Workspace</desc>
<node>localhost</node>
</testCase>
<testCase>
<class>Deploy_Object</class>
<desc>Deploy OAI 5G CoreNetwork</desc>
<node>localhost</node>
<yaml_path>ci-scripts/yaml_files/5g_vrtsim_cirdb</yaml_path>
<services>mysql oai-amf oai-smf oai-upf oai-ext-dn</services>
</testCase>
<testCase>
<class>Deploy_Object</class>
<desc>Deploy OAI 5G gNB+UE RF sim SA</desc>
<node>localhost</node>
<yaml_path>ci-scripts/yaml_files/5g_vrtsim_cirdb</yaml_path>
<services>oai-gnb oai-nr-ue</services>
</testCase>
<testCase>
<class>Attach_UE</class>
<desc>Attach OAI UE (Wait for IP)</desc>
<id>rfsim5g_ue</id>
<node>localhost</node>
</testCase>
<testCase>
<class>Ping</class>
<desc>Ping ext-dn from NR-UE</desc>
<id>rfsim5g_ue</id>
<node>localhost</node>
<svr_id>rfsim5g_ext_dn</svr_id>
<svr_node>localhost</svr_node>
<ping_args>-c 20 -i 0.25</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
</testCase>
<testCase>
<class>Ping</class>
<desc>Ping NR-UE from ext-dn</desc>
<id>rfsim5g_ext_dn</id>
<node>localhost</node>
<svr_id>rfsim5g_ue</svr_id>
<svr_node>localhost</svr_node>
<ping_args>-c 20 -i 0.25</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
</testCase>
<testCase>
<class>Iperf</class>
<desc>Iperf (DL/3Mbps/UDP)(20 sec)</desc>
<iperf_args>-u -b 3M -t 20 -R</iperf_args>
<id>rfsim5g_ue</id>
<node>localhost</node>
<svr_id>rfsim5g_ext_dn</svr_id>
<svr_node>localhost</svr_node>
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
</testCase>
<testCase>
<class>Iperf</class>
<desc>Iperf (UL/1Mbps/UDP)(20 sec)</desc>
<iperf_args>-u -b 1M -t 20</iperf_args>
<id>rfsim5g_ue</id>
<node>localhost</node>
<svr_id>rfsim5g_ext_dn</svr_id>
<svr_node>localhost</svr_node>
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
</testCase>
<testCase>
<class>Undeploy_Object</class>
<always_exec>true</always_exec>
<desc>Undeploy all OAI 5G stack</desc>
<node>localhost</node>
<yaml_path>ci-scripts/yaml_files/5g_vrtsim_cirdb</yaml_path>
<d_retx_th>10,0,0,0</d_retx_th>
<u_retx_th>1,0,0,0</u_retx_th>
</testCase>
<testCase>
<class>Clean_Test_Server_Images</class>
<always_exec>true</always_exec>
<desc>Clean Test Images on Test Server</desc>
<node>localhost</node>
<images>oai-gnb oai-nr-ue</images>
</testCase>
</testCaseList>
@@ -0,0 +1,183 @@
services:
# CIR Generator service - generates CIRDB files on every CI run
cir-generator:
image: oaisoftwarealliance/cir-generator:test
environment:
GENERATOR_ARGS: "--demo"
OUTPUT: "--out /opt/output/cir_db.bin"
volumes:
- cirdb_data:/opt/output
mysql:
container_name: "rfsim5g-mysql"
image: mysql:8.0
init: true
volumes:
- ../5g_rfsimulator/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql
- ../5g_rfsimulator/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh
environment:
- TZ=Europe/Paris
- MYSQL_DATABASE=oai_db
- MYSQL_USER=test
- MYSQL_PASSWORD=test
- MYSQL_ROOT_PASSWORD=linux
healthcheck:
test: /bin/bash -c "/tmp/mysql-healthcheck.sh"
interval: 10s
timeout: 5s
start_period: 10s
start_interval: 500ms
retries: 30
networks:
public_net:
ipv4_address: 192.168.71.131
oai-amf:
container_name: "rfsim5g-oai-amf"
image: oaisoftwarealliance/oai-amf:v2.1.10
environment:
- TZ=Europe/paris
volumes:
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml
depends_on:
- mysql
networks:
public_net:
ipv4_address: 192.168.71.132
oai-smf:
container_name: "rfsim5g-oai-smf"
image: oaisoftwarealliance/oai-smf:v2.1.10
environment:
- TZ=Europe/Paris
volumes:
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml
depends_on:
- oai-amf
networks:
public_net:
ipv4_address: 192.168.71.133
oai-upf:
container_name: "rfsim5g-oai-upf"
image: oaisoftwarealliance/oai-upf:v2.1.10
init: true
environment:
- TZ=Europe/Paris
volumes:
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml
depends_on:
- oai-smf
cap_add:
- NET_ADMIN
- SYS_ADMIN
cap_drop:
- ALL
privileged: true
networks:
public_net:
ipv4_address: 192.168.71.134
interface_name: eth0
traffic_net:
ipv4_address: 192.168.72.134
interface_name: eth1
oai-ext-dn:
privileged: true
container_name: rfsim5g-oai-ext-dn
image: oaisoftwarealliance/trf-gen-cn5g:latest
init: true
entrypoint: /bin/bash -c \
"iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\
"ip route add 12.1.1.0/24 via 192.168.72.134 dev eth0; sleep infinity"
depends_on:
- oai-upf
networks:
traffic_net:
ipv4_address: 192.168.72.135
healthcheck:
test: /bin/bash -c "ping -c 2 192.168.72.134"
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
start_interval: 500ms
oai-gnb:
image: ${REGISTRY-oaisoftwarealliance/}${GNB_IMG:-oai-gnb}:${TAG:-develop}
container_name: rfsim5g-oai-gnb
cap_drop:
- ALL
environment:
USE_ADDITIONAL_OPTIONS: -E --log_config.global_log_options level,nocolor,time --device.name vrtsim --vrtsim.role server --vrtsim.cirdb 1 --vrtsim.cirdb_file /cirdb/cir_db.bin --vrtsim.cirdb_yaml /cirdb/cir_db.yaml --vrtsim.cirdb_model_id 0 --vrtsim.cirdb_ds_ns 1 --vrtsim.cirdb_speed_mps 1.5
depends_on:
cir-generator:
condition: service_completed_successfully
oai-ext-dn:
condition: service_started
networks:
public_net:
ipv4_address: 192.168.71.140
volumes:
- ../../conf_files/gnb.sa.band78.106prb.vrtsim.2x2.yaml:/opt/oai-gnb/etc/gnb.yaml
- cirdb_data:/cirdb:ro
- tmp_data:/tmp/
healthcheck:
test: /bin/bash -c "pgrep nr-softmodem"
interval: 10s
timeout: 5s
start_period: 10s
start_interval: 500ms
retries: 5
ipc: host
oai-nr-ue:
image: ${REGISTRY-oaisoftwarealliance/}${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
container_name: rfsim5g-oai-nr-ue
cap_drop:
- ALL
cap_add:
- NET_ADMIN
- NET_RAW
environment:
USE_ADDITIONAL_OPTIONS: -E -r 106 --numerology 1 --band 78 -C 3619200000 --ssb 516 --uicc0.imsi 208990100001100 --log_config.global_log_options level,nocolor,time --device.name vrtsim --vrtsim.role client
depends_on:
- oai-gnb
networks:
public_net:
ipv4_address: 192.168.71.150
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ../../conf_files/nrue.vrtsim.chanmod.yaml:/opt/oai-nr-ue/etc/nr-ue.yaml
- tmp_data:/tmp/
healthcheck:
test: /bin/bash -c "pgrep nr-uesoftmodem"
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
start_interval: 500ms
ipc: host
networks:
public_net:
driver: bridge
name: rfsim5g-oai-public-net
ipam:
config:
- subnet: 192.168.71.128/26
driver_opts:
com.docker.network.bridge.name: "rfsim5g-public"
traffic_net:
driver: bridge
name: rfsim5g-oai-traffic-net
ipam:
config:
- subnet: 192.168.72.128/26
driver_opts:
com.docker.network.bridge.name: "rfsim5g-traffic"
volumes:
tmp_data:
cirdb_data:
+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
+111 -29
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);
}
@@ -381,20 +450,21 @@ static void perform_channel_modelling(void *arg)
int nb_tx_ant = channel_modelling_args->nbAnt;
c16_t **input_samples = (c16_t **)channel_modelling_args->samples;
int aligned_nsamps = ceil_mod(nsamps, (512 / 8) / sizeof(cf_t));
cf_t samples[aligned_nsamps] __attribute__((aligned(64)));
// Apply noise from global settings
get_noise_vector((float *)samples, nsamps * 2);
channel_desc_t *channel_desc = vrtsim_state->channel_desc;
if (channel_desc == NULL) {
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++) {
@@ -413,16 +483,25 @@ static void perform_channel_modelling(void *arg)
}
for (int batch_index = 0; batch_index < channel_modelling_args->num_batches; batch_index++) {
int start_sample = batch_index * channel_modelling_args->batch_size;
int num_samples = min(channel_modelling_args->batch_size, nsamps - start_sample);
const int start_sample = batch_index * channel_modelling_args->batch_size;
const int num_samples = min(channel_modelling_args->batch_size, nsamps - start_sample);
if (start_sample >= nsamps) {
break;
}
const int aligned_batch = ceil_mod(num_samples, (512 / 8) / sizeof(cf_t));
cf_t samples[aligned_batch] __attribute__((aligned(64)));
memset(samples, 0, sizeof(samples));
// Apply noise from global settings (only valid samples)
get_noise_vector((float *)samples, num_samples * 2);
for (int aatx = 0; aatx < nb_tx_ant; aatx++) {
for (int i = start_sample; i < start_sample + num_samples; i++) {
cf_t *impulse_response = channel_impulse_response_p[aatx];
cf_t *impulse_response = channel_impulse_response_p[aatx];
for (int i = 0; i < num_samples; i++) {
const int gi = start_sample + i;
for (int l = 0; l < channel_desc->channel_length; l++) {
int idx = i - l;
const int idx = gi - l;
// TODO: Use AVX2 for this
c16_t tx_input = idx >= 0 ? input_samples[aatx][idx]
: channel_modelling_args->saved_samples[aatx][SAVED_SAMPLES_LEN + idx];
@@ -432,31 +511,32 @@ static void perform_channel_modelling(void *arg)
}
}
// Convert to c16_t
c16_t samples_out[aligned_nsamps] __attribute__((aligned(64)));
#if defined(__AVX512F__)
for (int i = 0; i < aligned_nsamps / 8; i++) {
// Convert to c16_t (only once per batch)
c16_t samples_out[aligned_batch] __attribute__((aligned(64)));
#if defined(__AVX512F__)
for (int i = 0; i < aligned_batch / 8; i++) {
simde__m512 *in = (simde__m512 *)&samples[i * 8];
simde__m256i *out = (simde__m256i *)&samples_out[i * 8];
*out = simde_mm512_cvtsepi32_epi16(simde_mm512_cvtps_epi32(*in));
}
#elif defined(__AVX2__)
for (int i = 0; i < aligned_nsamps / 4; i++) {
#elif defined(__AVX2__)
for (int i = 0; i < aligned_batch / 4; i++) {
simde__m256 *in = (simde__m256 *)&samples[i * 4];
simde__m128i *out = (simde__m128i *)&samples_out[i * 4];
*out = simde_mm256_cvtsepi32_epi16(simde_mm256_cvtps_epi32(*in));
}
#else
for (int i = 0; i < nsamps; i++) {
#else
for (int i = 0; i < num_samples; i++) {
samples_out[i].r = lroundf(samples[i].r);
samples_out[i].i = lroundf(samples[i].i);
}
#endif
#endif
// Write the batch as a single contiguous transfer
vrtsim_write_internal(channel_modelling_args->vrtsim_state,
channel_modelling_args->timestamp,
channel_modelling_args->timestamp + start_sample,
samples_out,
channel_modelling_args->nsamps,
num_samples,
aarx,
channel_modelling_args->flags,
aarx);
@@ -507,7 +587,6 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state,
for (int aatx = 0; aatx < nbAnt; aatx++)
memcpy(&args->saved_samples[aatx][0], saved_samples[aatx], sizeof(c16_t) * SAVED_SAMPLES_LEN);
}
memcpy(args->saved_samples, saved_samples, sizeof(saved_samples));
pushNotifiedFIFO(&vrtsim_state->channel_modelling_actors[aarx].fifo, task);
}
@@ -543,7 +622,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 +688,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 +702,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 +792,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);
@@ -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)
};