/** * * \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. * */ #include "dummy_gnb_stack.h" #include "dummy_ue_stack.h" #include "srsran/common/phy_cfg_nr_default.h" #include "srsran/common/test_common.h" #include "test_bench.h" #include #include // shorten boost program options namespace namespace bpo = boost::program_options; test_bench::args_t::args_t(int argc, char** argv) { std::string reference_cfg_str = ""; bpo::options_description options("Test bench options"); bpo::options_description options_gnb_stack("gNb stack and scheduling related options"); bpo::options_description options_gnb_phy("gNb PHY related options"); bpo::options_description options_ue_stack("UE stack options"); bpo::options_description options_ue_phy("UE stack options"); uint16_t rnti = 0x1234; gnb_stack.pdsch.slots = "0,1,2,3,4,5"; gnb_stack.pusch.slots = "6,7,8,9"; // clang-format off options.add_options() ("rnti", bpo::value(&rnti)->default_value(rnti), "UE RNTI") ("duration", bpo::value(&durations_slots)->default_value(durations_slots), "Test duration in slots") ("lib.log.level", bpo::value(&phy_lib_log_level)->default_value(phy_lib_log_level), "PHY librray log level") ("reference", bpo::value(&reference_cfg_str)->default_value(reference_cfg_str), "Reference PHY configuration arguments") ; options_gnb_stack.add_options() ("gnb.stack.pdcch.aggregation_level", bpo::value(&gnb_stack.pdcch_aggregation_level)->default_value(gnb_stack.pdcch_aggregation_level), "PDCCH aggregation level") ("gnb.stack.pdsch.candidate", bpo::value(&gnb_stack.pdcch_dl_candidate)->default_value(gnb_stack.pdcch_dl_candidate), "PDCCH candidate index for PDSCH") ("gnb.stack.pdsch.start", bpo::value(&gnb_stack.pdsch.rb_start)->default_value(0), "PDSCH scheduling frequency allocation start") ("gnb.stack.pdsch.length", bpo::value(&gnb_stack.pdsch.rb_length)->default_value(gnb_stack.pdsch.rb_length), "PDSCH scheduling frequency allocation length") ("gnb.stack.pdsch.slots", bpo::value(&gnb_stack.pdsch.slots)->default_value(gnb_stack.pdsch.slots), "Slots enabled for PDSCH") ("gnb.stack.pdsch.mcs", bpo::value(&gnb_stack.pdsch.mcs)->default_value(gnb_stack.pdsch.mcs), "PDSCH scheduling modulation code scheme") ("gnb.stack.pusch.candidate", bpo::value(&gnb_stack.pdcch_ul_candidate)->default_value(gnb_stack.pdcch_ul_candidate), "PDCCH candidate index for PUSCH") ("gnb.stack.pusch.start", bpo::value(&gnb_stack.pusch.rb_start)->default_value(0), "PUSCH scheduling frequency allocation start") ("gnb.stack.pusch.length", bpo::value(&gnb_stack.pusch.rb_length)->default_value(gnb_stack.pusch.rb_length), "PUSCH scheduling frequency allocation length") ("gnb.stack.pusch.slots", bpo::value(&gnb_stack.pusch.slots)->default_value(gnb_stack.pusch.slots), "Slots enabled for PUSCH") ("gnb.stack.pusch.mcs", bpo::value(&gnb_stack.pusch.mcs)->default_value(gnb_stack.pusch.mcs), "PUSCH scheduling modulation code scheme") ("gnb.stack.log.level", bpo::value(&gnb_stack.log_level)->default_value(gnb_stack.log_level), "Stack log level") ("gnb.stack.use_dummy_sched", bpo::value(&gnb_stack.use_dummy_sched)->default_value(true), "Use dummy or real NR scheduler") ; options_gnb_phy.add_options() ("gnb.phy.nof_threads", bpo::value(&gnb_phy.nof_phy_threads)->default_value(1), "Number of threads") ("gnb.phy.log.level", bpo::value(&gnb_phy.log.phy_level)->default_value("warning"), "gNb PHY log level") ("gnb.phy.log.hex_limit", bpo::value(&gnb_phy.log.phy_hex_limit)->default_value(0), "gNb PHY log hex limit") ("gnb.phy.log.id_preamble", bpo::value(&gnb_phy.log.id_preamble)->default_value("GNB/"), "gNb PHY log ID preamble") ("gnb.phy.pusch.max_iter", bpo::value(&gnb_phy.pusch_max_nof_iter)->default_value(10), "PUSCH LDPC max number of iterations") ; options_ue_phy.add_options() ("ue.phy.nof_threads", bpo::value(&ue_phy.nof_phy_threads)->default_value(1), "Number of threads") ("ue.phy.log.level", bpo::value(&ue_phy.log.phy_level)->default_value("warning"), "UE PHY log level") ("ue.phy.log.hex_limit", bpo::value(&ue_phy.log.phy_hex_limit)->default_value(0), "UE PHY log hex limit") ("ue.phy.log.id_preamble", bpo::value(&ue_phy.log.id_preamble)->default_value(" UE/"), "UE PHY log ID preamble") ; options_ue_stack.add_options() ("ue.stack.sr.period", bpo::value(&ue_stack.sr_period)->default_value(ue_stack.sr_period), "SR period in number of opportunities. Set 0 to disable and 1 for all.") ("ue.stack.prach.period", bpo::value(&ue_stack.prach_period)->default_value(ue_stack.prach_period), "PRACH period in SFN. Set 0 to disable and 1 for all.") ("ue.stack.prach.preamble", bpo::value(&ue_stack.prach_preamble)->default_value(ue_stack.prach_preamble), "PRACH preamble. Set 0 to disable and 1 for all.") ; options.add(options_gnb_stack).add(options_gnb_phy).add(options_ue_stack).add(options_ue_phy).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; return; } // help option was given or error - print usage and exit if (vm.count("help")) { std::cout << "Usage: " << argv[0] << " [OPTIONS] config_file" << std::endl << std::endl; std::cout << options << std::endl << std::endl; return; } // Load default reference configuration phy_cfg = srsran::phy_cfg_nr_default_t(srsran::phy_cfg_nr_default_t::reference_cfg_t(reference_cfg_str)); // Calculate sampling rate in Hz srate_hz = (double)(srsran_min_symbol_sz_rb(phy_cfg.carrier.nof_prb) * SRSRAN_SUBC_SPACING_NR(phy_cfg.carrier.scs)); cell_list.resize(1); cell_list[0].carrier = phy_cfg.carrier; cell_list[0].rf_port = 0; cell_list[0].cell_id = 0; cell_list[0].pdcch = phy_cfg.pdcch; ue_stack.rnti = rnti; gnb_stack.rnti = rnti; gnb_stack.phy_cfg = phy_cfg; gnb_stack.wait_preamble = ue_stack.prach_preamble > 0; if (gnb_stack.pdsch.rb_length == 0) { gnb_stack.pdsch.rb_length = phy_cfg.carrier.nof_prb; gnb_stack.pdsch.rb_start = 0; } if (gnb_stack.pusch.rb_length == 0) { gnb_stack.pusch.rb_length = phy_cfg.carrier.nof_prb; gnb_stack.pdsch.rb_start = 0; } // Flag configuration as valid valid = true; } int main(int argc, char** argv) { srslog::init(); // Parse test bench arguments test_bench::args_t args(argc, argv); // Parse arguments TESTASSERT(args.valid); // Create test bench test_bench tb(args); // Assert bench is initialised correctly TESTASSERT(tb.is_initialised()); // Run per TTI basis while (tb.run_tti()) { ; // Do nothing } // Stop test bench tb.stop(); // Flush log srslog::flush(); // Retrieve MAC metrics test_bench::metrics_t metrics = tb.get_gnb_metrics(); // Print PDSCH metrics if scheduled if (metrics.gnb_stack.mac.tx_pkts > 0) { float pdsch_bler = 0.0f; pdsch_bler = (float)metrics.gnb_stack.mac.tx_errors / (float)metrics.gnb_stack.mac.tx_pkts; float pdsch_shed_rate = 0.0f; pdsch_shed_rate = (float)metrics.gnb_stack.mac.tx_brate / (float)metrics.gnb_stack.mac.tx_pkts / 1000.0f; srsran::console("PDSCH:\n"); srsran::console(" Count: %d\n", metrics.gnb_stack.mac.tx_pkts); srsran::console(" BLER: %f\n", pdsch_bler); srsran::console(" Sched Rate: %f Mbps\n", pdsch_shed_rate); srsran::console(" Net Rate: %f Mbps\n", (1.0f - pdsch_bler) * pdsch_shed_rate); srsran::console(" Retx Rate: %f Mbps\n", pdsch_bler * pdsch_shed_rate); srsran::console("\n"); } // Print PUSCH metrics if scheduled if (metrics.gnb_stack.mac.rx_pkts > 0) { float pusch_bler = 0.0f; if (metrics.gnb_stack.mac.rx_pkts != 0) { pusch_bler = (float)metrics.gnb_stack.mac.rx_errors / (float)metrics.gnb_stack.mac.rx_pkts; } float pusch_shed_rate = 0.0f; if (metrics.gnb_stack.mac.rx_pkts != 0) { pusch_shed_rate = (float)metrics.gnb_stack.mac.rx_brate / (float)metrics.gnb_stack.mac.rx_pkts / 1000.0f; } srsran::console("PUSCH:\n"); srsran::console(" Count: %d\n", metrics.gnb_stack.mac.rx_pkts); srsran::console(" BLER: %f\n", pusch_bler); srsran::console(" Sched Rate: %f Mbps\n", pusch_shed_rate); srsran::console(" Net Rate: %f Mbps\n", (1.0f - pusch_bler) * pusch_shed_rate); srsran::console(" Retx Rate: %f Mbps\n", pusch_bler * pusch_shed_rate); srsran::console("\n"); } // Print PRACH if (metrics.ue_stack.prach.size() > 0) { srsran::console("PRACH:\n"); srsran::console(" UE transmitted:\n"); srsran::console(" +------------+------------+\n"); srsran::console(" | %10s | %10s |\n", "preamble", "count"); srsran::console(" +------------+------------+\n"); for (const auto& p : metrics.ue_stack.prach) { srsran::console(" | %10d | %10d |\n", p.first, p.second.count); // Ensure the detected count matches with transmission TESTASSERT(metrics.gnb_stack.prach.count(p.first)); TESTASSERT(metrics.gnb_stack.prach[p.first].count == p.second.count); } srsran::console(" +------------+------------+\n\n"); srsran::console(" GNB detected:\n"); srsran::console(" +------------+------------+------------+\n"); srsran::console(" | %10s | %10s | %10s |\n", "preamble", "count", "avg TA"); srsran::console(" +------------+------------+------------+\n"); for (const auto& p : metrics.gnb_stack.prach) { srsran::console(" | %10d | %10d | %10.1f |\n", p.first, p.second.count, p.second.avg_ta); // Ensure all detected preambles were transmitted TESTASSERT(metrics.ue_stack.prach.count(p.first) > 0); } srsran::console(" +------------+------------+------------+\n\n"); } // Print PUCCH if (metrics.gnb_stack.pucch.count > 0) { srsran::console("PUCCH DMRS Receiver metrics:\n"); srsran::console(" +------------+------------+------------+------------+\n"); srsran::console(" | %10s | %10s | %10s | %10s |\n", "Measure", "Average", "Min", "Max"); srsran::console(" +------------+------------+------------+------------+\n"); srsran::console(" | %10s | %+10.2f | %+10.2f | %+10.2f |\n", "EPRE (dB)", metrics.gnb_stack.pucch.epre_db_avg, metrics.gnb_stack.pucch.epre_db_min, metrics.gnb_stack.pucch.epre_db_min); srsran::console(" | %10s | %+10.2f | %+10.2f | %+10.2f |\n", "RSRP (dB)", metrics.gnb_stack.pucch.rsrp_db_avg, metrics.gnb_stack.pucch.rsrp_db_min, metrics.gnb_stack.pucch.rsrp_db_max); srsran::console(" | %10s | %+10.2f | %+10.2f | %+10.2f |\n", "SINR (dB)", metrics.gnb_stack.pucch.snr_db_avg, metrics.gnb_stack.pucch.snr_db_min, metrics.gnb_stack.pucch.snr_db_max); srsran::console(" | %10s | %+10.2f | %+10.2f | %+10.2f |\n", "TA (us)", metrics.gnb_stack.pucch.ta_us_avg, metrics.gnb_stack.pucch.ta_us_min, metrics.gnb_stack.pucch.ta_us_max); srsran::console(" +------------+------------+------------+------------+\n"); } // Print SR if (metrics.ue_stack.sr_count > 0) { srsran::console("SR:\n"); srsran::console(" +------------+------------+------------+\n"); srsran::console(" | %10s | %10s | %10s |\n", "Transmit'd", "Received", "Detection"); srsran::console(" +------------+------------+------------+\n"); srsran::console(" | %10d | %10d | %10.5f |\n", metrics.ue_stack.sr_count, metrics.gnb_stack.sr_count, (double)metrics.gnb_stack.sr_count / (double)metrics.ue_stack.sr_count); srsran::console(" +------------+------------+------------+\n"); } // Assert metrics TESTASSERT_EQ(0, metrics.gnb_stack.mac.tx_errors); TESTASSERT_EQ(0, metrics.gnb_stack.mac.rx_errors); TESTASSERT(metrics.ue_stack.sr_count == metrics.gnb_stack.sr_count); // If reached here, the test is successful return SRSRAN_SUCCESS; }