Added multi gNb simulation and RF

This commit is contained in:
Ismael Gomez 2021-11-23 22:01:49 +01:00
parent f5446422bc
commit 58ab8086ff
7 changed files with 544 additions and 227 deletions

View File

@ -387,8 +387,9 @@ typedef struct SRSRAN_API {
#define SRSRAN_DEFAULT_CARRIER_NR \
{ \
.pci = 500, .dl_center_frequency_hz = 3.5e9, .ul_center_frequency_hz = 3.5e9, .ssb_center_freq_hz = 3.5e9, \
.offset_to_carrier = 0, .scs = srsran_subcarrier_spacing_15kHz, .nof_prb = 52, .start = 0, .max_mimo_layers = 1 \
.pci = 500, .dl_center_frequency_hz = 117000 * 30e3, .ul_center_frequency_hz = 117000 * 30e3, \
.ssb_center_freq_hz = 3.5e9, .offset_to_carrier = 0, .scs = srsran_subcarrier_spacing_15kHz, .nof_prb = 52, \
.start = 0, .max_mimo_layers = 1 \
}
/**

View File

@ -75,6 +75,11 @@ bool cell_search::start(const cfg_t& cfg)
return false;
}
logger.info("Cell search: starting in center frequency %.2f and SSB frequency %.2f with subcarrier spacing of %s",
cfg.center_freq_hz / 1e6,
cfg.ssb_freq_hz / 1e6,
srsran_subcarrier_spacing_to_str(cfg.ssb_scs));
// Set RX frequency
radio.set_rx_freq(0, cfg.center_freq_hz);
@ -105,7 +110,7 @@ bool cell_search::run()
}
// Consider the SSB is found and decoded if the PBCH CRC matched
if (res.pbch_msg.crc) {
if (res.measurements.snr_dB >= -10.0f and res.pbch_msg.crc) {
rrc_interface_phy_sa_nr::cell_search_result_t cs_res = {};
cs_res.pci = res.N_id;
cs_res.pbch_msg = res.pbch_msg;

View File

@ -72,6 +72,7 @@ target_link_libraries(nr_cell_search_rf
${CMAKE_THREAD_LIBS_INIT}
${Boost_LIBRARIES})
# RF based Usage example: nr_sa_cell_search_test --phy.log.level=info --stack.log.level=info --duration=10000 --freq_dl=3.67536e9 --rf.freq_offset=10e3 --rf.rx_gain=90
add_executable(nr_sa_cell_search_test nr_sa_cell_search_test.cc)
target_link_libraries(nr_sa_cell_search_test
srsue_phy

View File

@ -0,0 +1,108 @@
/**
*
* \section COPYRIGHT
*
* Copyright 2013-2021 Software Radio Systems Limited
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the distribution.
*
*/
#ifndef SRSRAN_GNB_EMULATOR_H
#define SRSRAN_GNB_EMULATOR_H
#include <srsran/phy/channel/channel.h>
#include <srsran/radio/rf_timestamp.h>
#include <srsran/srsran.h>
#include <srsran/support/srsran_assert.h>
class gnb_emulator
{
private:
uint32_t sf_len = 0;
srsran_carrier_nr_t carrier = {};
srsran_ssb_t ssb = {};
srsran::channel channel;
std::vector<cf_t> buffer;
srslog::basic_logger& logger = srslog::fetch_basic_logger("GNB-EMULATOR");
public:
struct args_t {
double srate_hz;
srsran_carrier_nr_t carrier;
srsran_subcarrier_spacing_t ssb_scs;
srsran_ssb_patern_t ssb_pattern;
uint32_t ssb_periodicity_ms;
srsran_duplex_mode_t duplex_mode;
srsran::channel::args_t channel;
std::string log_level = "warning";
};
gnb_emulator(const args_t& args) : channel(args.channel, 1, srslog::fetch_basic_logger("GNB-EMULATOR"))
{
logger.set_level(srslog::str_to_basic_level(args.log_level));
srsran_assert(
std::isnormal(args.srate_hz) and args.srate_hz > 0, "Invalid sampling rate (%.2f MHz)", args.srate_hz);
// Initialise internals
sf_len = args.srate_hz / 1000;
carrier = args.carrier;
buffer.resize(sf_len);
srsran_ssb_args_t ssb_args = {};
ssb_args.enable_encode = true;
srsran_assert(srsran_ssb_init(&ssb, &ssb_args) == SRSRAN_SUCCESS, "SSB initialisation failed");
srsran_ssb_cfg_t ssb_cfg = {};
ssb_cfg.srate_hz = args.srate_hz;
ssb_cfg.center_freq_hz = args.carrier.dl_center_frequency_hz;
ssb_cfg.ssb_freq_hz = args.carrier.ssb_center_freq_hz;
ssb_cfg.scs = args.ssb_scs;
ssb_cfg.pattern = args.ssb_pattern;
ssb_cfg.duplex_mode = args.duplex_mode;
ssb_cfg.periodicity_ms = args.ssb_periodicity_ms;
srsran_assert(srsran_ssb_set_cfg(&ssb, &ssb_cfg) == SRSRAN_SUCCESS, "SSB set config failed");
// Configure channel
channel.set_srate((uint32_t)args.srate_hz);
}
int work(uint32_t sf_idx, cf_t* baseband_buffer, const srsran::rf_timestamp_t& ts)
{
logger.set_context(sf_idx);
// Zero buffer
srsran_vec_cf_zero(buffer.data(), sf_len);
// Check if SSB needs to be sent
if (srsran_ssb_send(&ssb, sf_idx)) {
// Prepare PBCH message
srsran_pbch_msg_nr_t msg = {};
// Add SSB
if (srsran_ssb_add(&ssb, carrier.pci, &msg, buffer.data(), buffer.data()) < SRSRAN_SUCCESS) {
logger.error("Error adding SSB");
return SRSRAN_ERROR;
}
}
// Run channel
cf_t* in[SRSRAN_MAX_CHANNELS] = {};
cf_t* out[SRSRAN_MAX_CHANNELS] = {};
in[0] = buffer.data();
out[0] = buffer.data();
channel.run(in, out, sf_len, ts.get(0));
// Add buffer to baseband buffer
srsran_vec_sum_ccc(baseband_buffer, buffer.data(), baseband_buffer, sf_len);
return SRSRAN_SUCCESS;
}
~gnb_emulator() { srsran_ssb_free(&ssb); }
};
#endif // SRSRAN_GNB_EMULATOR_H

View File

@ -0,0 +1,142 @@
/**
*
* \section COPYRIGHT
*
* Copyright 2013-2021 Software Radio Systems Limited
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the distribution.
*
*/
#ifndef SRSRAN_GNB_RF_EMULATOR_H
#define SRSRAN_GNB_RF_EMULATOR_H
#include "gnb_emulator.h"
#include <set>
#include <srsran/interfaces/radio_interfaces.h>
#include <srsran/srslog/srslog.h>
#include <srsran/srsran.h>
#include <srsran/support/srsran_assert.h>
class gnb_rf_emulator final : public srsran::radio_interface_phy
{
private:
const uint32_t BUFFER_SIZE_SF = 10;
const std::string LOGNAME = "RF";
uint32_t sf_len = 0;
srsran_ringbuffer_t ringbuffer = {};
uint32_t slot_idx = 0;
std::atomic<bool> running = {true};
srsran::rf_timestamp_t ts_write = {};
std::vector<cf_t> buffer;
std::vector<std::shared_ptr<gnb_emulator> > gnb_vector;
void run_async_slot()
{
// Early return if not running
if (not running) {
return;
}
// Zero slot buffer
srsran_vec_cf_zero(buffer.data(), sf_len);
for (std::shared_ptr<gnb_emulator>& gnb : gnb_vector) {
srsran_assert(gnb->work(slot_idx, buffer.data(), ts_write) == SRSRAN_SUCCESS, "Failed to run gNb emulator");
}
// Write slot samples in ringbuffer
srsran_assert(srsran_ringbuffer_write(&ringbuffer, buffer.data(), (int)sizeof(cf_t) * sf_len) > SRSRAN_SUCCESS,
"Error writing in ringbuffer");
// Increment time
ts_write.add(0.001f);
}
public:
struct args_t {
double srate_hz;
srsran_carrier_nr_t base_carrier;
srsran_subcarrier_spacing_t ssb_scs;
srsran_ssb_patern_t ssb_pattern;
uint32_t ssb_periodicity_ms;
srsran_duplex_mode_t duplex_mode;
std::set<uint32_t> pci_list;
};
gnb_rf_emulator(const args_t& args)
{
srsran_assert(
std::isnormal(args.srate_hz) and args.srate_hz > 0, "Invalid sampling rate (%.2f MHz)", args.srate_hz);
sf_len = args.srate_hz / 1000;
for (uint32_t pci : args.pci_list) {
gnb_emulator::args_t gnb_args = {};
gnb_args.srate_hz = args.srate_hz;
gnb_args.carrier = args.base_carrier;
gnb_args.carrier.pci = pci;
gnb_args.ssb_scs = args.ssb_scs;
gnb_args.ssb_pattern = args.ssb_pattern;
gnb_args.ssb_periodicity_ms = args.ssb_periodicity_ms;
gnb_args.duplex_mode = args.duplex_mode;
gnb_vector.emplace_back(std::make_shared<gnb_emulator>(gnb_args));
}
srsran_assert(srsran_ringbuffer_init(&ringbuffer, sizeof(cf_t) * BUFFER_SIZE_SF * sf_len) >= SRSRAN_SUCCESS,
"Ringbuffer initialisation failed");
buffer.resize(BUFFER_SIZE_SF * sf_len);
}
~gnb_rf_emulator() = default;
void tx_end() override {}
bool tx(srsran::rf_buffer_interface& tx_buffer, const srsran::rf_timestamp_interface& tx_time) override
{
return false;
}
bool rx_now(srsran::rf_buffer_interface& rx_buffer, srsran::rf_timestamp_interface& rxd_time) override
{
int nbytes = (int)(sizeof(cf_t) * rx_buffer.get_nof_samples());
cf_t* temp_buffer = rx_buffer.get(0);
// If the buffer is invalid, use internal temporal buffer
if (temp_buffer == nullptr) {
temp_buffer = buffer.data();
}
// As long as there are not enough samples
while (srsran_ringbuffer_status(&ringbuffer) < nbytes and running) {
run_async_slot();
}
if (not running) {
return true;
}
srsran_assert(srsran_ringbuffer_read(&ringbuffer, temp_buffer, nbytes) >= SRSRAN_SUCCESS,
"Error reading from ringbuffer");
return true;
}
void set_tx_freq(const uint32_t& carrier_idx, const double& freq) override {}
void set_rx_freq(const uint32_t& carrier_idx, const double& freq) override {}
void release_freq(const uint32_t& carrier_idx) override {}
void set_tx_gain(const float& gain) override {}
void set_rx_gain_th(const float& gain) override {}
void set_rx_gain(const float& gain) override {}
void set_tx_srate(const double& srate) override {}
void set_rx_srate(const double& srate) override {}
void set_channel_rx_offset(uint32_t ch, int32_t offset_samples) override {}
double get_freq_offset() override { return 0; }
float get_rx_gain() override { return 0; }
bool is_continuous_tx() override { return false; }
bool get_is_start_of_burst() override { return false; }
bool is_init() override { return false; }
void reset() override { running = false; }
srsran_rf_info_t* get_info() override { return nullptr; }
};
#endif // SRSRAN_GNB_RF_EMULATOR_H

View File

@ -10,164 +10,31 @@
*
*/
#include "gnb_rf_emulator.h"
#include "srsran/asn1/rrc_nr.h"
#include "srsran/common/band_helper.h"
#include "srsran/common/crash_handler.h"
#include "srsran/common/string_helpers.h"
#include "srsue/hdr/phy/phy_nr_sa.h"
#include "test/phy/dummy_ue_stack.h"
#include <boost/program_options.hpp>
#include <boost/program_options/parsers.hpp>
#include <iostream>
struct test_args_t {};
class gnb_emulator : public srsran::radio_interface_phy
class throttled_dummy_stack : public ue_dummy_stack
{
private:
const uint32_t BUFFER_SIZE_SF = 10;
const std::string LOGNAME = "RF";
uint32_t sf_len = 0;
srsran_ssb_t ssb = {};
srsran_ringbuffer_t ringbuffer = {};
cf_t* buffer = nullptr;
uint32_t slot_idx = 0;
srsran_carrier_nr_t carrier = {};
std::atomic<bool> running = {true};
srslog::basic_logger& logger;
void run_async_slot()
{
// Early return if not running
if (not running) {
return;
}
// Zero slot buffer
srsran_vec_cf_zero(buffer, sf_len);
if (srsran_ssb_send(&ssb, slot_idx)) {
// Create MIB for this slot
srsran_mib_nr_t mib = {};
// Create PBCH message from packed MIB
srsran_pbch_msg_nr_t pbch_msg = {};
srsran_assert(srsran_pbch_msg_nr_mib_pack(&mib, &pbch_msg) >= SRSRAN_SUCCESS,
"Error packing MIB into PBCH message");
// Encode SSB signal and add it to the baseband
srsran_assert(srsran_ssb_add(&ssb, carrier.pci, &pbch_msg, buffer, buffer) >= SRSRAN_SUCCESS,
"Error adding SSB signal");
}
slot_idx++;
// Write slot samples in ringbuffer
srsran_assert(srsran_ringbuffer_write(&ringbuffer, buffer, (int)sizeof(cf_t) * sf_len) > SRSRAN_SUCCESS,
"Error writing in ringbuffer");
}
public:
struct args_t {
double srate_hz;
srsran_carrier_nr_t carrier;
srsran_subcarrier_spacing_t ssb_scs;
srsran_ssb_patern_t ssb_pattern;
uint32_t ssb_periodicity_ms;
srsran_duplex_mode_t duplex_mode;
};
gnb_emulator(const args_t& args) : logger(srslog::fetch_basic_logger(LOGNAME))
{
srsran_assert(
std::isnormal(args.srate_hz) and args.srate_hz > 0, "Invalid sampling rate (%.2f MHz)", args.srate_hz);
sf_len = args.srate_hz / 1000;
carrier = args.carrier;
srsran_ssb_args_t ssb_args = {};
ssb_args.enable_encode = true;
srsran_assert(srsran_ssb_init(&ssb, &ssb_args) == SRSRAN_SUCCESS, "SSB initialisation failed");
srsran_ssb_cfg_t ssb_cfg = {};
ssb_cfg.srate_hz = args.srate_hz;
ssb_cfg.center_freq_hz = args.carrier.dl_center_frequency_hz;
ssb_cfg.ssb_freq_hz = args.carrier.ssb_center_freq_hz;
ssb_cfg.scs = args.ssb_scs;
ssb_cfg.pattern = args.ssb_pattern;
ssb_cfg.duplex_mode = args.duplex_mode;
ssb_cfg.periodicity_ms = args.ssb_periodicity_ms;
srsran_assert(srsran_ssb_set_cfg(&ssb, &ssb_cfg) == SRSRAN_SUCCESS, "SSB set config failed");
srsran_assert(srsran_ringbuffer_init(&ringbuffer, sizeof(cf_t) * BUFFER_SIZE_SF * sf_len) >= SRSRAN_SUCCESS,
"Ringbuffer initialisation failed");
buffer = srsran_vec_cf_malloc(BUFFER_SIZE_SF * sf_len);
}
~gnb_emulator()
{
srsran_ssb_free(&ssb);
srsran_ringbuffer_free(&ringbuffer);
if (buffer != nullptr) {
free(buffer);
}
}
void tx_end() override {}
bool tx(srsran::rf_buffer_interface& tx_buffer, const srsran::rf_timestamp_interface& tx_time) override
{
return false;
}
bool rx_now(srsran::rf_buffer_interface& rx_buffer, srsran::rf_timestamp_interface& rxd_time) override
{
int nbytes = (int)(sizeof(cf_t) * rx_buffer.get_nof_samples());
cf_t* temp_buffer = rx_buffer.get(0);
// If the buffer is invalid, use internal temporal buffer
if (temp_buffer == nullptr) {
temp_buffer = buffer;
}
// As long as there are not enough samples
while (srsran_ringbuffer_status(&ringbuffer) < nbytes and running) {
run_async_slot();
}
if (not running) {
return true;
}
srsran_assert(srsran_ringbuffer_read(&ringbuffer, temp_buffer, nbytes) >= SRSRAN_SUCCESS,
"Error reading from ringbuffer");
return true;
}
void set_tx_freq(const uint32_t& carrier_idx, const double& freq) override {}
void set_rx_freq(const uint32_t& carrier_idx, const double& freq) override {}
void release_freq(const uint32_t& carrier_idx) override {}
void set_tx_gain(const float& gain) override {}
void set_rx_gain_th(const float& gain) override {}
void set_rx_gain(const float& gain) override {}
void set_tx_srate(const double& srate) override {}
void set_rx_srate(const double& srate) override {}
void set_channel_rx_offset(uint32_t ch, int32_t offset_samples) override {}
double get_freq_offset() override { return 0; }
float get_rx_gain() override { return 0; }
bool is_continuous_tx() override { return false; }
bool get_is_start_of_burst() override { return false; }
bool is_init() override { return false; }
void reset() override { running = false; }
srsran_rf_info_t* get_info() override { return nullptr; }
};
class dummy_stack : public srsue::stack_interface_phy_sa_nr
{
private:
srslog::basic_logger& logger = srslog::fetch_basic_logger("STACK");
bool pending_tti = false;
std::mutex pending_tti_mutex;
std::condition_variable pending_tti_cvar;
std::atomic<bool> running = {true};
public:
dummy_stack() { logger.set_level(srslog::str_to_basic_level("info")); }
void in_sync() override {}
void out_of_sync() override {}
void run_tti(const uint32_t tti) override
throttled_dummy_stack(const ue_dummy_stack::args_t& args, srsue::phy_interface_stack_nr& phy) :
ue_dummy_stack(args, phy)
{}
void wait_tti() override
{
logger.debug("Run TTI %d", tti);
// Wait for tick
std::unique_lock<std::mutex> lock(pending_tti_mutex);
while (not pending_tti and running) {
@ -178,35 +45,6 @@ public:
pending_tti = false;
pending_tti_cvar.notify_all();
}
void cell_search_found_cell(const cell_search_result_t& result) override
{
// Unpack MIB with ASN1
asn1::rrc_nr::mib_s mib;
asn1::cbit_ref cbit(result.pbch_msg.payload, SRSRAN_PBCH_MSG_NR_SZ);
mib.unpack(cbit);
// Convert MIB to JSON
asn1::json_writer json;
mib.to_json(json);
// Convert CSI to string
std::array<char, 512> csi_info = {};
srsran_csi_meas_info_short(&result.measurements, csi_info.data(), (uint32_t)csi_info.size());
logger.info("Cell found pci=%d %s MIB: %s", result.pci, csi_info.data(), json.to_string().c_str());
}
int sf_indication(const uint32_t tti) override
{
logger.info("SF %d indication", tti);
return 0;
}
sched_rnti_t get_dl_sched_rnti_nr(const uint32_t tti) override { return sched_rnti_t(); }
sched_rnti_t get_ul_sched_rnti_nr(const uint32_t tti) override { return sched_rnti_t(); }
void new_grant_dl(const uint32_t cc_idx, const mac_nr_grant_dl_t& grant, tb_action_dl_t* action) override {}
void tb_decoded(const uint32_t cc_idx, const mac_nr_grant_dl_t& grant, tb_action_dl_result_t result) override {}
void new_grant_ul(const uint32_t cc_idx, const mac_nr_grant_ul_t& grant, tb_action_ul_t* action) override {}
void prach_sent(uint32_t tti, uint32_t s_id, uint32_t t_id, uint32_t f_id, uint32_t ul_carrier_id) override {}
bool sr_opportunity(uint32_t tti, uint32_t sr_id, bool meas_gap, bool ul_sch_tx) override { return false; }
void tick()
{
@ -229,28 +67,189 @@ public:
};
struct args_t {
double srate_hz = 11.52e6;
srsran_carrier_nr_t carrier = SRSRAN_DEFAULT_CARRIER_NR;
srsran_ssb_patern_t ssb_pattern = SRSRAN_SSB_PATTERN_A;
uint32_t ssb_periodicity_ms = 10;
srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_15kHz;
srsran_duplex_mode_t duplex_mode = SRSRAN_DUPLEX_MODE_FDD;
uint32_t duration_ms = 1000;
// Generic parameters
double srate_hz = 11.52e6;
srsran_carrier_nr_t base_carrier = SRSRAN_DEFAULT_CARRIER_NR;
srsran_ssb_patern_t ssb_pattern = SRSRAN_SSB_PATTERN_A;
srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_15kHz;
srsran_duplex_mode_t duplex_mode = SRSRAN_DUPLEX_MODE_FDD;
uint32_t duration_ms = 1000;
std::string phy_log_level = "warning";
std::string stack_log_level = "warning";
void set_ssb_from_band(uint16_t band)
// Simulation parameters
uint32_t sim_ssb_periodicity_ms = 10;
std::set<uint32_t> sim_cell_pci;
// RF parameters
std::string rf_device_name = "auto";
std::string rf_device_args = "auto";
std::string rf_log_level = "info";
float rf_rx_gain_dB = 20.0f;
float rf_freq_offset_Hz = 0.0f;
void set_ssb_from_band()
{
srsran::srsran_band_helper bands;
duplex_mode = bands.get_duplex_mode(band);
ssb_scs = bands.get_ssb_scs(band);
ssb_pattern = bands.get_ssb_pattern(band, ssb_scs);
carrier.ssb_center_freq_hz = bands.get_ssb_center_freq(carrier);
// Deduce band number
uint16_t band = bands.get_band_from_dl_freq_Hz(base_carrier.dl_center_frequency_hz);
srsran_assert(band != UINT16_MAX, "Invalid band");
// Deduce point A in Hz
double pointA_Hz =
bands.get_abs_freq_point_a_from_center_freq(base_carrier.nof_prb, base_carrier.dl_center_frequency_hz);
// Deduce DL center frequency ARFCN
uint32_t pointA_arfcn = bands.freq_to_nr_arfcn(pointA_Hz);
srsran_assert(pointA_arfcn != 0, "Invalid frequency");
// Select a valid SSB subcarrier spacing
ssb_scs = bands.get_ssb_scs(band);
// Deduce SSB center frequency ARFCN
uint32_t ssb_arfcn = bands.get_abs_freq_ssb_arfcn(band, ssb_scs, pointA_arfcn);
srsran_assert(ssb_arfcn, "Invalid SSB center frequency");
duplex_mode = bands.get_duplex_mode(band);
ssb_pattern = bands.get_ssb_pattern(band, ssb_scs);
base_carrier.ssb_center_freq_hz = bands.nr_arfcn_to_freq(ssb_arfcn);
}
};
// shorten boost program options namespace
namespace bpo = boost::program_options;
static void pci_list_parse_helper(std::string& list_str, std::set<uint32_t>& list)
{
if (list_str == "all") {
// Add all possible cells
for (int i = 0; i < 504; i++) {
list.insert(i);
}
} else if (list_str == "none") {
// Do nothing
} else if (not list_str.empty()) {
// Remove spaces from neightbour cell list
list_str = srsran::string_remove_char(list_str, ' ');
// Add cell to known cells
srsran::string_parse_list(list_str, ',', list);
}
}
int parse_args(int argc, char** argv, args_t& args)
{
int ret = SRSRAN_SUCCESS;
std::string simulation_cell_list = "";
bpo::options_description options("General options");
bpo::options_description phy("Physical layer options");
bpo::options_description stack("Stack options");
bpo::options_description over_the_air("Mode 1: Over the air options (Default)");
bpo::options_description simulation("Mode 2: Simulation options (enabled if simulation_cell_list is not empty)");
// clang-format off
over_the_air.add_options()
("rf.device_name", bpo::value<std::string>(&args.rf_device_name)->default_value(args.rf_device_name), "RF Device Name")
("rf.device_args", bpo::value<std::string>(&args.rf_device_args)->default_value(args.rf_device_args), "RF Device arguments")
("rf.log_level", bpo::value<std::string>(&args.rf_log_level)->default_value(args.rf_log_level), "RF Log level (none, warning, info, debug)")
("rf.rx_gain", bpo::value<float>(&args.rf_rx_gain_dB)->default_value(args.rf_rx_gain_dB), "RF Receiver gain in dB")
("rf.freq_offset", bpo::value<float>(&args.rf_freq_offset_Hz)->default_value(args.rf_freq_offset_Hz), "RF Frequency offset")
;
simulation.add_options()
("sim.pci_list", bpo::value<std::string>(&simulation_cell_list)->default_value(simulation_cell_list), "Comma separated PCI cell list to simulate")
("sim.bw", bpo::value<uint32_t>(&args.base_carrier.nof_prb)->default_value(args.base_carrier.nof_prb), "Carrier bandwidth in RB")
("sim.ssb_period", bpo::value<uint32_t>(&args.sim_ssb_periodicity_ms)->default_value(args.sim_ssb_periodicity_ms), "SSB period in ms")
;
phy.add_options()
("phy.srate", bpo::value<double>(&args.srate_hz)->default_value(args.srate_hz), "Sampling Rate in Hz")
("phy.log.level", bpo::value<std::string>(&args.phy_log_level)->default_value(args.phy_log_level), "Physical layer logging level")
;
stack.add_options()
("stack.log.level", bpo::value<std::string>(&args.stack_log_level)->default_value(args.stack_log_level), "Stack logging level")
;
options.add(over_the_air).add(simulation).add(phy).add(stack).add_options()
("help,h", "Show this message")
("duration", bpo::value<uint32_t>(&args.duration_ms)->default_value(args.duration_ms), "Duration of the test in milli-seconds")
("freq_dl", bpo::value<double>(&args.base_carrier.dl_center_frequency_hz)->default_value(args.base_carrier.dl_center_frequency_hz), "Carrier center frequency in Hz")
;
// 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 = SRSRAN_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 = SRSRAN_ERROR;
}
// Parse PCI lists
pci_list_parse_helper(simulation_cell_list, args.sim_cell_pci);
args.set_ssb_from_band();
return ret;
}
class dummy_ue
{
private:
throttled_dummy_stack stack;
srsue::phy_nr_sa phy;
public:
struct args_t {
srsue::phy_nr_sa::args_t phy;
ue_dummy_stack::args_t stack;
};
dummy_ue(const args_t& args, srsran::radio_interface_phy& radio) : stack(args.stack, phy), phy(stack, radio)
{
srsran_assert(phy.init(args.phy), "Failed to initialise PHY");
}
bool start_cell_search(const srsue::phy_interface_stack_sa_nr::cell_search_args_t& args)
{
return phy.start_cell_search(args);
}
void run_tti() { stack.tick(); }
void stop()
{
// First transition PHY to IDLE
phy.reset();
// Make sure PHY transitioned to IDLE
// ...
// Stop stack, it will let PHY free run
stack.stop();
// Stop PHY
phy.stop();
}
};
int main(int argc, char** argv)
{
srsran_debug_handle_crash(argc, argv);
// Parse Test arguments
args_t args;
srsran_assert(parse_args(argc, argv, args) == SRSRAN_SUCCESS, "Failed to parse arguments");
// Initialise logging infrastructure
srslog::init();
@ -258,53 +257,77 @@ int main(int argc, char** argv)
// Radio can be constructed from different options
std::shared_ptr<srsran::radio_interface_phy> radio = nullptr;
// Create Radio as gNb emulator
gnb_emulator::args_t gnb_args = {};
gnb_args.srate_hz = args.srate_hz;
gnb_args.carrier = args.carrier;
gnb_args.ssb_pattern = args.ssb_pattern;
gnb_args.ssb_periodicity_ms = args.ssb_periodicity_ms;
gnb_args.duplex_mode = args.duplex_mode;
radio = std::make_shared<gnb_emulator>(gnb_args);
// Build radio
if (not args.sim_cell_pci.empty()) {
// Create Radio as gNb emulator if the device RF name is not defined
gnb_rf_emulator::args_t gnb_args = {};
gnb_args.srate_hz = args.srate_hz;
gnb_args.base_carrier = args.base_carrier;
gnb_args.ssb_pattern = args.ssb_pattern;
gnb_args.ssb_periodicity_ms = args.sim_ssb_periodicity_ms;
gnb_args.ssb_scs = args.ssb_scs;
gnb_args.duplex_mode = args.duplex_mode;
gnb_args.pci_list = args.sim_cell_pci;
// Create stack
dummy_stack stack;
radio = std::make_shared<gnb_rf_emulator>(gnb_args);
} else {
// Create an actual radio based on RF
srsran::rf_args_t rf_args = {};
rf_args.type = "multi";
rf_args.log_level = args.rf_log_level;
rf_args.srate_hz = args.srate_hz;
rf_args.rx_gain = args.rf_rx_gain_dB;
rf_args.nof_carriers = 1;
rf_args.nof_antennas = 1;
rf_args.device_args = args.rf_device_args;
rf_args.device_name = args.rf_device_name;
rf_args.freq_offset = args.rf_freq_offset_Hz;
// Create UE PHY
srsue::phy_nr_sa phy(stack, *radio);
// Instantiate
std::shared_ptr<srsran::radio> r = std::make_shared<srsran::radio>();
srsran_assert(r->init(rf_args, nullptr) == SRSRAN_SUCCESS, "Failed Radio initialisation");
// Initialise PHY, it will instantly start free running
srsue::phy_nr_sa::args_t phy_args = {};
phy_args.srate_hz = args.srate_hz;
phy.init(phy_args);
// Move to base pointer
radio = std::move(r);
// Set sampling rate
radio->set_rx_srate(args.srate_hz);
// Set DL center frequency
radio->set_rx_freq(0, args.base_carrier.dl_center_frequency_hz);
// Set Rx gain
radio->set_rx_gain(args.rf_rx_gain_dB);
}
// Create dummy UE
dummy_ue::args_t ue_args = {};
ue_args.stack.log_level = args.stack_log_level;
ue_args.phy.log_level = args.phy_log_level;
ue_args.phy.srate_hz = args.srate_hz;
dummy_ue ue(ue_args, *radio);
// Transition PHY to cell search
srsue::phy_nr_sa::cell_search_args_t cell_search_req = {};
cell_search_req.center_freq_hz = args.carrier.dl_center_frequency_hz;
cell_search_req.ssb_freq_hz = args.carrier.ssb_center_freq_hz;
cell_search_req.center_freq_hz = args.base_carrier.dl_center_frequency_hz;
cell_search_req.ssb_freq_hz = args.base_carrier.ssb_center_freq_hz;
cell_search_req.ssb_scs = args.ssb_scs;
cell_search_req.ssb_pattern = args.ssb_pattern;
cell_search_req.duplex_mode = args.duplex_mode;
phy.start_cell_search(cell_search_req);
srsran_assert(ue.start_cell_search(cell_search_req), "Failed cell search start");
for (uint32_t i = 0; i < args.duration_ms; i++) {
stack.tick();
ue.run_tti();
}
// First transition PHY to IDLE
phy.reset();
// Make sure PHY transitioned to IDLE
// ...
// Stop stack, it will let PHY free run
stack.stop();
// Stop PHY
phy.stop();
// Tear down UE
ue.stop();
// Stop Radio
radio->reset();
// Erase radio
radio = nullptr;
return 0;
}

View File

@ -13,9 +13,12 @@
#ifndef SRSRAN_DUMMY_UE_STACK_H
#define SRSRAN_DUMMY_UE_STACK_H
#include <srsran/interfaces/ue_nr_interfaces.h>
#include "dummy_rx_harq_proc.h"
#include "dummy_tx_harq_proc.h"
#include "srsran/asn1/rrc_nr.h"
#include "srsran/interfaces/ue_nr_interfaces.h"
class ue_dummy_stack : public srsue::stack_interface_phy_nr
class ue_dummy_stack : public srsue::stack_interface_phy_sa_nr
{
public:
struct prach_metrics_t {
@ -28,6 +31,7 @@ public:
};
private:
srslog::basic_logger& logger = srslog::fetch_basic_logger("UE-STCK");
std::mutex rnti_mutex;
srsran_random_t random_gen = srsran_random_init(0x1323);
srsran_rnti_type_t dl_rnti_type = srsran_rnti_type_c;
@ -46,20 +50,29 @@ private:
public:
struct args_t {
uint16_t rnti = 0x1234; ///< C-RNTI for PUSCH and PDSCH transmissions
uint32_t sr_period = 0; ///< Indicates positive SR period in number of opportunities. Set to 0 to disable.
uint32_t prach_period = 0; ///< Requests PHY to transmit PRACH periodically in frames. Set to 0 to disable.
uint16_t rnti = 0x1234; ///< C-RNTI for PUSCH and PDSCH transmissions
uint32_t sr_period = 0; ///< Indicates positive SR period in number of opportunities. Set to 0 to disable.
uint32_t prach_period = 0; ///< Requests PHY to transmit PRACH periodically in frames. Set to 0 to disable.
std::string log_level = "warning";
};
ue_dummy_stack(const args_t& args, srsue::phy_interface_stack_nr& phy_) :
rnti(args.rnti), sr_period(args.sr_period), prach_period(args.prach_period), phy(phy_)
{
logger.set_level(srslog::str_to_basic_level(args.log_level));
valid = true;
}
~ue_dummy_stack() { srsran_random_free(random_gen); }
virtual void wait_tti()
{
// Do nothing
}
void in_sync() override {}
void out_of_sync() override {}
void run_tti(const uint32_t tti) override
{
wait_tti();
// Run PRACH
if (prach_period != 0) {
uint32_t slot_idx = tti % SRSRAN_NSLOTS_PER_FRAME_NR(srsran_subcarrier_spacing_15kHz);
@ -124,9 +137,33 @@ public:
metrics_t get_metrics() { return metrics; }
void set_phy_config_complete(bool status) override
{
void set_phy_config_complete(bool status) override {}
void cell_search_found_cell(const cell_search_result_t& result) override
{
// Unpack MIB with ASN1
asn1::rrc_nr::mib_s mib_asn1;
asn1::cbit_ref cbit(result.pbch_msg.payload, SRSRAN_PBCH_MSG_NR_SZ);
mib_asn1.unpack(cbit);
// Convert MIB to JSON
asn1::json_writer json;
mib_asn1.to_json(json);
// Unpack MIB with C lib
srsran_mib_nr_t mib_c = {};
srsran_pbch_msg_nr_mib_unpack(&result.pbch_msg, &mib_c);
// Convert MIB from C lib to info
std::array<char, 512> mib_info = {};
srsran_pbch_msg_nr_mib_info(&mib_c, mib_info.data(), (uint32_t)mib_info.size());
// Convert CSI to string
std::array<char, 512> csi_info = {};
srsran_csi_meas_info_short(&result.measurements, csi_info.data(), (uint32_t)csi_info.size());
logger.info(
"Cell found pci=%d %s %s ASN1: %s", result.pci, mib_info.data(), csi_info.data(), json.to_string().c_str());
}
};