@@ -142,6 +142,37 @@ static unsigned extract_layer_hop_rx_pilots(dmrs_symbol_list&
142142 unsigned hop,
143143 unsigned i_layer);
144144
145+ // / \brief Preprocesses the pilots and computes the CFO.
146+ // /
147+ // / For the current hop, the function does the following:
148+ // / - matches the received pilots with the expected ones (element-wise multiplication with complex conjugate);
149+ // / - estimates the CFO (if the number of OFDM symbols with pilots is at least 2);
150+ // / - compensates the CFO for all pilots;
151+ // / - accumulates all the matched, CFO-compensated received pilots.
152+ // / \param[out] pilots_lse Result of the accumulation of all matched pilots.
153+ // / \param[out] pilot_products Helper buffer for internal computations (same size as pilots_lse).
154+ // / \param[in] rx_pilots Received pilots.
155+ // / \param[in] pilots Transmitted pilots.
156+ // / \param[in] scs Subcarrier spacing.
157+ // / \param[in] cp_cum_duration Cumulative duration of all CPs in the slot.
158+ // / \param[in] first_hop_symbol Index of the first OFDM symbol of the current hop, within the slot.
159+ // / \param[in] last_hop_symbol Index of the last OFDM symbol of the current hop (not included), within the slot.
160+ // / \param[in] hop_offset Number of OFDM symbols carrying DM-RS in the previous hop.
161+ // / \param[in] i_layer The considered transmission layer.
162+ // / \return A contribution to the EPRE and CFO estimates. CFO is empty if the hop has only one OFDM symbol carrying
163+ // / DM-RS.
164+ static std::pair<float , optional<float >> preprocess_pilots_and_cfo (span<cf_t > pilots_lse,
165+ span<cf_t > pilot_products,
166+ const dmrs_symbol_list& rx_pilots,
167+ const dmrs_symbol_list& pilots,
168+ const bounded_bitset<MAX_NSYMB_PER_SLOT>& dmrs_mask,
169+ const subcarrier_spacing& scs,
170+ span<const float > cp_cum_duration,
171+ unsigned first_hop_symbol,
172+ unsigned last_hop_symbol,
173+ unsigned hop_offset,
174+ unsigned i_layer);
175+
145176// / \brief Estimates the noise energy of one hop.
146177// / \param[in] pilots DM-RS pilots.
147178// / \param[in] rx_pilots Received samples corresponding to DM-RS pilots.
@@ -199,26 +230,47 @@ void port_channel_estimator_average_impl::compute(channel_estimate& es
199230 // Prepare symbol destination.
200231 rx_pilots.resize (symbols_size);
201232
233+ // Compute the cumulative duration of all CPs for the given subcarrier spacing.
234+ initialize_cp_cum_duration (cfg.cp , cfg.scs );
235+
202236 // For each layer...
203237 for (unsigned i_layer = 0 , nof_tx_layers = cfg.dmrs_pattern .size (); i_layer != nof_tx_layers; ++i_layer) {
204238 rsrp = 0 ;
205239 epre = 0 ;
206240 noise_var = 0 ;
207241 time_alignment_s = 0 ;
242+ cfo_normalized = nullopt ;
208243
209- // compute_layer_hop updates rsrp, epre, niose_var and time_alignment_s .
210- compute_layer_hop (estimate, grid, port, pilots, cfg, 0 , i_layer);
244+ // compute_layer_hop updates rsrp, epre, niose_var, time_alignment_s, and cfo_normalized .
245+ compute_layer_hop (estimate, grid, port, pilots, cfg, /* hop= */ 0 , i_layer);
211246 if (cfg.dmrs_pattern [i_layer].hopping_symbol_index .has_value ()) {
212- compute_layer_hop (estimate, grid, port, pilots, cfg, 1 , i_layer);
247+ compute_layer_hop (estimate, grid, port, pilots, cfg, /* hop= */ 1 , i_layer);
213248 time_alignment_s /= 2 .0F ;
214249 }
215250
251+ if (cfo_normalized.has_value ()) {
252+ // Apply CFO to the estimated channel.
253+ float cfo = cfo_normalized.value ();
254+ for (unsigned i_symbol = cfg.first_symbol , last_symbol = cfg.first_symbol + cfg.nof_symbols ;
255+ i_symbol != last_symbol;
256+ ++i_symbol) {
257+ span<cf_t > symbol_ch_estimate = estimate.get_symbol_ch_estimate (i_symbol);
258+ srsvec::sc_prod (symbol_ch_estimate,
259+ std::polar (1 .0f , TWOPI * (i_symbol + cp_cum_duration[i_symbol]) * cfo),
260+ symbol_ch_estimate);
261+ }
262+ }
263+
216264 rsrp /= static_cast <float >(nof_dmrs_pilots);
217265 epre /= static_cast <float >(nof_dmrs_pilots);
218266
219267 estimate.set_rsrp (rsrp, port, i_layer);
220268 estimate.set_epre (epre, port, i_layer);
221269 estimate.set_time_alignment (phy_time_unit::from_seconds (time_alignment_s), port, i_layer);
270+ estimate.set_cfo_Hz (
271+ cfo_normalized.has_value () ? optional<float >(cfo_normalized.value () * scs_to_khz (cfg.scs ) * 1000 ) : nullopt ,
272+ port,
273+ i_layer);
222274
223275 noise_var /= static_cast <float >(nof_dmrs_pilots - 1 );
224276
@@ -271,19 +323,27 @@ void port_channel_estimator_average_impl::compute_layer_hop(srsran::channel_esti
271323
272324 span<cf_t > pilot_products = span<cf_t >(aux_pilot_products).first (pilots.size ().nof_subc );
273325 span<cf_t > pilots_lse = span<cf_t >(aux_pilots_lse).subspan (MAX_V_PILOTS, pilots.size ().nof_subc );
274- srsvec::prod_conj (rx_pilots.get_symbol (0 , i_layer), pilots.get_symbol (hop_offset, i_layer), pilots_lse);
275326
276- epre += std::real (srsvec::dot_prod (rx_pilots.get_symbol (0 , i_layer), rx_pilots.get_symbol (0 , i_layer)));
277-
278- // Accumulate all symbols frequency domain response.
279- for (unsigned i_dmrs_symbol = 1 ; i_dmrs_symbol != nof_dmrs_symbols; ++i_dmrs_symbol) {
280- srsvec::prod_conj (rx_pilots.get_symbol (i_dmrs_symbol, i_layer),
281- pilots.get_symbol (hop_offset + i_dmrs_symbol, i_layer),
282- pilot_products);
283- srsvec::add (pilots_lse, pilot_products, pilots_lse);
284-
285- epre += std::real (
286- srsvec::dot_prod (rx_pilots.get_symbol (i_dmrs_symbol, i_layer), rx_pilots.get_symbol (i_dmrs_symbol, i_layer)));
327+ std::pair</* epre*/ float , /* cfo*/ optional<float >> hop_results = preprocess_pilots_and_cfo (pilots_lse,
328+ pilot_products,
329+ rx_pilots,
330+ pilots,
331+ pattern.symbols ,
332+ cfg.scs ,
333+ cp_cum_duration,
334+ first_symbol,
335+ last_symbol,
336+ hop_offset,
337+ i_layer);
338+ epre += hop_results.first ;
339+
340+ if (hop_results.second .has_value ()) {
341+ float cfo_hop = hop_results.second .value ();
342+ if (cfo_normalized.has_value ()) {
343+ cfo_normalized = (cfo_normalized.value () + cfo_hop) / 2 ;
344+ } else {
345+ cfo_normalized = cfo_hop;
346+ }
287347 }
288348
289349 // Average and apply DM-RS-to-data gain.
@@ -378,6 +438,79 @@ static unsigned extract_layer_hop_rx_pilots(dmrs_symbol_list&
378438 return dmrs_symbol_index;
379439}
380440
441+ void port_channel_estimator_average_impl::initialize_cp_cum_duration (cyclic_prefix cp, subcarrier_spacing scs)
442+ {
443+ unsigned nof_symbols_slot = get_nsymb_per_slot (cp);
444+ cp_cum_duration = span<float >(cp_cum_help).first (nof_symbols_slot);
445+
446+ // Compute cumulative duration of CPs.
447+ cp_cum_duration[0 ] = cp.get_length (0 , scs).to_seconds () * scs_to_khz (scs) * 1000 ;
448+ for (unsigned i_cp = 1 ; i_cp != nof_symbols_slot; ++i_cp) {
449+ cp_cum_duration[i_cp] = cp_cum_duration[i_cp - 1 ] + cp.get_length (i_cp, scs).to_seconds () * scs_to_khz (scs) * 1000 ;
450+ }
451+ }
452+
453+ static std::pair<float , optional<float >> preprocess_pilots_and_cfo (span<cf_t > pilots_lse,
454+ span<cf_t > pilot_products,
455+ const dmrs_symbol_list& rx_pilots,
456+ const dmrs_symbol_list& pilots,
457+ const bounded_bitset<MAX_NSYMB_PER_SLOT>& dmrs_mask,
458+ const subcarrier_spacing& scs,
459+ span<const float > cp_cum_duration,
460+ unsigned first_hop_symbol,
461+ unsigned last_hop_symbol,
462+ unsigned hop_offset,
463+ unsigned i_layer)
464+ {
465+ // Number of OFDM symbols carrying DM-RS in the current hop.
466+ unsigned nof_dmrs_symbols = dmrs_mask.slice (first_hop_symbol, last_hop_symbol).count ();
467+
468+ // Match received and transmitted pilots in the first DM-RS symbol and compute EPRE contribution.
469+ srsvec::prod_conj (rx_pilots.get_symbol (0 , i_layer), pilots.get_symbol (hop_offset, i_layer), pilots_lse);
470+ float epre = std::real (srsvec::dot_prod (rx_pilots.get_symbol (0 , i_layer), rx_pilots.get_symbol (0 , i_layer)));
471+
472+ if (nof_dmrs_symbols == 1 ) {
473+ return {epre, nullopt };
474+ }
475+
476+ // Match received and transmitted pilots in the second DM-RS symbol and compute EPRE contribution.
477+ srsvec::prod_conj (rx_pilots.get_symbol (1 , i_layer), pilots.get_symbol (hop_offset + 1 , i_layer), pilot_products);
478+ epre += std::real (srsvec::dot_prod (rx_pilots.get_symbol (1 , i_layer), rx_pilots.get_symbol (1 , i_layer)));
479+
480+ // Use the first two DM-RS symbols to estimate the CFO.
481+ unsigned i_dmrs_0 = dmrs_mask.find_lowest (first_hop_symbol, last_hop_symbol);
482+ unsigned i_dmrs_1 = dmrs_mask.find_lowest (i_dmrs_0 + 1 , dmrs_mask.size ());
483+
484+ cf_t noisy_phase = srsvec::dot_prod (pilot_products, pilots_lse);
485+
486+ float cfo =
487+ std::arg (noisy_phase) / TWOPI / (i_dmrs_1 - i_dmrs_0 + cp_cum_duration[i_dmrs_1] - cp_cum_duration[i_dmrs_0]);
488+
489+ // Compensate the CFO in the first two DM-RS symbols and combine them.
490+ srsvec::sc_prod (pilots_lse, std::polar (1 .0f , -TWOPI * (i_dmrs_0 + cp_cum_duration[i_dmrs_0]) * cfo), pilots_lse);
491+ srsvec::sc_prod (
492+ pilot_products, std::polar (1 .0f , -TWOPI * (i_dmrs_1 + cp_cum_duration[i_dmrs_1]) * cfo), pilot_products);
493+ srsvec::add (pilots_lse, pilot_products, pilots_lse);
494+
495+ // If there are other DM-RS symbols in the hop, match them with the corresponding transmitted symbols, compensate the
496+ // CFO and combine with the previous DM-RS symbols.
497+ if (i_dmrs_1 < last_hop_symbol) {
498+ auto combine_pilots = [&, i_dmrs = 2 ](size_t i_symbol) mutable {
499+ srsvec::prod_conj (
500+ rx_pilots.get_symbol (i_dmrs, i_layer), pilots.get_symbol (hop_offset + i_dmrs, i_layer), pilot_products);
501+ srsvec::sc_prod (
502+ pilot_products, std::polar (1 .0f , -TWOPI * (i_symbol + cp_cum_duration[i_symbol]) * cfo), pilot_products);
503+ srsvec::add (pilots_lse, pilot_products, pilots_lse);
504+ epre += std::real (srsvec::dot_prod (rx_pilots.get_symbol (i_dmrs, i_layer), rx_pilots.get_symbol (i_dmrs, i_layer)));
505+ i_dmrs++;
506+ };
507+
508+ dmrs_mask.for_each (i_dmrs_1 + 1 , last_hop_symbol, combine_pilots);
509+ }
510+
511+ return {epre, cfo};
512+ }
513+
381514static float estimate_noise (const dmrs_symbol_list& pilots,
382515 const dmrs_symbol_list& rx_pilots,
383516 span<const cf_t > estimates,
0 commit comments