diff --git a/srsenb/test/mac/CMakeLists.txt b/srsenb/test/mac/CMakeLists.txt index 5d8ec5a9e..b5c93f12b 100644 --- a/srsenb/test/mac/CMakeLists.txt +++ b/srsenb/test/mac/CMakeLists.txt @@ -18,7 +18,7 @@ # and at http://www.gnu.org/licenses/. # -add_library(scheduler_test_common STATIC scheduler_test_common.cc sched_common_test_suite.cc) +add_library(scheduler_test_common STATIC scheduler_test_common.cc sched_common_test_suite.cc sched_ue_ded_test_suite.cc sched_sim_ue.cc) # Scheduler subcomponent testing add_executable(sched_grid_test sched_grid_test.cc) diff --git a/srsenb/test/mac/sched_sim_ue.cc b/srsenb/test/mac/sched_sim_ue.cc new file mode 100644 index 000000000..732a0a81c --- /dev/null +++ b/srsenb/test/mac/sched_sim_ue.cc @@ -0,0 +1,134 @@ +/* + * Copyright 2013-2020 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 "sched_sim_ue.h" + +namespace srsenb { + +bool ue_sim::enqueue_pending_acks(srslte::tti_point tti_rx, + pucch_feedback& feedback_list, + std::bitset ack_val) +{ + bool ack_added = false; + for (uint32_t ue_cc_idx = 0; ue_cc_idx < ctxt.cc_list.size(); ++ue_cc_idx) { + uint32_t enb_cc_idx = ctxt.ue_cfg.supported_cc_list[ue_cc_idx].enb_cc_idx; + auto& ue_cc = ctxt.cc_list[ue_cc_idx]; + + for (uint32_t pid = 0; pid < SRSLTE_FDD_NOF_HARQ; ++pid) { + auto& h = ue_cc.dl_harqs[pid]; + + if (h.active and to_tx_dl_ack(h.last_tti_rx) == tti_rx) { + if (feedback_list.cc_list.size() <= ue_cc_idx) { + feedback_list.cc_list.resize(ue_cc_idx + 1); + } + auto& result = feedback_list.cc_list[ue_cc_idx]; + result.enb_cc_idx = enb_cc_idx; + result.ack = ack_val[ue_cc_idx]; + result.pid = pid; + + if (result.pid >= 0 and (result.ack or h.nof_retxs + 1 >= ctxt.ue_cfg.maxharq_tx)) { + h.active = false; + } + + ack_added = true; + } + } + } + + return ack_added; +} + +int ue_sim::update(const sf_output_res_t& sf_out) +{ + update_dl_harqs(sf_out); + update_ul_harqs(sf_out); + + return SRSLTE_SUCCESS; +} + +void ue_sim::update_dl_harqs(const sf_output_res_t& sf_out) +{ + for (uint32_t cc = 0; cc < sf_out.cc_params.size(); ++cc) { + for (uint32_t i = 0; i < sf_out.dl_cc_result[cc].nof_data_elems; ++i) { + const auto& data = sf_out.dl_cc_result[cc].data[i]; + if (data.dci.rnti != ctxt.rnti) { + continue; + } + auto& h = ctxt.cc_list[data.dci.ue_cc_idx].dl_harqs[data.dci.pid]; + if (h.nof_txs == 0 or h.ndi != data.dci.tb[0].ndi) { + // It is newtx + h.active = true; + h.nof_retxs = 0; + h.ndi = data.dci.tb[0].ndi; + } else { + // it is retx + h.nof_retxs++; + } + h.last_tti_rx = sf_out.tti_rx; + } + } +} + +void ue_sim::update_ul_harqs(const sf_output_res_t& sf_out) +{ + for (uint32_t cc = 0; cc < sf_out.cc_params.size(); ++cc) { + // Update UL harqs with PHICH info + for (uint32_t i = 0; i < sf_out.ul_cc_result[cc].nof_phich_elems; ++i) { + const auto& phich = sf_out.ul_cc_result[cc].phich[i]; + if (phich.rnti != ctxt.rnti) { + continue; + } + + const auto *cc_cfg = ctxt.get_cc_cfg(cc), *start = &ctxt.ue_cfg.supported_cc_list[0]; + uint32_t ue_cc_idx = std::distance(start, cc_cfg); + auto& ue_cc_ctxt = ctxt.cc_list[ue_cc_idx]; + auto& h = ue_cc_ctxt.ul_harqs[to_tx_ul(sf_out.tti_rx).to_uint() % ue_cc_ctxt.ul_harqs.size()]; + + if (phich.phich == sched_interface::ul_sched_phich_t::ACK or h.nof_retxs + 1 >= ctxt.ue_cfg.maxharq_tx) { + h.active = false; + } + } + + // Update UL harqs with PUSCH grants + for (uint32_t i = 0; i < sf_out.ul_cc_result[cc].nof_dci_elems; ++i) { + const auto& data = sf_out.ul_cc_result[cc].pusch[i]; + if (data.dci.rnti != ctxt.rnti) { + continue; + } + auto& ue_cc_ctxt = ctxt.cc_list[data.dci.ue_cc_idx]; + auto& h = ue_cc_ctxt.ul_harqs[to_tx_ul(sf_out.tti_rx).to_uint() % ue_cc_ctxt.ul_harqs.size()]; + + if (h.nof_txs == 0 or h.ndi != data.dci.tb.ndi) { + // newtx + h.active = true; + h.nof_retxs = 0; + h.ndi = data.dci.tb.ndi; + } else { + h.nof_retxs++; + } + h.last_tti_rx = sf_out.tti_rx; + h.riv = data.dci.type2_alloc.riv; + h.nof_txs++; + } + } +} + +} // namespace srsenb diff --git a/srsenb/test/mac/sched_sim_ue.h b/srsenb/test/mac/sched_sim_ue.h new file mode 100644 index 000000000..23ad9371d --- /dev/null +++ b/srsenb/test/mac/sched_sim_ue.h @@ -0,0 +1,89 @@ +/* + * Copyright 2013-2020 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/. + * + */ + +#ifndef SRSLTE_SCHED_SIM_UE_H +#define SRSLTE_SCHED_SIM_UE_H + +#include "sched_common_test_suite.h" +#include "srslte/interfaces/sched_interface.h" +#include + +namespace srsenb { + +struct ue_harq_ctxt_t { + bool active = false; + bool ndi = false; + uint32_t pid = 0; + uint32_t nof_txs = 0; + uint32_t nof_retxs = 0; + uint32_t riv = 0; + srslte::tti_point last_tti_rx; +}; +struct ue_cc_ctxt_t { + std::array dl_harqs; + std::array ul_harqs; +}; +struct sim_ue_ctxt_t { + uint16_t rnti; + srslte::tti_point prach_tti_rx; + sched_interface::ue_cfg_t ue_cfg; + std::vector cc_list; + const sched_interface::ue_cfg_t::cc_cfg_t* get_cc_cfg(uint32_t enb_cc_idx) const; + int enb_to_ue_cc_idx(uint32_t enb_cc_idx) const; +}; + +struct sim_enb_ctxt_t { + std::vector cell_params; + std::map ue_db; +}; +struct pucch_feedback { + struct cc_data { + uint32_t enb_cc_idx = 0; + int cqi = -1; + int pid = -1; + bool ack = false; + }; + std::vector cc_list; +}; + +class ue_sim +{ +public: + ue_sim() = default; + + int update(const sf_output_res_t& sf_out); + + bool enqueue_pending_acks(srslte::tti_point tti_rx, + pucch_feedback& feedback_list, + std::bitset ack_val); + + const sim_ue_ctxt_t& get_ctxt() const { return ctxt; } + +private: + void update_dl_harqs(const sf_output_res_t& sf_out); + void update_ul_harqs(const sf_output_res_t& sf_out); + + sim_ue_ctxt_t ctxt; +}; + +} // namespace srsenb + +#endif // SRSLTE_SCHED_SIM_UE_H diff --git a/srsenb/test/mac/sched_ue_ded_test_suite.cc b/srsenb/test/mac/sched_ue_ded_test_suite.cc new file mode 100644 index 000000000..60fa24083 --- /dev/null +++ b/srsenb/test/mac/sched_ue_ded_test_suite.cc @@ -0,0 +1,178 @@ +/* + * Copyright 2013-2020 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 "sched_ue_ded_test_suite.h" +#include "srslte/common/test_common.h" + +namespace srsenb { + +using phich_t = sched_interface::ul_sched_phich_t; +using pusch_t = sched_interface::ul_sched_data_t; + +const sched_interface::ue_cfg_t::cc_cfg_t* sim_ue_ctxt_t::get_cc_cfg(uint32_t enb_cc_idx) const +{ + // TODO: verify SCELL Act was received + auto it = + std::find_if(ue_cfg.supported_cc_list.begin(), + ue_cfg.supported_cc_list.end(), + [enb_cc_idx](const sched_interface::ue_cfg_t::cc_cfg_t& cc) { return cc.enb_cc_idx == enb_cc_idx; }); + return (it == ue_cfg.supported_cc_list.end()) ? nullptr : &(*it); +} + +int sim_ue_ctxt_t::enb_to_ue_cc_idx(uint32_t enb_cc_idx) const +{ + auto it = + std::find_if(ue_cfg.supported_cc_list.begin(), + ue_cfg.supported_cc_list.end(), + [enb_cc_idx](const sched_interface::ue_cfg_t::cc_cfg_t& cc) { return cc.enb_cc_idx == enb_cc_idx; }); + return it == ue_cfg.supported_cc_list.end() ? -1 : std::distance(ue_cfg.supported_cc_list.begin(), it); +} + +int test_pdsch_grant(const sim_ue_ctxt_t& ue_ctxt, + srslte::tti_point tti_rx, + uint32_t enb_cc_idx, + const sched_interface::dl_sched_data_t& pdsch) +{ + auto* cc_cfg = ue_ctxt.get_cc_cfg(enb_cc_idx); + + // TEST: Check if CC is configured and active + CONDERROR(cc_cfg == nullptr or not cc_cfg->active, "PDSCH allocation for disabled or unavailable cc\n"); + CONDERROR(pdsch.dci.ue_cc_idx != std::distance(&ue_ctxt.ue_cfg.supported_cc_list.front(), cc_cfg), + "Inconsistent enb_cc_idx -> ue_cc_idx mapping\n"); + + // TEST: DCI is consistent with current UE DL harq state + auto& h = ue_ctxt.cc_list[pdsch.dci.ue_cc_idx].dl_harqs[pdsch.dci.pid]; + uint32_t nof_retx = sched_utils::get_nof_retx(pdsch.dci.tb[0].rv); // 0..3 + if (h.nof_txs == 0 or h.ndi != pdsch.dci.tb[0].ndi) { + // It is newtx + CONDERROR(nof_retx != 0, "Invalid rv index for new tx\n"); + } else { + // it is retx + CONDERROR(sched_utils::get_rvidx(h.nof_retxs + 1) != (uint32_t)pdsch.dci.tb[0].rv, "Invalid rv index for retx\n"); + CONDERROR(not h.active, "retx for inactive dl harq pid=%d\n", h.pid); + CONDERROR(to_tx_dl_ack(h.last_tti_rx) > tti_rx, "harq pid=%d reused too soon\n", h.pid); + CONDERROR(h.nof_retxs + 1 > ue_ctxt.ue_cfg.maxharq_tx, + "The number of retx=%d exceeded its max=%d\n", + h.nof_retxs + 1, + ue_ctxt.ue_cfg.maxharq_tx); + } + + return SRSLTE_SUCCESS; +} + +int test_ul_sched_result(const sim_enb_ctxt_t& enb_ctxt, const sf_output_res_t& sf_out) +{ + uint32_t pid = to_tx_ul(sf_out.tti_rx).to_uint() % SRSLTE_MAX_HARQ_PROC; + + for (uint32_t cc = 0; cc < enb_ctxt.cell_params.size(); ++cc) { + const auto* phich_begin = &sf_out.ul_cc_result[cc].phich[0]; + const auto* phich_end = &sf_out.ul_cc_result[cc].phich[sf_out.ul_cc_result[cc].nof_phich_elems]; + const auto* pusch_begin = &sf_out.ul_cc_result[cc].pusch[0]; + const auto* pusch_end = &sf_out.ul_cc_result[cc].pusch[sf_out.ul_cc_result[cc].nof_dci_elems]; + + // TEST: rnti must exist for all PHICH + CONDERROR(std::any_of(phich_begin, + phich_end, + [&enb_ctxt](const phich_t& phich) { return enb_ctxt.ue_db.count(phich.rnti) == 0; }), + "Scheduled PHICH does not have associated rnti\n"); + + // TEST: rnti must exist for all PUSCH + CONDERROR(std::any_of(pusch_begin, + pusch_end, + [&enb_ctxt](const pusch_t& pusch) { return enb_ctxt.ue_db.count(pusch.dci.rnti) == 0; }), + "Scheduled PUSCH does not have associated rnti."); + + for (const auto& ue_pair : enb_ctxt.ue_db) { + const auto& ue = ue_pair.second; + uint16_t rnti = ue.rnti; + int ue_cc_idx = ue.enb_to_ue_cc_idx(cc); + + // TEST: Check if CC is configured and active + CONDERROR(ue_cc_idx < 0 or not ue.ue_cfg.supported_cc_list[ue_cc_idx].active, + "PUSCH allocation for disabled or unavailable cc\n"); + + const phich_t* phich_ptr = + std::find_if(phich_begin, phich_end, [rnti](const phich_t& phich) { return phich.rnti == rnti; }); + phich_ptr = phich_ptr == phich_end ? nullptr : phich_ptr; + const pusch_t* pusch_ptr = + std::find_if(pusch_begin, pusch_end, [rnti](const pusch_t& pusch) { return pusch.dci.rnti == rnti; }); + pusch_ptr = pusch_ptr == pusch_end ? nullptr : pusch_ptr; + + // TEST: Already active UL HARQs have to receive PHICH + const auto& h = ue.cc_list[ue_cc_idx].ul_harqs[pid]; + CONDERROR( + h.active and phich_ptr == nullptr, "PHICH not received for rnti=0x%x active UL HARQ pid=%d\n", rnti, pid); + CONDERROR( + not h.active and phich_ptr != nullptr, "PHICH received for rnti=0x%x inactive UL HARQ pid=%d\n", rnti, pid); + + // TEST: absent PUSCH grants for active DL HARQs must be either ACKs, last retx, or interrupted HARQs + if (phich_ptr != nullptr and pusch_ptr == nullptr) { + bool ack = phich_ptr->phich == phich_t::ACK, last_retx = h.nof_retxs + 1 >= ue.ue_cfg.maxharq_tx; + CONDERROR(not ack and not last_retx, "PHICH NACK received for rnti=0x%x but no PUSCH retx reallocated\n", rnti); + } + + if (pusch_ptr != nullptr) { + CONDERROR(pusch_ptr->dci.ue_cc_idx != (uint32_t)ue_cc_idx, "Inconsistent enb_cc_idx -> ue_cc_idx mapping\n"); + + // TEST: DCI is consistent with current UE UL harq state + uint32_t nof_retx = sched_utils::get_nof_retx(pusch_ptr->dci.tb.rv); // 0..3 + + if (h.nof_txs == 0 or h.ndi != pusch_ptr->dci.tb.ndi) { + // newtx + CONDERROR(nof_retx != 0, "Invalid rv index for new tx\n"); + } else { + CONDERROR(not h.active, "retx for inactive UL harq pid=%d\n", h.pid); + if (pusch_ptr->needs_pdcch) { + // adaptive retx + } else { + // non-adaptive retx + CONDERROR(pusch_ptr->dci.type2_alloc.riv != h.riv, "Non-adaptive retx must keep the same riv\n"); + } + if (pusch_ptr->tbs > 0) { + CONDERROR(sched_utils::get_rvidx(h.nof_retxs + 1) != (uint32_t)pusch_ptr->dci.tb.rv, + "Invalid rv index for retx\n"); + } + CONDERROR(to_tx_ul(h.last_tti_rx) > sf_out.tti_rx, "UL harq pid=%d was reused too soon\n", h.pid); + } + } + } + } + + return SRSLTE_SUCCESS; +} + +int test_all_ues(const sim_enb_ctxt_t& enb_ctxt, const sf_output_res_t& sf_out) +{ + for (uint32_t cc = 0; cc < enb_ctxt.cell_params.size(); ++cc) { + for (uint32_t i = 0; i < sf_out.dl_cc_result[cc].nof_data_elems; ++i) { + const sched_interface::dl_sched_data_t& data = sf_out.dl_cc_result[cc].data[i]; + CONDERROR( + enb_ctxt.ue_db.count(data.dci.rnti) == 0, "Allocated DL grant for non-existent rnti=0x%x\n", data.dci.rnti); + TESTASSERT(test_pdsch_grant(enb_ctxt.ue_db.at(data.dci.rnti), sf_out.tti_rx, cc, data) == SRSLTE_SUCCESS); + } + } + + TESTASSERT(test_ul_sched_result(enb_ctxt, sf_out) == SRSLTE_SUCCESS); + + return SRSLTE_SUCCESS; +} + +} // namespace srsenb diff --git a/srsenb/test/mac/sched_ue_ded_test_suite.h b/srsenb/test/mac/sched_ue_ded_test_suite.h new file mode 100644 index 000000000..c2869e51a --- /dev/null +++ b/srsenb/test/mac/sched_ue_ded_test_suite.h @@ -0,0 +1,70 @@ +/* + * Copyright 2013-2020 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/. + * + */ + +#ifndef SRSLTE_SCHED_UE_DED_TEST_SUITE_H +#define SRSLTE_SCHED_UE_DED_TEST_SUITE_H + +#include "sched_common_test_suite.h" +#include "sched_sim_ue.h" + +namespace srsenb { + +/** + * Checks DL grant content and whether it is consistent with the current UE context. Current checks: + * - The DCI rv matches the nof DL harq retxs observed from the UE perspective + * - The number of retxs per DL harq does not exceed its maximum set in the ue cfg + * - HARQ pids are not reused too early (ACK hasn't arrive to the eNB yet) + * @param ue_ctxt current simulation UE context + * @param tti_rx TTI when scheduling decision was made + * @param enb_cc_idx eNB carrier index + * @param pdsch PDSCH grant data + * @return error code + */ +int test_pdsch_grant(const sim_ue_ctxt_t& ue_ctxt, + srslte::tti_point tti_rx, + uint32_t enb_cc_idx, + const sched_interface::dl_sched_data_t& pdsch); + +/** + * Checks PHICH & PUSCH grant content and whether it is consistent with the current UE HARQ state. Current checks: + * - All PHICH and PUSCH grants' rnti values are present in the eNB context + * - UEs only get PUSCH grants in active CCs + * - Active UE UL HARQs expect PHICH + * - Active UE UL HARQs expect PUSCH grants except if it is the last retx + * - The DCI rv matches the nof UL harq retxs observed from the UE perspective + * - The number of retxs per UL harq does not exceed its maximum value set in the ue cfg + * @param enb_ctxt current eNB state, including list of UEs + * @param sf_out result of a subframe sched result + * @return error code + */ +int test_ul_sched_result(const sim_enb_ctxt_t& enb_ctxt, const sf_output_res_t& sf_out); + +/** + * Call all tests that depend on the UE internal state for all registered UEs in the eNB + * @param enb_ctxt current eNB state, including list of UEs + * @param sf_out result of a subframe sched result + * @return error code + */ +int test_all_ues(const sim_enb_ctxt_t& enb_ctxt, const sf_output_res_t& sf_out); + +} // namespace srsenb + +#endif // SRSLTE_SCHED_UE_DED_TEST_SUITE_H