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
master
Francisco 4 years ago committed by Andre Puschmann
parent d6b5229dbe
commit 99f94d9a1a

@ -14,6 +14,7 @@
#define SRSLTE_ACCUMULATORS_H
#include <cassert>
#include <cmath>
#include <cstdint>
#include <limits>
#include <vector>
@ -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<T> window;
@ -133,6 +134,24 @@ private:
detail::sliding_window<T> window;
};
template <typename T>
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

@ -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<int>::max();
static constexpr int undefined_phr = std::numeric_limits<int>::max();
static constexpr float null_snr = std::numeric_limits<float>::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<float> snr_avg;
srslte::sliding_sum<int> 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<float>::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<float> snr_avg;
float pending_snr = srslte::null_sliding_average<float>::null_value();
uint32_t last_snr_sample_count = 1;
// Accumulation of past TPC commands
srslte::sliding_sum<int> 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

@ -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<int8_t>::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);

Loading…
Cancel
Save