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:
@@ -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:
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user