mirror of https://github.com/pvnis/srsRAN_4G.git
Merge branch 'next' into agpl_next
commit
42cc7d5d1a
@ -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;
|
||||||
|
}
|
@ -0,0 +1,426 @@
|
|||||||
|
/**
|
||||||
|
* \copyright Copyright 2013-2021 Software Radio Systems Limited
|
||||||
|
*
|
||||||
|
* \copyright 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \file pusch_nr_bler_test.c
|
||||||
|
* \brief BLER and throughput test for PUSCH NR.
|
||||||
|
*
|
||||||
|
* This program simulates several PUSCH transmissions in order to estimate its performance in terms of the receiver BLER
|
||||||
|
* and throughput (expressed as a percentage of the transmitted one). Specifically, the simulation runs until 100
|
||||||
|
* transmissions fail (or after 2,000,000 transmitted transport blocks). Failures are detected by CRC verification.
|
||||||
|
*
|
||||||
|
* The simulation setup can be controlled by means of the following arguments.
|
||||||
|
* - <tt>-p num</tt>: sets the number of granted PUSCH PRBs to \c num.
|
||||||
|
* - <tt>-T tab</tt>: sets the modulation and coding scheme table (valid options: \c 64qam, \c 256qam, \c 64qamLowSE).
|
||||||
|
* - <tt>-m mcs</tt>: sets the modulation and coding scheme index to \c mcs.
|
||||||
|
* - <tt>-L num</tt>: sets the number of transmission layers to \c num.
|
||||||
|
* - <tt>-A num</tt>: sets the number of HARQ-ACK bits to \c num.
|
||||||
|
* - <tt>-C num</tt>: sets the number of CSI bits to \c num.
|
||||||
|
* - <tt>-N num</tt>: sets the maximum number of simulated transport blocks to \c num.
|
||||||
|
* - <tt>-s val</tt>: sets the nominal SNR to \c val (in dB).
|
||||||
|
* - <tt>-f </tt>: activates full BLER simulations (Tx--Rx comparison as opposed to CRC-verification only).
|
||||||
|
* - <tt>-v </tt>: activates verbose output.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* \code{.cpp}
|
||||||
|
* pusch_nr_bler_test -p 52 -m 2 -T 64qam -s -1.8 -f
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "srsran/phy/channel/ch_awgn.h"
|
||||||
|
#include "srsran/phy/phch/pusch_nr.h"
|
||||||
|
#include "srsran/phy/phch/ra_nr.h"
|
||||||
|
#include "srsran/phy/phch/ra_ul_nr.h"
|
||||||
|
#include "srsran/phy/utils/debug.h"
|
||||||
|
#include "srsran/phy/utils/random.h"
|
||||||
|
#include "srsran/phy/utils/vector.h"
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
|
static srsran_carrier_nr_t carrier = SRSRAN_DEFAULT_CARRIER_NR;
|
||||||
|
static uint32_t n_prb = 0;
|
||||||
|
static uint32_t mcs = 30;
|
||||||
|
static srsran_sch_cfg_nr_t pusch_cfg = {};
|
||||||
|
static uint16_t rnti = 0x1234;
|
||||||
|
static uint32_t nof_ack_bits = 0;
|
||||||
|
static uint32_t nof_csi_bits = 0;
|
||||||
|
static uint32_t max_blocks = 2e6; // max number of simulated transport blocks
|
||||||
|
static float snr = 10;
|
||||||
|
static bool full_check = false;
|
||||||
|
|
||||||
|
void usage(char* prog)
|
||||||
|
{
|
||||||
|
printf("Usage: %s [pmTLACNsfv] \n", prog);
|
||||||
|
printf("\t-p Number of grant PRB [Default %d]\n", n_prb);
|
||||||
|
printf("\t-m MCS PRB [Default %d]\n", mcs);
|
||||||
|
printf("\t-T Provide MCS table (64qam, 256qam, 64qamLowSE) [Default %s]\n",
|
||||||
|
srsran_mcs_table_to_str(pusch_cfg.sch_cfg.mcs_table));
|
||||||
|
printf("\t-L Provide number of layers [Default %d]\n", carrier.max_mimo_layers);
|
||||||
|
printf("\t-A Provide a number of HARQ-ACK bits [Default %d]\n", nof_ack_bits);
|
||||||
|
printf("\t-C Provide a number of CSI bits [Default %d]\n", nof_csi_bits);
|
||||||
|
printf("\t-N Maximum number of simulated transport blocks [Default %d]\n", max_blocks);
|
||||||
|
printf("\t-s Signal-to-Noise Ratio in dB [Default %.1f]\n", snr);
|
||||||
|
printf("\t-f Perform full BLER check instead of CRC only [Default %s]\n", full_check ? "true" : "false");
|
||||||
|
printf("\t-v [set srsran_verbose to debug, default none]\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_args(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int opt = 0;
|
||||||
|
while ((opt = getopt(argc, argv, "p:m:T:L:A:C:N:s:fv")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'p':
|
||||||
|
n_prb = (uint32_t)strtol(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
mcs = (uint32_t)strtol(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
pusch_cfg.sch_cfg.mcs_table = srsran_mcs_table_from_str(optarg);
|
||||||
|
break;
|
||||||
|
case 'L':
|
||||||
|
carrier.max_mimo_layers = (uint32_t)strtol(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
|
case 'A':
|
||||||
|
nof_ack_bits = (uint32_t)strtol(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
nof_csi_bits = (uint32_t)strtol(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
max_blocks = (uint32_t)strtol(optarg, NULL, 10);
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
snr = strtof(optarg, NULL);
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
full_check = true;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
increase_srsran_verbose_level();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage(argv[0]);
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
srsran_pusch_nr_t pusch_tx = {};
|
||||||
|
srsran_pusch_nr_t pusch_rx = {};
|
||||||
|
srsran_chest_dl_res_t chest = {};
|
||||||
|
srsran_random_t rand_gen = srsran_random_init(1234);
|
||||||
|
|
||||||
|
srsran_pusch_data_nr_t data_tx = {};
|
||||||
|
srsran_pusch_res_nr_t data_rx = {};
|
||||||
|
cf_t* sf_symbols_tx[SRSRAN_MAX_LAYERS_NR] = {};
|
||||||
|
cf_t* sf_symbols_rx[SRSRAN_MAX_LAYERS_NR] = {};
|
||||||
|
|
||||||
|
// Set default PUSCH configuration
|
||||||
|
pusch_cfg.sch_cfg.mcs_table = srsran_mcs_table_64qam;
|
||||||
|
|
||||||
|
if (parse_args(argc, argv) < SRSRAN_SUCCESS) {
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_pusch_nr_args_t pusch_args = {};
|
||||||
|
pusch_args.sch.disable_simd = false;
|
||||||
|
pusch_args.measure_evm = true;
|
||||||
|
|
||||||
|
if (srsran_pusch_nr_init_ue(&pusch_tx, &pusch_args) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error initiating PUSCH for Tx");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_pusch_nr_init_gnb(&pusch_rx, &pusch_args) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error initiating SCH NR for Rx");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_pusch_nr_set_carrier(&pusch_tx, &carrier)) {
|
||||||
|
ERROR("Error setting SCH NR carrier");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_pusch_nr_set_carrier(&pusch_rx, &carrier)) {
|
||||||
|
ERROR("Error setting SCH NR carrier");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t slot_length = SRSRAN_SLOT_LEN_RE_NR(carrier.nof_prb);
|
||||||
|
for (uint32_t i = 0; i < carrier.max_mimo_layers; i++) {
|
||||||
|
sf_symbols_tx[i] = srsran_vec_cf_malloc(slot_length);
|
||||||
|
sf_symbols_rx[i] = srsran_vec_cf_malloc(slot_length);
|
||||||
|
if (sf_symbols_tx[i] == NULL || sf_symbols_rx[i] == NULL) {
|
||||||
|
ERROR("Error malloc");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < pusch_tx.max_cw; i++) {
|
||||||
|
data_tx.payload[i] = srsran_vec_u8_malloc(SRSRAN_SLOT_MAX_NOF_BITS_NR);
|
||||||
|
data_rx.tb[i].payload = srsran_vec_u8_malloc(SRSRAN_SLOT_MAX_NOF_BITS_NR);
|
||||||
|
if (data_tx.payload[i] == NULL || data_rx.tb[i].payload == NULL) {
|
||||||
|
ERROR("Error malloc");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_softbuffer_tx_t softbuffer_tx = {};
|
||||||
|
srsran_softbuffer_rx_t softbuffer_rx = {};
|
||||||
|
|
||||||
|
if (srsran_softbuffer_tx_init_guru(&softbuffer_tx, SRSRAN_SCH_NR_MAX_NOF_CB_LDPC, SRSRAN_LDPC_MAX_LEN_ENCODED_CB) <
|
||||||
|
SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error init soft-buffer");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_softbuffer_rx_init_guru(&softbuffer_rx, SRSRAN_SCH_NR_MAX_NOF_CB_LDPC, SRSRAN_LDPC_MAX_LEN_ENCODED_CB) <
|
||||||
|
SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error init soft-buffer");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use grant default A time resources with m=0
|
||||||
|
if (srsran_ra_ul_nr_pusch_time_resource_default_A(carrier.scs, 0, &pusch_cfg.grant) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error loading default grant");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set PUSCH grant without considering any procedure
|
||||||
|
pusch_cfg.grant.nof_dmrs_cdm_groups_without_data = 1; // No need for MIMO
|
||||||
|
pusch_cfg.grant.nof_layers = carrier.max_mimo_layers;
|
||||||
|
pusch_cfg.grant.dci_format = srsran_dci_format_nr_1_0;
|
||||||
|
pusch_cfg.grant.rnti = rnti;
|
||||||
|
|
||||||
|
// Check input: PRB
|
||||||
|
if (n_prb > carrier.nof_prb) {
|
||||||
|
ERROR("Invalid number of PRB");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check input: MCS
|
||||||
|
uint32_t mcs_end = pusch_cfg.sch_cfg.mcs_table == srsran_mcs_table_256qam ? 28 : 29;
|
||||||
|
if (mcs > mcs_end) {
|
||||||
|
ERROR("Invalid MCS");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_sch_hl_cfg_nr_t sch_hl_cfg = {};
|
||||||
|
sch_hl_cfg.scaling = 1.0F;
|
||||||
|
sch_hl_cfg.beta_offsets.fix_ack = 12.625F;
|
||||||
|
sch_hl_cfg.beta_offsets.fix_csi1 = 2.25F;
|
||||||
|
sch_hl_cfg.beta_offsets.fix_csi2 = 2.25F;
|
||||||
|
|
||||||
|
if (srsran_chest_dl_res_init(&chest, carrier.nof_prb) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Initiating chest");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t n = 0; n < SRSRAN_MAX_PRB_NR; n++) {
|
||||||
|
pusch_cfg.grant.prb_idx[n] = (n < n_prb);
|
||||||
|
}
|
||||||
|
pusch_cfg.grant.nof_prb = n_prb;
|
||||||
|
|
||||||
|
pusch_cfg.grant.dci_format = srsran_dci_format_nr_0_0;
|
||||||
|
pusch_cfg.grant.nof_dmrs_cdm_groups_without_data = 2;
|
||||||
|
pusch_cfg.dmrs.type = srsran_dmrs_sch_type_1;
|
||||||
|
pusch_cfg.dmrs.length = srsran_dmrs_sch_len_1;
|
||||||
|
pusch_cfg.dmrs.additional_pos = srsran_dmrs_sch_add_pos_2;
|
||||||
|
if (srsran_ra_nr_fill_tb(&pusch_cfg, &pusch_cfg.grant, mcs, &pusch_cfg.grant.tb[0]) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error filling tb");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t n_blocks = 0;
|
||||||
|
uint32_t n_errors = 0;
|
||||||
|
uint32_t crc_false_pos = 0;
|
||||||
|
uint32_t crc_false_neg = 0;
|
||||||
|
float evm = 0;
|
||||||
|
for (; n_blocks < max_blocks && n_errors < 100; n_blocks++) {
|
||||||
|
// Generate SCH payload
|
||||||
|
for (uint32_t tb = 0; tb < SRSRAN_MAX_TB; tb++) {
|
||||||
|
// Skip TB if no allocated
|
||||||
|
if (data_tx.payload[tb] == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load payload with bytes
|
||||||
|
for (uint32_t i = 0; i < pusch_cfg.grant.tb[tb].tbs / 8 + 1; i++) {
|
||||||
|
data_tx.payload[tb][i] = (uint8_t)srsran_random_uniform_int_dist(rand_gen, 0, UINT8_MAX);
|
||||||
|
}
|
||||||
|
pusch_cfg.grant.tb[tb].softbuffer.tx = &softbuffer_tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate HARQ ACK bits
|
||||||
|
if (nof_ack_bits > 0) {
|
||||||
|
pusch_cfg.uci.ack.count = nof_ack_bits;
|
||||||
|
for (uint32_t i = 0; i < nof_ack_bits; i++) {
|
||||||
|
data_tx.uci.ack[i] = (uint8_t)srsran_random_uniform_int_dist(rand_gen, 0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate CSI report bits
|
||||||
|
uint8_t csi_report_tx[SRSRAN_UCI_NR_MAX_CSI1_BITS] = {};
|
||||||
|
uint8_t csi_report_rx[SRSRAN_UCI_NR_MAX_CSI1_BITS] = {};
|
||||||
|
if (nof_csi_bits > 0) {
|
||||||
|
pusch_cfg.uci.csi[0].cfg.quantity = SRSRAN_CSI_REPORT_QUANTITY_NONE;
|
||||||
|
pusch_cfg.uci.csi[0].K_csi_rs = nof_csi_bits;
|
||||||
|
pusch_cfg.uci.nof_csi = 1;
|
||||||
|
data_tx.uci.csi[0].none = csi_report_tx;
|
||||||
|
for (uint32_t i = 0; i < nof_csi_bits; i++) {
|
||||||
|
csi_report_tx[i] = (uint8_t)srsran_random_uniform_int_dist(rand_gen, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
data_rx.uci.csi[0].none = csi_report_rx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_ra_ul_set_grant_uci_nr(&carrier, &sch_hl_cfg, &pusch_cfg.uci, &pusch_cfg) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Setting UCI");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_pusch_nr_encode(&pusch_tx, &pusch_cfg, &pusch_cfg.grant, &data_tx, sf_symbols_tx) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error encoding");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise_var = srsran_convert_dB_to_power(-snr);
|
||||||
|
for (uint32_t i = 0; i < carrier.max_mimo_layers; i++) {
|
||||||
|
srsran_ch_awgn_c(sf_symbols_tx[i], sf_symbols_rx[i], noise_var, slot_length);
|
||||||
|
// memcpy(sf_symbols_rx[i], sf_symbols_tx[i], slot_length * sizeof(cf_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_srsran_verbose_level() >= SRSRAN_VERBOSE_INFO) {
|
||||||
|
uint32_t nof_re_total = carrier.nof_prb * SRSRAN_NRE;
|
||||||
|
uint32_t nof_re_used = pusch_cfg.grant.nof_prb * SRSRAN_NRE;
|
||||||
|
for (int i_layer = 0; i_layer < carrier.max_mimo_layers; i_layer++) {
|
||||||
|
INFO("Layer %d", i_layer);
|
||||||
|
float tx_power = 0;
|
||||||
|
float rx_power = 0;
|
||||||
|
uint8_t n_symbols = 0;
|
||||||
|
for (int i = 0; i < SRSRAN_NSYMB_PER_SLOT_NR; i++) {
|
||||||
|
if (!pusch_tx.dmrs_re_pattern.symbol[i]) {
|
||||||
|
n_symbols++;
|
||||||
|
tx_power += srsran_vec_avg_power_cf(sf_symbols_tx[0] + i * nof_re_total, nof_re_total);
|
||||||
|
rx_power += srsran_vec_avg_power_cf(sf_symbols_rx[0] + i * nof_re_total, nof_re_total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx_power *= (float)nof_re_total / nof_re_used; // compensate for unused REs
|
||||||
|
INFO(" Tx power: %.3f", tx_power / n_symbols);
|
||||||
|
INFO(" Rx power: %.3f", rx_power / n_symbols);
|
||||||
|
INFO(" SNR: %.3f dB", srsran_convert_power_to_dB(tx_power / (rx_power - tx_power)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t tb = 0; tb < SRSRAN_MAX_TB; tb++) {
|
||||||
|
pusch_cfg.grant.tb[tb].softbuffer.rx = &softbuffer_rx;
|
||||||
|
srsran_softbuffer_rx_reset(pusch_cfg.grant.tb[tb].softbuffer.rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// assume perfect channel estimation (including noise variance)
|
||||||
|
for (uint32_t i = 0; i < pusch_cfg.grant.tb->nof_re; i++) {
|
||||||
|
chest.ce[0][0][i] = 1.0F;
|
||||||
|
}
|
||||||
|
chest.nof_re = pusch_cfg.grant.tb->nof_re;
|
||||||
|
chest.noise_estimate = 2 * noise_var;
|
||||||
|
|
||||||
|
if (srsran_pusch_nr_decode(&pusch_rx, &pusch_cfg, &pusch_cfg.grant, &chest, sf_symbols_rx, &data_rx) <
|
||||||
|
SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Error decoding");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
evm += data_rx.evm[0];
|
||||||
|
// Validate UL-SCH CRC check
|
||||||
|
if (!data_rx.tb[0].crc) {
|
||||||
|
n_errors++;
|
||||||
|
printf("*");
|
||||||
|
fflush(stdout);
|
||||||
|
if (n_errors % 20 == 0) {
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (full_check) {
|
||||||
|
// Validate by comparing payload (recall, payload is represented in bytes)
|
||||||
|
if ((memcmp(data_rx.tb[0].payload, data_tx.payload[0], pusch_cfg.grant.tb[0].tbs * sizeof(uint8_t) / 8) == 0) &&
|
||||||
|
!data_rx.tb[0].crc) {
|
||||||
|
printf("\nWARNING! Codeword OK but CRC KO!\n");
|
||||||
|
crc_false_pos++;
|
||||||
|
} else if ((memcmp(data_rx.tb[0].payload, data_tx.payload[0], pusch_cfg.grant.tb[0].tbs * sizeof(uint8_t) / 8) !=
|
||||||
|
0) &&
|
||||||
|
data_rx.tb[0].crc) {
|
||||||
|
printf("\nWarning! Codeword KO but CRC OK!\n");
|
||||||
|
crc_false_neg++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char str[512];
|
||||||
|
srsran_pusch_nr_rx_info(&pusch_rx, &pusch_cfg, &pusch_cfg.grant, &data_rx, str, (uint32_t)sizeof(str));
|
||||||
|
|
||||||
|
char str_extra[2048];
|
||||||
|
srsran_sch_cfg_nr_info(&pusch_cfg, str_extra, (uint32_t)sizeof(str_extra));
|
||||||
|
printf("\nPUSCH: %s\n%s", str, str_extra);
|
||||||
|
|
||||||
|
printf("\nNominal SNR: %.1f dB\n", snr);
|
||||||
|
printf("Average EVM: %.3f\n", evm / n_blocks);
|
||||||
|
|
||||||
|
printf("BLER: %.3e (%d errors out of %d blocks)\n", (double)n_errors / n_blocks, n_errors, n_blocks);
|
||||||
|
printf("Tx Throughput: %.3e Mbps -- Rx Throughput: %.3e Mbps (%.2f%%)\n",
|
||||||
|
pusch_cfg.grant.tb[0].tbs / 1e3,
|
||||||
|
(n_blocks - n_errors) / 1e3 * pusch_cfg.grant.tb[0].tbs / n_blocks,
|
||||||
|
100.0F * (n_blocks - n_errors) / n_blocks);
|
||||||
|
|
||||||
|
if (full_check) {
|
||||||
|
uint32_t true_errors = n_errors + crc_false_neg - crc_false_pos;
|
||||||
|
printf("CRC: missed detection/Type I err. %.2f%% (%d out of %d)",
|
||||||
|
100.0F * crc_false_neg / true_errors,
|
||||||
|
crc_false_neg,
|
||||||
|
true_errors);
|
||||||
|
printf(" -- false alarm %.2f%% (%d out of %d)", 100.0F * crc_false_pos / n_errors, crc_false_pos, n_errors);
|
||||||
|
printf(" -- Type II err. %.2f%% (%d out of %d)\n",
|
||||||
|
100.0F * crc_false_pos / (n_blocks - true_errors),
|
||||||
|
crc_false_pos,
|
||||||
|
n_blocks - true_errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
srsran_chest_dl_res_free(&chest);
|
||||||
|
srsran_random_free(rand_gen);
|
||||||
|
srsran_pusch_nr_free(&pusch_tx);
|
||||||
|
srsran_pusch_nr_free(&pusch_rx);
|
||||||
|
for (uint32_t i = 0; i < SRSRAN_MAX_CODEWORDS; i++) {
|
||||||
|
if (data_tx.payload[i]) {
|
||||||
|
free(data_tx.payload[i]);
|
||||||
|
}
|
||||||
|
if (data_rx.tb[i].payload) {
|
||||||
|
free(data_rx.tb[i].payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < SRSRAN_MAX_LAYERS_NR; i++) {
|
||||||
|
if (sf_symbols_tx[i]) {
|
||||||
|
free(sf_symbols_tx[i]);
|
||||||
|
}
|
||||||
|
if (sf_symbols_rx[i]) {
|
||||||
|
free(sf_symbols_rx[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
srsran_softbuffer_tx_free(&softbuffer_tx);
|
||||||
|
srsran_softbuffer_rx_free(&softbuffer_rx);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
@ -0,0 +1,859 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \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_RF_IMP_TRX_H
|
||||||
|
#define SRSRAN_RF_IMP_TRX_H
|
||||||
|
|
||||||
|
#include "rf_file_imp.h"
|
||||||
|
#include "rf_file_imp_trx.h"
|
||||||
|
#include "rf_helper.h"
|
||||||
|
#include <errno.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <srsran/phy/common/phy_common.h>
|
||||||
|
#include <srsran/phy/common/timestamp.h>
|
||||||
|
#include <srsran/phy/utils/vector.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// Common attributes
|
||||||
|
char* devname;
|
||||||
|
srsran_rf_info_t info;
|
||||||
|
uint32_t nof_channels;
|
||||||
|
|
||||||
|
// RF State
|
||||||
|
uint32_t srate; // radio rate configured by upper layers
|
||||||
|
uint32_t base_srate;
|
||||||
|
uint32_t decim_factor; // decimation factor between base_srate used on transport on radio's rate
|
||||||
|
double rx_gain;
|
||||||
|
uint32_t tx_freq_mhz[SRSRAN_MAX_CHANNELS];
|
||||||
|
uint32_t rx_freq_mhz[SRSRAN_MAX_CHANNELS];
|
||||||
|
bool tx_off;
|
||||||
|
char id[RF_PARAM_LEN];
|
||||||
|
|
||||||
|
// FILEs
|
||||||
|
rf_file_tx_t transmitter[SRSRAN_MAX_CHANNELS];
|
||||||
|
rf_file_rx_t receiver[SRSRAN_MAX_CHANNELS];
|
||||||
|
bool close_files;
|
||||||
|
|
||||||
|
// Various sample buffers
|
||||||
|
cf_t* buffer_decimation[SRSRAN_MAX_CHANNELS];
|
||||||
|
cf_t* buffer_tx;
|
||||||
|
|
||||||
|
// Rx timestamp
|
||||||
|
uint64_t next_rx_ts;
|
||||||
|
|
||||||
|
pthread_mutex_t tx_config_mutex;
|
||||||
|
pthread_mutex_t rx_config_mutex;
|
||||||
|
pthread_mutex_t decim_mutex;
|
||||||
|
pthread_mutex_t rx_gain_mutex;
|
||||||
|
} rf_file_handler_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Static methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void update_rates(rf_file_handler_t* handler, double srate);
|
||||||
|
|
||||||
|
void rf_file_info(char* id, const char* format, ...)
|
||||||
|
{
|
||||||
|
#if VERBOSE
|
||||||
|
struct timeval t;
|
||||||
|
gettimeofday(&t, NULL);
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
printf("[%s@%02ld.%06ld] ", id ? id : "file", t.tv_sec % 10, t.tv_usec);
|
||||||
|
vprintf(format, args);
|
||||||
|
va_end(args);
|
||||||
|
#else /* VERBOSE */
|
||||||
|
// Do nothing
|
||||||
|
#endif /* VERBOSE */
|
||||||
|
}
|
||||||
|
|
||||||
|
void rf_file_error(char* id, const char* format, ...)
|
||||||
|
{
|
||||||
|
struct timeval t;
|
||||||
|
gettimeofday(&t, NULL);
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
vfprintf(stderr, format, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int update_ts(void* h, uint64_t* ts, int nsamples, const char* dir)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
|
||||||
|
if (h && nsamples > 0) {
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
|
||||||
|
(*ts) += nsamples;
|
||||||
|
|
||||||
|
srsran_timestamp_t _ts = {};
|
||||||
|
srsran_timestamp_init_uint64(&_ts, *ts, handler->base_srate);
|
||||||
|
rf_file_info(
|
||||||
|
handler->id, " -> next %s time after %d samples: %d + %.3f\n", dir, nsamples, _ts.full_secs, _ts.frac_secs);
|
||||||
|
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_handle_error(char* id, const char* text)
|
||||||
|
{
|
||||||
|
// Not implemented
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Public methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
const char* rf_file_devname(void* h)
|
||||||
|
{
|
||||||
|
return DEVNAME_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_start_rx_stream(void* h, bool now)
|
||||||
|
{
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_stop_rx_stream(void* h)
|
||||||
|
{
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rf_file_flush_buffer(void* h)
|
||||||
|
{
|
||||||
|
printf("%s\n", __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rf_file_has_rssi(void* h)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float rf_file_get_rssi(void* h)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rf_file_suppress_stdout(void* h)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
void rf_file_register_error_handler(void* h, srsran_rf_error_handler_t error_handler, void* arg)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_open(char* args, void** h)
|
||||||
|
{
|
||||||
|
return rf_file_open_multi(args, h, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_open_multi(char* args, void** h, uint32_t nof_channels)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
|
||||||
|
FILE* rx_files[SRSRAN_MAX_CHANNELS] = {NULL};
|
||||||
|
FILE* tx_files[SRSRAN_MAX_CHANNELS] = {NULL};
|
||||||
|
|
||||||
|
if (h && nof_channels <= SRSRAN_MAX_CHANNELS) {
|
||||||
|
uint32_t base_srate = FILE_BASERATE_DEFAULT_HZ;
|
||||||
|
|
||||||
|
// parse args
|
||||||
|
if (args && strlen(args)) {
|
||||||
|
// base_srate
|
||||||
|
parse_uint32(args, "base_srate", -1, &base_srate);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "[file] Error: RF device args are required for file-based no-RF module\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < nof_channels; i++) {
|
||||||
|
// rx_file
|
||||||
|
char rx_file[RF_PARAM_LEN] = {};
|
||||||
|
parse_string(args, "rx_file", i, rx_file);
|
||||||
|
|
||||||
|
// tx_file
|
||||||
|
char tx_file[RF_PARAM_LEN] = {};
|
||||||
|
parse_string(args, "tx_file", i, tx_file);
|
||||||
|
|
||||||
|
// initialize transmitter
|
||||||
|
if (strlen(tx_file) != 0) {
|
||||||
|
tx_files[i] = fopen(tx_file, "wb");
|
||||||
|
if (tx_files[i] == NULL) {
|
||||||
|
fprintf(stderr, "[file] Error: opening tx_file%d: %s; %s\n", i, tx_file, strerror(errno));
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize receiver
|
||||||
|
if (strlen(rx_file) != 0) {
|
||||||
|
rx_files[i] = fopen(rx_file, "rb");
|
||||||
|
if (rx_files[i] == NULL) {
|
||||||
|
fprintf(stderr, "[file] Error: opening rx_file%d: %s; %s\n", i, rx_file, strerror(errno));
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defer further initialization to open_file method
|
||||||
|
ret = rf_file_open_file(h, rx_files, tx_files, nof_channels, base_srate);
|
||||||
|
if (ret != SRSRAN_SUCCESS) {
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add flag to close all files when closing device
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)(*h);
|
||||||
|
handler->close_files = true;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
for (int i = 0; i < nof_channels; i++) {
|
||||||
|
if (rx_files[i] != NULL) {
|
||||||
|
fclose(rx_files[i]);
|
||||||
|
}
|
||||||
|
if (tx_files[i] != NULL) {
|
||||||
|
fclose(tx_files[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_open_file(void** h, FILE** rx_files, FILE** tx_files, uint32_t nof_channels, uint32_t base_srate)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
|
||||||
|
if (h) {
|
||||||
|
*h = NULL;
|
||||||
|
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)malloc(sizeof(rf_file_handler_t));
|
||||||
|
if (!handler) {
|
||||||
|
fprintf(stderr, "malloc: %s\n", strerror(errno));
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
memset(handler, 0, sizeof(rf_file_handler_t));
|
||||||
|
*h = handler;
|
||||||
|
handler->base_srate = base_srate;
|
||||||
|
handler->info.max_rx_gain = FILE_MAX_GAIN_DB;
|
||||||
|
handler->info.min_rx_gain = FILE_MIN_GAIN_DB;
|
||||||
|
handler->info.max_tx_gain = FILE_MAX_GAIN_DB;
|
||||||
|
handler->info.min_tx_gain = FILE_MIN_GAIN_DB;
|
||||||
|
handler->nof_channels = nof_channels;
|
||||||
|
strcpy(handler->id, "file\0");
|
||||||
|
|
||||||
|
rf_file_opts_t rx_opts = {};
|
||||||
|
rf_file_opts_t tx_opts = {};
|
||||||
|
tx_opts.id = handler->id;
|
||||||
|
rx_opts.id = handler->id;
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&handler->tx_config_mutex, NULL)) {
|
||||||
|
fprintf(stderr, "Mutex init: %s\n", strerror(errno));
|
||||||
|
}
|
||||||
|
if (pthread_mutex_init(&handler->rx_config_mutex, NULL)) {
|
||||||
|
fprintf(stderr, "Mutex init: %s\n", strerror(errno));
|
||||||
|
}
|
||||||
|
if (pthread_mutex_init(&handler->decim_mutex, NULL)) {
|
||||||
|
fprintf(stderr, "Mutex init: %s\n", strerror(errno));
|
||||||
|
}
|
||||||
|
if (pthread_mutex_init(&handler->rx_gain_mutex, NULL)) {
|
||||||
|
fprintf(stderr, "Mutex init: %s\n", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&handler->rx_gain_mutex);
|
||||||
|
handler->rx_gain = 0.0;
|
||||||
|
pthread_mutex_unlock(&handler->rx_gain_mutex);
|
||||||
|
|
||||||
|
// id
|
||||||
|
// TODO: set some meaningful ID in handler->id
|
||||||
|
|
||||||
|
// rx_format, tx_format
|
||||||
|
// TODO: add other formats
|
||||||
|
rx_opts.sample_format = FILERF_TYPE_FC32;
|
||||||
|
tx_opts.sample_format = FILERF_TYPE_FC32;
|
||||||
|
|
||||||
|
update_rates(handler, 1.92e6);
|
||||||
|
|
||||||
|
// Create channels
|
||||||
|
for (int i = 0; i < handler->nof_channels; i++) {
|
||||||
|
if (rx_files != NULL && rx_files[i] != NULL) {
|
||||||
|
rx_opts.file = rx_files[i];
|
||||||
|
if (rf_file_rx_open(&handler->receiver[i], rx_opts) != SRSRAN_SUCCESS) {
|
||||||
|
fprintf(stderr, "[file] Error: opening receiver\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no rx_files provided
|
||||||
|
fprintf(stdout, "[file] %s rx channel %d not specified. Disabling receiver.\n", handler->id, i);
|
||||||
|
}
|
||||||
|
if (tx_files != NULL && tx_files[i] != NULL) {
|
||||||
|
tx_opts.file = tx_files[i];
|
||||||
|
if (rf_file_tx_open(&handler->transmitter[i], tx_opts) != SRSRAN_SUCCESS) {
|
||||||
|
fprintf(stderr, "[file] Error: opening transmitter\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no tx_files provided
|
||||||
|
fprintf(stdout, "[file] %s tx channel %d not specified. Disabling transmitter.\n", handler->id, i);
|
||||||
|
handler->tx_off = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handler->transmitter[i].running && !handler->receiver[i].running) {
|
||||||
|
fprintf(stderr, "[file] Error: Neither tx nor rx specificed for channel %d.\n", i);
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create decimation and overflow buffer
|
||||||
|
for (uint32_t i = 0; i < handler->nof_channels; i++) {
|
||||||
|
handler->buffer_decimation[i] = srsran_vec_malloc(FILE_MAX_BUFFER_SIZE);
|
||||||
|
if (!handler->buffer_decimation[i]) {
|
||||||
|
fprintf(stderr, "Error: allocating decimation buffer\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler->buffer_tx = srsran_vec_malloc(FILE_MAX_BUFFER_SIZE);
|
||||||
|
if (!handler->buffer_tx) {
|
||||||
|
fprintf(stderr, "Error: allocating tx buffer\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
if (ret) {
|
||||||
|
rf_file_close(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_close(void* h)
|
||||||
|
{
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
|
||||||
|
rf_file_info(handler->id, "Closing ...\n");
|
||||||
|
|
||||||
|
// close receiver+transmitter and release related resources (except for the file handles)
|
||||||
|
for (int i = 0; i < handler->nof_channels; i++) {
|
||||||
|
rf_file_tx_close(&handler->transmitter[i]);
|
||||||
|
rf_file_rx_close(&handler->receiver[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// release other resources
|
||||||
|
for (uint32_t i = 0; i < handler->nof_channels; i++) {
|
||||||
|
if (handler->buffer_decimation[i]) {
|
||||||
|
free(handler->buffer_decimation[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler->buffer_tx) {
|
||||||
|
free(handler->buffer_tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&handler->tx_config_mutex);
|
||||||
|
pthread_mutex_destroy(&handler->rx_config_mutex);
|
||||||
|
pthread_mutex_destroy(&handler->decim_mutex);
|
||||||
|
pthread_mutex_destroy(&handler->rx_gain_mutex);
|
||||||
|
|
||||||
|
// now close the files if we opened them ourselves
|
||||||
|
if (handler->close_files) {
|
||||||
|
for (int i = 0; i < handler->nof_channels; i++) {
|
||||||
|
if (handler->receiver[i].file != NULL) {
|
||||||
|
fclose(handler->receiver[i].file);
|
||||||
|
}
|
||||||
|
if (handler->transmitter[i].file != NULL) {
|
||||||
|
fclose(handler->transmitter[i].file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free all
|
||||||
|
free(handler);
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_rates(rf_file_handler_t* handler, double srate)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&handler->decim_mutex);
|
||||||
|
if (handler) {
|
||||||
|
// Decimation must be full integer
|
||||||
|
if (((uint64_t)handler->base_srate % (uint64_t)srate) == 0) {
|
||||||
|
handler->srate = (uint32_t)srate;
|
||||||
|
handler->decim_factor = handler->base_srate / handler->srate;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Error: couldn't update sample rate. %.2f is not divisible by %.2f\n",
|
||||||
|
srate / 1e6,
|
||||||
|
handler->base_srate / 1e6);
|
||||||
|
}
|
||||||
|
printf("Current sample rate is %.2f MHz with a base rate of %.2f MHz (x%d decimation)\n",
|
||||||
|
handler->srate / 1e6,
|
||||||
|
handler->base_srate / 1e6,
|
||||||
|
handler->decim_factor);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&handler->decim_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
double rf_file_set_rx_srate(void* h, double srate)
|
||||||
|
{
|
||||||
|
double ret = 0.0;
|
||||||
|
if (h) {
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
update_rates(handler, srate);
|
||||||
|
ret = handler->srate;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_set_rx_gain(void* h, double gain)
|
||||||
|
{
|
||||||
|
double ret = 0.0;
|
||||||
|
if (h) {
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
handler->rx_gain = gain;
|
||||||
|
ret = gain;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_set_rx_gain_ch(void* h, uint32_t ch, double gain)
|
||||||
|
{
|
||||||
|
return rf_file_set_rx_gain(h, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_set_tx_gain(void* h, double gain)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_set_tx_gain_ch(void* h, uint32_t ch, double gain)
|
||||||
|
{
|
||||||
|
return rf_file_set_tx_gain(h, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
double rf_file_get_rx_gain(void* h)
|
||||||
|
{
|
||||||
|
double ret = 0.0;
|
||||||
|
if (h) {
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
ret = handler->rx_gain;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
double rf_file_get_tx_gain(void* h)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_rf_info_t* rf_file_get_info(void* h)
|
||||||
|
{
|
||||||
|
srsran_rf_info_t* info = NULL;
|
||||||
|
if (h) {
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
info = &handler->info;
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
double rf_file_set_rx_freq(void* h, uint32_t ch, double freq)
|
||||||
|
{
|
||||||
|
double ret = NAN;
|
||||||
|
if (h) {
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
pthread_mutex_lock(&handler->rx_config_mutex);
|
||||||
|
if (ch < handler->nof_channels && isnormal(freq) && freq > 0.0) {
|
||||||
|
handler->rx_freq_mhz[ch] = (uint32_t)(freq / 1e6);
|
||||||
|
ret = freq;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&handler->rx_config_mutex);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
double rf_file_set_tx_srate(void* h, double srate)
|
||||||
|
{
|
||||||
|
double ret = 0.0;
|
||||||
|
if (h) {
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
update_rates(handler, srate);
|
||||||
|
ret = srate;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
double rf_file_set_tx_freq(void* h, uint32_t ch, double freq)
|
||||||
|
{
|
||||||
|
double ret = NAN;
|
||||||
|
if (h) {
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
pthread_mutex_lock(&handler->tx_config_mutex);
|
||||||
|
if (ch < handler->nof_channels && isnormal(freq) && freq > 0.0) {
|
||||||
|
handler->tx_freq_mhz[ch] = (uint32_t)(freq / 1e6);
|
||||||
|
ret = freq;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&handler->tx_config_mutex);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rf_file_get_time(void* h, time_t* secs, double* frac_secs)
|
||||||
|
{
|
||||||
|
if (h) {
|
||||||
|
if (secs) {
|
||||||
|
*secs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frac_secs) {
|
||||||
|
*frac_secs = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs)
|
||||||
|
{
|
||||||
|
return rf_file_recv_with_time_multi(h, &data, nsamples, blocking, secs, frac_secs);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_recv_with_time_multi(void* h,
|
||||||
|
void** data,
|
||||||
|
uint32_t nsamples,
|
||||||
|
bool blocking,
|
||||||
|
time_t* secs,
|
||||||
|
double* frac_secs)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
|
||||||
|
if (h) {
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
|
||||||
|
// Map ports to data buffers according to the selected frequencies
|
||||||
|
pthread_mutex_lock(&handler->rx_config_mutex);
|
||||||
|
bool mapped[SRSRAN_MAX_CHANNELS] = {}; // Mapped mask, set to true when the physical channel is used
|
||||||
|
cf_t* buffers[SRSRAN_MAX_CHANNELS] = {}; // Buffer pointers, NULL if unmatched
|
||||||
|
|
||||||
|
// For each logical channel...
|
||||||
|
for (uint32_t logical = 0; logical < handler->nof_channels; logical++) {
|
||||||
|
bool unmatched = true;
|
||||||
|
|
||||||
|
// For each physical channel...
|
||||||
|
for (uint32_t physical = 0; physical < handler->nof_channels; physical++) {
|
||||||
|
// Consider a match if the physical channel is NOT mapped and the frequency match
|
||||||
|
if (!mapped[physical] && rf_file_rx_match_freq(&handler->receiver[physical], handler->rx_freq_mhz[logical])) {
|
||||||
|
// Not mapped and matched frequency with receiver
|
||||||
|
buffers[physical] = (cf_t*)data[logical];
|
||||||
|
mapped[physical] = true;
|
||||||
|
unmatched = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no matching frequency found; set data to zeros
|
||||||
|
if (unmatched) {
|
||||||
|
srsran_vec_zero(data[logical], nsamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&handler->rx_config_mutex);
|
||||||
|
|
||||||
|
// Protect the access to decim_factor since is a shared variable
|
||||||
|
pthread_mutex_lock(&handler->decim_mutex);
|
||||||
|
uint32_t decim_factor = handler->decim_factor;
|
||||||
|
pthread_mutex_unlock(&handler->decim_mutex);
|
||||||
|
|
||||||
|
uint32_t nbytes = NSAMPLES2NBYTES(nsamples * decim_factor);
|
||||||
|
uint32_t nsamples_baserate = nsamples * decim_factor;
|
||||||
|
|
||||||
|
rf_file_info(handler->id, "Rx %d samples (%d B)\n", nsamples, nbytes);
|
||||||
|
|
||||||
|
// set timestamp for this reception
|
||||||
|
if (secs != NULL && frac_secs != NULL) {
|
||||||
|
srsran_timestamp_t ts = {};
|
||||||
|
srsran_timestamp_init_uint64(&ts, handler->next_rx_ts, handler->base_srate);
|
||||||
|
*secs = ts.full_secs;
|
||||||
|
*frac_secs = ts.frac_secs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return if receiver is turned off
|
||||||
|
if (!handler->receiver[0].running) {
|
||||||
|
update_ts(handler, &handler->next_rx_ts, nsamples_baserate, "rx");
|
||||||
|
return nsamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check available buffer size
|
||||||
|
if (nbytes > FILE_MAX_BUFFER_SIZE) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"[file] Error: Trying to receive %d B but buffer is only %zu B at channel %d.\n",
|
||||||
|
nbytes,
|
||||||
|
FILE_MAX_BUFFER_SIZE,
|
||||||
|
0);
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// receive samples
|
||||||
|
srsran_timestamp_t ts_tx = {}, ts_rx = {};
|
||||||
|
srsran_timestamp_init_uint64(&ts_tx, handler->transmitter[0].nsamples, handler->base_srate);
|
||||||
|
srsran_timestamp_init_uint64(&ts_rx, handler->next_rx_ts, handler->base_srate);
|
||||||
|
rf_file_info(handler->id, " - next rx time: %d + %.3f\n", ts_rx.full_secs, ts_rx.frac_secs);
|
||||||
|
rf_file_info(handler->id, " - next tx time: %d + %.3f\n", ts_tx.full_secs, ts_tx.frac_secs);
|
||||||
|
|
||||||
|
// check for tx gap if we're also transmitting on this radio
|
||||||
|
for (int i = 0; i < handler->nof_channels; i++) {
|
||||||
|
if (handler->transmitter[i].running) {
|
||||||
|
rf_file_tx_align(&handler->transmitter[i], handler->next_rx_ts + nsamples_baserate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy from rx buffer as many samples as requested into provided buffer
|
||||||
|
bool completed = false;
|
||||||
|
int32_t count[SRSRAN_MAX_CHANNELS] = {};
|
||||||
|
while (!completed) {
|
||||||
|
uint32_t completed_count = 0;
|
||||||
|
|
||||||
|
// Iterate channels
|
||||||
|
for (uint32_t i = 0; i < handler->nof_channels; i++) {
|
||||||
|
cf_t* ptr = (decim_factor != 1 || buffers[i] == NULL) ? handler->buffer_decimation[i] : buffers[i];
|
||||||
|
|
||||||
|
// Completed condition
|
||||||
|
if (count[i] < nsamples_baserate && handler->receiver[i].running) {
|
||||||
|
// Keep receiving
|
||||||
|
int32_t n = rf_file_rx_baseband(&handler->receiver[i], &ptr[count[i]], nsamples_baserate - count[i]);
|
||||||
|
if (n > 0) {
|
||||||
|
// No error
|
||||||
|
count[i] += n;
|
||||||
|
} else {
|
||||||
|
if (n != SRSRAN_ERROR_RX_EOF) {
|
||||||
|
// Other error, exit
|
||||||
|
fprintf(stderr, "Error: receiving data.\n");
|
||||||
|
}
|
||||||
|
ret = n;
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Completed, count it
|
||||||
|
completed_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all channels are completed
|
||||||
|
completed = (completed_count == handler->nof_channels);
|
||||||
|
}
|
||||||
|
rf_file_info(handler->id, " - read %d samples.\n", NBYTES2NSAMPLES(nbytes));
|
||||||
|
|
||||||
|
// decimate if needed
|
||||||
|
if (decim_factor != 1) {
|
||||||
|
for (uint32_t c = 0; c < handler->nof_channels; c++) {
|
||||||
|
// skip if buffer is not available
|
||||||
|
if (buffers[c]) {
|
||||||
|
cf_t* dst = buffers[c];
|
||||||
|
cf_t* ptr = handler->buffer_decimation[c];
|
||||||
|
|
||||||
|
for (uint32_t i = 0, n = 0; i < nsamples; i++) {
|
||||||
|
// Averaging decimation
|
||||||
|
cf_t avg = 0.0f;
|
||||||
|
for (int j = 0; j < decim_factor; j++, n++) {
|
||||||
|
avg += ptr[n];
|
||||||
|
}
|
||||||
|
dst[i] = avg; // divide by decim_factor later via scale
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_file_info(handler->id,
|
||||||
|
" - re-adjust bytes due to %dx decimation %d --> %d samples)\n",
|
||||||
|
decim_factor,
|
||||||
|
nsamples_baserate,
|
||||||
|
nsamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set gain
|
||||||
|
pthread_mutex_lock(&handler->rx_gain_mutex);
|
||||||
|
float scale = srsran_convert_dB_to_amplitude(handler->rx_gain);
|
||||||
|
pthread_mutex_unlock(&handler->rx_gain_mutex);
|
||||||
|
// scale shall also incorporate decim_factor
|
||||||
|
scale = scale / decim_factor;
|
||||||
|
for (uint32_t c = 0; c < handler->nof_channels; c++) {
|
||||||
|
if (buffers[c]) {
|
||||||
|
srsran_vec_sc_prod_cfc(buffers[c], scale, buffers[c], nsamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update rx time
|
||||||
|
update_ts(handler, &handler->next_rx_ts, nsamples_baserate, "rx");
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = nsamples;
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_send_timed(void* h,
|
||||||
|
void* data,
|
||||||
|
int nsamples,
|
||||||
|
time_t secs,
|
||||||
|
double frac_secs,
|
||||||
|
bool has_time_spec,
|
||||||
|
bool blocking,
|
||||||
|
bool is_start_of_burst,
|
||||||
|
bool is_end_of_burst)
|
||||||
|
{
|
||||||
|
void* _data[4] = {data, NULL, NULL, NULL};
|
||||||
|
|
||||||
|
return rf_file_send_timed_multi(
|
||||||
|
h, _data, nsamples, secs, frac_secs, has_time_spec, blocking, is_start_of_burst, is_end_of_burst);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_send_timed_multi(void* h,
|
||||||
|
void* data[4],
|
||||||
|
int nsamples,
|
||||||
|
time_t secs,
|
||||||
|
double frac_secs,
|
||||||
|
bool has_time_spec,
|
||||||
|
bool blocking,
|
||||||
|
bool is_start_of_burst,
|
||||||
|
bool is_end_of_burst)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
|
||||||
|
if (h && data && nsamples > 0) {
|
||||||
|
rf_file_handler_t* handler = (rf_file_handler_t*)h;
|
||||||
|
|
||||||
|
// Map ports to data buffers according to the selected frequencies
|
||||||
|
pthread_mutex_lock(&handler->tx_config_mutex);
|
||||||
|
bool mapped[SRSRAN_MAX_CHANNELS] = {}; // Mapped mask, set to true when the physical channel is used
|
||||||
|
cf_t* buffers[SRSRAN_MAX_CHANNELS] = {}; // Buffer pointers, NULL if unmatched or zero transmission
|
||||||
|
|
||||||
|
// For each logical channel...
|
||||||
|
for (uint32_t logical = 0; logical < handler->nof_channels; logical++) {
|
||||||
|
// For each physical channel...
|
||||||
|
for (uint32_t physical = 0; physical < handler->nof_channels; physical++) {
|
||||||
|
// Consider a match if the physical channel is NOT mapped and the frequency match
|
||||||
|
if (!mapped[physical] &&
|
||||||
|
rf_file_tx_match_freq(&handler->transmitter[physical], handler->tx_freq_mhz[logical])) {
|
||||||
|
// Not mapped and matched frequency with receiver
|
||||||
|
buffers[physical] = (cf_t*)data[logical];
|
||||||
|
mapped[physical] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&handler->tx_config_mutex);
|
||||||
|
|
||||||
|
// Protect the access to decim_factor since is a shared variable
|
||||||
|
pthread_mutex_lock(&handler->decim_mutex);
|
||||||
|
uint32_t decim_factor = handler->decim_factor;
|
||||||
|
pthread_mutex_unlock(&handler->decim_mutex);
|
||||||
|
|
||||||
|
uint32_t nbytes = NSAMPLES2NBYTES(nsamples);
|
||||||
|
uint32_t nsamples_baseband = nsamples * decim_factor;
|
||||||
|
uint32_t nbytes_baseband = NSAMPLES2NBYTES(nsamples_baseband);
|
||||||
|
if (nbytes_baseband > FILE_MAX_BUFFER_SIZE) {
|
||||||
|
fprintf(stderr, "Error: trying to transmit too many samples (%d > %zu).\n", nbytes, FILE_MAX_BUFFER_SIZE);
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_file_info(handler->id, "Tx %d samples (%d B)\n", nsamples, nbytes);
|
||||||
|
|
||||||
|
// return if transmitter is switched off
|
||||||
|
if (handler->tx_off) {
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if this is a tx in the future
|
||||||
|
if (has_time_spec) {
|
||||||
|
rf_file_info(handler->id, " - tx time: %d + %.3f\n", secs, frac_secs);
|
||||||
|
|
||||||
|
srsran_timestamp_t ts = {};
|
||||||
|
srsran_timestamp_init(&ts, secs, frac_secs);
|
||||||
|
uint64_t tx_ts = srsran_timestamp_uint64(&ts, handler->base_srate);
|
||||||
|
int num_tx_gap_samples = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < handler->nof_channels; i++) {
|
||||||
|
if (handler->transmitter[i].running) {
|
||||||
|
num_tx_gap_samples = rf_file_tx_align(&handler->transmitter[i], tx_ts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_tx_gap_samples < 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"[file] Error: tx time is %.3f ms in the past (%" PRIu64 " < %" PRIu64 ")\n",
|
||||||
|
-1000.0 * num_tx_gap_samples / handler->base_srate,
|
||||||
|
tx_ts,
|
||||||
|
handler->transmitter[0].nsamples);
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send base-band samples
|
||||||
|
for (int i = 0; i < handler->nof_channels; i++) {
|
||||||
|
if (buffers[i] != NULL) {
|
||||||
|
// Select buffer pointer depending on interpolation
|
||||||
|
cf_t* buf = (decim_factor != 1) ? handler->buffer_tx : buffers[i];
|
||||||
|
|
||||||
|
// Interpolate if required
|
||||||
|
if (decim_factor != 1) {
|
||||||
|
rf_file_info(handler->id,
|
||||||
|
" - re-adjust bytes due to %dx interpolation %d --> %d samples)\n",
|
||||||
|
decim_factor,
|
||||||
|
nsamples,
|
||||||
|
nsamples_baseband);
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
cf_t* src = buffers[i];
|
||||||
|
for (int k = 0; k < nsamples; k++) {
|
||||||
|
// perform zero order hold
|
||||||
|
for (int j = 0; j < decim_factor; j++, n++) {
|
||||||
|
buf[n] = src[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nsamples_baseband != n) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Number of tx samples (%d) does not match with number of interpolated samples (%d)\n",
|
||||||
|
nsamples_baseband,
|
||||||
|
n);
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = rf_file_tx_baseband(&handler->transmitter[i], buf, nsamples_baseband);
|
||||||
|
if (n == SRSRAN_ERROR) {
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int n = rf_file_tx_zeros(&handler->transmitter[i], nsamples_baseband);
|
||||||
|
if (n == SRSRAN_ERROR) {
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \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_RF_FILE_IMP_H
|
||||||
|
#define SRSRAN_RF_FILE_IMP_H
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "srsran/config.h"
|
||||||
|
#include "srsran/phy/common/phy_common.h"
|
||||||
|
#include "srsran/phy/rf/rf.h"
|
||||||
|
|
||||||
|
#define DEVNAME_FILE "file"
|
||||||
|
#define PARAM_LEN (128)
|
||||||
|
#define PARAM_LEN_SHORT (PARAM_LEN / 2)
|
||||||
|
|
||||||
|
SRSRAN_API const char* rf_file_devname(void* h);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_start_rx_stream(void* h, bool now);
|
||||||
|
|
||||||
|
// SRSRAN_API int rf_file_start_rx_stream_nsamples(void* h, uint32_t nsamples);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_stop_rx_stream(void* h);
|
||||||
|
|
||||||
|
SRSRAN_API void rf_file_flush_buffer(void* h);
|
||||||
|
|
||||||
|
SRSRAN_API bool rf_file_has_rssi(void* h);
|
||||||
|
|
||||||
|
SRSRAN_API float rf_file_get_rssi(void* h);
|
||||||
|
|
||||||
|
SRSRAN_API void rf_file_suppress_stdout(void* h);
|
||||||
|
|
||||||
|
SRSRAN_API void rf_file_register_error_handler(void* h, srsran_rf_error_handler_t error_handler, void* arg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This function is not supported for file-based RF abstraction
|
||||||
|
*
|
||||||
|
* Use @c rf_file_open_file() to open this device
|
||||||
|
*
|
||||||
|
* @param args not used
|
||||||
|
* @param h not used
|
||||||
|
* @return SRSRAN_ERROR_INVALID_COMMAND
|
||||||
|
*/
|
||||||
|
SRSRAN_API int rf_file_open(char* args, void** h);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This function is not supported for file-based RF abstraction
|
||||||
|
*
|
||||||
|
* Use @c rf_file_open_file() to open this device
|
||||||
|
*
|
||||||
|
* @param args not used
|
||||||
|
* @param h not used
|
||||||
|
* @param nof_channels not used
|
||||||
|
* @return SRSRAN_ERROR_INVALID_COMMAND
|
||||||
|
*/
|
||||||
|
SRSRAN_API int rf_file_open_multi(char* args, void** h, uint32_t nof_channels);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_close(void* h);
|
||||||
|
|
||||||
|
SRSRAN_API double rf_file_set_rx_srate(void* h, double srate);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_set_rx_gain(void* h, double gain);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_set_rx_gain_ch(void* h, uint32_t ch, double gain);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_set_tx_gain(void* h, double gain);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_set_tx_gain_ch(void* h, uint32_t ch, double gain);
|
||||||
|
|
||||||
|
SRSRAN_API double rf_file_get_rx_gain(void* h);
|
||||||
|
|
||||||
|
SRSRAN_API double rf_file_get_tx_gain(void* h);
|
||||||
|
|
||||||
|
SRSRAN_API srsran_rf_info_t* rf_file_get_info(void* h);
|
||||||
|
|
||||||
|
SRSRAN_API double rf_file_set_rx_freq(void* h, uint32_t ch, double freq);
|
||||||
|
|
||||||
|
SRSRAN_API double rf_file_set_tx_srate(void* h, double srate);
|
||||||
|
|
||||||
|
SRSRAN_API double rf_file_set_tx_freq(void* h, uint32_t ch, double freq);
|
||||||
|
|
||||||
|
SRSRAN_API void rf_file_get_time(void* h, time_t* secs, double* frac_secs);
|
||||||
|
|
||||||
|
// srsran_rf_sync_pps
|
||||||
|
|
||||||
|
SRSRAN_API int
|
||||||
|
rf_file_recv_with_time(void* h, void* data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs);
|
||||||
|
|
||||||
|
SRSRAN_API int
|
||||||
|
rf_file_recv_with_time_multi(void* h, void** data, uint32_t nsamples, bool blocking, time_t* secs, double* frac_secs);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_send_timed(void* h,
|
||||||
|
void* data,
|
||||||
|
int nsamples,
|
||||||
|
time_t secs,
|
||||||
|
double frac_secs,
|
||||||
|
bool has_time_spec,
|
||||||
|
bool blocking,
|
||||||
|
bool is_start_of_burst,
|
||||||
|
bool is_end_of_burst);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_send_timed_multi(void* h,
|
||||||
|
void* data[4],
|
||||||
|
int nsamples,
|
||||||
|
time_t secs,
|
||||||
|
double frac_secs,
|
||||||
|
bool has_time_spec,
|
||||||
|
bool blocking,
|
||||||
|
bool is_start_of_burst,
|
||||||
|
bool is_end_of_burst);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dedicated function to open a file-based RF abstraction
|
||||||
|
* @param[out] h Resulting object handle
|
||||||
|
* @param[in] rx_files List of pre-opened FILE* for each RX channel; NULL to disable
|
||||||
|
* @param[in] tx_files List of pre-opened FILE* for each TX channel; NULL to disable
|
||||||
|
* @param[in] nof_channels Number of channels per direction
|
||||||
|
* @param[in] base_srate Sample rate of RX and TX files
|
||||||
|
* @return SRSRAN_SUCCESS on success, otherwise error code
|
||||||
|
*/
|
||||||
|
SRSRAN_API int rf_file_open_file(void** h, FILE** rx_files, FILE** tx_files, uint32_t nof_channels, uint32_t base_srate);
|
||||||
|
|
||||||
|
#endif // SRSRAN_RF_FILE_IMP_H
|
@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \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 "rf_file_imp_trx.h"
|
||||||
|
#include <srsran/phy/utils/vector.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int rf_file_rx_open(rf_file_rx_t* q, rf_file_opts_t opts)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
|
||||||
|
if (q) {
|
||||||
|
// Zero object
|
||||||
|
memset(q, 0, sizeof(rf_file_rx_t));
|
||||||
|
|
||||||
|
// Copy id
|
||||||
|
strncpy(q->id, opts.id, FILE_ID_STRLEN - 1);
|
||||||
|
q->id[FILE_ID_STRLEN - 1] = '\0';
|
||||||
|
|
||||||
|
// Assign file
|
||||||
|
q->file = opts.file;
|
||||||
|
|
||||||
|
// Configure formats
|
||||||
|
q->sample_format = opts.sample_format;
|
||||||
|
q->frequency_mhz = opts.frequency_mhz;
|
||||||
|
|
||||||
|
q->temp_buffer = srsran_vec_malloc(FILE_MAX_BUFFER_SIZE);
|
||||||
|
if (!q->temp_buffer) {
|
||||||
|
fprintf(stderr, "Error: allocating rx buffer\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
q->temp_buffer_convert = srsran_vec_malloc(FILE_MAX_BUFFER_SIZE);
|
||||||
|
if (!q->temp_buffer_convert) {
|
||||||
|
fprintf(stderr, "Error: allocating rx buffer\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&q->mutex, NULL)) {
|
||||||
|
fprintf(stderr, "Error: creating mutex\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
q->running = true;
|
||||||
|
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_rx_baseband(rf_file_rx_t* q, cf_t* buffer, uint32_t nsamples)
|
||||||
|
{
|
||||||
|
uint32_t sample_sz = sizeof(cf_t);
|
||||||
|
|
||||||
|
int ret = fread(buffer, sample_sz, nsamples, q->file);
|
||||||
|
if (ret > 0) {
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
return SRSRAN_ERROR_RX_EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rf_file_rx_match_freq(rf_file_rx_t* q, uint32_t freq_hz)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
if (q) {
|
||||||
|
ret = (q->frequency_mhz == 0 || q->frequency_mhz == freq_hz);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rf_file_rx_close(rf_file_rx_t* q)
|
||||||
|
{
|
||||||
|
rf_file_info(q->id, "Closing ...\n");
|
||||||
|
q->running = false;
|
||||||
|
|
||||||
|
if (q->temp_buffer) {
|
||||||
|
free(q->temp_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q->temp_buffer_convert) {
|
||||||
|
free(q->temp_buffer_convert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// not touching q->file as we don't know if we need to close it ourselves
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \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_RF_FILE_IMP_TRX_H
|
||||||
|
#define SRSRAN_RF_FILE_IMP_TRX_H
|
||||||
|
|
||||||
|
#include "srsran/config.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Definitions */
|
||||||
|
#define VERBOSE (0)
|
||||||
|
#define NSAMPLES2NBYTES(X) (((uint32_t)(X)) * sizeof(cf_t))
|
||||||
|
#define NBYTES2NSAMPLES(X) ((X) / sizeof(cf_t))
|
||||||
|
#define FILE_MAX_BUFFER_SIZE (NSAMPLES2NBYTES(3072000)) // 10 subframes at 20 MHz
|
||||||
|
#define FILE_TIMEOUT_MS (1000)
|
||||||
|
#define FILE_BASERATE_DEFAULT_HZ (23040000)
|
||||||
|
#define FILE_ID_STRLEN 16
|
||||||
|
#define FILE_MAX_GAIN_DB (30.0f)
|
||||||
|
#define FILE_MIN_GAIN_DB (0.0f)
|
||||||
|
|
||||||
|
typedef enum { FILERF_TYPE_FC32 = 0, FILERF_TYPE_SC16 } rf_file_format_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char id[FILE_ID_STRLEN];
|
||||||
|
rf_file_format_t sample_format;
|
||||||
|
FILE* file;
|
||||||
|
uint64_t nsamples;
|
||||||
|
bool running;
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
cf_t* zeros;
|
||||||
|
void* temp_buffer_convert;
|
||||||
|
uint32_t frequency_mhz;
|
||||||
|
int32_t sample_offset;
|
||||||
|
} rf_file_tx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char id[FILE_ID_STRLEN];
|
||||||
|
rf_file_format_t sample_format;
|
||||||
|
FILE* file;
|
||||||
|
uint64_t nsamples;
|
||||||
|
bool running;
|
||||||
|
pthread_t thread;
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
cf_t* temp_buffer;
|
||||||
|
void* temp_buffer_convert;
|
||||||
|
uint32_t frequency_mhz;
|
||||||
|
} rf_file_rx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char* id;
|
||||||
|
rf_file_format_t sample_format;
|
||||||
|
FILE* file;
|
||||||
|
uint32_t frequency_mhz;
|
||||||
|
} rf_file_opts_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Common functions
|
||||||
|
*/
|
||||||
|
SRSRAN_API void rf_file_info(char* id, const char* format, ...);
|
||||||
|
|
||||||
|
SRSRAN_API void rf_file_error(char* id, const char* format, ...);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_handle_error(char* id, const char* text);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transmitter functions
|
||||||
|
*/
|
||||||
|
SRSRAN_API int rf_file_tx_open(rf_file_tx_t* q, rf_file_opts_t opts);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_tx_align(rf_file_tx_t* q, uint64_t ts);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_tx_baseband(rf_file_tx_t* q, cf_t* buffer, uint32_t nsamples);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_tx_get_nsamples(rf_file_tx_t* q);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_tx_zeros(rf_file_tx_t* q, uint32_t nsamples);
|
||||||
|
|
||||||
|
SRSRAN_API bool rf_file_tx_match_freq(rf_file_tx_t* q, uint32_t freq_hz);
|
||||||
|
|
||||||
|
SRSRAN_API void rf_file_tx_close(rf_file_tx_t* q);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Receiver functions
|
||||||
|
*/
|
||||||
|
SRSRAN_API int rf_file_rx_open(rf_file_rx_t* q, rf_file_opts_t opts);
|
||||||
|
|
||||||
|
SRSRAN_API int rf_file_rx_baseband(rf_file_rx_t* q, cf_t* buffer, uint32_t nsamples);
|
||||||
|
|
||||||
|
SRSRAN_API bool rf_file_rx_match_freq(rf_file_rx_t* q, uint32_t freq_hz);
|
||||||
|
|
||||||
|
SRSRAN_API void rf_file_rx_close(rf_file_rx_t* q);
|
||||||
|
|
||||||
|
#endif // SRSRAN_RF_FILE_IMP_TRX_H
|
@ -0,0 +1,189 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \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 "rf_file_imp_trx.h"
|
||||||
|
#include <errno.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <srsran/config.h>
|
||||||
|
#include <srsran/phy/utils/vector.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int rf_file_tx_open(rf_file_tx_t* q, rf_file_opts_t opts)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
|
||||||
|
if (q) {
|
||||||
|
// Zero object
|
||||||
|
memset(q, 0, sizeof(rf_file_tx_t));
|
||||||
|
|
||||||
|
// Copy id
|
||||||
|
strncpy(q->id, opts.id, FILE_ID_STRLEN - 1);
|
||||||
|
q->id[FILE_ID_STRLEN - 1] = '\0';
|
||||||
|
|
||||||
|
// Assign file
|
||||||
|
q->file = opts.file;
|
||||||
|
|
||||||
|
// Configure formats
|
||||||
|
q->sample_format = opts.sample_format;
|
||||||
|
q->frequency_mhz = opts.frequency_mhz;
|
||||||
|
|
||||||
|
q->temp_buffer_convert = srsran_vec_malloc(FILE_MAX_BUFFER_SIZE);
|
||||||
|
if (!q->temp_buffer_convert) {
|
||||||
|
fprintf(stderr, "Error: allocating tx buffer\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&q->mutex, NULL)) {
|
||||||
|
fprintf(stderr, "Error: creating mutex\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
q->zeros = srsran_vec_malloc(FILE_MAX_BUFFER_SIZE);
|
||||||
|
if (!q->zeros) {
|
||||||
|
fprintf(stderr, "Error: allocating zeros\n");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
memset(q->zeros, 0, FILE_MAX_BUFFER_SIZE);
|
||||||
|
|
||||||
|
q->running = true;
|
||||||
|
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _rf_file_tx_baseband(rf_file_tx_t* q, cf_t* buffer, uint32_t nsamples)
|
||||||
|
{
|
||||||
|
int n = SRSRAN_ERROR;
|
||||||
|
|
||||||
|
// convert samples if necessary
|
||||||
|
void* buf = (buffer) ? buffer : q->zeros;
|
||||||
|
uint32_t sample_sz = sizeof(cf_t);
|
||||||
|
|
||||||
|
if (q->sample_format == FILERF_TYPE_SC16) {
|
||||||
|
buf = q->temp_buffer_convert;
|
||||||
|
sample_sz = 2 * sizeof(short);
|
||||||
|
srsran_vec_convert_fi((float*)buffer, INT16_MAX, (short*)q->temp_buffer_convert, 2 * nsamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ret = fwrite(buf, (size_t)sample_sz, (size_t)nsamples, q->file);
|
||||||
|
if (ret < (size_t)nsamples) {
|
||||||
|
rf_file_error(q->id,
|
||||||
|
"[file] Error: transmitter expected %d bytes and sent %d. %s.\n",
|
||||||
|
NSAMPLES2NBYTES(nsamples),
|
||||||
|
ret,
|
||||||
|
strerror(errno));
|
||||||
|
n = SRSRAN_ERROR;
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment sample counter
|
||||||
|
q->nsamples += nsamples;
|
||||||
|
n = nsamples;
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_tx_align(rf_file_tx_t* q, uint64_t ts)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&q->mutex);
|
||||||
|
|
||||||
|
int64_t nsamples = (int64_t)ts - (int64_t)q->nsamples;
|
||||||
|
|
||||||
|
if (nsamples > 0) {
|
||||||
|
rf_file_info(q->id, " - Detected Tx gap of %d samples.\n", nsamples);
|
||||||
|
_rf_file_tx_baseband(q, q->zeros, (uint32_t)nsamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&q->mutex);
|
||||||
|
|
||||||
|
return (int)nsamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_tx_baseband(rf_file_tx_t* q, cf_t* buffer, uint32_t nsamples)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&q->mutex);
|
||||||
|
|
||||||
|
if (q->sample_offset > 0) {
|
||||||
|
_rf_file_tx_baseband(q, q->zeros, (uint32_t)q->sample_offset);
|
||||||
|
q->sample_offset = 0;
|
||||||
|
} else if (q->sample_offset < 0) {
|
||||||
|
n = SRSRAN_MIN(-q->sample_offset, nsamples);
|
||||||
|
buffer += n;
|
||||||
|
nsamples -= n;
|
||||||
|
q->sample_offset += n;
|
||||||
|
if (nsamples == 0) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n = _rf_file_tx_baseband(q, buffer, nsamples);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&q->mutex);
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_tx_get_nsamples(rf_file_tx_t* q)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&q->mutex);
|
||||||
|
int ret = q->nsamples;
|
||||||
|
pthread_mutex_unlock(&q->mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rf_file_tx_zeros(rf_file_tx_t* q, uint32_t nsamples)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&q->mutex);
|
||||||
|
|
||||||
|
rf_file_info(q->id, " - Tx %d Zeros.\n", nsamples);
|
||||||
|
_rf_file_tx_baseband(q, q->zeros, (uint32_t)nsamples);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&q->mutex);
|
||||||
|
|
||||||
|
return (int)nsamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rf_file_tx_match_freq(rf_file_tx_t* q, uint32_t freq_hz)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
if (q) {
|
||||||
|
ret = (q->frequency_mhz == 0 || q->frequency_mhz == freq_hz);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rf_file_tx_close(rf_file_tx_t* q)
|
||||||
|
{
|
||||||
|
rf_file_info(q->id, "Closing ...\n");
|
||||||
|
pthread_mutex_lock(&q->mutex);
|
||||||
|
q->running = false;
|
||||||
|
pthread_mutex_unlock(&q->mutex);
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&q->mutex);
|
||||||
|
|
||||||
|
if (q->zeros) {
|
||||||
|
free(q->zeros);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q->temp_buffer_convert) {
|
||||||
|
free(q->temp_buffer_convert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// not touching q->file as we don't know if we need to close it ourselves
|
||||||
|
}
|
@ -0,0 +1,327 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \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 "rf_file_imp.h"
|
||||||
|
#include "srsran/common/tsan_options.h"
|
||||||
|
#include "srsran/phy/common/timestamp.h"
|
||||||
|
#include "srsran/phy/utils/debug.h"
|
||||||
|
#include <complex.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <srsran/phy/common/phy_common.h>
|
||||||
|
#include <srsran/phy/utils/vector.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define PRINT_SAMPLES 0
|
||||||
|
#define COMPARE_BITS 0
|
||||||
|
#define COMPARE_EPSILON (1e-6f)
|
||||||
|
#define NOF_RX_ANT 4
|
||||||
|
#define NUM_SF (500)
|
||||||
|
#define SF_LEN (1920)
|
||||||
|
#define RF_BUFFER_SIZE (SF_LEN * NUM_SF)
|
||||||
|
#define TX_OFFSET_MS (4)
|
||||||
|
|
||||||
|
static cf_t ue_rx_buffer[NOF_RX_ANT][RF_BUFFER_SIZE];
|
||||||
|
static cf_t enb_tx_buffer[NOF_RX_ANT][RF_BUFFER_SIZE];
|
||||||
|
static cf_t enb_rx_buffer[NOF_RX_ANT][RF_BUFFER_SIZE];
|
||||||
|
|
||||||
|
static srsran_rf_t ue_radio, enb_radio;
|
||||||
|
pthread_t rx_thread;
|
||||||
|
|
||||||
|
void* ue_rx_thread_function(void* args)
|
||||||
|
{
|
||||||
|
char rf_args[RF_PARAM_LEN];
|
||||||
|
strncpy(rf_args, (char*)args, RF_PARAM_LEN - 1);
|
||||||
|
rf_args[RF_PARAM_LEN - 1] = 0;
|
||||||
|
|
||||||
|
// sleep(1);
|
||||||
|
|
||||||
|
printf("opening rx device with args=%s\n", rf_args);
|
||||||
|
if (srsran_rf_open_devname(&ue_radio, "file", rf_args, NOF_RX_ANT)) {
|
||||||
|
fprintf(stderr, "Error opening rf\n");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// receive 5 subframes at once (i.e. mimic initial rx that receives one slot)
|
||||||
|
uint32_t num_slots = NUM_SF / 5;
|
||||||
|
uint32_t num_samps_per_slot = SF_LEN * 5;
|
||||||
|
uint32_t num_rxed_samps = 0;
|
||||||
|
for (uint32_t i = 0; i < num_slots; ++i) {
|
||||||
|
void* data_ptr[SRSRAN_MAX_PORTS] = {NULL};
|
||||||
|
for (uint32_t c = 0; c < NOF_RX_ANT; c++) {
|
||||||
|
data_ptr[c] = &ue_rx_buffer[c][i * num_samps_per_slot];
|
||||||
|
}
|
||||||
|
num_rxed_samps += srsran_rf_recv_with_time_multi(&ue_radio, data_ptr, num_samps_per_slot, true, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("received %d samples.\n", num_rxed_samps);
|
||||||
|
|
||||||
|
printf("closing ue rx device\n");
|
||||||
|
srsran_rf_close(&ue_radio);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enb_tx_function(const char* tx_args, bool timed_tx)
|
||||||
|
{
|
||||||
|
char rf_args[RF_PARAM_LEN];
|
||||||
|
strncpy(rf_args, tx_args, RF_PARAM_LEN - 1);
|
||||||
|
rf_args[RF_PARAM_LEN - 1] = 0;
|
||||||
|
|
||||||
|
printf("opening tx device with args=%s\n", rf_args);
|
||||||
|
if (srsran_rf_open_devname(&enb_radio, "file", rf_args, NOF_RX_ANT)) {
|
||||||
|
fprintf(stderr, "Error opening rf\n");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate random tx data
|
||||||
|
for (int c = 0; c < NOF_RX_ANT; c++) {
|
||||||
|
for (int i = 0; i < RF_BUFFER_SIZE; i++) {
|
||||||
|
enb_tx_buffer[c][i] = ((float)rand() / (float)RAND_MAX) + _Complex_I * ((float)rand() / (float)RAND_MAX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send data subframe per subframe
|
||||||
|
uint32_t num_txed_samples = 0;
|
||||||
|
|
||||||
|
// initial transmission without ts
|
||||||
|
void* data_ptr[SRSRAN_MAX_PORTS] = {NULL};
|
||||||
|
for (int c = 0; c < NOF_RX_ANT; c++) {
|
||||||
|
data_ptr[c] = &enb_tx_buffer[c][num_txed_samples];
|
||||||
|
}
|
||||||
|
int ret = srsran_rf_send_multi(&enb_radio, (void**)data_ptr, SF_LEN, true, true, false);
|
||||||
|
num_txed_samples += SF_LEN;
|
||||||
|
|
||||||
|
// from here on, all transmissions are timed relative to the last rx time
|
||||||
|
srsran_timestamp_t rx_time, tx_time;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < NUM_SF - ((timed_tx) ? TX_OFFSET_MS : 1); ++i) {
|
||||||
|
// first recv samples
|
||||||
|
for (int c = 0; c < NOF_RX_ANT; c++) {
|
||||||
|
data_ptr[c] = enb_rx_buffer[c];
|
||||||
|
}
|
||||||
|
srsran_rf_recv_with_time_multi(&enb_radio, data_ptr, SF_LEN, true, &rx_time.full_secs, &rx_time.frac_secs);
|
||||||
|
|
||||||
|
// prepare data buffer
|
||||||
|
for (int c = 0; c < NOF_RX_ANT; c++) {
|
||||||
|
data_ptr[c] = &enb_tx_buffer[c][num_txed_samples];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timed_tx) {
|
||||||
|
// timed tx relative to receive time (this will cause a cap in the rx'ed samples at the UE resulting in 3 zero
|
||||||
|
// subframes)
|
||||||
|
srsran_timestamp_copy(&tx_time, &rx_time);
|
||||||
|
srsran_timestamp_add(&tx_time, 0, TX_OFFSET_MS * 1e-3);
|
||||||
|
ret = srsran_rf_send_timed_multi(
|
||||||
|
&enb_radio, (void**)data_ptr, SF_LEN, tx_time.full_secs, tx_time.frac_secs, true, true, false);
|
||||||
|
} else {
|
||||||
|
// normal tx
|
||||||
|
ret = srsran_rf_send_multi(&enb_radio, (void**)data_ptr, SF_LEN, true, true, false);
|
||||||
|
}
|
||||||
|
if (ret != SRSRAN_SUCCESS) {
|
||||||
|
fprintf(stderr, "Error sending data\n");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
num_txed_samples += SF_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("transmitted %d samples in %d subframes\n", num_txed_samples, NUM_SF);
|
||||||
|
|
||||||
|
printf("closing tx device\n");
|
||||||
|
srsran_rf_close(&enb_radio);
|
||||||
|
}
|
||||||
|
|
||||||
|
int run_test(const char* rx_args, const char* tx_args, bool timed_tx)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
|
||||||
|
// make sure we can receive in slots
|
||||||
|
if (NUM_SF % 5 != 0) {
|
||||||
|
fprintf(stderr, "number of subframes must be multiple of 5\n");
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write to file(s)
|
||||||
|
enb_tx_function(tx_args, timed_tx);
|
||||||
|
|
||||||
|
// read from file(s)
|
||||||
|
ue_rx_thread_function((void*)rx_args);
|
||||||
|
|
||||||
|
// channel-wise comparison
|
||||||
|
for (int c = 0; c < NOF_RX_ANT; c++) {
|
||||||
|
// subframe-wise compare tx'ed and rx'ed data (stop 3 subframes earlier for timed tx)
|
||||||
|
for (uint32_t i = 0; i < NUM_SF - (timed_tx ? 3 : 0); ++i) {
|
||||||
|
uint32_t sf_offet = 0;
|
||||||
|
if (timed_tx && i >= 1) {
|
||||||
|
// for timed transmission, the enb inserts 3 zero subframes after the first untimed tx
|
||||||
|
sf_offet = (TX_OFFSET_MS - 1) * SF_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if PRINT_SAMPLES
|
||||||
|
// print first 10 samples for each SF
|
||||||
|
printf("enb_tx_buffer sf%d:\n", i);
|
||||||
|
srsran_vec_fprint_c(stdout, &enb_tx_buffer[c][i * SF_LEN], 10);
|
||||||
|
printf("ue_rx_buffer sf%d:\n", i);
|
||||||
|
srsran_vec_fprint_c(stdout, &ue_rx_buffer[c][sf_offet + i * SF_LEN], 10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if COMPARE_BITS
|
||||||
|
int d = memcmp(&ue_rx_buffer[sf_offet + i * SF_LEN], &enb_tx_buffer[i * SF_LEN], SF_LEN);
|
||||||
|
if (d) {
|
||||||
|
d = d > 0 ? d : -d;
|
||||||
|
fprintf(stderr, "data mismatch in subframe %d, sample %d\n", i, d);
|
||||||
|
printf("enb_tx_buffer sf%d:\n", i);
|
||||||
|
srsran_vec_fprint_c(stdout, &enb_tx_buffer[i * SF_LEN + d], 10);
|
||||||
|
printf("ue_rx_buffer sf%d:\n", i);
|
||||||
|
srsran_vec_fprint_c(stdout, &ue_rx_buffer[sf_offet + i * SF_LEN + d], 10);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
srsran_vec_sub_ccc(&ue_rx_buffer[c][sf_offet + i * SF_LEN],
|
||||||
|
&enb_tx_buffer[c][i * SF_LEN],
|
||||||
|
&ue_rx_buffer[c][sf_offet + i * SF_LEN],
|
||||||
|
SF_LEN);
|
||||||
|
uint32_t max_ix = srsran_vec_max_abs_ci(&ue_rx_buffer[c][sf_offet + i * SF_LEN], SF_LEN);
|
||||||
|
if (cabsf(ue_rx_buffer[c][sf_offet + i * SF_LEN + max_ix]) > COMPARE_EPSILON) {
|
||||||
|
fprintf(stderr, "data mismatch in subframe %d\n", i);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int param_test(const char* args_param, const int num_channels)
|
||||||
|
{
|
||||||
|
char rf_args[RF_PARAM_LEN] = {};
|
||||||
|
strncpy(rf_args, (char*)args_param, RF_PARAM_LEN - 1);
|
||||||
|
rf_args[RF_PARAM_LEN - 1] = 0;
|
||||||
|
|
||||||
|
printf("opening tx device with args=%s\n", rf_args);
|
||||||
|
if (srsran_rf_open_devname(&enb_radio, "file", rf_args, num_channels)) {
|
||||||
|
fprintf(stderr, "Error opening rf\n");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
srsran_rf_close(&enb_radio);
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_file(const char* filename)
|
||||||
|
{
|
||||||
|
FILE* f = fopen(filename, "w");
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_file(const char* filename)
|
||||||
|
{
|
||||||
|
remove(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// create files for testing
|
||||||
|
create_file("rx_file0");
|
||||||
|
create_file("rx_file1");
|
||||||
|
create_file("rx_file2");
|
||||||
|
create_file("rx_file3");
|
||||||
|
|
||||||
|
// two RX files
|
||||||
|
if (param_test("rx_file=rx_file0,"
|
||||||
|
"rx_file1=rx_file1",
|
||||||
|
2)) {
|
||||||
|
fprintf(stderr, "Param test failed!\n");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiple RX files, no channel index provided
|
||||||
|
if (param_test("rx_file=rx_file0,"
|
||||||
|
"rx_file=rx_file1,"
|
||||||
|
"rx_file=rx_file2,"
|
||||||
|
"rx_file=rx_file3,"
|
||||||
|
"base_srate=1.92e6",
|
||||||
|
4)) {
|
||||||
|
fprintf(stderr, "Param test failed!\n");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// one RX, one TX and all generic options
|
||||||
|
if (param_test("rx_file0=rx_file0,"
|
||||||
|
"tx_file0=tx_file0,"
|
||||||
|
"base_srate=1.92e6",
|
||||||
|
1)) {
|
||||||
|
fprintf(stderr, "Param test failed!\n");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// two RX, two TX
|
||||||
|
if (param_test("rx_file0=rx_file0,"
|
||||||
|
"rx_file1=rx_file1,"
|
||||||
|
"tx_file0=tx_file0,"
|
||||||
|
"tx_file1=tx_file1",
|
||||||
|
2)) {
|
||||||
|
fprintf(stderr, "Param test failed!\n");
|
||||||
|
return SRSRAN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NOF_RX_ANT == 1
|
||||||
|
// single tx, single rx with continuous transmissions (no decimation, no timed tx)
|
||||||
|
if (run_test("rx_file=tx_file0,base_srate=1.92e6", "tx_file=tx_file0,base_srate=1.92e6", false) != SRSRAN_SUCCESS) {
|
||||||
|
fprintf(stderr, "Single tx, single rx test failed (no decimation, no timed tx)!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// up to 4 trx radios with continous tx (no decimation, no timed tx)
|
||||||
|
if (run_test("rx_file=tx_file0,rx_file=tx_file1,rx_file=tx_file2,rx_file=tx_file3,base_srate=1.92e6",
|
||||||
|
"tx_file=tx_file0,tx_file=tx_file1,tx_file=tx_file2,tx_file=tx_file3,base_srate=1.92e6",
|
||||||
|
false) != SRSRAN_SUCCESS) {
|
||||||
|
fprintf(stderr, "Multi TRx radio test failed (no decimation, no timed tx)!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// up to 4 trx radios with continous tx (with decimation, no timed tx)
|
||||||
|
if (run_test("rx_file=tx_file0,rx_file=tx_file1,rx_file=tx_file2,rx_file=tx_file3",
|
||||||
|
"tx_file=tx_file0,tx_file=tx_file1,tx_file=tx_file2,tx_file=tx_file3",
|
||||||
|
false) != SRSRAN_SUCCESS) {
|
||||||
|
fprintf(stderr, "Multi TRx radio test failed (with decimation, no timed tx)!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// up to 4 trx radios with continous tx (with decimation, timed tx)
|
||||||
|
if (run_test("rx_file=tx_file0,rx_file=tx_file1,rx_file=tx_file2,rx_file=tx_file3",
|
||||||
|
"tx_file=tx_file0,tx_file=tx_file1,tx_file=tx_file2,tx_file=tx_file3",
|
||||||
|
true) != SRSRAN_SUCCESS) {
|
||||||
|
fprintf(stderr, "Two TRx radio test failed (with decimation, timed tx)!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean workspace
|
||||||
|
remove_file("rx_file0");
|
||||||
|
remove_file("rx_file1");
|
||||||
|
remove_file("rx_file2");
|
||||||
|
remove_file("rx_file3");
|
||||||
|
remove_file("tx_file0");
|
||||||
|
remove_file("tx_file1");
|
||||||
|
remove_file("tx_file2");
|
||||||
|
remove_file("tx_file3");
|
||||||
|
|
||||||
|
fprintf(stdout, "Test passed!\n");
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
@ -0,0 +1,180 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* \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/common/test_common.h"
|
||||||
|
#include "srsran/phy/channel/ch_awgn.h"
|
||||||
|
#include "srsran/phy/sync/ssb.h"
|
||||||
|
#include "srsran/phy/utils/debug.h"
|
||||||
|
#include "srsran/phy/utils/vector.h"
|
||||||
|
#include <complex.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <srsran/phy/utils/random.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// NR parameters
|
||||||
|
static srsran_subcarrier_spacing_t carrier_scs = srsran_subcarrier_spacing_15kHz;
|
||||||
|
static double carrier_freq_hz = 3.5e9 + 960e3;
|
||||||
|
static srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_30kHz;
|
||||||
|
static double ssb_freq_hz = 3.5e9;
|
||||||
|
static srsran_ssb_pattern_t ssb_pattern = SRSRAN_SSB_PATTERN_A;
|
||||||
|
static uint32_t ssb_idx = 0; // SSB candidate index to test
|
||||||
|
static uint32_t pci = 123; // N_id
|
||||||
|
|
||||||
|
// Test context
|
||||||
|
static srsran_random_t random_gen = NULL;
|
||||||
|
static double srate_hz = 0.0f; // Base-band sampling rate
|
||||||
|
static cf_t* grid = NULL; // Resource grid
|
||||||
|
static uint32_t grid_bw_sc = 52 * SRSRAN_NRE; // Resource grid bandwidth in subcarriers
|
||||||
|
|
||||||
|
static void usage(char* prog)
|
||||||
|
{
|
||||||
|
printf("Usage: %s [v]\n", prog);
|
||||||
|
printf("\t-s SSB subcarrier spacing [default, %s kHz]\n", srsran_subcarrier_spacing_to_str(ssb_scs));
|
||||||
|
printf("\t-f SSB center frequency [default, %.3f MHz]\n", ssb_freq_hz / 1e6);
|
||||||
|
printf("\t-S cell/carrier subcarrier spacing [default, %s kHz]\n", srsran_subcarrier_spacing_to_str(carrier_scs));
|
||||||
|
printf("\t-F cell/carrier center frequency in Hz [default, %.3f MHz]\n", carrier_freq_hz / 1e6);
|
||||||
|
printf("\t-P SSB pattern [default, %s]\n", srsran_ssb_pattern_to_str(ssb_pattern));
|
||||||
|
printf("\t-v [set srsran_verbose to debug, default none]\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_args(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int opt;
|
||||||
|
while ((opt = getopt(argc, argv, "SsFfPv")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 's':
|
||||||
|
ssb_scs = srsran_subcarrier_spacing_from_str(argv[optind]);
|
||||||
|
if (ssb_scs == srsran_subcarrier_spacing_invalid) {
|
||||||
|
ERROR("Invalid SSB subcarrier spacing %s\n", argv[optind]);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
ssb_freq_hz = strtod(argv[optind], NULL);
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
carrier_scs = srsran_subcarrier_spacing_from_str(argv[optind]);
|
||||||
|
if (carrier_scs == srsran_subcarrier_spacing_invalid) {
|
||||||
|
ERROR("Invalid Cell/Carrier subcarrier spacing %s\n", argv[optind]);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'F':
|
||||||
|
carrier_freq_hz = strtod(argv[optind], NULL);
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
ssb_pattern = srsran_ssb_pattern_fom_str(argv[optind]);
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
increase_srsran_verbose_level();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage(argv[0]);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gen_pbch_msg(srsran_pbch_msg_nr_t* pbch_msg)
|
||||||
|
{
|
||||||
|
// Default all to zero
|
||||||
|
SRSRAN_MEM_ZERO(pbch_msg, srsran_pbch_msg_nr_t, 1);
|
||||||
|
|
||||||
|
// Generate payload
|
||||||
|
srsran_random_bit_vector(random_gen, pbch_msg->payload, SRSRAN_PBCH_MSG_NR_SZ);
|
||||||
|
|
||||||
|
pbch_msg->ssb_idx = ssb_idx;
|
||||||
|
pbch_msg->crc = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_case(srsran_ssb_t* ssb)
|
||||||
|
{
|
||||||
|
// SSB configuration
|
||||||
|
srsran_ssb_cfg_t ssb_cfg = {};
|
||||||
|
ssb_cfg.srate_hz = srate_hz;
|
||||||
|
ssb_cfg.center_freq_hz = carrier_freq_hz;
|
||||||
|
ssb_cfg.ssb_freq_hz = ssb_freq_hz;
|
||||||
|
ssb_cfg.scs = ssb_scs;
|
||||||
|
ssb_cfg.pattern = ssb_pattern;
|
||||||
|
|
||||||
|
TESTASSERT(srsran_ssb_set_cfg(ssb, &ssb_cfg) == SRSRAN_SUCCESS);
|
||||||
|
|
||||||
|
// Build PBCH message
|
||||||
|
srsran_pbch_msg_nr_t pbch_msg_tx = {};
|
||||||
|
gen_pbch_msg(&pbch_msg_tx);
|
||||||
|
|
||||||
|
// Print encoded PBCH message
|
||||||
|
char str[512] = {};
|
||||||
|
srsran_pbch_msg_info(&pbch_msg_tx, str, sizeof(str));
|
||||||
|
INFO("test_case - encoded pci=%d %s", pci, str);
|
||||||
|
|
||||||
|
// Add the SSB base-band
|
||||||
|
TESTASSERT(srsran_ssb_put_grid(ssb, pci, &pbch_msg_tx, grid, grid_bw_sc) == SRSRAN_SUCCESS);
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
srsran_pbch_msg_nr_t pbch_msg_rx = {};
|
||||||
|
TESTASSERT(srsran_ssb_decode_grid(ssb, pci, pbch_msg_tx.hrf, pbch_msg_tx.ssb_idx, grid, grid_bw_sc, &pbch_msg_rx) ==
|
||||||
|
SRSRAN_SUCCESS);
|
||||||
|
|
||||||
|
// Print decoded PBCH message
|
||||||
|
srsran_pbch_msg_info(&pbch_msg_rx, str, sizeof(str));
|
||||||
|
INFO("test_case - decoded pci=%d %s crc=%s", pci, str, pbch_msg_rx.crc ? "OK" : "KO");
|
||||||
|
|
||||||
|
// Assert PBCH message CRC
|
||||||
|
TESTASSERT(pbch_msg_rx.crc);
|
||||||
|
TESTASSERT(memcmp(&pbch_msg_rx, &pbch_msg_tx, sizeof(srsran_pbch_msg_nr_t)) == 0);
|
||||||
|
|
||||||
|
return SRSRAN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int ret = SRSRAN_ERROR;
|
||||||
|
parse_args(argc, argv);
|
||||||
|
|
||||||
|
random_gen = srsran_random_init(1234);
|
||||||
|
srate_hz = (double)SRSRAN_SUBC_SPACING_NR(carrier_scs) * srsran_min_symbol_sz_rb(grid_bw_sc / SRSRAN_NRE);
|
||||||
|
grid = srsran_vec_cf_malloc(grid_bw_sc * SRSRAN_NSYMB_PER_SLOT_NR);
|
||||||
|
|
||||||
|
srsran_ssb_t ssb = {};
|
||||||
|
srsran_ssb_args_t ssb_args = {};
|
||||||
|
ssb_args.enable_encode = true;
|
||||||
|
ssb_args.enable_decode = true;
|
||||||
|
ssb_args.enable_search = true;
|
||||||
|
|
||||||
|
if (grid == NULL) {
|
||||||
|
ERROR("Malloc");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsran_ssb_init(&ssb, &ssb_args) < SRSRAN_SUCCESS) {
|
||||||
|
ERROR("Init");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test_case(&ssb) != SRSRAN_SUCCESS) {
|
||||||
|
ERROR("test case failed");
|
||||||
|
goto clean_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = SRSRAN_SUCCESS;
|
||||||
|
|
||||||
|
clean_exit:
|
||||||
|
srsran_random_free(random_gen);
|
||||||
|
srsran_ssb_free(&ssb);
|
||||||
|
|
||||||
|
if (grid) {
|
||||||
|
free(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue