Initial UE synchronization for NR

This commit is contained in:
Xavier Arteaga 2021-06-16 18:10:34 +02:00 committed by Xavier Arteaga
parent 4d13906760
commit eb7980f2b3
8 changed files with 1092 additions and 6 deletions

View File

@ -601,6 +601,14 @@ SRSRAN_API uint32_t srsran_csi_meas_info(const srsran_csi_trs_measurements_t* me
*/
SRSRAN_API srsran_subcarrier_spacing_t srsran_subcarrier_spacing_from_str(const char* str);
/**
* @brief Combine Channel State Information from Tracking Reference Signals (CSI-TRS)
* @param[in] a CSI-RS measurement
* @param[in] b CSI-RS measurement
* @param[out] dst Destination of the combined
*/
SRSRAN_API void srsran_combine_csi_trs_measurements(const srsran_csi_trs_measurements_t *a, const srsran_csi_trs_measurements_t *b, srsran_csi_trs_measurements_t *dst);
#ifdef __cplusplus
}
#endif

View File

@ -64,10 +64,10 @@ typedef struct SRSRAN_API {
srsran_ssb_patern_t pattern; ///< SSB pattern as defined in TS 38.313 section 4.1 Cell search
srsran_duplex_mode_t duplex_mode; ///< Set to true if the spectrum is paired (FDD)
uint32_t periodicity_ms; ///< SSB periodicity in ms
float beta_pss; ////< PSS power allocation
float beta_sss; ////< SSS power allocation
float beta_pbch; ////< PBCH power allocation
float beta_pbch_dmrs; ////< PBCH DMRS power allocation
float beta_pss; ///< PSS power allocation
float beta_sss; ///< SSS power allocation
float beta_pbch; ///< PBCH power allocation
float beta_pbch_dmrs; ///< PBCH DMRS power allocation
} srsran_ssb_cfg_t;
/**
@ -79,11 +79,15 @@ typedef struct SRSRAN_API {
/// Sampling rate dependent parameters
float scs_hz; ///< Subcarrier spacing in Hz
uint32_t max_sf_sz; ///< Maximum subframe size at the specified sampling rate
uint32_t max_symbol_sz; ///< Maximum symbol size given the minimum supported SCS and sampling rate
uint32_t max_corr_sz; ///< Maximum correlation size
uint32_t max_ssb_sz; ///< Maximum SSB size in samples at the configured sampling rate
uint32_t sf_sz; ///< Current subframe size at the specified sampling rate
uint32_t symbol_sz; ///< Current SSB symbol size (for the given base-band sampling rate)
uint32_t corr_sz; ///< Correlation size
uint32_t corr_window; ///< Correlation window length
uint32_t ssb_sz; ///< SSB size in samples at the configured sampling rate
int32_t f_offset; ///< Current SSB integer frequency offset (multiple of SCS)
uint32_t cp_sz; ///< CP length for the given symbol size
@ -102,6 +106,7 @@ typedef struct SRSRAN_API {
cf_t* tmp_freq; ///< Temporal frequency domain buffer
cf_t* tmp_time; ///< Temporal time domain buffer
cf_t* tmp_corr; ///< Temporal correlation frequency domain buffer
cf_t* sf_buffer; ///< subframe buffer
cf_t* pss_seq[SRSRAN_NOF_NID_2_NR]; ///< Possible frequency domain PSS for find
} srsran_ssb_t;
@ -184,7 +189,7 @@ srsran_ssb_add(srsran_ssb_t* q, uint32_t N_id, const srsran_pbch_msg_nr_t* msg,
/**
* @brief Perform cell search and measurement
* @note This function assumes the SSB transmission is aligned with the input base-band signal
* @param q NR PSS object
* @param q SSB object
* @param in Base-band signal buffer
* @param N_id Physical Cell Identifier of the most suitable cell identifier
* @param meas SSB-based CSI measurement of the most suitable cell identifier
@ -198,7 +203,7 @@ SRSRAN_API int srsran_ssb_csi_search(srsran_ssb_t* q,
/**
* @brief Perform Channel State Information (CSI) measurement from the SSB
* @param q NR PSS object
* @param q SSB object
* @param N_id Physical Cell Identifier
* @param ssb_idx SSB candidate index
* @param in Base-band signal
@ -211,4 +216,55 @@ SRSRAN_API int srsran_ssb_csi_measure(srsran_ssb_t* q,
const cf_t* in,
srsran_csi_trs_measurements_t* meas);
/**
* @brief Find SSB signal in a given time domain subframe buffer
* @param q SSB object
* @param sf_buffer subframe buffer with 1ms worth of samples
* @param N_id Physical cell identifier to find
* @param meas Measurements performed on the found peak
* @param pbch_msg PBCH decoded message
* @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise
*/
SRSRAN_API int srsran_ssb_find(srsran_ssb_t* q,
const cf_t* sf_buffer,
uint32_t N_id,
srsran_csi_trs_measurements_t* meas,
srsran_pbch_msg_nr_t* pbch_msg);
/**
* @brief Track SSB by performing measurements and decoding PBCH
* @param q SSB object
* @param sf_buffer subframe buffer with 1ms worth of samples
* @param N_id Physical cell identifier to find
* @param ssb_idx SSB candidate index
* @param n_hf Number of half frame
* @param meas Measurements perform
* @param pbch_msg PBCH decoded message
* @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise
*/
SRSRAN_API int srsran_ssb_track(srsran_ssb_t* q,
const cf_t* sf_buffer,
uint32_t N_id,
uint32_t ssb_idx,
uint32_t n_hf,
srsran_csi_trs_measurements_t* meas,
srsran_pbch_msg_nr_t* pbch_msg);
/**
* @brief Calculates the subframe index within the radio frame of a given SSB candidate for the SSB object
* @param q SSB object
* @param ssb_idx SSB candidate index
* @param half_frame Indicates whether it is half frame
* @return The subframe index
*/
SRSRAN_API uint32_t srsran_ssb_candidate_sf_idx(const srsran_ssb_t* q, uint32_t ssb_idx, bool half_frame);
/**
* @brief Calculates the SSB offset within the subframe of a given SSB candidate for the SSB object
* @param q SSB object
* @param ssb_idx SSB candidate index
* @return The sample offset within the subframe
*/
SRSRAN_API uint32_t srsran_ssb_candidate_sf_offset(const srsran_ssb_t* q, uint32_t ssb_idx);
#endif // SRSRAN_SSB_H

