/* * 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 "scheduler_test_common.h" #include "srsenb/hdr/stack/mac/scheduler.h" #include "srslte/common/test_common.h" using namespace srsenb; int output_sched_tester::test_pusch_collisions(const tti_params_t& tti_params, const sched_interface::ul_sched_res_t& ul_result, prbmask_t& ul_allocs) const { uint32_t nof_prb = params.cfg->cell.nof_prb; ul_allocs.resize(nof_prb); ul_allocs.reset(); auto try_ul_fill = [&](srsenb::ul_harq_proc::ul_alloc_t alloc, const char* ch_str, bool strict = true) { CONDERROR((alloc.RB_start + alloc.L) > nof_prb, "[TESTER] Allocated RBs (%d,%d) out-of-bounds\n", alloc.RB_start, alloc.RB_start + alloc.L); CONDERROR(alloc.L == 0, "[TESTER] Allocations must have at least one PRB\n"); if (strict and ul_allocs.any(alloc.RB_start, alloc.RB_start + alloc.L)) { TESTERROR("[TESTER] Collision Detected of %s alloc=(%d,%d) and cumulative_mask=0x%s\n", ch_str, alloc.RB_start, alloc.RB_start + alloc.L, ul_allocs.to_hex().c_str()); } ul_allocs.fill(alloc.RB_start, alloc.RB_start + alloc.L, true); return SRSLTE_SUCCESS; }; /* TEST: Check if there is space for PRACH */ bool is_prach_tti_tx_ul = srslte_prach_tti_opportunity_config_fdd(params.cfg->prach_config, tti_params.tti_tx_ul, -1); if (is_prach_tti_tx_ul) { try_ul_fill({params.cfg->prach_freq_offset, 6}, "PRACH"); } /* TEST: check collisions in PUCCH */ bool strict = nof_prb != 6 or (not is_prach_tti_tx_ul); // and not tti_data.ul_pending_msg3_present); try_ul_fill({0, (uint32_t)params.cfg->nrb_pucch}, "PUCCH", strict); try_ul_fill({params.cfg->cell.nof_prb - params.cfg->nrb_pucch, (uint32_t)params.cfg->nrb_pucch}, "PUCCH", strict); /* TEST: check collisions in the UL PUSCH */ for (uint32_t i = 0; i < ul_result.nof_dci_elems; ++i) { uint32_t L, RBstart; srslte_ra_type2_from_riv(ul_result.pusch[i].dci.type2_alloc.riv, &L, &RBstart, nof_prb, nof_prb); strict = ul_result.pusch[i].needs_pdcch or nof_prb != 6; // Msg3 may collide with PUCCH at PRB==6 try_ul_fill({RBstart, L}, "PUSCH", strict); // ue_stats[ul_result.pusch[i].dci.rnti].nof_ul_rbs += L; } return SRSLTE_SUCCESS; } int output_sched_tester::test_pdsch_collisions(const tti_params_t& tti_params, const sched_interface::dl_sched_res_t& dl_result, rbgmask_t& rbgmask) const { srslte::bounded_bitset<100, true> dl_allocs(params.cfg->cell.nof_prb), alloc_mask(params.cfg->cell.nof_prb); auto try_dl_mask_fill = [&](const srslte_dci_dl_t& dci, const char* channel) { if (extract_dl_prbmask(params.cfg->cell, dci, &alloc_mask) != SRSLTE_SUCCESS) { return SRSLTE_ERROR; } if ((dl_allocs & alloc_mask).any()) { TESTERROR("[TESTER] Detected collision in the DL %s allocation (%s intersects %s)\n", channel, dl_allocs.to_string().c_str(), alloc_mask.to_string().c_str()); } dl_allocs |= alloc_mask; return SRSLTE_SUCCESS; }; // Decode BC allocations, check collisions, and fill cumulative mask for (uint32_t i = 0; i < dl_result.nof_bc_elems; ++i) { TESTASSERT(try_dl_mask_fill(dl_result.bc[i].dci, "BC") == SRSLTE_SUCCESS); } // Decode RAR allocations, check collisions, and fill cumulative mask for (uint32_t i = 0; i < dl_result.nof_rar_elems; ++i) { TESTASSERT(try_dl_mask_fill(dl_result.rar[i].dci, "RAR") == SRSLTE_SUCCESS); } // forbid Data in DL if it conflicts with PRACH for PRB==6 if (params.cfg->cell.nof_prb == 6) { uint32_t tti_rx_ack = TTI_RX_ACK(tti_params.tti_rx); if (srslte_prach_tti_opportunity_config_fdd(params.cfg->prach_config, tti_rx_ack, -1)) { dl_allocs.fill(0, dl_allocs.size()); } } // Decode Data allocations, check collisions and fill cumulative mask for (uint32_t i = 0; i < dl_result.nof_data_elems; ++i) { TESTASSERT(try_dl_mask_fill(dl_result.data[i].dci, "data") == SRSLTE_SUCCESS); } // TEST: check for holes in the PRB mask (RBGs not fully filled) rbgmask.resize(params.nof_rbgs); rbgmask.reset(); srslte::bounded_bitset<100, true> rev_alloc = ~dl_allocs; for (uint32_t i = 0; i < params.nof_rbgs; ++i) { uint32_t lim = SRSLTE_MIN((i + 1) * params.P, dl_allocs.size()); bool val = dl_allocs.any(i * params.P, lim); CONDERROR(rev_alloc.any(i * params.P, lim) and val, "[TESTER] No holes can be left in an RBG\n"); if (val) { rbgmask.set(i); } } return SRSLTE_SUCCESS; } int output_sched_tester::test_sib_scheduling(const tti_params_t& tti_params, const sched_interface::dl_sched_res_t& dl_result) const { uint32_t sfn = tti_params.sfn; uint32_t sf_idx = tti_params.sf_idx; bool sib1_present = ((sfn % 2) == 0) and sf_idx == 5; using bc_elem = const sched_interface::dl_sched_bc_t; bc_elem* bc_begin = &dl_result.bc[0]; bc_elem* bc_end = &dl_result.bc[dl_result.nof_bc_elems]; /* Test if SIB1 was correctly scheduled */ if (sib1_present) { auto it = std::find_if(bc_begin, bc_end, [](bc_elem& elem) { return elem.index == 0; }); CONDERROR(it == bc_end, "Failed to allocate SIB1 in even sfn, sf_idx==5\n"); } /* Test if any SIB was scheduled with wrong index, tbs, or outside of its window */ for (bc_elem* bc = bc_begin; bc != bc_end; ++bc) { if (bc->index == 0) { continue; } CONDERROR(bc->index >= sched_interface::MAX_SIBS, "Invalid SIB idx=%d\n", bc->index + 1); CONDERROR(bc->tbs < params.cfg->sibs[bc->index].len, "Allocated BC process with TBS=%d < sib_len=%d\n", bc->tbs, params.cfg->sibs[bc->index].len); uint32_t x = (bc->index - 1) * params.cfg->si_window_ms; uint32_t sf = x % 10; uint32_t sfn_start = sfn; while ((sfn_start % params.cfg->sibs[bc->index].period_rf) != x / 10) { sfn_start--; } uint32_t win_start = sfn_start * 10 + sf; uint32_t win_end = win_start + params.cfg->si_window_ms; CONDERROR(tti_params.tti_tx_dl < win_start or tti_params.tti_tx_dl > win_end, "Scheduled SIB is outside of its SIB window\n"); } return SRSLTE_SUCCESS; } int output_sched_tester::test_pdcch_collisions(const sched_interface::dl_sched_res_t& dl_result, const sched_interface::ul_sched_res_t& ul_result, srslte::bounded_bitset<128, true>* used_cce) const { used_cce->resize(srslte_regs_pdcch_ncce(params.regs, dl_result.cfi)); used_cce->reset(); // Helper Function: checks if there is any collision. If not, fills the PDCCH mask auto try_cce_fill = [&](const srslte_dci_location_t& dci_loc, const char* ch) { uint32_t cce_start = dci_loc.ncce, cce_stop = dci_loc.ncce + (1u << dci_loc.L); if (used_cce->any(cce_start, cce_stop)) { TESTERROR("[TESTER] %s DCI collision between CCE positions (%u, %u)\n", ch, cce_start, cce_stop); } used_cce->fill(cce_start, cce_stop); return SRSLTE_SUCCESS; }; /* TEST: verify there are no dci collisions for UL, DL data, BC, RAR */ for (uint32_t i = 0; i < ul_result.nof_dci_elems; ++i) { const auto& pusch = ul_result.pusch[i]; if (not pusch.needs_pdcch) { // In case of non-adaptive retx or Msg3 continue; } try_cce_fill(pusch.dci.location, "UL"); } for (uint32_t i = 0; i < dl_result.nof_data_elems; ++i) { try_cce_fill(dl_result.data[i].dci.location, "DL data"); } for (uint32_t i = 0; i < dl_result.nof_bc_elems; ++i) { try_cce_fill(dl_result.bc[i].dci.location, "DL BC"); } for (uint32_t i = 0; i < dl_result.nof_rar_elems; ++i) { try_cce_fill(dl_result.rar[i].dci.location, "DL RAR"); } return SRSLTE_SUCCESS; } int output_sched_tester::test_dci_values_consistency(const sched_interface::dl_sched_res_t& dl_result, const sched_interface::ul_sched_res_t& ul_result) const { for (uint32_t i = 0; i < ul_result.nof_dci_elems; ++i) { const auto& pusch = ul_result.pusch[i]; CONDERROR(pusch.tbs == 0, "Allocated RAR process with invalid TBS=%d\n", pusch.tbs); // CONDERROR(ue_db.count(pusch.dci.rnti) == 0, "The allocated rnti=0x%x does not exist\n", pusch.dci.rnti); if (not pusch.needs_pdcch) { // In case of non-adaptive retx or Msg3 continue; } CONDERROR(pusch.dci.location.L == 0, "[TESTER] Invalid aggregation level %d\n", pusch.dci.location.L); // TODO: Extend this test } for (uint32_t i = 0; i < dl_result.nof_data_elems; ++i) { auto& data = dl_result.data[i]; CONDERROR(data.tbs[0] == 0, "Allocated DL data has empty TBS\n"); } for (uint32_t i = 0; i < dl_result.nof_bc_elems; ++i) { auto& bc = dl_result.bc[i]; if (bc.type == sched_interface::dl_sched_bc_t::BCCH) { CONDERROR(bc.tbs < params.cfg->sibs[bc.index].len, "Allocated BC process with TBS=%d < sib_len=%d\n", bc.tbs, params.cfg->sibs[bc.index].len); } else if (bc.type == sched_interface::dl_sched_bc_t::PCCH) { CONDERROR(bc.tbs == 0, "Allocated paging process with invalid TBS=%d\n", bc.tbs); } else { TESTERROR("Invalid broadcast process id=%d\n", (int)bc.type); } } for (uint32_t i = 0; i < dl_result.nof_rar_elems; ++i) { const auto& rar = dl_result.rar[i]; CONDERROR(rar.tbs == 0, "Allocated RAR process with invalid TBS=%d\n", rar.tbs); } 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) { srslte_pdsch_grant_t grant; srslte_dl_sf_cfg_t dl_sf = {}; srslte_dci_dl_t* dci_dyn = const_cast(&dci); // TODO srslte_cell_t* cell_dyn = const_cast(&cell); alloc_mask->resize(cell.nof_prb); alloc_mask->reset(); CONDERROR(srslte_ra_dl_dci_to_grant(cell_dyn, &dl_sf, SRSLTE_TM1, false, dci_dyn, &grant) == SRSLTE_ERROR, "Failed to decode PDSCH grant\n"); for (uint32_t j = 0; j < alloc_mask->size(); ++j) { if (grant.prb_idx[0][j]) { alloc_mask->set(j); } } return SRSLTE_SUCCESS; }