diff --git a/lib/include/srsran/phy/sync/ssb.h b/lib/include/srsran/phy/sync/ssb.h index c718350d3..4d1d4bd25 100644 --- a/lib/include/srsran/phy/sync/ssb.h +++ b/lib/include/srsran/phy/sync/ssb.h @@ -89,7 +89,7 @@ typedef struct SRSRAN_API { 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) + int32_t f_offset; ///< SSB integer frequency offset (multiple of SCS) between DC and the SSB center uint32_t cp_sz; ///< CP length for the given symbol size /// Other parameters @@ -143,22 +143,6 @@ SRSRAN_API void srsran_ssb_free(srsran_ssb_t* q); * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ 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 - * @note It currently expects an input buffer of half radio frame - * @param q SSB object - * @param N_id Physical Cell Identifier - * @param n_hf Number of hald radio frame, 0 or 1 - * @param ssb_idx SSB candidate index - * @param in Input baseband buffer - * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise - */ -SRSRAN_API int srsran_ssb_decode_pbch(srsran_ssb_t* q, - uint32_t N_id, - uint32_t n_hf, - uint32_t ssb_idx, - const cf_t* in, - srsran_pbch_msg_nr_t* msg); /** * @brief Searches for an SSB transmission and decodes the PBCH message @@ -178,6 +162,21 @@ SRSRAN_API int srsran_ssb_search(srsran_ssb_t* q, const cf_t* in, uint32_t nof_s */ SRSRAN_API bool srsran_ssb_send(srsran_ssb_t* q, uint32_t sf_idx); +/** + * + * @param q SSB object + * @param N_id Physical Cell Identifier + * @param msg NR PBCH message to transmit + * @param re_grid Resource grid pointer + * @param grid_sz_rb Resource grid bandwidth in subcarriers + * @return SRSRAN_SUCCES if inputs and data are correct, otherwise SRSRAN_ERROR code + */ +SRSRAN_API int srsran_ssb_put_grid(srsran_ssb_t* q, + uint32_t N_id, + const srsran_pbch_msg_nr_t* msg, + cf_t* re_grid, + uint32_t grid_bw_sc); + /** * @brief Adds SSB to a given signal in time domain * @param q SSB object @@ -188,6 +187,41 @@ SRSRAN_API bool srsran_ssb_send(srsran_ssb_t* q, uint32_t sf_idx); 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 Decodes PBCH in the given time domain signal + * @note It currently expects an input buffer of half radio frame + * @param q SSB object + * @param N_id Physical Cell Identifier + * @param n_hf Number of hald radio frame, 0 or 1 + * @param ssb_idx SSB candidate index + * @param in Input baseband buffer + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise + */ +SRSRAN_API int srsran_ssb_decode_pbch(srsran_ssb_t* q, + uint32_t N_id, + uint32_t n_hf, + uint32_t ssb_idx, + const cf_t* grid, + srsran_pbch_msg_nr_t* msg); + +/** + * @brief Decodes PBCH in the given resource grid + * @param q SSB object + * @param N_id Physical Cell Identifier + * @param n_hf Number of hald radio frame, 0 or 1 + * @param ssb_idx SSB candidate index + * @param grid Input resource grid buffer + * @param grid_sz_rb Resource grid bandwidth in subcarriers + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise + */ +SRSRAN_API int srsran_ssb_decode_grid(srsran_ssb_t* q, + uint32_t N_id, + uint32_t n_hf, + uint32_t ssb_idx, + const cf_t* in, + uint32_t grid_sz_rb, + 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 diff --git a/lib/src/phy/sync/ssb.c b/lib/src/phy/sync/ssb.c index 72e894afe..5149da502 100644 --- a/lib/src/phy/sync/ssb.c +++ b/lib/src/phy/sync/ssb.c @@ -527,21 +527,10 @@ bool srsran_ssb_send(srsran_ssb_t* q, uint32_t sf_idx) return (sf_idx % q->cfg.periodicity_ms == 0); } -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) +static int ssb_encode(srsran_ssb_t* q, uint32_t N_id, const srsran_pbch_msg_nr_t* msg, cf_t ssb_grid[SRSRAN_SSB_NOF_RE]) { - // 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] = {}; + uint32_t N_id_1 = SRSRAN_NID_1_NR(N_id); + uint32_t N_id_2 = SRSRAN_NID_2_NR(N_id); // Put PSS if (srsran_pss_nr_put(ssb_grid, N_id_2, q->cfg.beta_pss) < SRSRAN_SUCCESS) { @@ -580,6 +569,64 @@ int srsran_ssb_add(srsran_ssb_t* q, uint32_t N_id, const srsran_pbch_msg_nr_t* m return SRSRAN_ERROR; } + return SRSRAN_SUCCESS; +} + +SRSRAN_API int +srsran_ssb_put_grid(srsran_ssb_t* q, uint32_t N_id, const srsran_pbch_msg_nr_t* msg, cf_t* re_grid, uint32_t grid_bw_sc) +{ + // Verify input parameters + if (q == NULL || N_id >= SRSRAN_NOF_NID_NR || msg == NULL || re_grid == NULL || + grid_bw_sc * SRSRAN_NRE < SRSRAN_SSB_BW_SUBC) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + if (!q->args.enable_encode) { + ERROR("SSB is not configured for encode"); + return SRSRAN_ERROR; + } + + // Put signals in SSB grid + cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {}; + if (ssb_encode(q, N_id, msg, ssb_grid) < SRSRAN_SUCCESS) { + ERROR("Putting SSB in grid"); + return SRSRAN_ERROR; + } + + // First symbol in the half frame + uint32_t l_first = q->l_first[msg->ssb_idx]; + + // Frequency offset fom the bottom of the grid + uint32_t f_offset = grid_bw_sc / 2 + q->f_offset - SRSRAN_SSB_BW_SUBC / 2; + + // Put SSB grid in the actual resource grid + for (uint32_t l = 0; l < SRSRAN_SSB_DURATION_NSYMB; l++) { + srsran_vec_cf_copy( + &re_grid[grid_bw_sc * (l_first + l) + f_offset], &ssb_grid[SRSRAN_SSB_BW_SUBC * l], SRSRAN_SSB_BW_SUBC); + } + + 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; + } + + // Put signals in SSB grid + cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {}; + if (ssb_encode(q, N_id, msg, ssb_grid) < SRSRAN_SUCCESS) { + ERROR("Putting SSB in grid"); + return SRSRAN_ERROR; + } + // Select start symbol from SSB candidate index int t_offset = ssb_get_t_offset(q, msg->ssb_idx); if (t_offset < SRSRAN_SUCCESS) { @@ -1132,6 +1179,46 @@ static int ssb_decode_pbch(srsran_ssb_t* q, return SRSRAN_SUCCESS; } +int srsran_ssb_decode_grid(srsran_ssb_t* q, + uint32_t N_id, + uint32_t n_hf, + uint32_t ssb_idx, + const cf_t* re_grid, + uint32_t grid_bw_sc, + srsran_pbch_msg_nr_t* msg) +{ + // Verify input parameters + if (q == NULL || N_id >= SRSRAN_NOF_NID_NR || msg == NULL || re_grid == NULL || grid_bw_sc < SRSRAN_SSB_BW_SUBC) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + if (!q->args.enable_encode) { + ERROR("SSB is not configured for encode"); + return SRSRAN_ERROR; + } + + // First symbol in the half frame + uint32_t l_first = q->l_first[ssb_idx]; + + // Frequency offset fom the bottom of the grid + uint32_t f_offset = grid_bw_sc / 2 + q->f_offset - SRSRAN_SSB_BW_SUBC / 2; + + // Get SSB grid from resource grid + cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {}; + for (uint32_t l = 0; l < SRSRAN_SSB_DURATION_NSYMB; l++) { + srsran_vec_cf_copy( + &ssb_grid[SRSRAN_SSB_BW_SUBC * l], &re_grid[grid_bw_sc * (l_first + l) + f_offset], SRSRAN_SSB_BW_SUBC); + } + + // Decode PBCH + if (ssb_decode_pbch(q, N_id, n_hf, ssb_idx, ssb_grid, msg) < SRSRAN_SUCCESS) { + ERROR("Error decoding"); + return SRSRAN_ERROR; + } + + return SRSRAN_SUCCESS; +} + int srsran_ssb_decode_pbch(srsran_ssb_t* q, uint32_t N_id, uint32_t n_hf, diff --git a/lib/src/phy/sync/test/CMakeLists.txt b/lib/src/phy/sync/test/CMakeLists.txt index bba52f5ea..afde5fe7b 100644 --- a/lib/src/phy/sync/test/CMakeLists.txt +++ b/lib/src/phy/sync/test/CMakeLists.txt @@ -133,6 +133,9 @@ target_link_libraries(ssb_measure_test srsran_phy) add_executable(ssb_decode_test ssb_decode_test.c) target_link_libraries(ssb_decode_test srsran_phy) +add_executable(ssb_grid_test ssb_grid_test.c) +target_link_libraries(ssb_grid_test srsran_phy) + # For 1.0 GHz and 3.5 GHz Center frequencies foreach (CELL_FREQ 1000000 3500000) # For each supported Cell/Carrier subcarrier spacing @@ -153,6 +156,10 @@ foreach (CELL_FREQ 1000000 3500000) # Test SSB PBCH decoding add_nr_test(ssb_decode_test_${CELL_FREQ}000_${CELL_SCS}_${SSB_FREQ}000_${SSB_SCS}_${SSB_PATTERN} ssb_decode_test -F ${CELL_FREQ}000 -S ${CELL_SCS} -f ${SSB_FREQ}000 -s ${SSB_SCS} -P ${SSB_PATTERN}) + + # Test SSB grid put/get decoding + add_nr_test(ssb_grid_test_${CELL_FREQ}000_${CELL_SCS}_${SSB_FREQ}000_${SSB_SCS}_${SSB_PATTERN} ssb_grid_test + -F ${CELL_FREQ}000 -S ${CELL_SCS} -f ${SSB_FREQ}000 -s ${SSB_SCS} -P ${SSB_PATTERN}) endforeach () endforeach () endforeach () diff --git a/lib/src/phy/sync/test/ssb_grid_test.c b/lib/src/phy/sync/test/ssb_grid_test.c new file mode 100644 index 000000000..2020f900e --- /dev/null +++ b/lib/src/phy/sync/test/ssb_grid_test.c @@ -0,0 +1,180 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsran/common/test_common.h" +#include "srsran/phy/channel/ch_awgn.h" +#include "srsran/phy/sync/ssb.h" +#include "srsran/phy/utils/debug.h" +#include "srsran/phy/utils/vector.h" +#include +#include +#include +#include + +// NR parameters +static srsran_subcarrier_spacing_t carrier_scs = srsran_subcarrier_spacing_15kHz; +static double carrier_freq_hz = 3.5e9 + 960e3; +static srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_30kHz; +static double ssb_freq_hz = 3.5e9; +static srsran_ssb_patern_t ssb_pattern = SRSRAN_SSB_PATTERN_A; +static uint32_t ssb_idx = 0; // SSB candidate index to test +static uint32_t pci = 123; // N_id + +// Test context +static srsran_random_t random_gen = NULL; +static double srate_hz = 0.0f; // Base-band sampling rate +static cf_t* grid = NULL; // Resource grid +static uint32_t grid_bw_sc = 52 * SRSRAN_NRE; // Resource grid bandwidth in subcarriers + +static void usage(char* prog) +{ + printf("Usage: %s [v]\n", prog); + printf("\t-s SSB subcarrier spacing [default, %s kHz]\n", srsran_subcarrier_spacing_to_str(ssb_scs)); + printf("\t-f SSB center frequency [default, %.3f MHz]\n", ssb_freq_hz / 1e6); + printf("\t-S cell/carrier subcarrier spacing [default, %s kHz]\n", srsran_subcarrier_spacing_to_str(carrier_scs)); + printf("\t-F cell/carrier center frequency in Hz [default, %.3f MHz]\n", carrier_freq_hz / 1e6); + printf("\t-P SSB pattern [default, %s]\n", srsran_ssb_pattern_to_str(ssb_pattern)); + printf("\t-v [set srsran_verbose to debug, default none]\n"); +} + +static void parse_args(int argc, char** argv) +{ + int opt; + while ((opt = getopt(argc, argv, "SsFfPv")) != -1) { + switch (opt) { + case 's': + ssb_scs = srsran_subcarrier_spacing_from_str(argv[optind]); + if (ssb_scs == srsran_subcarrier_spacing_invalid) { + ERROR("Invalid SSB subcarrier spacing %s\n", argv[optind]); + exit(-1); + } + break; + case 'f': + ssb_freq_hz = strtod(argv[optind], NULL); + break; + case 'S': + carrier_scs = srsran_subcarrier_spacing_from_str(argv[optind]); + if (carrier_scs == srsran_subcarrier_spacing_invalid) { + ERROR("Invalid Cell/Carrier subcarrier spacing %s\n", argv[optind]); + exit(-1); + } + break; + case 'F': + carrier_freq_hz = strtod(argv[optind], NULL); + break; + case 'P': + ssb_pattern = srsran_ssb_pattern_fom_str(argv[optind]); + break; + case 'v': + increase_srsran_verbose_level(); + break; + default: + usage(argv[0]); + exit(-1); + } + } +} + +static void gen_pbch_msg(srsran_pbch_msg_nr_t* pbch_msg) +{ + // Default all to zero + SRSRAN_MEM_ZERO(pbch_msg, srsran_pbch_msg_nr_t, 1); + + // Generate payload + srsran_random_bit_vector(random_gen, pbch_msg->payload, SRSRAN_PBCH_MSG_NR_SZ); + + pbch_msg->ssb_idx = ssb_idx; + pbch_msg->crc = true; +} + +static int test_case(srsran_ssb_t* ssb) +{ + // SSB configuration + srsran_ssb_cfg_t ssb_cfg = {}; + ssb_cfg.srate_hz = srate_hz; + ssb_cfg.center_freq_hz = carrier_freq_hz; + ssb_cfg.ssb_freq_hz = ssb_freq_hz; + ssb_cfg.scs = ssb_scs; + ssb_cfg.pattern = ssb_pattern; + + TESTASSERT(srsran_ssb_set_cfg(ssb, &ssb_cfg) == SRSRAN_SUCCESS); + + // Build PBCH message + srsran_pbch_msg_nr_t pbch_msg_tx = {}; + gen_pbch_msg(&pbch_msg_tx); + + // Print encoded PBCH message + char str[512] = {}; + srsran_pbch_msg_info(&pbch_msg_tx, str, sizeof(str)); + INFO("test_case - encoded pci=%d %s", pci, str); + + // Add the SSB base-band + TESTASSERT(srsran_ssb_put_grid(ssb, pci, &pbch_msg_tx, grid, grid_bw_sc) == SRSRAN_SUCCESS); + + // Decode + srsran_pbch_msg_nr_t pbch_msg_rx = {}; + TESTASSERT(srsran_ssb_decode_grid(ssb, pci, pbch_msg_tx.hrf, pbch_msg_tx.ssb_idx, grid, grid_bw_sc, &pbch_msg_rx) == + SRSRAN_SUCCESS); + + // Print decoded PBCH message + srsran_pbch_msg_info(&pbch_msg_rx, str, sizeof(str)); + INFO("test_case - decoded pci=%d %s crc=%s", pci, str, pbch_msg_rx.crc ? "OK" : "KO"); + + // Assert PBCH message CRC + TESTASSERT(pbch_msg_rx.crc); + TESTASSERT(memcmp(&pbch_msg_rx, &pbch_msg_tx, sizeof(srsran_pbch_msg_nr_t)) == 0); + + return SRSRAN_SUCCESS; +} + +int main(int argc, char** argv) +{ + int ret = SRSRAN_ERROR; + parse_args(argc, argv); + + random_gen = srsran_random_init(1234); + srate_hz = (double)SRSRAN_SUBC_SPACING_NR(carrier_scs) * srsran_min_symbol_sz_rb(grid_bw_sc / SRSRAN_NRE); + grid = srsran_vec_cf_malloc(grid_bw_sc * SRSRAN_NSYMB_PER_SLOT_NR); + + srsran_ssb_t ssb = {}; + srsran_ssb_args_t ssb_args = {}; + ssb_args.enable_encode = true; + ssb_args.enable_decode = true; + ssb_args.enable_search = true; + + if (grid == NULL) { + ERROR("Malloc"); + goto clean_exit; + } + + if (srsran_ssb_init(&ssb, &ssb_args) < SRSRAN_SUCCESS) { + ERROR("Init"); + goto clean_exit; + } + + if (test_case(&ssb) != SRSRAN_SUCCESS) { + ERROR("test case failed"); + goto clean_exit; + } + + ret = SRSRAN_SUCCESS; + +clean_exit: + srsran_random_free(random_gen); + srsran_ssb_free(&ssb); + + if (grid) { + free(grid); + } + + return ret; +} \ No newline at end of file