View File

@ -0,0 +1,144 @@
/**
*
* \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_UE_SYNC_NR_H
#define SRSRAN_UE_SYNC_NR_H
#include "srsran/phy/common/timestamp.h"
#include "srsran/phy/sync/ssb.h"
#define SRSRAN_RECV_CALLBACK_TEMPLATE(NAME) int (*NAME)(void*, cf_t**, uint32_t, srsran_timestamp_t*)
/**
* @brief Describes NR UE synchronization object internal states
*/
typedef enum SRSRAN_API {
SRSRAN_UE_SYNC_NR_STATE_IDLE = 0, ///< Initial state, the object has no configuration
SRSRAN_UE_SYNC_NR_STATE_FIND, ///< State just after configuring, baseband is not aligned
SRSRAN_UE_SYNC_NR_STATE_TRACK ///< Baseband is aligned with subframes
} srsran_ue_sync_nr_state_t;
/**
* @brief Describes a UE sync NR object arguments
*/
typedef struct SRSRAN_API {
// Memory allocation constraints
double max_srate_hz; ///< Maximum sampling rate in Hz, set to zero to use default
srsran_subcarrier_spacing_t min_scs; ///< Minimum subcarrier spacing
uint32_t nof_rx_channels; ///< Number of receive channels, set to 0 for 1
// Enable/Disable features
bool disable_cfo; ///< Set to true for disabling the CFO compensation close loop
// Signal detection thresholds and averaging coefficients
float pbch_dmrs_thr; ///< NR-PBCH DMRS threshold for blind decoding, set to 0 for default
float cfo_alpha; ///< Exponential Moving Average (EMA) alpha coefficient for CFO
// Receive callback
void* recv_obj; ///< Receive object
SRSRAN_RECV_CALLBACK_TEMPLATE(recv_callback); ///< Receive callback
} srsran_ue_sync_nr_args_t;
/**
* @brief Describes a UE sync NR object configuration
*/
typedef struct SRSRAN_API {
srsran_ssb_cfg_t ssb; ///< SSB configuration
uint32_t N_id; ///< Physicall cell identifier
} srsran_ue_sync_nr_cfg_t;
/**
* @brief Describes a UE sync NR object
*/
typedef struct SRSRAN_API {
// State
srsran_ue_sync_nr_state_t state; ///< Internal state
int32_t next_rf_sample_offset; ///< Next RF sample offset
uint32_t ssb_idx; ///< Tracking SSB candidate index
uint32_t sf_idx; ///< Current subframe index (0-9)
uint32_t sfn; ///< Current system frame number (0-1023)
srsran_csi_trs_measurements_t feedback; ///< Feedback measurements
// Components
srsran_ssb_t ssb; ///< SSB internal object
cf_t** tmp_buffer; ///< Temporal buffer pointers
// Initialised arguments
uint32_t nof_rx_channels; ///< Number of receive channels
bool disable_cfo; ///< Set to true for disabling the CFO compensation close loop
float cfo_alpha; ///< Exponential Moving Average (EMA) alpha coefficient for CFO
void* recv_obj; ///< Receive object
SRSRAN_RECV_CALLBACK_TEMPLATE(recv_callback); ///< Receive callback
// Current configuration
uint32_t N_id; ///< Current physical cell identifier
double srate_hz; ///< Current sampling rate in Hz
uint32_t sf_sz; ///< Current subframe size
// Metrics
float cfo_hz; ///< Current CFO in Hz
float avg_delay_us; ///< Current average delay
} srsran_ue_sync_nr_t;
/**
* @brief Describes a UE sync NR zerocopy outcome
*/
typedef struct SRSRAN_API {
bool in_sync; ///< Indicates whether the received baseband is synchronized
uint32_t sf_idx; ///< Subframe index
uint32_t sfn; ///< System Frame Number
srsran_timestamp_t timestamp; ///< Last received timestamp
float cfo_hz; ///< Current CFO in Hz
float delay_us; ///< Current average delay in microseconds
} srsran_ue_sync_nr_outcome_t;
/**
* @brief Initialises a UE sync NR object
* @param q NR UE synchronization object
* @param[in] args NR UE synchronization initialization arguments
* @return SRSRAN_SUCCESS if no error occurs, SRSRAN_ERROR code otherwise
*/
SRSRAN_API int srsran_ue_sync_nr_init(srsran_ue_sync_nr_t* q, const srsran_ue_sync_nr_args_t* args);
/**
* @brief Deallocate an NR UE synchronization object
* @param q NR UE synchronization object
*/
SRSRAN_API void srsran_ue_sync_nr_free(srsran_ue_sync_nr_t* q);
/**
* @brief Configures a UE sync NR object
* @param q NR UE synchronization object
* @param[in] cfg NR UE synchronization configuration
* @return SRSRAN_SUCCESS if no error occurs, SRSRAN_ERROR code otherwise
*/
SRSRAN_API int srsran_ue_sync_nr_set_cfg(srsran_ue_sync_nr_t* q, const srsran_ue_sync_nr_cfg_t* cfg);
/**
* @brief Runs the NR UE synchronization object, tries to find and track the configured SSB leaving in buffer the
* received baseband subframe
* @param q NR UE synchronization object
* @param buffer 2D complex buffer
* @param outcome zerocopy outcome
* @return SRSRAN_SUCCESS if no error occurs, SRSRAN_ERROR code otherwise
*/
SRSRAN_API int srsran_ue_sync_nr_zerocopy(srsran_ue_sync_nr_t* q, cf_t** buffer, srsran_ue_sync_nr_outcome_t* outcome);
/**
* @brief Feedback Channel State Information from Tracking Reference Signals into a UE synchronization object
* @param q NR UE synchronization object
* @param measurements CSI-TRS feedback measurement
* @return SRSRAN_SUCCESS if no error occurs, SRSRAN_ERROR code otherwise
*/
SRSRAN_API int srsran_ue_sync_nr_feedback(srsran_ue_sync_nr_t* q, const srsran_csi_trs_measurements_t* measurements);
#endif // SRSRAN_UE_SYNC_NR_H

