mirror of https://github.com/pvnis/srsRAN_4G.git
Crest Factor Reduction feature for the phy layer (#3720)
* Add CFR module to the phy lib * Add dynamic threshold with PAPR estimation * Add a CFR unit test, CFR module improvements and refactoring. Swap the gain normalization before the CFR. * Add CFR config interface to srsenb * Add CFR support to pdsch_enodeb * Add DL PAPR measurement to eNB. Co-authored-by: Cristian Balint <cristian.balint@gmail.com> * Add test coverage to srsran_vec_gen_clip_envmaster
parent
91502c87db
commit
b2075673e5
@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \section COPYRIGHT
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SRSRAN_CFR_H
|
||||||
|
#define SRSRAN_CFR_H
|
||||||
|
|
||||||
|
#include "srsran/config.h"
|
||||||
|
#include "srsran/phy/common/phy_common.h"
|
||||||
|
#include "srsran/phy/dft/dft.h"
|
||||||
|
|
||||||
|
#define CFR_EMA_INIT_AVG_PWR 0.1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief CFR manual threshold or PAPR limiting with Moving Average or EMA power averaging
|
||||||
|
*/
|
||||||
|
typedef enum SRSRAN_API {
|
||||||
|
SRSRAN_CFR_THR_MANUAL = 1,
|
||||||
|
SRSRAN_CFR_THR_AUTO_CMA = 2,
|
||||||
|
SRSRAN_CFR_THR_AUTO_EMA = 3
|
||||||
|
} srsran_cfr_mode_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief CFR module configuration arguments
|
||||||
|
*/
|
||||||
|
typedef struct SRSRAN_API {
|
||||||
|
bool cfr_enable;
|
||||||
|
srsran_cfr_mode_t cfr_mode;
|
||||||
|
// always used (mandatory)
|
||||||
|
uint32_t symbol_bw; ///< OFDM symbol bandwidth, in FFT bins
|
||||||
|
uint32_t symbol_sz; ///< OFDM symbol size (in samples). This is the FFT size
|
||||||
|
float alpha; ///< Alpha parameter of the clipping algorithm
|
||||||
|
bool dc_sc; ///< Take into account the DC subcarrier for the filter BW
|
||||||
|
|
||||||
|
// SRSRAN_CFR_THR_MANUAL mode parameters
|
||||||
|
float manual_thr; ///< Fixed threshold used in SRSRAN_CFR_THR_MANUAL mode
|
||||||
|
|
||||||
|
// SRSRAN_CFR_THR_AUTO_CMA and SRSRAN_CFR_THR_AUTO_EMA mode parameters
|
||||||
|
bool measure_out_papr; ///< Enable / disable output PAPR measurement
|
||||||
|
float max_papr_db; ///< Input PAPR threshold used in SRSRAN_CFR_THR_AUTO_CMA and SRSRAN_CFR_THR_AUTO_EMA modes
|
||||||
|
float ema_alpha; ///< EMA alpha parameter for avg power calculation, used in SRSRAN_CFR_THR_AUTO_EMA mode
|
||||||
|
} srsran_cfr_cfg_t;
|
||||||
|
|
||||||
|
typedef struct SRSRAN_API {
|
||||||
|
srsran_cfr_cfg_t cfg;
|
||||||
|
float max_papr_lin;
|
||||||
|
|
||||||
|
srsran_dft_plan_t fft_plan;
|
||||||
|
srsran_dft_plan_t ifft_plan;
|
||||||
|
float* lpf_spectrum; ///< FFT filter spectrum
|
||||||
|
uint32_t lpf_bw; ///< Bandwidth of the LPF
|
||||||
|
|
||||||
|
float* abs_buffer_in; ///< Store the input absolute value
|
||||||
|
float* abs_buffer_out; ///< Store the output absolute value
|
||||||
|
cf_t* peak_buffer;
|
||||||
|
|
||||||
|
float pwr_avg_in; ///< store the avg. input power with MA or EMA averaging
|
||||||
|
float pwr_avg_out; ///< store the avg. output power with MA or EMA averaging
|
||||||
|
|
||||||
|
// Power average buffers, used in SRSRAN_CFR_THR_AUTO_CMA mode
|
||||||
|
uint64_t cma_n;
|
||||||
|
} srsran_cfr_t;
|
||||||
|
|
||||||
|
SRSRAN_API int srsran_cfr_init(srsran_cfr_t* q, srsran_cfr_cfg_t* cfg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Applies the CFR algorithm to the time domain OFDM symbols
|
||||||
|
*
|
||||||
|
* @attention This function must be called once per symbol, and it will process q->symbol_sz samples
|
||||||
|
*
|
||||||
|
* @param[in] q The CFR object and configuration
|
||||||
|
* @param[in] in Input buffer containing the time domain OFDM symbol without CP
|
||||||
|
* @param[out] out Output buffer with the processed OFDM symbol
|
||||||
|
* @return SRSRAN_SUCCESS if the CFR object is initialised, otherwise SRSRAN_ERROR
|
||||||
|
*/
|
||||||
|
SRSRAN_API void srsran_cfr_process(srsran_cfr_t* q, cf_t* in, cf_t* out);
|
||||||
|
|
||||||
|
SRSRAN_API void srsran_cfr_free(srsran_cfr_t* q);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks the validity of the CFR algorithm parameters.
|
||||||
|
*
|
||||||
|
* @attention Does not check symbol size and bandwidth
|
||||||
|
*
|
||||||
|
* @param[in] cfr_conf the CFR configuration
|
||||||
|
* @return true if the configuration is valid, false otherwise
|
||||||
|
*/
|
||||||
|
SRSRAN_API bool srsran_cfr_params_valid(srsran_cfr_cfg_t* cfr_conf);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the manual threshold of the CFR (used in manual mode).
|
||||||
|
*
|
||||||
|
* @attention this is not thread-safe
|
||||||
|
*
|
||||||
|
* @param[in] q the CFR object
|
||||||
|
* @return SRSRAN_SUCCESS if successful, SRSRAN_ERROR or SRSRAN_ERROR_INVALID_INPUTS otherwise
|
||||||
|
*/
|
||||||
|
SRSRAN_API int srsran_cfr_set_threshold(srsran_cfr_t* q, float thres);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the papr target of the CFR (used in auto modes).
|
||||||
|
*
|
||||||
|
* @attention this is not thread-safe
|
||||||
|
*
|
||||||
|
* @param[in] q the CFR object
|
||||||
|
* @return SRSRAN_SUCCESS if successful, SRSRAN_ERROR or SRSRAN_ERROR_INVALID_INPUTS otherwise
|
||||||
|
*/
|
||||||
|
SRSRAN_API int srsran_cfr_set_papr(srsran_cfr_t* q, float papr);
|
||||||
|
|
||||||
|
#endif // SRSRAN_CFR_H
|
@ -0,0 +1,13 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2013-2021 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
set(SRCS cfr.c)
|
||||||
|
add_library(srsran_cfr OBJECT ${SRCS})
|
||||||
|
|
||||||
|
add_subdirectory(test)
|
||||||
|
|
@ -0,0 +1,367 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \section COPYRIGHT
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 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 "srsran/phy/cfr/cfr.h"
|
||||||
|
#include "srsran/phy/utils/debug.h"
|
||||||
|
#include "srsran/phy/utils/vector.h"
|
||||||
|
|
||||||
|
// Uncomment this to use a literal implementation of the CFR algorithm
|
||||||
|
// #define CFR_PEAK_EXTRACTION
|
||||||
|
|
||||||
|
// Uncomment this to filter by zeroing the FFT bins instead of applying a frequency window
|
||||||
|
#define CFR_LPF_WITH_ZEROS
|
||||||
|
|
||||||
|
static inline float cfr_symb_peak(float* in_abs, int len);
|
||||||
|
|
||||||
|
void srsran_cfr_process(srsran_cfr_t* q, cf_t* in, cf_t* out)
|
||||||
|
{
|
||||||
|
if (q == NULL || in == NULL || out == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!q->cfg.cfr_enable) {
|
||||||
|
// If no processing, copy the input samples into the output buffer
|
||||||
|
if (in != out) {
|
||||||
|
srsran_vec_cf_copy(out, in, q->cfg.symbol_sz);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float alpha = q->cfg.alpha;
|
||||||
|
const uint32_t symbol_sz = q->cfg.symbol_sz;
|
||||||
|
float beta = 0.0f;
|
||||||
|
|
||||||
|
// Calculate absolute input values
|
||||||
|
srsran_vec_abs_cf(in, q->abs_buffer_in, symbol_sz);
|
||||||
|
|
||||||
|
// In auto modes, the beta threshold is calculated based on the measured PAPR
|
||||||
|
if (q->cfg.cfr_mode == SRSRAN_CFR_THR_MANUAL) {
|
||||||
|
beta = q->cfg.manual_thr;
|
||||||
|
} else {
|
||||||
|
const float symb_peak = cfr_symb_peak(q->abs_buffer_in, q->cfg.symbol_sz);
|
||||||
|
const float pwr_symb_peak = symb_peak * symb_peak;
|
||||||
|
const float pwr_symb_avg = srsran_vec_avg_power_ff(q->abs_buffer_in, q->cfg.symbol_sz);
|
||||||
|
float symb_papr = 0.0f;
|
||||||
|
|
||||||
|
if (isnormal(pwr_symb_avg) && isnormal(pwr_symb_peak)) {
|
||||||
|
if (q->cfg.cfr_mode == SRSRAN_CFR_THR_AUTO_CMA) {
|
||||||
|
// Once cma_n reaches its max value, stop incrementing to prevent overflow.
|
||||||
|
// This turns the averaging into a de-facto EMA with an extremely slow time constant
|
||||||
|
q->pwr_avg_in = SRSRAN_VEC_CMA(pwr_symb_avg, q->pwr_avg_in, q->cma_n++);
|
||||||
|
q->cma_n = q->cma_n & UINT64_MAX ? q->cma_n : UINT64_MAX;
|
||||||
|
} else if (q->cfg.cfr_mode == SRSRAN_CFR_THR_AUTO_EMA) {
|
||||||
|
q->pwr_avg_in = SRSRAN_VEC_EMA(pwr_symb_avg, q->pwr_avg_in, q->cfg.ema_alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
symb_papr = pwr_symb_peak / q->pwr_avg_in;
|
||||||
|
}
|
||||||
|
float papr_reduction = symb_papr / q->max_papr_lin;
|
||||||
|
beta = (papr_reduction > 1) ? symb_peak / sqrtf(papr_reduction) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clipping algorithm
|
||||||
|
if (isnormal(beta)) {
|
||||||
|
#ifdef CFR_PEAK_EXTRACTION
|
||||||
|
srsran_vec_cf_zero(q->peak_buffer, symbol_sz);
|
||||||
|
cf_t clip_thr = 0;
|
||||||
|
for (int i = 0; i < symbol_sz; i++) {
|
||||||
|
if (q->abs_buffer_in[i] > beta) {
|
||||||
|
clip_thr = beta * (in[i] / q->abs_buffer_in[i]);
|
||||||
|
q->peak_buffer[i] = in[i] - clip_thr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply FFT filter to the peak signal
|
||||||
|
srsran_dft_run_c(&q->fft_plan, q->peak_buffer, q->peak_buffer);
|
||||||
|
#ifdef CFR_LPF_WITH_ZEROS
|
||||||
|
srsran_vec_cf_zero(q->peak_buffer + q->lpf_bw / 2 + q->cfg.dc_sc, symbol_sz - q->cfg.symbol_bw - q->cfg.dc_sc);
|
||||||
|
#else /* CFR_LPF_WITH_ZEROS */
|
||||||
|
srsran_vec_prod_cfc(q->peak_buffer, q->lpf_spectrum, q->peak_buffer, symbol_sz);
|
||||||
|
#endif /* CFR_LPF_WITH_ZEROS */
|
||||||
|
srsran_dft_run_c(&q->ifft_plan, q->peak_buffer, q->peak_buffer);
|
||||||
|
|
||||||
|
// Scale the peak signal according to alpha
|
||||||
|
srsran_vec_sc_prod_cfc(q->peak_buffer, alpha, q->peak_buffer, symbol_sz);
|
||||||
|
|
||||||
|
// Apply the filtered clipping
|
||||||
|
srsran_vec_sub_ccc(in, q->peak_buffer, out, symbol_sz);
|
||||||
|
#else /* CFR_PEAK_EXTRACTION */
|
||||||
|
|
||||||
|
// Generate a clipping envelope and clip the signal
|
||||||
|
srsran_vec_gen_clip_env(q->abs_buffer_in, beta, alpha, q->abs_buffer_in, symbol_sz);
|
||||||
|
srsran_vec_prod_cfc(in, q->abs_buffer_in, out, symbol_sz);
|
||||||
|
|
||||||
|
// FFT filter
|
||||||
|
srsran_dft_run_c(&q->fft_plan, out, out);
|
||||||
|
#ifdef CFR_LPF_WITH_ZEROS
|
||||||
|
srsran_vec_cf_zero(out + q->lpf_bw / 2 + q->cfg.dc_sc, symbol_sz - q->cfg.symbol_bw - q->cfg.dc_sc);
|
||||||
|
#else /* CFR_LPF_WITH_ZEROS */
|
||||||
|
srsran_vec_prod_cfc(out, q->lpf_spectrum, out, symbol_sz);
|
||||||
|
#endif /* CFR_LPF_WITH_ZEROS */
|
||||||
|
srsran_dft_run_c(&q->ifft_plan, out, out);
|
||||||
|
#endif /* CFR_PEAK_EXTRACTION */
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If no processing, copy the input samples into the output buffer
|
||||||
|
if (in != out) {
|
||||||
|
srsran_vec_cf_copy(out, in, symbol_sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (q->cfg.cfr_mode != SRSRAN_CFR_THR_MANUAL && q->cfg.measure_out_papr) {
|
||||||
|
srsran_vec_abs_cf(in, q->abs_buffer_out, symbol_sz);
|
||||||
|
|
||||||
|
const float symb_peak = cfr_symb_peak(q->abs_buffer_out, q->cfg.symbol_sz);
|
||||||
|
const float pwr_symb_peak = symb_peak * symb_peak;
|
||||||
|
const float pwr_symb_avg = srsran_vec_avg_power_ff(q->abs_buffer_out, q->cfg.symbol_sz);
|
||||||
|
float symb_papr = 0.0f;
|
||||||
|
|
||||||
|
if (isnormal(pwr_symb_avg) && isnormal(pwr_symb_peak)) {
|
||||||
|
if (q->cfg.cfr_mode == SRSRAN_CFR_THR_AUTO_CMA) {
|
||||||
|
// Do not increment cma_n here, as it is being done when calculating input PAPR
|
||||||
|
q->pwr_avg_out = SRSRAN_VEC_CMA(pwr_symb_avg, q->pwr_avg_out, q->cma_n);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (q->cfg.cfr_mode == SRSRAN_CFR_THR_AUTO_EMA) {
|
||||||
|
q->pwr_avg_out = SRSRAN_VEC_EMA(pwr_symb_avg, q->pwr_avg_out, q->cfg.ema_alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
symb_papr = pwr_symb_peak / q->pwr_avg_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float papr_out_db = srsran_convert_power_to_dB(symb_papr);
|
||||||
|
printf("Output PAPR: %f dB\n", papr_out_db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int srsran_cfr_init(srsran_cfr_t* q, srsran_cfr_cfg_t* cfg)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
if (q == NULL || cfg == NULL) {
|
||||||
|
ERROR("Error, invalid inputs");
|
||||||
|
ret = SRSRAN_ERROR_INVALID_INPUTS;
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
if (!cfg->symbol_sz || !cfg->symbol_bw || cfg->alpha < 0 || cfg->alpha > 1) {
|
||||||
|
ERROR("Error, invalid configuration");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
if (cfg->cfr_mode == SRSRAN_CFR_THR_MANUAL && cfg->manual_thr <= 0) {
|
||||||
|
ERROR("Error, invalid configuration for manual threshold");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
if (cfg->cfr_mode == SRSRAN_CFR_THR_AUTO_CMA && (cfg->max_papr_db <= 0)) {
|
||||||
|
ERROR("Error, invalid configuration for CMA averaging");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
if (cfg->cfr_mode == SRSRAN_CFR_THR_AUTO_EMA &&
|
||||||
|
(cfg->max_papr_db <= 0 || (cfg->ema_alpha < 0 || cfg->ema_alpha > 1))) {
|
||||||
|
ERROR("Error, invalid configuration for EMA averaging");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy all the configuration parameters
|
||||||
|
q->cfg = *cfg;
|
||||||
|
q->max_papr_lin = srsran_convert_dB_to_power(q->cfg.max_papr_db);
|
||||||
|
q->pwr_avg_in = CFR_EMA_INIT_AVG_PWR;
|
||||||
|
q->cma_n = 0;
|
||||||
|
|
||||||
|
if (q->cfg.measure_out_papr) {
|
||||||
|
q->pwr_avg_out = CFR_EMA_INIT_AVG_PWR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q->abs_buffer_in) {
|
||||||
|
free(q->abs_buffer_in);
|
||||||
|
}
|
||||||
|
q->abs_buffer_in = srsran_vec_f_malloc(q->cfg.symbol_sz);
|
||||||
|
if (!q->abs_buffer_in) {
|
||||||
|
ERROR("Error allocating abs_buffer_in");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q->abs_buffer_out) {
|
||||||
|
free(q->abs_buffer_out);
|
||||||
|
}
|
||||||
|
q->abs_buffer_out = srsran_vec_f_malloc(q->cfg.symbol_sz);
|
||||||
|
if (!q->abs_buffer_out) {
|
||||||
|
ERROR("Error allocating abs_buffer_out");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q->peak_buffer) {
|
||||||
|
free(q->peak_buffer);
|
||||||
|
}
|
||||||
|
q->peak_buffer = srsran_vec_cf_malloc(q->cfg.symbol_sz);
|
||||||
|
if (!q->peak_buffer) {
|
||||||
|
ERROR("Error allocating peak_buffer");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the filter
|
||||||
|
if (q->lpf_spectrum) {
|
||||||
|
free(q->lpf_spectrum);
|
||||||
|
}
|
||||||
|
q->lpf_spectrum = srsran_vec_f_malloc(q->cfg.symbol_sz);
|
||||||
|
if (!q->lpf_spectrum) {
|
||||||
|
ERROR("Error allocating lpf_spectrum");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The LPF bandwidth is exactly the OFDM symbol bandwidth, in number of FFT bins
|
||||||
|
q->lpf_bw = q->cfg.symbol_bw;
|
||||||
|
|
||||||
|
// Initialise the filter
|
||||||
|
srsran_vec_f_zero(q->lpf_spectrum, q->cfg.symbol_sz);
|
||||||
|
|
||||||
|
// DC subcarrier is in position 0, so the OFDM symbol can go from index 1 to q->lpf_bw / 2 + 1
|
||||||
|
for (uint32_t i = 0; i < q->lpf_bw / 2 + q->cfg.dc_sc; i++) {
|
||||||
|
q->lpf_spectrum[i] = 1;
|
||||||
|
}
|
||||||
|
for (uint32_t i = q->cfg.symbol_sz - q->lpf_bw / 2; i < q->cfg.symbol_sz; i++) {
|
||||||
|
q->lpf_spectrum[i] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FFT plans, for 1 OFDM symbol
|
||||||
|
if (q->fft_plan.size) {
|
||||||
|
// Replan if it was initialised previously with bigger FFT size
|
||||||
|
if (q->fft_plan.size >= q->cfg.symbol_sz) {
|
||||||
|
if (srsran_dft_replan(&q->fft_plan, q->cfg.symbol_sz)) {
|
||||||
|
ERROR("Replaning DFT plan");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
srsran_dft_plan_free(&q->fft_plan);
|
||||||
|
if (srsran_dft_plan_c(&q->fft_plan, q->cfg.symbol_sz, SRSRAN_DFT_FORWARD)) {
|
||||||
|
ERROR("Creating DFT plan");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create plan from zero otherwise
|
||||||
|
if (srsran_dft_plan_c(&q->fft_plan, q->cfg.symbol_sz, SRSRAN_DFT_FORWARD)) {
|
||||||
|
ERROR("Creating DFT plan");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q->ifft_plan.size) {
|
||||||
|
if (q->ifft_plan.size >= q->cfg.symbol_sz) {
|
||||||
|
// Replan if it was initialised previously with bigger FFT size
|
||||||
|
if (srsran_dft_replan(&q->ifft_plan, q->cfg.symbol_sz)) {
|
||||||
|
ERROR("Replaning DFT plan");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
srsran_dft_plan_free(&q->ifft_plan);
|
||||||
|
if (srsran_dft_plan_c(&q->ifft_plan, q->cfg.symbol_sz, SRSRAN_DFT_BACKWARD)) {
|
||||||
|
ERROR("Creating DFT plan");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create plan from zero otherwise
|
||||||
|
if (srsran_dft_plan_c(&q->ifft_plan, q->cfg.symbol_sz, SRSRAN_DFT_BACKWARD)) {
|
||||||
|
ERROR("Creating DFT plan");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_dft_plan_set_norm(&q->fft_plan, true);
|
||||||
|
srsran_dft_plan_set_norm(&q->ifft_plan, true);
|
||||||
|
|
||||||
|
srsran_vec_cf_zero(q->peak_buffer, q->cfg.symbol_sz);
|
||||||
|
srsran_vec_f_zero(q->abs_buffer_in, q->cfg.symbol_sz);
|
||||||
|
srsran_vec_f_zero(q->abs_buffer_out, q->cfg.symbol_sz);
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
if (ret < SRSRAN_SUCCESS) {
|
||||||
|
srsran_cfr_free(q);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void srsran_cfr_free(srsran_cfr_t* q)
|
||||||
|
{
|
||||||
|
if (q) {
|
||||||
|
srsran_dft_plan_free(&q->fft_plan);
|
||||||
|
srsran_dft_plan_free(&q->ifft_plan);
|
||||||
|
if (q->abs_buffer_in) {
|
||||||
|
free(q->abs_buffer_in);
|
||||||
|
}
|
||||||
|
if (q->abs_buffer_out) {
|
||||||
|
free(q->abs_buffer_out);
|
||||||
|
}
|
||||||
|
if (q->peak_buffer) {
|
||||||
|
free(q->peak_buffer);
|
||||||
|
}
|
||||||
|
if (q->lpf_spectrum) {
|
||||||
|
free(q->lpf_spectrum);
|
||||||
|
}
|
||||||
|
SRSRAN_MEM_ZERO(q, srsran_cfr_t, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the peak absolute value of an OFDM symbol
|
||||||
|
static inline float cfr_symb_peak(float* in_abs, int len)
|
||||||
|
{
|
||||||
|
const uint32_t max_index = srsran_vec_max_fi(in_abs, len);
|
||||||
|
return in_abs[max_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool srsran_cfr_params_valid(srsran_cfr_cfg_t* cfr_conf)
|
||||||
|
{
|
||||||
|
if (cfr_conf == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cfr_conf->alpha < 0 || cfr_conf->alpha > 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cfr_conf->cfr_mode == SRSRAN_CFR_THR_MANUAL && cfr_conf->manual_thr <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cfr_conf->cfr_mode == SRSRAN_CFR_THR_AUTO_CMA && (cfr_conf->max_papr_db <= 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cfr_conf->cfr_mode == SRSRAN_CFR_THR_AUTO_EMA &&
|
||||||
|
(cfr_conf->max_papr_db <= 0 || (cfr_conf->ema_alpha < 0 || cfr_conf->ema_alpha > 1))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int srsran_cfr_set_threshold(srsran_cfr_t* q, float thres)
|
||||||
|
{
|
||||||
|
if (q == NULL) {
|
||||||
|
ERROR("Invalid CFR object");
|
||||||
|
return SRSRAN_ERROR_INVALID_INPUTS;
|
||||||
|
}
|
||||||
|
if (thres <= 0.0f) {
|
||||||
|
ERROR("Invalid CFR threshold");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
q->cfg.manual_thr = thres;
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int srsran_cfr_set_papr(srsran_cfr_t* q, float papr)
|
||||||
|
{
|
||||||
|
if (q == NULL) {
|
||||||
|
ERROR("Invalid CFR object");
|
||||||
|
return SRSRAN_ERROR_INVALID_INPUTS;
|
||||||
|
}
|
||||||
|
if (papr <= 0.0f) {
|
||||||
|
ERROR("Invalid CFR configuration");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
q->cfg.max_papr_db = papr;
|
||||||
|
q->max_papr_lin = srsran_convert_dB_to_power(q->cfg.max_papr_db);
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2013-2021 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# CFR Test
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
add_executable(cfr_test cfr_test.c)
|
||||||
|
target_link_libraries(cfr_test srsran_phy)
|
||||||
|
|
||||||
|
add_test(cfr_test_default cfr_test)
|
||||||
|
|
@ -0,0 +1,308 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \section COPYRIGHT
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 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 <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "srsran/phy/utils/random.h"
|
||||||
|
#include "srsran/srsran.h"
|
||||||
|
|
||||||
|
#define MAX_ACPR_DB -100
|
||||||
|
|
||||||
|
// CFR type test args
|
||||||
|
static char cfr_manual_str[] = "manual";
|
||||||
|
static char cfr_auto_cma_str[] = "auto_cma";
|
||||||
|
static char cfr_auto_ema_str[] = "auto_ema";
|
||||||
|
|
||||||
|
// Default CFR type
|
||||||
|
static char* cfr_type_arg = cfr_manual_str;
|
||||||
|
|
||||||
|
static int nof_prb = -1;
|
||||||
|
static srsran_cp_t cp = SRSRAN_CP_NORM;
|
||||||
|
static int nof_repetitions = 1;
|
||||||
|
static int nof_frames = 10;
|
||||||
|
static srsran_cfr_mode_t cfr_mode = SRSRAN_CFR_THR_MANUAL;
|
||||||
|
static float alpha = 1.0f;
|
||||||
|
static bool dc_empty = true;
|
||||||
|
static float thr_manual = 1.5f;
|
||||||
|
static float max_papr_db = 8.0f;
|
||||||
|
static float ema_alpha = (float)1 / (float)SRSRAN_CP_NORM_NSYMB;
|
||||||
|
|
||||||
|
static uint32_t force_symbol_sz = 0;
|
||||||
|
static double elapsed_us(struct timeval* ts_start, struct timeval* ts_end)
|
||||||
|
{
|
||||||
|
if (ts_end->tv_usec > ts_start->tv_usec) {
|
||||||
|
return ((double)ts_end->tv_sec - (double)ts_start->tv_sec) * 1000000 + (double)ts_end->tv_usec -
|
||||||
|
(double)ts_start->tv_usec;
|
||||||
|
} else {
|
||||||
|
return ((double)ts_end->tv_sec - (double)ts_start->tv_sec - 1) * 1000000 + ((double)ts_end->tv_usec + 1000000) -
|
||||||
|
(double)ts_start->tv_usec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usage(char* prog)
|
||||||
|
{
|
||||||
|
printf("Usage: %s\n", prog);
|
||||||
|
printf("\t-N Force symbol size, 0 for auto [Default %d]\n", force_symbol_sz);
|
||||||
|
printf("\t-n Force number of Resource blocks [Default All]\n");
|
||||||
|
printf("\t-e extended cyclic prefix [Default Normal]\n");
|
||||||
|
printf("\t-f Number of frames [Default %d]\n", nof_frames);
|
||||||
|
printf("\t-r Number of repetitions [Default %d]\n", nof_repetitions);
|
||||||
|
printf("\t-m CFR mode: %s, %s, %s [Default %s]\n", cfr_manual_str, cfr_auto_cma_str, cfr_auto_ema_str, cfr_type_arg);
|
||||||
|
printf("\t-d Use DC subcarrier: [Default DC empty]\n");
|
||||||
|
printf("\t-a CFR alpha: [Default %.2f]\n", alpha);
|
||||||
|
printf("\t-t CFR manual threshold: [Default %.2f]\n", thr_manual);
|
||||||
|
printf("\t-p CFR Max PAPR in dB (auto modes): [Default %.2f]\n", max_papr_db);
|
||||||
|
printf("\t-E Power avg EMA alpha (EMA mode): [Default %.2f]\n", ema_alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_args(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int opt;
|
||||||
|
while ((opt = getopt(argc, argv, "NnerfmatdpE")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'n':
|
||||||
|
nof_prb = (int)strtol(argv[optind], NULL, 10);
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
force_symbol_sz = (uint32_t)strtol(argv[optind], NULL, 10);
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
cp = SRSRAN_CP_EXT;
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
nof_repetitions = (int)strtol(argv[optind], NULL, 10);
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
nof_frames = (int)strtol(argv[optind], NULL, 10);
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
cfr_type_arg = argv[optind];
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
alpha = strtof(argv[optind], NULL);
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
thr_manual = strtof(argv[optind], NULL);
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
dc_empty = false;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
max_papr_db = strtof(argv[optind], NULL);
|
||||||
|
break;
|
||||||
|
case 'E':
|
||||||
|
ema_alpha = strtof(argv[optind], NULL);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage(argv[0]);
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
|
||||||
|
srsran_random_t random_gen = srsran_random_init(0);
|
||||||
|
struct timeval start, end;
|
||||||
|
srsran_cfr_t cfr = {};
|
||||||
|
cf_t* input = NULL;
|
||||||
|
cf_t* output = NULL;
|
||||||
|
cf_t* error = NULL;
|
||||||
|
float* acpr_buff = NULL;
|
||||||
|
float mse_dB = 0.0f;
|
||||||
|
float nmse_dB = 0.0f;
|
||||||
|
float evm = 0.0f;
|
||||||
|
int max_prb = 0.0f;
|
||||||
|
float acpr_in_dB = 0.0f;
|
||||||
|
float acpr_out_dB = 0.0f;
|
||||||
|
|
||||||
|
srsran_dft_plan_t ofdm_ifft = {};
|
||||||
|
srsran_dft_plan_t ofdm_fft = {};
|
||||||
|
|
||||||
|
if (parse_args(argc, argv) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error in parse_args");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(cfr_type_arg, cfr_manual_str)) {
|
||||||
|
cfr_mode = SRSRAN_CFR_THR_MANUAL;
|
||||||
|
} else if (!strcmp(cfr_type_arg, cfr_auto_cma_str)) {
|
||||||
|
cfr_mode = SRSRAN_CFR_THR_AUTO_CMA;
|
||||||
|
} else if (!strcmp(cfr_type_arg, cfr_auto_ema_str)) {
|
||||||
|
cfr_mode = SRSRAN_CFR_THR_AUTO_EMA;
|
||||||
|
} else {
|
||||||
|
ERROR("CFR mode is not recognised");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nof_prb == -1) {
|
||||||
|
nof_prb = 6;
|
||||||
|
max_prb = SRSRAN_MAX_PRB;
|
||||||
|
} else {
|
||||||
|
max_prb = nof_prb;
|
||||||
|
}
|
||||||
|
while (nof_prb <= max_prb) {
|
||||||
|
const uint32_t symbol_sz = (force_symbol_sz) ? force_symbol_sz : (uint32_t)srsran_symbol_sz(nof_prb);
|
||||||
|
const uint32_t symbol_bw = nof_prb * SRSRAN_NRE;
|
||||||
|
const uint32_t nof_symb_slot = SRSRAN_CP_NSYMB(cp);
|
||||||
|
const uint32_t nof_symb_frame = nof_symb_slot * SRSRAN_NOF_SLOTS_PER_SF * SRSRAN_NOF_SF_X_FRAME;
|
||||||
|
const uint32_t frame_sz = symbol_sz * nof_symb_frame;
|
||||||
|
const uint32_t total_nof_re = frame_sz * nof_frames;
|
||||||
|
const uint32_t total_nof_symb = nof_symb_frame * nof_frames;
|
||||||
|
printf("Running test for %d PRB, %d Frames: \t", nof_prb, nof_frames);
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
input = srsran_vec_cf_malloc(total_nof_re);
|
||||||
|
output = srsran_vec_cf_malloc(total_nof_re);
|
||||||
|
error = srsran_vec_cf_malloc(total_nof_re);
|
||||||
|
acpr_buff = srsran_vec_f_malloc(total_nof_symb);
|
||||||
|
if (!input || !output || !error || !acpr_buff) {
|
||||||
|
perror("malloc");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
srsran_vec_cf_zero(input, total_nof_re);
|
||||||
|
srsran_vec_cf_zero(output, total_nof_re);
|
||||||
|
srsran_vec_cf_zero(error, total_nof_re);
|
||||||
|
srsran_vec_f_zero(acpr_buff, total_nof_symb);
|
||||||
|
|
||||||
|
// Set the parameters for the CFR.
|
||||||
|
srsran_cfr_cfg_t cfr_tx_cfg = {};
|
||||||
|
cfr_tx_cfg.cfr_enable = true;
|
||||||
|
cfr_tx_cfg.symbol_sz = symbol_sz;
|
||||||
|
cfr_tx_cfg.symbol_bw = nof_prb * SRSRAN_NRE;
|
||||||
|
cfr_tx_cfg.cfr_mode = cfr_mode;
|
||||||
|
cfr_tx_cfg.max_papr_db = max_papr_db;
|
||||||
|
cfr_tx_cfg.alpha = alpha;
|
||||||
|
cfr_tx_cfg.manual_thr = thr_manual;
|
||||||
|
cfr_tx_cfg.ema_alpha = ema_alpha;
|
||||||
|
cfr_tx_cfg.dc_sc = dc_empty;
|
||||||
|
|
||||||
|
if (srsran_cfr_init(&cfr, &cfr_tx_cfg)) {
|
||||||
|
ERROR("Error initializing CFR");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_dft_plan_c(&ofdm_ifft, (int)symbol_sz, SRSRAN_DFT_BACKWARD)) {
|
||||||
|
ERROR("Creating IFFT plan");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
srsran_dft_plan_set_norm(&ofdm_ifft, true);
|
||||||
|
if (srsran_dft_plan_c(&ofdm_fft, (int)symbol_sz, SRSRAN_DFT_FORWARD)) {
|
||||||
|
ERROR("Creating FFT plan");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
srsran_dft_plan_set_norm(&ofdm_fft, true);
|
||||||
|
|
||||||
|
// Generate Random data
|
||||||
|
cf_t* ofdm_symb = NULL;
|
||||||
|
for (int i = 0; i < total_nof_symb; i++) {
|
||||||
|
ofdm_symb = input + i * symbol_sz;
|
||||||
|
srsran_random_uniform_complex_dist_vector(random_gen, ofdm_symb + dc_empty, symbol_bw / 2, -1.0f, +1.0f);
|
||||||
|
srsran_random_uniform_complex_dist_vector(
|
||||||
|
random_gen, ofdm_symb + symbol_sz - symbol_bw / 2, symbol_bw / 2, -1.0f, +1.0f);
|
||||||
|
acpr_buff[i] = srsran_vec_acpr_c(ofdm_symb, symbol_bw / 2 + dc_empty, symbol_bw / 2, symbol_sz);
|
||||||
|
srsran_dft_run_c(&ofdm_ifft, ofdm_symb, ofdm_symb);
|
||||||
|
}
|
||||||
|
// compute the average intput ACPR
|
||||||
|
acpr_in_dB = srsran_vec_acc_ff(acpr_buff, total_nof_symb) / (float)total_nof_symb;
|
||||||
|
acpr_in_dB = srsran_convert_power_to_dB(acpr_in_dB);
|
||||||
|
|
||||||
|
// Execute CFR
|
||||||
|
gettimeofday(&start, NULL);
|
||||||
|
for (uint32_t i = 0; i < nof_repetitions; i++) {
|
||||||
|
for (uint32_t j = 0; j < nof_frames; j++) {
|
||||||
|
for (uint32_t k = 0; k < nof_symb_frame; k++) {
|
||||||
|
srsran_cfr_process(&cfr,
|
||||||
|
input + (size_t)((k * symbol_sz) + (j * frame_sz)),
|
||||||
|
output + (size_t)((k * symbol_sz) + (j * frame_sz)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gettimeofday(&end, NULL);
|
||||||
|
printf("%.1fMsps \t", (float)(total_nof_re * nof_repetitions) / elapsed_us(&start, &end));
|
||||||
|
|
||||||
|
// Compute metrics
|
||||||
|
srsran_vec_sub_ccc(input, output, error, total_nof_re);
|
||||||
|
|
||||||
|
float power_in = srsran_vec_avg_power_cf(input, total_nof_re);
|
||||||
|
float power_err = srsran_vec_avg_power_cf(error, total_nof_re);
|
||||||
|
|
||||||
|
mse_dB = srsran_convert_power_to_dB(power_err);
|
||||||
|
nmse_dB = srsran_convert_power_to_dB(power_err / power_in);
|
||||||
|
evm = 100 * sqrtf(power_err / power_in);
|
||||||
|
|
||||||
|
float snr_dB = srsran_convert_power_to_dB(power_in / power_err);
|
||||||
|
|
||||||
|
float papr_in = srsran_convert_power_to_dB(srsran_vec_papr_c(input, total_nof_re));
|
||||||
|
float papr_out = srsran_convert_power_to_dB(srsran_vec_papr_c(output, total_nof_re));
|
||||||
|
|
||||||
|
ofdm_symb = NULL;
|
||||||
|
for (int i = 0; i < total_nof_symb; i++) {
|
||||||
|
ofdm_symb = output + i * symbol_sz;
|
||||||
|
srsran_dft_run_c(&ofdm_fft, ofdm_symb, ofdm_symb);
|
||||||
|
acpr_buff[i] = srsran_vec_acpr_c(ofdm_symb, symbol_bw / 2 + dc_empty, symbol_bw / 2, symbol_sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the output average ACPR
|
||||||
|
acpr_out_dB = srsran_vec_acc_ff(acpr_buff, total_nof_symb) / (float)total_nof_symb;
|
||||||
|
acpr_out_dB = srsran_convert_power_to_dB(acpr_out_dB);
|
||||||
|
|
||||||
|
printf("MSE=%.3fdB NMSE=%.3fdB EVM=%.3f%% SNR=%.3fdB", mse_dB, nmse_dB, evm, snr_dB);
|
||||||
|
printf(" In-PAPR=%.3fdB Out-PAPR=%.3fdB", papr_in, papr_out);
|
||||||
|
printf(" In-ACPR=%.3fdB Out-ACPR=%.3fdB\n", acpr_in_dB, acpr_out_dB);
|
||||||
|
|
||||||
|
srsran_dft_plan_free(&ofdm_ifft);
|
||||||
|
srsran_dft_plan_free(&ofdm_fft);
|
||||||
|
free(input);
|
||||||
|
free(output);
|
||||||
|
free(error);
|
||||||
|
free(acpr_buff);
|
||||||
|
input = NULL;
|
||||||
|
output = NULL;
|
||||||
|
error = NULL;
|
||||||
|
acpr_buff = NULL;
|
||||||
|
|
||||||
|
++nof_prb;
|
||||||
|
if (acpr_out_dB > MAX_ACPR_DB) {
|
||||||
|
printf("ACPR too large \n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
|
||||||
|
// Free resources
|
||||||
|
clean_exit:
|
||||||
|
srsran_random_free(random_gen);
|
||||||
|
srsran_cfr_free(&cfr);
|
||||||
|
srsran_dft_plan_free(&ofdm_ifft);
|
||||||
|
srsran_dft_plan_free(&ofdm_fft);
|
||||||
|
if (input) {
|
||||||
|
free(input);
|
||||||
|
}
|
||||||
|
if (output) {
|
||||||
|
free(output);
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
free(error);
|
||||||
|
}
|
||||||
|
if (acpr_buff) {
|
||||||
|
free(acpr_buff);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
Loading…
Reference in New Issue