diff --git a/lib/examples/pdsch_enodeb.c b/lib/examples/pdsch_enodeb.c index cf647851b..291ab1f61 100644 --- a/lib/examples/pdsch_enodeb.c +++ b/lib/examples/pdsch_enodeb.c @@ -13,6 +13,7 @@ #include "srsran/common/crash_handler.h" #include "srsran/common/gen_mch_tables.h" #include "srsran/srsran.h" +#include #include #include #include @@ -43,6 +44,12 @@ static char* output_file_name = NULL; #define PAGE_UP 53 #define PAGE_DOWN 54 +#define CFR_THRES_UP_KEY 't' +#define CFR_THRES_DN_KEY 'g' + +#define CFR_THRES_STEP 0.05f +#define CFR_PAPR_STEP 0.1f + static srsran_cell_t cell = { 25, // nof_prb 1, // nof_ports @@ -70,6 +77,31 @@ static bool enable_256qam = false; static float output_file_snr = +INFINITY; static bool use_standard_lte_rate = false; +// 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"; + +// CFR runtime control flags +static bool cfr_thr_inc = false; +static bool cfr_thr_dec = false; + +typedef struct { + int enable; + char* mode; + float manual_thres; + float strength; + float auto_target_papr; + float ema_alpha; +} cfr_args_t; + +static cfr_args_t cfr_args = {.enable = 0, + .mode = cfr_manual_str, + .manual_thres = 1.0f, + .strength = 1.0f, + .auto_target_papr = 8.0f, + .ema_alpha = 1.0f / (float)SRSRAN_CP_NORM_NSYMB}; + static bool null_file_sink = false; static srsran_filesink_t fsink; static srsran_ofdm_t ifft[SRSRAN_MAX_PORTS]; @@ -85,6 +117,7 @@ static srsran_softbuffer_tx_t* softbuffers[SRSRAN_MAX_CODEWORDS]; static srsran_regs_t regs; static srsran_dci_dl_t dci_dl; static int rvidx[SRSRAN_MAX_CODEWORDS] = {0, 0}; +static srsran_cfr_cfg_t cfr_config = {}; static cf_t * sf_buffer[SRSRAN_MAX_PORTS] = {NULL}, *output_buffer[SRSRAN_MAX_PORTS] = {NULL}; static uint32_t sf_n_re, sf_n_samples; @@ -134,14 +167,28 @@ static void usage(char* prog) printf("\t-s output file SNR [Default %f]\n", output_file_snr); printf("\t-q Enable/Disable 256QAM modulation (default %s)\n", enable_256qam ? "enabled" : "disabled"); printf("\t-Q Use standard LTE sample rates (default %s)\n", use_standard_lte_rate ? "enabled" : "disabled"); + printf("CFR Options:\n"); + printf("\t--enable_cfr Enable the CFR (default %s)\n", cfr_args.enable ? "enabled" : "disabled"); + printf("\t--cfr_mode CFR mode: manual, auto_cma, auto_ema. (default %s)\n", cfr_args.mode); + printf("\t--cfr_manual_thres CFR manual threshold (default %.2f)\n", cfr_args.manual_thres); + printf("\t--cfr_strength CFR strength (default %.2f)\n", cfr_args.strength); + printf("\t--cfr_auto_papr CFR PAPR target for auto modes (default %.2f)\n", cfr_args.auto_target_papr); + printf("\t--cfr_ema_alpha CFR alpha parameter for EMA mode (default %.2f)\n", cfr_args.ema_alpha); printf("\n"); printf("\t*: See 3GPP 36.212 Table 5.3.3.1.5-4 for more information\n"); } +struct option cfr_opts[] = {{"enable_cfr", no_argument, &cfr_args.enable, 1}, + {"cfr_mode", required_argument, NULL, 'C'}, + {"cfr_manual_thres", required_argument, NULL, 'T'}, + {"cfr_strength", required_argument, NULL, 'S'}, + {"cfr_auto_papr", required_argument, NULL, 'P'}, + {"cfr_ema_alpha", required_argument, NULL, 'e'}, + {0, 0, 0, 0}}; static void parse_args(int argc, char** argv) { int opt; - while ((opt = getopt(argc, argv, "IadglfmoncpqvutxbwMsBQ")) != -1) { + while ((opt = getopt_long(argc, argv, "IadglfmoncpqvutxbwMsBQ", cfr_opts, NULL)) != -1) { switch (opt) { case 'I': rf_dev = argv[optind]; @@ -206,6 +253,24 @@ static void parse_args(int argc, char** argv) case 'E': cell.cp = SRSRAN_CP_EXT; break; + case 'C': + cfr_args.mode = optarg; + break; + case 'T': + cfr_args.manual_thres = strtof(optarg, NULL); + break; + case 'S': + cfr_args.strength = strtof(optarg, NULL); + break; + case 'P': + cfr_args.auto_target_papr = strtof(optarg, NULL); + break; + case 'e': + cfr_args.ema_alpha = strtof(optarg, NULL); + break; + case 0: + /* getopt_long() set a variable, keep going */ + break; default: usage(argv[0]); exit(-1); @@ -219,6 +284,32 @@ static void parse_args(int argc, char** argv) #endif } +static int parse_cfr_args() +{ + cfr_config.cfr_enable = cfr_args.enable; + cfr_config.manual_thr = cfr_args.manual_thres; + cfr_config.max_papr_db = cfr_args.auto_target_papr; + cfr_config.alpha = cfr_args.strength; + cfr_config.ema_alpha = cfr_args.ema_alpha; + + if (!strcmp(cfr_args.mode, cfr_manual_str)) { + cfr_config.cfr_mode = SRSRAN_CFR_THR_MANUAL; + } else if (!strcmp(cfr_args.mode, cfr_auto_cma_str)) { + cfr_config.cfr_mode = SRSRAN_CFR_THR_AUTO_CMA; + } else if (!strcmp(cfr_args.mode, cfr_auto_ema_str)) { + cfr_config.cfr_mode = SRSRAN_CFR_THR_AUTO_EMA; + } else { + ERROR("CFR mode is not recognised"); + return SRSRAN_ERROR; + } + + if (!srsran_cfr_params_valid(&cfr_config)) { + ERROR("Invalid CFR parameters"); + return SRSRAN_ERROR; + } + return SRSRAN_SUCCESS; +} + static void base_init() { int i; @@ -316,6 +407,10 @@ static void base_init() } srsran_ofdm_set_normalize(&ifft[i], true); + if (srsran_ofdm_set_cfr(&ifft[i], &cfr_config)) { + ERROR("Error setting CFR object"); + exit(-1); + } } if (srsran_ofdm_tx_init_mbsfn(&ifft_mbsfn, SRSRAN_CP_EXT, sf_buffer[0], output_buffer[0], cell.nof_prb)) { @@ -324,6 +419,10 @@ static void base_init() } srsran_ofdm_set_non_mbsfn_region(&ifft_mbsfn, 2); srsran_ofdm_set_normalize(&ifft_mbsfn, true); + if (srsran_ofdm_set_cfr(&ifft_mbsfn, &cfr_config)) { + ERROR("Error setting CFR object"); + exit(-1); + } if (srsran_pbch_init(&pbch)) { ERROR("Error creating PBCH object"); @@ -474,6 +573,8 @@ static int update_radl() { ZERO_OBJECT(dci_dl); + int ret = SRSRAN_ERROR; + /* Configure cell and PDSCH in function of the transmission mode */ switch (transmission_mode) { case SRSRAN_TM1: @@ -496,7 +597,7 @@ static int update_radl() break; default: ERROR("Transmission mode not implemented."); - exit(-1); + goto exit; } dci_dl.rnti = UE_CRNTI; @@ -517,7 +618,80 @@ static int update_radl() SRSRAN_DCI_TB_DISABLE(dci_dl.tb[1]); } + // Increase the CFR threshold or target PAPR + if (cfr_thr_inc) { + cfr_thr_inc = false; // Reset the flag + if (cfr_config.cfr_enable && cfr_config.cfr_mode == SRSRAN_CFR_THR_MANUAL) { + cfr_config.manual_thr += CFR_THRES_STEP; + for (int i = 0; i < cell.nof_ports; i++) { + if (srsran_cfr_set_threshold(&ifft[i].tx_cfr, cfr_config.manual_thr) < SRSRAN_SUCCESS) { + ERROR("Setting the CFR"); + goto exit; + } + } + if (srsran_cfr_set_threshold(&ifft_mbsfn.tx_cfr, cfr_config.manual_thr) < SRSRAN_SUCCESS) { + ERROR("Setting the CFR"); + goto exit; + } + printf("CFR Thres. set to %.3f\n", cfr_config.manual_thr); + } else if (cfr_config.cfr_enable && cfr_config.cfr_mode != SRSRAN_CFR_THR_MANUAL) { + cfr_config.max_papr_db += CFR_PAPR_STEP; + for (int i = 0; i < cell.nof_ports; i++) { + if (srsran_cfr_set_papr(&ifft[i].tx_cfr, cfr_config.max_papr_db) < SRSRAN_SUCCESS) { + ERROR("Setting the CFR"); + goto exit; + } + } + if (srsran_cfr_set_papr(&ifft_mbsfn.tx_cfr, cfr_config.max_papr_db) < SRSRAN_SUCCESS) { + ERROR("Setting the CFR"); + goto exit; + } + printf("CFR target PAPR set to %.3f\n", cfr_config.max_papr_db); + } + } + + // Decrease the CFR threshold or target PAPR + if (cfr_thr_dec) { + cfr_thr_dec = false; // Reset the flag + if (cfr_config.cfr_enable && cfr_config.cfr_mode == SRSRAN_CFR_THR_MANUAL) { + if (cfr_config.manual_thr - CFR_THRES_STEP >= 0) { + cfr_config.manual_thr -= CFR_THRES_STEP; + for (int i = 0; i < cell.nof_ports; i++) { + if (srsran_cfr_set_threshold(&ifft[i].tx_cfr, cfr_config.manual_thr) < SRSRAN_SUCCESS) { + ERROR("Setting the CFR"); + goto exit; + } + } + if (srsran_cfr_set_threshold(&ifft_mbsfn.tx_cfr, cfr_config.manual_thr) < SRSRAN_SUCCESS) { + ERROR("Setting the CFR"); + goto exit; + } + printf("CFR Thres. set to %.3f\n", cfr_config.manual_thr); + } + } else if (cfr_config.cfr_enable && cfr_config.cfr_mode != SRSRAN_CFR_THR_MANUAL) { + if (cfr_config.max_papr_db - CFR_PAPR_STEP >= 0) { + cfr_config.max_papr_db -= CFR_PAPR_STEP; + for (int i = 0; i < cell.nof_ports; i++) { + if (srsran_cfr_set_papr(&ifft[i].tx_cfr, cfr_config.max_papr_db) < SRSRAN_SUCCESS) { + ERROR("Setting the CFR"); + goto exit; + } + } + if (srsran_cfr_set_papr(&ifft_mbsfn.tx_cfr, cfr_config.max_papr_db) < SRSRAN_SUCCESS) { + ERROR("Setting the CFR"); + goto exit; + } + printf("CFR target PAPR set to %.3f\n", cfr_config.max_papr_db); + } + } + } + srsran_dci_dl_fprint(stdout, &dci_dl, cell.nof_prb); + printf("\nCFR controls:\n"); + printf(" Param | INC | DEC |\n"); + printf("------------+-----+-----+\n"); + printf(" Thres/PAPR | %c | %c |\n", CFR_THRES_UP_KEY, CFR_THRES_DN_KEY); + printf("\n"); if (transmission_mode != SRSRAN_TM1) { printf("\nTransmission mode key table:\n"); printf(" Mode | 1TB | 2TB |\n"); @@ -526,13 +700,15 @@ static int update_radl() printf(" CDD | | z |\n"); printf("Multiplex | q,w,e,r | a,s |\n"); printf("\n"); - printf("Type new MCS index (0-28) or mode key and press Enter: "); + printf("Type new MCS index (0-28) or cfr/mode key and press Enter: "); } else { - printf("Type new MCS index (0-28) and press Enter: "); + printf("Type new MCS index (0-28) or cfr key and press Enter: "); } fflush(stdout); + ret = SRSRAN_SUCCESS; - return 0; +exit: + return ret; } /* Read new MCS from stdin */ @@ -626,6 +802,12 @@ static int update_control() case 'x': transmission_mode = SRSRAN_TM2; break; + case CFR_THRES_UP_KEY: + cfr_thr_inc = true; + break; + case CFR_THRES_DN_KEY: + cfr_thr_dec = true; + break; default: last_mcs_idx = mcs_idx; mcs_idx = strtol(input, NULL, 10); @@ -643,9 +825,9 @@ static int update_control() } else if (n < 0) { // error perror("select"); - return -1; + return SRSRAN_ERROR; } else { - return 0; + return SRSRAN_SUCCESS; } } @@ -719,6 +901,10 @@ int main(int argc, char** argv) #endif parse_args(argc, argv); + if (parse_cfr_args() < SRSRAN_SUCCESS) { + ERROR("Error parsing CFR args"); + exit(-1); + } srsran_use_standard_symbol_size(use_standard_lte_rate); @@ -873,7 +1059,7 @@ int main(int argc, char** argv) srsran_pcfich_encode(&pcfich, &dl_sf, sf_symbols); /* Update DL resource allocation from control port */ - if (update_control()) { + if (update_control() < SRSRAN_SUCCESS) { ERROR("Error updating parameters from control port"); } diff --git a/lib/include/srsran/interfaces/enb_command_interface.h b/lib/include/srsran/interfaces/enb_command_interface.h index 70bf82e7a..8e0e0bb3e 100644 --- a/lib/include/srsran/interfaces/enb_command_interface.h +++ b/lib/include/srsran/interfaces/enb_command_interface.h @@ -19,6 +19,11 @@ namespace srsenb { class enb_command_interface { public: + /** + * Trigger downlink singnal measurements (currently PAPR) + */ + virtual void cmd_cell_measure() = 0; + /** * Sets the relative gain of a cell from it's index (following rr.conf) order. * @param cell_id Provides a cell identifier diff --git a/lib/include/srsran/phy/cfr/cfr.h b/lib/include/srsran/phy/cfr/cfr.h new file mode 100644 index 000000000..a53204de5 --- /dev/null +++ b/lib/include/srsran/phy/cfr/cfr.h @@ -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 diff --git a/lib/include/srsran/phy/dft/ofdm.h b/lib/include/srsran/phy/dft/ofdm.h index 767b3534e..a3a16970f 100644 --- a/lib/include/srsran/phy/dft/ofdm.h +++ b/lib/include/srsran/phy/dft/ofdm.h @@ -25,6 +25,7 @@ #include #include "srsran/config.h" +#include "srsran/phy/cfr/cfr.h" #include "srsran/phy/common/phy_common.h" #include "srsran/phy/dft/dft.h" @@ -39,18 +40,19 @@ typedef struct SRSRAN_API { // Compulsory parameters uint32_t nof_prb; ///< Number of Resource Block - cf_t* in_buffer; ///< Input bnuffer pointer + cf_t* in_buffer; ///< Input buffer pointer cf_t* out_buffer; ///< Output buffer pointer srsran_cp_t cp; ///< Cyclic prefix type // Optional parameters - srsran_sf_t sf_type; ///< Subframe type, normal or MBSFN - bool normalize; ///< Normalization flag, it divides the output by square root of the symbol size - float freq_shift_f; ///< Frequency shift, normalised by sampling rate (used in UL) - float rx_window_offset; ///< DFT Window offset in CP portion (0-1), RX only - uint32_t symbol_sz; ///< Symbol size, forces a given symbol size for the number of PRB - bool keep_dc; ///< If true, it does not remove the DC - double phase_compensation_hz; ///< Carrier frequency in Hz for phase compensation, set to 0 to disable + srsran_sf_t sf_type; ///< Subframe type, normal or MBSFN + bool normalize; ///< Normalization flag, it divides the output by square root of the symbol size + float freq_shift_f; ///< Frequency shift, normalised by sampling rate (used in UL) + float rx_window_offset; ///< DFT Window offset in CP portion (0-1), RX only + uint32_t symbol_sz; ///< Symbol size, forces a given symbol size for the number of PRB + bool keep_dc; ///< If true, it does not remove the DC + double phase_compensation_hz; ///< Carrier frequency in Hz for phase compensation, set to 0 to disable + srsran_cfr_cfg_t cfr_tx_cfg; ///< Tx CFR configuration } srsran_ofdm_cfg_t; /** @@ -76,6 +78,7 @@ typedef struct SRSRAN_API { cf_t* shift_buffer; cf_t* window_offset_buffer; cf_t phase_compensation[SRSRAN_MAX_NSYMB * SRSRAN_NOF_SLOTS_PER_SF]; + srsran_cfr_t tx_cfr; ///< Tx CFR object } srsran_ofdm_t; /** @@ -136,4 +139,6 @@ SRSRAN_API int srsran_ofdm_set_phase_compensation(srsran_ofdm_t* q, double cente SRSRAN_API void srsran_ofdm_set_non_mbsfn_region(srsran_ofdm_t* q, uint8_t non_mbsfn_region); +SRSRAN_API int srsran_ofdm_set_cfr(srsran_ofdm_t* q, srsran_cfr_cfg_t* cfr); + #endif // SRSRAN_OFDM_H diff --git a/lib/include/srsran/phy/enb/enb_dl.h b/lib/include/srsran/phy/enb/enb_dl.h index 5c1c03010..2195c7623 100644 --- a/lib/include/srsran/phy/enb/enb_dl.h +++ b/lib/include/srsran/phy/enb/enb_dl.h @@ -55,7 +55,9 @@ typedef struct SRSRAN_API { srsran_dl_sf_cfg_t dl_sf; - cf_t* sf_symbols[SRSRAN_MAX_PORTS]; + srsran_cfr_cfg_t cfr_config; + + cf_t* sf_symbols[SRSRAN_MAX_PORTS]; cf_t* out_buffer[SRSRAN_MAX_PORTS]; srsran_ofdm_t ifft[SRSRAN_MAX_PORTS]; srsran_ofdm_t ifft_mbsfn; @@ -93,6 +95,8 @@ SRSRAN_API void srsran_enb_dl_free(srsran_enb_dl_t* q); SRSRAN_API int srsran_enb_dl_set_cell(srsran_enb_dl_t* q, srsran_cell_t cell); +SRSRAN_API int srsran_enb_dl_set_cfr(srsran_enb_dl_t* q, const srsran_cfr_cfg_t* cfr); + SRSRAN_API bool srsran_enb_dl_location_is_common_ncce(srsran_enb_dl_t* q, const srsran_dci_location_t* loc); SRSRAN_API void srsran_enb_dl_put_base(srsran_enb_dl_t* q, srsran_dl_sf_cfg_t* dl_sf); diff --git a/lib/include/srsran/phy/utils/vector.h b/lib/include/srsran/phy/utils/vector.h index 20023d875..83385638a 100644 --- a/lib/include/srsran/phy/utils/vector.h +++ b/lib/include/srsran/phy/utils/vector.h @@ -224,6 +224,7 @@ SRSRAN_API void srsran_vec_conj_cc(const cf_t* x, cf_t* y, const uint32_t len); SRSRAN_API float srsran_vec_avg_power_cf(const cf_t* x, const uint32_t len); SRSRAN_API float srsran_vec_avg_power_sf(const int16_t* x, const uint32_t len); SRSRAN_API float srsran_vec_avg_power_bf(const int8_t* x, const uint32_t len); +SRSRAN_API float srsran_vec_avg_power_ff(const float* x, const uint32_t len); /* Correlation between complex vectors x and y */ SRSRAN_API float srsran_vec_corr_ccc(const cf_t* x, cf_t* y, const uint32_t len); @@ -352,6 +353,36 @@ SRSRAN_API void srsran_vec_apply_cfo(const cf_t* x, float cfo, cf_t* z, int len) SRSRAN_API float srsran_vec_estimate_frequency(const cf_t* x, int len); +/*! + * @brief Generates an amplitude envelope that, multiplied point-wise with a vector, results in clipping + * by a specified amplitude threshold. + * @param[in] x_abs Absolute value vector of the signal to be clipped + * @param[in] thres Clipping threshold + * @param[out] clip_env The generated clipping envelope + * @param[in] len Length of the vector. + */ +SRSRAN_API void +srsran_vec_gen_clip_env(const float* x_abs, const float thres, const float alpha, float* env, const int len); + +/*! + * @brief Calculates the PAPR of a complex vector + * @param[in] in Input vector + * @param[in] len Vector length. + */ +SRSRAN_API float srsran_vec_papr_c(const cf_t* in, const int len); + +/*! + * @brief Calculates the ACPR of a signal using its baseband spectrum + * @attention The spectrum passed by x_f needs to be in FFT form + * @param[in] x_f Spectrum of the signal + * @param[in] win_pos_len Channel frequency window for the positive side of the spectrum + * @param[in] win_neg_len Channel frequency window for the negative side of the spectrum + * @param[in] len Length of the x_f vector + * @returns The ACPR in linear form + */ +SRSRAN_API float +srsran_vec_acpr_c(const cf_t* x_f, const uint32_t win_pos_len, const uint32_t win_neg_len, const uint32_t len); + #ifdef __cplusplus } #endif diff --git a/lib/include/srsran/srsran.h b/lib/include/srsran/srsran.h index d9be8b910..7bcd2eb90 100644 --- a/lib/include/srsran/srsran.h +++ b/lib/include/srsran/srsran.h @@ -52,6 +52,7 @@ extern "C" { #include "srsran/phy/channel/ch_awgn.h" +#include "srsran/phy/cfr/cfr.h" #include "srsran/phy/dft/dft.h" #include "srsran/phy/dft/dft_precoding.h" #include "srsran/phy/dft/ofdm.h" diff --git a/lib/src/phy/CMakeLists.txt b/lib/src/phy/CMakeLists.txt index 66018f1dc..880e9ef09 100644 --- a/lib/src/phy/CMakeLists.txt +++ b/lib/src/phy/CMakeLists.txt @@ -24,6 +24,7 @@ add_subdirectory(scrambling) add_subdirectory(ue) add_subdirectory(enb) add_subdirectory(gnb) +add_subdirectory(cfr) set(srsran_srcs $ $ $ @@ -41,6 +42,7 @@ set(srsran_srcs $ $ $ $ + $ ) add_library(srsran_phy STATIC ${srsran_srcs} ) diff --git a/lib/src/phy/cfr/CMakeLists.txt b/lib/src/phy/cfr/CMakeLists.txt new file mode 100644 index 000000000..4de435223 --- /dev/null +++ b/lib/src/phy/cfr/CMakeLists.txt @@ -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) + diff --git a/lib/src/phy/cfr/cfr.c b/lib/src/phy/cfr/cfr.c new file mode 100644 index 000000000..325c3cc2a --- /dev/null +++ b/lib/src/phy/cfr/cfr.c @@ -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; +} diff --git a/lib/src/phy/cfr/test/CMakeLists.txt b/lib/src/phy/cfr/test/CMakeLists.txt new file mode 100644 index 000000000..34be8ede3 --- /dev/null +++ b/lib/src/phy/cfr/test/CMakeLists.txt @@ -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) + diff --git a/lib/src/phy/cfr/test/cfr_test.c b/lib/src/phy/cfr/test/cfr_test.c new file mode 100644 index 000000000..368178457 --- /dev/null +++ b/lib/src/phy/cfr/test/cfr_test.c @@ -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 +#include +#include +#include +#include + +#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; +} diff --git a/lib/src/phy/dft/ofdm.c b/lib/src/phy/dft/ofdm.c index 1b1856873..fa349fb31 100644 --- a/lib/src/phy/dft/ofdm.c +++ b/lib/src/phy/dft/ofdm.c @@ -68,6 +68,19 @@ static int ofdm_init_mbsfn_(srsran_ofdm_t* q, srsran_ofdm_cfg_t* cfg, srsran_dft q->slot_sz = (uint32_t)SRSRAN_SLOT_LEN(q->cfg.symbol_sz); q->sf_sz = (uint32_t)SRSRAN_SF_LEN(q->cfg.symbol_sz); + // Set the CFR parameters related to OFDM symbol and FFT size + q->cfg.cfr_tx_cfg.symbol_sz = symbol_sz; + q->cfg.cfr_tx_cfg.symbol_bw = q->nof_re; + + // in the DL, the DC carrier is empty but still counts when designing the filter BW + q->cfg.cfr_tx_cfg.dc_sc = (!q->cfg.keep_dc) && (!isnormal(q->cfg.freq_shift_f)); + if (q->cfg.cfr_tx_cfg.cfr_enable) { + if (srsran_cfr_init(&q->tx_cfr, &q->cfg.cfr_tx_cfg) < SRSRAN_SUCCESS) { + ERROR("Error while initialising CFR module"); + return SRSRAN_ERROR; + } + } + // Plan MBSFN if (q->fft_plan.size) { // Replan if it was initialised previously @@ -242,6 +255,7 @@ void srsran_ofdm_free_(srsran_ofdm_t* q) if (q->window_offset_buffer) { free(q->window_offset_buffer); } + srsran_cfr_free(&q->tx_cfr); SRSRAN_MEM_ZERO(q, srsran_ofdm_t, 1); } @@ -289,6 +303,10 @@ int srsran_ofdm_tx_init(srsran_ofdm_t* q, srsran_cp_t cp, cf_t* in_buffer, cf_t* int srsran_ofdm_tx_init_cfg(srsran_ofdm_t* q, srsran_ofdm_cfg_t* cfg) { + if (q == NULL || cfg == NULL) { + ERROR("Error, invalid inputs"); + return SRSRAN_ERROR_INVALID_INPUTS; + } return ofdm_init_mbsfn_(q, cfg, SRSRAN_DFT_BACKWARD); } @@ -610,6 +628,11 @@ static void ofdm_tx_slot(srsran_ofdm_t* q, int slot_in_sf) srsran_vec_sc_prod_cfc(&output[cp_len], norm, &output[cp_len], symbol_sz); } + // CFR: Process the time-domain signal without the CP + if (q->cfg.cfr_tx_cfg.cfr_enable) { + srsran_cfr_process(&q->tx_cfr, output + cp_len, output + cp_len); + } + /* add CP */ srsran_vec_cf_copy(output, &output[symbol_sz], cp_len); output += symbol_sz + cp_len; @@ -656,3 +679,37 @@ void srsran_ofdm_tx_sf(srsran_ofdm_t* q) srsran_vec_prod_ccc(q->cfg.out_buffer, q->shift_buffer, q->cfg.out_buffer, q->sf_sz); } } + +int srsran_ofdm_set_cfr(srsran_ofdm_t* q, srsran_cfr_cfg_t* cfr) +{ + if (q == NULL || cfr == NULL) { + ERROR("Error, invalid inputs"); + return SRSRAN_ERROR_INVALID_INPUTS; + } + if (!q->max_prb) { + ERROR("Error, ofdm object not initialised"); + return SRSRAN_ERROR; + } + // Check if there is nothing to configure + if (memcmp(&q->cfg.cfr_tx_cfg, cfr, sizeof(srsran_cfr_cfg_t)) == 0) { + return SRSRAN_SUCCESS; + } + + // Copy the CFR config into the OFDM object + q->cfg.cfr_tx_cfg = *cfr; + + // Set the CFR parameters related to OFDM symbol and FFT size + q->cfg.cfr_tx_cfg.symbol_sz = q->cfg.symbol_sz; + q->cfg.cfr_tx_cfg.symbol_bw = q->nof_re; + + // in the DL, the DC carrier is empty but still counts when designing the filter BW + q->cfg.cfr_tx_cfg.dc_sc = (!q->cfg.keep_dc) && (!isnormal(q->cfg.freq_shift_f)); + if (q->cfg.cfr_tx_cfg.cfr_enable) { + if (srsran_cfr_init(&q->tx_cfr, &q->cfg.cfr_tx_cfg) < SRSRAN_SUCCESS) { + ERROR("Error while initialising CFR module"); + return SRSRAN_ERROR; + } + } + + return SRSRAN_SUCCESS; +} diff --git a/lib/src/phy/enb/enb_dl.c b/lib/src/phy/enb/enb_dl.c index 5c079aab8..941b0492b 100644 --- a/lib/src/phy/enb/enb_dl.c +++ b/lib/src/phy/enb/enb_dl.c @@ -49,9 +49,9 @@ int srsran_enb_dl_init(srsran_enb_dl_t* q, cf_t* out_buffer[SRSRAN_MAX_PORTS], u ofdm_cfg.nof_prb = max_prb; ofdm_cfg.cp = SRSRAN_CP_EXT; ofdm_cfg.normalize = false; - ofdm_cfg.in_buffer = q->sf_symbols[0]; - ofdm_cfg.out_buffer = out_buffer[0]; - ofdm_cfg.sf_type = SRSRAN_SF_MBSFN; + ofdm_cfg.in_buffer = q->sf_symbols[0]; + ofdm_cfg.out_buffer = out_buffer[0]; + ofdm_cfg.sf_type = SRSRAN_SF_MBSFN; if (srsran_ofdm_tx_init_cfg(&q->ifft_mbsfn, &ofdm_cfg)) { ERROR("Error initiating FFT"); goto clean_exit; @@ -142,7 +142,7 @@ int srsran_enb_dl_set_cell(srsran_enb_dl_t* q, srsran_cell_t cell) if (q->cell.nof_prb != 0) { srsran_regs_free(&q->regs); } - q->cell = cell; + q->cell = cell; srsran_ofdm_cfg_t ofdm_cfg = {}; ofdm_cfg.nof_prb = q->cell.nof_prb; ofdm_cfg.cp = cell.cp; @@ -151,6 +151,7 @@ int srsran_enb_dl_set_cell(srsran_enb_dl_t* q, srsran_cell_t cell) ofdm_cfg.in_buffer = q->sf_symbols[i]; ofdm_cfg.out_buffer = q->out_buffer[i]; ofdm_cfg.sf_type = SRSRAN_SF_NORM; + ofdm_cfg.cfr_tx_cfg = q->cfr_config; if (srsran_ofdm_tx_init_cfg(&q->ifft[i], &ofdm_cfg)) { ERROR("Error initiating FFT (%d)", i); return SRSRAN_ERROR; @@ -229,6 +230,31 @@ int srsran_enb_dl_set_cell(srsran_enb_dl_t* q, srsran_cell_t cell) return ret; } +int srsran_enb_dl_set_cfr(srsran_enb_dl_t* q, const srsran_cfr_cfg_t* cfr) +{ + if (q == NULL || cfr == NULL) { + ERROR("Error, invalid inputs"); + return SRSRAN_ERROR_INVALID_INPUTS; + } + + // Copy the cfr config into the eNB + q->cfr_config = *cfr; + + // Set the cfr for the ifft's + if (srsran_ofdm_set_cfr(&q->ifft_mbsfn, &q->cfr_config) < SRSRAN_SUCCESS) { + ERROR("Error setting the CFR for ifft_mbsfn"); + return SRSRAN_ERROR; + } + for (int i = 0; i < SRSRAN_MAX_PORTS; i++) { + if (srsran_ofdm_set_cfr(&q->ifft[i], &q->cfr_config) < SRSRAN_SUCCESS) { + ERROR("Error setting the CFR for the IFFT (%d)", i); + return SRSRAN_ERROR; + } + } + + return SRSRAN_SUCCESS; +} + #ifdef resolve void srsran_enb_dl_apply_power_allocation(srsran_enb_dl_t* q) { @@ -410,22 +436,22 @@ int srsran_enb_dl_put_pmch(srsran_enb_dl_t* q, srsran_pmch_cfg_t* pmch_cfg, uint void srsran_enb_dl_gen_signal(srsran_enb_dl_t* q) { - // TODO: PAPR control float norm_factor = enb_dl_get_norm_factor(q->cell.nof_prb); + // First apply the amplitude normalization, then perform the IFFT and optional CFR reduction if (q->dl_sf.sf_type == SRSRAN_SF_MBSFN) { - srsran_ofdm_tx_sf(&q->ifft_mbsfn); - srsran_vec_sc_prod_cfc(q->ifft_mbsfn.cfg.out_buffer, + srsran_vec_sc_prod_cfc(q->ifft_mbsfn.cfg.in_buffer, norm_factor, - q->ifft_mbsfn.cfg.out_buffer, - (uint32_t)SRSRAN_SF_LEN_PRB(q->cell.nof_prb)); + q->ifft_mbsfn.cfg.in_buffer, + SRSRAN_NOF_SLOTS_PER_SF * q->cell.nof_prb * SRSRAN_NRE * SRSRAN_CP_NSYMB(q->cell.cp)); + srsran_ofdm_tx_sf(&q->ifft_mbsfn); } else { for (int i = 0; i < q->cell.nof_ports; i++) { - srsran_ofdm_tx_sf(&q->ifft[i]); - srsran_vec_sc_prod_cfc(q->ifft[i].cfg.out_buffer, + srsran_vec_sc_prod_cfc(q->ifft[i].cfg.in_buffer, norm_factor, - q->ifft[i].cfg.out_buffer, - (uint32_t)SRSRAN_SF_LEN_PRB(q->cell.nof_prb)); + q->ifft[i].cfg.in_buffer, + SRSRAN_NOF_SLOTS_PER_SF * q->cell.nof_prb * SRSRAN_NRE * SRSRAN_CP_NSYMB(q->cell.cp)); + srsran_ofdm_tx_sf(&q->ifft[i]); } } } diff --git a/lib/src/phy/utils/test/vector_test.c b/lib/src/phy/utils/test/vector_test.c index 50bb1bdbc..ebaaa5d0d 100644 --- a/lib/src/phy/utils/test/vector_test.c +++ b/lib/src/phy/utils/test/vector_test.c @@ -836,6 +836,38 @@ TEST( free(z); srsran_cfo_free(&srsran_cfo);) +// This test compares the clipping method used for the CFR module in its default configuration to the original CFR +// algorithm. The original algorithm can still be used by defining CFR_PEAK_EXTRACTION in the CFR module. +TEST( + srsran_vec_gen_clip_env, MALLOC(cf_t, x); MALLOC(float, x_abs); MALLOC(float, env); float thres = 0.5f; + float alpha = 0.5f; + cf_t gold = 0.0f; + + for (int i = 0; i < block_size; i++) { + x[i] = RANDOM_F(); + env[i] = 0.0f; + x_abs[i] = cabsf(x[i]); + } + + // current implementation generates an amplitude envelope which is then multiplied with the signal + TEST_CALL(srsran_vec_gen_clip_env(x_abs, thres, alpha, env, block_size)) + + // Recreates the original method for clipping the signal, skipping the low-pass filtering + for (int i = 0; i < block_size; i++) { + if (x_abs[i] <= thres) { + gold = x[i]; + } else { + cf_t peak = x[i] - (thres * x[i] / x_abs[i]); // extract the peak + gold = x[i] - alpha * peak; // subtract the peak from the signal, scaled by alpha + } + // Compare the two clipping methods by applying the envelope to x and determining the error + mse += cabsf(gold - env[i] * x[i]); + } if (isnormal(mse)) { mse /= block_size; } + + free(x); + free(x_abs); + free(env);) + int main(int argc, char** argv) { char func_names[MAX_FUNCTIONS][32]; @@ -1014,6 +1046,10 @@ int main(int argc, char** argv) test_srsran_cfo_correct_change(func_names[func_count], &timmings[func_count][size_count], block_size); func_count++; + passed[func_count][size_count] = + test_srsran_vec_gen_clip_env(func_names[func_count], &timmings[func_count][size_count], block_size); + func_count++; + sizes[size_count] = block_size; size_count++; } diff --git a/lib/src/phy/utils/vector.c b/lib/src/phy/utils/vector.c index 6ca8294ad..4259c91ce 100644 --- a/lib/src/phy/utils/vector.c +++ b/lib/src/phy/utils/vector.c @@ -580,7 +580,11 @@ int32_t srsran_vec_dot_prod_sss(const int16_t* x, const int16_t* y, const uint32 float srsran_vec_avg_power_cf(const cf_t* x, const uint32_t len) { - return crealf(srsran_vec_dot_prod_conj_ccc(x, x, len)) / len; + if (!len) { + return 0; + } else { + return crealf(srsran_vec_dot_prod_conj_ccc(x, x, len)) / len; + } } float srsran_vec_avg_power_sf(const int16_t* x, const uint32_t len) @@ -627,6 +631,17 @@ float srsran_vec_avg_power_bf(const int8_t* x, const uint32_t len) return acc; } +float srsran_vec_avg_power_ff(const float* x, const uint32_t len) +{ + if (!len) { + return 0; + } else { + float pwr_symb_avg = srsran_vec_dot_prod_fff(x, x, len); + pwr_symb_avg /= (float)len; + return pwr_symb_avg; + } +} + // Correlation assumes zero-mean x and y float srsran_vec_corr_ccc(const cf_t* x, cf_t* y, const uint32_t len) { @@ -837,3 +852,38 @@ float srsran_vec_estimate_frequency(const cf_t* x, int len) { return srsran_vec_estimate_frequency_simd(x, len); } + +// TODO: implement with SIMD +void srsran_vec_gen_clip_env(const float* x_abs, const float thres, const float alpha, float* env, const int len) +{ + for (int i = 0; i < len; i++) { + env[i] = (x_abs[i] > thres) ? (1 - alpha) + alpha * thres / x_abs[i] : 1; + } +} + +float srsran_vec_papr_c(const cf_t* in, const int len) +{ + uint32_t max = srsran_vec_max_abs_ci(in, len); + float peak = SRSRAN_CSQABS(in[max]); + return peak / srsran_vec_avg_power_cf(in, len); +} + +float srsran_vec_acpr_c(const cf_t* x_f, const uint32_t win_pos_len, const uint32_t win_neg_len, const uint32_t len) +{ + // The adjacent channel cannot extend beyond the FFT len + const uint32_t ch_len = win_pos_len + win_neg_len; + const uint32_t adj_ch_len = ch_len > len / 2 ? len - ch_len : ch_len; + + // Integrate positive half of the signal power spectrum + float signal_pwr = srsran_vec_dot_prod_conj_ccc(x_f, x_f, win_pos_len); + // Integrate negative halt of the signal power spectrum + signal_pwr += srsran_vec_dot_prod_conj_ccc(x_f + len - win_neg_len, x_f + len - win_neg_len, win_neg_len); + + const float adj_ch_pwr = srsran_vec_dot_prod_conj_ccc(x_f + win_pos_len, x_f + win_pos_len, adj_ch_len); + + if (isnormal(signal_pwr)) { + return adj_ch_pwr / signal_pwr; + } else { + return 0; + } +} diff --git a/srsenb/enb.conf.example b/srsenb/enb.conf.example index 63e42c82f..4d5cce198 100644 --- a/srsenb/enb.conf.example +++ b/srsenb/enb.conf.example @@ -326,6 +326,32 @@ enable = false #fd_hz = -750.0 #init_time_s = 0.0 +##################################################################### +# CFR configuration options +# +# The CFR module provides crest factor reduction for the transmitted signal. +# +# enable: Enable or disable the CFR. Default: disabled +# +# mode: manual: CFR threshold is set by cfr_manual_thres (default). +# auto_ema: CFR threshold is adaptive based on the signal PAPR. Power avg. with Exponential Moving Average. +# The time constant of the averaging can be tweaked with the ema_alpha parameter. +# auto_cma: CFR threshold is adaptive based on the signal PAPR. Power avg. with Cumulative Moving Average. +# Use with care, as CMA's increasingly slow response may be unsuitable for most use cases. +# +# strength: Ratio between amplitude-limited vs unprocessed signal (0 to 1). Default: 1 +# manual_thres: Fixed manual clipping threshold for CFR manual mode. Default: 0.5 +# auto_target_papr: Signal PAPR target (in dB) in CFR auto modes. output PAPR can be higher due to peak smoothing. Default: 8 +# ema_alpha: Alpha coefficient for the power average in auto_ema mode. Default: 1/7 +# +##################################################################### +[cfr] +#enable = false +#mode = manual +#manual_thres = 0.5 +#strength = 1 +#auto_target_papr = 8 +#ema_alpha = 0.0143 ##################################################################### # Expert configuration options diff --git a/srsenb/hdr/enb.h b/srsenb/hdr/enb.h index 44ceac557..3bc690713 100644 --- a/srsenb/hdr/enb.h +++ b/srsenb/hdr/enb.h @@ -142,6 +142,8 @@ public: // eNodeB command interface void cmd_cell_gain(uint32_t cell_id, float gain) override; + void cmd_cell_measure() override; + void toggle_padding() override; void tti_clock() override; diff --git a/srsenb/hdr/phy/enb_phy_base.h b/srsenb/hdr/phy/enb_phy_base.h index 5d4ee4af8..57cf87159 100644 --- a/srsenb/hdr/phy/enb_phy_base.h +++ b/srsenb/hdr/phy/enb_phy_base.h @@ -38,6 +38,8 @@ public: virtual void get_metrics(std::vector& m) = 0; virtual void cmd_cell_gain(uint32_t cell_idx, float gain_db) = 0; + + virtual void cmd_cell_measure() = 0; }; } // namespace srsenb diff --git a/srsenb/hdr/phy/phy.h b/srsenb/hdr/phy/phy.h index ade136bc3..8bef23ebb 100644 --- a/srsenb/hdr/phy/phy.h +++ b/srsenb/hdr/phy/phy.h @@ -66,6 +66,7 @@ public: void get_metrics(std::vector& metrics) override; void cmd_cell_gain(uint32_t cell_id, float gain_db) override; + void cmd_cell_measure() override; void radio_overflow() override{}; void radio_failure() override{}; diff --git a/srsenb/hdr/phy/phy_common.h b/srsenb/hdr/phy/phy_common.h index e929c1a46..925567904 100644 --- a/srsenb/hdr/phy/phy_common.h +++ b/srsenb/hdr/phy/phy_common.h @@ -166,6 +166,45 @@ public: return c; } + void set_cell_measure_trigger() + { + // Trigger on LTE cell + for (auto it_lte = cell_list_lte.begin(); it_lte != cell_list_lte.end(); ++it_lte) { + it_lte->dl_measure = true; + } + + // Trigger on NR cell + for (auto it_nr = cell_list_nr.begin(); it_nr != cell_list_nr.end(); ++it_nr) { + it_nr->dl_measure = true; + } + } + + bool get_cell_measure_trigger(uint32_t cc_idx) + { + if (cc_idx < cell_list_lte.size()) { + return cell_list_lte.at(cc_idx).dl_measure; + } + + cc_idx -= cell_list_lte.size(); + if (cc_idx < cell_list_nr.size()) { + return cell_list_nr.at(cc_idx).dl_measure; + } + + return false; + } + + void clear_cell_measure_trigger(uint32_t cc_idx) + { + if (cc_idx < cell_list_lte.size()) { + cell_list_lte.at(cc_idx).dl_measure = false; + } + + cc_idx -= cell_list_lte.size(); + if (cc_idx < cell_list_nr.size()) { + cell_list_nr.at(cc_idx).dl_measure = false; + } + } + void set_cell_gain(uint32_t cell_id, float gain_db) { // Find LTE cell @@ -208,6 +247,11 @@ public: return 0.0f; } + // Common CFR configuration + srsran_cfr_cfg_t cfr_config = {}; + void set_cfr_config(srsran_cfr_cfg_t cfr_cfg) { cfr_config = cfr_cfg; } + srsran_cfr_cfg_t get_cfr_config() { return cfr_config; } + // Common Physical Uplink DMRS configuration srsran_refsignal_dmrs_pusch_cfg_t dmrs_pusch_cfg = {}; diff --git a/srsenb/hdr/phy/phy_interfaces.h b/srsenb/hdr/phy/phy_interfaces.h index 02eb00020..0caf92528 100644 --- a/srsenb/hdr/phy/phy_interfaces.h +++ b/srsenb/hdr/phy/phy_interfaces.h @@ -32,10 +32,20 @@ struct phy_cell_cfg_t { uint32_t root_seq_idx; uint32_t num_ra_preambles; float gain_db; + bool dl_measure; }; typedef std::vector phy_cell_cfg_list_t; +struct cfr_args_t { + bool enable = false; + srsran_cfr_mode_t mode = SRSRAN_CFR_THR_MANUAL; + float manual_thres = 0.5f; + float strength = 1.0f; + float auto_target_papr = 8.0f; + float ema_alpha = 1.0f / (float)SRSRAN_CP_NORM_NSYMB; +}; + struct phy_args_t { std::string type; srsran::phy_log_args_t log; @@ -56,6 +66,7 @@ struct phy_args_t { bool extended_cp = false; srsran::channel::args_t dl_channel_args; srsran::channel::args_t ul_channel_args; + cfr_args_t cfr_args; }; struct phy_cfg_t { @@ -69,6 +80,8 @@ struct phy_cfg_t { asn1::rrc::pusch_cfg_common_s pusch_cnfg; asn1::rrc::pucch_cfg_common_s pucch_cnfg; asn1::rrc::srs_ul_cfg_common_c srs_ul_cnfg; + + srsran_cfr_cfg_t cfr_config; }; } // namespace srsenb diff --git a/srsenb/src/enb.cc b/srsenb/src/enb.cc index 215a0354e..5b1a8c5ae 100644 --- a/srsenb/src/enb.cc +++ b/srsenb/src/enb.cc @@ -219,6 +219,11 @@ void enb::cmd_cell_gain(uint32_t cell_id, float gain) phy->cmd_cell_gain(cell_id, gain); } +void enb::cmd_cell_measure() +{ + phy->cmd_cell_measure(); +} + std::string enb::get_build_mode() { return std::string(srsran_get_build_mode()); diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index 45eeb5738..aa1b17655 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -1153,6 +1153,30 @@ int parse_cell_cfg(all_args_t* args_, srsran_cell_t* cell) return SRSRAN_SUCCESS; } +// Parse the relevant CFR configuration params +int parse_cfr_args(all_args_t* args, srsran_cfr_cfg_t* cfr_config) +{ + cfr_config->cfr_enable = args->phy.cfr_args.enable; + cfr_config->cfr_mode = args->phy.cfr_args.mode; + cfr_config->alpha = args->phy.cfr_args.strength; + cfr_config->manual_thr = args->phy.cfr_args.manual_thres; + cfr_config->max_papr_db = args->phy.cfr_args.auto_target_papr; + cfr_config->ema_alpha = args->phy.cfr_args.ema_alpha; + + if (!srsran_cfr_params_valid(cfr_config)) { + fprintf(stderr, + "Invalid CFR parameters: cfr_mode=%d, alpha=%.2f, manual_thr=%.2f, \n " + "max_papr_db=%.2f, ema_alpha=%.2f\n", + cfr_config->cfr_mode, + cfr_config->alpha, + cfr_config->manual_thr, + cfr_config->max_papr_db, + cfr_config->ema_alpha); + return SRSRAN_ERROR; + } + return SRSRAN_SUCCESS; +} + int parse_cfg_files(all_args_t* args_, rrc_cfg_t* rrc_cfg_, rrc_nr_cfg_t* rrc_nr_cfg_, phy_cfg_t* phy_cfg_) { // Parse config files @@ -1272,6 +1296,12 @@ int parse_cfg_files(all_args_t* args_, rrc_cfg_t* rrc_cfg_, rrc_nr_cfg_t* rrc_nr } } + // Parse CFR args + if (parse_cfr_args(args_, &phy_cfg_->cfr_config) < SRSRAN_SUCCESS) { + fprintf(stderr, "Error parsing CFR configuration\n"); + return SRSRAN_ERROR; + } + return SRSRAN_SUCCESS; } diff --git a/srsenb/src/main.cc b/srsenb/src/main.cc index 1ed0b4a4b..0cc0e0db9 100644 --- a/srsenb/src/main.cc +++ b/srsenb/src/main.cc @@ -56,6 +56,7 @@ void parse_args(all_args_t* args, int argc, char* argv[]) string mcc; string mnc; string enb_id; + string cfr_mode; bool use_standard_lte_rates = false; // Command line only options @@ -212,6 +213,14 @@ void parse_args(all_args_t* args, int argc, char* argv[]) ("channel.ul.hst.fd_hz", bpo::value(&args->phy.ul_channel_args.hst_fd_hz)->default_value(+750.0f), "Doppler frequency in Hz") ("channel.ul.hst.init_time_s", bpo::value(&args->phy.ul_channel_args.hst_init_time_s)->default_value(0), "Initial time in seconds") + /* CFR section */ + ("cfr.enable", bpo::value(&args->phy.cfr_args.enable)->default_value(args->phy.cfr_args.enable), "CFR enable") + ("cfr.mode", bpo::value(&cfr_mode)->default_value("manual"), "CFR mode") + ("cfr.manual_thres", bpo::value(&args->phy.cfr_args.manual_thres)->default_value(args->phy.cfr_args.manual_thres), "Fixed manual clipping threshold for CFR manual mode") + ("cfr.strength", bpo::value(&args->phy.cfr_args.strength)->default_value(args->phy.cfr_args.strength), "CFR ratio between amplitude-limited vs original signal (0 to 1)") + ("cfr.auto_target_papr", bpo::value(&args->phy.cfr_args.auto_target_papr)->default_value(args->phy.cfr_args.auto_target_papr), "Signal PAPR target (in dB) in CFR auto modes") + ("cfr.ema_alpha", bpo::value(&args->phy.cfr_args.ema_alpha)->default_value(args->phy.cfr_args.ema_alpha), "Alpha coefficient for the power average in auto_ema mode (0 to 1)") + /* Expert section */ ("expert.metrics_period_secs", bpo::value(&args->general.metrics_period_secs)->default_value(1.0), "Periodicity for metrics in seconds.") ("expert.metrics_csv_enable", bpo::value(&args->general.metrics_csv_enable)->default_value(false), "Write metrics to CSV file.") @@ -369,6 +378,20 @@ void parse_args(all_args_t* args, int argc, char* argv[]) exit(1); } + // convert CFR mode + if (!cfr_mode.empty()) { + if (cfr_mode == "manual") { + args->phy.cfr_args.mode = SRSRAN_CFR_THR_MANUAL; + } else if (cfr_mode == "auto_cma") { + args->phy.cfr_args.mode = SRSRAN_CFR_THR_AUTO_CMA; + } else if (cfr_mode == "auto_ema") { + args->phy.cfr_args.mode = SRSRAN_CFR_THR_AUTO_EMA; + } else { + cout << "Error, invalid CFR mode: " << cfr_mode << endl; + exit(1); + } + } + // Apply all_level to any unset layers if (vm.count("log.all_level")) { if (!vm.count("log.rf_level")) { @@ -465,6 +488,9 @@ static void execute_cmd(metrics_stdout* metrics, srsenb::enb_command_interface* cout << "Enter t to restart trace." << endl; } metrics->toggle_print(do_metrics); + } else if (cmd[0] == "m") { + // Trigger cell measurements + control->cmd_cell_measure(); } else if (cmd[0] == "sleep") { if (cmd.size() != 2) { cout << "Usage: " << cmd[0] << " [number of seconds]" << endl; @@ -507,6 +533,7 @@ static void execute_cmd(metrics_stdout* metrics, srsenb::enb_command_interface* } else { cout << "Available commands: " << endl; cout << " t: starts console trace" << endl; + cout << " m: downlink signal measurements" << endl; cout << " q: quit srsenb" << endl; cout << " cell_gain: set relative cell gain" << endl; cout << " sleep: pauses the commmand line operation for a given time in seconds" << endl; diff --git a/srsenb/src/phy/lte/cc_worker.cc b/srsenb/src/phy/lte/cc_worker.cc index 46ce668a3..aa17fb709 100644 --- a/srsenb/src/phy/lte/cc_worker.cc +++ b/srsenb/src/phy/lte/cc_worker.cc @@ -10,6 +10,8 @@ * */ +#include + #include "srsran/common/threads.h" #include "srsran/srsran.h" @@ -77,11 +79,12 @@ FILE* f; void cc_worker::init(phy_common* phy_, uint32_t cc_idx_) { - phy = phy_; - cc_idx = cc_idx_; - srsran_cell_t cell = phy_->get_cell(cc_idx); - uint32_t nof_prb = phy_->get_nof_prb(cc_idx); - uint32_t sf_len = SRSRAN_SF_LEN_PRB(nof_prb); + phy = phy_; + cc_idx = cc_idx_; + srsran_cell_t cell = phy_->get_cell(cc_idx); + uint32_t nof_prb = phy_->get_nof_prb(cc_idx); + uint32_t sf_len = SRSRAN_SF_LEN_PRB(nof_prb); + srsran_cfr_cfg_t cfr_config = phy_->get_cfr_config(); // Init cell here for (uint32_t p = 0; p < phy->get_nof_ports(cc_idx); p++) { @@ -106,6 +109,10 @@ void cc_worker::init(phy_common* phy_, uint32_t cc_idx_) ERROR("Error initiating ENB DL (cc=%d)", cc_idx); return; } + if (srsran_enb_dl_set_cfr(&enb_dl, &cfr_config) < SRSRAN_SUCCESS) { + ERROR("Error setting the CFR"); + return; + } if (srsran_enb_ul_init(&enb_ul, signal_buffer_rx[0], nof_prb)) { ERROR("Error initiating ENB UL"); return; @@ -251,6 +258,20 @@ void cc_worker::work_dl(const srsran_dl_sf_cfg_t& dl_sf_cfg, srsran_vec_sc_prod_cfc(signal_buffer_tx[i], scale, signal_buffer_tx[i], sf_len); } } + + // Measure PAPR if flag was triggered + bool cell_meas_flag = phy->get_cell_measure_trigger(cc_idx); + if (cell_meas_flag) { + uint32_t sf_len = SRSRAN_SF_LEN_PRB(enb_dl.cell.nof_prb); + for (uint32_t i = 0; i < enb_dl.cell.nof_ports; i++) { + // PAPR measure + float papr_db = 10.0f * log10(srsran_vec_papr_c(signal_buffer_tx[i], sf_len)); + std::cout << "Cell #" << cc_idx << " port #" << i << " PAPR = " << std::setprecision(4) << papr_db << " dB " + << std::endl; + } + // clear measurement flag on cell + phy->clear_cell_measure_trigger(cc_idx); + } } bool cc_worker::decode_pusch_rnti(stack_interface_phy_lte::ul_sched_grant_t& ul_grant, diff --git a/srsenb/src/phy/phy.cc b/srsenb/src/phy/phy.cc index aba1cfa30..8a185187a 100644 --- a/srsenb/src/phy/phy.cc +++ b/srsenb/src/phy/phy.cc @@ -165,6 +165,9 @@ int phy::init_lte(const phy_args_t& args, workers_common.params = args; workers_common.init(cfg.phy_cell_cfg, cfg.phy_cell_cfg_nr, radio, stack_lte_); + if (cfg.cfr_config.cfr_enable) { + workers_common.set_cfr_config(cfg.cfr_config); + } parse_common_config(cfg); @@ -272,6 +275,11 @@ void phy::cmd_cell_gain(uint32_t cell_id, float gain_db) workers_common.set_cell_gain(cell_id, gain_db); } +void phy::cmd_cell_measure() +{ + workers_common.set_cell_measure_trigger(); +} + /***** RRC->PHY interface **********/ void phy::set_config(uint16_t rnti, const phy_rrc_cfg_list_t& phy_cfg_list) diff --git a/srsgnb/hdr/phy/phy_nr_interfaces.h b/srsgnb/hdr/phy/phy_nr_interfaces.h index 9248d06b0..6784909fc 100644 --- a/srsgnb/hdr/phy/phy_nr_interfaces.h +++ b/srsgnb/hdr/phy/phy_nr_interfaces.h @@ -30,6 +30,7 @@ struct phy_cell_cfg_nr_t { srsran_pdcch_cfg_nr_t pdcch = {}; ///< Common CORESET and Search Space configuration srsran_pdsch_cfg_t pdsch = {}; srsran_prach_cfg_t prach = {}; + bool dl_measure; }; using phy_cell_cfg_list_nr_t = std::vector;