Files
openairinterface5g/radio/vrtsim/cirdb_provider.c
T
Merkebu Girmay ccf5021072 radio/vrtsim: Add CIRDB support for precomputed channel taps
Integrate Channel Impulse Response Database (CIRDB) to enable playback
of precomputed realistic channel taps in vrtsim, with runtime selection
via YAML metadata.

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

Fixes:
- RNG initialization: Add randominit() call before gaussZiggurat()
- FlatBuffers linking: Handle both 'FlatBuffers' and 'flatbuffers' package names when working with external channel emmulator
- Address review comments: use pthread directly, update calloc_or_fail, simplify CMake
2026-02-09 23:22:40 -08:00

349 lines
11 KiB
C

/*
* 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;
}