From 99f94d9a1a1dd44b6a78150971d0f22405485b7b Mon Sep 17 00:00:00 2001 From: Francisco Date: Fri, 4 Dec 2020 14:39:58 +0000 Subject: [PATCH] Bug fixes of srseNB scheduler TPC - fix encoding of TPC command - use of exponential average with irregular sampling for the ULSNR average estimate. Turns out using a time-windowed average for the SNR was a bad idea. If the UL grants are very sporadic, the SNR time window will never have samples when a TPC is encoded - update of TPC sched test - other fixes in accumulators lib --- lib/include/srslte/adt/accumulators.h | 21 ++++- srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h | 113 ++++++++++++++--------- srsenb/test/mac/sched_tpc_test.cc | 38 ++++---- 3 files changed, 112 insertions(+), 60 deletions(-) diff --git a/lib/include/srslte/adt/accumulators.h b/lib/include/srslte/adt/accumulators.h index 4c89653d6..778edfeab 100644 --- a/lib/include/srslte/adt/accumulators.h +++ b/lib/include/srslte/adt/accumulators.h @@ -14,6 +14,7 @@ #define SRSLTE_ACCUMULATORS_H #include +#include #include #include #include @@ -72,7 +73,7 @@ struct sliding_window { } } std::size_t size() const { return window.size(); } - const T& oldest() const { return window[(next_idx + size() - 1) % size()]; } + const T& oldest() const { return window[next_idx % size()]; } T& operator[](std::size_t i) { return window[i]; } const T& operator[](std::size_t i) const { return window[i]; } std::vector window; @@ -133,6 +134,24 @@ private: detail::sliding_window window; }; +template +struct exp_average_irreg_sampling { + // an exp_average has the formula y_n = alpha*x + (1-alpha)*y_n-1 <=> y_n += alpha(x - y_n-1) + // alpha can be thought as 1-exp^{-dt/T} where dt is the sample period and T is the time-constant of a LP filter + // for variable dt, alpha[dt] = 1-exp^{-dt/T} = 1-(exp^{-1/T})^dt = 1 - (1-alpha[1])^dt + exp_average_irreg_sampling(T alpha_, T init_val) : avg_(init_val) + { + assert(alpha_ < 1 and alpha_ > 0 and "Invalid alpha parameter."); + coeff = 1 - alpha_; + } + void push(T sample, uint32_t sample_jump) { avg_ += (1 - pow(coeff, sample_jump)) * (sample - avg_); } + T value() const { return avg_; } + +private: + T avg_ = 0; + T coeff; +}; + } // namespace srslte #endif // SRSLTE_ACCUMULATORS_H diff --git a/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h b/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h index ae01069cd..1dfd9086f 100644 --- a/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h +++ b/srsenb/hdr/stack/mac/sched_ue_ctrl/tpc.h @@ -15,6 +15,7 @@ #include "srslte/adt/accumulators.h" #include "srslte/common/common.h" +#include "srslte/common/logmap.h" namespace srsenb { @@ -26,8 +27,8 @@ namespace srsenb { */ class tpc { - static constexpr size_t SNR_WINDOW_SIZE_MS = 20; - static constexpr int undefined_phr = std::numeric_limits::max(); + static constexpr int undefined_phr = std::numeric_limits::max(); + static constexpr float null_snr = std::numeric_limits::max(); public: static constexpr int PHR_NEG_NOF_PRB = 1; @@ -35,7 +36,7 @@ public: 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), + snr_avg(0.1, target_snr_dB_), 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) { @@ -63,27 +64,29 @@ public: void new_tti() { if (target_snr_dB < 0) { - pending_pusch_tpc = 0; - pending_pucch_tpc = 0; + pending_pusch_delta = 0; + pending_pucch_delta = 0; return; } // Enqueue pending SNR measurement - if (pending_snr == snr_avg.null_value()) { + if (pending_snr == null_snr) { + last_snr_sample_count++; 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, last_snr_sample_count); + last_snr_sample_count = 1; } - snr_avg.push(pending_snr); - pending_snr = snr_avg.null_value(); + pending_snr = null_snr; // 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; + win_pusch_tpc_values.push(pending_pusch_delta); + pending_pusch_delta = 0; + win_pucch_tpc_values.push(pending_pucch_delta); + pending_pucch_delta = 0; } /** @@ -91,31 +94,32 @@ public: * @remark See TS 36.213 Section 5.1.1 * @return accumulated TPC value {-1, 0, 1, 3} */ - int8_t encode_pusch_tpc() + uint8_t encode_pusch_tpc() { - assert(pending_pusch_tpc == 0); // ensure called once per {cc,tti} + assert(pending_pusch_delta == 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; + pending_pusch_delta = (max_prbs_cached == nof_prb) ? 1 : (last_phr < 0 ? -1 : 0); + pusch_phr_flag = true; } - } else if (snr_avg.value() != snr_avg.null_value()) { + } else { // target SINR is finite and there is power headroom float diff = target_snr_dB - snr_avg.value(); diff -= win_pusch_tpc_values.value() + acc_pusch_tpc_values; - if (diff > 1) { - pending_pusch_tpc = diff > 3 ? 3 : 1; - } else if (diff <= -1) { - pending_pusch_tpc = -1; + int8_t diff_round = roundf(diff); + if (diff_round >= 1) { + pending_pusch_delta = diff_round > 3 ? 3 : 1; + } else if (diff_round <= -1) { + pending_pusch_delta = -1; } if (last_phr <= 0) { // In case there is no headroom, forbid power increases - pending_pusch_tpc = std::min(pending_pusch_tpc, 0); + pending_pusch_delta = std::min(pending_pusch_delta, 0); } } - return pending_pusch_tpc; + return encode_tpc_delta(pending_pusch_delta); } /** @@ -124,48 +128,71 @@ public: * @remark See TS 36.213 Section 5.1.2 * @return accumulated TPC value {-1, 0, 1, 3} */ - int8_t encode_pucch_tpc() + uint8_t encode_pucch_tpc() { - assert(pending_pucch_tpc == 0); // ensure called once per {cc,tti} + assert(pending_pucch_delta == 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; + pending_pucch_delta = (max_prbs_cached == nof_prb) ? 1 : (last_phr < 0 ? -1 : 0); + pucch_phr_flag = true; } - } else if (snr_avg.value() != snr_avg.null_value()) { + } else { // target SINR is finite and there is power headroom float diff = target_snr_dB - snr_avg.value(); diff -= win_pucch_tpc_values.value() + acc_pucch_tpc_values; - pending_pucch_tpc = 0; - if (diff > 1) { - pending_pucch_tpc = diff > 3 ? 3 : 1; - } else if (diff <= -1) { - pending_pucch_tpc = -1; + int8_t diff_round = roundf(diff); + if (diff_round >= 1) { + pending_pucch_delta = diff_round > 3 ? 3 : 1; + } else if (diff_round <= -1) { + pending_pucch_delta = -1; } if (last_phr <= 0) { // In case there is no headroom, forbid power increases - pending_pucch_tpc = std::min(pending_pucch_tpc, 0); + pending_pucch_delta = std::min(pending_pucch_delta, 0); } } - return pending_pucch_tpc; + return encode_tpc_delta(pending_pucch_delta); } uint32_t max_ul_prbs() const { return max_prbs_cached; } private: + uint8_t encode_tpc_delta(int8_t delta) + { + switch (delta) { + case -1: + return 0; + case 0: + return 1; + case 1: + return 2; + case 3: + return 3; + default: + srslte::logmap::get("MAC")->warning("Invalid TPC delta value=%d\n", delta); + return 1; + } + } + uint32_t nof_prb; float target_snr_dB; - srslte::null_sliding_average snr_avg; - 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(); - 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; + // PHR-related variables + int last_phr = undefined_phr; + uint32_t max_prbs_cached = 100; + bool pusch_phr_flag = false, pucch_phr_flag = false; + + // SNR estimation + srslte::exp_average_irreg_sampling snr_avg; + float pending_snr = srslte::null_sliding_average::null_value(); + uint32_t last_snr_sample_count = 1; + + // Accumulation of past TPC commands + srslte::sliding_sum win_pusch_tpc_values, win_pucch_tpc_values; + int pending_pusch_delta = 0, pending_pucch_delta = 0; + int acc_pusch_tpc_values = 0, acc_pucch_tpc_values = 0; }; } // namespace srsenb diff --git a/srsenb/test/mac/sched_tpc_test.cc b/srsenb/test/mac/sched_tpc_test.cc index 08b651660..181b9b821 100644 --- a/srsenb/test/mac/sched_tpc_test.cc +++ b/srsenb/test/mac/sched_tpc_test.cc @@ -15,6 +15,12 @@ namespace srsenb { +int8_t decode_tpc(uint8_t encoded_tpc) +{ + const static int8_t tpc_table[] = {-1, 0, 1, 3}; + return encoded_tpc < sizeof(tpc_table) ? tpc_table[encoded_tpc] : std::numeric_limits::max(); +} + int test_finite_target_snr() { const uint32_t nof_prbs = 50; @@ -25,8 +31,8 @@ int test_finite_target_snr() // 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); + TESTASSERT(decode_tpc(tpcfsm.encode_pucch_tpc()) == 0); + TESTASSERT(decode_tpc(tpcfsm.encode_pusch_tpc()) == 0); } // TEST: current SNR above target SNR. Checks: @@ -37,9 +43,9 @@ int test_finite_target_snr() 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_pusch += decode_tpc(tpcfsm.encode_pusch_tpc()); TESTASSERT(sum_pusch < 0 and sum_pusch >= -snr_diff); - sum_pucch += tpcfsm.encode_pucch_tpc(); + sum_pucch += decode_tpc(tpcfsm.encode_pucch_tpc()); TESTASSERT(sum_pucch < 0 and sum_pucch >= -snr_diff); } @@ -52,9 +58,9 @@ int test_finite_target_snr() sum_pucch = 0; for (uint32_t i = 0; i < 100; ++i) { tpcfsm.new_tti(); - sum_pusch += tpcfsm.encode_pusch_tpc(); + sum_pusch += decode_tpc(tpcfsm.encode_pusch_tpc()); TESTASSERT(sum_pusch > 0 and sum_pusch <= -snr_diff); - sum_pucch += tpcfsm.encode_pucch_tpc(); + sum_pucch += decode_tpc(tpcfsm.encode_pucch_tpc()); TESTASSERT(sum_pucch > 0 and sum_pucch <= -snr_diff); } @@ -72,8 +78,8 @@ int test_undefined_target_snr() 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(); + sum_pusch += decode_tpc(tpcfsm.encode_pusch_tpc()); + sum_pucch += decode_tpc(tpcfsm.encode_pucch_tpc()); } TESTASSERT(sum_pusch <= 3 and sum_pusch >= -1); TESTASSERT(sum_pucch <= 3 and sum_pucch >= -1); @@ -85,8 +91,8 @@ int test_undefined_target_snr() 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(); + sum_pusch += decode_tpc(tpcfsm.encode_pusch_tpc()); + sum_pucch += decode_tpc(tpcfsm.encode_pucch_tpc()); } TESTASSERT(sum_pusch == 0); TESTASSERT(sum_pucch == 0); @@ -99,8 +105,8 @@ int test_undefined_target_snr() 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(); + sum_pusch += decode_tpc(tpcfsm.encode_pusch_tpc()); + sum_pucch += decode_tpc(tpcfsm.encode_pucch_tpc()); } TESTASSERT(sum_pusch > 0 and sum_pusch <= 3); TESTASSERT(sum_pucch > 0 and sum_pucch <= 3); @@ -111,8 +117,8 @@ int test_undefined_target_snr() 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); + TESTASSERT(decode_tpc(tpcfsm.encode_pusch_tpc()) == 0); + TESTASSERT(decode_tpc(tpcfsm.encode_pucch_tpc()) == 0); } // TEST: PHR is negative. The TPC should slightly decrease Tx UL power until next PHR @@ -123,8 +129,8 @@ int test_undefined_target_snr() 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(); + sum_pusch += decode_tpc(tpcfsm.encode_pusch_tpc()); + sum_pucch += decode_tpc(tpcfsm.encode_pucch_tpc()); } TESTASSERT(sum_pusch <= 0 and sum_pusch >= -1); TESTASSERT(sum_pucch <= 0 and sum_pucch >= -1);