mirror of https://github.com/PentHertz/srsLTE.git
RFNOC: added DUC/DDC internal loopback option and test
This commit is contained in:
parent
dcf05f7a53
commit
f80c779d88
|
@ -43,6 +43,12 @@ if(RF_FOUND)
|
||||||
endif(UHD_ENABLE_CUSTOM_RFNOC)
|
endif(UHD_ENABLE_CUSTOM_RFNOC)
|
||||||
endif (UHD_FOUND)
|
endif (UHD_FOUND)
|
||||||
|
|
||||||
|
if (UHD_FOUND AND UHD_ENABLE_CUSTOM_RFNOC)
|
||||||
|
add_executable(rfnoc_test rfnoc_test.cc)
|
||||||
|
target_link_libraries(rfnoc_test srslte_rf ${UHD_LIBRARIES} ${Boost_LIBRARIES} /usr/lib/x86_64-linux-gnu/libboost_system.so)
|
||||||
|
message(info ${Boost_LIBRARIES})
|
||||||
|
endif (UHD_FOUND AND UHD_ENABLE_CUSTOM_RFNOC)
|
||||||
|
|
||||||
if (BLADERF_FOUND)
|
if (BLADERF_FOUND)
|
||||||
add_definitions(-DENABLE_BLADERF)
|
add_definitions(-DENABLE_BLADERF)
|
||||||
list(APPEND SOURCES_RF rf_blade_imp.c)
|
list(APPEND SOURCES_RF rf_blade_imp.c)
|
||||||
|
|
|
@ -72,12 +72,15 @@ private:
|
||||||
const size_t spp = 1920 / 8;
|
const size_t spp = 1920 / 8;
|
||||||
|
|
||||||
// Primary parameters
|
// Primary parameters
|
||||||
|
bool loopback = false;
|
||||||
double master_clock_rate = 184.32e6;
|
double master_clock_rate = 184.32e6;
|
||||||
double bw_hz = 100.0;
|
double bw_hz = 100.0;
|
||||||
size_t nof_radios = 1; ///< Number of RF devices in the device
|
size_t nof_radios = 1; ///< Number of RF devices in the device
|
||||||
size_t nof_channels = 1; ///< Number of Channels per Radio
|
size_t nof_channels = 1; ///< Number of Channels per Radio
|
||||||
std::vector<double> rx_freq_hz;
|
std::vector<double> rx_freq_hz;
|
||||||
|
std::vector<double> rx_center_freq_hz;
|
||||||
std::vector<double> tx_freq_hz;
|
std::vector<double> tx_freq_hz;
|
||||||
|
std::vector<double> tx_center_freq_hz;
|
||||||
size_t dma_fifo_depth = 8192UL * 4096UL;
|
size_t dma_fifo_depth = 8192UL * 4096UL;
|
||||||
|
|
||||||
// Radio control
|
// Radio control
|
||||||
|
@ -126,6 +129,13 @@ private:
|
||||||
// Parse master clock rate
|
// Parse master clock rate
|
||||||
parse_param(args, "master_clock_rate", master_clock_rate, false);
|
parse_param(args, "master_clock_rate", master_clock_rate, false);
|
||||||
|
|
||||||
|
// Parser loopback (optional)
|
||||||
|
loopback = args.has_key("loopback");
|
||||||
|
if (loopback) {
|
||||||
|
args.pop("loopback");
|
||||||
|
Warning("RFNOC Loopback activated!")
|
||||||
|
}
|
||||||
|
|
||||||
// Parse number of radios
|
// Parse number of radios
|
||||||
parse_param(args, "rfnoc_nof_radios", nof_radios);
|
parse_param(args, "rfnoc_nof_radios", nof_radios);
|
||||||
if (nof_radios == 0) {
|
if (nof_radios == 0) {
|
||||||
|
@ -143,7 +153,9 @@ private:
|
||||||
// Set secondary parameters
|
// Set secondary parameters
|
||||||
bw_hz = master_clock_rate / 2.0;
|
bw_hz = master_clock_rate / 2.0;
|
||||||
tx_freq_hz.resize(nof_radios * nof_channels);
|
tx_freq_hz.resize(nof_radios * nof_channels);
|
||||||
|
tx_center_freq_hz.resize(nof_radios);
|
||||||
rx_freq_hz.resize(nof_radios * nof_channels);
|
rx_freq_hz.resize(nof_radios * nof_channels);
|
||||||
|
rx_center_freq_hz.resize(nof_radios);
|
||||||
|
|
||||||
return UHD_ERROR_NONE;
|
return UHD_ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
@ -153,9 +165,12 @@ private:
|
||||||
UHD_SAFE_C_SAVE_ERROR(
|
UHD_SAFE_C_SAVE_ERROR(
|
||||||
this,
|
this,
|
||||||
// Create Radio control
|
// Create Radio control
|
||||||
radio_ctrl.resize(nof_radios);
|
if (not loopback) {
|
||||||
radio_id.resize(nof_radios);
|
radio_ctrl.resize(nof_radios);
|
||||||
for (size_t i = 0; i < nof_radios; i++) {
|
radio_id.resize(nof_radios);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < radio_ctrl.size(); i++) {
|
||||||
// Create handle for radio object
|
// Create handle for radio object
|
||||||
radio_id[i] = uhd::rfnoc::block_id_t(0, RADIO_BLOCK_NAME, i);
|
radio_id[i] = uhd::rfnoc::block_id_t(0, RADIO_BLOCK_NAME, i);
|
||||||
// This next line will fail if the radio is not actually available
|
// This next line will fail if the radio is not actually available
|
||||||
|
@ -210,6 +225,7 @@ private:
|
||||||
if (ddc_ctrl[i] == nullptr) {
|
if (ddc_ctrl[i] == nullptr) {
|
||||||
ddc_id[i] = uhd::rfnoc::block_id_t(0, DDC_BLOCK_NAME, i);
|
ddc_id[i] = uhd::rfnoc::block_id_t(0, DDC_BLOCK_NAME, i);
|
||||||
ddc_ctrl[i] = device3->get_block_ctrl<uhd::rfnoc::ddc_block_ctrl>(ddc_id[i]);
|
ddc_ctrl[i] = device3->get_block_ctrl<uhd::rfnoc::ddc_block_ctrl>(ddc_id[i]);
|
||||||
|
ddc_ctrl[i]->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t j = 0; j < nof_channels; j++) {
|
for (size_t j = 0; j < nof_channels; j++) {
|
||||||
|
@ -244,6 +260,7 @@ private:
|
||||||
if (duc_ctrl[i] == nullptr) {
|
if (duc_ctrl[i] == nullptr) {
|
||||||
duc_id[i] = uhd::rfnoc::block_id_t(0, DUC_BLOCK_NAME, i);
|
duc_id[i] = uhd::rfnoc::block_id_t(0, DUC_BLOCK_NAME, i);
|
||||||
duc_ctrl[i] = device3->get_block_ctrl<uhd::rfnoc::duc_block_ctrl>(duc_id[i]);
|
duc_ctrl[i] = device3->get_block_ctrl<uhd::rfnoc::duc_block_ctrl>(duc_id[i]);
|
||||||
|
duc_ctrl[i]->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t j = 0; j < nof_channels; j++) {
|
for (size_t j = 0; j < nof_channels; j++) {
|
||||||
|
@ -307,6 +324,35 @@ private:
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uhd_error connect_loopback()
|
||||||
|
{
|
||||||
|
size_t nof_samples_per_packet = 0;
|
||||||
|
|
||||||
|
if (spp != 0) {
|
||||||
|
nof_samples_per_packet = spp * 4 + 2 * sizeof(uint64_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
UHD_SAFE_C_SAVE_ERROR(this,
|
||||||
|
|
||||||
|
// Get Tx and Rx Graph
|
||||||
|
graph = device3->create_graph("graph");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nof_radios; i++) {
|
||||||
|
// DUC -> DDC
|
||||||
|
UHD_LOG_DEBUG(graph->get_name(), "Connecting " << duc_id[i] << " -> " << ddc_id[i]);
|
||||||
|
graph->connect(duc_id[i], 0, ddc_id[i], 0, nof_samples_per_packet);
|
||||||
|
|
||||||
|
for (size_t j = 0; j < nof_channels; j++) {
|
||||||
|
// DMA FIFO -> DUC
|
||||||
|
size_t dma_port = i * nof_channels + j;
|
||||||
|
UHD_LOG_DEBUG(graph->get_name(),
|
||||||
|
"Connecting " << dma_id << ":" << dma_port << " -> " << duc_id[i] << ":"
|
||||||
|
<< j);
|
||||||
|
graph->connect(dma_id, dma_port, duc_id[i], j, nof_samples_per_packet);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static bool is_required(const uhd::device_addr_t& device_addr)
|
static bool is_required(const uhd::device_addr_t& device_addr)
|
||||||
{
|
{
|
||||||
|
@ -360,7 +406,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect components
|
// Connect components
|
||||||
err = connect();
|
if (loopback) {
|
||||||
|
err = connect_loopback();
|
||||||
|
} else {
|
||||||
|
err = connect();
|
||||||
|
}
|
||||||
if (err != UHD_ERROR_NONE) {
|
if (err != UHD_ERROR_NONE) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -402,10 +452,19 @@ public:
|
||||||
}
|
}
|
||||||
uhd_error get_time_now(uhd::time_spec_t& timespec) override
|
uhd_error get_time_now(uhd::time_spec_t& timespec) override
|
||||||
{
|
{
|
||||||
|
if (loopback) {
|
||||||
|
timespec = 0.0;
|
||||||
|
return UHD_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
UHD_SAFE_C_SAVE_ERROR(this, timespec = radio_ctrl[0]->get_time_now(););
|
UHD_SAFE_C_SAVE_ERROR(this, timespec = radio_ctrl[0]->get_time_now(););
|
||||||
}
|
}
|
||||||
uhd_error set_sync_source(const std::string& source) override
|
uhd_error set_sync_source(const std::string& source) override
|
||||||
{
|
{
|
||||||
|
if (loopback) {
|
||||||
|
return UHD_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
UHD_SAFE_C_SAVE_ERROR(this, for (size_t radio_idx = 0; radio_idx < nof_radios; radio_idx++) {
|
UHD_SAFE_C_SAVE_ERROR(this, for (size_t radio_idx = 0; radio_idx < nof_radios; radio_idx++) {
|
||||||
UHD_LOG_DEBUG(radio_id[radio_idx], "Setting sync source to " << source);
|
UHD_LOG_DEBUG(radio_id[radio_idx], "Setting sync source to " << source);
|
||||||
radio_ctrl[radio_idx]->set_clock_source(source);
|
radio_ctrl[radio_idx]->set_clock_source(source);
|
||||||
|
@ -495,6 +554,10 @@ public:
|
||||||
return UHD_ERROR_INDEX;
|
return UHD_ERROR_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (radio_ctrl.size() == 0) {
|
||||||
|
return UHD_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
size_t radio_idx = ch / nof_channels;
|
size_t radio_idx = ch / nof_channels;
|
||||||
size_t channel_idx = ch % nof_channels;
|
size_t channel_idx = ch % nof_channels;
|
||||||
|
|
||||||
|
@ -517,6 +580,10 @@ public:
|
||||||
return UHD_ERROR_INDEX;
|
return UHD_ERROR_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (radio_ctrl.size() == 0) {
|
||||||
|
return UHD_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
size_t radio_idx = ch / nof_channels;
|
size_t radio_idx = ch / nof_channels;
|
||||||
size_t channel_idx = ch % nof_channels;
|
size_t channel_idx = ch % nof_channels;
|
||||||
|
|
||||||
|
@ -531,8 +598,23 @@ public:
|
||||||
radio_ctrl[radio_idx]->set_rx_gain(gain, 0);
|
radio_ctrl[radio_idx]->set_rx_gain(gain, 0);
|
||||||
UHD_LOG_DEBUG(radio_id[radio_idx], "Actual RX Gain: " << radio_ctrl[radio_idx]->get_rx_gain(0) << " dB...");)
|
UHD_LOG_DEBUG(radio_id[radio_idx], "Actual RX Gain: " << radio_ctrl[radio_idx]->get_rx_gain(0) << " dB...");)
|
||||||
}
|
}
|
||||||
uhd_error get_tx_gain(double& gain) override { UHD_SAFE_C_SAVE_ERROR(this, gain = radio_ctrl[0]->get_tx_gain(0);) }
|
uhd_error get_tx_gain(double& gain) override
|
||||||
uhd_error get_rx_gain(double& gain) override { UHD_SAFE_C_SAVE_ERROR(this, gain = radio_ctrl[0]->get_rx_gain(0);) }
|
{
|
||||||
|
if (radio_ctrl.size() == 0) {
|
||||||
|
return UHD_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
UHD_SAFE_C_SAVE_ERROR(this, gain = radio_ctrl[0]->get_tx_gain(0);)
|
||||||
|
}
|
||||||
|
uhd_error get_rx_gain(double& gain) override
|
||||||
|
{
|
||||||
|
|
||||||
|
if (radio_ctrl.size() == 0) {
|
||||||
|
return UHD_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
UHD_SAFE_C_SAVE_ERROR(this, gain = radio_ctrl[0]->get_rx_gain(0);)
|
||||||
|
}
|
||||||
uhd_error set_tx_freq(uint32_t ch, double target_freq, double& actual_freq) override
|
uhd_error set_tx_freq(uint32_t ch, double target_freq, double& actual_freq) override
|
||||||
{
|
{
|
||||||
if (ch >= tx_freq_hz.size()) {
|
if (ch >= tx_freq_hz.size()) {
|
||||||
|
@ -557,31 +639,30 @@ public:
|
||||||
|
|
||||||
Debug("Tuning Tx " << ch << " to " << actual_freq / 1e6 << " MHz...");
|
Debug("Tuning Tx " << ch << " to " << actual_freq / 1e6 << " MHz...");
|
||||||
size_t i = ch / nof_channels;
|
size_t i = ch / nof_channels;
|
||||||
|
size_t j = ch % nof_channels;
|
||||||
// Calculate center frequency from average
|
|
||||||
double center_freq_hz = 0.0;
|
|
||||||
for (size_t j = 0; j < nof_channels; j++) {
|
|
||||||
if (not std::isnormal(tx_freq_hz[i * nof_channels + j])) {
|
|
||||||
tx_freq_hz[i * nof_channels + j] = target_freq;
|
|
||||||
}
|
|
||||||
center_freq_hz += tx_freq_hz[i * nof_channels + j];
|
|
||||||
}
|
|
||||||
center_freq_hz /= nof_channels;
|
|
||||||
|
|
||||||
UHD_SAFE_C_SAVE_ERROR(
|
UHD_SAFE_C_SAVE_ERROR(
|
||||||
this,
|
this,
|
||||||
|
|
||||||
// Set Radio Tx freq
|
// Set Radio Tx freq
|
||||||
UHD_LOG_DEBUG(radio_id[i], "Setting TX Freq: " << center_freq_hz / 1e6 << " MHz...");
|
if (not std::isnormal(tx_center_freq_hz[i]) and not loopback) {
|
||||||
radio_ctrl[i]->set_tx_frequency(center_freq_hz, 0);
|
UHD_LOG_DEBUG(radio_id[i],
|
||||||
center_freq_hz = radio_ctrl[i]->get_tx_frequency(0);
|
"Setting TX Freq: " << target_freq / 1e6 << " (" << uint64_t(target_freq) << ") MHz...");
|
||||||
UHD_LOG_DEBUG(radio_id[i], "Actual TX Freq: " << center_freq_hz / 1e6 << " MHz...");
|
radio_ctrl[i]->set_tx_frequency(target_freq, 0);
|
||||||
|
tx_center_freq_hz[i] = radio_ctrl[i]->get_tx_frequency(0);
|
||||||
|
UHD_LOG_DEBUG(radio_id[i],
|
||||||
|
"Actual TX Freq: " << tx_center_freq_hz[i] / 1e6 << "(" << uint64_t(tx_center_freq_hz[i])
|
||||||
|
<< ") MHz...");
|
||||||
|
}
|
||||||
|
|
||||||
// Setup DUC
|
// Setup DUC
|
||||||
for (size_t j = 0; j < nof_channels; j++) {
|
double freq_hz = tx_freq_hz[nof_channels * i + j] - tx_center_freq_hz[i];
|
||||||
double freq_hz = tx_freq_hz[nof_channels * i + j] - center_freq_hz;
|
if (std::round(duc_ctrl[i]->get_arg<double>("freq") != freq_hz)) {
|
||||||
UHD_LOG_DEBUG(duc_id[i], "Setting " << j << " freq: " << freq_hz / 1e6 << " MHz ...");
|
UHD_LOG_DEBUG(duc_id[i],
|
||||||
|
"Setting " << j << " freq: " << freq_hz / 1e6 << "(" << uint64_t(freq_hz) << ") MHz ...");
|
||||||
duc_ctrl[i]->set_arg("freq", std::to_string(freq_hz), j);
|
duc_ctrl[i]->set_arg("freq", std::to_string(freq_hz), j);
|
||||||
|
freq_hz = duc_ctrl[i]->get_arg<double>("freq");
|
||||||
|
Debug("Actual Tx " << ch << " freq error: " << tx_center_freq_hz[i] + freq_hz - target_freq << " Hz...");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
uhd_error set_rx_freq(uint32_t ch, double target_freq, double& actual_freq) override
|
uhd_error set_rx_freq(uint32_t ch, double target_freq, double& actual_freq) override
|
||||||
|
@ -608,31 +689,26 @@ public:
|
||||||
|
|
||||||
Debug("Tuning Rx " << ch << " to " << actual_freq / 1e6 << " MHz...");
|
Debug("Tuning Rx " << ch << " to " << actual_freq / 1e6 << " MHz...");
|
||||||
size_t i = ch / nof_channels;
|
size_t i = ch / nof_channels;
|
||||||
|
size_t j = ch % nof_channels;
|
||||||
// Calculate center frequency from average
|
|
||||||
double center_freq_hz = 0.0;
|
|
||||||
for (size_t j = 0; j < nof_channels; j++) {
|
|
||||||
if (not std::isnormal(rx_freq_hz[i * nof_channels + j])) {
|
|
||||||
rx_freq_hz[i * nof_channels + j] = target_freq;
|
|
||||||
}
|
|
||||||
center_freq_hz += rx_freq_hz[i * nof_channels + j];
|
|
||||||
}
|
|
||||||
center_freq_hz /= nof_channels;
|
|
||||||
|
|
||||||
UHD_SAFE_C_SAVE_ERROR(
|
UHD_SAFE_C_SAVE_ERROR(
|
||||||
this,
|
this,
|
||||||
|
|
||||||
// Set Radio Tx freq
|
// Set Radio Tx freq
|
||||||
UHD_LOG_DEBUG(radio_id[i], "Setting RX Freq: " << center_freq_hz / 1e6 << " MHz...");
|
if (not std::isnormal(rx_center_freq_hz[i]) and not loopback) {
|
||||||
radio_ctrl[i]->set_rx_frequency(center_freq_hz, 0);
|
UHD_LOG_DEBUG(radio_id[i], "Setting RX Freq: " << target_freq / 1e6 << " MHz...");
|
||||||
center_freq_hz = radio_ctrl[i]->get_rx_frequency(0);
|
radio_ctrl[i]->set_rx_frequency(target_freq, 0);
|
||||||
UHD_LOG_DEBUG(radio_id[i], "Actual RX Freq: " << center_freq_hz / 1e6 << " MHz...");
|
rx_center_freq_hz[i] = radio_ctrl[i]->get_rx_frequency(0);
|
||||||
|
UHD_LOG_DEBUG(radio_id[i], "Actual RX Freq: " << rx_center_freq_hz[i] / 1e6 << " MHz...");
|
||||||
|
}
|
||||||
|
|
||||||
// Setup DDC
|
// Setup DDC
|
||||||
for (size_t j = 0; j < nof_channels; j++) {
|
double freq_hz = rx_center_freq_hz[i] - rx_freq_hz[nof_channels * i + j];
|
||||||
double freq_hz = center_freq_hz - rx_freq_hz[nof_channels * i + j];
|
if (std::round(ddc_ctrl[i]->get_arg<double>("freq") != freq_hz)) {
|
||||||
UHD_LOG_DEBUG(ddc_id[i], "Setting " << j << " freq: " << freq_hz / 1e6 << " MHz ...");
|
UHD_LOG_DEBUG(ddc_id[i], "Setting " << j << " freq: " << freq_hz / 1e6 << " MHz ...");
|
||||||
ddc_ctrl[i]->set_arg("freq", freq_hz, j);
|
ddc_ctrl[i]->set_arg("freq", freq_hz, j);
|
||||||
|
freq_hz = ddc_ctrl[i]->get_arg<double>("freq");
|
||||||
|
Debug("Actual Rx " << ch << " freq error: " << rx_center_freq_hz[i] - freq_hz - target_freq << " Hz...");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -129,8 +129,8 @@ public:
|
||||||
|
|
||||||
UHD_SAFE_C_SAVE_ERROR(this, uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
|
UHD_SAFE_C_SAVE_ERROR(this, uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
|
||||||
stream_cmd.time_spec = time_spec;
|
stream_cmd.time_spec = time_spec;
|
||||||
stream_cmd.time_spec += 0.1;
|
stream_cmd.time_spec += delay;
|
||||||
stream_cmd.stream_now = false;
|
stream_cmd.stream_now = not std::isnormal(delay);
|
||||||
|
|
||||||
rx_stream->issue_stream_cmd(stream_cmd);)
|
rx_stream->issue_stream_cmd(stream_cmd);)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,357 @@
|
||||||
|
//
|
||||||
|
// Copyright 2016 Ettus Research LLC
|
||||||
|
// Copyright 2018 Ettus Research, a National Instruments Company
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
//
|
||||||
|
|
||||||
|
// Example UHD/RFNoC application: Connect an rx radio to a tx radio and
|
||||||
|
// run a loopback.
|
||||||
|
#include "rf_uhd_rfnoc.h"
|
||||||
|
#include <boost/program_options.hpp>
|
||||||
|
#include <boost/program_options/cmdline.hpp>
|
||||||
|
#include <boost/program_options/options_description.hpp>
|
||||||
|
#include <boost/program_options/variables_map.hpp>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <srslte/config.h>
|
||||||
|
#include <srslte/phy/utils/vector.h>
|
||||||
|
#include <uhd/device3.hpp>
|
||||||
|
#include <uhd/rfnoc/dma_fifo_block_ctrl.hpp>
|
||||||
|
#include <uhd/rfnoc/duc_ch2_block_ctrl.hpp>
|
||||||
|
|
||||||
|
#define TESTASSERT(cond) \
|
||||||
|
do { \
|
||||||
|
if (!(cond)) { \
|
||||||
|
printf("[%s][Line %d] Fail at \"%s\"\n", __FUNCTION__, __LINE__, (#cond)); \
|
||||||
|
return -1; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* main
|
||||||
|
***************************************************************************/
|
||||||
|
cf_t cexp_(float arg)
|
||||||
|
{
|
||||||
|
std::complex<float> v = std::polar(0.5f, arg);
|
||||||
|
return *((cf_t*)&v);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct rfnoc_custom_testbench_s {
|
||||||
|
public:
|
||||||
|
typedef struct {
|
||||||
|
// Main parameters
|
||||||
|
std::string ip_address = "192.168.40.2";
|
||||||
|
double duration_s = 1.0;
|
||||||
|
double master_clock_rate_hz = 184.32e6;
|
||||||
|
double srate_hz = 1.92e6;
|
||||||
|
double init_tx_time = 0.1;
|
||||||
|
size_t period_nsamples = 19;
|
||||||
|
uint32_t nof_channels = 2;
|
||||||
|
uint32_t nof_radios = 1;
|
||||||
|
bool asynchronous_msg = false;
|
||||||
|
bool blocking = false;
|
||||||
|
size_t blocking_min_tx = 1;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
float get_freq_norm() { return 1.0f / float(period_nsamples); }
|
||||||
|
} config_s;
|
||||||
|
|
||||||
|
private:
|
||||||
|
config_s config;
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
std::condition_variable tx_cvar;
|
||||||
|
std::condition_variable rx_cvar;
|
||||||
|
|
||||||
|
cf_t* tx_buffer[2] = {};
|
||||||
|
cf_t* rx_buffer[2] = {};
|
||||||
|
|
||||||
|
std::shared_ptr<rf_uhd_safe_interface> rfnoc;
|
||||||
|
size_t packet_size_nsamples = 0;
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
size_t txd_nsamples_total = 0;
|
||||||
|
size_t txd_count = 0;
|
||||||
|
size_t rxd_nsamples_total = 0;
|
||||||
|
size_t rxd_count = 0;
|
||||||
|
bool running = true;
|
||||||
|
|
||||||
|
void check_async_msg()
|
||||||
|
{
|
||||||
|
bool valid = false;
|
||||||
|
uhd::async_metadata_t async_metadata;
|
||||||
|
|
||||||
|
rfnoc->recv_async_msg(async_metadata, 0.0, valid);
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
switch (async_metadata.event_code) {
|
||||||
|
|
||||||
|
case uhd::async_metadata_t::EVENT_CODE_BURST_ACK:
|
||||||
|
Warning("BURST ACK") break;
|
||||||
|
case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW:
|
||||||
|
Warning("UNDERFLOW") break;
|
||||||
|
case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR:
|
||||||
|
Warning("SEQUENCE ERROR") break;
|
||||||
|
case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR:
|
||||||
|
Warning("TIME ERROR") break;
|
||||||
|
case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET:
|
||||||
|
Warning("UNDERFLOW IN PACKET") break;
|
||||||
|
case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST:
|
||||||
|
Warning("ERROR IN BURST") break;
|
||||||
|
case uhd::async_metadata_t::EVENT_CODE_USER_PAYLOAD:
|
||||||
|
Warning("ERROR USER PAYLOAD") break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* tx_routine()
|
||||||
|
{
|
||||||
|
size_t txd_nsamples = 0;
|
||||||
|
void* tx_buffs_ptr[] = {tx_buffer[0], tx_buffer[1]};
|
||||||
|
|
||||||
|
uhd::tx_metadata_t tx_metadata;
|
||||||
|
tx_metadata.has_time_spec = true;
|
||||||
|
tx_metadata.time_spec = config.init_tx_time;
|
||||||
|
tx_metadata.start_of_burst = true;
|
||||||
|
|
||||||
|
while (tx_metadata.time_spec < config.duration_s + config.init_tx_time and running) {
|
||||||
|
cf_t v = cexp_(2.0f * M_PI * float(txd_nsamples_total % config.period_nsamples) * config.get_freq_norm());
|
||||||
|
srslte_vec_gen_sine(v, config.get_freq_norm(), tx_buffer[0], packet_size_nsamples);
|
||||||
|
srslte_vec_sc_prod_cfc(tx_buffer[0], -1, tx_buffer[1], packet_size_nsamples);
|
||||||
|
|
||||||
|
if (config.blocking) {
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
while (rxd_count + config.blocking_min_tx <= txd_count and running) {
|
||||||
|
std::cv_status status = rx_cvar.wait_for(lock, std::chrono::milliseconds(100));
|
||||||
|
if (status == std::cv_status::timeout) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actual transmission
|
||||||
|
rfnoc->send(tx_buffs_ptr, packet_size_nsamples, tx_metadata, 0.1, txd_nsamples);
|
||||||
|
tx_metadata.start_of_burst = false;
|
||||||
|
|
||||||
|
// Update Tx state
|
||||||
|
mutex.lock();
|
||||||
|
txd_nsamples_total += txd_nsamples;
|
||||||
|
txd_count++;
|
||||||
|
tx_cvar.notify_all();
|
||||||
|
mutex.unlock();
|
||||||
|
tx_metadata.time_spec += packet_size_nsamples / config.srate_hz;
|
||||||
|
tx_metadata.start_of_burst = false;
|
||||||
|
|
||||||
|
if (not config.asynchronous_msg) {
|
||||||
|
check_async_msg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx_metadata.end_of_burst = true;
|
||||||
|
rfnoc->send(tx_buffs_ptr, 0, tx_metadata, 0.1, txd_nsamples);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* rx_routine()
|
||||||
|
{
|
||||||
|
void* rx_buffs_ptr[] = {rx_buffer[0], rx_buffer[1]};
|
||||||
|
|
||||||
|
do {
|
||||||
|
uhd::rx_metadata_t rx_metadata;
|
||||||
|
size_t rxd_nsamples = 0;
|
||||||
|
|
||||||
|
if (config.blocking) {
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
while (rxd_count >= txd_count and running) {
|
||||||
|
std::cv_status status = tx_cvar.wait_for(lock, std::chrono::milliseconds(100));
|
||||||
|
if (status == std::cv_status::timeout) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rfnoc->receive(rx_buffs_ptr, packet_size_nsamples, rx_metadata, 0.1, false, rxd_nsamples);
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(mutex);
|
||||||
|
if (rx_metadata.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) {
|
||||||
|
running = false;
|
||||||
|
} else {
|
||||||
|
// Update stats
|
||||||
|
rxd_nsamples_total += rxd_nsamples;
|
||||||
|
rxd_count++;
|
||||||
|
float avg_power = srslte_vec_avg_power_cf(rx_buffer[0], rxd_nsamples);
|
||||||
|
if (std::isnan(avg_power) or avg_power > 1e-6) {
|
||||||
|
Warning("-- RX " << rxd_count << " Received " << avg_power);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rx_cvar.notify_all();
|
||||||
|
} while (running);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* async_msg_routine()
|
||||||
|
{
|
||||||
|
while (running) {
|
||||||
|
check_async_msg();
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
rfnoc_custom_testbench_s(const config_s& config_)
|
||||||
|
{
|
||||||
|
rfnoc = std::make_shared<rf_uhd_rfnoc>();
|
||||||
|
|
||||||
|
config = config_;
|
||||||
|
}
|
||||||
|
int init()
|
||||||
|
{
|
||||||
|
uhd::device_addr_t hint;
|
||||||
|
hint["ip_addr"] = config.ip_address;
|
||||||
|
hint["master_clock_rate"] = std::to_string(config.master_clock_rate_hz);
|
||||||
|
hint["rfnoc_nof_channels"] = std::to_string(config.nof_channels);
|
||||||
|
hint["rfnoc_nof_radios"] = std::to_string(config.nof_radios);
|
||||||
|
hint["loopback"] = "true";
|
||||||
|
uhd::log::set_console_level(uhd::log::severity_level::trace);
|
||||||
|
|
||||||
|
if (rfnoc->usrp_make(hint, config.nof_channels * config.nof_radios) != UHD_ERROR_NONE) {
|
||||||
|
Warning(rfnoc->last_error);
|
||||||
|
return SRSLTE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rfnoc->set_tx_rate(config.srate_hz) != UHD_ERROR_NONE) {
|
||||||
|
Warning(rfnoc->last_error);
|
||||||
|
return SRSLTE_ERROR;
|
||||||
|
}
|
||||||
|
if (rfnoc->set_rx_rate(config.srate_hz) != UHD_ERROR_NONE) {
|
||||||
|
Warning(rfnoc->last_error);
|
||||||
|
return SRSLTE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t nof_tx_samples = 0;
|
||||||
|
size_t nof_rx_samples = 0;
|
||||||
|
rfnoc->get_tx_stream(nof_tx_samples);
|
||||||
|
rfnoc->get_rx_stream(nof_rx_samples);
|
||||||
|
packet_size_nsamples = SRSLTE_MIN(nof_tx_samples, nof_rx_samples);
|
||||||
|
|
||||||
|
for (size_t j = 0; j < config.nof_channels; j++) {
|
||||||
|
tx_buffer[j] = srslte_vec_cf_malloc(packet_size_nsamples);
|
||||||
|
rx_buffer[j] = srslte_vec_cf_malloc(packet_size_nsamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rfnoc->start_rx_stream(config.init_tx_time) != UHD_ERROR_NONE) {
|
||||||
|
Warning(rfnoc->last_error);
|
||||||
|
return SRSLTE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SRSLTE_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int run()
|
||||||
|
{
|
||||||
|
running = true;
|
||||||
|
|
||||||
|
std::thread tx_thread(&rfnoc_custom_testbench_s::tx_routine, this);
|
||||||
|
std::thread rx_thread(&rfnoc_custom_testbench_s::rx_routine, this);
|
||||||
|
|
||||||
|
if (config.asynchronous_msg) {
|
||||||
|
std::thread async_msg_thread(&rfnoc_custom_testbench_s::async_msg_routine, this);
|
||||||
|
async_msg_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
tx_thread.join();
|
||||||
|
rx_thread.join();
|
||||||
|
|
||||||
|
Info("Tx packets: " << txd_count);
|
||||||
|
Info("Tx samples: " << txd_nsamples_total);
|
||||||
|
|
||||||
|
Info("Rx packets: " << rxd_count);
|
||||||
|
Info("Rx samples: " << rxd_nsamples_total);
|
||||||
|
|
||||||
|
return SRSLTE_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
~rfnoc_custom_testbench_s()
|
||||||
|
{
|
||||||
|
for (size_t j = 0; j < config.nof_channels; j++) {
|
||||||
|
if (tx_buffer[j] != nullptr) {
|
||||||
|
free(tx_buffer[j]);
|
||||||
|
}
|
||||||
|
if (rx_buffer[j] != nullptr) {
|
||||||
|
free(rx_buffer[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int test(const rfnoc_custom_testbench_s::config_s& config)
|
||||||
|
{
|
||||||
|
rfnoc_custom_testbench_s context(config);
|
||||||
|
|
||||||
|
TESTASSERT(context.init() == SRSLTE_SUCCESS);
|
||||||
|
TESTASSERT(context.run() == SRSLTE_SUCCESS);
|
||||||
|
|
||||||
|
return SRSLTE_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace bpo = boost::program_options;
|
||||||
|
|
||||||
|
static int parse_args(int argc, char** argv, rfnoc_custom_testbench_s::config_s& args)
|
||||||
|
{
|
||||||
|
int ret = SRSLTE_SUCCESS;
|
||||||
|
|
||||||
|
bpo::options_description options;
|
||||||
|
bpo::options_description common("Common execution options");
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
common.add_options()
|
||||||
|
("ip_addr", bpo::value<std::string>(&args.ip_address), "Device 3 IP address")
|
||||||
|
("duration", bpo::value<double>(&args.duration_s), "Duration of the execution in seconds")
|
||||||
|
("master_clock_rate", bpo::value<double>(&args.master_clock_rate_hz), "Device master clock rate in Hz")
|
||||||
|
("srate", bpo::value<double>(&args.srate_hz), "Sampling rate in Hz")
|
||||||
|
("init_time", bpo::value<double>(&args.init_tx_time), "Initial time in seconds")
|
||||||
|
("period_nsamples", bpo::value<size_t>(&args.period_nsamples), "Sine wave period in number of samples")
|
||||||
|
("asynch_msg", bpo::value<bool>(&args.asynchronous_msg), "Enable/disable asynchronous message")
|
||||||
|
("blocking", bpo::value<bool>(&args.blocking), "Block transmitter/receiver for every transaction")
|
||||||
|
("blocking_min_tx", bpo::value<size_t>(&args.blocking_min_tx),"Ensure a minimum of enqueued Tx")
|
||||||
|
;
|
||||||
|
options.add(common).add_options()("help", "Show this message");
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
bpo::variables_map vm;
|
||||||
|
try {
|
||||||
|
bpo::store(bpo::command_line_parser(argc, argv).options(options).run(), vm);
|
||||||
|
bpo::notify(vm);
|
||||||
|
} catch (bpo::error& e) {
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
ret = SRSLTE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// help option was given or error - print usage and exit
|
||||||
|
if (vm.count("help") || ret) {
|
||||||
|
std::cout << "Usage: " << argv[0] << " [OPTIONS] config_file" << std::endl << std::endl;
|
||||||
|
std::cout << options << std::endl << std::endl;
|
||||||
|
ret = SRSLTE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
rfnoc_custom_testbench_s::config_s config;
|
||||||
|
|
||||||
|
TESTASSERT(parse_args(argc, argv, config) == SRSLTE_SUCCESS);
|
||||||
|
|
||||||
|
TESTASSERT(test(config) == SRSLTE_SUCCESS);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
Loading…
Reference in New Issue