View File

@ -351,3 +351,33 @@ srsran_subcarrier_spacing_t srsran_subcarrier_spacing_from_str(const char* str)
return srsran_subcarrier_spacing_invalid;
}
void srsran_combine_csi_trs_measurements(const srsran_csi_trs_measurements_t* a,
const srsran_csi_trs_measurements_t* b,
srsran_csi_trs_measurements_t* dst)
{
// Verify inputs
if (a == NULL || b == NULL || dst == NULL) {
return;
}
// Protect from zero division
uint32_t nof_re_sum = a->nof_re + b->nof_re;
if (nof_re_sum == 0) {
SRSRAN_MEM_ZERO(dst, srsran_csi_trs_measurements_t, 1);
return;
}
// Perform proportional average
dst->rsrp = SRSRAN_VEC_PMA(a->rsrp, a->nof_re, b->rsrp, b->nof_re);
dst->rsrp_dB = SRSRAN_VEC_PMA(a->rsrp_dB, a->nof_re, b->rsrp_dB, b->nof_re);
dst->epre = SRSRAN_VEC_PMA(a->epre, a->nof_re, b->epre, b->nof_re);
dst->epre_dB = SRSRAN_VEC_PMA(a->epre_dB, a->nof_re, b->epre_dB, b->nof_re);
dst->n0 = SRSRAN_VEC_PMA(a->n0, a->nof_re, b->n0, b->nof_re);
dst->n0_dB = SRSRAN_VEC_PMA(a->n0_dB, a->nof_re, b->n0_dB, b->nof_re);
dst->snr_dB = SRSRAN_VEC_PMA(a->snr_dB, a->nof_re, b->snr_dB, b->nof_re);
dst->cfo_hz = SRSRAN_VEC_PMA(a->cfo_hz, a->nof_re, b->cfo_hz, b->nof_re);
dst->cfo_hz_max = SRSRAN_MAX(a->cfo_hz_max, b->cfo_hz_max);
dst->delay_us = SRSRAN_VEC_PMA(a->delay_us, a->nof_re, b->delay_us, b->nof_re);
dst->nof_re = nof_re_sum;
}

View File

