diff --git a/CMakeLists.txt b/CMakeLists.txt index f0e80f9375..111ff06f59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/ci-scripts/docker/Dockerfile.unittest.ubuntu b/ci-scripts/docker/Dockerfile.unittest.ubuntu index 53ef95f3e6..dbe95f9c29 100644 --- a/ci-scripts/docker/Dockerfile.unittest.ubuntu +++ b/ci-scripts/docker/Dockerfile.unittest.ubuntu @@ -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 diff --git a/common/utils/shm_iq_channel/shm_td_iq_channel.c b/common/utils/shm_iq_channel/shm_td_iq_channel.c index fc93fb4bf0..25e031609e 100644 --- a/common/utils/shm_iq_channel/shm_td_iq_channel.c +++ b/common/utils/shm_iq_channel/shm_td_iq_channel.c @@ -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)); diff --git a/openair1/SIMULATION/TOOLS/random_channel.c b/openair1/SIMULATION/TOOLS/random_channel.c index 23c11d945c..daf9c4c52f 100644 --- a/openair1/SIMULATION/TOOLS/random_channel.c +++ b/openair1/SIMULATION/TOOLS/random_channel.c @@ -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); diff --git a/radio/vrtsim/CMakeLists.txt b/radio/vrtsim/CMakeLists.txt index 687bd13566..c71daafc8e 100644 --- a/radio/vrtsim/CMakeLists.txt +++ b/radio/vrtsim/CMakeLists.txt @@ -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() diff --git a/radio/vrtsim/taps_client.cpp b/radio/vrtsim/taps_client.cpp index c944576916..7d18b5efdd 100644 --- a/radio/vrtsim/taps_client.cpp +++ b/radio/vrtsim/taps_client.cpp @@ -3,184 +3,207 @@ */ #include #include -#include #include #include -#include "pthread.h" +#include #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 +#include +#include +#include +#include +#include +#include +#include -#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 should_run_; -typedef struct { - void *taps_msg; - channel_desc_t *channel_desc; -} taps_buffer_t; + // Raw message buffer pool + std::vector> buffer_storage_; + std::queue 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 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(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 *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 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 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 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 lock(mutex_); + free_buffers_.push(buf); + continue; + } + + std::lock_guard 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 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(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 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(handle)->get_model(id); +} + +extern "C" void taps_client_stop(void *handle) +{ + delete static_cast(handle); } diff --git a/radio/vrtsim/taps_client.h b/radio/vrtsim/taps_client.h index 2e7fd6e490..17181faa00 100644 --- a/radio/vrtsim/taps_client.h +++ b/radio/vrtsim/taps_client.h @@ -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 } diff --git a/radio/vrtsim/tests/CMakeLists.txt b/radio/vrtsim/tests/CMakeLists.txt new file mode 100644 index 0000000000..9bcc0ba7ec --- /dev/null +++ b/radio/vrtsim/tests/CMakeLists.txt @@ -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) diff --git a/radio/vrtsim/tests/test_vrtsim.cpp b/radio/vrtsim/tests/test_vrtsim.cpp new file mode 100644 index 0000000000..72132c54db --- /dev/null +++ b/radio/vrtsim/tests/test_vrtsim.cpp @@ -0,0 +1,173 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#include "common_lib.h" +#include +#include "common/config/config_userapi.h" +#include +#include +#include + +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 { + 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 ¶m = GetParam(); + + // Setup server + std::vector 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 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 ¶m = GetParam(); + const int nsamps = 1024; + + // Prepare TX samples for client (UL) + std::vector> client_tx_samples(param.client_tx, std::vector(nsamps)); + std::vector 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> server_tx_samples(param.server_tx, std::vector(nsamps)); + std::vector 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> server_rx_samples(param.server_rx, std::vector(nsamps)); + std::vector 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> client_rx_samples(param.client_rx, std::vector(nsamps)); + std::vector 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(); +} diff --git a/radio/vrtsim/tests/test_vrtsim_cirdb.cpp b/radio/vrtsim/tests/test_vrtsim_cirdb.cpp new file mode 100644 index 0000000000..d4533391d5 --- /dev/null +++ b/radio/vrtsim/tests/test_vrtsim_cirdb.cpp @@ -0,0 +1,309 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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() * e["n_rx"].as() * e["L"].as(); + std::vector data(count * 2, 0.0f); + for (int tx = 0; tx < e["n_tx"].as(); tx++) { + for (int rx = 0; rx < e["n_rx"].as(); rx++) { + int L = e["L"].as(); + data[((rx + tx * e["n_rx"].as()) * L + 7) * 2] = 1.0f; + } + } + bin.write(reinterpret_cast(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 { + 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> tx_chan_samples(p.gnb_tx, std::vector(nsamps)); + std::vector 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> rx_chan_samples(p.ue_rx, std::vector(nsamps)); + std::vector 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> tx_chan_samples(p.ue_tx, std::vector(nsamps)); + std::vector 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> rx_chan_samples(p.gnb_rx, std::vector(nsamps)); + std::vector 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(); +} diff --git a/radio/vrtsim/tests/test_vrtsim_taps.cpp b/radio/vrtsim/tests/test_vrtsim_taps.cpp new file mode 100644 index 0000000000..f763fc6ba8 --- /dev/null +++ b/radio/vrtsim/tests/test_vrtsim_taps.cpp @@ -0,0 +1,344 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#include "common_lib.h" +#include +#include "common/config/config_userapi.h" +#include +#include +#include +#include +#include +#include + +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> taps; + channel_desc_t desc; + std::vector ch_ps_ptrs; + bool has_data = false; + } data[4]; + std::mutex mutex; +}; + +static std::mutex g_registry_mutex; +static std::vector g_mock_clients; + +static void register_mock_client(mock_taps_client_t *client) +{ + std::lock_guard lock(g_registry_mutex); + g_mock_clients.push_back(client); +} + +static void unregister_mock_client(mock_taps_client_t *client) +{ + std::lock_guard 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(handle); + unregister_mock_client(client); + delete client; +} + +channel_desc_t *taps_client_get_model(void *handle, int id) +{ + auto *client = static_cast(handle); + std::lock_guard 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> &taps) + { + std::lock_guard lock(g_registry_mutex); + for (auto *client : g_mock_clients) { + if (client->socket_path == url_) { + std::lock_guard 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 { + 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> 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> tx_chan_samples(p.gnb_tx, std::vector(nsamps)); + std::vector 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> rx_chan_samples(p.ue_rx, std::vector(nsamps)); + std::vector 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> 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> tx_chan_samples(p.ue_tx, std::vector(nsamps)); + std::vector 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> rx_chan_samples(p.gnb_rx, std::vector(nsamps)); + std::vector 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(); +} diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index ac9e6d2cf9..c4bdc805b5 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -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();