diff --git a/ci-scripts/xml_files/container_5g_vrtsim_cirdb.xml b/ci-scripts/xml_files/container_5g_vrtsim_cirdb.xml
new file mode 100644
index 0000000000..84a6a527f7
--- /dev/null
+++ b/ci-scripts/xml_files/container_5g_vrtsim_cirdb.xml
@@ -0,0 +1,130 @@
+
+
+ vrtsim-5gnr-cirdb
+ Monolithic gNB vrtsim + CIRDB
+ wrench
+
+
+ Pull_Local_Registry
+ Pull Images from Local Registry
+ localhost
+ oai-gnb oai-nr-ue
+ arm_
+
+
+
+ Create_Workspace
+ Create new Workspace
+ localhost
+
+
+
+ Deploy_Object
+ Deploy OAI 5G CoreNetwork
+ localhost
+ ci-scripts/yaml_files/5g_vrtsim_cirdb
+ mysql oai-amf oai-smf oai-upf oai-ext-dn
+
+
+
+ Deploy_Object
+ Deploy OAI 5G gNB+UE RF sim SA
+ localhost
+ ci-scripts/yaml_files/5g_vrtsim_cirdb
+ oai-gnb oai-nr-ue
+
+
+
+ Attach_UE
+ Attach OAI UE (Wait for IP)
+ rfsim5g_ue
+ localhost
+
+
+
+ Ping
+ Ping ext-dn from NR-UE
+ rfsim5g_ue
+ localhost
+ rfsim5g_ext_dn
+ localhost
+ -c 20 -i 0.25
+ 5
+
+
+
+ Ping
+ Ping NR-UE from ext-dn
+ rfsim5g_ext_dn
+ localhost
+ rfsim5g_ue
+ localhost
+ -c 20 -i 0.25
+ 5
+
+
+
+ Iperf
+ Iperf (DL/3Mbps/UDP)(20 sec)
+ -u -b 3M -t 20 -R
+ rfsim5g_ue
+ localhost
+ rfsim5g_ext_dn
+ localhost
+ 5
+ 90
+
+
+
+ Iperf
+ Iperf (UL/1Mbps/UDP)(20 sec)
+ -u -b 1M -t 20
+ rfsim5g_ue
+ localhost
+ rfsim5g_ext_dn
+ localhost
+ 5
+ 90
+
+
+
+ Undeploy_Object
+ true
+ Undeploy all OAI 5G stack
+ localhost
+ ci-scripts/yaml_files/5g_vrtsim_cirdb
+ 10,0,0,0
+ 1,0,0,0
+
+
+
+ Clean_Test_Server_Images
+ true
+ Clean Test Images on Test Server
+ localhost
+ oai-gnb oai-nr-ue
+
+
+
+
diff --git a/ci-scripts/yaml_files/5g_vrtsim_cirdb/docker-compose.yaml b/ci-scripts/yaml_files/5g_vrtsim_cirdb/docker-compose.yaml
new file mode 100644
index 0000000000..0d7b509450
--- /dev/null
+++ b/ci-scripts/yaml_files/5g_vrtsim_cirdb/docker-compose.yaml
@@ -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:
diff --git a/radio/vrtsim/CMakeLists.txt b/radio/vrtsim/CMakeLists.txt
index 71d18cc5bc..9726743361 100644
--- a/radio/vrtsim/CMakeLists.txt
+++ b/radio/vrtsim/CMakeLists.txt
@@ -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})
diff --git a/radio/vrtsim/cirdb_provider.c b/radio/vrtsim/cirdb_provider.c
new file mode 100644
index 0000000000..39a7bff29c
--- /dev/null
+++ b/radio/vrtsim/cirdb_provider.c
@@ -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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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;
+}
diff --git a/radio/vrtsim/cirdb_provider.h b/radio/vrtsim/cirdb_provider.h
new file mode 100644
index 0000000000..42e49f3c5e
--- /dev/null
+++ b/radio/vrtsim/cirdb_provider.h
@@ -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
+#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 */
diff --git a/radio/vrtsim/cirdb_yaml.cpp b/radio/vrtsim/cirdb_yaml.cpp
new file mode 100644
index 0000000000..aa95b09505
--- /dev/null
+++ b/radio/vrtsim/cirdb_yaml.cpp
@@ -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
+#include
+#include
+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(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(0);
+
+ int model_id = e["model_id"].as(-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(0);
+ int n_rx = e["n_rx"].as(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(0.0f);
+ float sp = e["speed_mps"].as(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(0);
+ best.S = e["S"].as(0);
+ best.fs_hz = e["fs_hz"].as(0.0);
+ best.snapshot_dt_s = e["snapshot_dt_s"].as(0.0);
+ best.ds_ns = ds_ns;
+ best.speed_mps = sp;
+ best.pair_order = pair_order;
+ best.offset_bytes = e["offset_bytes"].as(0);
+ best.nbytes = e["nbytes"].as(0);
+ }
+ }
+
+ if (!found)
+ return 0;
+ *out = best;
+ return 1;
+}
diff --git a/radio/vrtsim/cirdb_yaml.h b/radio/vrtsim/cirdb_yaml.h
new file mode 100644
index 0000000000..e3cf61f8cf
--- /dev/null
+++ b/radio/vrtsim/cirdb_yaml.h
@@ -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
+
+#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
diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c
index f2f9cfef1d..9a0cd779dc 100644
--- a/radio/vrtsim/vrtsim.c
+++ b/radio/vrtsim/vrtsim.c
@@ -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);
diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf
index 7f750d7259..ec036d30ca 100644
--- a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf
+++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf
@@ -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)
+};