Merge remote-tracking branch 'origin/taps-client-rewrite' into integration_2026_w22 (!4086)

vrtsim: Refactor peer antenna configuration and taps_client

Refactor peer antenna configuration and taps_client

This commit addresses several architectural issues in vrtsim,
specifically regarding how peer antenna counts are managed. Some
additional changes were made to allow unit tests and several fixes were
delivered.

Key changes:

1. Refactored peer antenna management:

  * Removed the redundant 'peer_info_t' structure which overlapped with
    ue_config and client_info.
  * Added explicit 'peer_tx_ant' and 'peer_rx_ant' fields to
    vrtsim_state_t.
  * Server now pulls peer info from the UE config, while Client pulls it
    from the GNB info published by the server.

2. Refactor taps_client to be thread safe

3. Fixed a bug where only the first antenna IQ was read from underlying
   SHM mechanism

4. Test / usability related changes:

* Added support for configurable SHM channel names via
  '--vrtsim.shm_channel_name' to prevent IPC conflicts between tests.
* Reduced the sleep() calls inside the code to reduce test runtime and
  speedup vrtsim connection initialization and cleanup
* Added a unit tests for:
  - transpartent channel mode
  - taps_client mode
  - cirdb mode

Reviewed-By: Merkebu Girmay <merkebu.girmay@openairinterface.org>
This commit is contained in:
Robert Schmidt
2026-05-27 09:22:41 +02:00
12 changed files with 1220 additions and 281 deletions

View File