@ -56,6 +56,13 @@ static int ssb_init_corr(srsran_ssb_t* q)
}
}
q->sf_buffer = srsran_vec_cf_malloc(q->max_ssb_sz + q->max_sf_sz);
if (q->sf_buffer == NULL) {
ERROR("Malloc");
return SRSRAN_ERROR;
}
srsran_vec_cf_zero(q->sf_buffer, q->max_ssb_sz + q->max_sf_sz);
return SRSRAN_SUCCESS;
}
@ -93,8 +100,10 @@ int srsran_ssb_init(srsran_ssb_t* q, const srsran_ssb_args_t* args)
q->args.pbch_dmrs_thr = (!isnormal(q->args.pbch_dmrs_thr)) ? SSB_PBCH_DMRS_DEFAULT_CORR_THR : q->args.pbch_dmrs_thr;
q->scs_hz = (float)SRSRAN_SUBC_SPACING_NR(q->args.min_scs);
q->max_sf_sz = (uint32_t)round(1e-3 * q->args.max_srate_hz);
q->max_symbol_sz = (uint32_t)round(q->args.max_srate_hz / q->scs_hz);
q->max_corr_sz = SSB_CORR_SZ(q->max_symbol_sz);
q->max_ssb_sz = SRSRAN_SSB_DURATION_NSYMB * (q->max_symbol_sz + (144 * q->max_symbol_sz) / 2048);
// Allocate temporal data
q->tmp_time = srsran_vec_cf_malloc(q->max_corr_sz);
@ -143,6 +152,10 @@ void srsran_ssb_free(srsran_ssb_t* q)
}
}
if (q->sf_buffer != NULL) {
free(q->sf_buffer);
}
srsran_dft_plan_free(&q->ifft);
srsran_dft_plan_free(&q->fft);
srsran_dft_plan_free(&q->fft_corr);
@ -437,6 +450,7 @@ int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg)
// Verify symbol size
if (q->max_symbol_sz < symbol_sz) {
ERROR("New symbol size (%d) exceeds maximum symbol size (%d)", symbol_sz, q->max_symbol_sz);
return SRSRAN_ERROR;
}
// Replan iFFT
@ -468,6 +482,8 @@ int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg)
// Finally, copy configuration
q->cfg = *cfg;
q->symbol_sz = symbol_sz;
q->sf_sz = (uint32_t)round(1e-3 * cfg->srate_hz);
q->ssb_sz = SRSRAN_SSB_DURATION_NSYMB * (q->symbol_sz + q->cp_sz);
// Initialise correlation
if (ssb_setup_corr(q) < SRSRAN_SUCCESS) {
@ -1093,3 +1109,213 @@ int srsran_ssb_search(srsran_ssb_t* q, const cf_t* in, uint32_t nof_samples, srs
return SRSRAN_SUCCESS;
}
static int ssb_pss_find(srsran_ssb_t* q, const cf_t* in, uint32_t nof_samples, uint32_t N_id_2, uint32_t* found_delay)
{
// verify it is initialised
if (q->corr_sz == 0) {
return SRSRAN_ERROR;
}
// Correlation best sequence
float best_corr = 0;
uint32_t best_delay = 0;
// Delay in correlation window
uint32_t t_offset = 0;
while ((t_offset + q->symbol_sz) < nof_samples) {
// Number of samples taken in this iteration
uint32_t n = q->corr_sz;
// Detect if the correlation input exceeds the input length, take the maximum amount of samples
if (t_offset + q->corr_sz > nof_samples) {
n = nof_samples - t_offset;
}
// Copy the amount of samples
srsran_vec_cf_copy(q->tmp_time, &in[t_offset], n);
// Append zeros if there is space left
if (n < q->corr_sz) {
srsran_vec_cf_zero(&q->tmp_time[n], q->corr_sz - n);
}
// Convert to frequency domain
srsran_dft_run_guru_c(&q->fft_corr);
// Actual correlation in frequency domain
srsran_vec_prod_conj_ccc(q->tmp_freq, q->pss_seq[N_id_2], q->tmp_corr, q->corr_sz);
// Convert to time domain
srsran_dft_run_guru_c(&q->ifft_corr);
// Find maximum
uint32_t peak_idx = srsran_vec_max_abs_ci(q->tmp_time, q->corr_window);
// Average power, skip window if value is invalid (0.0, nan or inf)
float avg_pwr_corr = srsran_vec_avg_power_cf(&q->tmp_time[peak_idx], q->symbol_sz);
if (!isnormal(avg_pwr_corr)) {
continue;
}
// Normalise correlation
float corr = SRSRAN_CSQABS(q->tmp_time[peak_idx]) / avg_pwr_corr / sqrtf(SRSRAN_PSS_NR_LEN);
// Update if the correlation is better than the current best
if (best_corr < corr) {
best_corr = corr;
best_delay = peak_idx + t_offset;
}
// Advance time
t_offset += q->corr_window;
}
// Save findings
*found_delay = best_delay;
return SRSRAN_SUCCESS;
}
int srsran_ssb_find(srsran_ssb_t* q,
const cf_t* sf_buffer,
uint32_t N_id,
srsran_csi_trs_measurements_t* meas,
srsran_pbch_msg_nr_t* pbch_msg)
{
// Verify inputs
if (q == NULL || sf_buffer == NULL || meas == NULL || !isnormal(q->scs_hz)) {
return SRSRAN_ERROR_INVALID_INPUTS;
}
if (!q->args.enable_search) {
ERROR("SSB is not configured for search");
return SRSRAN_ERROR;
}
// Copy tail from previous execution into the start of this
srsran_vec_cf_copy(q->sf_buffer, &q->sf_buffer[q->sf_sz], q->ssb_sz);
// Append new samples
srsran_vec_cf_copy(&q->sf_buffer[q->ssb_sz], sf_buffer, q->sf_sz);
// Search for PSS in time domain
uint32_t t_offset = 0;
if (ssb_pss_find(q, q->sf_buffer, q->sf_sz + q->ssb_sz, SRSRAN_NID_2_NR(N_id), &t_offset) < SRSRAN_SUCCESS) {
ERROR("Error searching for N_id_2");
return SRSRAN_ERROR;
}
// Remove CP offset prior demodulation
if (t_offset >= q->cp_sz) {
t_offset -= q->cp_sz;
} else {
t_offset = 0;
}
// Demodulate
cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {};
if (ssb_demodulate(q, q->sf_buffer, t_offset, ssb_grid) < SRSRAN_SUCCESS) {
ERROR("Error demodulating");
return SRSRAN_ERROR;
}
// Measure selected N_id
if (ssb_measure(q, ssb_grid, N_id, meas)) {
ERROR("Error measuring");
return SRSRAN_ERROR;
}
// Select the most suitable SSB candidate
uint32_t n_hf = 0;
uint32_t ssb_idx = 0; // SSB candidate index
if (ssb_select_pbch(q, N_id, ssb_grid, &n_hf, &ssb_idx) < SRSRAN_SUCCESS) {
ERROR("Error selecting PBCH");
return SRSRAN_ERROR;
}
// Calculate the SSB offset in the subframe
uint32_t ssb_offset = srsran_ssb_candidate_sf_offset(q, ssb_idx);
// Compute PBCH channel estimates
if (ssb_decode_pbch(q, N_id, n_hf, ssb_idx, ssb_grid, pbch_msg) < SRSRAN_SUCCESS) {
ERROR("Error decoding PBCH");
return SRSRAN_ERROR;
}
// SSB delay in SF
float ssb_delay_us = (float)(1e6 * (((double)t_offset - (double)q->ssb_sz - (double)ssb_offset) / q->cfg.srate_hz));
// Add delay to measure
meas->delay_us += ssb_delay_us;
return SRSRAN_SUCCESS;
}
int srsran_ssb_track(srsran_ssb_t* q,
const cf_t* sf_buffer,
uint32_t N_id,
uint32_t ssb_idx,
uint32_t n_hf,
srsran_csi_trs_measurements_t* meas,
srsran_pbch_msg_nr_t* pbch_msg)
{
// Verify inputs
if (q == NULL || sf_buffer == NULL || meas == NULL || !isnormal(q->scs_hz)) {
return SRSRAN_ERROR_INVALID_INPUTS;
}
if (!q->args.enable_search) {
ERROR("SSB is not configured for search");
return SRSRAN_ERROR;
}
// Calculate SSB offset
uint32_t t_offset = srsran_ssb_candidate_sf_offset(q, ssb_idx);
// Demodulate
cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {};
if (ssb_demodulate(q, sf_buffer, t_offset, ssb_grid) < SRSRAN_SUCCESS) {
ERROR("Error demodulating");
return SRSRAN_ERROR;
}
// Measure selected N_id
if (ssb_measure(q, ssb_grid, N_id, meas)) {
ERROR("Error measuring");
return SRSRAN_ERROR;
}
// Compute PBCH channel estimates
if (ssb_decode_pbch(q, N_id, n_hf, ssb_idx, ssb_grid, pbch_msg) < SRSRAN_SUCCESS) {
ERROR("Error decoding PBCH");
return SRSRAN_ERROR;
}
return SRSRAN_SUCCESS;
}
uint32_t srsran_ssb_candidate_sf_idx(const srsran_ssb_t* q, uint32_t ssb_idx, bool half_frame)
{
if (q == NULL) {
return 0;
}
uint32_t nof_symbols_subframe = SRSRAN_NSYMB_PER_SLOT_NR * SRSRAN_NSLOTS_PER_SF_NR(q->cfg.scs);
return q->l_first[ssb_idx] / nof_symbols_subframe + (half_frame ? (SRSRAN_NOF_SF_X_FRAME / 2) : 0);
}
uint32_t srsran_ssb_candidate_sf_offset(const srsran_ssb_t* q, uint32_t ssb_idx)
{
if (q == NULL) {
return 0;
}
uint32_t nof_symbols_subframe = SRSRAN_NSYMB_PER_SLOT_NR * SRSRAN_NSLOTS_PER_SF_NR(q->cfg.scs);
uint32_t l = q->l_first[ssb_idx] % nof_symbols_subframe;
uint32_t cp_sz_0 = (16U * q->symbol_sz) / 2048U;
return cp_sz_0 + l * (q->symbol_sz + q->cp_sz);
}

View File

@ -26,6 +26,10 @@ add_executable(ue_dl_nbiot_test ue_dl_nbiot_test.c)
target_link_libraries(ue_dl_nbiot_test srsran_phy pthread)
add_test(ue_dl_nbiot_test ue_dl_nbiot_test)
add_executable(ue_sync_nr_test ue_sync_nr_test.c)
target_link_libraries(ue_sync_nr_test srsran_phy pthread)
add_test(ue_sync_nr_test ue_sync_nr_test)
if(RF_FOUND)
add_executable(ue_mib_sync_test_nbiot_usrp ue_mib_sync_test_nbiot_usrp.c)
target_link_libraries(ue_mib_sync_test_nbiot_usrp srsran_phy srsran_rf pthread)

View File

@ -0,0 +1,304 @@
/**
*
* \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/channel/delay.h"
#include "srsran/phy/ue/ue_sync_nr.h"
#include "srsran/phy/utils/debug.h"
#include "srsran/phy/utils/ringbuffer.h"
#include "srsran/phy/utils/vector.h"
#include <getopt.h>
#include <stdlib.h>
// NR parameters
static uint32_t pci = 1; // Physical Cell Identifier
static uint32_t carrier_nof_prb = 52; // Carrier bandwidth
static srsran_subcarrier_spacing_t carrier_scs = srsran_subcarrier_spacing_15kHz;
static srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_30kHz;
// Test and channel parameters
static uint32_t nof_sf = 1000; // Number of subframes to test
static float cfo_hz = 100.0f; // CFO in Hz
static float n0_dB = -10.0f; // Noise floor in dB relative to full-scale
static float delay_min_us = 10.0f; // Minimum dynamic delay in microseconds
static float delay_max_us = 1000.0f; // Maximum dynamic delay in microseconds
static float delay_period_s = 60.0f; // Delay period in seconds
// Test context
static double srate_hz = 0.0f; // Base-band sampling rate
static uint32_t sf_len = 0; // Subframe length
static cf_t* buffer = NULL; // Base-band buffer
static cf_t* buffer2 = NULL; // Base-band buffer
static void usage(char* prog)
{
printf("Usage: %s [v]\n", prog);
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, "v")) != -1) {
switch (opt) {
case 'v':
srsran_verbose++;
break;
default:
usage(argv[0]);
exit(-1);
}
}
}
typedef struct {
uint32_t sf_idx;
uint32_t sfn;
srsran_ringbuffer_t ringbuffer;
srsran_ssb_t ssb;
srsran_timestamp_t timestamp;
srsran_channel_awgn_t awgn;
srsran_channel_delay_t delay;
} test_context_t;
static void run_channel(test_context_t* ctx)
{
// Delay
srsran_channel_delay_execute(&ctx->delay, buffer, buffer2, sf_len, &ctx->timestamp);
// CFO
srsran_vec_apply_cfo(buffer2, -cfo_hz / srate_hz, buffer, sf_len);
// AWGN
srsran_channel_awgn_run_c(&ctx->awgn, buffer, buffer, sf_len);
}
static int test_context_init(test_context_t* ctx)
{
SRSRAN_MEM_ZERO(ctx, test_context_t, 1);
if (ctx == NULL) {
return SRSRAN_ERROR_INVALID_INPUTS;
}
ctx->sfn = 1;
if (srsran_ringbuffer_init(&ctx->ringbuffer, (int)(10 * sf_len * sizeof(cf_t))) < SRSRAN_SUCCESS) {
return SRSRAN_ERROR;
}
srsran_ssb_args_t ssb_args = {};
ssb_args.max_srate_hz = srate_hz;
ssb_args.min_scs = carrier_scs;
ssb_args.enable_encode = true;
if (srsran_ssb_init(&ctx->ssb, &ssb_args) < SRSRAN_SUCCESS) {
return SRSRAN_ERROR;
}
srsran_ssb_cfg_t ssb_cfg = {};
ssb_cfg.srate_hz = srate_hz;
ssb_cfg.srate_hz = srate_hz;
ssb_cfg.center_freq_hz = 3.5e9;
ssb_cfg.ssb_freq_hz = 3.5e9 - 960e3;
ssb_cfg.scs = ssb_scs;
ssb_cfg.pattern = SRSRAN_SSB_PATTERN_C;
if (srsran_ssb_set_cfg(&ctx->ssb, &ssb_cfg) < SRSRAN_SUCCESS) {
return SRSRAN_ERROR;
}
if (srsran_channel_delay_init(&ctx->delay, delay_min_us, delay_max_us, delay_period_s, 0, (uint32_t)srate_hz) <
SRSRAN_SUCCESS) {
ERROR("Init");
return SRSRAN_ERROR;
}
if (srsran_channel_awgn_init(&ctx->awgn, 0x0) < SRSRAN_SUCCESS) {
ERROR("Init");
return SRSRAN_ERROR;
}
if (srsran_channel_awgn_set_n0(&ctx->awgn, n0_dB) < SRSRAN_SUCCESS) {
ERROR("Init");
return SRSRAN_ERROR;
}
return SRSRAN_SUCCESS;
}
static void test_context_free(test_context_t* ctx)
{
if (ctx == NULL) {
return;
}
srsran_ringbuffer_free(&ctx->ringbuffer);
srsran_ssb_free(&ctx->ssb);
srsran_channel_delay_free(&ctx->delay);
srsran_channel_awgn_free(&ctx->awgn);
}
static int recv_callback(void* ptr, cf_t** rx_buffer, uint32_t nof_samples, srsran_timestamp_t* timestamp)
{
test_context_t* ctx = (test_context_t*)ptr;
// Check inputs
if (ctx == NULL || rx_buffer == NULL || rx_buffer[0] == NULL) {
return SRSRAN_ERROR;
}
// Calculate the number of required bytes
int required_nbytes = (int)sizeof(cf_t) * nof_samples;
// Execute subframe until the ringbuffer has data
while (srsran_ringbuffer_status(&ctx->ringbuffer) < required_nbytes) {
// Reset buffer
srsran_vec_cf_zero(buffer, sf_len);
if (ctx->sf_idx % (SRSRAN_NOF_SF_X_FRAME / 2) == 0) {
// Prepare PBCH message
srsran_pbch_msg_nr_t pbch_msg = {};
pbch_msg.ssb_idx = 0;
pbch_msg.hrf = ctx->sf_idx >= (SRSRAN_NOF_SF_X_FRAME / 2);
pbch_msg.sfn_4lsb = ctx->sfn & 0b1111U;
// Encode SSB
if (srsran_ssb_add(&ctx->ssb, pci, &pbch_msg, buffer, buffer) < SRSRAN_SUCCESS) {
return SRSRAN_ERROR;
}
}
// Run channel
run_channel(ctx);
// Write in the ring buffer
if (srsran_ringbuffer_write(&ctx->ringbuffer, buffer, (int)sf_len * sizeof(cf_t)) < SRSRAN_SUCCESS) {
return SRSRAN_ERROR;
}
// Increment subframe index
ctx->sf_idx++;
// Increment SFN if required
if (ctx->sf_idx >= SRSRAN_NOF_SF_X_FRAME) {
ctx->sfn = (ctx->sfn + 1) % 1024U;
ctx->sf_idx = 0;
}
}
srsran_vec_cf_zero(buffer, sf_len);
// Read ringbuffer
if (srsran_ringbuffer_read(&ctx->ringbuffer, rx_buffer[0], required_nbytes) < SRSRAN_SUCCESS) {
return SRSRAN_ERROR;
}
// Setup timestamp
*timestamp = ctx->timestamp;
// Advance timestamp
srsran_timestamp_add(&ctx->timestamp, 0, (float)(nof_samples / srate_hz));
return SRSRAN_SUCCESS;
}
static int test_case_1(srsran_ue_sync_nr_t* ue_sync)
{
for (uint32_t sf_idx = 0; sf_idx < nof_sf; sf_idx++) {
srsran_ue_sync_nr_outcome_t outcome = {};
TESTASSERT(srsran_ue_sync_nr_zerocopy(ue_sync, &buffer, &outcome) == SRSRAN_SUCCESS);
// Print outcome
INFO("measure - zerocpy in-sync=%s sf_idx=%d sfn=%d timestamp=%f cfo_hz=%+.1f delay_us=%+.3f",
outcome.in_sync ? "y" : "n",
outcome.sf_idx,
outcome.sfn,
srsran_timestamp_real(&outcome.timestamp),
outcome.cfo_hz,
outcome.delay_us);
}
return SRSRAN_SUCCESS;
}
int main(int argc, char** argv)
{
int ret = SRSRAN_ERROR;
parse_args(argc, argv);
srate_hz = (double)SRSRAN_SUBC_SPACING_NR(carrier_scs) * srsran_min_symbol_sz_rb(carrier_nof_prb);
sf_len = (uint32_t)ceil(srate_hz / 1000.0);
buffer = srsran_vec_cf_malloc(sf_len);
buffer2 = srsran_vec_cf_malloc(sf_len);
test_context_t ctx = {};
srsran_ue_sync_nr_t ue_sync = {};
if (buffer == NULL) {
ERROR("Malloc");
goto clean_exit;
}
if (buffer2 == NULL) {
ERROR("Malloc");
goto clean_exit;
}
srsran_ue_sync_nr_args_t ue_sync_args = {};
ue_sync_args.max_srate_hz = srate_hz;
ue_sync_args.min_scs = carrier_scs;
ue_sync_args.recv_obj = &ctx;
ue_sync_args.recv_callback = &recv_callback;
ue_sync_args.disable_cfo = false;
if (srsran_ue_sync_nr_init(&ue_sync, &ue_sync_args) < SRSRAN_SUCCESS) {
ERROR("Init");
goto clean_exit;
}
srsran_ue_sync_nr_cfg_t ue_sync_cfg = {};
ue_sync_cfg.ssb.srate_hz = srate_hz;
ue_sync_cfg.ssb.center_freq_hz = 3.5e9;
ue_sync_cfg.ssb.ssb_freq_hz = 3.5e9 - 960e3;
ue_sync_cfg.ssb.scs = ssb_scs;
ue_sync_cfg.ssb.pattern = SRSRAN_SSB_PATTERN_C;
ue_sync_cfg.N_id = pci;
if (srsran_ue_sync_nr_set_cfg(&ue_sync, &ue_sync_cfg) < SRSRAN_SUCCESS) {
ERROR("Init");
goto clean_exit;
}
if (test_context_init(&ctx) < SRSRAN_SUCCESS) {
ERROR("Init");
goto clean_exit;
}
if (test_case_1(&ue_sync) != SRSRAN_SUCCESS) {
ERROR("test case failed");
}
ret = SRSRAN_SUCCESS;
clean_exit:
srsran_ue_sync_nr_free(&ue_sync);
if (buffer) {
free(buffer);
}
if (buffer2) {
free(buffer2);
}
test_context_free(&ctx);
return ret;
}

314
lib/src/phy/ue/ue_sync_nr.c Normal file
View File

@ -0,0 +1,314 @@
/**
*
* \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/ue/ue_sync_nr.h"
#include "srsran/phy/utils/vector.h"
#define UE_SYNC_NR_DEFAULT_CFO_ALPHA 0.1
int srsran_ue_sync_nr_init(srsran_ue_sync_nr_t* q, const srsran_ue_sync_nr_args_t* args)
{
// Check inputs
if (q == NULL || args == NULL) {
return SRSRAN_ERROR_INVALID_INPUTS;
}
// Copy arguments
q->recv_obj = args->recv_obj;
q->recv_callback = args->recv_callback;
q->nof_rx_channels = args->nof_rx_channels == 0 ? 1 : args->nof_rx_channels;
q->disable_cfo = args->disable_cfo;
q->cfo_alpha = isnormal(args->cfo_alpha) ? args->cfo_alpha : UE_SYNC_NR_DEFAULT_CFO_ALPHA;
// Initialise SSB
srsran_ssb_args_t ssb_args = {};
ssb_args.max_srate_hz = args->max_srate_hz;
ssb_args.min_scs = args->min_scs;
ssb_args.enable_search = true;
ssb_args.enable_decode = true;
ssb_args.pbch_dmrs_thr = args->pbch_dmrs_thr;
if (srsran_ssb_init(&q->ssb, &ssb_args) < SRSRAN_SUCCESS) {
ERROR("Error SSB init");
return SRSRAN_ERROR;
}
// Allocate temporal buffer pointers
q->tmp_buffer = SRSRAN_MEM_ALLOC(cf_t*, q->nof_rx_channels);
if (q->tmp_buffer == NULL) {
ERROR("Error alloc");
return SRSRAN_ERROR;
}
return SRSRAN_SUCCESS;
}
void srsran_ue_sync_nr_free(srsran_ue_sync_nr_t* q)
{
// Check inputs
if (q == NULL) {
return;
}
srsran_ssb_free(&q->ssb);
if (q->tmp_buffer) {
free(q->tmp_buffer);
}
SRSRAN_MEM_ZERO(q, srsran_ue_sync_nr_t, 1);
}
int srsran_ue_sync_nr_set_cfg(srsran_ue_sync_nr_t* q, const srsran_ue_sync_nr_cfg_t* cfg)
{
// Check inputs
if (q == NULL || cfg == NULL) {
return SRSRAN_ERROR_INVALID_INPUTS;
}
// Copy parameters
q->N_id = cfg->N_id;
q->srate_hz = cfg->ssb.srate_hz;
// Calculate new subframe size
q->sf_sz = (uint32_t)round(1e-3 * q->srate_hz);
// Configure SSB
if (srsran_ssb_set_cfg(&q->ssb, &cfg->ssb) < SRSRAN_SUCCESS) {
ERROR("Error configuring SSB");
return SRSRAN_ERROR;
}
// Transition to find
q->state = SRSRAN_UE_SYNC_NR_STATE_FIND;
return SRSRAN_SUCCESS;
}
static void ue_sync_nr_reset_feedback(srsran_ue_sync_nr_t* q)
{
SRSRAN_MEM_ZERO(&q->feedback, srsran_csi_trs_measurements_t, 1);
}
static void ue_sync_nr_apply_feedback(srsran_ue_sync_nr_t* q)
{
// Skip any update if there is no feedback available
if (q->feedback.nof_re == 0) {
return;
}
// Update number of samples
q->avg_delay_us = q->feedback.delay_us;
q->next_rf_sample_offset = (uint32_t)round((double)q->avg_delay_us * (q->srate_hz * 1e-6));
// Integrate CFO
if (q->disable_cfo) {
q->cfo_hz = SRSRAN_VEC_SAFE_EMA(q->feedback.cfo_hz, q->cfo_hz, q->cfo_alpha);
} else {
q->cfo_hz += q->feedback.cfo_hz * q->cfo_alpha;
}
// Reset feedback
ue_sync_nr_reset_feedback(q);
}
static int ue_sync_nr_run_find(srsran_ue_sync_nr_t* q, cf_t* buffer)
{
srsran_csi_trs_measurements_t measurements = {};
srsran_pbch_msg_nr_t pbch_msg = {};
// Find SSB, measure PSS/SSS and decode PBCH
if (srsran_ssb_find(&q->ssb, buffer, q->N_id, &measurements, &pbch_msg) < SRSRAN_SUCCESS) {
ERROR("Error finding SSB");
return SRSRAN_ERROR;
}
// If the PBCH message was NOT decoded, early return
if (!pbch_msg.crc) {
return SRSRAN_SUCCESS;
}
// Reset feedback to prevent any previous erroneous measurement
ue_sync_nr_reset_feedback(q);
// Set feedback measurement
srsran_combine_csi_trs_measurements(&q->feedback, &measurements, &q->feedback);
// Apply feedback
ue_sync_nr_apply_feedback(q);
// Setup context
q->ssb_idx = pbch_msg.ssb_idx;
q->sf_idx = srsran_ssb_candidate_sf_idx(&q->ssb, pbch_msg.ssb_idx, pbch_msg.hrf);
q->sfn = pbch_msg.sfn_4lsb;
// Transition to track only if the measured delay is below 2.4 microseconds
if (measurements.delay_us < 2.4f) {
q->state = SRSRAN_UE_SYNC_NR_STATE_TRACK;
}
return SRSRAN_SUCCESS;
}
static int ue_sync_nr_run_track(srsran_ue_sync_nr_t* q, cf_t* buffer)
{
srsran_csi_trs_measurements_t measurements = {};
srsran_pbch_msg_nr_t pbch_msg = {};
uint32_t half_frame = q->sf_idx / (SRSRAN_NOF_SF_X_FRAME / 2);
// Check if the SSB selected candidate index shall be received in this subframe
bool is_ssb_opportunity = (q->sf_idx == srsran_ssb_candidate_sf_idx(&q->ssb, q->ssb_idx, half_frame > 0));
// If
if (is_ssb_opportunity) {
// Measure PSS/SSS and decode PBCH
if (srsran_ssb_track(&q->ssb, buffer, q->N_id, q->ssb_idx, half_frame, &measurements, &pbch_msg) < SRSRAN_SUCCESS) {
ERROR("Error finding SSB");
return SRSRAN_ERROR;
}
// If the PBCH message was NOT decoded, transition to track
if (!pbch_msg.crc) {
q->state = SRSRAN_UE_SYNC_NR_STATE_FIND;
return SRSRAN_SUCCESS;
}
// Otherwise feedback measurements and apply
srsran_combine_csi_trs_measurements(&q->feedback, &measurements, &q->feedback);
}
// Apply accumulated feedback
ue_sync_nr_apply_feedback(q);
return SRSRAN_SUCCESS;
}
static int ue_sync_nr_recv(srsran_ue_sync_nr_t* q, cf_t** buffer, srsran_timestamp_t* timestamp)
{
// Verify callback and srate are valid
if (q->recv_callback == NULL && !isnormal(q->srate_hz)) {
return SRSRAN_ERROR;
}
uint32_t buffer_offset = 0;
uint32_t nof_samples = q->sf_sz;
if (q->next_rf_sample_offset > 0) {
// Discard a number of samples from RF
if (q->recv_callback(q->recv_obj, buffer, (uint32_t)q->next_rf_sample_offset, timestamp) < SRSRAN_SUCCESS) {
return SRSRAN_ERROR;
}
} else {
// Adjust receive buffer
buffer_offset = (uint32_t)(-q->next_rf_sample_offset);
nof_samples = (uint32_t)(q->sf_sz + q->next_rf_sample_offset);
}
q->next_rf_sample_offset = 0;
// Select buffer offsets
for (uint32_t chan = 0; chan < q->nof_rx_channels; chan++) {
// Set buffer to NULL if not present
if (buffer[chan] == NULL) {
q->tmp_buffer[chan] = NULL;
continue;
}
// Initialise first offset samples to zero
if (buffer_offset > 0) {
srsran_vec_cf_zero(buffer[chan], buffer_offset);
}
// Set to sample index
q->tmp_buffer[chan] = &buffer[chan][buffer_offset];
}
// Receive
if (q->recv_callback(q->recv_obj, q->tmp_buffer, nof_samples, timestamp) < SRSRAN_SUCCESS) {
return SRSRAN_ERROR;
}
// Compensate CFO
for (uint32_t chan = 0; chan < q->nof_rx_channels; chan++) {
if (buffer[chan] != 0 && !q->disable_cfo) {
srsran_vec_apply_cfo(buffer[chan], q->cfo_hz / q->srate_hz, buffer[chan], (int)q->sf_sz);
}
}
return SRSRAN_SUCCESS;
}
int srsran_ue_sync_nr_zerocopy(srsran_ue_sync_nr_t* q, cf_t** buffer, srsran_ue_sync_nr_outcome_t* outcome)
{
// Check inputs
if (q == NULL || buffer == NULL || outcome == NULL) {
return SRSRAN_ERROR_INVALID_INPUTS;
}
// Verify callback is valid
if (q->recv_callback == NULL) {
return SRSRAN_ERROR;
}
// Receive
if (ue_sync_nr_recv(q, buffer, &outcome->timestamp) < SRSRAN_SUCCESS) {
ERROR("Error receiving baseband");
return SRSRAN_ERROR;
}
// Run FSM
switch (q->state) {
case SRSRAN_UE_SYNC_NR_STATE_IDLE:
// Do nothing
break;
case SRSRAN_UE_SYNC_NR_STATE_FIND:
if (ue_sync_nr_run_find(q, buffer[0]) < SRSRAN_SUCCESS) {
ERROR("Error running find");
return SRSRAN_ERROR;
}
break;
case SRSRAN_UE_SYNC_NR_STATE_TRACK:
if (ue_sync_nr_run_track(q, buffer[0]) < SRSRAN_SUCCESS) {
ERROR("Error running track");
return SRSRAN_ERROR;
}
break;
}
// Increment subframe counter
q->sf_idx++;
// Increment SFN
if (q->sf_idx >= SRSRAN_NOF_SF_X_FRAME) {
q->sfn = (q->sfn + 1) % 1024;
q->sf_idx = 0;
}
// Fill outcome
outcome->in_sync = (q->state == SRSRAN_UE_SYNC_NR_STATE_TRACK);
outcome->sf_idx = q->sf_idx;
outcome->sfn = q->sfn;
outcome->cfo_hz = q->cfo_hz;
outcome->delay_us = q->avg_delay_us;
return SRSRAN_SUCCESS;
}
int srsran_ue_sync_nr_feedback(srsran_ue_sync_nr_t* q, const srsran_csi_trs_measurements_t* measurements)
{
if (q == NULL || measurements == NULL) {
return SRSRAN_ERROR_INVALID_INPUTS;
}
// Accumulate feedback proportional to the number of elements provided by the measurement
srsran_combine_csi_trs_measurements(&q->feedback, measurements, &q->feedback);
return SRSRAN_SUCCESS;
}