diff --git a/lib/include/srsran/phy/ch_estimation/csi_rs.h b/lib/include/srsran/phy/ch_estimation/csi_rs.h index fc87326bb..4975fec5f 100644 --- a/lib/include/srsran/phy/ch_estimation/csi_rs.h +++ b/lib/include/srsran/phy/ch_estimation/csi_rs.h @@ -39,26 +39,6 @@ */ #define SRSRAN_CSI_RS_NOF_FREQ_DOMAIN_ALLOC_OTHER 6 -/** - * @brief Describes a measurement for NZP-CSI-RS - * @note Used for fine tracking RSRP, SNR, CFO, SFO, and so on - * @note srsran_csi_channel_measurements_t is used for CSI report generation - */ -typedef struct SRSRAN_API { - float rsrp; ///< Linear scale RSRP - float rsrp_dB; ///< Logarithm scale RSRP relative to full-scale - float epre; ///< Linear scale EPRE - float epre_dB; ///< Logarithm scale EPRE relative to full-scale - float n0; ///< Linear noise level - float n0_dB; ///< Logarithm scale noise level relative to full-scale - float snr_dB; ///< Signal to noise ratio in decibels - float cfo_hz; ///< Carrier frequency offset in Hz. Only set if more than 2 symbols are available in a TRS set - float cfo_hz_max; ///< Maximum CFO in Hz that can be measured. It is set to 0 if CFO cannot be estimated - float delay_us; ///< Average measured delay in microseconds - uint32_t nof_re; ///< Number of available RE for the measurement, it can be used for weighting among different - ///< measurements -} srsran_csi_trs_measurements_t; - /** * @brief Calculates if the given periodicity implies a CSI-RS transmission in the given slot * @remark Described in TS 36.211 section 7.4.1.5.3 Mapping to physical resources diff --git a/lib/include/srsran/phy/common/phy_common_nr.h b/lib/include/srsran/phy/common/phy_common_nr.h index 26c43347e..e6ac1fad2 100644 --- a/lib/include/srsran/phy/common/phy_common_nr.h +++ b/lib/include/srsran/phy/common/phy_common_nr.h @@ -155,25 +155,31 @@ extern "C" { * @brief Number of NR N_id_1 Physical Cell Identifier (PCI) as described in TS 38.211 section 7.4.2.1 Physical-layer * cell identities */ -#define SRSRAN_NOF_N_ID_1 336 +#define SRSRAN_NOF_NID_1_NR 336 /** * @brief Number of NR N_id_2 Physical Cell Identifier (PCI) as described in TS 38.211 section 7.4.2.1 Physical-layer * cell identities */ -#define SRSRAN_NOF_N_ID_2 3 +#define SRSRAN_NOF_NID_2_NR 3 + +/** + * @brief Number of NR N_id Physical Cell Identifier (PCI) as described in TS 38.211 section 7.4.2.1 Physical-layer + * cell identities + */ +#define SRSRAN_NOF_NID_NR (SRSRAN_NOF_NID_1_NR * SRSRAN_NOF_NID_2_NR) /** * @brief Compute N_id_1 from the Physical Cell Identifier (PCI) as described in TS 38.211 section 7.4.2.1 * Physical-layer cell identities */ -#define SRSRAN_N_ID_1(N_ID) ((N_ID) / SRSRAN_NOF_N_ID_2) +#define SRSRAN_NID_1_NR(N_ID) ((N_ID) / SRSRAN_NOF_NID_2_NR) /** * @brief Compute N_id_2 from the Physical Cell Identifier (PCI) as described in TS 38.211 section 7.4.2.1 * Physical-layer cell identities */ -#define SRSRAN_N_ID_2(N_ID) ((N_ID) % SRSRAN_NOF_N_ID_2) +#define SRSRAN_NID_2_NR(N_ID) ((N_ID) % SRSRAN_NOF_NID_2_NR) /** * @brief SSB number of resource elements, described in TS 38.211 section 7.4.3.1 Time-frequency structure of an SS/PBCH @@ -417,6 +423,26 @@ typedef struct SRSRAN_API { srsran_tdd_pattern_t pattern2; } srsran_tdd_config_nr_t; +/** + * @brief Describes a measurement based on NZP-CSI-RS or SSB-CSI + * @note Used for tracking RSRP, SNR, CFO, SFO, and so on + * @note srsran_csi_channel_measurements_t is used for CSI report generation + */ +typedef struct SRSRAN_API { + float rsrp; ///< Linear scale RSRP + float rsrp_dB; ///< Logarithm scale RSRP relative to full-scale + float epre; ///< Linear scale EPRE + float epre_dB; ///< Logarithm scale EPRE relative to full-scale + float n0; ///< Linear noise level + float n0_dB; ///< Logarithm scale noise level relative to full-scale + float snr_dB; ///< Signal to noise ratio in decibels + float cfo_hz; ///< Carrier frequency offset in Hz. Only set if more than 2 symbols are available in a TRS set + float cfo_hz_max; ///< Maximum CFO in Hz that can be measured. It is set to 0 if CFO cannot be estimated + float delay_us; ///< Average measured delay in microseconds + uint32_t nof_re; ///< Number of available RE for the measurement, it can be used for weighting among different + ///< measurements +} srsran_csi_trs_measurements_t; + /** * @brief Get the RNTI type name for NR * @param rnti_type RNTI type name @@ -490,10 +516,10 @@ SRSRAN_API uint32_t srsran_min_symbol_sz_rb(uint32_t nof_prb); * @remark All symbol size reference and values are taken from TS 38.211 section 5.3 OFDM baseband signal generation * @param l0 First symbol index within the slot * @param l1 Second symbol index within the slot - * @param numerology NR Carrier numerology + * @param scs Subcarrier spacing * @return Returns the time in seconds between the two symbols if the condition above is satisfied, 0 seconds otherwise */ -SRSRAN_API float srsran_symbol_distance_s(uint32_t l0, uint32_t l1, uint32_t numerology); +SRSRAN_API float srsran_symbol_distance_s(uint32_t l0, uint32_t l1, srsran_subcarrier_spacing_t scs); /** * @brief Decides whether a given slot is configured as Downlink @@ -515,6 +541,15 @@ SRSRAN_API bool srsran_tdd_nr_is_ul(const srsran_tdd_config_nr_t* cfg, uint32_t SRSRAN_API int srsran_carrier_to_cell(const srsran_carrier_nr_t* carrier, srsran_cell_t* cell); +/** + * @brief Writes Channel State Information measurement into a string + * @param meas Provides the measurement + * @param str Provides string + * @param str_len Maximum string length + * @return The number of writen characters + */ +SRSRAN_API uint32_t srsran_csi_meas_info(const srsran_csi_trs_measurements_t* meas, char* str, uint32_t str_len); + #ifdef __cplusplus } #endif diff --git a/lib/include/srsran/phy/phch/pbch_nr.h b/lib/include/srsran/phy/phch/pbch_nr.h new file mode 100644 index 000000000..cc31fa3b3 --- /dev/null +++ b/lib/include/srsran/phy/phch/pbch_nr.h @@ -0,0 +1,25 @@ +/** + * + * \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_PBCH_NR_H +#define SRSRAN_PBCH_NR_H + +#include "srsran/config.h" + +/** + * @brief Descibes the NR PBCH message + */ +typedef struct SRSRAN_API { + // TBD +} srsran_pbch_msg_nr_t; + +#endif // SRSRAN_PBCH_NR_H diff --git a/lib/include/srsran/phy/sync/pss_nr.h b/lib/include/srsran/phy/sync/pss_nr.h new file mode 100644 index 000000000..e56a10dfa --- /dev/null +++ b/lib/include/srsran/phy/sync/pss_nr.h @@ -0,0 +1,49 @@ +/** + * + * \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_PSS_NR_H +#define SRSRAN_PSS_NR_H + +#include "srsran/config.h" +#include "srsran/phy/common/phy_common_nr.h" +#include + +/** + * @brief NR PSS sequence length in frequency domain + */ +#define SRSRAN_PSS_NR_LEN 127 + +/** + * @brief NR PSS Symbol number + */ +#define SRSRAN_PSS_NR_SYMBOL_IDX 0 + +/** + * @brief Put NR PSS sequence in the SSB grid + * @remark Described in TS 38.211 section 7.4.2.2 Primary synchronization signal + * @param ssb_grid SSB resource grid + * @param N_id_2 Physical cell ID 2 + * @param beta PSS power allocation + * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + */ +SRSRAN_API int srsran_pss_nr_put(cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id_2, float beta); + +/** + * @brief Extracts the NR PSS Least Square Estimates (LSE) from the SSB grid + * @param ssb_grid received SSB resource grid + * @param N_id_2 Physical cell ID 2 + * @param lse Provides LSE pointer + * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + */ +SRSRAN_API int srsran_pss_nr_extract_lse(const cf_t* ssb_grid, uint32_t N_id_2, cf_t lse[SRSRAN_PSS_NR_LEN]); + +#endif // SRSRAN_PSS_NR_H diff --git a/lib/include/srsran/phy/sync/ssb.h b/lib/include/srsran/phy/sync/ssb.h index 9ab51d202..31e47a647 100644 --- a/lib/include/srsran/phy/sync/ssb.h +++ b/lib/include/srsran/phy/sync/ssb.h @@ -16,6 +16,7 @@ #include "srsran/config.h" #include "srsran/phy/common/phy_common_nr.h" #include "srsran/phy/dft/dft.h" +#include "srsran/phy/phch/pbch_nr.h" #include /** @@ -23,24 +24,39 @@ */ #define SRSRAN_SSB_DEFAULT_MAX_SRATE_HZ 61.44e6 +/** + * @brief Default SSB minimum subcarrier spacing + */ +#define SRSRAN_SSB_DEFAULT_MIN_SCS srsran_subcarrier_spacing_15kHz + +/** + * @brief Default beta value, used in case they are set to zero + */ +#define SRSRAN_SSB_DEFAULT_BETA 1.0f + /** * @brief Describes SSB object initializatoion arguments */ typedef struct SRSRAN_API { - double srate_hz; ///< Maximum sampling rate in Hz (common for gNb and UE), set to zero to use default - bool enable_correlate; ///< Enables PSS/SSS correlation and peak search (UE cell search) - bool enable_pbch_encode; ///< Enables PBCH Encoder (intended for gNb) - bool enable_pbch_decode; ///< Enables PBCH Decoder (intented for UE) - bool enable_measure; ///< Enables PSS/SSS CSI measurements + double max_srate_hz; ///< Maximum sampling rate in Hz (common for gNb and UE), set to zero to use default + srsran_subcarrier_spacing_t min_scs; ///< Minimum subcarrier spacing + bool enable_correlate; ///< Enables PSS/SSS correlation and peak search (UE cell search) + bool enable_encode; ///< Enables PBCH Encoder (intended for gNb) + bool enable_decode; ///< Enables PBCH Decoder (intented for UE) + bool enable_measure; ///< Enables PSS/SSS CSI measurements } srsran_ssb_args_t; /** * @brief Describes SSB configuration arguments */ typedef struct SRSRAN_API { - double srate_hz; ///< Current sampling rate in Hz - double ssb_freq_offset_hz; ///< SSB base-band frequency offset - srsran_subcarrier_spacing_t ssb_scs; ///< SSB configured Subcarrier spacing + double srate_hz; ///< Current sampling rate in Hz + double freq_offset_hz; ///< SSB base-band frequency offset + srsran_subcarrier_spacing_t scs; ///< SSB configured Subcarrier spacing + 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; /** @@ -51,9 +67,12 @@ typedef struct SRSRAN_API { srsran_ssb_cfg_t cfg; ///< Stores last configuration /// Sampling rate dependent parameters - uint32_t symbol_sz; ///< Current symbol size - uint32_t cp0_sz; ///< First symbol cyclic prefix size - uint32_t cp_sz; ///< Other symbol cyclic prefix size + float scs_hz; ///< Subcarrier spacing in Hz + uint32_t max_symbol_sz; ///< Maximum symbol size given the minimum supported SCS and sampling rate + uint32_t symbol_sz; ///< Current SSB symbol size (for the given base-band sampling rate) + int32_t offset; ///< Current SSB integer offset (multiple of SCS) + uint32_t cp0_sz; ///< First symbol cyclic prefix size + uint32_t cp_sz; ///< Other symbol cyclic prefix size /// Internal Objects // srsran_pbch_nr_t pbch; ///< PBCH object for encoding/decoding @@ -61,14 +80,15 @@ typedef struct SRSRAN_API { srsran_dft_plan_t ifft; ///< IFFT object for modulating the SSB srsran_dft_plan_t fft; ///< FFT object for demodulate the SSB. - /// Frequency domain temporal data - cf_t ssb_grid[SRSRAN_SSB_NOF_RE]; ///< SSB resource grid + /// Frequency/Time domain temporal data + cf_t* tmp_freq; + cf_t* tmp_time; /// Time domain sequences - cf_t* pss[SRSRAN_NOF_N_ID_2]; ///< PSS signal for each possible N_id_2 - cf_t* sss[SRSRAN_NOF_N_ID_1]; ///< SSS signal for each possible N_id_1 + // cf_t* pss[SRSRAN_NOF_NID_2_NR]; ///< PSS signal for each possible N_id_2 + // cf_t* sss[SRSRAN_NOF_NID_1_NR]; ///< SSS signal for each possible N_id_1 -} srsran_ssb_nr_t; +} srsran_ssb_t; /** * @brief Initialises configures NR SSB with the given arguments @@ -76,13 +96,13 @@ typedef struct SRSRAN_API { * @param args NR PSS initialization arguments * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise */ -SRSRAN_API int srsran_ssb_init(srsran_ssb_nr_t* q, const srsran_ssb_args_t* args); +SRSRAN_API int srsran_ssb_init(srsran_ssb_t* q, const srsran_ssb_args_t* args); /** * @brief Frees NR SSB object * @param q SSB object */ -SRSRAN_API void srsran_ssb_free(srsran_ssb_nr_t* q); +SRSRAN_API void srsran_ssb_free(srsran_ssb_t* q); /** * @brief Sets SSB configuration with the current SSB configuration @@ -90,27 +110,33 @@ SRSRAN_API void srsran_ssb_free(srsran_ssb_nr_t* q); * @param cfg Current SSB configuration * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise */ -SRSRAN_API int srsran_ssb_set_cfg(srsran_ssb_nr_t* q, const srsran_ssb_cfg_t* cfg); +SRSRAN_API int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg); /** * @brief Decodes PBCH in the given time domain signal * @param q SSB object * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise */ -SRSRAN_API int srsran_ssb_decode_pbch(srsran_ssb_nr_t* q, const cf_t* in, srsran_pbch_msg_t* msg); +SRSRAN_API int srsran_ssb_decode_pbch(srsran_ssb_t* q, const cf_t* in, srsran_pbch_msg_nr_t* msg); /** * @brief Adds SSB to a given signal in time domain * @param q SSB object + * @param N_id Physical Cell Identifier + * @param msg NR PBCH message to transmit * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise */ -SRSRAN_API int srsran_ssb_add(srsran_ssb_nr_t* q, const srsran_pbch_msg_t* msg, const cf_t* in, cf_t* out); +SRSRAN_API int +srsran_ssb_add(srsran_ssb_t* q, uint32_t N_id, const srsran_pbch_msg_nr_t* msg, const cf_t* in, cf_t* out); /** * @brief Perform Channel State Information (CSI) measurement from the SSB * @param q NR PSS object + * @param N_id Physical Cell Identifier + * @param in Base-band signal + * @param meas SSB-based CSI measurement * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise */ SRSRAN_API int -srsran_ssb_csi_measure(srsran_ssb_nr_t* q, const cf_t* in, srsran_csi_channel_measurements_t* measurement); +srsran_ssb_csi_measure(srsran_ssb_t* q, uint32_t N_id, const cf_t* in, srsran_csi_trs_measurements_t* meas); #endif // SRSRAN_SSB_H diff --git a/lib/include/srsran/phy/sync/sss_nr.h b/lib/include/srsran/phy/sync/sss_nr.h new file mode 100644 index 000000000..87ec5092c --- /dev/null +++ b/lib/include/srsran/phy/sync/sss_nr.h @@ -0,0 +1,64 @@ +/** + * + * \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_SSS_NR_H +#define SRSRAN_SSS_NR_H + +#include "srsran/config.h" +#include "srsran/phy/common/phy_common_nr.h" +#include + +/** + * @brief NR SSS sequence length in frequency domain + */ +#define SRSRAN_SSS_NR_LEN 127 + +/** + * @brief NR SSS Symbol number + */ +#define SRSRAN_SSS_NR_SYMBOL_IDX 2 + +/** + * @brief Put NR SSS sequence in the SSB grid + * @remark Described in TS 38.211 section 7.4.2.3 Secondary synchronization signal + * @param ssb_grid SSB resource grid + * @param N_id_1 Physical cell ID 1 + * @param N_id_2 Physical cell ID 2 + * @param beta SSS power allocation + * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + */ +SRSRAN_API int srsran_sss_nr_put(cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id_1, uint32_t N_id_2, float beta); + +/** + * @brief Extracts the NR SSS Least Square Estimates (LSE) from the SSB grid + * @param ssb_grid received SSB resource grid + * @param N_id_1 Physical cell ID 1 + * @param N_id_2 Physical cell ID 2 + * @param lse Provides LSE pointer + * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + */ +SRSRAN_API int +srsran_sss_nr_extract_lse(const cf_t* ssb_grid, uint32_t N_id_1, uint32_t N_id_2, cf_t lse[SRSRAN_SSS_NR_LEN]); + +/** + * @brief Find the best SSS sequence given the N_id_2 and the SSB resource grid + * @attention Assumes the SSB is synchronized and the average delay is pre-compensated + * @param ssb_grid The SSB resource grid to search + * @param N_id_2 Fix N_id_2 to search, it reduces the search space 1/3 + * @param norm_corr Normalised correlation of the best found sequence + * @param found_N_id_1 The N_id_1 of the best sequence + * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + */ +SRSRAN_API int +srsran_sss_nr_find(const cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id_2, float* norm_corr, uint32_t* found_N_id_1); + +#endif // SRSRAN_SSS_NR_H diff --git a/lib/src/phy/common/phy_common_nr.c b/lib/src/phy/common/phy_common_nr.c index fd10c134d..126049ad3 100644 --- a/lib/src/phy/common/phy_common_nr.c +++ b/lib/src/phy/common/phy_common_nr.c @@ -174,8 +174,10 @@ float srsran_symbol_distance_s(uint32_t l0, uint32_t l1, srsran_subcarrier_spaci // Compute reference FFT size uint32_t N = (2048 + 144) * count + extra_cp; + float TS = SRSRAN_LTE_TS / (float)(1U << (uint32_t)scs); + // Return symbol distance in microseconds - return (N << (uint32_t)scs) * SRSRAN_LTE_TS; + return (float)N * TS; } bool srsran_tdd_nr_is_dl(const srsran_tdd_config_nr_t* cfg, uint32_t numerology, uint32_t slot_idx) @@ -267,3 +269,21 @@ int srsran_carrier_to_cell(const srsran_carrier_nr_t* carrier, srsran_cell_t* ce return SRSRAN_SUCCESS; } + +uint32_t srsran_csi_meas_info(const srsran_csi_trs_measurements_t* meas, char* str, uint32_t str_len) +{ + if (meas == NULL || str == NULL || str_len == 0) { + return 0; + } + + return srsran_print_check(str, + str_len, + 0, + "rsrp=%+.1f epre=%+.1f n0=%+.1f snr=%+.1f cfo=%+.1f delay=%+.1f", + meas->rsrp_dB, + meas->epre_dB, + meas->n0_dB, + meas->snr_dB, + meas->cfo_hz, + meas->delay_us); +} diff --git a/lib/src/phy/sync/pss_nr.c b/lib/src/phy/sync/pss_nr.c new file mode 100644 index 000000000..488ee0f19 --- /dev/null +++ b/lib/src/phy/sync/pss_nr.c @@ -0,0 +1,96 @@ +/** + * + * \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/sync/pss_nr.h" +#include "srsran/phy/utils/vector.h" + +/** + * NR PSS First Subcarrier index + */ +#define PSS_NR_SUBC_BEGIN 56 + +/** + * Calculates Sequence circular offset + */ +#define PSS_NR_SEQUENCE_M(N_id_2) ((43U * (N_id_2)) % SRSRAN_PSS_NR_LEN) + +/** + * Pregenerated modulated sequence + */ +static cf_t pss_nr_d[SRSRAN_PSS_NR_LEN] = {}; + +/** + * Sequence generation as described in TS 38.211 clause 7.4.2.2.1 + */ +__attribute__((constructor)) __attribute__((unused)) static void pss_nr_pregen() +{ + // Initialise M sequence x + uint32_t x[SRSRAN_PSS_NR_LEN + 7]; + x[6] = 1; + x[5] = 1; + x[4] = 1; + x[3] = 0; + x[2] = 1; + x[1] = 1; + x[0] = 0; + + // Generate M sequence x + for (uint32_t i = 0; i < SRSRAN_PSS_NR_LEN; i++) { + x[i + 7] = (x[i + 4] + x[i]) % 2; + } + + // Modulate M sequence d + for (uint32_t i = 0; i < SRSRAN_PSS_NR_LEN; i++) { + pss_nr_d[i] = 1.0f - 2.0f * (float)x[i]; + } +} + +int srsran_pss_nr_put(cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id_2, float beta) +{ + // Verify inputs + if (ssb_grid == NULL || N_id_2 >= SRSRAN_NOF_NID_2_NR) { + return SRSRAN_ERROR; + } + + // Calculate generation parameters + uint32_t m = PSS_NR_SEQUENCE_M(N_id_2); + uint32_t copy_sz_1 = SRSRAN_PSS_NR_LEN - m; + uint32_t grid_idx_1 = SRSRAN_PSS_NR_SYMBOL_IDX * SRSRAN_SSB_BW_SUBC + PSS_NR_SUBC_BEGIN; + uint32_t grid_idx_2 = grid_idx_1 + copy_sz_1; + + // Copy sequence from offset to the end + srsran_vec_sc_prod_cfc(&pss_nr_d[m], beta, &ssb_grid[grid_idx_1], copy_sz_1); + + // Copy sequence from 0 to offset + srsran_vec_sc_prod_cfc(&pss_nr_d[0], beta, &ssb_grid[grid_idx_2], m); + + return SRSRAN_SUCCESS; +} + +int srsran_pss_nr_extract_lse(const cf_t* ssb_grid, uint32_t N_id_2, cf_t lse[SRSRAN_PSS_NR_LEN]) +{ + // Verify inputs + if (ssb_grid == NULL || N_id_2 >= SRSRAN_NOF_NID_2_NR || lse == NULL) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + // Extract PSS + srsran_vec_cf_copy( + lse, &ssb_grid[SRSRAN_PSS_NR_SYMBOL_IDX * SRSRAN_SSB_BW_SUBC + PSS_NR_SUBC_BEGIN], SRSRAN_PSS_NR_LEN); + + // Estimate + uint32_t m = PSS_NR_SEQUENCE_M(N_id_2); + srsran_vec_prod_ccc(&pss_nr_d[m], lse, lse, SRSRAN_PSS_NR_LEN - m); + srsran_vec_prod_ccc(&pss_nr_d[0], &lse[SRSRAN_PSS_NR_LEN - m], &lse[SRSRAN_PSS_NR_LEN - m], m); + + return SRSRAN_SUCCESS; +} diff --git a/lib/src/phy/sync/ssb.c b/lib/src/phy/sync/ssb.c new file mode 100644 index 000000000..c1b5da84f --- /dev/null +++ b/lib/src/phy/sync/ssb.c @@ -0,0 +1,363 @@ +/** + * + * \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/sync/ssb.h" +#include "srsran/phy/sync/pss_nr.h" +#include "srsran/phy/sync/sss_nr.h" +#include "srsran/phy/utils/debug.h" +#include "srsran/phy/utils/vector.h" +#include + +/* + * Maximum allowed maximum sampling rate error in Hz + */ +#define SSB_SRATE_MAX_ERROR_HZ 0.01 + +/* + * Maximum allowed maximum frequency error offset in Hz + */ +#define SSB_FREQ_OFFSET_MAX_ERROR_HZ 0.01 + +int srsran_ssb_init(srsran_ssb_t* q, const srsran_ssb_args_t* args) +{ + // Verify input parameters + if (q == NULL || args == NULL) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + // Copy arguments + q->args = *args; + + // Check if the maximum sampling rate is in range, force default otherwise + if (!isnormal(q->args.max_srate_hz) || q->args.max_srate_hz < 0.0) { + q->args.max_srate_hz = SRSRAN_SSB_DEFAULT_MAX_SRATE_HZ; + } + + q->scs_hz = (float)SRSRAN_SUBC_SPACING_NR(q->args.min_scs); + q->max_symbol_sz = (uint32_t)round(q->args.max_srate_hz / q->scs_hz); + + // Allocate temporal data + q->tmp_time = srsran_vec_cf_malloc(q->max_symbol_sz); + q->tmp_freq = srsran_vec_cf_malloc(q->max_symbol_sz); + if (q->tmp_time == NULL || q->tmp_time == NULL) { + ERROR("Malloc"); + return SRSRAN_ERROR; + } + + return SRSRAN_SUCCESS; +} + +void srsran_ssb_free(srsran_ssb_t* q) +{ + if (q == NULL) { + return; + } + + if (q->tmp_time != NULL) { + free(q->tmp_time); + } + + if (q->tmp_freq != NULL) { + free(q->tmp_freq); + } + + srsran_dft_plan_free(&q->ifft); + srsran_dft_plan_free(&q->fft); + + SRSRAN_MEM_ZERO(q, srsran_ssb_t, 1); +} + +int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg) +{ + // Verify input parameters + if (q == NULL || cfg == NULL) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + // Calculate subcarrier spacing in Hz + q->scs_hz = (double)SRSRAN_SUBC_SPACING_NR(cfg->scs); + + // Calculate SSB symbol size and integer offset + uint32_t symbol_sz = (uint32_t)round(cfg->srate_hz / q->scs_hz); + q->offset = (uint32_t)(cfg->freq_offset_hz / q->scs_hz); + q->cp0_sz = (160U * symbol_sz) / 2048U; + q->cp_sz = (144U * symbol_sz) / 2048U; + + // Calculate SSB sampling error and check + double ssb_srate_error_Hz = ((double)symbol_sz * q->scs_hz) - cfg->srate_hz; + if (fabs(ssb_srate_error_Hz) > SSB_SRATE_MAX_ERROR_HZ) { + ERROR("Invalid sampling rate (%.2f MHz)", cfg->srate_hz / 1e6); + return SRSRAN_ERROR; + } + + // Calculate SSB offset error and check + double ssb_offset_error_Hz = ((double)q->offset * q->scs_hz) - cfg->freq_offset_hz; + if (fabs(ssb_offset_error_Hz) > SSB_FREQ_OFFSET_MAX_ERROR_HZ) { + ERROR("SSB Offset error exceeds maximum allowed"); + return SRSRAN_ERROR; + } + + // 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); + } + + // Replan iFFT + if ((q->args.enable_encode) && q->symbol_sz != symbol_sz) { + // free the current IFFT, it internally checks if the plan was created + srsran_dft_plan_free(&q->ifft); + + // Creates DFT plan + if (srsran_dft_plan_guru_c(&q->ifft, (int)symbol_sz, SRSRAN_DFT_BACKWARD, q->tmp_freq, q->tmp_time, 1, 1, 1, 1, 1) < + SRSRAN_SUCCESS) { + ERROR("Error creating iDFT"); + return SRSRAN_ERROR; + } + } + + // Replan FFT + if ((q->args.enable_measure || q->args.enable_decode) && q->symbol_sz != symbol_sz) { + // free the current FFT, it internally checks if the plan was created + srsran_dft_plan_free(&q->fft); + + // Creates DFT plan + if (srsran_dft_plan_guru_c(&q->fft, (int)symbol_sz, SRSRAN_DFT_FORWARD, q->tmp_time, q->tmp_freq, 1, 1, 1, 1, 1) < + SRSRAN_SUCCESS) { + ERROR("Error creating iDFT"); + return SRSRAN_ERROR; + } + } + + // Finally, copy configuration + q->cfg = *cfg; + q->symbol_sz = symbol_sz; + + if (!isnormal(q->cfg.beta_pss)) { + q->cfg.beta_pss = SRSRAN_SSB_DEFAULT_BETA; + } + + if (!isnormal(q->cfg.beta_sss)) { + q->cfg.beta_sss = SRSRAN_SSB_DEFAULT_BETA; + } + + if (!isnormal(q->cfg.beta_pbch)) { + q->cfg.beta_pbch = SRSRAN_SSB_DEFAULT_BETA; + } + + if (!isnormal(q->cfg.beta_pbch_dmrs)) { + q->cfg.beta_pbch = SRSRAN_SSB_DEFAULT_BETA; + } + + return SRSRAN_SUCCESS; +} + +int srsran_ssb_add(srsran_ssb_t* q, uint32_t N_id, const srsran_pbch_msg_nr_t* msg, const cf_t* in, cf_t* out) +{ + // Verify input parameters + if (q == NULL || N_id >= SRSRAN_NOF_NID_NR || msg == NULL || in == NULL || out == NULL) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + if (!q->args.enable_encode) { + ERROR("SSB is not configured for encode"); + return SRSRAN_ERROR; + } + + uint32_t N_id_1 = SRSRAN_NID_1_NR(N_id); + uint32_t N_id_2 = SRSRAN_NID_2_NR(N_id); + cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {}; + + // Put PSS + if (srsran_pss_nr_put(ssb_grid, N_id_2, q->cfg.beta_pss) < SRSRAN_SUCCESS) { + ERROR("Error putting PSS"); + return SRSRAN_ERROR; + } + + // Put SSS + if (srsran_sss_nr_put(ssb_grid, N_id_1, N_id_2, q->cfg.beta_sss) < SRSRAN_SUCCESS) { + ERROR("Error putting PSS"); + return SRSRAN_ERROR; + } + + // Put PBCH DMRS + // ... + + // Put PBCH payload + // ... + + // Initialise frequency domain + srsran_vec_cf_zero(q->tmp_freq, q->symbol_sz); + + // Modulate + const cf_t* in_ptr = in; + cf_t* out_ptr = out; + for (uint32_t l = 0; l < SRSRAN_SSB_DURATION_NSYMB; l++) { + // Get CP length + uint32_t cp_len = (l == 0) ? q->cp0_sz : q->cp_sz; + + // Select symbol in grid + cf_t* ptr = &ssb_grid[l * SRSRAN_SSB_BW_SUBC]; + + // Map grid into frequency domain symbol + if (q->offset >= SRSRAN_SSB_BW_SUBC / 2) { + srsran_vec_cf_copy(&q->tmp_freq[q->offset - SRSRAN_SSB_BW_SUBC / 2], ptr, SRSRAN_SSB_BW_SUBC); + } else if (q->offset <= -SRSRAN_SSB_BW_SUBC / 2) { + srsran_vec_cf_copy(&q->tmp_freq[q->symbol_sz + q->offset - SRSRAN_SSB_BW_SUBC / 2], ptr, SRSRAN_SSB_BW_SUBC); + } else { + srsran_vec_cf_copy(&q->tmp_freq[0], &ptr[SRSRAN_SSB_BW_SUBC / 2 - q->offset], SRSRAN_SSB_BW_SUBC / 2 + q->offset); + srsran_vec_cf_copy( + &q->tmp_freq[q->symbol_sz - SRSRAN_SSB_BW_SUBC / 2 + q->offset], &ptr[0], SRSRAN_SSB_BW_SUBC / 2 - q->offset); + } + + // Convert to time domain + srsran_dft_run_guru_c(&q->ifft); + + // Normalise output + float norm = sqrtf((float)q->symbol_sz); + if (isnormal(norm)) { + srsran_vec_sc_prod_cfc(q->tmp_time, 1.0f / norm, q->tmp_time, q->symbol_sz); + } + + // Add cyclic prefix to input; + srsran_vec_sum_ccc(in_ptr, &q->tmp_time[q->symbol_sz - cp_len], out_ptr, cp_len); + in_ptr += cp_len; + out_ptr += cp_len; + + // Add symbol to the input baseband + srsran_vec_sum_ccc(in_ptr, q->tmp_time, out_ptr, q->symbol_sz); + in_ptr += q->symbol_sz; + out_ptr += q->symbol_sz; + } + + return SRSRAN_SUCCESS; +} + +int srsran_ssb_csi_measure(srsran_ssb_t* q, uint32_t N_id, const cf_t* in, srsran_csi_trs_measurements_t* meas) +{ + // Verify inputs + if (q == NULL || N_id >= SRSRAN_NOF_NID_NR || in == NULL || meas == NULL || !isnormal(q->scs_hz)) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + if (!q->args.enable_measure) { + ERROR("SSB is not configured for measure"); + return SRSRAN_ERROR; + } + + cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {}; + + // Demodulate + const cf_t* in_ptr = in; + for (uint32_t l = 0; l < SRSRAN_SSB_DURATION_NSYMB; l++) { + // Get CP length + uint32_t cp_len = (l == 0) ? q->cp0_sz : q->cp_sz; + + // Advance half CP, to avoid inter symbol interference + in_ptr += SRSRAN_FLOOR(cp_len, 2); + + // Copy FFT window in temporal time domain buffer + srsran_vec_cf_copy(q->tmp_time, in_ptr, q->symbol_sz); + in_ptr += q->symbol_sz + SRSRAN_CEIL(cp_len, 2); + + // Convert to frequency domain + srsran_dft_run_guru_c(&q->fft); + + // Compensate half CP delay + srsran_vec_apply_cfo(q->tmp_freq, SRSRAN_CEIL(cp_len, 2) / (float)(q->symbol_sz), q->tmp_freq, q->symbol_sz); + + // Select symbol in grid + cf_t* ptr = &ssb_grid[l * SRSRAN_SSB_BW_SUBC]; + + // Map frequency domain symbol into the SSB grid + if (q->offset >= SRSRAN_SSB_BW_SUBC / 2) { + srsran_vec_cf_copy(ptr, &q->tmp_freq[q->offset - SRSRAN_SSB_BW_SUBC / 2], SRSRAN_SSB_BW_SUBC); + } else if (q->offset <= -SRSRAN_SSB_BW_SUBC / 2) { + srsran_vec_cf_copy(ptr, &q->tmp_freq[q->symbol_sz + q->offset - SRSRAN_SSB_BW_SUBC / 2], SRSRAN_SSB_BW_SUBC); + } else { + srsran_vec_cf_copy(&ptr[SRSRAN_SSB_BW_SUBC / 2 - q->offset], &q->tmp_freq[0], SRSRAN_SSB_BW_SUBC / 2 + q->offset); + srsran_vec_cf_copy( + &ptr[0], &q->tmp_freq[q->symbol_sz - SRSRAN_SSB_BW_SUBC / 2 + q->offset], SRSRAN_SSB_BW_SUBC / 2 - q->offset); + } + + // Normalize + float norm = sqrtf((float)q->symbol_sz); + if (isnormal(norm)) { + srsran_vec_sc_prod_cfc(ptr, 1.0f / norm, ptr, SRSRAN_SSB_BW_SUBC); + } + } + + // Extract PSS LSE + cf_t pss_lse[SRSRAN_PSS_NR_LEN]; + cf_t sss_lse[SRSRAN_SSS_NR_LEN]; + if (srsran_pss_nr_extract_lse(ssb_grid, SRSRAN_NID_2_NR(N_id), pss_lse) < SRSRAN_SUCCESS || + srsran_sss_nr_extract_lse(ssb_grid, SRSRAN_NID_1_NR(N_id), SRSRAN_NID_2_NR(N_id), sss_lse) < SRSRAN_SUCCESS) { + ERROR("Error extracting LSE"); + return SRSRAN_ERROR; + } + + // Estimate average delay + float delay_pss_norm = srsran_vec_estimate_frequency(pss_lse, SRSRAN_PSS_NR_LEN); + float delay_sss_norm = srsran_vec_estimate_frequency(sss_lse, SRSRAN_SSS_NR_LEN); + float delay_avg_norm = (delay_pss_norm + delay_sss_norm) / 2.0f; + float delay_avg_us = 1e6f * delay_avg_norm / q->scs_hz; + + // Pre-compensate delay + for (uint32_t l = 0; l < SRSRAN_SSB_DURATION_NSYMB; l++) { + srsran_vec_apply_cfo( + &ssb_grid[SRSRAN_SSB_BW_SUBC * l], delay_avg_norm, &ssb_grid[SRSRAN_SSB_BW_SUBC * l], SRSRAN_SSB_BW_SUBC); + } + + // Extract LSE again + if (srsran_pss_nr_extract_lse(ssb_grid, SRSRAN_NID_2_NR(N_id), pss_lse) < SRSRAN_SUCCESS || + srsran_sss_nr_extract_lse(ssb_grid, SRSRAN_NID_1_NR(N_id), SRSRAN_NID_2_NR(N_id), sss_lse) < SRSRAN_SUCCESS) { + ERROR("Error extracting LSE"); + return SRSRAN_ERROR; + } + + // Estimate average EPRE + float epre_pss = srsran_vec_avg_power_cf(pss_lse, SRSRAN_PSS_NR_LEN); + float epre_sss = srsran_vec_avg_power_cf(sss_lse, SRSRAN_SSS_NR_LEN); + float epre = (epre_pss + epre_sss) / 2.0f; + + // Compute correlation + cf_t corr_pss = srsran_vec_acc_cc(pss_lse, SRSRAN_PSS_NR_LEN) / SRSRAN_PSS_NR_LEN; + cf_t corr_sss = srsran_vec_acc_cc(sss_lse, SRSRAN_SSS_NR_LEN) / SRSRAN_SSS_NR_LEN; + + // Compute CFO in Hz + float distance_s = srsran_symbol_distance_s(SRSRAN_PSS_NR_SYMBOL_IDX, SRSRAN_SSS_NR_SYMBOL_IDX, q->cfg.scs); + float cfo_hz_max = 1.0f / distance_s; + float cfo_hz = cargf(corr_pss * conjf(corr_sss)) / (2.0f * M_PI) * cfo_hz_max; + + // Compute average RSRP + float rsrp = (SRSRAN_CSQABS(corr_pss) + SRSRAN_CSQABS(corr_sss)) / 2.0f; + + // Compute Noise + float n0 = 1e-9; // Almost 0 + if (epre > rsrp) { + n0 = epre - rsrp; + } + + // Put measurements together + meas->epre = epre; + meas->epre_dB = srsran_convert_power_to_dB(epre); + meas->rsrp = rsrp; + meas->epre_dB = srsran_convert_power_to_dB(rsrp); + meas->n0 = n0; + meas->n0_dB = srsran_convert_power_to_dB(n0); + meas->snr_dB = meas->rsrp_dB - meas->n0_dB; + meas->cfo_hz = cfo_hz; + meas->cfo_hz_max = cfo_hz_max; + meas->delay_us = delay_avg_us; // Convert the delay to microseconds + meas->nof_re = SRSRAN_PSS_NR_LEN + SRSRAN_SSS_NR_LEN; + + return SRSRAN_SUCCESS; +} \ No newline at end of file diff --git a/lib/src/phy/sync/sss_nr.c b/lib/src/phy/sync/sss_nr.c new file mode 100644 index 000000000..0ef3fa0bc --- /dev/null +++ b/lib/src/phy/sync/sss_nr.c @@ -0,0 +1,208 @@ +/** + * + * \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/sync/sss_nr.h" +#include "srsran/phy/utils/vector.h" + +/** + * NR SSS First Subcarrier index + */ +#define SSS_NR_SUBC_BEGIN 56 + +/** + * Number of possible M1 shifts + */ +#define SSS_NR_NOF_M1 112U + +/** + * Number of possible M0 shifts + */ +#define SSS_NR_NOF_M0 SRSRAN_FLOOR(SRSRAN_NOF_NID_1_NR, SSS_NR_NOF_M1) + +/** + * Calculates Sequence circular offset M0 value + */ +#define SSS_NR_SEQUENCE_M0(N_id_1, N_id_2) \ + ((15U * SRSRAN_FLOOR(N_id_1, SSS_NR_NOF_M1) + 5 * (N_id_2)) % SRSRAN_SSS_NR_LEN) + +/** + * Calculates Sequence circular offset M1 value + */ +#define SSS_NR_SEQUENCE_M1(N_id_1) (N_id_1 % SSS_NR_NOF_M1) + +/** + * Pregenerated modulated sequences + */ +static cf_t sss_nr_d0[SRSRAN_SSS_NR_LEN] = {}; +static cf_t sss_nr_d1[SRSRAN_SSS_NR_LEN] = {}; + +/** + * Sequence generation as described in TS 38.211 clause 7.4.2.2.1 + */ +__attribute__((constructor)) __attribute__((unused)) static void sss_nr_pregen() +{ + // Initialise M sequence x0 + uint32_t x0[SRSRAN_SSS_NR_LEN + 7]; + x0[6] = 0; + x0[5] = 0; + x0[4] = 0; + x0[3] = 0; + x0[2] = 0; + x0[1] = 0; + x0[0] = 1; + + // Initialise M sequence x1 + uint32_t x1[SRSRAN_SSS_NR_LEN + 7]; + x1[6] = 0; + x1[5] = 0; + x1[4] = 0; + x1[3] = 0; + x1[2] = 0; + x1[1] = 0; + x1[0] = 1; + + // Generate M sequence x + for (uint32_t i = 0; i < SRSRAN_SSS_NR_LEN; i++) { + x0[i + 7] = (x0[i + 4] + x0[i]) % 2; + x1[i + 7] = (x1[i + 1] + x1[i]) % 2; + } + + // Modulate M sequence d + for (uint32_t i = 0; i < SRSRAN_SSS_NR_LEN; i++) { + sss_nr_d0[i] = 1.0f - 2.0f * (float)x0[i]; + sss_nr_d1[i] = 1.0f - 2.0f * (float)x1[i]; + } +} + +int srsran_sss_nr_put(cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id_1, uint32_t N_id_2, float beta) +{ + // Verify inputs + if (ssb_grid == NULL || N_id_1 >= SRSRAN_NOF_NID_1_NR || N_id_2 >= SRSRAN_NOF_NID_2_NR) { + return SRSRAN_ERROR; + } + + // Calculate generation parameters + uint32_t m0 = SSS_NR_SEQUENCE_M0(N_id_1, N_id_2); + uint32_t m1 = SSS_NR_SEQUENCE_M1(N_id_1); + uint32_t grid_idx_m0_1 = SRSRAN_SSS_NR_SYMBOL_IDX * SRSRAN_SSB_BW_SUBC + SSS_NR_SUBC_BEGIN; + uint32_t grid_idx_m0_2 = grid_idx_m0_1 + (SRSRAN_SSS_NR_LEN - m0); + uint32_t grid_idx_m1_1 = SRSRAN_SSS_NR_SYMBOL_IDX * SRSRAN_SSB_BW_SUBC + SSS_NR_SUBC_BEGIN; + uint32_t grid_idx_m1_2 = grid_idx_m1_1 + (SRSRAN_SSS_NR_LEN - m1); + + // Copy d0 sequence first part from m0 to the end + srsran_vec_sc_prod_cfc(&sss_nr_d0[m0], beta, &ssb_grid[grid_idx_m0_1], SRSRAN_SSS_NR_LEN - m0); + + // Copy d0 sequence second part from 0 to m0 + srsran_vec_sc_prod_cfc(&sss_nr_d0[0], beta, &ssb_grid[grid_idx_m0_2], m0); + + // Multiply d1 sequence first part from m1 to the end + srsran_vec_prod_ccc(&ssb_grid[grid_idx_m1_1], &sss_nr_d1[m1], &ssb_grid[grid_idx_m1_1], SRSRAN_SSS_NR_LEN - m1); + + // Multiply d1 sequence second part from 0 to m1 + srsran_vec_prod_ccc(&ssb_grid[grid_idx_m1_2], &sss_nr_d1[0], &ssb_grid[grid_idx_m1_2], m1); + + return SRSRAN_SUCCESS; +} + +int srsran_sss_nr_extract_lse(const cf_t* ssb_grid, uint32_t N_id_1, uint32_t N_id_2, cf_t lse[SRSRAN_SSS_NR_LEN]) +{ + // Verify inputs + if (ssb_grid == NULL || N_id_2 >= SRSRAN_NOF_NID_2_NR || lse == NULL) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + // Extract SSS + srsran_vec_cf_copy( + lse, &ssb_grid[SRSRAN_SSS_NR_SYMBOL_IDX * SRSRAN_SSB_BW_SUBC + SSS_NR_SUBC_BEGIN], SRSRAN_SSS_NR_LEN); + + // Estimate + uint32_t m0 = SSS_NR_SEQUENCE_M0(N_id_1, N_id_2); + srsran_vec_prod_ccc(&sss_nr_d0[m0], lse, lse, SRSRAN_SSS_NR_LEN - m0); + srsran_vec_prod_ccc(&sss_nr_d0[0], &lse[SRSRAN_SSS_NR_LEN - m0], &lse[SRSRAN_SSS_NR_LEN - m0], m0); + + uint32_t m1 = SSS_NR_SEQUENCE_M1(N_id_1); + srsran_vec_prod_ccc(&sss_nr_d1[m1], lse, lse, SRSRAN_SSS_NR_LEN - m1); + srsran_vec_prod_ccc(&sss_nr_d1[0], &lse[SRSRAN_SSS_NR_LEN - m1], &lse[SRSRAN_SSS_NR_LEN - m1], m1); + + return SRSRAN_SUCCESS; +} + +int srsran_sss_nr_find(const cf_t ssb_grid[SRSRAN_SSB_NOF_RE], + uint32_t N_id_2, + float* norm_corr, + uint32_t* found_N_id_1) +{ + // Verify inputs + if (ssb_grid == NULL || N_id_2 >= SRSRAN_NOF_NID_2_NR) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + // Extract SSS ptr + const cf_t* sss_ptr = &ssb_grid[SRSRAN_SSS_NR_SYMBOL_IDX * SRSRAN_SSB_BW_SUBC + SSS_NR_SUBC_BEGIN]; + + // Measure SSS average power + float avg_power = srsran_vec_avg_power_cf(sss_ptr, SRSRAN_SSS_NR_LEN); + + // If the measured power is invalid or zero, consider no SSS signal + if (!isnormal(avg_power)) { + if (norm_corr) { + *norm_corr = 0.0f; + } + return SRSRAN_SUCCESS; + } + + // Search state + float max_corr = -INFINITY; //< Stores best correlation + uint32_t N_id_1 = 0; //< Best N_id_1 + + // Iterate over all M1 shifts + for (uint32_t m1 = 0; m1 < SSS_NR_NOF_M1; m1++) { + // Temporal storage of SSS after applying d1 sequence + cf_t sss_seq_m1[SRSRAN_SSS_NR_LEN]; + + // Apply d1 sequence fist part + srsran_vec_prod_ccc(&sss_ptr[0], &sss_nr_d1[m1], &sss_seq_m1[0], SRSRAN_SSS_NR_LEN - m1); + + // Apply d1 sequence second part + srsran_vec_prod_ccc(&sss_ptr[SRSRAN_SSS_NR_LEN - m1], &sss_nr_d1[0], &sss_seq_m1[SRSRAN_SSS_NR_LEN - m1], m1); + + // Iterate over all N_id_1 with the given m1 sequence + for (uint32_t N_id_1_blind = m1; N_id_1_blind < SRSRAN_NOF_NID_1; N_id_1_blind += SSS_NR_NOF_M1) { + uint32_t m0 = SSS_NR_SEQUENCE_M0(N_id_1_blind, N_id_2); + + cf_t sss_seq_m0[SRSRAN_SSS_NR_LEN]; + + // Apply d0 sequence fist part + srsran_vec_prod_ccc(&sss_seq_m1[0], &sss_nr_d0[m0], &sss_seq_m0[0], SRSRAN_SSS_NR_LEN - m0); + + // Apply d0 sequence second part + srsran_vec_prod_ccc(&sss_seq_m1[SRSRAN_SSS_NR_LEN - m0], &sss_nr_d0[0], &sss_seq_m0[SRSRAN_SSS_NR_LEN - m0], m0); + + // Correlate + float corr = SRSRAN_CSQABS(srsran_vec_acc_cc(sss_seq_m0, SRSRAN_SSS_NR_LEN)) / avg_power; + if (corr > max_corr) { + N_id_1 = N_id_1_blind; + max_corr = corr; + } + } + } + + if (norm_corr) { + *norm_corr = max_corr; + } + + if (found_N_id_1) { + *found_N_id_1 = N_id_1; + } + + return SRSRAN_SUCCESS; +} diff --git a/lib/src/phy/sync/test/CMakeLists.txt b/lib/src/phy/sync/test/CMakeLists.txt index 968c4146a..bfec15b99 100644 --- a/lib/src/phy/sync/test/CMakeLists.txt +++ b/lib/src/phy/sync/test/CMakeLists.txt @@ -121,3 +121,13 @@ target_link_libraries(cfo_test srsran_phy) add_test(cfo_test_1 cfo_test -f 0.12345 -n 1000) add_test(cfo_test_2 cfo_test -f 0.99849 -n 1000) + + +######################################################################## +# NE TEST +######################################################################## + +add_executable(ssb_measure_test ssb_measure_test.c) +target_link_libraries(ssb_measure_test srsran_phy) + +add_test(ssb_measure_test ssb_measure_test) diff --git a/lib/src/phy/sync/test/ssb_measure_test.c b/lib/src/phy/sync/test/ssb_measure_test.c new file mode 100644 index 000000000..1b201a57f --- /dev/null +++ b/lib/src/phy/sync/test/ssb_measure_test.c @@ -0,0 +1,185 @@ +/** + * + * \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/sync/ssb.h" +#include "srsran/phy/utils/debug.h" +#include "srsran/phy/utils/vector.h" +#include +#include +#include + +// NR parameters +static uint32_t carrier_nof_prb = 52; +static srsran_subcarrier_spacing_t carrier_scs = srsran_subcarrier_spacing_15kHz; +static srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_30kHz; + +// Channel parameters +static int32_t delay_n = 1; +static float cfo_hz = 100.0f; +static float n0_dB = -30.0f; + +// Test context +static srsran_channel_awgn_t awgn = {}; +static double srate_hz = 0.0f; // Base-band sampling rate +static float delay_us = 0.0f; // Base-band sampling rate +static uint32_t sf_len = 0; // Subframe length +static cf_t* buffer = NULL; // Base-band buffer + +#define RSRP_MAX_ERROR 1.0f +#define EPRE_MAX_ERROR 1.0f +#define N0_MAX_ERROR 2.0f +#define SNR_MAX_ERROR 2.0f +#define CFO_MAX_ERROR (cfo_hz * 0.3f) +#define DELAY_MAX_ERROR (delay_us * 0.1f) + +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); + } + } +} + +static void run_channel() +{ + // Delay + for (uint32_t i = 0; i < sf_len; i++) { + buffer[i] = buffer[(i + delay_n) % sf_len]; + } + + // CFO + srsran_vec_apply_cfo(buffer, -cfo_hz / srate_hz, buffer, sf_len); + + // AWGN + srsran_channel_awgn_run_c(&awgn, buffer, buffer, sf_len); +} + +static int test_case_1(srsran_ssb_t* ssb) +{ + uint64_t t_usec = 0; + srsran_ssb_cfg_t ssb_cfg = {}; + ssb_cfg.srate_hz = srate_hz; + ssb_cfg.freq_offset_hz = 0.0; + ssb_cfg.scs = ssb_scs; + + TESTASSERT(srsran_ssb_set_cfg(ssb, &ssb_cfg) == SRSRAN_SUCCESS); + + // Build PBCH message + srsran_pbch_msg_nr_t pbch_msg = {}; + + for (uint32_t pci = 0; pci < SRSRAN_NOF_NID_NR; pci++) { + struct timeval t[3] = {}; + + // Initialise baseband + srsran_vec_cf_zero(buffer, sf_len); + + // Add the SSB base-band + TESTASSERT(srsran_ssb_add(ssb, pci, &pbch_msg, buffer, buffer) == SRSRAN_SUCCESS); + + // Run channel + run_channel(); + + // Measure + srsran_csi_trs_measurements_t meas = {}; + TESTASSERT(srsran_ssb_csi_measure(ssb, pci, buffer, &meas) == SRSRAN_SUCCESS); + + gettimeofday(&t[1], NULL); + TESTASSERT(srsran_ssb_csi_measure(ssb, pci, buffer, &meas) == SRSRAN_SUCCESS); + gettimeofday(&t[2], NULL); + get_time_interval(t); + t_usec += t[0].tv_usec + t[0].tv_sec * 1000000UL; + + // Print measurement + char str[512]; + srsran_csi_meas_info(&meas, str, sizeof(str)); + INFO("test_case_1 - pci=%d %s", pci, str); + + // Assert measurements + TESTASSERT(fabsf(meas.rsrp_dB - 0.0f) < RSRP_MAX_ERROR); + TESTASSERT(fabsf(meas.epre_dB - 0.0f) < EPRE_MAX_ERROR); + TESTASSERT(fabsf(meas.n0_dB - n0_dB) < N0_MAX_ERROR); + TESTASSERT(fabsf(meas.snr_dB + n0_dB) < SNR_MAX_ERROR); + TESTASSERT(fabsf(meas.cfo_hz - cfo_hz) < CFO_MAX_ERROR); + TESTASSERT(fabsf(meas.delay_us + delay_us) < DELAY_MAX_ERROR); + } + + INFO("test_case_1 - %.1f usec/measurement", (double)t_usec / (double)SRSRAN_NOF_NID_NR); + + 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); + delay_us = 1e6f * delay_n / (float)srate_hz; + sf_len = (uint32_t)ceil(srate_hz / 1000.0); + buffer = srsran_vec_cf_malloc(sf_len); + + srsran_ssb_t ssb = {}; + srsran_ssb_args_t ssb_args = {}; + ssb_args.enable_encode = true; + ssb_args.enable_measure = true; + + if (buffer == NULL) { + ERROR("Malloc"); + goto clean_exit; + } + + if (srsran_channel_awgn_init(&awgn, 0x0) < SRSRAN_SUCCESS) { + ERROR("AWGN"); + goto clean_exit; + } + + if (srsran_channel_awgn_set_n0(&awgn, n0_dB) < SRSRAN_SUCCESS) { + ERROR("AWGN"); + goto clean_exit; + } + + if (srsran_ssb_init(&ssb, &ssb_args) < SRSRAN_SUCCESS) { + ERROR("Init"); + goto clean_exit; + } + + if (test_case_1(&ssb) != SRSRAN_SUCCESS) { + ERROR("test case failed"); + } + + ret = SRSRAN_SUCCESS; + +clean_exit: + srsran_ssb_free(&ssb); + + srsran_channel_awgn_free(&awgn); + + if (buffer) { + free(buffer); + } + + return ret; +} \ No newline at end of file diff --git a/srsue/src/phy/nr/cc_worker.cc b/srsue/src/phy/nr/cc_worker.cc index b1b1d1c68..df0982fbf 100644 --- a/srsue/src/phy/nr/cc_worker.cc +++ b/srsue/src/phy/nr/cc_worker.cc @@ -321,13 +321,11 @@ bool cc_worker::measure_csi() continue; } - logger.info("NZP-CSI-RS (TRS): id=%d rsrp=%+.1f epre=%+.1f snr=%+.1f cfo=%+.1f delay=%.1f", - resource_set_id, - trs_measurements.rsrp_dB, - trs_measurements.epre_dB, - trs_measurements.snr_dB, - trs_measurements.cfo_hz, - trs_measurements.delay_us); + if (logger.info.enabled()) { + std::array str = {}; + srsran_csi_meas_info(&trs_measurements, str.data(), (uint32_t)str.size()); + logger.info("NZP-CSI-RS (TRS): id=%d %s", resource_set_id, str.data()); + } // Compute channel metrics and push it ch_metrics_t ch_metrics = {};