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..89e3b431aa 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); } @@ -392,9 +461,15 @@ static void perform_channel_modelling(void *arg) return; } + if (vrtsim_state->use_cirdb) { + double seconds = (double)channel_modelling_args->timestamp / vrtsim_state->sample_rate; + uint64_t elapsed_ns = (uint64_t)(seconds * 1e9 + 0.5); + cirdb_update(elapsed_ns); + } + cf_t channel_impulse_response[nb_tx_ant][channel_desc->channel_length]; cf_t *channel_impulse_response_p[nb_tx_ant]; - if (!vrtsim_state->taps_socket) { + if (!vrtsim_state->taps_socket && !vrtsim_state->use_cirdb) { const float pathloss_linear = powf(10, channel_desc->path_loss_dB / 20.0); // Convert channel impulse response to float + apply pathloss for (int aatx = 0; aatx < nb_tx_ant; aatx++) { @@ -543,7 +618,7 @@ static int vrtsim_write(openair0_device_t *device, AssertFatal(timestamp >= 0, "Timestamp must be non-negative, got %ld\n", timestamp); timestamp -= device->openair0_cfg->command_line_sample_advance; vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; - bool channel_modelling = vrtsim_state->chanmod || vrtsim_state->taps_socket; + bool channel_modelling = vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb; return channel_modelling ? vrtsim_write_with_chanmod(vrtsim_state, timestamp, samplesVoid, nsamps, nbAnt, flags) : vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0); } @@ -609,7 +684,7 @@ static void vrtsim_end(openair0_device_t *device) } tx_timing_t *tx_timing = vrtsim_state->tx_timing; - if (vrtsim_state->chanmod || vrtsim_state->taps_socket) { + if (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb) { for (int i = 0; i < vrtsim_state->peer_info.num_rx_antennas; i++) { shutdown_actor(&vrtsim_state->channel_modelling_actors[i]); } @@ -623,7 +698,9 @@ static void vrtsim_end(openair0_device_t *device) } tx_timing->average_tx_budget /= vrtsim_state->peer_info.num_rx_antennas; free_noise_device(); - if (vrtsim_state->taps_socket) { + if (vrtsim_state->use_cirdb) { + cirdb_stop(); + } else if (vrtsim_state->taps_socket) { taps_client_stop(); } } @@ -711,8 +788,9 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device_t *de vrtsim_state->tx_num_channels = openair0_cfg->tx_num_channels; vrtsim_state->rx_num_channels = openair0_cfg->rx_num_channels; - if (vrtsim_state->chanmod || vrtsim_state->taps_socket) { + if (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb) { init_channelmod(); + randominit(); int noise_power_dBFS = get_noise_power_dBFS(); int16_t noise_power = noise_power_dBFS == INVALID_DBFS_VALUE ? 0 : (int16_t)(32767.0 / powf(10.0, .05 * -noise_power_dBFS)); LOG_A(HW, "VRTSIM: Noise power %d sample value\n", noise_power); 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) +};