From eb7980f2b3c3b5b0d29f7513bbcb94bb2186853b Mon Sep 17 00:00:00 2001 From: Xavier Arteaga Date: Wed, 16 Jun 2021 18:10:34 +0200 Subject: [PATCH] Initial UE synchronization for NR --- lib/include/srsran/phy/common/phy_common_nr.h | 8 + lib/include/srsran/phy/sync/ssb.h | 68 +++- lib/include/srsran/phy/ue/ue_sync_nr.h | 144 ++++++++ lib/src/phy/common/phy_common_nr.c | 30 ++ lib/src/phy/sync/ssb.c | 226 +++++++++++++ lib/src/phy/ue/test/CMakeLists.txt | 4 + lib/src/phy/ue/test/ue_sync_nr_test.c | 304 +++++++++++++++++ lib/src/phy/ue/ue_sync_nr.c | 314 ++++++++++++++++++ 8 files changed, 1092 insertions(+), 6 deletions(-) create mode 100644 lib/include/srsran/phy/ue/ue_sync_nr.h create mode 100644 lib/src/phy/ue/test/ue_sync_nr_test.c create mode 100644 lib/src/phy/ue/ue_sync_nr.c diff --git a/lib/include/srsran/phy/common/phy_common_nr.h b/lib/include/srsran/phy/common/phy_common_nr.h index 1fb15d410..4f7c1776d 100644 --- a/lib/include/srsran/phy/common/phy_common_nr.h +++ b/lib/include/srsran/phy/common/phy_common_nr.h @@ -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 diff --git a/lib/include/srsran/phy/sync/ssb.h b/lib/include/srsran/phy/sync/ssb.h index 26f2f9c1c..c3aa8d4ff 100644 --- a/lib/include/srsran/phy/sync/ssb.h +++ b/lib/include/srsran/phy/sync/ssb.h @@ -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 diff --git a/lib/include/srsran/phy/ue/ue_sync_nr.h b/lib/include/srsran/phy/ue/ue_sync_nr.h new file mode 100644 index 000000000..da4fcb381 --- /dev/null +++ b/lib/include/srsran/phy/ue/ue_sync_nr.h @@ -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 diff --git a/lib/src/phy/common/phy_common_nr.c b/lib/src/phy/common/phy_common_nr.c index 2f206ef62..21f0151fb 100644 --- a/lib/src/phy/common/phy_common_nr.c +++ b/lib/src/phy/common/phy_common_nr.c @@ -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; +} diff --git a/lib/src/phy/sync/ssb.c b/lib/src/phy/sync/ssb.c index 28fa42666..e79826d13 100644 --- a/lib/src/phy/sync/ssb.c +++ b/lib/src/phy/sync/ssb.c @@ -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); +} diff --git a/lib/src/phy/ue/test/CMakeLists.txt b/lib/src/phy/ue/test/CMakeLists.txt index 9bf31d112..7c512f9ab 100644 --- a/lib/src/phy/ue/test/CMakeLists.txt +++ b/lib/src/phy/ue/test/CMakeLists.txt @@ -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) diff --git a/lib/src/phy/ue/test/ue_sync_nr_test.c b/lib/src/phy/ue/test/ue_sync_nr_test.c new file mode 100644 index 000000000..eff7480ce --- /dev/null +++ b/lib/src/phy/ue/test/ue_sync_nr_test.c @@ -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 +#include + +// 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; +} \ No newline at end of file diff --git a/lib/src/phy/ue/ue_sync_nr.c b/lib/src/phy/ue/ue_sync_nr.c new file mode 100644 index 000000000..97ae3df10 --- /dev/null +++ b/lib/src/phy/ue/ue_sync_nr.c @@ -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; +}