diff --git a/lib/include/srsran/interfaces/ue_nr_interfaces.h b/lib/include/srsran/interfaces/ue_nr_interfaces.h index 367b08b6a..04fc73ee5 100644 --- a/lib/include/srsran/interfaces/ue_nr_interfaces.h +++ b/lib/include/srsran/interfaces/ue_nr_interfaces.h @@ -166,17 +166,21 @@ public: }; struct phy_args_nr_t { - uint32_t rf_channel_offset = 0; ///< Specifies the RF channel the NR carrier shall fill - uint32_t nof_carriers = 1; - uint32_t max_nof_prb = 106; - uint32_t nof_phy_threads = 3; - uint32_t worker_cpu_mask = 0; - srsran::phy_log_args_t log = {}; - srsran_ue_dl_nr_args_t dl = {}; - srsran_ue_ul_nr_args_t ul = {}; - std::set fixed_sr = {1}; - uint32_t fix_wideband_cqi = 15; // Set to a non-zero value for fixing the wide-band CQI report - bool store_pdsch_ko = false; + uint32_t rf_channel_offset = 0; ///< Specifies the RF channel the NR carrier shall fill + uint32_t nof_carriers = 1; + uint32_t max_nof_prb = 106; + uint32_t nof_phy_threads = 3; + uint32_t worker_cpu_mask = 0; + srsran::phy_log_args_t log = {}; + srsran_ue_dl_nr_args_t dl = {}; + srsran_ue_ul_nr_args_t ul = {}; + std::set fixed_sr = {1}; + uint32_t fix_wideband_cqi = 15; // Set to a non-zero value for fixing the wide-band CQI report + bool store_pdsch_ko = false; + float trs_epre_ema_alpha = 0.1f; ///< EPRE measurement exponential average alpha + float trs_rsrp_ema_alpha = 0.1f; ///< RSRP measurement exponential average alpha + float trs_cfo_ema_alpha = 0.1f; ///< RSRP measurement exponential average alpha + bool enable_worker_cfo = true; ///< Enable/Disable open loop CFO correction at the workers phy_args_nr_t() { diff --git a/lib/src/phy/common/phy_common_nr.c b/lib/src/phy/common/phy_common_nr.c index c46dfd222..3dbc974e7 100644 --- a/lib/src/phy/common/phy_common_nr.c +++ b/lib/src/phy/common/phy_common_nr.c @@ -343,12 +343,13 @@ uint32_t srsran_csi_meas_info(const srsran_csi_trs_measurements_t* meas, char* s return srsran_print_check(str, str_len, 0, - "rsrp=%+.1f epre=%+.1f n0=%+.1f snr=%+.1f cfo=%+.1f delay=%+.1f", + "rsrp=%+.1f epre=%+.1f n0=%+.1f snr=%+.1f cfo=%+.1f cfo_max=%.0f delay=%+.1f", meas->rsrp_dB, meas->epre_dB, meas->n0_dB, meas->snr_dB, meas->cfo_hz, + meas->cfo_hz_max, meas->delay_us); } diff --git a/srsue/hdr/phy/nr/sf_worker.h b/srsue/hdr/phy/nr/sf_worker.h index 7b69c079e..9f8927644 100644 --- a/srsue/hdr/phy/nr/sf_worker.h +++ b/srsue/hdr/phy/nr/sf_worker.h @@ -59,6 +59,7 @@ private: cf_t* prach_ptr = nullptr; float prach_power = 0; srsran::phy_common_interface::worker_context_t context = {}; + float ul_ext_cfo = 0.0f; ///< UL CFO external offset in Hz }; } // namespace nr diff --git a/srsue/hdr/phy/nr/state.h b/srsue/hdr/phy/nr/state.h index 5c7d09f36..6ff0afbfb 100644 --- a/srsue/hdr/phy/nr/state.h +++ b/srsue/hdr/phy/nr/state.h @@ -60,6 +60,13 @@ private: std::mutex csi_measurements_mutex; std::array csi_measurements = {}; + /// TRS measurements + mutable std::mutex trs_measurements_mutex; + srsran_csi_trs_measurements_t trs_measurements = {}; + + /// Other measurements + std::atomic ul_ext_cfo_hz = {0.0f}; + /** * @brief Resets all metrics (unprotected) */ @@ -453,6 +460,64 @@ public: return; } } + + /** + * @brief Processes a new Tracking Reference Signal (TRS) measurement + * @param new_measure New measurement + * @param resource_set_id NZP-CSI-RS resource set identifier used for the channel measurement if it is configured from + * a NZP-CSI-RS + * @param K_csi_rs Number of NZP-CSI-RS resources used for the measurement, set to 0 if another type of signal is + * measured (i.e. SSB) + */ + void new_csi_trs_measurement(const srsran_csi_trs_measurements_t& new_meas, + uint32_t resource_set_id = 0, + uint32_t K_csi_rs = 0) + { + // Compute channel metrics and push it + ch_metrics_t new_ch_metrics = {}; + new_ch_metrics.sinr = new_meas.snr_dB; + new_ch_metrics.rsrp = new_meas.rsrp_dB; + new_ch_metrics.rsrq = 0.0f; // Not supported + new_ch_metrics.rssi = 0.0f; // Not supported + new_ch_metrics.sync_err = new_meas.delay_us; + set_channel_metrics(new_ch_metrics); + + // Compute synch metrics and report it to the PHY state + sync_metrics_t new_sync_metrics = {}; + new_sync_metrics.cfo = new_meas.cfo_hz; + set_sync_metrics(sync_metrics); + + // Convert to CSI channel measurement and report new NZP-CSI-RS measurement to the PHY state + srsran_csi_channel_measurements_t measurements = {}; + measurements.cri = 0; + measurements.wideband_rsrp_dBm = new_meas.rsrp_dB; + measurements.wideband_epre_dBm = new_meas.epre_dB; + measurements.wideband_snr_db = new_meas.snr_dB; + measurements.nof_ports = 1; // Other values are not supported + measurements.K_csi_rs = K_csi_rs; + new_nzp_csi_rs_channel_measurement(measurements, resource_set_id); + + trs_measurements_mutex.lock(); + trs_measurements.rsrp_dB = SRSRAN_VEC_SAFE_EMA(new_meas.rsrp_dB, trs_measurements.rsrp_dB, args.trs_epre_ema_alpha); + trs_measurements.epre_dB = SRSRAN_VEC_SAFE_EMA(new_meas.epre_dB, trs_measurements.epre_dB, args.trs_rsrp_ema_alpha); + trs_measurements.cfo_hz = SRSRAN_VEC_SAFE_EMA(new_meas.cfo_hz, trs_measurements.cfo_hz, args.trs_cfo_ema_alpha); + trs_measurements.nof_re++; + trs_measurements_mutex.unlock(); + } + + float get_dl_cfo() + { + std::lock_guard lock(trs_measurements_mutex); + return trs_measurements.cfo_hz; + } + + float get_ul_cfo() const + { + std::lock_guard lock(trs_measurements_mutex); + return trs_measurements.cfo_hz + ul_ext_cfo_hz; + } + + void set_ul_ext_cfo(float ext_cfo_hz) { ul_ext_cfo_hz = ext_cfo_hz; } }; } // namespace nr } // namespace srsue diff --git a/srsue/hdr/phy/nr/worker_pool.h b/srsue/hdr/phy/nr/worker_pool.h index 81d579ef8..c0d96232e 100644 --- a/srsue/hdr/phy/nr/worker_pool.h +++ b/srsue/hdr/phy/nr/worker_pool.h @@ -49,6 +49,12 @@ public: void clear_pending_grants() override; void get_metrics(phy_metrics_t& m); int tx_request(const tx_request_t& request) override; + + /** + * @brief Sets external CFO to compensate UL signal frequency offset + * @param ext_cfo_hz External CFO in Hz + */ + void set_ul_ext_cfo(float ext_cfo_hz) { phy_state.set_ul_ext_cfo(ext_cfo_hz); } }; } // namespace nr diff --git a/srsue/hdr/phy/phy_metrics.h b/srsue/hdr/phy/phy_metrics.h index c5ba106e9..11233ceb9 100644 --- a/srsue/hdr/phy/phy_metrics.h +++ b/srsue/hdr/phy/phy_metrics.h @@ -27,7 +27,7 @@ struct info_metrics_t { #define PHY_METRICS_SET(PARAM) \ do { \ - PARAM = PARAM + (other.PARAM - PARAM) / count; \ + PARAM = SRSRAN_VEC_SAFE_CMA(other.PARAM, PARAM, count); \ } while (false) struct sync_metrics_t { @@ -41,12 +41,12 @@ struct sync_metrics_t { void set(const sync_metrics_t& other) { - count++; ta_us = other.ta_us; distance_km = other.distance_km; speed_kmph = other.speed_kmph; PHY_METRICS_SET(cfo); PHY_METRICS_SET(sfo); + count++; } void reset() diff --git a/srsue/src/phy/nr/cc_worker.cc b/srsue/src/phy/nr/cc_worker.cc index a69faafae..11478547f 100644 --- a/srsue/src/phy/nr/cc_worker.cc +++ b/srsue/src/phy/nr/cc_worker.cc @@ -286,10 +286,11 @@ bool cc_worker::decode_pdsch_dl() srsran_sch_cfg_nr_info(&pdsch_cfg, str_extra.data(), (uint32_t)str_extra.size()); logger.info(pdsch_res.tb[0].payload, pdsch_cfg.grant.tb[0].tbs / 8, - "PDSCH: cc=%d pid=%d %s\n%s", + "PDSCH: cc=%d pid=%d %s cfo=%.1f\n%s", cc_idx, pid, str.data(), + ue_dl.chest.cfo, str_extra.data()); } else { logger.info(pdsch_res.tb[0].payload, @@ -381,27 +382,13 @@ bool cc_worker::measure_csi() logger.debug("SSB-CSI: %s", str.data()); } - // Compute channel metrics and push it - ch_metrics_t ch_metrics = {}; - ch_metrics.sinr = meas.snr_dB; - ch_metrics.rsrp = meas.rsrp_dB; - ch_metrics.rsrq = 0.0f; // Not supported - ch_metrics.rssi = 0.0f; // Not supported - ch_metrics.sync_err = - meas.delay_us / (float)(ue_dl.fft->fft_plan.size * SRSRAN_SUBC_SPACING_NR(phy.cfg.carrier.scs)); - phy.set_channel_metrics(ch_metrics); - - // Compute synch metrics and report it to the PHY state - sync_metrics_t sync_metrics = {}; - sync_metrics.cfo = meas.cfo_hz; - phy.set_sync_metrics(sync_metrics); - // Report SSB candidate channel measurement to the PHY state - // ... + phy.new_csi_trs_measurement(meas); } } // Iterate all NZP-CSI-RS marked as TRS and perform channel measurements + bool estimate_fft = false; for (uint32_t resource_set_id = 0; resource_set_id < SRSRAN_PHCH_CFG_MAX_NOF_CSI_RS_SETS; resource_set_id++) { // Select NZP-CSI-RS set const srsran_csi_rs_nzp_set_t& nzp_set = phy.cfg.pdsch.nzp_csi_rs_sets[resource_set_id]; @@ -411,6 +398,12 @@ bool cc_worker::measure_csi() continue; } + // Run FFT if not done before in this slot + if (not estimate_fft) { + srsran_ue_dl_nr_estimate_fft(&ue_dl, &dl_slot_cfg); + estimate_fft = true; + } + // Perform measurement, n > 0 is any measurement is performed, n = 0 otherwise srsran_csi_trs_measurements_t trs_measurements = {}; int n = srsran_ue_dl_nr_csi_measure_trs(&ue_dl, &dl_slot_cfg, &nzp_set, &trs_measurements); @@ -430,30 +423,7 @@ bool cc_worker::measure_csi() logger.debug("NZP-CSI-RS (TRS): id=%d %s", resource_set_id, str.data()); } - // Compute channel metrics and push it - ch_metrics_t ch_metrics = {}; - ch_metrics.sinr = trs_measurements.snr_dB; - ch_metrics.rsrp = trs_measurements.rsrp_dB; - ch_metrics.rsrq = 0.0f; // Not supported - ch_metrics.rssi = 0.0f; // Not supported - ch_metrics.sync_err = - trs_measurements.delay_us / (float)(ue_dl.fft->fft_plan.size * SRSRAN_SUBC_SPACING_NR(phy.cfg.carrier.scs)); - phy.set_channel_metrics(ch_metrics); - - // Compute synch metrics and report it to the PHY state - sync_metrics_t sync_metrics = {}; - sync_metrics.cfo = trs_measurements.cfo_hz; - phy.set_sync_metrics(sync_metrics); - - // Convert to CSI channel measurement and report new NZP-CSI-RS measurement to the PHY state - srsran_csi_channel_measurements_t measurements = {}; - measurements.cri = 0; - measurements.wideband_rsrp_dBm = trs_measurements.rsrp_dB; - measurements.wideband_epre_dBm = trs_measurements.epre_dB; - measurements.wideband_snr_db = trs_measurements.snr_dB; - measurements.nof_ports = 1; // Other values are not supported - measurements.K_csi_rs = (uint32_t)n; - phy.new_nzp_csi_rs_channel_measurement(measurements, resource_set_id); + phy.new_csi_trs_measurement(trs_measurements, resource_set_id, (uint32_t)n); } // Iterate all NZP-CSI-RS not marked as TRS and perform channel measurements @@ -466,6 +436,12 @@ bool cc_worker::measure_csi() continue; } + // Run FFT if not done before in this slot + if (not estimate_fft) { + srsran_ue_dl_nr_estimate_fft(&ue_dl, &dl_slot_cfg); + estimate_fft = true; + } + // Perform channel measurement, n > 0 is any measurement is performed, n = 0 otherwise srsran_csi_channel_measurements_t measurements = {}; int n = srsran_ue_dl_nr_csi_measure_channel(&ue_dl, &dl_slot_cfg, &nzp_set, &measurements); @@ -504,6 +480,22 @@ bool cc_worker::work_dl() return true; } + // Measure CSI + if (not measure_csi()) { + logger.error("Error measuring, aborting work DL"); + return false; + } + + // Compensate CFO from TRS measurements + if (std::isnormal(phy.args.enable_worker_cfo)) { + float dl_cfo_hz = phy.get_dl_cfo(); + for (cf_t* b : rx_buffer) { + if (b != nullptr and ue_ul.ifft.sf_sz != 0) { + srsran_vec_apply_cfo(b, dl_cfo_hz / (1000.0f * ue_ul.ifft.sf_sz), b, ue_ul.ifft.sf_sz); + } + } + } + // Run FFT srsran_ue_dl_nr_estimate_fft(&ue_dl, &dl_slot_cfg); @@ -519,12 +511,6 @@ bool cc_worker::work_dl() return false; } - // Measure CSI - if (not measure_csi()) { - logger.error("Error measuring, aborting work DL"); - return false; - } - return true; } @@ -582,6 +568,9 @@ bool cc_worker::work_ul() // Add CSI reports to UCI data if available phy.get_periodic_csi(ul_slot_cfg.idx, uci_data); + // Setup frequency offset + srsran_ue_ul_nr_set_freq_offset(&ue_ul, phy.get_ul_cfo()); + if (has_pusch_grant) { // Notify MAC about PUSCH found grant mac_interface_phy_nr::tb_action_ul_t ul_action = {}; diff --git a/srsue/src/phy/nr/worker_pool.cc b/srsue/src/phy/nr/worker_pool.cc index 44e985784..53fa8f13b 100644 --- a/srsue/src/phy/nr/worker_pool.cc +++ b/srsue/src/phy/nr/worker_pool.cc @@ -79,7 +79,7 @@ sf_worker* worker_pool::wait_worker(uint32_t tti) if (prach_buffer->is_ready_to_send(tti, phy_state.cfg.carrier.pci)) { uint32_t nof_prach_sf = 0; float prach_target_power = 0.0f; - cf_t* prach_ptr = prach_buffer->generate(0.0f, &nof_prach_sf, &prach_target_power); + cf_t* prach_ptr = prach_buffer->generate(-phy_state.get_ul_cfo() / 15000, &nof_prach_sf, &prach_target_power); worker->set_prach(prach_ptr, prach_target_power); } diff --git a/srsue/src/phy/sync.cc b/srsue/src/phy/sync.cc index 22b1c3ced..5d7c56608 100644 --- a/srsue/src/phy/sync.cc +++ b/srsue/src/phy/sync.cc @@ -541,6 +541,10 @@ void sync::run_camping_in_sync_state(lte::sf_worker* lte_worker, nr_worker->set_context(context); + // As UE sync compensates CFO externally based on LTE signal and the NR carrier may estimate the CFO from the LTE + // signal. It is necessary setting an NR external CFO offset to compensate it. + nr_worker_pool->set_ul_ext_cfo(-srsran_ue_sync_get_cfo(&ue_sync)); + // NR worker needs to be launched first, phy_common::worker_end expects first the NR worker and the LTE worker. worker_com->semaphore.push(nr_worker); nr_worker_pool->start_worker(nr_worker);