@@ -3,6 +3,7 @@
cmake_minimum_required (VERSION 3.19)
project (OpenAirInterface LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(OAI_VERSION 2.4.0)
option(ENABLE_CHANNEL_SIM_CUDA "Enable CUDA accelerated channel simulation" OFF)
@@ -235,9 +236,7 @@ endif()
set(CMAKE_C_FLAGS
"${C_FLAGS_PROCESSOR} ${commonOpts} -std=gnu11 -funroll-loops ${CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS
"${C_FLAGS_PROCESSOR} ${commonOpts} -std=c++11 ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "${C_FLAGS_PROCESSOR} ${commonOpts} ${CMAKE_CXX_FLAGS}")
add_boolean_option(SANITIZE_ADDRESS OFF "enable the address sanitizer (ASan)" ON)
if (SANITIZE_ADDRESS)

View File

@@ -14,11 +14,13 @@ RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --yes \
libgtest-dev \
libyaml-cpp-dev \
libzmq3-dev
libzmq3-dev \
flatbuffers-compiler \
libnanomsg-dev
RUN rm -Rf /oai-ran
WORKDIR /oai-ran
COPY . .
WORKDIR /oai-ran/build
RUN cmake -GNinja -DENABLE_TESTS=ON -DOAI_ZMQ=ON -DCMAKE_BUILD_TYPE=Debug -DSANITIZE_ADDRESS=True .. && ninja
RUN cmake -GNinja -DENABLE_TESTS=ON -DOAI_ZMQ=ON -DCMAKE_BUILD_TYPE=Debug -DSANITIZE_ADDRESS=True -DOAI_VRTSIM_TAPS_CLIENT=ON .. && ninja

View File

@@ -16,7 +16,7 @@
#include "utils.h"
#include "common/utils/threadPool/pthread_utils.h"
#define CIRCULAR_BUFFER_SIZE (30720 * 14 * 20)
#define CIRCULAR_BUFFER_SIZE (30720 * 20)
typedef struct {
int magic;
@@ -97,10 +97,17 @@ ShmTDIQChannel *shm_td_iq_channel_connect(const char *name, int timeout_in_secon
// Create shared memory segment
int fd = -1;
while (timeout_in_seconds > 0 && fd == -1) {
fd = shm_open(name, O_RDWR, S_IRUSR | S_IWUSR);
for (int i = 0; i < 1000; i++) {
fd = shm_open(name, O_RDWR, S_IRUSR | S_IWUSR);
if (fd != -1) {
break;
}
usleep(1000);
}
timeout_in_seconds--;
printf("Waiting for server to create shared memory segment\n");
sleep(1);
if (fd == -1) {
printf("Waiting for server to create shared memory segment\n");
}
}
AssertFatal(fd != -1, "shm_open() failed: errno %d, %s", errno, strerror(errno));

View File

@@ -2265,8 +2265,10 @@ void init_channelmod(void) {
int numparams = sizeofArray(channelmod_params);
int ret = config_get(config_get_if(), channelmod_params, numparams, CHANNELMOD_SECTION);
AssertFatal(ret >= 0, "configuration couldn't be performed");
defined_channels=calloc(max_chan,sizeof( channel_desc_t *));
AssertFatal(defined_channels!=NULL, "couldn't allocate %u channel descriptors\n",max_chan);
if (defined_channels == NULL) {
defined_channels = calloc(max_chan, sizeof(channel_desc_t *));
AssertFatal(defined_channels != NULL, "couldn't allocate %u channel descriptors\n", max_chan);
}
/* look for telnet server, if it is loaded, add the channel modeling commands to it */
add_telnetcmd_func_t addcmd = (add_telnetcmd_func_t)get_shlibmodule_fptr("telnetsrv", TELNET_ADDCMD_FNAME);

View File

@@ -29,7 +29,6 @@ endif()
# 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
@@ -62,3 +61,25 @@ if (OAI_VRTSIM_TAPS_CLIENT)
target_link_libraries(taps_client PUBLIC ${FLATBUFFERS_TARGET})
target_link_libraries(taps_client PUBLIC flatbuffers taps_api SIMU nanomsg)
endif()
if (ENABLE_TESTS)
add_library(vrtsim_static vrtsim.c cirdb_provider.c cirdb_yaml.cpp)
target_link_libraries(vrtsim_static
PUBLIC
SIMU
shm_td_iq_channel
noise_device
log_headers
yaml-cpp
channel_pipeline
thread-pool
)
if (OAI_VRTSIM_TAPS_CLIENT)
target_link_libraries(vrtsim_static PRIVATE taps_client)
endif()
if (CUDA_ENABLE)
target_compile_definitions(vrtsim_static PRIVATE CUDA_ENABLE)
endif()
add_subdirectory(tests)
endif()

View File

@@ -3,184 +3,207 @@
*/
#include <nanomsg/nn.h>
#include <nanomsg/pubsub.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "pthread.h"
#include <unistd.h>
#include "taps_generated.h"
#include "SIMULATION/TOOLS/sim.h"
extern "C" {
#include "assertions.h"
#include "common/utils/LOG/log.h"
#include "sim.h"
#include "utils.h"
}
#include <cfloat>
#include <cmath>
#include <atomic>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
#define NUM_TAPS_BUFFERS 4
#define NUM_TAPS_BUFFERS 16
#define MAX_NUM_IDS 4
#define MAX_TAPS_LEN 100
#define MAX_TAPS_MSG_SIZE (sizeof(struct complexf) * MAX_TAPS_LEN * 4 * 4 + 20)
#define MAX_TX_RX_ANTENNAS 8
#define MAX_TAPS_MSG_SIZE (sizeof(struct complexf) * MAX_TAPS_LEN * MAX_TX_RX_ANTENNAS * MAX_TX_RX_ANTENNAS + 256)
static pthread_t client_thread;
static bool should_run = true;
typedef struct {
int id;
int sock;
uint32_t num_tx_antennas;
uint32_t num_rx_antennas;
channel_desc_t **channel_desc;
} client_thread_args_t;
struct taps_client_t {
int sock_;
std::atomic<bool> should_run_;
typedef struct {
void *taps_msg;
channel_desc_t *channel_desc;
} taps_buffer_t;
// Raw message buffer pool
std::vector<std::unique_ptr<uint8_t[]>> buffer_storage_;
std::queue<uint8_t *> free_buffers_;
typedef struct {
taps_buffer_t taps_buffers[NUM_TAPS_BUFFERS];
int current_buffer;
} taps_storage_t;
// Per-ID incoming queue and buffer currently backing channel_desc ch_ps
std::queue<uint8_t *> read_buffers_[MAX_NUM_IDS];
uint8_t *current_buffers_[MAX_NUM_IDS];
void ascii_line_plot(const float *data, size_t size, char *buffer)
{
const char levels[] = "_.-=#"; // ASCII characters for different levels
size_t num_levels = sizeof(levels) - 1; // Number of levels (excluding the null terminator)
// Per-ID channel descriptors with pre-allocated ch_ps arrays
channel_desc_t channel_descs_[MAX_NUM_IDS];
struct complexf *ch_ps_storage_[MAX_NUM_IDS][MAX_TX_RX_ANTENNAS * MAX_TX_RX_ANTENNAS];
// Calculate min and max values from the data
float min_val = FLT_MAX;
float max_val = FLT_MIN;
for (size_t i = 0; i < size; i++) {
if (data[i] < min_val)
min_val = data[i];
if (data[i] > max_val)
max_val = data[i];
}
std::mutex mutex_;
std::thread thread_;
// Handle edge case where all values are the same
if (min_val == max_val) {
min_val -= 1.0f;
max_val += 1.0f;
}
uint num_tx_ant_;
uint num_rx_ant_;
// Normalize and map data to levels
for (size_t i = 0; i < size; i++) {
float normalized = (data[i] - min_val) / (max_val - min_val); // Normalize to [0, 1]
size_t level_index = (size_t)(normalized * num_levels); // Map to level index
if (level_index > num_levels)
level_index = num_levels; // Clamp to valid range
snprintf(buffer + i, 2, "%c", levels[level_index]);
}
}
taps_client_t(const char *socket_path, int num_tx_ant, int num_rx_ant)
: should_run_(true), num_tx_ant_(num_tx_ant), num_rx_ant_(num_rx_ant)
{
memset(current_buffers_, 0, sizeof(current_buffers_));
memset(channel_descs_, 0, sizeof(channel_descs_));
static void init_taps_storage(taps_storage_t *storage, int num_tx_antennas, int num_rx_antennas)
{
for (int i = 0; i < NUM_TAPS_BUFFERS; i++) {
storage->taps_buffers[i].taps_msg = calloc_or_fail(1, MAX_TAPS_MSG_SIZE);
storage->taps_buffers[i].channel_desc = (channel_desc_t *)calloc_or_fail(1, sizeof(channel_desc_t));
storage->taps_buffers[i].channel_desc->ch_ps =
(struct complexf **)calloc_or_fail(num_rx_antennas * num_tx_antennas, sizeof(struct complexf *));
}
storage->current_buffer = 0;
}
for (int i = 0; i < MAX_NUM_IDS; i++)
channel_descs_[i].ch_ps = ch_ps_storage_[i];
static taps_storage_t taps_storage;
void *client_thread_func(void *args)
{
client_thread_args_t *client_thread_args = (client_thread_args_t *)args;
while (should_run) {
int next_buffer = (taps_storage.current_buffer + 1) % NUM_TAPS_BUFFERS;
taps_buffer_t *taps_buffer = &taps_storage.taps_buffers[next_buffer];
int ret = nn_recv(client_thread_args->sock, taps_buffer->taps_msg, MAX_TAPS_MSG_SIZE, NN_DONTWAIT);
if (ret < 0) {
if (errno == EAGAIN) {
// Timeout: no message available, sleep briefly and continue
usleep(100);
continue;
}
LOG_E(HW, "nn_recv() failed: errno: %d, %s\n", errno, strerror(errno));
continue;
buffer_storage_.reserve(NUM_TAPS_BUFFERS);
for (int i = 0; i < NUM_TAPS_BUFFERS; i++) {
buffer_storage_.push_back(std::make_unique<uint8_t[]>(MAX_TAPS_MSG_SIZE));
free_buffers_.push(buffer_storage_.back().get());
}
auto taps_message = Phy::GetTaps(taps_buffer->taps_msg);
if (taps_message->num_rx_antennas() != client_thread_args->num_rx_antennas
|| taps_message->num_tx_antennas() != client_thread_args->num_tx_antennas) {
LOG_E(HW,
"Number of antennas mismatch: expected %d x %d, got %d x %d\n",
client_thread_args->num_rx_antennas,
client_thread_args->num_tx_antennas,
taps_message->num_rx_antennas(),
taps_message->num_tx_antennas());
continue;
}
channel_desc_t *channel_desc = taps_buffer->channel_desc;
const flatbuffers::Vector<float> *taps = taps_message->taps();
struct complexf *base_pointer = (struct complexf *)taps->data();
int taps_len = taps_message->taps_len();
AssertFatal(taps_len < MAX_TAPS_LEN, "Too many samples in the taps array, taps_len = %d\n", taps_len);
for (unsigned int aarx = 0U; aarx < client_thread_args->num_rx_antennas; aarx++) {
for (unsigned int aatx = 0U; aatx < client_thread_args->num_tx_antennas; aatx++) {
channel_desc->ch_ps[aarx + (client_thread_args->num_rx_antennas * aatx)] =
&base_pointer[(aarx + (client_thread_args->num_rx_antennas * aatx)) * taps_len];
}
}
channel_desc->path_loss_dB = 0;
channel_desc->channel_length = taps_len;
*client_thread_args->channel_desc = channel_desc;
taps_storage.current_buffer = next_buffer;
LOG_A(HW, "Receved new taps message, channel_length %d, buffer %d\n", channel_desc->channel_length, next_buffer);
for (unsigned int aarx = 0; aarx < client_thread_args->num_rx_antennas; aarx++) {
for (unsigned int aatx = 0; aatx < client_thread_args->num_tx_antennas; aatx++) {
char buffer[MAX_TAPS_LEN + 1];
memset(buffer, 0, sizeof(buffer));
float magnitudes[MAX_TAPS_LEN];
cf_t *channel = channel_desc->ch_ps[aarx + (client_thread_args->num_rx_antennas * aatx)];
for (int i = 0; i < channel_desc->channel_length; i++) {
magnitudes[i] = sqrtf(powf(channel[i].r, 2) + powf(channel[i].i, 2));
sock_ = nn_socket(AF_SP, NN_SUB);
AssertFatal(sock_ >= 0, "nn_socket() failed: errno: %d, %s\n", errno, strerror(errno));
int ret = nn_connect(sock_, socket_path);
AssertFatal(ret >= 0, "nn_connect() failed: errno: %d, %s\n", errno, strerror(errno));
ret = nn_setsockopt(sock_, NN_SUB, NN_SUB_SUBSCRIBE, "", 0);
AssertFatal(ret == 0, "nn_setsockopt() failed: errno: %d, %s\n", errno, strerror(errno));
thread_ = std::thread(&taps_client_t::run, this);
}
~taps_client_t()
{
should_run_ = false;
thread_.join();
nn_close(sock_);
}
void run()
{
while (should_run_) {
uint8_t *buf = nullptr;
{
std::lock_guard<std::mutex> lock(mutex_);
if (!free_buffers_.empty()) {
buf = free_buffers_.front();
free_buffers_.pop();
}
ascii_line_plot(magnitudes, channel_desc->channel_length, buffer);
LOG_A(HW, "Taps message %d, channel %d x %d: %s\n", client_thread_args->id, aarx, aatx, buffer);
}
if (!buf) {
usleep(100);
continue;
}
int ret = nn_recv(sock_, buf, MAX_TAPS_MSG_SIZE, NN_DONTWAIT);
if (ret < 0) {
{
std::lock_guard<std::mutex> lock(mutex_);
free_buffers_.push(buf);
}
if (errno != EAGAIN)
LOG_E(HW, "nn_recv() failed: errno: %d, %s\n", errno, strerror(errno));
usleep(100);
continue;
}
auto *msg = Phy::GetTaps(buf);
uint32_t id = msg->id();
if (id >= MAX_NUM_IDS) {
LOG_E(HW, "Received taps message with invalid id %u (max %d)\n", id, MAX_NUM_IDS - 1);
std::lock_guard<std::mutex> lock(mutex_);
free_buffers_.push(buf);
continue;
}
uint32_t num_rx = msg->num_rx_antennas();
uint32_t num_tx = msg->num_tx_antennas();
if (num_tx != num_tx_ant_ || num_rx != num_rx_ant_) {
LOG_E(HW,
"Mismatch between received and expected channel model %d: %ux%u vs %ux%u\n",
id,
num_rx,
num_tx,
num_rx_ant_,
num_tx_ant_);
std::lock_guard<std::mutex> lock(mutex_);
free_buffers_.push(buf);
continue;
}
std::lock_guard<std::mutex> lock(mutex_);
read_buffers_[id].push(buf);
}
}
return NULL;
}
extern "C" void taps_client_connect(int id,
const char *socket_path,
int num_tx_antennas,
int num_rx_antennas,
channel_desc_t **channel_desc)
{
// Create a socket
int sock = nn_socket(AF_SP, NN_SUB);
AssertFatal(sock >= 0, "nn_socket() failed: errno: %d, %s\n", errno, strerror(errno));
channel_desc_t *get_model(int id)
{
if (id < 0 || id >= MAX_NUM_IDS)
return nullptr;
int ret = nn_connect(sock, socket_path);
AssertFatal(ret >= 0, "nn_connect() failed: errno: %d, %s\n", errno, strerror(errno));
uint8_t *buf = nullptr;
{
std::lock_guard<std::mutex> lock(mutex_);
if (read_buffers_[id].empty()) {
if (current_buffers_[id]) {
return &channel_descs_[id];
} else {
return nullptr;
}
}
buf = read_buffers_[id].front();
read_buffers_[id].pop();
}
// Subscribe to all messages
ret = nn_setsockopt(sock, NN_SUB, NN_SUB_SUBSCRIBE, "", 0);
AssertFatal(ret == 0, "nn_setsockopt() failed, errno %d, %s\n", errno, strerror(errno));
auto *msg = Phy::GetTaps(buf);
uint32_t num_rx = msg->num_rx_antennas();
uint32_t num_tx = msg->num_tx_antennas();
uint32_t taps_len = msg->taps_len();
init_taps_storage(&taps_storage, num_tx_antennas, num_rx_antennas);
AssertFatal(taps_len <= MAX_TAPS_LEN, "Too many taps: %u (max %d)\n", taps_len, MAX_TAPS_LEN);
client_thread_args_t *client_thread_args = static_cast<client_thread_args_t *>(malloc(sizeof(client_thread_args_t)));
client_thread_args->id = id;
client_thread_args->sock = sock;
client_thread_args->num_rx_antennas = num_rx_antennas;
client_thread_args->num_tx_antennas = num_tx_antennas;
client_thread_args->channel_desc = channel_desc;
ret = pthread_create(&client_thread, NULL, client_thread_func, client_thread_args);
AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno));
}
auto *base = (struct complexf *)msg->taps()->data();
channel_desc_t *desc = &channel_descs_[id];
desc->nb_rx = num_rx;
desc->nb_tx = num_tx;
desc->nb_taps = taps_len;
extern "C" void taps_client_stop()
{
should_run = false;
pthread_join(client_thread, NULL);
for (int i = 0; i < NUM_TAPS_BUFFERS; i++) {
free(taps_storage.taps_buffers[i].taps_msg);
free(taps_storage.taps_buffers[i].channel_desc->ch_ps);
free(taps_storage.taps_buffers[i].channel_desc);
for (uint32_t aarx = 0; aarx < num_rx; aarx++) {
for (uint32_t aatx = 0; aatx < num_tx; aatx++) {
desc->ch_ps[aarx + num_rx * aatx] = &base[(aarx + num_rx * aatx) * taps_len];
}
}
desc->path_loss_dB = 0;
desc->channel_length = taps_len;
{
std::lock_guard<std::mutex> lock(mutex_);
if (current_buffers_[id])
free_buffers_.push(current_buffers_[id]);
current_buffers_[id] = buf;
}
LOG_A(HW, "New taps for id %d: channel_length %u, %ux%u antennas\n", id, taps_len, num_rx, num_tx);
return desc;
}
};
extern "C" void *taps_client_connect(const char *socket_path, int num_tx_ant, int num_rx_ant)
{
return new taps_client_t(socket_path, num_tx_ant, num_rx_ant);
}
extern "C" channel_desc_t *taps_client_get_model(void *handle, int id)
{
return static_cast<taps_client_t *>(handle)->get_model(id);
}
extern "C" void taps_client_stop(void *handle)
{
delete static_cast<taps_client_t *>(handle);
}

View File

@@ -11,8 +11,9 @@ extern "C" {
#include "sim.h"
void taps_client_connect(int id, const char *socket_path, int num_tx_antennas, int num_rx_antennas, channel_desc_t **channel_desc);
void taps_client_stop();
void *taps_client_connect(const char *socket_path, int num_tx_ant, int num_rx_ant);
channel_desc_t *taps_client_get_model(void *handle, int id);
void taps_client_stop(void *handle);
#ifdef __cplusplus
}

View File

@@ -0,0 +1,24 @@
# SPDX-License-Identifier: LicenseRef-CSSL-1.0
add_executable(test_vrtsim test_vrtsim.cpp)
target_link_libraries(test_vrtsim PRIVATE vrtsim_static UTIL CONFIG_LIB GTest::gtest dl shlib_loader)
if (OAI_VRTSIM_TAPS_CLIENT)
target_link_libraries(test_vrtsim PRIVATE taps_client nanomsg)
endif()
add_dependencies(tests test_vrtsim)
add_test(NAME test_vrtsim
COMMAND ./test_vrtsim)
if (OAI_VRTSIM_TAPS_CLIENT)
add_executable(test_vrtsim_taps test_vrtsim_taps.cpp)
target_link_libraries(test_vrtsim_taps PRIVATE vrtsim_static UTIL CONFIG_LIB GTest::gtest dl shlib_loader taps_client nanomsg flatbuffers taps_api)
add_dependencies(tests test_vrtsim_taps)
add_test(NAME test_vrtsim_taps
COMMAND ./test_vrtsim_taps)
endif()
add_executable(test_vrtsim_cirdb test_vrtsim_cirdb.cpp)
target_link_libraries(test_vrtsim_cirdb PRIVATE vrtsim_static UTIL CONFIG_LIB GTest::gtest dl shlib_loader yaml-cpp)
add_dependencies(tests test_vrtsim_cirdb)
add_test(NAME test_vrtsim_cirdb
COMMAND ./test_vrtsim_cirdb)

View File

@@ -0,0 +1,173 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "common_lib.h"
#include <gtest/gtest.h>
#include "common/config/config_userapi.h"
#include <thread>
#include <vector>
#include <string>
configmodule_interface_t *uniqCfg = NULL;
extern "C" {
#include "common/config/config_userapi.h"
#include "openair1/SIMULATION/TOOLS/sim.h"
extern int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg);
extern void vrtsim_produce_samples(openair0_device_t *device, size_t num_samples);
static softmodem_params_t softmodem_params;
softmodem_params_t *get_softmodem_params(void)
{
return &softmodem_params;
}
void exit_function(const char *file, const char *function, const int line, const char *s, const int assert)
{
fprintf(stderr, "FATAL: %s at %s:%s:%d\n", s, file, function, line);
exit(EXIT_FAILURE);
}
}
struct VRTSIMTestCase {
int server_tx;
int server_rx;
int client_tx;
int client_rx;
std::string ue_antennas;
};
class VRTSIMTest : public ::testing::TestWithParam<VRTSIMTestCase> {
protected:
configmodule_interface_t *cfg1 = nullptr;
configmodule_interface_t *cfg2 = nullptr;
openair0_device_t server_device = {0};
openair0_device_t client_device = {0};
openair0_config_t server_config = {0};
openair0_config_t client_config = {0};
void SetUp() override
{
const auto &param = GetParam();
// Setup server
std::vector<const char *> server_argv = {"--vrtsim.role",
"server",
"--vrtsim.disable-timing-thread",
"1",
"--vrtsim.ue_config.[0].antennas",
param.ue_antennas.c_str()};
cfg1 = load_configmodule(server_argv.size(), (char **)server_argv.data(), CONFIG_ENABLECMDLINEONLY);
uniqCfg = cfg1;
server_config.tx_num_channels = param.server_tx;
server_config.rx_num_channels = param.server_rx;
server_config.sample_rate = 30.72e6;
ASSERT_EQ(device_init(&server_device, &server_config), 0);
ASSERT_EQ(server_device.trx_start_func(&server_device), 0);
// Setup client
std::vector<const char *> client_argv = {"--vrtsim.role", "client"};
cfg2 = load_configmodule(client_argv.size(), (char **)client_argv.data(), CONFIG_ENABLECMDLINEONLY);
uniqCfg = cfg2;
client_config.tx_num_channels = param.client_tx;
client_config.rx_num_channels = param.client_rx;
client_config.sample_rate = 30.72e6;
ASSERT_EQ(device_init(&client_device, &client_config), 0);
ASSERT_EQ(client_device.trx_start_func(&client_device), 0);
}
void TearDown() override
{
if (server_device.trx_end_func)
server_device.trx_end_func(&server_device);
if (client_device.trx_end_func)
client_device.trx_end_func(&client_device);
if (cfg1)
end_configmodule(cfg1);
if (cfg2)
end_configmodule(cfg2);
}
};
TEST_P(VRTSIMTest, TransparentChannel)
{
const auto &param = GetParam();
const int nsamps = 1024;
// Prepare TX samples for client (UL)
std::vector<std::vector<c16_t>> client_tx_samples(param.client_tx, std::vector<c16_t>(nsamps));
std::vector<void *> client_tx_ptrs(param.client_tx);
for (int a = 0; a < param.client_tx; a++) {
for (int i = 0; i < nsamps; i++) {
client_tx_samples[a][i] = {(int16_t)(i + a * 100), (int16_t) - (i + a * 100)};
}
client_tx_ptrs[a] = client_tx_samples[a].data();
}
// Prepare TX samples for server (DL)
std::vector<std::vector<c16_t>> server_tx_samples(param.server_tx, std::vector<c16_t>(nsamps));
std::vector<void *> server_tx_ptrs(param.server_tx);
for (int a = 0; a < param.server_tx; a++) {
for (int i = 0; i < nsamps; i++) {
server_tx_samples[a][i] = {(int16_t)(i + a * 200), (int16_t)(i + a * 200 + 10)};
}
server_tx_ptrs[a] = server_tx_samples[a].data();
}
// Both sides write at timestamp 0
ASSERT_EQ(client_device.trx_write_func(&client_device, 0, client_tx_ptrs.data(), nsamps, param.client_tx, 0), nsamps);
ASSERT_EQ(server_device.trx_write_func(&server_device, 0, server_tx_ptrs.data(), nsamps, param.server_tx, 0), nsamps);
// Server produces samples (advances time to nsamps)
vrtsim_produce_samples(&server_device, nsamps);
// Server reads from client (UL) at timestamp 0
std::vector<std::vector<c16_t>> server_rx_samples(param.server_rx, std::vector<c16_t>(nsamps));
std::vector<void *> server_rx_ptrs(param.server_rx);
for (int a = 0; a < param.server_rx; a++)
server_rx_ptrs[a] = server_rx_samples[a].data();
openair0_timestamp_t rx_ts;
ASSERT_EQ(server_device.trx_read_func(&server_device, &rx_ts, server_rx_ptrs.data(), nsamps, param.server_rx), nsamps);
EXPECT_EQ(rx_ts, 0);
// Verify UL mapping (Order mapping: min(client_tx, server_rx))
int num_ul_mapped = std::min(param.client_tx, param.server_rx);
for (int a = 0; a < num_ul_mapped; a++) {
for (int i = 0; i < nsamps; i++) {
EXPECT_EQ(server_rx_samples[a][i].r, client_tx_samples[a][i].r) << "UL Mismatch at index " << i << " antenna " << a;
EXPECT_EQ(server_rx_samples[a][i].i, client_tx_samples[a][i].i) << "UL Mismatch at index " << i << " antenna " << a;
}
}
// Client reads from server (DL) at timestamp 0
std::vector<std::vector<c16_t>> client_rx_samples(param.client_rx, std::vector<c16_t>(nsamps));
std::vector<void *> client_rx_ptrs(param.client_rx);
for (int a = 0; a < param.client_rx; a++)
client_rx_ptrs[a] = client_rx_samples[a].data();
ASSERT_EQ(client_device.trx_read_func(&client_device, &rx_ts, client_rx_ptrs.data(), nsamps, param.client_rx), nsamps);
EXPECT_EQ(rx_ts, 0);
// Verify DL mapping (Order mapping: min(server_tx, client_rx))
int num_dl_mapped = std::min(param.server_tx, param.client_rx);
for (int a = 0; a < num_dl_mapped; a++) {
for (int i = 0; i < nsamps; i++) {
EXPECT_EQ(client_rx_samples[a][i].r, server_tx_samples[a][i].r) << "DL Mismatch at index " << i << " antenna " << a;
EXPECT_EQ(client_rx_samples[a][i].i, server_tx_samples[a][i].i) << "DL Mismatch at index " << i << " antenna " << a;
}
}
}
INSTANTIATE_TEST_SUITE_P(AntennaVariations,
VRTSIMTest,
::testing::Values(VRTSIMTestCase{2, 2, 1, 1, "1x1"},
VRTSIMTestCase{8, 8, 1, 1, "1x1"},
VRTSIMTestCase{8, 8, 2, 2, "2x2"},
VRTSIMTestCase{1, 1, 2, 2, "2x2"}));
int main(int argc, char **argv)
{
logInit();
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,309 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <yaml-cpp/yaml.h>
#include <gtest/gtest.h>
#include <thread>
#include <vector>
#include <string>
#include <complex>
#include <filesystem>
#include <fstream>
#include <tuple>
#include "common_lib.h"
#include "common/config/config_userapi.h"
namespace fs = std::filesystem;
configmodule_interface_t *uniqCfg = NULL;
extern "C" {
#include "common/config/config_userapi.h"
#include "openair1/SIMULATION/TOOLS/sim.h"
extern int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg);
extern void vrtsim_produce_samples(openair0_device_t *device, size_t num_samples);
static softmodem_params_t softmodem_params;
softmodem_params_t *get_softmodem_params(void)
{
return &softmodem_params;
}
void exit_function(const char *file, const char *function, const int line, const char *s, const int assert)
{
fprintf(stderr, "FATAL: %s at %s:%s:%d\n", s, file, function, line);
exit(EXIT_FAILURE);
}
}
class CIRDBProducer {
fs::path root;
std::vector<YAML::Node> entries;
public:
CIRDBProducer(fs::path r) : root(r)
{
fs::create_directories(root);
}
void add_entry(int model_id, int n_tx, int n_rx, int L, int S, double fs, double dt, double ds, double speed)
{
YAML::Node entry;
entry["model_id"] = model_id;
entry["n_tx"] = n_tx;
entry["n_rx"] = n_rx;
entry["L"] = L;
entry["S"] = S;
entry["fs_hz"] = fs;
entry["snapshot_dt_s"] = dt;
entry["ds_ns"] = ds;
entry["speed_mps"] = speed;
entry["offset_bytes"] = entries.size() * n_tx * n_rx * L * sizeof(float) * 2;
entry["nbytes"] = n_tx * n_rx * L * sizeof(float) * 2;
entries.push_back(entry);
}
void write_files()
{
YAML::Node root_node;
root_node["entries"] = entries;
std::ofstream out(root / "vrtsim.yaml");
out << root_node;
std::ofstream bin(root / "cir_db.bin", std::ios::binary);
for (const auto &e : entries) {
int count = e["n_tx"].as<int>() * e["n_rx"].as<int>() * e["L"].as<int>();
std::vector<float> data(count * 2, 0.0f);
for (int tx = 0; tx < e["n_tx"].as<int>(); tx++) {
for (int rx = 0; rx < e["n_rx"].as<int>(); rx++) {
int L = e["L"].as<int>();
data[((rx + tx * e["n_rx"].as<int>()) * L + 7) * 2] = 1.0f;
}
}
bin.write(reinterpret_cast<char *>(data.data()), data.size() * sizeof(float));
}
}
};
struct CIRDBAntParams {
int gnb_tx;
int gnb_rx;
int ue_tx;
int ue_rx;
std::string ue_ant_str;
CIRDBAntParams(int gt, int gr, int ut, int ur, std::string s) : gnb_tx(gt), gnb_rx(gr), ue_tx(ut), ue_rx(ur), ue_ant_str(s)
{
}
};
class VRTSTapsCIRDBTest : public ::testing::TestWithParam<CIRDBAntParams> {
protected:
openair0_device_t server_device = {0};
openair0_device_t client_device = {0};
openair0_config_t server_config = {0};
openair0_config_t client_config = {0};
fs::path tmp_dir;
std::string shm_name;
configmodule_interface_t *cfg1 = nullptr;
configmodule_interface_t *cfg2 = nullptr;
void SetUp() override
{
const CIRDBAntParams &p = GetParam();
tmp_dir = fs::temp_directory_path() / ("vrtsim_cirdb_test_" + std::to_string(p.gnb_tx) + "_" + p.ue_ant_str);
CIRDBProducer producer(tmp_dir);
// Add entries for both directions to both model IDs to ensure test robustness against unintended configuration state leakage
producer.add_entry(0, p.gnb_tx, p.ue_rx, 8, 1, 30.72e6, 0.5, 10.0, 1.5);
producer.add_entry(1, p.gnb_tx, p.ue_rx, 8, 1, 30.72e6, 0.5, 10.0, 1.5);
producer.add_entry(0, p.ue_tx, p.gnb_rx, 8, 1, 30.72e6, 0.5, 10.0, 1.5);
producer.add_entry(1, p.ue_tx, p.gnb_rx, 8, 1, 30.72e6, 0.5, 10.0, 1.5);
producer.write_files();
shm_name = "shm_cirdb_" + std::to_string(p.gnb_tx) + "_" + p.ue_ant_str;
}
void TearDown() override
{
if (server_device.trx_end_func)
server_device.trx_end_func(&server_device);
if (client_device.trx_end_func)
client_device.trx_end_func(&client_device);
if (cfg1)
end_configmodule(cfg1);
if (cfg2)
end_configmodule(cfg2);
fs::remove_all(tmp_dir);
server_device = {0};
client_device = {0};
cfg1 = nullptr;
cfg2 = nullptr;
uniqCfg = nullptr;
}
};
TEST_P(VRTSTapsCIRDBTest, CIRDBDelayDL)
{
const CIRDBAntParams &p = GetParam();
// Setup Server
{
const char *s_argv[] = {"test_vrtsim_cirdb",
"--vrtsim.role",
"server",
"--vrtsim.shm_channel_name",
shm_name.c_str(),
"--vrtsim.disable-timing-thread",
"1",
"--vrtsim.cirdb",
"1",
"--vrtsim.cirdb-path",
tmp_dir.c_str(),
"--vrtsim.cirdb_model_id",
"0",
"--vrtsim.ue_config.[0].antennas",
p.ue_ant_str.c_str()};
cfg1 = load_configmodule(sizeof(s_argv) / sizeof(char *), (char **)s_argv, CONFIG_ENABLECMDLINEONLY);
uniqCfg = cfg1;
server_config.tx_num_channels = p.gnb_tx;
server_config.rx_num_channels = p.gnb_rx;
server_config.sample_rate = 30.72e6;
ASSERT_EQ(device_init(&server_device, &server_config), 0);
ASSERT_EQ(server_device.trx_start_func(&server_device), 0);
}
// Setup Client
{
const char *c_argv[] = {"test_vrtsim_cirdb",
"--vrtsim.role",
"client",
"--vrtsim.shm_channel_name",
shm_name.c_str(),
"--vrtsim.cirdb",
"0",
"--vrtsim.ue_config.[0].antennas",
p.ue_ant_str.c_str()};
cfg2 = load_configmodule(sizeof(c_argv) / sizeof(char *), (char **)c_argv, CONFIG_ENABLECMDLINEONLY);
uniqCfg = cfg2;
client_config.tx_num_channels = p.ue_tx;
client_config.rx_num_channels = p.ue_rx;
client_config.sample_rate = 30.72e6;
ASSERT_EQ(device_init(&client_device, &client_config), 0);
ASSERT_EQ(client_device.trx_start_func(&client_device), 0);
}
const int nsamps = 1024;
std::vector<std::vector<c16_t>> tx_chan_samples(p.gnb_tx, std::vector<c16_t>(nsamps));
std::vector<void *> tx_ptrs(p.gnb_tx);
for (int ch = 0; ch < p.gnb_tx; ch++) {
for (int i = 0; i < nsamps; i++)
tx_chan_samples[ch][i] = {(int16_t)(i + 1 + ch * 100), (int16_t) - (i + 1 + ch * 100)};
tx_ptrs[ch] = tx_chan_samples[ch].data();
}
ASSERT_EQ(server_device.trx_write_func(&server_device, 0, tx_ptrs.data(), nsamps, p.gnb_tx, 0), nsamps);
vrtsim_produce_samples(&server_device, nsamps);
std::vector<std::vector<c16_t>> rx_chan_samples(p.ue_rx, std::vector<c16_t>(nsamps));
std::vector<void *> rx_ptrs(p.ue_rx);
for (int ch = 0; ch < p.ue_rx; ch++)
rx_ptrs[ch] = rx_chan_samples[ch].data();
openair0_timestamp_t rx_ts;
ASSERT_EQ(client_device.trx_read_func(&client_device, &rx_ts, rx_ptrs.data(), nsamps, p.ue_rx), nsamps);
for (int rx_ch = 0; rx_ch < p.ue_rx; rx_ch++) {
for (int i = 7; i < nsamps; i++) {
int32_t expected_r = 0;
for (int tx_ch = 0; tx_ch < p.gnb_tx; tx_ch++)
expected_r += tx_chan_samples[tx_ch][i - 7].r;
EXPECT_EQ(rx_chan_samples[rx_ch][i].r, (int16_t)expected_r);
}
}
}
TEST_P(VRTSTapsCIRDBTest, CIRDBDelayUL)
{
const CIRDBAntParams &p = GetParam();
// Setup Server
{
const char *s_argv[] = {"test_vrtsim_cirdb",
"--vrtsim.role",
"server",
"--vrtsim.shm_channel_name",
shm_name.c_str(),
"--vrtsim.disable-timing-thread",
"1",
"--vrtsim.cirdb",
"0",
"--vrtsim.ue_config.[0].antennas",
p.ue_ant_str.c_str()};
cfg1 = load_configmodule(sizeof(s_argv) / sizeof(char *), (char **)s_argv, CONFIG_ENABLECMDLINEONLY);
uniqCfg = cfg1;
server_config.tx_num_channels = p.gnb_tx;
server_config.rx_num_channels = p.gnb_rx;
server_config.sample_rate = 30.72e6;
ASSERT_EQ(device_init(&server_device, &server_config), 0);
ASSERT_EQ(server_device.trx_start_func(&server_device), 0);
}
// Setup Client
{
const char *c_argv[] = {"test_vrtsim_cirdb",
"--vrtsim.role",
"client",
"--vrtsim.shm_channel_name",
shm_name.c_str(),
"--vrtsim.cirdb",
"1",
"--vrtsim.cirdb-path",
tmp_dir.c_str(),
"--vrtsim.cirdb_model_id",
"1",
"--vrtsim.ue_config.[0].antennas",
p.ue_ant_str.c_str()};
cfg2 = load_configmodule(sizeof(c_argv) / sizeof(char *), (char **)c_argv, CONFIG_ENABLECMDLINEONLY);
uniqCfg = cfg2;
client_config.tx_num_channels = p.ue_tx;
client_config.rx_num_channels = p.ue_rx;
client_config.sample_rate = 30.72e6;
ASSERT_EQ(device_init(&client_device, &client_config), 0);
ASSERT_EQ(client_device.trx_start_func(&client_device), 0);
}
const int nsamps = 1024;
std::vector<std::vector<c16_t>> tx_chan_samples(p.ue_tx, std::vector<c16_t>(nsamps));
std::vector<void *> tx_ptrs(p.ue_tx);
for (int ch = 0; ch < p.ue_tx; ch++) {
for (int i = 0; i < nsamps; i++)
tx_chan_samples[ch][i] = {(int16_t)(i + 1 + ch * 100), (int16_t) - (i + 1 + ch * 100)};
tx_ptrs[ch] = tx_chan_samples[ch].data();
}
ASSERT_EQ(client_device.trx_write_func(&client_device, 0, tx_ptrs.data(), nsamps, p.ue_tx, 0), nsamps);
vrtsim_produce_samples(&server_device, nsamps);
std::vector<std::vector<c16_t>> rx_chan_samples(p.gnb_rx, std::vector<c16_t>(nsamps));
std::vector<void *> rx_ptrs(p.gnb_rx);
for (int ch = 0; ch < p.gnb_rx; ch++)
rx_ptrs[ch] = rx_chan_samples[ch].data();
openair0_timestamp_t rx_ts;
ASSERT_EQ(server_device.trx_read_func(&server_device, &rx_ts, rx_ptrs.data(), nsamps, p.gnb_rx), nsamps);
for (int rx_ch = 0; rx_ch < p.gnb_rx; rx_ch++) {
for (int i = 7; i < nsamps; i++) {
int32_t expected_r = 0;
for (int tx_ch = 0; tx_ch < p.ue_tx; tx_ch++)
expected_r += tx_chan_samples[tx_ch][i - 7].r;
EXPECT_EQ(rx_chan_samples[rx_ch][i].r, (int16_t)expected_r);
}
}
}
INSTANTIATE_TEST_SUITE_P(AntennaVariations,
VRTSTapsCIRDBTest,
::testing::Values(CIRDBAntParams(1, 1, 1, 1, "1x1"),
CIRDBAntParams(2, 2, 1, 1, "1x1"),
CIRDBAntParams(4, 4, 2, 2, "2x2"),
CIRDBAntParams(8, 8, 2, 2, "2x2")));
int main(int argc, char **argv)
{
logInit();
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,344 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "common_lib.h"
#include <gtest/gtest.h>
#include "common/config/config_userapi.h"
#include <thread>
#include <vector>
#include <complex>
#include <mutex>
#include <string>
#include <algorithm>
configmodule_interface_t *uniqCfg = NULL;
extern "C" {
#include "common/config/config_userapi.h"
#include "openair1/SIMULATION/TOOLS/sim.h"
extern int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg);
extern void vrtsim_produce_samples(openair0_device_t *device, size_t num_samples);
static softmodem_params_t softmodem_params;
softmodem_params_t *get_softmodem_params(void)
{
return &softmodem_params;
}
void exit_function(const char *file, const char *function, const int line, const char *s, const int assert)
{
fprintf(stderr, "FATAL: %s at %s:%s:%d\n", s, file, function, line);
exit(EXIT_FAILURE);
}
}
struct mock_taps_client_t {
int num_tx_ant;
int num_rx_ant;
std::string socket_path;
struct TapsData {
uint32_t tx;
uint32_t rx;
uint32_t len;
std::vector<std::complex<float>> taps;
channel_desc_t desc;
std::vector<struct complexf *> ch_ps_ptrs;
bool has_data = false;
} data[4];
std::mutex mutex;
};
static std::mutex g_registry_mutex;
static std::vector<mock_taps_client_t *> g_mock_clients;
static void register_mock_client(mock_taps_client_t *client)
{
std::lock_guard<std::mutex> lock(g_registry_mutex);
g_mock_clients.push_back(client);
}
static void unregister_mock_client(mock_taps_client_t *client)
{
std::lock_guard<std::mutex> lock(g_registry_mutex);
auto it = std::find(g_mock_clients.begin(), g_mock_clients.end(), client);
if (it != g_mock_clients.end()) {
g_mock_clients.erase(it);
}
}
extern "C" {
void *taps_client_connect(const char *socket_path, int num_tx_ant, int num_rx_ant)
{
auto *client = new mock_taps_client_t();
client->socket_path = socket_path;
client->num_tx_ant = num_tx_ant;
client->num_rx_ant = num_rx_ant;
register_mock_client(client);
return client;
}
void taps_client_stop(void *handle)
{
auto *client = static_cast<mock_taps_client_t *>(handle);
unregister_mock_client(client);
delete client;
}
channel_desc_t *taps_client_get_model(void *handle, int id)
{
auto *client = static_cast<mock_taps_client_t *>(handle);
std::lock_guard<std::mutex> lock(client->mutex);
if (id < 0 || id >= 4)
return nullptr;
if (!client->data[id].has_data)
return nullptr;
return &client->data[id].desc;
}
}
class TapsProducer {
std::string url_;
public:
TapsProducer(const char *url) : url_(url)
{
}
~TapsProducer()
{
}
void send_taps(uint32_t id, uint32_t tx, uint32_t rx, uint32_t len, const std::vector<std::complex<float>> &taps)
{
std::lock_guard<std::mutex> lock(g_registry_mutex);
for (auto *client : g_mock_clients) {
if (client->socket_path == url_) {
std::lock_guard<std::mutex> client_lock(client->mutex);
auto &d = client->data[id];
d.tx = tx;
d.rx = rx;
d.len = len;
d.taps = taps;
d.ch_ps_ptrs.resize(tx * rx);
for (uint32_t aarx = 0; aarx < rx; aarx++) {
for (uint32_t aatx = 0; aatx < tx; aatx++) {
d.ch_ps_ptrs[aarx + rx * aatx] = (struct complexf *)&d.taps[(aarx + rx * aatx) * len];
}
}
d.desc.nb_rx = rx;
d.desc.nb_tx = tx;
d.desc.nb_taps = len;
d.desc.channel_length = len;
d.desc.path_loss_dB = 0;
d.desc.ch_ps = (struct complexf **)d.ch_ps_ptrs.data();
d.has_data = true;
break;
}
}
}
};
struct TapsAntParams {
int gnb_tx;
int gnb_rx;
int ue_tx;
int ue_rx;
std::string ue_ant_str;
TapsAntParams(int gt, int gr, int ut, int ur, std::string s) : gnb_tx(gt), gnb_rx(gr), ue_tx(ut), ue_rx(ur), ue_ant_str(s)
{
}
};
class VRTSTapsTest : public ::testing::TestWithParam<TapsAntParams> {
protected:
configmodule_interface_t *cfg1 = nullptr;
configmodule_interface_t *cfg2 = nullptr;
openair0_device_t server_device = {0};
openair0_device_t client_device = {0};
openair0_config_t server_config = {0};
openair0_config_t client_config = {0};
std::string server_taps_url;
std::string client_taps_url;
std::string descriptor_path;
void SetUp() override
{
auto p = GetParam();
std::string tag = std::to_string(p.gnb_tx) + "_" + p.ue_ant_str;
server_taps_url = "ipc:///tmp/server_taps_" + tag + ".ipc";
client_taps_url = "ipc:///tmp/client_taps_" + tag + ".ipc";
descriptor_path = "/tmp/vrtsim_connection_" + tag;
std::string shm_name = "shm_taps_" + tag;
// Setup server
const char *server_argv[] = {"--vrtsim.role",
"server",
"--vrtsim.shm_channel_name",
shm_name.c_str(),
"--vrtsim.disable-timing-thread",
"1",
"--vrtsim.taps-socket",
server_taps_url.c_str(),
"--vrtsim.ue_config.[0].antennas",
p.ue_ant_str.c_str(),
"--vrtsim.connection_descriptor",
descriptor_path.c_str()};
cfg1 = load_configmodule(sizeof(server_argv) / sizeof(char *), (char **)server_argv, CONFIG_ENABLECMDLINEONLY);
uniqCfg = cfg1;
server_config.tx_num_channels = p.gnb_tx;
server_config.rx_num_channels = p.gnb_rx;
server_config.sample_rate = 30.72e6;
ASSERT_EQ(device_init(&server_device, &server_config), 0);
ASSERT_EQ(server_device.trx_start_func(&server_device), 0);
// Setup client
const char *client_argv[] = {"--vrtsim.role",
"client",
"--vrtsim.shm_channel_name",
shm_name.c_str(),
"--vrtsim.taps-socket",
client_taps_url.c_str(),
"--vrtsim.connection_descriptor",
descriptor_path.c_str()};
cfg2 = load_configmodule(sizeof(client_argv) / sizeof(char *), (char **)client_argv, CONFIG_ENABLECMDLINEONLY);
uniqCfg = cfg2;
client_config.tx_num_channels = p.ue_tx;
client_config.rx_num_channels = p.ue_rx;
client_config.sample_rate = 30.72e6;
ASSERT_EQ(device_init(&client_device, &client_config), 0);
ASSERT_EQ(client_device.trx_start_func(&client_device), 0);
}
void TearDown() override
{
if (server_device.trx_end_func)
server_device.trx_end_func(&server_device);
if (client_device.trx_end_func)
client_device.trx_end_func(&client_device);
if (cfg1)
end_configmodule(cfg1);
if (cfg2)
end_configmodule(cfg2);
}
};
TEST_P(VRTSTapsTest, TapsDLSum)
{
auto p = GetParam();
TapsProducer server_producer(server_taps_url.c_str());
const uint32_t L = 16;
// DL (gNB -> UE): nb_tx = gnb_tx, nb_rx = ue_rx.
std::vector<std::complex<float>> taps(p.gnb_tx * p.ue_rx * L, {0.0f, 0.0f});
// All TX channels contribute to RX channel 0 with no delay (tap 0 = 1.0)
for (int tx = 0; tx < p.gnb_tx; tx++) {
int link = (0 + p.ue_rx * tx); // rx=0, num_rx=ue_rx
taps[link * L + 0] = {1.0f, 0.0f};
}
server_producer.send_taps(0, p.gnb_tx, p.ue_rx, L, taps);
const int nsamps = 1024;
std::vector<std::vector<c16_t>> tx_chan_samples(p.gnb_tx, std::vector<c16_t>(nsamps));
std::vector<void *> tx_ptrs(p.gnb_tx);
for (int ch = 0; ch < p.gnb_tx; ch++) {
for (int i = 0; i < nsamps; i++) {
tx_chan_samples[ch][i] = {(int16_t)(i + 1 + ch * 100), (int16_t) - (i + 1 + ch * 100)};
}
tx_ptrs[ch] = tx_chan_samples[ch].data();
}
// Write DL samples
ASSERT_EQ(server_device.trx_write_func(&server_device, 0, tx_ptrs.data(), nsamps, p.gnb_tx, 0), nsamps);
vrtsim_produce_samples(&server_device, nsamps);
// Read on client (DL)
std::vector<std::vector<c16_t>> rx_chan_samples(p.ue_rx, std::vector<c16_t>(nsamps));
std::vector<void *> rx_ptrs(p.ue_rx);
for (int ch = 0; ch < p.ue_rx; ch++)
rx_ptrs[ch] = rx_chan_samples[ch].data();
openair0_timestamp_t rx_ts;
ASSERT_EQ(client_device.trx_read_func(&client_device, &rx_ts, rx_ptrs.data(), nsamps, p.ue_rx), nsamps);
// Verify DL Sum for rx_ch 0
for (int i = 0; i < nsamps; i++) {
int32_t expected_r = 0;
int32_t expected_i = 0;
for (int tx_ch = 0; tx_ch < p.gnb_tx; tx_ch++) {
expected_r += tx_chan_samples[tx_ch][i].r;
expected_i += tx_chan_samples[tx_ch][i].i;
}
EXPECT_EQ(rx_chan_samples[0][i].r, (int16_t)expected_r) << "DL Sum Mismatch at rx_ch 0 index " << i;
EXPECT_EQ(rx_chan_samples[0][i].i, (int16_t)expected_i) << "DL Sum Mismatch at rx_ch 0 index " << i;
}
}
TEST_P(VRTSTapsTest, TapsULDelay)
{
auto p = GetParam();
TapsProducer client_producer(client_taps_url.c_str());
const uint32_t L = 16;
// UL (UE -> gNB): nb_tx = ue_tx, nb_rx = gnb_rx.
std::vector<std::complex<float>> taps(p.ue_tx * p.gnb_rx * L, {0.0f, 0.0f});
// UE TX 0 -> gNB RX 0 (no delay)
// UE TX 0 -> gNB RX 1 (delayed L-1) if gnb_rx > 1
taps[(0 + p.gnb_rx * 0) * L + 0] = {1.0f, 0.0f};
if (p.gnb_rx > 1) {
taps[(1 + p.gnb_rx * 0) * L + (L - 1)] = {1.0f, 0.0f};
}
client_producer.send_taps(0, p.ue_tx, p.gnb_rx, L, taps);
const int nsamps = 1024;
std::vector<std::vector<c16_t>> tx_chan_samples(p.ue_tx, std::vector<c16_t>(nsamps));
std::vector<void *> tx_ptrs(p.ue_tx);
for (int ch = 0; ch < p.ue_tx; ch++) {
for (int i = 0; i < nsamps; i++) {
tx_chan_samples[ch][i] = {(int16_t)(i + 500), (int16_t) - (i + 500)};
}
tx_ptrs[ch] = tx_chan_samples[ch].data();
}
// Write UL samples from client
ASSERT_EQ(client_device.trx_write_func(&client_device, 0, tx_ptrs.data(), nsamps, p.ue_tx, 0), nsamps);
vrtsim_produce_samples(&server_device, nsamps);
// Read on server (UL)
std::vector<std::vector<c16_t>> rx_chan_samples(p.gnb_rx, std::vector<c16_t>(nsamps));
std::vector<void *> rx_ptrs(p.gnb_rx);
for (int ch = 0; ch < p.gnb_rx; ch++)
rx_ptrs[ch] = rx_chan_samples[ch].data();
openair0_timestamp_t rx_ts;
ASSERT_EQ(server_device.trx_read_func(&server_device, &rx_ts, rx_ptrs.data(), nsamps, p.gnb_rx), nsamps);
// Verify UL RX 0 (no delay)
for (int i = 0; i < nsamps; i++) {
EXPECT_EQ(rx_chan_samples[0][i].r, tx_chan_samples[0][i].r) << "UL RX 0 Mismatch at " << i;
}
// Verify UL RX 1 (delayed) if present
if (p.gnb_rx > 1) {
for (int i = L - 1; i < nsamps; i++) {
EXPECT_EQ(rx_chan_samples[1][i].r, tx_chan_samples[0][i - (L - 1)].r) << "UL RX 1 Delay Mismatch at " << i;
}
}
}
INSTANTIATE_TEST_SUITE_P(AntennaVariations,
VRTSTapsTest,
::testing::Values(TapsAntParams(1, 1, 1, 1, "1x1"),
TapsAntParams(2, 2, 2, 2, "2x2"),
TapsAntParams(8, 8, 1, 1, "1x1"),
TapsAntParams(8, 8, 2, 2, "2x2")));
int main(int argc, char **argv)
{
logInit();
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -41,7 +41,7 @@
// Simulator role
typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
#define MAX_NUM_ANTENNAS_TX 4
#define MAX_NUM_ANTENNAS_TX 8
#define SAVED_SAMPLES_LEN 256
#define MAX_NUM_UES MAX_MOBILES_PER_GNB
@@ -66,7 +66,6 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
{"timescale", TIME_SCALE_HLP, 0, .dblptr = &vrtsim_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \
{"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}, \
@@ -79,7 +78,9 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role;
{"cirdb_aoa_deg", "Desired AoA in degrees (TDL-D/E only)", 0, .dblptr = &vrtsim_state->cirdb_aoa_deg, .defdblval = 0.0, TYPE_DOUBLE, 0}, \
{"num_ues", "Number of UE slots (server only)\n", 0, .iptr = &vrtsim_state->num_ues, .defintval = 1, TYPE_INT, 0}, \
{"ue_id", "UE slot index 0..num_ues-1 (client only)\n", 0, .iptr = &vrtsim_state->ue_id, .defintval = 0, TYPE_INT, 0}, \
{"thread-pool", TPOOL_HLP, .strptr = &vrtsim_state->thread_pool_cores, .defstrval = "-1,-1,-1,-1", TYPE_STRING, 0} \
{"thread-pool", TPOOL_HLP, .strptr = &vrtsim_state->thread_pool_cores, .defstrval = "-1,-1,-1,-1", TYPE_STRING, 0}, \
{"shm_channel_name", "Shared memory channel name\n", 0, .strptr = &vrtsim_state->shm_channel_name, .defstrval = DEFAULT_CHANNEL_NAME, TYPE_STRING, 0}, \
{"disable-timing-thread", "Disable timing thread for testing", 0, .iptr = &vrtsim_state->disable_timing_thread, .defintval = 0, TYPE_INT, 0} \
};
// clang-format on
@@ -90,11 +91,6 @@ typedef struct histogram_s {
double range;
} histogram_t;
// Information about the peer
typedef struct peer_info_s {
int num_rx_antennas;
} peer_info_t;
typedef struct tx_timing_s {
uint64_t tx_samples_late;
uint64_t tx_early;
@@ -132,7 +128,6 @@ typedef struct {
uint64_t rx_samples_total;
tx_timing_t tx_timing;
histogram_t chanmod_histogram;
peer_info_t peer_info;
int chanmod;
double rx_freq;
double tx_bw;
@@ -140,7 +135,9 @@ typedef struct {
int rx_num_channels;
channel_desc_t *channel_desc[MAX_NUM_UES];
char *taps_socket;
int client_num_rx_antennas;
void *taps_client;
int peer_tx_ant;
int peer_rx_ant;
struct timespec start_ts;
/* CIR DB state */
int use_cirdb;
@@ -161,6 +158,8 @@ typedef struct {
tpool_t tpool;
void *channel_pipeline_context;
char *thread_pool_cores;
char *shm_channel_name;
int disable_timing_thread;
} vrtsim_state_t;
static void histogram_add(histogram_t *histogram, double diff)
@@ -183,19 +182,13 @@ static void histogram_print(histogram_t *histogram, char *title)
}
}
static void load_channel_model(vrtsim_state_t *vrtsim_state)
static void load_channel_model(vrtsim_state_t *vrtsim_state, int nb_rx)
{
load_channellist(vrtsim_state->tx_num_channels,
vrtsim_state->peer_info.num_rx_antennas,
vrtsim_state->sample_rate,
vrtsim_state->rx_freq,
vrtsim_state->tx_bw);
load_channellist(vrtsim_state->tx_num_channels, nb_rx, vrtsim_state->sample_rate, vrtsim_state->rx_freq, vrtsim_state->tx_bw);
char *model_name = vrtsim_state->role == ROLE_CLIENT ? "client_tx_channel_model" : "server_tx_channel_model";
vrtsim_state->channel_desc[0] = find_channel_desc_fromname(model_name);
channel_desc_t *channel_desc = vrtsim_state->channel_desc[0];
AssertFatal(channel_desc != NULL,
"Could not find model name %s. Make sure it is present in the config file\n",
model_name);
AssertFatal(channel_desc != NULL, "Could not find model name %s. Make sure it is present in the config file\n", model_name);
LOG_A(HW,
"Channel model %s parameters: path_loss_dB=%.2f, nb_tx=%d, nb_rx=%d, channel_length=%d\n",
model_name,
@@ -316,7 +309,9 @@ static client_info_t client_read_info(char *descriptor_file)
static void parse_ue_config(vrtsim_state_t *vrtsim_state)
{
AssertFatal(vrtsim_state->num_ues > 0 && vrtsim_state->num_ues <= MAX_NUM_UES,
"num_ues=%d out of range (1..%d)\n", vrtsim_state->num_ues, MAX_NUM_UES);
"num_ues=%d out of range (1..%d)\n",
vrtsim_state->num_ues,
MAX_NUM_UES);
for (int i = 0; i < vrtsim_state->num_ues; i++) {
char prefix[64];
@@ -329,11 +324,17 @@ static void parse_ue_config(vrtsim_state_t *vrtsim_state)
double aoa_deg = vrtsim_state->cirdb_aoa_deg;
paramdef_t ue_params[] = {
{"antennas", "Antenna config e.g. \"1x2\"", 0, .strptr = &antennas, .defstrval = NULL, TYPE_STRING, 0},
{"model_id", "TDL model id 0..4", 0, .iptr = &model_id, .defintval = vrtsim_state->cirdb_model_id, TYPE_INT, 0},
{"ds_ns", "Delay spread in ns", 0, .dblptr = &ds_ns, .defdblval = vrtsim_state->cirdb_ds_ns, TYPE_DOUBLE, 0},
{"speed_mps", "Speed in m/s", 0, .dblptr = &speed_mps, .defdblval = vrtsim_state->cirdb_speed_mps, TYPE_DOUBLE, 0},
{"aoa_deg", "LOS AoA in degrees (TDL-D/E only)", 0, .dblptr = &aoa_deg, .defdblval = vrtsim_state->cirdb_aoa_deg, TYPE_DOUBLE, 0},
{"antennas", "Antenna config e.g. \"1x2\"", 0, .strptr = &antennas, .defstrval = NULL, TYPE_STRING, 0},
{"model_id", "TDL model id 0..4", 0, .iptr = &model_id, .defintval = vrtsim_state->cirdb_model_id, TYPE_INT, 0},
{"ds_ns", "Delay spread in ns", 0, .dblptr = &ds_ns, .defdblval = vrtsim_state->cirdb_ds_ns, TYPE_DOUBLE, 0},
{"speed_mps", "Speed in m/s", 0, .dblptr = &speed_mps, .defdblval = vrtsim_state->cirdb_speed_mps, TYPE_DOUBLE, 0},
{"aoa_deg",
"LOS AoA in degrees (TDL-D/E only)",
0,
.dblptr = &aoa_deg,
.defdblval = vrtsim_state->cirdb_aoa_deg,
TYPE_DOUBLE,
0},
};
config_get(config_get_if(), ue_params, sizeofArray(ue_params), prefix);
@@ -341,35 +342,37 @@ static void parse_ue_config(vrtsim_state_t *vrtsim_state)
if (antennas != NULL) {
int tx_ant, rx_ant;
AssertFatal(sscanf(antennas, "%dx%d", &tx_ant, &rx_ant) == 2,
"Invalid antenna format '%s' for UE %d, use e.g. '1x2'\n", antennas, i);
AssertFatal(tx_ant > 0 && tx_ant <= MAX_NUM_ANTENNAS_TX,
"Invalid TX antenna count %d for UE %d\n", tx_ant, i);
AssertFatal(rx_ant > 0 && rx_ant <= MAX_NUM_ANTENNAS_TX,
"Invalid RX antenna count %d for UE %d\n", rx_ant, i);
"Invalid antenna format '%s' for UE %d, use e.g. '1x2'\n",
antennas,
i);
AssertFatal(tx_ant > 0 && tx_ant <= MAX_NUM_ANTENNAS_TX, "Invalid TX antenna count %d for UE %d\n", tx_ant, i);
AssertFatal(rx_ant > 0 && rx_ant <= MAX_NUM_ANTENNAS_TX, "Invalid RX antenna count %d for UE %d\n", rx_ant, i);
vrtsim_state->ue_conf[i].tx_ant = tx_ant;
vrtsim_state->ue_conf[i].rx_ant = rx_ant;
vrtsim_state->ue_conf[i].rx_ant = rx_ant;
} else {
vrtsim_state->ue_conf[i].tx_ant = 1;
vrtsim_state->ue_conf[i].rx_ant = 1;
}
AssertFatal(model_id >= 0 && model_id <= 4,
"Invalid model_id %d for UE %d (must be 0-4)\n", model_id, i);
AssertFatal(ds_ns > 0,
"Invalid ds_ns %.1f for UE %d (must be > 0)\n", ds_ns, i);
AssertFatal(speed_mps >= 0,
"Invalid speed_mps %.1f for UE %d (must be >= 0)\n", speed_mps, i);
AssertFatal(model_id >= 0 && model_id <= 4, "Invalid model_id %d for UE %d (must be 0-4)\n", model_id, i);
AssertFatal(ds_ns > 0, "Invalid ds_ns %.1f for UE %d (must be > 0)\n", ds_ns, i);
AssertFatal(speed_mps >= 0, "Invalid speed_mps %.1f for UE %d (must be >= 0)\n", speed_mps, i);
vrtsim_state->ue_conf[i].cir_conf.model_id = model_id;
vrtsim_state->ue_conf[i].cir_conf.ds_ns = ds_ns;
vrtsim_state->ue_conf[i].cir_conf.model_id = model_id;
vrtsim_state->ue_conf[i].cir_conf.ds_ns = ds_ns;
vrtsim_state->ue_conf[i].cir_conf.speed_mps = speed_mps;
vrtsim_state->ue_conf[i].cir_conf.aoa_deg = aoa_deg;
vrtsim_state->ue_conf[i].cir_conf.aoa_deg = aoa_deg;
LOG_I(HW, "VRTSIM: UE %d configuration: UE_tx=%d UE_rx=%d, Model %d (TDL-%c), DS %.1fns, Speed %.1fm/s, AoA %.1fdeg\n",
i,
vrtsim_state->ue_conf[i].tx_ant,
vrtsim_state->ue_conf[i].rx_ant,
model_id, 'A' + model_id, ds_ns, speed_mps, aoa_deg);
LOG_I(HW,
"VRTSIM: UE %d configuration: UE_tx=%d UE_rx=%d, Model %d (TDL-%c), DS %.1fns, Speed %.1fm/s, AoA %.1fdeg\n",
i,
vrtsim_state->ue_conf[i].tx_ant,
vrtsim_state->ue_conf[i].rx_ant,
model_id,
'A' + model_id,
ds_ns,
speed_mps,
aoa_deg);
}
}
@@ -408,9 +411,12 @@ static int vrtsim_connect(openair0_device_t *device)
for (int i = 0; i < vrtsim_state->num_ues; i++) {
num_tx_streams += vrtsim_state->ue_conf[i].rx_ant;
}
vrtsim_state->channel =
shm_td_iq_channel_create(DEFAULT_CHANNEL_NAME, num_tx_streams, num_rx_streams);
LOG_A(HW, "vrtsim created a shm_td_iq_channel with config tx: %d rx: %d\n", num_tx_streams, num_rx_streams);
vrtsim_state->channel = shm_td_iq_channel_create(vrtsim_state->shm_channel_name, num_tx_streams, num_rx_streams);
LOG_A(HW,
"vrtsim created a shm_td_iq_channel %s with config tx: %d rx: %d\n",
vrtsim_state->shm_channel_name,
num_tx_streams,
num_rx_streams);
// Exchange peer info
client_info_t client_info = {
@@ -423,10 +429,15 @@ static int vrtsim_connect(openair0_device_t *device)
client_info.ues[i] = vrtsim_state->ue_conf[i];
server_publish_client_info(client_info, vrtsim_state->connection_descriptor);
vrtsim_state->peer_info.num_rx_antennas = vrtsim_state->client_num_rx_antennas;
if (vrtsim_state->num_ues > 0) {
vrtsim_state->peer_tx_ant = vrtsim_state->ue_conf[0].tx_ant;
vrtsim_state->peer_rx_ant = vrtsim_state->ue_conf[0].rx_ant;
}
vrtsim_state->last_received_sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel);
vrtsim_state->run_timing_thread = true;
threadCreate(&vrtsim_state->timing_thread, vrtsim_timing_job, vrtsim_state, "vrtsim_timing", -1, OAI_PRIORITY_RT_MAX);
if (!vrtsim_state->disable_timing_thread) {
vrtsim_state->run_timing_thread = true;
threadCreate(&vrtsim_state->timing_thread, vrtsim_timing_job, vrtsim_state, "vrtsim_timing", -1, OAI_PRIORITY_RT_MAX);
}
} else {
client_info_t client_info = client_read_info(vrtsim_state->connection_descriptor);
AssertFatal(client_info.num_ues > 0, "Server did not publish valid num_ues\n");
@@ -459,7 +470,6 @@ static int vrtsim_connect(openair0_device_t *device)
vrtsim_state->cirdb_speed_mps = vrtsim_state->ue.cir_conf.speed_mps;
vrtsim_state->cirdb_aoa_deg = vrtsim_state->ue.cir_conf.aoa_deg;
LOG_I(HW,
"VRTSIM: UE %d channel - Model %d (TDL-%c), DS %.1fns, Speed %.1fm/s, AoA %.1fdeg\n",
vrtsim_state->ue_id,
@@ -469,20 +479,25 @@ static int vrtsim_connect(openair0_device_t *device)
vrtsim_state->cirdb_speed_mps,
vrtsim_state->cirdb_aoa_deg);
}
vrtsim_state->channel = shm_td_iq_channel_connect(DEFAULT_CHANNEL_NAME, 10);
vrtsim_state->peer_info.num_rx_antennas = client_info.gnb_num_rx_ant;
vrtsim_state->peer_tx_ant = client_info.gnb_num_tx_ant;
vrtsim_state->peer_rx_ant = client_info.gnb_num_rx_ant;
vrtsim_state->channel = shm_td_iq_channel_connect(vrtsim_state->shm_channel_name, 10);
vrtsim_state->last_received_sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel);
}
// Handle channel modelling after number of RX antennas are known
if (vrtsim_state->chanmod || vrtsim_state->taps_socket || vrtsim_state->use_cirdb) {
#ifdef OAI_VRTSIM_TAPS_CLIENT
if (vrtsim_state->taps_socket) {
taps_client_connect(0,
vrtsim_state->taps_socket,
device->openair0_cfg[0].tx_num_channels,
vrtsim_state->peer_info.num_rx_antennas,
&vrtsim_state->channel_desc[0]);
vrtsim_state->taps_client =
taps_client_connect(vrtsim_state->taps_socket, vrtsim_state->tx_num_channels, vrtsim_state->peer_rx_ant);
} else if (vrtsim_state->use_cirdb) {
#else
if (vrtsim_state->taps_socket) {
AssertFatal(false, "Build with OAI_VRTSIM_TAPS_CLIENT to use taps socket\n");
}
if (vrtsim_state->use_cirdb) {
#endif
const char *yaml_path = NULL;
const char *bin_path = NULL;
@@ -548,27 +563,36 @@ static int vrtsim_connect(openair0_device_t *device)
AssertFatal(cd != NULL, "CIRDB failed to create channel_desc for UE %d\n", u);
AssertFatal(cd->nb_tx == device->openair0_cfg[0].tx_num_channels,
"CIRDB shape mismatch UE%d: nb_tx=%d expected %d\n",
u, cd->nb_tx, device->openair0_cfg[0].tx_num_channels);
LOG_I(HW, "VRTSIM: UE %d channel_desc=%p ch_ps=%p ch=%p nb_tx=%d nb_rx=%d\n",
u, cd, cd->ch_ps, cd->ch, cd->nb_tx, cd->nb_rx);
LOG_I(HW, "VRTSIM: UE %d channel model configuration: channel model antenna dimension %dx%d (RU_tx x UE_rx), Model %d (TDL-%c), DS %.1fns, Speed %.1fm/s, AoA %.1fdeg\n",
u,
cd->nb_tx,
device->openair0_cfg[0].tx_num_channels);
LOG_I(HW,
"VRTSIM: UE %d channel_desc=%p ch_ps=%p ch=%p nb_tx=%d nb_rx=%d\n",
u,
cd,
cd->ch_ps,
cd->ch,
cd->nb_tx,
cd->nb_rx);
LOG_I(HW,
"VRTSIM: UE %d channel model configuration: channel model antenna dimension %dx%d (RU_tx x UE_rx), Model %d "
"(TDL-%c), DS %.1fns, Speed %.1fm/s, AoA %.1fdeg\n",
u,
device->openair0_cfg[0].tx_num_channels,
vrtsim_state->ue_conf[u].rx_ant,
ue_sel.want_model_id, 'A' + ue_sel.want_model_id,
ue_sel.want_ds_ns, ue_sel.want_speed_mps, ue_sel.want_aoa_deg);
ue_sel.want_model_id,
'A' + ue_sel.want_model_id,
ue_sel.want_ds_ns,
ue_sel.want_speed_mps,
ue_sel.want_aoa_deg);
}
LOG_A(HW, "VRTSIM: Multi-UE channel taps via CIR DB\n");
} else {
cirdb_connect(0,
device->openair0_cfg[0].tx_num_channels,
vrtsim_state->peer_info.num_rx_antennas,
&sel,
&vrtsim_state->channel_desc[0]);
cirdb_connect(0, device->openair0_cfg[0].tx_num_channels, vrtsim_state->peer_rx_ant, &sel, &vrtsim_state->channel_desc[0]);
LOG_A(HW, "VRTSIM: channel taps via CIR DB\n");
}
} else {
load_channel_model(vrtsim_state);
load_channel_model(vrtsim_state, vrtsim_state->peer_rx_ant);
}
}
vrtsim_state->tx_timing.tx_histogram.min_samples = 100;
@@ -579,11 +603,7 @@ static int vrtsim_connect(openair0_device_t *device)
return 0;
}
static int vrtsim_write_internal(vrtsim_state_t *vrtsim_state,
openair0_timestamp_t timestamp,
c16_t *samples,
int nsamps,
int aarx)
static int vrtsim_write_internal(vrtsim_state_t *vrtsim_state, openair0_timestamp_t timestamp, c16_t *samples, int nsamps, int aarx)
{
tx_timing_t *tx_timing = &vrtsim_state->tx_timing;
@@ -619,11 +639,6 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state,
cirdb_update(elapsed_ns);
}
if (!vrtsim_state->channel_desc[0]) {
LOG_E(HW, "No channel_desc found\n");
return nsamps;
}
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));
@@ -634,8 +649,19 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state,
int rx_antenna_offset = 0;
int nb_tx = nbAnt;
for (int i = 0; i < num_chan_desc; i++) {
channel_desc_t *chan_desc = vrtsim_state->channel_desc[i];
AssertFatal(chan_desc, "Channel not provided\n");
channel_desc_t *chan_desc = NULL;
#ifdef OAI_VRTSIM_TAPS_CLIENT
if (vrtsim_state->taps_client) {
chan_desc = taps_client_get_model(vrtsim_state->taps_client, i);
} else {
chan_desc = vrtsim_state->channel_desc[i];
}
#else
chan_desc = vrtsim_state->channel_desc[i];
#endif
if (!chan_desc) {
continue;
}
int nb_rx = chan_desc->nb_rx;
size_t channel_length = chan_desc->channel_length;
AssertFatal((channel_length - 1) < SAVED_SAMPLES_LEN,
@@ -681,7 +707,7 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state,
}
size_t saved_samples_input_len = channel_length - 1;
#ifdef CHANNEL_SIM_CUDA
#ifdef CHANNEL_SIM_CUDA
cuda_channel_pipeline(vrtsim_state->channel_pipeline_context,
(const cf_t **)channel_impulse_response_p,
(const c16_t **)saved_samples_ptr,
@@ -695,21 +721,21 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state,
nb_tx,
nb_rx,
noise_power);
#else
#else
channel_pipeline(&vrtsim_state->tpool,
(const cf_t **)channel_impulse_response_p,
(const c16_t **)saved_samples_ptr,
(const c16_t **)input_ptr,
saved_samples_input_len,
output_ptr,
NULL,
nsamps,
nsamps,
channel_length,
nb_tx,
nb_rx,
noise_power);
#endif
(const cf_t **)channel_impulse_response_p,
(const c16_t **)saved_samples_ptr,
(const c16_t **)input_ptr,
saved_samples_input_len,
output_ptr,
NULL,
nsamps,
nsamps,
channel_length,
nb_tx,
nb_rx,
noise_power);
#endif
for (int aarx = 0; aarx < nb_rx; aarx++) {
vrtsim_write_internal(vrtsim_state, timestamp, output[aarx], nsamps, rx_antenna_offset + aarx);
@@ -761,20 +787,22 @@ static int vrtsim_write(openair0_device_t *device,
histogram_add(&vrtsim_state->chanmod_histogram, microseconds);
return num_samples_processed;
}
// No channel model means there is no user-defined mapping between TX and RX antennas.
// We map the antennas in order: first TX stream is mapped to first RX stream and so on.
if (vrtsim_state->role == ROLE_CLIENT) {
for (int aatx = 0; aatx < nbAnt; aatx++) {
for (int aatx = 0; aatx < nbAnt && aatx < vrtsim_state->peer_rx_ant; aatx++) {
int global_ul_ant = vrtsim_state->ue.tx_offset + aatx;
vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[aatx], nsamps, global_ul_ant);
}
return nsamps;
} else {
AssertFatal(nbAnt == 1, "Multi-TX gNB not yet supported in multi-UE no-chanmod mode\n");
for (int u = 0; u < vrtsim_state->num_ues; u++) {
int rx_offset = vrtsim_state->ue_conf[u].rx_offset;
int num_rx_ant = vrtsim_state->ue_conf[u].rx_ant;
for (int aarx = 0; aarx < num_rx_ant; aarx++) {
for (int aarx = 0; aarx < num_rx_ant && aarx < nbAnt; aarx++) {
int global_dl_ant = rx_offset + aarx;
vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, global_dl_ant);
vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[aarx], nsamps, global_dl_ant);
}
}
return nsamps;
@@ -828,18 +856,14 @@ static int vrtsim_read(openair0_device_t *device, openair0_timestamp_t *ptimesta
for (int aarx = 0; aarx < nbAnt; aarx++) {
int stream = u * nbAnt + aarx;
sample_t buffer[nsamps];
int ret = shm_td_iq_channel_rx(vrtsim_state->channel,
vrtsim_state->last_received_sample,
nsamps,
stream,
buffer);
int ret = shm_td_iq_channel_rx(vrtsim_state->channel, vrtsim_state->last_received_sample, nsamps, stream, buffer);
if (ret == CHANNEL_ERROR_TOO_LATE) {
vrtsim_state->rx_samples_late += nsamps;
} else if (ret == CHANNEL_ERROR_TOO_EARLY) {
vrtsim_state->rx_early += 1;
}
int16_t *out = (int16_t *)samplesVoid[aarx];
int16_t *in = (int16_t *)buffer;
int16_t *in = (int16_t *)buffer;
for (int i = 0; i < nsamps * 2; i++) {
int32_t sum = (int32_t)out[i] + (int32_t)in[i];
out[i] = (int16_t)((sum > 32767) ? 32767 : (sum < -32768) ? -32768 : sum);
@@ -848,15 +872,15 @@ static int vrtsim_read(openair0_device_t *device, openair0_timestamp_t *ptimesta
}
} else {
/* Single-UE server UL read */
int ret = shm_td_iq_channel_rx(vrtsim_state->channel, vrtsim_state->last_received_sample, nsamps, 0, samplesVoid[0]);
if (ret == CHANNEL_ERROR_TOO_LATE) {
vrtsim_state->rx_samples_late += nsamps;
} else if (ret == CHANNEL_ERROR_TOO_EARLY) {
vrtsim_state->rx_early += 1;
}
for (int aarx = 1; aarx < nbAnt; aarx++) {
if (samplesVoid[aarx] != NULL)
memcpy(samplesVoid[aarx], samplesVoid[0], nsamps * sizeof(sample_t));
for (int aarx = 0; aarx < nbAnt; aarx++) {
int ret = shm_td_iq_channel_rx(vrtsim_state->channel, vrtsim_state->last_received_sample, nsamps, aarx, samplesVoid[aarx]);
if (aarx == 0) {
if (ret == CHANNEL_ERROR_TOO_LATE) {
vrtsim_state->rx_samples_late += nsamps;
} else if (ret == CHANNEL_ERROR_TOO_EARLY) {
vrtsim_state->rx_early += 1;
}
}
}
}
} else {
@@ -895,12 +919,14 @@ static void vrtsim_end(openair0_device_t *device)
#endif
if (vrtsim_state->use_cirdb) {
cirdb_stop();
} else if (vrtsim_state->taps_socket) {
taps_client_stop();
#ifdef OAI_VRTSIM_TAPS_CLIENT
} else if (vrtsim_state->taps_client) {
taps_client_stop(vrtsim_state->taps_client);
#endif
}
}
shm_td_iq_channel_abort(vrtsim_state->channel);
sleep(1);
usleep(1000);
shm_td_iq_channel_destroy(vrtsim_state->channel);
LOG_I(HW,
@@ -925,6 +951,8 @@ static void vrtsim_end(openair0_device_t *device)
LOG_A(HW, "Removed connection descriptor file %s\n", vrtsim_state->connection_descriptor);
}
}
free(device->priv);
device->priv = NULL;
}
static int vrtsim_stub(openair0_device_t *device)
@@ -954,6 +982,12 @@ static int vrtsim_set_beams2(openair0_device_t *device, int *beam_ids, int num_b
return 0;
}
__attribute__((__visibility__("default"))) void vrtsim_produce_samples(openair0_device_t *device, size_t num_samples)
{
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
shm_td_iq_channel_produce_samples(vrtsim_state->channel, num_samples);
}
__attribute__((__visibility__("default"))) int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg)
{
randominit();