diff --git a/lib/include/srslte/phy/ch_estimation/dmrs_pdcch.h b/lib/include/srslte/phy/ch_estimation/dmrs_pdcch.h index 0f1280e0c..6893d081e 100644 --- a/lib/include/srslte/phy/ch_estimation/dmrs_pdcch.h +++ b/lib/include/srslte/phy/ch_estimation/dmrs_pdcch.h @@ -27,10 +27,135 @@ extern "C" { #endif #include "srslte/phy/common/phy_common_nr.h" +#include "srslte/phy/resampling/resampler.h" #include "srslte/srslte.h" -SRSLTE_API int -srslte_dmrs_pdcch_put(const srslte_nr_pdcch_cfg_t* cfg, const srslte_dl_sf_cfg_t* dl_sf, cf_t* sf_symbols); +/** + * @brief Puts in the resource grid the DeModulation Reference Signals for decoding PDCCH. + * + * \remark: Implemented as specified by TS 38.211 V15.8.0 Section 7.1.4.3 + + * @param cfg Configuration that includes Carrier, CORESET, Search Space and PDCCH candidate + * @param slot_idx Slot index in the frame + * @param sf_symbols is the resource grid where the DMRS resource elements will be written + * @return SRSLTE_SUCCESS if the configurations are valid, otherwise it returns an SRSLTE_ERROR code + */ +SRSLTE_API int srslte_dmrs_pdcch_put(const srslte_nr_pdcch_cfg_t* cfg, uint32_t slot_idx, cf_t* sf_symbols); + +/** + * @brief PDCCH DMRS channel estimator object + * + * @see srslte_dmrs_pdcch_estimator_init + * @see srslte_dmrs_pdcch_estimator_free + * @see srslte_dmrs_pdcch_estimator_estimate + */ +typedef struct SRSLTE_API { + /// Current estimator carrier configuration + srslte_nr_carrier_t carrier; + + /// Current estimator CORESET configuration + srslte_coreset_t coreset; + + /// Stores the current CORESET bandwidth in PRB + uint32_t coreset_bw; + + /// Stores the current CORESET size in RE + uint32_t coreset_sz; + + /// Object for interpolating, it shall be initialised only once + srslte_resampler_fft_t interpolator; + + /// Pilots least square estimates, one vector for each possible symbol. Since there are one pilot every 4 sub-carriers + /// , each vector is three times the CORESEt band-width + cf_t* lse[SRSLTE_CORESET_DURATION_MAX]; + + /// Channel estimates, size coreset_sz + cf_t* ce; + + /// Stores latest slot index in frame + uint32_t slot_idx; +} srslte_dmrs_pdcch_estimator_t; + +/** + * @brief Initialises the PDCCH DMRS channel estimator from a given carrier and CORESET configuration + * + * \attention The initialization function expects the object being previously zeroed + * \attention Initialization is required every time the carrier and/or CORESET changes. No free is required between + * initializations. + * + * @param q provides PDCCH DMRS estimator object + * @param carrier provides the required carrier configuration for some estimation + * @param coreset provides the required configuration for initialising the object + * @return SRSLTE_SUCCESS if the configurations are valid, otherwise it returns an SRSLTE_ERROR code + */ +SRSLTE_API int srslte_dmrs_pdcch_estimator_init(srslte_dmrs_pdcch_estimator_t* q, + const srslte_nr_carrier_t* carrier, + const srslte_coreset_t* coreset); + +/** + * @brief Deallocate all memory allocated by a given PDCCH DMRS estimator object + * @param q provides PDCCH DMRS estimator object + */ +SRSLTE_API void srslte_dmrs_pdcch_estimator_free(srslte_dmrs_pdcch_estimator_t* q); + +/** + * Estimates the PDCCH channel from the received PDCCH's DMRS. The DMRS are only transmitted in the REG where the + * PDCCH is allocated. + * + * The most efficient way is estimating the overall CORESET independently whether there is data or not. Said so, the + * output grid `estimates` shall be at least as big as the CORESET. + * + * This function is designed to be called prior to the PDCCH blind decoding and shall be called only once for every + * CORESET. + * + * @param cfg Configuration that includes Carrier, CORESET and the Search Space + * @param slot_idx Slot index in the frame + * @param sf_symbols Received resource grid. + * @param estimates CORESET Resource grid with the estimated channel + * @return SRSLTE_SUCCESS if the configurations are valid, otherwise it returns an SRSLTE_ERROR code + */ +SRSLTE_API int srslte_dmrs_pdcch_estimate(srslte_dmrs_pdcch_estimator_t* q, uint32_t slot_idx, const cf_t* sf_symbols); + +/** + * @brief PDSCH DMRS measurement results + */ +typedef struct SRSLTE_API { + /// Linear reference signal received power (RSRP). Measure correlation + float rsrp; + + /// Energy per resource element (EPRE) + float epre; + + /// CFO Measure in Hz (only available for durations 2 and 3) + float cfo_hz; + + /// Measure synchronization error in micro-seconds + float sync_error_us; +} srslte_dmrs_pdcch_measure_t; + +/** + * @brief Performs PDCCH DMRS measurements of a given PDCCH candidate for an aggregation level + * + * @note The measurement is useful for determining whether there is a PDCCH transmission in the given candidate. + * + * @attention The provided aggregation level and candidate need to be according to the search space. + * + * @param q provides PDCCH DMRS estimator object + * @param search_space provides the search space + * @param slot_idx Slot index in the frame + * @param aggregation_level Indicates the aggregation level of the candidate to examine + * @param candidate Indicates the candidate index of the available + * @param rnti Indicates the UE RNTI (only used for UE search space type) + * @param measure Provides the structure for storing the channel estimate measurements + * @return SRSLTE_SUCCESS if the configurations are valid, otherwise it returns an SRSLTE_ERROR code + */ +int srslte_dmrs_pdcch_get_measure(srslte_dmrs_pdcch_estimator_t* q, + const srslte_search_space_t* search_space, + uint32_t slot_idx, + uint32_t aggregation_level, + uint32_t candidate, + uint16_t rnti, + srslte_dmrs_pdcch_measure_t* measure); #ifdef __cplusplus } diff --git a/lib/include/srslte/phy/common/phy_common_nr.h b/lib/include/srslte/phy/common/phy_common_nr.h index 796f85181..3b65205a2 100644 --- a/lib/include/srslte/phy/common/phy_common_nr.h +++ b/lib/include/srslte/phy/common/phy_common_nr.h @@ -39,6 +39,11 @@ extern "C" { */ #define SRSLTE_NR_MAX_NUMEROLOGY 4 +/** + * @brief Defines the symbol duration, including cyclic prefix + */ +#define SRSLTE_SUBC_SPACING(NUM) (15000U << (NUM)) + /** * @brief Defines the number of slots per SF. Defined by TS 38.211 v15.8.0 Table 4.3.2-1. */ @@ -80,18 +85,18 @@ typedef struct { #define SRSLTE_CORESET_FREQ_DOMAIN_RES_SIZE 45 #define SRSLTE_CORESET_SHIFT_INDEX_MAX (SRSLTE_CORESET_NOF_PRB_MAX - 1) -typedef enum { +typedef enum SRSLTE_API { srslte_coreset_mapping_type_interleaved = 0, srslte_coreset_mapping_type_non_interleaved, } srslte_coreset_mapping_type_t; -typedef enum { +typedef enum SRSLTE_API { srslte_coreset_bundle_size_n2 = 0, srslte_coreset_bundle_size_n3, srslte_coreset_bundle_size_n6, } srslte_coreset_bundle_size_t; -typedef enum { +typedef enum SRSLTE_API { srslte_coreset_precoder_granularity_contiguous = 0, srslte_coreset_precoder_granularity_reg_bundle } srslte_coreset_precoder_granularity_t; @@ -102,11 +107,11 @@ typedef enum { * Fields follow the same order than described in 3GPP 38.331 R15 - ControlResourceSet * */ -typedef struct { +typedef struct SRSLTE_API { srslte_coreset_mapping_type_t mapping_type; uint32_t id; uint32_t duration; - bool freq_domain_resources[SRSLTE_CORESET_FREQ_DOMAIN_RES_SIZE]; + bool freq_resources[SRSLTE_CORESET_FREQ_DOMAIN_RES_SIZE]; srslte_coreset_bundle_size_t interleaver_size; bool dmrs_scrambling_id_present; @@ -117,21 +122,21 @@ typedef struct { /** Missing TCI parameters */ } srslte_coreset_t; -typedef enum { +typedef enum SRSLTE_API { srslte_search_space_type_common = 0, srslte_search_space_type_ue, } srslte_search_space_type_t; #define SRSLTE_SEARCH_SPACE_NOF_AGGREGATION_LEVELS 5 -typedef struct { +typedef struct SRSLTE_API { uint32_t start; // start symbol within slot uint32_t duration; // in slots srslte_search_space_type_t type; uint32_t nof_candidates[SRSLTE_SEARCH_SPACE_NOF_AGGREGATION_LEVELS]; } srslte_search_space_t; -typedef struct { +typedef struct SRSLTE_API { srslte_nr_carrier_t carrier; uint16_t rnti; srslte_coreset_t coreset; @@ -140,6 +145,30 @@ typedef struct { uint32_t aggregation_level; } srslte_nr_pdcch_cfg_t; +/** + * @brief Calculates the bandwidth of a given CORESET in physical resource blocks (PRB) . This function uses the + * frequency domain resources bit-map for counting the number of PRB. + * + * @attention Prior to this function call, the frequency domain resources bit-map shall be zeroed beyond the + * carrier.nof_prb / 6 index, otherwise the CORESET bandwidth might be greater than the carrier. + * + * @param coreset provides the given CORESET configuration + * @return The number of PRB that the CORESET takes in frequency domain + */ +SRSLTE_API uint32_t srslte_coreset_get_bw(const srslte_coreset_t* coreset); + +/** + * @brief Calculates the number of Physical Resource Elements (time and frequency domain) that a given CORESET uses. + * This function uses the frequency domain resources bit-map and duration to compute the number of symbols. + * + * @attention Prior to this function call, the frequency domain resources bit-map shall be zeroed beyond the + * carrier.nof_prb / 6 index, otherwise the CORESET bandwidth might be greater than the carrier. + * + * @param coreset provides the given CORESET configuration + * @return The number of resource elements that compose the coreset + */ +SRSLTE_API uint32_t srslte_coreset_get_sz(const srslte_coreset_t* coreset); + #ifdef __cplusplus } #endif diff --git a/lib/src/phy/ch_estimation/dmrs_pdcch.c b/lib/src/phy/ch_estimation/dmrs_pdcch.c index 8827cdedd..68eab6263 100644 --- a/lib/src/phy/ch_estimation/dmrs_pdcch.c +++ b/lib/src/phy/ch_estimation/dmrs_pdcch.c @@ -21,6 +21,10 @@ #include "srslte/phy/ch_estimation/dmrs_pdcch.h" +/// @brief Every frequency resource is 6 Resource blocks, every resource block carries 3 pilots. So 18 possible pilots +/// per frequency resource. +#define NOF_PILOTS_X_FREQ_RES 18 + uint32_t srslte_pdcch_calculate_Y_p_n(uint32_t coreset_id, uint16_t rnti, int n) { const uint32_t A_p[3] = {39827, 39829, 39839}; @@ -38,27 +42,34 @@ uint32_t srslte_pdcch_calculate_Y_p_n(uint32_t coreset_id, uint16_t rnti, int n) * downlink control channel assignment * */ -int srslte_pdcch_get_ncce(const srslte_nr_pdcch_cfg_t* cfg, const srslte_dl_sf_cfg_t* dl_sf) +static int srslte_pdcch_get_ncce(const srslte_coreset_t* coreset, + const srslte_search_space_t* search_space, + uint16_t rnti, + uint32_t aggregation_level, + uint32_t candidate, + const uint32_t slot_idx) { - if (cfg->aggregation_level >= SRSLTE_SEARCH_SPACE_NOF_AGGREGATION_LEVELS) { - ERROR("Invalid aggregation level %d;\n", cfg->aggregation_level); + if (aggregation_level >= SRSLTE_SEARCH_SPACE_NOF_AGGREGATION_LEVELS) { + ERROR("Invalid aggregation level %d;\n", aggregation_level); return SRSLTE_ERROR; } - uint32_t L = 1U << cfg->aggregation_level; // Aggregation level - uint32_t n_ci = 0; // Carrier indicator field - uint32_t m = cfg->candidate; // Selected PDDCH candidate - uint32_t M = cfg->search_space.nof_candidates[cfg->aggregation_level]; // Number of aggregation levels + uint32_t L = 1U << aggregation_level; // Aggregation level + uint32_t n_ci = 0; // Carrier indicator field + uint32_t m = candidate; // Selected PDDCH candidate + uint32_t M = search_space->nof_candidates[aggregation_level]; // Number of aggregation levels if (M == 0) { - ERROR("Invalid number of candidates %d for aggregation level %d\n", M, cfg->aggregation_level); + ERROR("Invalid number of candidates %d for aggregation level %d\n", M, aggregation_level); return SRSLTE_ERROR; } // Count number of REG uint32_t N_cce = 0; for (uint32_t i = 0; i < SRSLTE_CORESET_FREQ_DOMAIN_RES_SIZE; i++) { - N_cce += cfg->coreset.freq_domain_resources[i] ? cfg->coreset.duration : 0; + // Every frequency domain resource is 6 PRB, a REG is 1PRB wide and a CCE is 6 REG. So, for every frequency domain + // resource there is one CCE. + N_cce += coreset->freq_resources[i] ? coreset->duration : 0; } if (N_cce < L) { @@ -68,9 +79,8 @@ int srslte_pdcch_get_ncce(const srslte_nr_pdcch_cfg_t* cfg, const srslte_dl_sf_c // Calculate Y_p_n uint32_t Y_p_n = 0; - if (cfg->search_space.type == srslte_search_space_type_ue) { - Y_p_n = srslte_pdcch_calculate_Y_p_n( - cfg->coreset.id, cfg->rnti, dl_sf->tti % SRSLTE_NR_NSLOTS_PER_FRAME(cfg->carrier.numerology)); + if (search_space->type == srslte_search_space_type_ue) { + Y_p_n = srslte_pdcch_calculate_Y_p_n(coreset->id, rnti, slot_idx); } return (int)(L * ((Y_p_n + (m * N_cce) / (L * M) + n_ci) % (N_cce / L))); @@ -100,21 +110,17 @@ dmrs_pdcch_put_symbol_noninterleaved(const srslte_nr_pdcch_cfg_t* cfg, uint32_t // CORESET Resource Block counter uint32_t rb_coreset_idx = 0; for (uint32_t i = 0; i < nof_freq_res; i++) { - // Every frequency resource is 6 Resource blocks, every resource block carries 3 pilots. So 18 possible pilots per - // frequency resource. - const uint32_t nof_pilots_x_resource = 18; - // Skip frequency resource if outside of the CORESET - if (!cfg->coreset.freq_domain_resources[i]) { + if (!cfg->coreset.freq_resources[i]) { // Skip possible DMRS locations in this region - sequence_skip += nof_pilots_x_resource; + sequence_skip += NOF_PILOTS_X_FREQ_RES; continue; } // Skip if the frequency resource highest RB is lower than the first CCE resource block. if ((rb_coreset_idx + 6) <= rb_coreset_idx_begin) { // Skip possible DMRS locations in this region - sequence_skip += nof_pilots_x_resource; + sequence_skip += NOF_PILOTS_X_FREQ_RES; // Since this is part of the CORESET, count the RB as CORESET rb_coreset_idx += 6; @@ -131,8 +137,8 @@ dmrs_pdcch_put_symbol_noninterleaved(const srslte_nr_pdcch_cfg_t* cfg, uint32_t sequence_skip = 0; // Generate pilots - cf_t rl[nof_pilots_x_resource]; - srslte_sequence_state_gen_f(&sequence_state, M_SQRT1_2, (float*)rl, nof_pilots_x_resource * 2); + cf_t rl[NOF_PILOTS_X_FREQ_RES]; + srslte_sequence_state_gen_f(&sequence_state, M_SQRT1_2, (float*)rl, NOF_PILOTS_X_FREQ_RES * 2); // For each RB in the frequency resource for (uint32_t j = 0; j < 6; j++) { @@ -155,16 +161,17 @@ dmrs_pdcch_put_symbol_noninterleaved(const srslte_nr_pdcch_cfg_t* cfg, uint32_t // Calculate sub-carrier index uint32_t k = n * SRSLTE_NRE + 4 * k_prime + 1; - sf_symbol[k] = rl[(3 * n + k_prime) % nof_pilots_x_resource]; + sf_symbol[k] = rl[3 * j + k_prime]; } rb_coreset_idx++; } } } -int srslte_dmrs_pdcch_put(const srslte_nr_pdcch_cfg_t* cfg, const srslte_dl_sf_cfg_t* dl_sf, cf_t* sf_symbols) +int srslte_dmrs_pdcch_put(const srslte_nr_pdcch_cfg_t* cfg, uint32_t slot_idx, cf_t* sf_symbols) { - int ncce = srslte_pdcch_get_ncce(cfg, dl_sf); + int ncce = srslte_pdcch_get_ncce( + &cfg->coreset, &cfg->search_space, cfg->rnti, cfg->aggregation_level, cfg->candidate, slot_idx); if (ncce < SRSLTE_SUCCESS) { return SRSLTE_ERROR; } @@ -190,11 +197,263 @@ int srslte_dmrs_pdcch_put(const srslte_nr_pdcch_cfg_t* cfg, const srslte_dl_sf_c for (uint32_t l = 0; l < cfg->coreset.duration; l++) { // Get Cin - uint32_t cinit = dmrs_pdcch_get_cinit(dl_sf->tti % SRSLTE_NR_NSLOTS_PER_FRAME(cfg->carrier.numerology), l, n_id); + uint32_t cinit = dmrs_pdcch_get_cinit(slot_idx, l, n_id); // Put data dmrs_pdcch_put_symbol_noninterleaved(cfg, cinit, ncce, &sf_symbols[cfg->carrier.nof_prb * SRSLTE_NRE * l]); } + return SRSLTE_SUCCESS; +} + +int srslte_dmrs_pdcch_estimator_init(srslte_dmrs_pdcch_estimator_t* q, + const srslte_nr_carrier_t* carrier, + const srslte_coreset_t* coreset) +{ + if (coreset->duration < SRSLTE_CORESET_DURATION_MIN || coreset->duration > SRSLTE_CORESET_DURATION_MAX) { + ERROR("Error CORESET duration %d is out-of-bounds (%d,%d)\n", + coreset->duration, + SRSLTE_CORESET_DURATION_MIN, + SRSLTE_CORESET_DURATION_MAX); + return SRSLTE_ERROR; + } + + // The carrier configuration is not used for initialization, so copy it always + q->carrier = *carrier; + + // Detect change in CORESET, if none, return early + if (memcmp(&q->coreset, coreset, sizeof(srslte_coreset_t)) == 0) { + return SRSLTE_SUCCESS; + } + + // Save new CORESET + q->coreset = *coreset; + + // The interpolator may return without reconfiguring after the first call + if (srslte_resampler_fft_init(&q->interpolator, SRSLTE_RESAMPLER_MODE_INTERPOLATE, 4)) { + ERROR("Initiating interpolator\n"); + return SRSLTE_ERROR; + } + + // Calculate new bandwidth and size + uint32_t coreset_bw = srslte_coreset_get_bw(coreset); + uint32_t coreset_sz = srslte_coreset_get_sz(coreset); + + // Reallocate only if the CORESET size or bandwidth changed + if (q->coreset_bw != coreset_bw || q->coreset_sz != coreset_sz) { + // Iterate all possible symbols + for (uint32_t l = 0; l < SRSLTE_CORESET_DURATION_MAX; l++) { + // Free if allocated + if (q->lse[l] != NULL) { + free(q->lse[l]); + q->lse[l] = NULL; + } + + // Allocate + if (l < coreset->duration) { + // Allocate for 3 pilots per physical resource block + q->lse[l] = srslte_vec_cf_malloc(coreset_bw * 3); + } + } + + if (q->ce) { + free(q->ce); + } + q->ce = srslte_vec_cf_malloc(coreset_sz); + } + + // Save new calculated values + q->coreset_bw = coreset_bw; + q->coreset_sz = coreset_sz; + + return SRSLTE_SUCCESS; +} + +void srslte_dmrs_pdcch_estimator_free(srslte_dmrs_pdcch_estimator_t* q) +{ + if (q->ce) { + free(q->ce); + } + + for (uint32_t i = 0; i < SRSLTE_CORESET_DURATION_MAX; i++) { + if (q->lse[i]) { + free(q->lse[i]); + } + } + + srslte_resampler_fft_free(&q->interpolator); + + memset(q, 0, sizeof(srslte_dmrs_pdcch_estimator_t)); +} + +static void +srslte_dmrs_pdcch_extract(srslte_dmrs_pdcch_estimator_t* q, uint32_t cinit, const cf_t* sf_symbol, cf_t* lse) +{ + // Initialise pseudo-random sequence + srslte_sequence_state_t sequence_state = {}; + srslte_sequence_state_init(&sequence_state, cinit); + + // Counter for skipping unused pilot locations + uint32_t sequence_skip = 0; + + // Counts enabled frequency domain resources + uint32_t rb_coreset_idx = 0; + // Iterate over all possible frequency resources + for (uint32_t i = 0; i < SRSLTE_CORESET_FREQ_DOMAIN_RES_SIZE; i++) { + // Skip disabled frequency resources + if (!q->coreset.freq_resources[i]) { + sequence_skip += NOF_PILOTS_X_FREQ_RES; + continue; + } + + // Skip unused pilots + srslte_sequence_state_advance(&sequence_state, 2 * sequence_skip); + sequence_skip = 0; + + // Generate sequence + cf_t rl[NOF_PILOTS_X_FREQ_RES]; + srslte_sequence_state_gen_f(&sequence_state, M_SQRT1_2, (float*)rl, NOF_PILOTS_X_FREQ_RES * 2); + + // Iterate all PRBs in the enabled frequency domain resource + for (uint32_t j = 0, idx = rb_coreset_idx * NOF_PILOTS_X_FREQ_RES; j < 6; j++) { + // Calculate Grid PRB index (n) + uint32_t n = i * 6 + j; + + // For each pilot in the PRB + for (uint32_t k_prime = 0; k_prime < 3; k_prime++, idx++) { + // Calculate sub-carrier index + uint32_t k = n * SRSLTE_NRE + 4 * k_prime + 1; + + // Extract symbol + lse[idx] = sf_symbol[k]; + } + } + + // Calculate least squared estimates + cf_t* lse_ptr = &lse[rb_coreset_idx * NOF_PILOTS_X_FREQ_RES]; + srslte_vec_prod_conj_ccc(lse_ptr, rl, lse_ptr, NOF_PILOTS_X_FREQ_RES); + + // Increment frequency domain resource counter + rb_coreset_idx++; + } +} + +int srslte_dmrs_pdcch_estimate(srslte_dmrs_pdcch_estimator_t* q, uint32_t slot_idx, const cf_t* sf_symbols) +{ + // Saves slot index for posterior use + q->slot_idx = slot_idx; + + // Use cell id if the DMR scrambling id is not provided by higher layers + uint32_t n_id = q->carrier.id; + if (q->coreset.dmrs_scrambling_id_present) { + n_id = q->coreset.dmrs_scrambling_id; + } + + // Extract pilots + for (uint32_t l = 0; l < q->coreset.duration; l++) { + // Calculate PRN sequence initial state + uint32_t cinit = dmrs_pdcch_get_cinit(slot_idx, l, n_id); + + // Extract pilots least square estimates + srslte_dmrs_pdcch_extract(q, cinit, &sf_symbols[l * q->carrier.nof_prb * SRSLTE_NRE], q->lse[l]); + } + + // Time averaging should be implemented here + // ... + + // Interpolator impulse response + uint32_t interpolation_delay = srslte_resampler_fft_get_delay(&q->interpolator) / 4; + + // Interpolation, it assumes all frequency domain resources are contiguous + for (uint32_t l = 0; l < q->coreset.duration; l++) { + cf_t* ce_ptr = &q->ce[SRSLTE_NRE * q->coreset_bw * l]; + + srslte_resampler_fft_reset_state(&q->interpolator); + + // Feed inital samples + uint32_t discard_initial = SRSLTE_MIN(interpolation_delay, q->coreset_bw * 3); + srslte_resampler_fft_run(&q->interpolator, q->lse[l], NULL, discard_initial); + uint32_t n = 0; + + // Pad zeroes until impulsional response is covered + if (discard_initial < interpolation_delay) { + srslte_resampler_fft_run(&q->interpolator, NULL, NULL, interpolation_delay - discard_initial); + } else { + n = q->coreset_bw * 3 - discard_initial; + srslte_resampler_fft_run(&q->interpolator, q->lse[l], ce_ptr, n); + } + + srslte_resampler_fft_run(&q->interpolator, NULL, &ce_ptr[n * 4], q->coreset_bw * 3 - n); + } + + return SRSLTE_SUCCESS; +} + +int srslte_dmrs_pdcch_get_measure(srslte_dmrs_pdcch_estimator_t* q, + const srslte_search_space_t* search_space, + uint32_t slot_idx, + uint32_t aggregation_level, + uint32_t candidate, + uint16_t rnti, + srslte_dmrs_pdcch_measure_t* measure) +{ + uint32_t L = 1U << aggregation_level; + int ncce = srslte_pdcch_get_ncce(&q->coreset, search_space, rnti, aggregation_level, candidate, slot_idx); + if (ncce < SRSLTE_SUCCESS) { + return SRSLTE_ERROR; + } + + if (q->coreset.mapping_type == srslte_coreset_mapping_type_interleaved) { + ERROR("Error interleaved mapping not implemented\n"); + return SRSLTE_ERROR; + } + + // Check that CORESET duration is not less than minimum + if (q->coreset.duration < SRSLTE_CORESET_DURATION_MIN) { + ERROR("Invalid CORESET duration\n"); + return SRSLTE_ERROR; + } + + // Get base pilot; + uint32_t pilot_idx = (ncce * 18) / q->coreset.duration; + uint32_t nof_pilots = (L * 18) / q->coreset.duration; + + float rsrp = 0.0f; + float epre = 0.0f; + float cfo = 0.0f; + float sync_err = 0.0f; + cf_t corr[SRSLTE_CORESET_DURATION_MAX] = {}; + for (uint32_t l = 0; l < q->coreset.duration; l++) { + // Correlate DMRS + corr[l] = srslte_vec_acc_cc(&q->lse[l][pilot_idx], nof_pilots) / (float)nof_pilots; + + // Measure symbol RSRP + rsrp += cabsf(corr[l]); + + // Measure symbol EPRE + epre += srslte_vec_avg_power_cf(&q->lse[l][pilot_idx], nof_pilots); + + // Measure CFO only from the second and third symbols + if (l != 0) { + cfo += cargf(corr[l] * conjf(corr[l - 1])); + } + + // Measure synchronization error + sync_err += srslte_vec_estimate_frequency(&q->lse[l][pilot_idx], nof_pilots); + } + + if (q->coreset.duration > 1) { + cfo /= (float)(q->coreset.duration - 1); + } + + // Symbol time, including cyclic prefix. Required for CFO estimation + float Ts = (71.3541666667f / (float)(1 << q->carrier.numerology)); + + measure->rsrp = rsrp / (float)q->coreset.duration; + measure->epre = epre / (float)q->coreset.duration; + measure->cfo_hz = cfo / (2.0f * (float)M_PI * Ts); + measure->sync_error_us = + (float)SRSLTE_SUBC_SPACING(q->carrier.numerology) * sync_err / (4.0e-6f * (float)q->coreset.duration); + return SRSLTE_SUCCESS; } \ No newline at end of file diff --git a/lib/src/phy/ch_estimation/test/dmrs_pdcch_test.c b/lib/src/phy/ch_estimation/test/dmrs_pdcch_test.c index c20c16127..7b2eaba69 100644 --- a/lib/src/phy/ch_estimation/test/dmrs_pdcch_test.c +++ b/lib/src/phy/ch_estimation/test/dmrs_pdcch_test.c @@ -67,23 +67,34 @@ void parse_args(int argc, char** argv) } } -static int run_test(const srslte_nr_pdcch_cfg_t* cfg, cf_t* sf_symbols, cf_t* h) +static int +run_test(srslte_dmrs_pdcch_estimator_t* estimator, const srslte_nr_pdcch_cfg_t* cfg, cf_t* sf_symbols, cf_t* h) { - srslte_dl_sf_cfg_t dl_sf = {}; - for (dl_sf.tti = 0; dl_sf.tti < SRSLTE_NOF_SF_X_FRAME; dl_sf.tti++) { - TESTASSERT(srslte_dmrs_pdcch_put(cfg, &dl_sf, sf_symbols) == SRSLTE_SUCCESS); - - /*srslte_dmrs_pdsch_get_sf(cfg, &dl_sf, sf_symbols, h); - - float mse = 0.0f; - for (uint32_t i = 0; i < dmrs_pdsch->nof_symbols * dmrs_pdsch->nof_sc * SRSLTE_NRE; i++) { - cf_t err = h[i] - 1.0f; - mse += cabsf(err); + for (uint32_t slot_idx = 0; slot_idx < SRSLTE_NR_NSLOTS_PER_SF(cfg->carrier.numerology); slot_idx++) { + uint32_t nof_re = carrier.nof_prb * SRSLTE_NRE * SRSLTE_NR_NSYMB_PER_SLOT; + srslte_vec_cf_zero(sf_symbols, nof_re); + + TESTASSERT(srslte_dmrs_pdcch_put(cfg, slot_idx, sf_symbols) == SRSLTE_SUCCESS); + + TESTASSERT(srslte_dmrs_pdcch_estimate(estimator, slot_idx, sf_symbols) == SRSLTE_SUCCESS); + + srslte_dmrs_pdcch_measure_t measure = {}; + TESTASSERT( + srslte_dmrs_pdcch_get_measure( + estimator, &cfg->search_space, slot_idx, cfg->aggregation_level, cfg->candidate, cfg->rnti, &measure) == + SRSLTE_SUCCESS); + + if (fabsf(measure.rsrp - 1.0f) > 1e-2) { + printf("EPRE=%f; RSRP=%f; CFO=%f; SYNC_ERR=%f;\n", + measure.epre, + measure.rsrp, + measure.cfo_hz, + measure.sync_error_us); } - mse /= (float)dmrs_pdsch->nof_symbols * dmrs_pdsch->nof_sc; - - TESTASSERT(!isnan(mse)); - TESTASSERT(mse < 1e-6f);*/ + TESTASSERT(fabsf(measure.epre - 1.0f) < 1e-3f); + TESTASSERT(fabsf(measure.rsrp - 1.0f) < 1e-3f); + TESTASSERT(fabsf(measure.cfo_hz) < 1e-3f); + TESTASSERT(fabsf(measure.sync_error_us) < 1e-3f); } return SRSLTE_SUCCESS; @@ -95,9 +106,10 @@ int main(int argc, char** argv) parse_args(argc, argv); - srslte_nr_pdcch_cfg_t cfg = {}; + srslte_nr_pdcch_cfg_t cfg = {}; + srslte_dmrs_pdcch_estimator_t estimator = {}; - uint32_t nof_re = carrier.nof_prb * SRSLTE_NRE * SRSLTE_NOF_SLOTS_PER_SF * SRSLTE_MAX_NSYMB; + uint32_t nof_re = carrier.nof_prb * SRSLTE_NRE * SRSLTE_NR_NSYMB_PER_SLOT; cf_t* sf_symbols = srslte_vec_cf_malloc(nof_re); cf_t* h = srslte_vec_cf_malloc(nof_re); @@ -112,8 +124,8 @@ int main(int argc, char** argv) for (uint32_t frequency_resources = 1; frequency_resources < (1U << nof_frequency_resource); frequency_resources++) { uint32_t nof_freq_resources = 0; for (uint32_t i = 0; i < nof_frequency_resource; i++) { - uint32_t mask = ((frequency_resources >> i) & 1U); - cfg.coreset.freq_domain_resources[i] = (mask == 1); + uint32_t mask = ((frequency_resources >> i) & 1U); + cfg.coreset.freq_resources[i] = (mask == 1); nof_freq_resources += mask; } @@ -136,7 +148,9 @@ int main(int argc, char** argv) for (cfg.candidate = 0; cfg.candidate < cfg.search_space.nof_candidates[cfg.aggregation_level]; cfg.candidate++) { - if (run_test(&cfg, sf_symbols, h)) { + srslte_dmrs_pdcch_estimator_init(&estimator, &cfg.carrier, &cfg.coreset); + + if (run_test(&estimator, &cfg, sf_symbols, h)) { ERROR("Test %d failed\n", test_counter); } else { test_passed++; @@ -148,6 +162,8 @@ int main(int argc, char** argv) } } + srslte_dmrs_pdcch_estimator_free(&estimator); + if (sf_symbols) { free(sf_symbols); } diff --git a/lib/src/phy/common/CMakeLists.txt b/lib/src/phy/common/CMakeLists.txt index 53726c931..2abad6f02 100644 --- a/lib/src/phy/common/CMakeLists.txt +++ b/lib/src/phy/common/CMakeLists.txt @@ -18,7 +18,7 @@ # and at http://www.gnu.org/licenses/. # -set(SOURCES phy_common.c phy_common_sl.c sequence.c timestamp.c) +set(SOURCES phy_common.c phy_common_sl.c phy_common_nr.c sequence.c timestamp.c) add_library(srslte_phy_common OBJECT ${SOURCES}) add_subdirectory(test) \ No newline at end of file diff --git a/lib/src/phy/common/phy_common_nr.c b/lib/src/phy/common/phy_common_nr.c new file mode 100644 index 000000000..163ed551b --- /dev/null +++ b/lib/src/phy/common/phy_common_nr.c @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2020 Software Radio Systems Limited + * + * This file is part of srsLTE. + * + * srsLTE is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsLTE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include + +uint32_t srslte_coreset_get_bw(const srslte_coreset_t* coreset) +{ + uint32_t prb_count = 0; + + // Iterate all the frequency domain resources bit-map... + for (uint32_t i = 0; i < SRSLTE_CORESET_FREQ_DOMAIN_RES_SIZE; i++) { + // ... and count 6 PRB for everu frequency domain resource that it is enabled + if (coreset->freq_resources[i]) { + prb_count += 6; + } + } + + // Return the total count of physical resource blocks + return prb_count; +} + +uint32_t srslte_coreset_get_sz(const srslte_coreset_t* coreset) +{ + // Returns the number of resource elements in time and frequency domains + return srslte_coreset_get_bw(coreset) * SRSLTE_NRE * coreset->duration; +}