/* * Copyright 2013-2019 Software Radio Systems Limited * * This file is part of srsLTE. * * srsLTE is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * srsLTE is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * A copy of the GNU Affero General Public License can be found in * the LICENSE file in the top-level directory of this distribution * and at http://www.gnu.org/licenses/. * */ #include "lib/include/srslte/common/pdu.h" #include "scheduler_test_common.h" #include "scheduler_test_utils.h" #include "srsenb/hdr/stack/mac/scheduler.h" using namespace srsenb; template void erase_if(MapContainer& c, Predicate should_remove) { for (auto it = c.begin(); it != c.end();) { if (should_remove(*it)) { it = c.erase(it); } else { ++it; } } } /******************* * Logging * *******************/ class sched_test_log final : public srslte::test_log_filter { public: sched_test_log() : srslte::test_log_filter("TEST") { exit_on_error = true; } ~sched_test_log() override { log_diagnostics(); } void log_diagnostics() override { info("[TESTER] Number of assertion warnings: %u\n", warn_counter); info("[TESTER] Number of assertion errors: %u\n", error_counter); info("[TESTER] This was the seed: %u\n", seed); } }; srslte::scoped_log log_global{}; /****************************** * Scheduler Tester for CA *****************************/ class sched_ca_tester : public srsenb::sched { public: struct tti_info_t { tti_params_t tti_params{10241}; uint32_t nof_prachs = 0; std::vector dl_sched_result; std::vector ul_sched_result; }; int cell_cfg(const std::vector& cell_params) final; int add_user(uint16_t rnti, const ue_cfg_t& ue_cfg_); void new_test_tti(uint32_t tti_rx); int process_tti_events(const tti_ev& tti_events); int process_ack_txs(); int set_acks(); void run_tti(uint32_t tti, const tti_ev& tti_events); int process_results(); // args sim_sched_args sim_args; ///< arguments used to generate TTI events // tti specific params tti_info_t tti_info; uint32_t tti_counter = 0; // testers std::vector output_tester; std::unique_ptr ue_tester; std::unique_ptr sched_stats; private: struct ack_info_t { uint16_t rnti; uint32_t tti; uint32_t ue_cc_idx; bool ack = false; uint32_t retx_delay = 0; srsenb::dl_harq_proc dl_harq; }; struct ul_ack_info_t { uint16_t rnti; uint32_t tti_ack, tti_tx_ul; uint32_t ue_cc_idx; bool ack = false; srsenb::ul_harq_proc ul_harq; }; std::multimap to_ack; std::multimap to_ul_ack; }; int sched_ca_tester::cell_cfg(const std::vector& cell_params) { sched::cell_cfg(cell_params); // call parent ue_tester.reset(new user_state_sched_tester{cell_params}); sched_stats.reset(new sched_result_stats{cell_params}); output_tester.clear(); output_tester.reserve(cell_params.size()); for (uint32_t i = 0; i < cell_params.size(); ++i) { output_tester.emplace_back(sched_cell_params[i]); } return SRSLTE_SUCCESS; } int sched_ca_tester::add_user(uint16_t rnti, const ue_cfg_t& ue_cfg_) { CONDERROR(ue_cfg(rnti, ue_cfg_) != SRSLTE_SUCCESS, "[TESTER] Configuring new user rnti=0x%x to sched\n", rnti); dl_sched_rar_info_t rar_info = {}; rar_info.prach_tti = tti_info.tti_params.tti_rx; rar_info.temp_crnti = rnti; rar_info.msg3_size = 7; rar_info.preamble_idx = tti_info.nof_prachs++; uint32_t pcell_idx = ue_cfg_.supported_cc_list[0].enb_cc_idx; dl_rach_info(pcell_idx, rar_info); ue_tester->add_user(rnti, rar_info.preamble_idx, ue_cfg_); log_global->info("[TESTER] Adding user rnti=0x%x\n", rnti); return SRSLTE_SUCCESS; } void sched_ca_tester::new_test_tti(uint32_t tti_rx) { tti_info.tti_params = tti_params_t{tti_rx}; tti_info.nof_prachs = 0; tti_info.dl_sched_result.clear(); tti_info.ul_sched_result.clear(); ue_tester->new_tti(tti_rx); } int sched_ca_tester::process_tti_events(const tti_ev& tti_ev) { for (const tti_ev::user_cfg_ev& ue_ev : tti_ev.user_updates) { // There is a new configuration if (ue_ev.ue_cfg != nullptr) { if (not ue_tester->user_exists(ue_ev.rnti)) { // new user TESTASSERT(add_user(ue_ev.rnti, *ue_ev.ue_cfg) == SRSLTE_SUCCESS); } else { // reconfiguration TESTASSERT(ue_cfg(ue_ev.rnti, *ue_ev.ue_cfg) == SRSLTE_SUCCESS); ue_tester->user_reconf(ue_ev.rnti, *ue_ev.ue_cfg); } } // There is a user to remove if (ue_ev.rem_user) { // bearer_ue_rem(ue_ev.rnti, 0); ue_rem(ue_ev.rnti); ue_tester->rem_user(ue_ev.rnti); log_global->info("[TESTER] Removing user rnti=0x%x\n", ue_ev.rnti); } // configure carriers if (ue_ev.bearer_cfg != nullptr) { CONDERROR(not ue_tester->user_exists(ue_ev.rnti), "User rnti=0x%x does not exist\n", ue_ev.rnti); // TODO: Instantiate more bearers bearer_ue_cfg(ue_ev.rnti, 0, ue_ev.bearer_cfg.get()); } // push UL SRs and DL packets if (ue_ev.buffer_ev != nullptr) { auto* user = ue_tester->get_user_state(ue_ev.rnti); CONDERROR(user == nullptr, "TESTER ERROR: Trying to schedule data for user that does not exist\n"); if (ue_ev.buffer_ev->dl_data > 0) { // If Msg3 has already been received if (user->msg3_tti >= 0 and (uint32_t) user->msg3_tti <= tti_info.tti_params.tti_rx) { // If Msg4 not yet sent, allocate data in SRB0 buffer uint32_t lcid = (user->msg4_tti >= 0) ? 2 : 0; uint32_t pending_dl_new_data = ue_db[ue_ev.rnti].get_pending_dl_new_data(); if (lcid == 2 and not user->drb_cfg_flag) { // If RRCSetup finished if (pending_dl_new_data == 0) { // setup lcid==2 bearer sched::ue_bearer_cfg_t cfg = {}; cfg.direction = ue_bearer_cfg_t::BOTH; ue_tester->bearer_cfg(ue_ev.rnti, 2, cfg); bearer_ue_cfg(ue_ev.rnti, 2, &cfg); } else { // Let SRB0 get emptied continue; } } // Update DL buffer uint32_t tot_dl_data = pending_dl_new_data + ue_ev.buffer_ev->dl_data; // TODO: derive pending based on rx dl_rlc_buffer_state(ue_ev.rnti, lcid, tot_dl_data, 0); // TODO: Check retx_queue } } if (ue_ev.buffer_ev->sr_data > 0 and user->drb_cfg_flag) { uint32_t tot_ul_data = ue_db[ue_ev.rnti].get_pending_ul_new_data(tti_info.tti_params.tti_tx_ul) + ue_ev.buffer_ev->sr_data; uint32_t lcid = 2; ul_bsr(ue_ev.rnti, lcid, tot_ul_data, true); } } } return SRSLTE_SUCCESS; } int sched_ca_tester::process_ack_txs() { /* check if user was removed. If so, clean respective acks */ erase_if(to_ack, [this](std::pair& elem) { return this->ue_db.count(elem.second.rnti) == 0; }); erase_if(to_ul_ack, [this](std::pair& elem) { return this->ue_db.count(elem.second.rnti) == 0; }); /* Ack DL HARQs */ for (const auto& ack_it : to_ack) { if (ack_it.second.tti != tti_info.tti_params.tti_rx) { continue; } const ack_info_t& dl_ack = ack_it.second; srsenb::dl_harq_proc* h = ue_db[dl_ack.rnti].get_dl_harq(ack_it.second.dl_harq.get_id(), dl_ack.ue_cc_idx); const srsenb::dl_harq_proc& hack = dl_ack.dl_harq; CONDERROR(hack.is_empty(), "[TESTER] The acked DL harq was not active\n"); bool ret = false; for (uint32_t tb = 0; tb < SRSLTE_MAX_TB; ++tb) { if (dl_ack.dl_harq.is_empty(tb)) { continue; } ret |= dl_ack_info(tti_info.tti_params.tti_rx, dl_ack.rnti, dl_ack.ue_cc_idx, tb, dl_ack.ack) > 0; } CONDERROR(not ret, "[TESTER] The dl harq proc that was ACKed does not exist\n"); if (dl_ack.ack) { CONDERROR(!h->is_empty(), "[TESTER] ACKed dl harq was not emptied\n"); CONDERROR(h->has_pending_retx(0, tti_info.tti_params.tti_tx_dl), "[TESTER] ACKed dl harq still has pending retx\n"); log_global->info("[TESTER] DL ACK tti=%u rnti=0x%x pid=%d\n", tti_info.tti_params.tti_rx, dl_ack.rnti, dl_ack.dl_harq.get_id()); } else { CONDERROR(h->is_empty() and hack.nof_retx(0) + 1 < hack.max_nof_retx(), "[TESTER] NACKed DL harq got emptied\n"); } } /* Ack UL HARQs */ for (const auto& ack_it : to_ul_ack) { if (ack_it.first != tti_info.tti_params.tti_rx) { continue; } const ul_ack_info_t& ul_ack = ack_it.second; srsenb::ul_harq_proc* h = ue_db[ul_ack.rnti].get_ul_harq(tti_info.tti_params.tti_rx, ul_ack.ue_cc_idx); const srsenb::ul_harq_proc& hack = ul_ack.ul_harq; CONDERROR(h == nullptr or h->get_tti() != hack.get_tti(), "[TESTER] UL Harq TTI does not match the ACK TTI\n"); CONDERROR(h->is_empty(0), "[TESTER] The acked UL harq is not active\n"); CONDERROR(hack.is_empty(0), "[TESTER] The acked UL harq was not active\n"); ul_crc_info(tti_info.tti_params.tti_rx, ul_ack.rnti, ul_ack.ue_cc_idx, ul_ack.ack); CONDERROR(!h->get_pending_data(), "[TESTER] UL harq lost its pending data\n"); CONDERROR(!h->has_pending_ack(), "[TESTER] ACK/NACKed UL harq should have a pending ACK\n"); if (ul_ack.ack) { CONDERROR(!h->is_empty(), "[TESTER] ACKed UL harq did not get emptied\n"); CONDERROR(h->has_pending_retx(), "[TESTER] ACKed UL harq still has pending retx\n"); log_global->info( "[TESTER] UL ACK tti=%u rnti=0x%x pid=%d\n", tti_info.tti_params.tti_rx, ul_ack.rnti, hack.get_id()); } else { // NACK CONDERROR(!h->is_empty() and !h->has_pending_retx(), "[TESTER] If NACKed, UL harq has to have pending retx\n"); CONDERROR(h->is_empty() and hack.nof_retx(0) + 1 < hack.max_nof_retx(), "[TESTER] Nacked UL harq did get emptied\n"); } } // erase processed acks to_ack.erase(tti_info.tti_params.tti_rx); to_ul_ack.erase(tti_info.tti_params.tti_rx); // bool ack = true; //(tti_data.tti_rx % 3) == 0; // if (tti_data.tti_rx >= FDD_HARQ_DELAY_MS) { // for (auto it = ue_db.begin(); it != ue_db.end(); ++it) { // uint16_t rnti = it->first; // srsenb::ul_harq_proc* h = ue_db[rnti].get_ul_harq(tti_data.tti_rx); // if (h != nullptr and not h->is_empty()) { // ul_crc_info(tti_data.tti_rx, rnti, ack); // } // } // } return SRSLTE_SUCCESS; } void sched_ca_tester::run_tti(uint32_t tti_rx, const tti_ev& tti_events) { new_test_tti(tti_rx); log_global->info("[TESTER] ---- tti=%u | nof_ues=%zd ----\n", tti_rx, ue_db.size()); process_tti_events(tti_events); process_ack_txs(); // before_sched(); // Call scheduler for all carriers tti_info.dl_sched_result.resize(sched_cell_params.size()); for (uint32_t i = 0; i < sched_cell_params.size(); ++i) { dl_sched(tti_info.tti_params.tti_tx_dl, i, tti_info.dl_sched_result[i]); } tti_info.ul_sched_result.resize(sched_cell_params.size()); for (uint32_t i = 0; i < sched_cell_params.size(); ++i) { ul_sched(tti_info.tti_params.tti_tx_ul, i, tti_info.ul_sched_result[i]); } process_results(); set_acks(); tti_counter++; } int sched_ca_tester::process_results() { for (uint32_t i = 0; i < sched_cell_params.size(); ++i) { TESTASSERT(ue_tester->test_all(i, tti_info.dl_sched_result[i], tti_info.ul_sched_result[i]) == SRSLTE_SUCCESS); TESTASSERT(output_tester[i].test_all( tti_info.tti_params, tti_info.dl_sched_result[i], tti_info.ul_sched_result[i]) == SRSLTE_SUCCESS); } sched_stats->process_results(tti_info.tti_params, tti_info.dl_sched_result, tti_info.ul_sched_result); return SRSLTE_SUCCESS; } int sched_ca_tester::set_acks() { for (uint32_t ccidx = 0; ccidx < sched_cell_params.size(); ++ccidx) { // schedule future acks for (uint32_t i = 0; i < tti_info.dl_sched_result[ccidx].nof_data_elems; ++i) { ack_info_t ack_data; ack_data.rnti = tti_info.dl_sched_result[ccidx].data[i].dci.rnti; ack_data.tti = FDD_HARQ_DELAY_MS + tti_info.tti_params.tti_tx_dl; ack_data.ue_cc_idx = ue_db[ack_data.rnti].get_cell_index(ccidx).second; const srsenb::dl_harq_proc* dl_h = ue_db[ack_data.rnti].get_dl_harq(tti_info.dl_sched_result[ccidx].data[i].dci.pid, ccidx); ack_data.dl_harq = *dl_h; if (ack_data.dl_harq.nof_retx(0) == 0) { ack_data.ack = randf() > sim_args.P_retx; } else { // always ack after three retxs ack_data.ack = ack_data.dl_harq.nof_retx(0) == 3; } // Remove harq from the ack list if there was a harq rewrite auto it = to_ack.begin(); while (it != to_ack.end() and it->first < ack_data.tti) { if (it->second.rnti == ack_data.rnti and it->second.dl_harq.get_id() == ack_data.dl_harq.get_id() and it->second.ue_cc_idx == ack_data.ue_cc_idx) { CONDERROR(it->second.tti + 2 * FDD_HARQ_DELAY_MS > ack_data.tti, "[TESTER] The retx dl harq id=%d was transmitted too soon\n", ack_data.dl_harq.get_id()); auto toerase_it = it++; to_ack.erase(toerase_it); continue; } ++it; } // add new ack to the list to_ack.insert(std::make_pair(ack_data.tti, ack_data)); } /* Schedule UL ACKs */ for (uint32_t i = 0; i < tti_info.ul_sched_result[ccidx].nof_dci_elems; ++i) { const auto& pusch = tti_info.ul_sched_result[ccidx].pusch[i]; ul_ack_info_t ack_data; ack_data.rnti = pusch.dci.rnti; ack_data.ul_harq = *ue_db[ack_data.rnti].get_ul_harq(tti_info.tti_params.tti_tx_ul, ccidx); ack_data.tti_tx_ul = tti_info.tti_params.tti_tx_ul; ack_data.tti_ack = tti_info.tti_params.tti_tx_ul + FDD_HARQ_DELAY_MS; ack_data.ue_cc_idx = ue_db[ack_data.rnti].get_cell_index(ccidx).second; if (ack_data.ul_harq.nof_retx(0) == 0) { ack_data.ack = randf() > sim_args.P_retx; } else { ack_data.ack = ack_data.ul_harq.nof_retx(0) == 3; } to_ul_ack.insert(std::make_pair(ack_data.tti_tx_ul, ack_data)); } } return SRSLTE_SUCCESS; } /****************************** * Scheduler Tests *****************************/ sim_sched_args generate_default_sim_args(uint32_t nof_prb, uint32_t nof_ccs) { sim_sched_args sim_args; sim_args.nof_ttis = 10240 + 10; sim_args.P_retx = 0.1; sim_args.ue_cfg = generate_default_ue_cfg(); // setup two cells std::vector cell_cfg(nof_ccs, generate_default_cell_cfg(nof_prb)); cell_cfg[0].scell_list.resize(1); cell_cfg[0].scell_list[0].enb_cc_idx = 1; cell_cfg[0].scell_list[0].cross_carrier_scheduling = false; cell_cfg[0].scell_list[0].ul_allowed = true; cell_cfg[1].cell.id = 2; // id=2 cell_cfg[1].scell_list = cell_cfg[0].scell_list; cell_cfg[1].scell_list[0].enb_cc_idx = 0; sim_args.cell_cfg = std::move(cell_cfg); sim_args.bearer_cfg = {}; sim_args.bearer_cfg.direction = srsenb::sched_interface::ue_bearer_cfg_t::BOTH; /* Setup Derived Params */ sim_args.ue_cfg.supported_cc_list.resize(nof_ccs); for (uint32_t i = 0; i < sim_args.ue_cfg.supported_cc_list.size(); ++i) { sim_args.ue_cfg.supported_cc_list[i].active = true; sim_args.ue_cfg.supported_cc_list[i].enb_cc_idx = i; } return sim_args; } int run_sim1() { /* Simulation Configuration Arguments */ uint32_t nof_prb = 25; uint32_t nof_ccs = 2; /* Simulation Objects Setup */ sched_sim_event_generator generator; // Setup scheduler sched_ca_tester tester; tester.sim_args = generate_default_sim_args(nof_prb, nof_ccs); tester.init(nullptr); TESTASSERT(tester.cell_cfg(tester.sim_args.cell_cfg) == SRSLTE_SUCCESS); /* Internal configurations. Do not touch */ float ul_sr_exps[] = {1, 4}; // log rand float dl_data_exps[] = {1, 4}; // log rand float P_ul_sr = randf() * 0.5, P_dl = randf() * 0.5; uint32_t tti_start = 0; // rand_int(0, 10240); const uint16_t rnti1 = 70; uint32_t pcell_idx = 0; /* Setup Simulation */ uint32_t prach_tti = 1, msg4_tot_delay = 10; // TODO: check correct value uint32_t msg4_size = 20; // TODO: Check uint32_t duration = 1000; auto process_ttis = [&generator, &tti_start, &tester]() { for (; tester.tti_counter <= generator.tti_counter;) { uint32_t tti = (tti_start + tester.tti_counter) % 10240; log_global->step(tti); tester.run_tti(tti, generator.tti_events[tester.tti_counter]); } }; /* Simulation */ // Event PRACH: PRACH takes place for "rnti1", and carrier "pcell_idx" generator.step_until(prach_tti); tti_ev::user_cfg_ev* user = generator.add_new_default_user(duration); user->ue_cfg->supported_cc_list[0].enb_cc_idx = pcell_idx; user->rnti = rnti1; process_ttis(); TESTASSERT(tester.ue_tester->user_exists(rnti1)); // Event (TTI=prach_tti+msg4_tot_delay): First Tx (Msg4). Goes in SRB0 and contains ConRes generator.step_tti(msg4_tot_delay); generator.add_dl_data(rnti1, msg4_size); process_ttis(); // Event (20 TTIs): Data back and forth auto generate_data = [&](uint32_t nof_ttis, float prob_dl, float prob_ul) { for (uint32_t i = 0; i < nof_ttis; ++i) { generator.step_tti(); bool ul_flag = randf() < prob_ul, dl_flag = randf() < prob_dl; if (dl_flag) { float exp = dl_data_exps[0] + randf() * (dl_data_exps[1] - dl_data_exps[0]); generator.add_dl_data(rnti1, pow(10, exp)); } if (ul_flag) { float exp = ul_sr_exps[0] + randf() * (ul_sr_exps[1] - ul_sr_exps[0]); generator.add_ul_data(rnti1, pow(10, exp)); } } }; generate_data(20, P_dl, P_ul_sr); process_ttis(); // Event: Reconf Complete. Activate SCells. Check if CE correctly transmitted generator.step_tti(); user = generator.user_reconf(rnti1); *user->ue_cfg = *tester.get_ue_cfg(rnti1); // use current cfg as starting point, and add more supported ccs user->ue_cfg->supported_cc_list.resize(nof_ccs); for (uint32_t i = 0; i < user->ue_cfg->supported_cc_list.size(); ++i) { user->ue_cfg->supported_cc_list[i].active = true; user->ue_cfg->supported_cc_list[i].enb_cc_idx = i; } process_ttis(); // When a new DL tx takes place, it should also encode the CE for (uint32_t i = 0; i < 100; ++i) { TESTASSERT(tester.tti_info.dl_sched_result[pcell_idx].nof_data_elems > 0); if (tester.tti_info.dl_sched_result[pcell_idx].data[0].nof_pdu_elems[0] > 0) { // it is a new DL tx TESTASSERT(tester.tti_info.dl_sched_result[pcell_idx].data[0].pdu[0][0].lcid == srslte::sch_subh::cetype::SCELL_ACTIVATION); break; } generator.step_tti(); process_ttis(); // now we have two CCs } // now we have two CCs // Event: Generate a bit more data, now it should go through both cells generate_data(10, 1.0, 1.0); process_ttis(); TESTASSERT(tester.sched_stats->users[rnti1].tot_dl_sched_data[0] > 0); TESTASSERT(tester.sched_stats->users[rnti1].tot_dl_sched_data[1] > 0); TESTASSERT(tester.sched_stats->users[rnti1].tot_ul_sched_data[0] > 0); TESTASSERT(tester.sched_stats->users[rnti1].tot_ul_sched_data[1] > 0); log_global->info("[TESTER] Sim1 finished successfully\n"); return SRSLTE_SUCCESS; } int main() { srslte::logmap::get_instance()->set_default_log_level(srslte::LOG_LEVEL_INFO); printf("[TESTER] This is the chosen seed: %u\n", seed); uint32_t N_runs = 1; for (uint32_t n = 0; n < N_runs; ++n) { printf("Sim run number: %u\n", n + 1); TESTASSERT(run_sim1() == SRSLTE_SUCCESS); } return 0; }