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
349 lines
11 KiB
C
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;
|
|
}
|