From d42dc50c44555ba4a3ae713659b52370fd921300 Mon Sep 17 00:00:00 2001 From: Francisco Date: Wed, 2 Dec 2020 17:14:25 +0000 Subject: [PATCH] Write TPC sched unit test and fix sched logical channel test --- lib/include/srslte/adt/accumulators.h | 25 ++-- srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h | 80 ++++++++----- srsenb/test/mac/CMakeLists.txt | 30 +++-- srsenb/test/mac/sched_lc_ch_test.cc | 51 +++++--- srsenb/test/mac/sched_tpc_test.cc | 142 +++++++++++++++++++++++ 5 files changed, 259 insertions(+), 69 deletions(-) create mode 100644 srsenb/test/mac/sched_tpc_test.cc diff --git a/lib/include/srslte/adt/accumulators.h b/lib/include/srslte/adt/accumulators.h index c87d4cdfb..86a652fd7 100644 --- a/lib/include/srslte/adt/accumulators.h +++ b/lib/include/srslte/adt/accumulators.h @@ -13,6 +13,7 @@ #ifndef SRSLTE_ACCUMULATORS_H #define SRSLTE_ACCUMULATORS_H +#include #include #include #include @@ -62,7 +63,7 @@ namespace detail { template struct sliding_window { - sliding_window(uint32_t N, T val) : window(N, val) {} + sliding_window(uint32_t N, T val = 0) : window(N, val) {} void push(T sample) { window[next_idx++] = sample; @@ -71,6 +72,7 @@ struct sliding_window { } } size_t size() const { return window.size(); } + const T& oldest() const { return window[(next_idx + size() - 1) % size()]; } T& operator[](size_t i) { return window[i]; } const T& operator[](size_t i) const { return window[i]; } std::vector window; @@ -80,22 +82,21 @@ struct sliding_window { } // namespace detail template -struct sliding_sum { - sliding_sum(uint32_t N) : window(N, 0) {} - - void push(T sample) { window.push(sample); } - T value() const +struct sliding_sum : private detail::sliding_window { + using base_t = detail::sliding_window; + using base_t::oldest; + using base_t::push; + using base_t::size; + using base_t::sliding_window; + + T value() const { T ret = 0; - for (size_t i = 0; i < window.size(); ++i) { - ret += window[i]; + for (size_t i = 0; i < size(); ++i) { + ret += (*this)[i]; } return ret; } - size_t size() const { return window.size(); } - -private: - detail::sliding_window window; }; template diff --git a/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h b/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h index e0279de86..3f07f0b7f 100644 --- a/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h +++ b/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h @@ -26,12 +26,18 @@ namespace srsenb { */ class tpc { - static constexpr size_t SNR_WINDOW_SIZE_MS = 8; - static constexpr int PHR_NEG_NOF_PRB = 1; + static constexpr size_t SNR_WINDOW_SIZE_MS = 20; + static constexpr int undefined_phr = std::numeric_limits::max(); public: + static constexpr int PHR_NEG_NOF_PRB = 1; + tpc(uint32_t cell_nof_prb, float target_snr_dB_ = -1.0) : - nof_prb(cell_nof_prb), target_snr_dB(target_snr_dB_), snr_avg(SNR_WINDOW_SIZE_MS) + nof_prb(cell_nof_prb), + target_snr_dB(target_snr_dB_), + snr_avg(SNR_WINDOW_SIZE_MS), + win_pusch_tpc_values(FDD_HARQ_DELAY_UL_MS + FDD_HARQ_DELAY_DL_MS), + win_pucch_tpc_values(FDD_HARQ_DELAY_DL_MS + FDD_HARQ_DELAY_UL_MS) { max_prbs_cached = nof_prb; } @@ -40,13 +46,13 @@ public: void set_snr(float snr) { pending_snr = snr; } void set_phr(int phr_) { - last_phr = phr_; - sum_pusch_tpc_values = 0; - sum_pucch_tpc_values = 0; + last_phr = phr_; + pucch_phr_flag = false; + pusch_phr_flag = false; // compute and cache the max nof UL PRBs that avoids overflowing PHR max_prbs_cached = PHR_NEG_NOF_PRB; - for (int n = nof_prb; n >= PHR_NEG_NOF_PRB - 1; --n) { + for (int n = nof_prb; n > PHR_NEG_NOF_PRB; --n) { if (last_phr >= 10 * log10(n)) { max_prbs_cached = n; break; @@ -57,17 +63,27 @@ public: void new_tti() { if (target_snr_dB < 0) { + pending_pusch_tpc = 0; + pending_pucch_tpc = 0; return; } - // Enqueue PUSCH/PUCCH TPC sent in last TTI (zero for both Delta_PUSCH/Delta_PUCCH=0 and TPC not sent) - sum_pusch_tpc_values += pending_pusch_tpc; - pending_pusch_tpc = 0; - sum_pucch_tpc_values += pending_pucch_tpc; - pending_pucch_tpc = 0; // Enqueue pending SNR measurement + if (pending_snr == snr_avg.null_value) { + acc_pusch_tpc_values += win_pusch_tpc_values.oldest(); + acc_pucch_tpc_values += win_pusch_tpc_values.oldest(); + } else { + acc_pucch_tpc_values = 0; + acc_pusch_tpc_values = 0; + } snr_avg.push(pending_snr); pending_snr = snr_avg.null_value; + + // Enqueue PUSCH/PUCCH TPC sent in last TTI (zero for both Delta_PUSCH/Delta_PUCCH=0 and TPC not sent) + win_pusch_tpc_values.push(pending_pusch_tpc); + pending_pusch_tpc = 0; + win_pucch_tpc_values.push(pending_pucch_tpc); + pending_pucch_tpc = 0; } /** @@ -77,15 +93,18 @@ public: */ int8_t encode_pusch_tpc() { - pending_pusch_tpc = 0; - if (target_snr_dB <= 0) { - // undefined target SINR case. Increase Tx power, while the number of allocable PRBs remains unchanged - pending_pusch_tpc = (max_prbs_cached == nof_prb) ? 1 : (last_phr < 0 ? -1 : 0); + assert(pending_pusch_tpc == 0); // ensure called once per {cc,tti} + if (target_snr_dB < 0) { + // undefined target SINR case. Increase Tx power once per PHR, considering the number of allocable PRBs remains + // unchanged + if (not pusch_phr_flag) { + pending_pusch_tpc = (max_prbs_cached == nof_prb) ? 1 : (last_phr < 0 ? -1 : 0); + pusch_phr_flag = true; + } } else if (snr_avg.value() != snr_avg.null_value) { // target SINR is finite and there is power headroom float diff = target_snr_dB - snr_avg.value(); - diff -= sum_pusch_tpc_values; - pending_pusch_tpc = 0; + diff -= win_pusch_tpc_values.value() + acc_pusch_tpc_values; if (diff > 1) { pending_pusch_tpc = diff > 3 ? 3 : 1; } else if (diff <= -1) { @@ -107,14 +126,18 @@ public: */ int8_t encode_pucch_tpc() { - pending_pusch_tpc = 0; - if (target_snr_dB <= 0) { - // undefined target SINR case. Increase Tx power, while the number of allocable PRBs remains unchanged - pending_pucch_tpc = (max_prbs_cached == nof_prb) ? 1 : (last_phr < 0 ? -1 : 0); + assert(pending_pucch_tpc == 0); // ensure called once per {cc,tti} + if (target_snr_dB < 0) { + // undefined target SINR case. Increase Tx power once per PHR, considering the number of allocable PRBs remains + // unchanged + if (not pucch_phr_flag) { + pending_pucch_tpc = (max_prbs_cached == nof_prb) ? 1 : (last_phr < 0 ? -1 : 0); + pucch_phr_flag = true; + } } else if (snr_avg.value() != snr_avg.null_value) { // target SINR is finite and there is power headroom float diff = target_snr_dB - snr_avg.value(); - diff -= sum_pucch_tpc_values; + diff -= win_pucch_tpc_values.value() + acc_pucch_tpc_values; pending_pucch_tpc = 0; if (diff > 1) { pending_pucch_tpc = diff > 3 ? 3 : 1; @@ -136,12 +159,13 @@ private: float target_snr_dB; srslte::null_sliding_average snr_avg; - int sum_pusch_tpc_values = 0; - int sum_pucch_tpc_values = 0; - uint32_t max_prbs_cached = 100; - int last_phr = std::numeric_limits::max(); + srslte::sliding_sum win_pusch_tpc_values, win_pucch_tpc_values; + uint32_t max_prbs_cached = 100; int pending_pusch_tpc = 0, pending_pucch_tpc = 0; - float pending_snr = srslte::null_sliding_average::null_value; + float pending_snr = srslte::null_sliding_average::null_value; + int acc_pusch_tpc_values = 0, acc_pucch_tpc_values = 0; + int last_phr = undefined_phr; + bool pusch_phr_flag = false, pucch_phr_flag = false; }; } // namespace srsenb diff --git a/srsenb/test/mac/CMakeLists.txt b/srsenb/test/mac/CMakeLists.txt index e57782743..ca2707755 100644 --- a/srsenb/test/mac/CMakeLists.txt +++ b/srsenb/test/mac/CMakeLists.txt @@ -6,8 +6,9 @@ # the distribution. # -add_library(scheduler_test_common STATIC sched_test_common.cc sched_common_test_suite.cc sched_ue_ded_test_suite.cc +add_library(sched_test_common STATIC sched_test_common.cc sched_common_test_suite.cc sched_ue_ded_test_suite.cc sched_sim_ue.cc sched_sim_ue.cc) +target_link_libraries(sched_test_common srslte_common srslte_mac srsenb_mac) # Scheduler subcomponent testing add_executable(sched_grid_test sched_grid_test.cc) @@ -15,36 +16,41 @@ target_link_libraries(sched_grid_test srsenb_mac srsenb_phy srslte_common srslte_mac - scheduler_test_common + sched_test_common ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) add_test(sched_grid_test sched_grid_test) # Scheduler test random -add_executable(scheduler_test_rand sched_test_rand.cc) -target_link_libraries(scheduler_test_rand srsenb_mac +add_executable(sched_test_rand sched_test_rand.cc) +target_link_libraries(sched_test_rand srsenb_mac srsenb_phy srslte_common srslte_mac - scheduler_test_common + sched_test_common srslte_phy rrc_asn1 ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) -add_test(scheduler_test_rand scheduler_test_rand) +add_test(sched_test_rand sched_test_rand) # Scheduler test random for CA -add_executable(scheduler_ca_test sched_ca_test.cc sched_test_common.cc) -target_link_libraries(scheduler_ca_test srsenb_mac +add_executable(sched_ca_test sched_ca_test.cc) +target_link_libraries(sched_ca_test srsenb_mac srsenb_phy srslte_common srslte_mac srslte_phy - scheduler_test_common + sched_test_common rrc_asn1 ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) -add_test(scheduler_ca_test scheduler_ca_test) +add_test(sched_ca_test sched_ca_test) -add_executable(sched_lc_ch_test sched_lc_ch_test.cc sched_test_common.cc) -target_link_libraries(sched_lc_ch_test srsenb_mac srslte_common srslte_mac scheduler_test_common) \ No newline at end of file +add_executable(sched_lc_ch_test sched_lc_ch_test.cc) +target_link_libraries(sched_lc_ch_test srslte_common srsenb_mac srslte_mac sched_test_common) +add_test(sched_lc_ch_test sched_lc_ch_test) + +add_executable(sched_tpc_test sched_tpc_test.cc) +target_link_libraries(sched_tpc_test srslte_common srslte_mac sched_test_common) +add_test(sched_tpc_test sched_tpc_test) diff --git a/srsenb/test/mac/sched_lc_ch_test.cc b/srsenb/test/mac/sched_lc_ch_test.cc index 38c917467..6191b629e 100644 --- a/srsenb/test/mac/sched_lc_ch_test.cc +++ b/srsenb/test/mac/sched_lc_ch_test.cc @@ -17,42 +17,59 @@ using namespace srsenb; const uint32_t seed = std::chrono::system_clock::now().time_since_epoch().count(); +uint32_t rlc_overhead(uint32_t lcid) +{ + return lcid == 0 ? 0 : 3; +} +uint32_t add_rlc_overhead(uint32_t lcid, uint32_t rlc_payload_size) +{ + return rlc_payload_size + (rlc_payload_size == 0 ? 0 : rlc_overhead(lcid)); +} /// Tests if a PDU was allocated with lcid and pdu_size bytes int test_pdu_alloc_successful(srsenb::lch_ue_manager& lch_handler, sched_interface::dl_sched_pdu_t& pdu, int lcid, - uint32_t pdu_size) + uint32_t rlc_payload_size) { + uint32_t mac_sdu_size = add_rlc_overhead(lcid, rlc_payload_size); TESTASSERT(lch_handler.get_max_prio_lcid() == lcid); - TESTASSERT(lch_handler.alloc_rlc_pdu(&pdu, pdu_size) == (int)pdu_size); + TESTASSERT(lch_handler.alloc_rlc_pdu(&pdu, mac_sdu_size) == (int)mac_sdu_size); TESTASSERT(pdu.lcid == (uint32_t)lcid); - TESTASSERT(pdu.nbytes == pdu_size); + TESTASSERT(pdu.nbytes == mac_sdu_size); return SRSLTE_SUCCESS; } -int test_retx_until_empty(srsenb::lch_ue_manager& lch_handler, int lcid, uint32_t pdu_size) +int test_retx_until_empty(srsenb::lch_ue_manager& lch_handler, int lcid, uint32_t rlc_payload_size) { - uint32_t nof_pdus = lch_handler.get_dl_retx(lcid) / pdu_size; - sched_interface::dl_sched_pdu_t pdu; + int start_rlc_bytes = lch_handler.get_dl_retx(lcid); + int nof_pdus = ceil(start_rlc_bytes / (float)rlc_payload_size); + int rem_rlc_bytes = start_rlc_bytes; - for (uint32_t i = 0; i < nof_pdus; ++i) { - TESTASSERT(test_pdu_alloc_successful(lch_handler, pdu, lcid, pdu_size) == SRSLTE_SUCCESS); - TESTASSERT(lch_handler.get_dl_retx(lcid) == (int)((nof_pdus - i - 1) * pdu_size)); + sched_interface::dl_sched_pdu_t pdu; + for (int i = 0; i < nof_pdus; ++i) { + uint32_t expected_payload_size = std::min(rlc_payload_size, (uint32_t)rem_rlc_bytes); + TESTASSERT(test_pdu_alloc_successful(lch_handler, pdu, lcid, expected_payload_size) == SRSLTE_SUCCESS); + rem_rlc_bytes -= expected_payload_size; + TESTASSERT(lch_handler.get_dl_retx(lcid) == rem_rlc_bytes); } - return nof_pdus * pdu_size; + return start_rlc_bytes; } -int test_newtx_until_empty(srsenb::lch_ue_manager& lch_handler, int lcid, uint32_t pdu_size) +int test_newtx_until_empty(srsenb::lch_ue_manager& lch_handler, int lcid, uint32_t rlc_payload_size) { - uint32_t nof_pdus = lch_handler.get_dl_tx(lcid) / pdu_size; - sched_interface::dl_sched_pdu_t pdu; + int start_rlc_bytes = lch_handler.get_dl_tx(lcid); + int nof_pdus = ceil(start_rlc_bytes / (float)rlc_payload_size); + int rem_rlc_bytes = start_rlc_bytes; - for (uint32_t i = 0; i < nof_pdus; ++i) { - TESTASSERT(test_pdu_alloc_successful(lch_handler, pdu, lcid, pdu_size) == SRSLTE_SUCCESS); - TESTASSERT(lch_handler.get_dl_tx(lcid) == (int)((nof_pdus - i - 1) * pdu_size)); + sched_interface::dl_sched_pdu_t pdu; + for (int i = 0; i < nof_pdus; ++i) { + uint32_t expected_payload_size = std::min(rlc_payload_size, (uint32_t)rem_rlc_bytes); + TESTASSERT(test_pdu_alloc_successful(lch_handler, pdu, lcid, expected_payload_size) == SRSLTE_SUCCESS); + rem_rlc_bytes -= expected_payload_size; + TESTASSERT(lch_handler.get_dl_tx(lcid) == (int)rem_rlc_bytes); } - return nof_pdus * pdu_size; + return start_rlc_bytes; } int test_lc_ch_pbr_infinity() diff --git a/srsenb/test/mac/sched_tpc_test.cc b/srsenb/test/mac/sched_tpc_test.cc new file mode 100644 index 000000000..08b651660 --- /dev/null +++ b/srsenb/test/mac/sched_tpc_test.cc @@ -0,0 +1,142 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2020 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 "srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h" +#include "srslte/common/test_common.h" + +namespace srsenb { + +int test_finite_target_snr() +{ + const uint32_t nof_prbs = 50; + const int target_snr = 15; + + tpc tpcfsm(nof_prbs, 15); + + // TEST: While no SNR info is provided, no TPC commands are sent + for (uint32_t i = 0; i < 100; ++i) { + tpcfsm.new_tti(); + TESTASSERT(tpcfsm.encode_pucch_tpc() == 0); + TESTASSERT(tpcfsm.encode_pusch_tpc() == 0); + } + + // TEST: current SNR above target SNR. Checks: + // - TPC commands should be sent to decrease power + // - The sum power of TPC commands should not exceed the difference between current and target SNRs + int snr_diff = 10; + tpcfsm.set_snr(target_snr + snr_diff); + int sum_pusch = 0, sum_pucch = 0; + for (uint32_t i = 0; i < 100; ++i) { + tpcfsm.new_tti(); + sum_pusch += tpcfsm.encode_pusch_tpc(); + TESTASSERT(sum_pusch < 0 and sum_pusch >= -snr_diff); + sum_pucch += tpcfsm.encode_pucch_tpc(); + TESTASSERT(sum_pucch < 0 and sum_pucch >= -snr_diff); + } + + // TEST: current SNR below target SNR. Checks: + // - TPC commands should be sent to increase power + // - The sum of TPC commands should not exceed the difference between current and target SNRs + snr_diff = -10; + tpcfsm.set_snr(target_snr + snr_diff); + sum_pusch = 0; + sum_pucch = 0; + for (uint32_t i = 0; i < 100; ++i) { + tpcfsm.new_tti(); + sum_pusch += tpcfsm.encode_pusch_tpc(); + TESTASSERT(sum_pusch > 0 and sum_pusch <= -snr_diff); + sum_pucch += tpcfsm.encode_pucch_tpc(); + TESTASSERT(sum_pucch > 0 and sum_pucch <= -snr_diff); + } + + return SRSLTE_SUCCESS; +} + +int test_undefined_target_snr() +{ + const uint32_t nof_prbs = 50; + + tpc tpcfsm(nof_prbs); + TESTASSERT(tpcfsm.max_ul_prbs() == 50); + + // TEST: While the PHR is not updated, a limited number of TPC commands should be sent + int sum_pusch = 0, sum_pucch = 0; + for (uint32_t i = 0; i < 100; ++i) { + tpcfsm.new_tti(); + sum_pusch += tpcfsm.encode_pusch_tpc(); + sum_pucch += tpcfsm.encode_pucch_tpc(); + } + TESTASSERT(sum_pusch <= 3 and sum_pusch >= -1); + TESTASSERT(sum_pucch <= 3 and sum_pucch >= -1); + + // TEST: SNR info should not affect TPC in undefined target SNR mode + int snr_info = 10; + tpcfsm.set_snr(snr_info); + sum_pusch = 0; + sum_pucch = 0; + for (uint32_t i = 0; i < 100; ++i) { + tpcfsm.new_tti(); + sum_pusch += tpcfsm.encode_pusch_tpc(); + sum_pucch += tpcfsm.encode_pucch_tpc(); + } + TESTASSERT(sum_pusch == 0); + TESTASSERT(sum_pucch == 0); + + // TEST: If the PHR allows full utilization of available PRBs, the TPC slightly increments UL Tx power + int phr = 30; + tpcfsm.set_phr(phr); + TESTASSERT(tpcfsm.max_ul_prbs() == 50); + sum_pusch = 0; + sum_pucch = 0; + for (uint32_t i = 0; i < 100; ++i) { + tpcfsm.new_tti(); + sum_pusch += tpcfsm.encode_pusch_tpc(); + sum_pucch += tpcfsm.encode_pucch_tpc(); + } + TESTASSERT(sum_pusch > 0 and sum_pusch <= 3); + TESTASSERT(sum_pucch > 0 and sum_pucch <= 3); + + // TEST: PHR is too low to allow all PRBs to be allocated. This event should not affect TPC commands + phr = 5; + tpcfsm.set_phr(phr); + TESTASSERT(tpcfsm.max_ul_prbs() < 50); + for (uint32_t i = 0; i < 100; ++i) { + tpcfsm.new_tti(); + TESTASSERT(tpcfsm.encode_pusch_tpc() == 0); + TESTASSERT(tpcfsm.encode_pucch_tpc() == 0); + } + + // TEST: PHR is negative. The TPC should slightly decrease Tx UL power until next PHR + phr = -1; + tpcfsm.set_phr(phr); + TESTASSERT(tpcfsm.max_ul_prbs() == tpc::PHR_NEG_NOF_PRB); + sum_pusch = 0; + sum_pucch = 0; + for (uint32_t i = 0; i < 100; ++i) { + tpcfsm.new_tti(); + sum_pusch += tpcfsm.encode_pusch_tpc(); + sum_pucch += tpcfsm.encode_pucch_tpc(); + } + TESTASSERT(sum_pusch <= 0 and sum_pusch >= -1); + TESTASSERT(sum_pucch <= 0 and sum_pucch >= -1); + + return SRSLTE_SUCCESS; +} + +} // namespace srsenb + +int main() +{ + TESTASSERT(srsenb::test_finite_target_snr() == 0); + TESTASSERT(srsenb::test_undefined_target_snr() == 0); + printf("Success\n"); +}