diff --git a/srsenb/src/stack/mac/scheduler_grid.cc b/srsenb/src/stack/mac/scheduler_grid.cc index 156f220ca..6ccd1a387 100644 --- a/srsenb/src/stack/mac/scheduler_grid.cc +++ b/srsenb/src/stack/mac/scheduler_grid.cc @@ -793,9 +793,10 @@ void sf_sched::set_dl_data_sched_result(const pdcch_grid_t::alloc_result_t& dci_ } // Print Resulting DL Allocation - log_h->info("SCHED: DL %s rnti=0x%x, pid=%d, mask=0x%s, dci=(%d,%d), n_rtx=%d, tbs=%d, buffer=%d/%d\n", + log_h->info("SCHED: DL %s rnti=0x%x, cc=%d, pid=%d, mask=0x%s, dci=(%d,%d), n_rtx=%d, tbs=%d, buffer=%d/%d\n", !is_newtx ? "retx" : "tx", user->get_rnti(), + cc_cfg->enb_cc_idx, h->get_id(), data_alloc.user_mask.to_hex().c_str(), data->dci.location.L, diff --git a/srsenb/src/stack/mac/scheduler_ue.cc b/srsenb/src/stack/mac/scheduler_ue.cc index 759499778..fd7744bdc 100644 --- a/srsenb/src/stack/mac/scheduler_ue.cc +++ b/srsenb/src/stack/mac/scheduler_ue.cc @@ -101,7 +101,8 @@ void sched_ue::set_cfg(const sched_interface::ue_cfg_t& cfg_) } // update configuration - cfg = cfg_; + std::vector prev_supported_cc_list = std::move(cfg.supported_cc_list); + cfg = cfg_; // update bearer cfgs for (uint32_t i = 0; i < sched_interface::MAX_LC; ++i) { @@ -110,17 +111,24 @@ void sched_ue::set_cfg(const sched_interface::ue_cfg_t& cfg_) // either add a new carrier, or reconfigure existing one bool scell_activation_state_changed = false; - for (auto& cc_cfg : cfg.supported_cc_list) { - sched_ue_carrier* c = get_ue_carrier(cc_cfg.enb_cc_idx); - if (c == nullptr) { - // add new carrier to sched_ue - carriers.emplace_back(cfg, (*cell_params_list)[cc_cfg.enb_cc_idx], rnti, carriers.size()); - scell_activation_state_changed |= carriers.size() > 1 and carriers.back().is_active(); + for (uint32_t ue_idx = 0; ue_idx < cfg.supported_cc_list.size(); ++ue_idx) { + auto& cc_cfg = cfg.supported_cc_list[ue_idx]; + + if (ue_idx >= prev_supported_cc_list.size()) { + // New carrier needs to be added + carriers.emplace_back(cfg, (*cell_params_list)[cc_cfg.enb_cc_idx], rnti, ue_idx); + scell_activation_state_changed |= ue_idx > 0 and carriers.back().is_active(); + } else if (cc_cfg.enb_cc_idx != prev_supported_cc_list[ue_idx].enb_cc_idx) { + // TODO: Check if this will ever happen. + // One carrier was added in the place of another + carriers[ue_idx] = sched_ue_carrier{cfg, (*cell_params_list)[cc_cfg.enb_cc_idx], rnti, ue_idx}; + scell_activation_state_changed |= ue_idx > 0 and carriers[ue_idx].is_active(); } else { - // if SCell state changed - scell_activation_state_changed = c->is_active() != cc_cfg.active and c->get_ue_cc_idx() != 0; + // The enb_cc_idx, ue_cc_idx match previous configuration. + // The SCell state may have changed. In such case we will schedule a SCell Activation CE + scell_activation_state_changed = carriers[ue_idx].is_active() != cc_cfg.active and ue_idx > 0; // reconfiguration of carrier might be needed. - c->set_cfg(cfg); + carriers[ue_idx].set_cfg(cfg); } } if (scell_activation_state_changed) { @@ -921,7 +929,6 @@ std::pair sched_ue::get_cell_index(uint32_t enb_cc_idx) const cfg.supported_cc_list.end(), [enb_cc_idx](const sched_interface::ue_cfg_t::cc_cfg_t& u) { return u.enb_cc_idx == enb_cc_idx; }); if (it == cfg.supported_cc_list.end()) { - log_h->error("The carrier with eNB_cc_idx=%d does not exist\n", enb_cc_idx); return std::make_pair(false, 0); } return std::make_pair(true, it->enb_cc_idx); diff --git a/srsenb/test/mac/scheduler_ca_test.cc b/srsenb/test/mac/scheduler_ca_test.cc index eb95c1c10..0cd14009c 100644 --- a/srsenb/test/mac/scheduler_ca_test.cc +++ b/srsenb/test/mac/scheduler_ca_test.cc @@ -19,9 +19,24 @@ * */ +#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 * *******************/ @@ -104,7 +119,7 @@ sched_sim_events generate_sim1() tti_ev::user_cfg_ev* user = generator.add_new_default_user(duration); uint16_t rnti = user->rnti; - // Event (TTI=prach_tti+msg4_tot_delay): First Tx (Msg4) + // 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(rnti, msg4_size); @@ -151,38 +166,358 @@ sched_sim_events generate_sim1() class sched_ca_tester : public srsenb::sched { public: - void run_tti(uint32_t tti); + 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; + + // testers + std::vector output_tester; + std::unique_ptr ue_tester; + +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; }; -void sched_ca_tester::run_tti(uint32_t tti_rx) +int sched_ca_tester::cell_cfg(const std::vector& cell_params) { - // new_test_tti(tti_rx); + sched::cell_cfg(cell_params); // call parent + ue_tester.reset(new user_state_sched_tester{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_args(); - // - // ack_txs(); + process_tti_events(tti_events); + process_ack_txs(); // before_sched(); - // - // dl_sched(tti_data.tti_tx_dl, CARRIER_IDX, tti_data.sched_result_dl); - // ul_sched(tti_data.tti_tx_ul, CARRIER_IDX, tti_data.sched_result_ul); - // - // process_results(); + + // 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(); +} + +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); + } + + 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; } int test_scheduler_ca(const sched_sim_events& sim_events) { - // Create classes sched_ca_tester tester; + tester.sim_args = sim_events.sim_args; + // Setup scheduler tester.init(nullptr); - tester.cell_cfg(sim_events.sim_args.cell_cfg); + TESTASSERT(tester.cell_cfg(sim_events.sim_args.cell_cfg) == SRSLTE_SUCCESS); uint32_t tti_start = 0; // rand_int(0, 10240); for (uint32_t nof_ttis = 0; nof_ttis < sim_events.sim_args.nof_ttis; ++nof_ttis) { uint32_t tti = (tti_start + nof_ttis) % 10240; log_global->step(tti); - tester.run_tti(tti); + tester.run_tti(tti, sim_events.tti_events[nof_ttis]); } return SRSLTE_SUCCESS; diff --git a/srsenb/test/mac/scheduler_test_common.cc b/srsenb/test/mac/scheduler_test_common.cc index 77cd63582..01056977a 100644 --- a/srsenb/test/mac/scheduler_test_common.cc +++ b/srsenb/test/mac/scheduler_test_common.cc @@ -20,10 +20,13 @@ */ #include "scheduler_test_common.h" +#include "lib/include/srslte/common/pdu.h" #include "srsenb/hdr/stack/mac/scheduler.h" #include "srslte/common/test_common.h" +#include + using namespace srsenb; int output_sched_tester::test_pusch_collisions(const tti_params_t& tti_params, @@ -255,6 +258,20 @@ int output_sched_tester::test_dci_values_consistency(const sched_interface::dl_s return SRSLTE_SUCCESS; } +int output_sched_tester::test_all(const tti_params_t& tti_params, + const sched_interface::dl_sched_res_t& dl_result, + const sched_interface::ul_sched_res_t& ul_result) const +{ + prbmask_t ul_allocs; + TESTASSERT(test_pusch_collisions(tti_params, ul_result, ul_allocs) == SRSLTE_SUCCESS); + rbgmask_t dl_mask; + TESTASSERT(test_pdsch_collisions(tti_params, dl_result, dl_mask) == SRSLTE_SUCCESS); + TESTASSERT(test_sib_scheduling(tti_params, dl_result) == SRSLTE_SUCCESS); + srslte::bounded_bitset<128, true> used_cce; + TESTASSERT(test_pdcch_collisions(dl_result, ul_result, &used_cce) == SRSLTE_SUCCESS); + return SRSLTE_SUCCESS; +} + int srsenb::extract_dl_prbmask(const srslte_cell_t& cell, const srslte_dci_dl_t& dci, srslte::bounded_bitset<100, true>* alloc_mask) @@ -276,3 +293,232 @@ int srsenb::extract_dl_prbmask(const srslte_cell_t& cell, } return SRSLTE_SUCCESS; } + +int user_state_sched_tester::add_user(uint16_t rnti, + uint32_t preamble_idx, + const srsenb::sched_interface::ue_cfg_t& ue_cfg) +{ + TESTASSERT(users.count(rnti) == 0); + ue_state ue; + ue.user_cfg = ue_cfg; + ue.prach_tti = tti_params.tti_rx; + ue.preamble_idx = preamble_idx; + users.insert(std::make_pair(rnti, ue)); + return SRSLTE_SUCCESS; +} + +int user_state_sched_tester::user_reconf(uint16_t rnti, const srsenb::sched_interface::ue_cfg_t& ue_cfg) +{ + TESTASSERT(users.count(rnti) > 0); + users[rnti].user_cfg = ue_cfg; + return SRSLTE_SUCCESS; +} + +int user_state_sched_tester::bearer_cfg(uint16_t rnti, + uint32_t lcid, + const srsenb::sched_interface::ue_bearer_cfg_t& bearer_cfg) +{ + auto it = users.find(rnti); + TESTASSERT(it != users.end()); + it->second.user_cfg.ue_bearers[lcid] = bearer_cfg; + users[rnti].drb_cfg_flag = false; + for (uint32_t i = 2; i < it->second.user_cfg.ue_bearers.size(); ++i) { + if (it->second.user_cfg.ue_bearers[i].direction != sched_interface::ue_bearer_cfg_t::IDLE) { + users[rnti].drb_cfg_flag = true; + } + } + return SRSLTE_SUCCESS; +} + +void user_state_sched_tester::rem_user(uint16_t rnti) +{ + users.erase(rnti); +} + +/** + * Tests whether the RAR and Msg3 were scheduled within the expected windows. Individual tests: + * - a user does not get UL allocs before Msg3 + * - a user does not get DL data allocs before Msg3 is correctly received + * - a user RAR alloc falls within its RAR window + * - There is only one RAR in the RAR window for a given user + * - Msg3 is allocated in expected TTI, without PDCCH, and correct rnti + * - First Data allocation happens after Msg3, and contains a ConRes + * - No RARs are allocated with wrong enb_cc_idx, preamble_idx or wrong user + * TODO: + * - check Msg3 PRBs match the ones advertised in the RAR + * - space is enough for Msg3 + */ +int user_state_sched_tester::test_ra(uint32_t enb_cc_idx, + const sched_interface::dl_sched_res_t& dl_result, + const sched_interface::ul_sched_res_t& ul_result) +{ + uint32_t msg3_count = 0; + + for (auto& iter : users) { + uint16_t rnti = iter.first; + ue_state& userinfo = iter.second; + + // No UL allocations before Msg3 + for (uint32_t i = 0; i < ul_result.nof_dci_elems; ++i) { + if (ul_result.pusch[i].dci.rnti == rnti) { + CONDERROR(ul_result.pusch[i].needs_pdcch and userinfo.msg3_tti < 0, + "[TESTER] No UL data allocation allowed before Msg3\n"); + CONDERROR(userinfo.rar_tti < 0, "[TESTER] No UL allocation allowed before RAR\n"); + uint32_t msg3_tti = (uint32_t)(userinfo.rar_tti + FDD_HARQ_DELAY_MS + MSG3_DELAY_MS) % 10240; + CONDERROR(msg3_tti > tti_params.tti_tx_ul, "No UL allocs allowed before Msg3 alloc\n"); + } + } + + // No DL data allocations before Msg3 is received + for (uint32_t i = 0; i < dl_result.nof_data_elems; ++i) { + if (dl_result.data[i].dci.rnti == rnti) { + CONDERROR(userinfo.msg3_tti < 0, "[TESTER] No DL data alloc allowed before Msg3 alloc\n"); + CONDERROR(tti_params.tti_rx < (uint32_t)userinfo.msg3_tti, + "[TESTER] Msg4 cannot be tx without Msg3 being received\n"); + } + } + + if (enb_cc_idx != userinfo.user_cfg.supported_cc_list[0].enb_cc_idx) { + // only check for RAR/Msg3 presence for a UE's PCell + continue; + } + + // No RAR allocations outside of rar_window + uint32_t prach_tti = (uint32_t)userinfo.prach_tti; + uint32_t primary_cc_idx = userinfo.user_cfg.supported_cc_list[0].enb_cc_idx; + std::array rar_window = {prach_tti + 3, prach_tti + 3 + cell_params[primary_cc_idx].prach_rar_window}; + + CONDERROR(userinfo.rar_tti < 0 and tti_params.tti_tx_dl > rar_window[1], + "[TESTER] RAR not scheduled within the RAR Window\n"); + if (tti_params.tti_tx_dl <= rar_window[1] and tti_params.tti_tx_dl >= rar_window[0]) { + // Inside RAR window + for (uint32_t i = 0; i < dl_result.nof_rar_elems; ++i) { + for (uint32_t j = 0; j < dl_result.rar[i].nof_grants; ++j) { + auto& data = dl_result.rar[i].msg3_grant[j].data; + if (data.prach_tti == (uint32_t)userinfo.prach_tti and data.preamble_idx == userinfo.preamble_idx) { + CONDERROR(userinfo.rar_tti >= 0, "There was more than one RAR for the same user\n"); + userinfo.rar_tti = tti_params.tti_tx_dl; + } + } + } + } + + // Check whether Msg3 was allocated in expected TTI + if (userinfo.rar_tti >= 0) { + uint32_t expected_msg3_tti = (uint32_t)(userinfo.rar_tti + FDD_HARQ_DELAY_MS + MSG3_DELAY_MS) % 10240; + if (expected_msg3_tti == tti_params.tti_tx_ul) { + for (uint32_t i = 0; i < ul_result.nof_dci_elems; ++i) { + if (ul_result.pusch[i].dci.rnti == rnti) { + CONDERROR(userinfo.msg3_tti >= 0, "[TESTER] Only one Msg3 allowed per user\n"); + CONDERROR(ul_result.pusch[i].needs_pdcch, "[TESTER] Msg3 allocations do not require PDCCH\n"); + // CONDERROR(tti_data.ul_pending_msg3.rnti != rnti, "[TESTER] The UL pending msg3 RNTI did not + // match\n"); CONDERROR(not tti_data.ul_pending_msg3_present, "[TESTER] The UL pending msg3 + // RNTI did not match\n"); + userinfo.msg3_tti = tti_params.tti_tx_ul; + msg3_count++; + } + } + } else if (expected_msg3_tti < tti_params.tti_tx_ul) { + CONDERROR(userinfo.msg3_tti < 0, "[TESTER] No UL msg3 allocation was made\n"); + } + } + + // Find any Msg4 Allocation + if (userinfo.msg4_tti < 0) { + for (uint32_t i = 0; i < dl_result.nof_data_elems; ++i) { + if (dl_result.data[i].dci.rnti == rnti) { + for (uint32_t j = 0; j < dl_result.data[i].nof_pdu_elems[0]; ++j) { + if (dl_result.data[i].pdu[0][j].lcid == srslte::sch_subh::CON_RES_ID) { + // ConRes found + CONDERROR(dl_result.data[i].dci.format != SRSLTE_DCI_FORMAT1, "ConRes must be format1\n"); + CONDERROR(userinfo.msg4_tti >= 0, "ConRes CE cannot be retransmitted for the same rnti\n"); + userinfo.msg4_tti = tti_params.tti_tx_dl; + } + } + CONDERROR(userinfo.msg4_tti < 0, "Data allocations are not allowed without first receiving ConRes\n"); + } + } + } + } + + return SRSLTE_SUCCESS; +} + +int user_state_sched_tester::test_ctrl_info(uint32_t enb_cc_idx, + const sched_interface::dl_sched_res_t& dl_result, + const sched_interface::ul_sched_res_t& ul_result) +{ + /* TEST: Ensure there are no spurious RARs that do not belong to any user */ + for (uint32_t i = 0; i < dl_result.nof_rar_elems; ++i) { + for (uint32_t j = 0; j < dl_result.rar[i].nof_grants; ++j) { + uint32_t prach_tti = dl_result.rar[i].msg3_grant[j].data.prach_tti; + uint32_t preamble_idx = dl_result.rar[i].msg3_grant[j].data.preamble_idx; + auto it = std::find_if(users.begin(), users.end(), [&](const std::pair& u) { + return u.second.preamble_idx == preamble_idx and ((uint32_t)u.second.prach_tti == prach_tti); + }); + CONDERROR(it == users.end(), "There was a RAR allocation with no associated user"); + CONDERROR(it->second.user_cfg.supported_cc_list[0].enb_cc_idx != enb_cc_idx, + "The allocated RAR is in the wrong cc\n"); + } + } + + /* TEST: All DL allocs have a correct rnti */ + std::set alloc_rntis; + for (uint32_t i = 0; i < dl_result.nof_data_elems; ++i) { + uint16_t rnti = dl_result.data[i].dci.rnti; + CONDERROR(alloc_rntis.count(rnti) > 0, "The user rnti=0x%x got allocated multiple times in DL\n", rnti); + CONDERROR(users.count(rnti) == 0, "The user rnti=0x%x allocated in DL does not exist\n", rnti); + alloc_rntis.insert(rnti); + } + + /* TEST: All UL allocs have a correct rnti */ + alloc_rntis.clear(); + for (uint32_t i = 0; i < ul_result.nof_dci_elems; ++i) { + uint16_t rnti = ul_result.pusch[i].dci.rnti; + CONDERROR(alloc_rntis.count(rnti) > 0, "The user rnti=0x%x got allocated multiple times in UL\n", rnti); + CONDERROR(users.count(rnti) == 0, "The user rnti=0x%x allocated in UL does not exist\n", rnti); + alloc_rntis.insert(rnti); + } + + return SRSLTE_SUCCESS; +} + +/** + * Tests whether the SCells are correctly activated. Individual tests: + * - no DL and UL allocations in inactive carriers + */ +int user_state_sched_tester::test_scell_activation(uint32_t enb_cc_idx, + const sched_interface::dl_sched_res_t& dl_result, + const sched_interface::ul_sched_res_t& ul_result) +{ + for (auto& iter : users) { + uint16_t rnti = iter.first; + ue_state& userinfo = iter.second; + + auto it = std::find_if(userinfo.user_cfg.supported_cc_list.begin(), + userinfo.user_cfg.supported_cc_list.end(), + [enb_cc_idx](const sched::ue_cfg_t::cc_cfg_t& cc) { return cc.enb_cc_idx == enb_cc_idx; }); + + if (it == userinfo.user_cfg.supported_cc_list.end() or not it->active) { + // cell not active. Ensure data allocations are not made + for (uint32_t i = 0; i < dl_result.nof_data_elems; ++i) { + CONDERROR(dl_result.data[i].dci.rnti == rnti, "Allocated user in inactive carrier\n"); + } + for (uint32_t i = 0; i < ul_result.nof_dci_elems; ++i) { + CONDERROR(ul_result.pusch[i].dci.rnti == rnti, "Allocated user in inactive carrier\n"); + } + } + } + + return SRSLTE_SUCCESS; +} + +int user_state_sched_tester::test_all(uint32_t enb_cc_idx, + const sched_interface::dl_sched_res_t& dl_result, + const sched_interface::ul_sched_res_t& ul_result) +{ + TESTASSERT(test_ra(enb_cc_idx, dl_result, ul_result) == SRSLTE_SUCCESS); + TESTASSERT(test_ctrl_info(enb_cc_idx, dl_result, ul_result) == SRSLTE_SUCCESS); + TESTASSERT(test_scell_activation(enb_cc_idx, dl_result, ul_result) == SRSLTE_SUCCESS); + return SRSLTE_SUCCESS; +} diff --git a/srsenb/test/mac/scheduler_test_common.h b/srsenb/test/mac/scheduler_test_common.h index b841bfe7d..7eed23c30 100644 --- a/srsenb/test/mac/scheduler_test_common.h +++ b/srsenb/test/mac/scheduler_test_common.h @@ -58,10 +58,71 @@ public: int test_dci_values_consistency(const sched_interface::dl_sched_res_t& dl_result, const sched_interface::ul_sched_res_t& ul_result) const; + int test_all(const tti_params_t& tti_params, + const sched_interface::dl_sched_res_t& dl_result, + const sched_interface::ul_sched_res_t& ul_result) const; + private: const sched_cell_params_t& cell_params; }; +class user_state_sched_tester +{ +public: + struct ue_state { + int prach_tti = -1, rar_tti = -1, msg3_tti = -1, msg4_tti = -1; + bool drb_cfg_flag = false; + srsenb::sched_interface::ue_cfg_t user_cfg; + uint32_t dl_data = 0; + uint32_t ul_data = 0; + uint32_t preamble_idx = 0; + }; + + explicit user_state_sched_tester(const std::vector& cell_params_) : + cell_params(cell_params_) + { + } + + void new_tti(uint32_t tti_rx) { tti_params = tti_params_t{tti_rx}; } + bool user_exists(uint16_t rnti) const { return users.find(rnti) != users.end(); } + const ue_state* get_user_state(uint16_t rnti) const + { + return users.count(rnti) > 0 ? &users.find(rnti)->second : nullptr; + } + + /* Register new users */ + int add_user(uint16_t rnti, uint32_t preamble_idx, const srsenb::sched_interface::ue_cfg_t& ue_cfg); + int user_reconf(uint16_t rnti, const srsenb::sched_interface::ue_cfg_t& ue_cfg); + int bearer_cfg(uint16_t rnti, uint32_t lcid, const srsenb::sched_interface::ue_bearer_cfg_t& bearer_cfg); + + void rem_user(uint16_t rnti); + + /* Test the timing of RAR, Msg3, Msg4 */ + int test_ra(uint32_t enb_cc_idx, + const sched_interface::dl_sched_res_t& dl_result, + const sched_interface::ul_sched_res_t& ul_result); + + /* Test allocs control content */ + int test_ctrl_info(uint32_t enb_cc_idx, + const sched_interface::dl_sched_res_t& dl_result, + const sched_interface::ul_sched_res_t& ul_result); + + /* Test correct activation of SCells */ + int test_scell_activation(uint32_t enb_cc_idx, + const sched_interface::dl_sched_res_t& dl_result, + const sched_interface::ul_sched_res_t& ul_result); + + int test_all(uint32_t enb_cc_idx, + const sched_interface::dl_sched_res_t& dl_result, + const sched_interface::ul_sched_res_t& ul_result); + +private: + const std::vector cell_params; + + std::map users; + tti_params_t tti_params{10241}; +}; + } // namespace srsenb #endif // SRSLTE_SCHEDULER_TEST_COMMON_H diff --git a/srsenb/test/mac/scheduler_test_utils.h b/srsenb/test/mac/scheduler_test_utils.h index c6bec8b0a..e3cd88f02 100644 --- a/srsenb/test/mac/scheduler_test_utils.h +++ b/srsenb/test/mac/scheduler_test_utils.h @@ -33,8 +33,8 @@ * Setup Random generators **************************/ -uint32_t const seed = std::chrono::system_clock::now().time_since_epoch().count(); -// uint32_t const seed = 2452071795; +// uint32_t const seed = std::chrono::system_clock::now().time_since_epoch().count(); +uint32_t const seed = 2452071795; // uint32_t const seed = 1581009287; // prb==25 std::default_random_engine rand_gen(seed); std::uniform_real_distribution unif_dist(0, 1.0); @@ -209,6 +209,8 @@ struct sched_sim_event_generator { } tti_ev::user_cfg_ev* user = get_user_cfg(rnti); user->ue_cfg.reset(new srsenb::sched_interface::ue_cfg_t{generate_default_ue_cfg()}); + // it should by now have a DRB1. Add other DRBs manually + user->ue_cfg->ue_bearers[2].direction = srsenb::sched_interface::ue_bearer_cfg_t::BOTH; return user; } @@ -220,6 +222,7 @@ private: user_updates.begin(), user_updates.end(), [&rnti](tti_ev::user_cfg_ev& user) { return user.rnti == rnti; }); if (it == user_updates.end()) { user_updates.emplace_back(); + user_updates.back().rnti = rnti; return &user_updates.back(); } return &(*it); @@ -256,89 +259,4 @@ private: } }; -int add_user(srsenb::sched* sched_ptr, - uint16_t rnti, - uint32_t prach_tti, - uint32_t preamble_idx, - uint32_t enb_cc_idx, - srsenb::sched::ue_cfg_t& ue_cfg) -{ - CONDERROR(sched_ptr->ue_cfg(rnti, ue_cfg) != SRSLTE_SUCCESS, "Configuring new user rnti=0x%x to sched\n", rnti); - - srsenb::sched::dl_sched_rar_info_t rar_info = {}; - rar_info.prach_tti = prach_tti; - rar_info.temp_crnti = rnti; - rar_info.msg3_size = 7; - rar_info.preamble_idx = preamble_idx; - sched_ptr->dl_rach_info(enb_cc_idx, rar_info); - - // // setup bearers - // bearer_ue_cfg(rnti, 0, &bearer_cfg); - - srslte::logmap::get("TEST")->info("Adding user rnti=0x%x\n", rnti); - - return SRSLTE_SUCCESS; -} - -int apply_tti_events(srsenb::sched* sched_ptr, uint32_t tti, const tti_ev& events) -{ - uint32_t prach_preamble_idx = 0; - uint32_t enb_cc_idx = 0; // TODO: Users can connect from any carrier - for (const tti_ev::user_cfg_ev& user_ev : events.user_updates) { - // may add a new user - if (user_ev.ue_cfg != nullptr) { - add_user(sched_ptr, user_ev.rnti, tti, prach_preamble_idx++, enb_cc_idx, *user_ev.ue_cfg); - } - - // may remove an existing user - if (user_ev.rem_user) { - sched_ptr->bearer_ue_rem(user_ev.rnti, 0); - sched_ptr->ue_rem(user_ev.rnti); - srslte::logmap::get("TEST")->info("Adding user rnti=0x%x\n", user_ev.rnti); - } - - // push UL SRs and DL packets - } - - // // may remove an existing user - // if (sim_args.tti_events[tti_data.tti_rx].rem_user) { - // uint16_t rnti = sim_args.tti_events[tti_data.tti_rx].rem_rnti; - // bearer_ue_rem(rnti, 0); - // ue_rem(rnti); - // rem_user(rnti); - // log_global->info("[TESTER] Removing user rnti=0x%x\n", rnti); - // } - // - // // push UL SRs and DL packets - // for (auto& e : sim_args.tti_events[tti_data.tti_rx].users) { - // if (e.second.sr_data > 0 and tester_ues[e.first].drb_cfg_flag) { - // uint32_t tot_ul_data = ue_db[e.first].get_pending_ul_new_data(tti_data.tti_tx_ul) + e.second.sr_data; - // uint32_t lcid = 0; - // ul_bsr(e.first, lcid, tot_ul_data, true); - // } - // if (e.second.dl_data > 0 and tester_ues[e.first].msg3_tti >= 0 and - // tester_ues[e.first].msg3_tti < (int)tti_data.tti_rx) { - // // If Msg4 not yet sent, allocate data in SRB0 buffer - // uint32_t lcid = (tester_ues[e.first].msg4_tti >= 0) ? 2 : 0; - // uint32_t pending_dl_new_data = ue_db[e.first].get_pending_dl_new_data(); - // if (lcid == 2 and not tester_ues[e.first].drb_cfg_flag) { - // // If RRCSetup finished - // if (pending_dl_new_data == 0) { - // // setup lcid==2 bearer - // tester_ues[e.first].drb_cfg_flag = true; - // bearer_ue_cfg(e.first, 2, &tester_ues[e.first].bearer_cfg); - // } else { - // // Let SRB0 get emptied - // continue; - // } - // } - // // TODO: Does it need TTI for checking pending data? - // uint32_t tot_dl_data = pending_dl_new_data + e.second.dl_data; - // dl_rlc_buffer_state(e.first, lcid, tot_dl_data, 0); - // } - // } - - return SRSLTE_SUCCESS; -} - #endif // SRSLTE_SCHEDULER_TEST_UTILS_H