This commit introduces ZMQ-based radio library. Each pair of RX/TX antennas is represented by a ZMQ REQ/REP socket pair which streams continuous IQ samples from radio start until stop. Usage: Simplest configuration is to connect OAI NR UE to OAI GNB with the same number of antennas - by inverting the RX and TX channels in ZMQ radio configuration the gNBs TX is mapped to UEs RX antennas and vice versa.
163 lines
4.5 KiB
C++
163 lines
4.5 KiB
C++
/*
|
|
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
|
*/
|
|
|
|
#include "common_lib.h"
|
|
#include <gtest/gtest.h>
|
|
#include "common/config/config_userapi.h"
|
|
#include <zmq.h>
|
|
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);
|
|
static softmodem_params_t softmodem_params;
|
|
softmodem_params_t *get_softmodem_params(void)
|
|
{
|
|
return &softmodem_params;
|
|
}
|
|
}
|
|
#include "common/platform_types.h"
|
|
#include <thread>
|
|
|
|
configmodule_interface_t *uniqCfg = NULL;
|
|
|
|
extern "C" 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 ZMQTest : public ::testing::Test {
|
|
protected:
|
|
configmodule_interface_t *cfg1 = nullptr;
|
|
configmodule_interface_t *cfg2 = nullptr;
|
|
openair0_device_t device1 = {0};
|
|
openair0_device_t device2 = {0};
|
|
openair0_config_t config1 = {0};
|
|
openair0_config_t config2 = {0};
|
|
char *argv[4];
|
|
|
|
void SetUp() override
|
|
{
|
|
argv[0] = strdup("--zmq.[0].tx_channels");
|
|
argv[1] = strdup("tcp://127.0.0.1:5555");
|
|
argv[2] = strdup("--zmq.[0].rx_channels");
|
|
argv[3] = strdup("tcp://127.0.0.1:5556");
|
|
|
|
cfg1 = load_configmodule(sizeofArray(argv), argv, CONFIG_ENABLECMDLINEONLY);
|
|
uniqCfg = cfg1;
|
|
// 2. Initialize the ZMQ device
|
|
config1.tx_num_channels = 1;
|
|
config1.rx_num_channels = 1;
|
|
config1.sample_rate = 30;
|
|
ASSERT_EQ(device_init(&device1, &config1), 0);
|
|
ASSERT_EQ(device1.trx_start_func(&device1), 0);
|
|
|
|
// Swap the RX with TX for second device
|
|
char *tmp = argv[0];
|
|
argv[0] = argv[2];
|
|
argv[2] = tmp;
|
|
cfg2 = load_configmodule(sizeofArray(argv), argv, CONFIG_ENABLECMDLINEONLY);
|
|
uniqCfg = cfg2;
|
|
config2.tx_num_channels = 1;
|
|
config2.rx_num_channels = 1;
|
|
config2.sample_rate = 1500;
|
|
ASSERT_EQ(device_init(&device2, &config2), 0);
|
|
ASSERT_EQ(device2.trx_start_func(&device2), 0);
|
|
}
|
|
|
|
void TearDown() override
|
|
{
|
|
if (device1.trx_end_func) {
|
|
device1.trx_end_func(&device1);
|
|
}
|
|
if (device2.trx_end_func) {
|
|
device2.trx_end_func(&device2);
|
|
}
|
|
if (cfg1) {
|
|
end_configmodule(cfg1);
|
|
}
|
|
if (cfg2) {
|
|
end_configmodule(cfg2);
|
|
}
|
|
for (auto i = 0U; i < sizeofArray(argv); i++) {
|
|
free(argv[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
TEST_F(ZMQTest, RXSamples)
|
|
{
|
|
std::thread t1([this]() {
|
|
c16_t rx_samples[10];
|
|
openair0_timestamp_t rx_timestamp;
|
|
void *samples[1] = {rx_samples};
|
|
ASSERT_EQ(device1.trx_read_func(&device1, &rx_timestamp, samples, 10, 1), 10);
|
|
for (int i = 0; i < 10; i++) {
|
|
ASSERT_EQ(rx_samples[i].r, 0);
|
|
ASSERT_EQ(rx_samples[i].i, 0);
|
|
}
|
|
});
|
|
std::thread t2([this]() {
|
|
c16_t rx_samples[10];
|
|
openair0_timestamp_t rx_timestamp;
|
|
void *samples[1] = {rx_samples};
|
|
ASSERT_EQ(device2.trx_read_func(&device2, &rx_timestamp, samples, 10, 1), 10);
|
|
for (int i = 0; i < 10; i++) {
|
|
ASSERT_EQ(rx_samples[i].r, 0);
|
|
ASSERT_EQ(rx_samples[i].i, 0);
|
|
}
|
|
});
|
|
t1.join();
|
|
t2.join();
|
|
}
|
|
|
|
TEST_F(ZMQTest, TxRxSamples)
|
|
{
|
|
std::thread t1([this]() {
|
|
c16_t rx_samples[10];
|
|
openair0_timestamp_t rx_timestamp;
|
|
void *samples[1] = {rx_samples};
|
|
ASSERT_EQ(device1.trx_read_func(&device1, &rx_timestamp, samples, 10, 1), 10);
|
|
for (int i = 0; i < 10; i++) {
|
|
ASSERT_EQ(rx_samples[i].r, 0);
|
|
ASSERT_EQ(rx_samples[i].i, 0);
|
|
}
|
|
c16_t tx_samples[10];
|
|
openair0_timestamp_t tx_timestamp = rx_timestamp + 10;
|
|
for (int i = 0; i < 10; i++) {
|
|
tx_samples[i].r = i;
|
|
tx_samples[i].i = i + 1;
|
|
}
|
|
samples[0] = tx_samples;
|
|
ASSERT_EQ(device1.trx_write_func(&device1, tx_timestamp, samples, 10, 1, 0), 10);
|
|
});
|
|
std::thread t2([this]() {
|
|
c16_t rx_samples[10];
|
|
openair0_timestamp_t rx_timestamp;
|
|
void *samples[1] = {rx_samples};
|
|
ASSERT_EQ(device2.trx_read_func(&device2, &rx_timestamp, samples, 10, 1), 10);
|
|
for (int i = 0; i < 10; i++) {
|
|
ASSERT_EQ(rx_samples[i].r, 0);
|
|
ASSERT_EQ(rx_samples[i].i, 0);
|
|
}
|
|
openair0_timestamp_t rx_timestamp2;
|
|
ASSERT_EQ(device2.trx_read_func(&device2, &rx_timestamp2, samples, 10, 1), 10);
|
|
for (int i = 0; i < 10; i++) {
|
|
ASSERT_EQ(rx_samples[i].r, i);
|
|
ASSERT_EQ(rx_samples[i].i, i + 1);
|
|
}
|
|
ASSERT_EQ(rx_timestamp + 10, rx_timestamp2);
|
|
});
|
|
t1.join();
|
|
t2.join();
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
logInit();
|
|
g_log->log_component[HW].level = OAILOG_DEBUG;
|
|
testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|