Skip to content

Commit 04b79a5

Browse files
committed
phy: Review PUCCH formats CSI measurements with zeros in the resource grid
1 parent e79e0af commit 04b79a5

File tree

9 files changed

+208
-15
lines changed

9 files changed

+208
-15
lines changed

include/srsran/phy/upper/channel_estimation.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,7 @@ class channel_estimate
160160
return rsrp_all_ports / noise_var_all_ports;
161161
}
162162

163-
// If noise variance is 0, report and SNR of 60 dB.
164-
return 1e6;
163+
return 0;
165164
}
166165

167166
/// Returns the estimated SNR for the given Rx port (dB scale).

include/srsran/phy/upper/channel_state_information.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ class channel_state_information
232232
for (unsigned i_port = 0; i_port != nof_ports; ++i_port) {
233233
float rsrp_lin = rsrp_per_port_lin[i_port];
234234
// Set the RSRP to NaN if the measurement is not valid and don't include it in the total RSRP.
235-
if (std::isnan(rsrp_lin)) {
236-
port_rsrp_dB[i_port] = std::numeric_limits<float>::quiet_NaN();
235+
if (!std::isnormal(rsrp_lin)) {
236+
port_rsrp_dB[i_port] = -std::numeric_limits<float>::infinity();
237237
continue;
238238
}
239239

@@ -245,11 +245,11 @@ class channel_state_information
245245
}
246246

247247
// Compute a global RSRP metric as the average of all valid RSRP values.
248-
if (rsrp_total_lin > 0.0F) {
249-
rsrp_dB = convert_power_to_dB(rsrp_total_lin / static_cast<float>(nof_valid_rsrp_values));
250-
} else {
251-
rsrp_dB.reset();
248+
float rsrp_lin = 0;
249+
if (nof_valid_rsrp_values != 0) {
250+
rsrp_lin = rsrp_total_lin / static_cast<float>(nof_valid_rsrp_values);
252251
}
252+
rsrp_dB = convert_power_to_dB(rsrp_lin);
253253
}
254254

255255
/// \brief Gets the Reference Signal Received Power (RSRP) in normalized dB units.

lib/phy/upper/channel_processors/pucch/pucch_detector_format1.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,15 @@ pucch_detector_format1::detect(const resource_grid_reader& grid
221221
++this_metric_hop1;
222222
}
223223

224+
// Determine SINR. Set the SINR to zero if the noise variance is zero, NaN or infinity.
225+
float sinr = 0;
226+
if (std::isnormal(noise_var)) {
227+
sinr = rsrp / noise_var;
228+
}
229+
224230
channel_state_information csi;
225231
csi.set_epre(convert_power_to_dB(epre));
226232
csi.set_rsrp_dB(convert_power_to_dB(rsrp));
227-
float sinr = std::isnormal(noise_var) ? rsrp / noise_var : std::numeric_limits<float>::quiet_NaN();
228233
csi.set_sinr_dB(channel_state_information::sinr_type::channel_estimator, convert_power_to_dB(sinr));
229234
csi.reset_time_alignment();
230235

lib/phy/upper/signal_processors/channel_estimator/port_channel_estimator_average_impl.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,14 @@ void port_channel_estimator_average_impl::do_compute(channel_estimate&
158158
// Compute the estimated data received power by scaling the RSRP.
159159
float datarp = rsrp * nof_tx_layers / cfg.scaling / cfg.scaling;
160160

161-
estimate.set_snr((noise_var != 0) ? datarp / noise_var : 1000, port);
161+
// Determine the linear SNR measurement. The linear SNR must result to zero if the noise variance is zero, NaN or
162+
// infinity.
163+
float snr = 0;
164+
if (std::isnormal(noise_var)) {
165+
snr = datarp / noise_var;
166+
}
167+
168+
estimate.set_snr(snr, port);
162169

163170
// Report stats for each layer.
164171
for (unsigned i_layer = 0; i_layer != nof_tx_layers; ++i_layer) {
@@ -310,10 +317,17 @@ void port_channel_estimator_average_impl::compute_hop(srsran::channel_estimate&
310317
interpolator_cfg.stride,
311318
fd_smoothing_strategy);
312319

313-
// Measure reference signal received power after filtering.
314-
rsrp += srsvec::average_power(filtered_pilots_lse.get_symbol(i_symbol, i_layer)) *
315-
filtered_pilots_lse.get_symbol(i_symbol, i_layer).size() * beta_scaling * beta_scaling *
316-
static_cast<float>(nof_dmrs_symbols) / static_cast<float>(nof_lse_symbols);
320+
// Energy of the DM-RS in the current symbol and layer.
321+
float avg = srsvec::average_power(filtered_pilots_lse.get_symbol(i_symbol, i_layer)) *
322+
filtered_pilots_lse.get_symbol(i_symbol, i_layer).size();
323+
324+
// Normalization factor: the factor nof_dmrs_symbols / nof_lse_symbols accounts for whether the
325+
// symbols carrying DM-RS have been combined or not (i.e., td_interpolation_strategy is average or interpolate,
326+
// respectively), while beta_scaling^2 accounts for the data-to-DM-RS scaling.
327+
float power_normalization_factor =
328+
beta_scaling * beta_scaling * static_cast<float>(nof_dmrs_symbols) / static_cast<float>(nof_lse_symbols);
329+
avg *= power_normalization_factor;
330+
rsrp += avg;
317331

318332
// Interpolate frequency response for this symbol.
319333
freq_interpolator->interpolate(
@@ -772,7 +786,8 @@ static float estimate_noise(const dmrs_symbol_list& pilots,
772786
// Process each OFDM containing DM-RS.
773787
dmrs_mask.for_each(first_hop_symbol, last_hop_symbol, estimate_noise_symbol);
774788

775-
return noise_energy;
789+
// Return 0 if the resultant noise is NaN or infinity.
790+
return std::isnormal(noise_energy) ? noise_energy : 0;
776791
}
777792

778793
__attribute_noinline__ static void

tests/unittests/phy/upper/channel_processors/pucch/pucch_processor_format0_vectortest.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,33 @@ TEST_P(PucchProcessorFormat0Fixture, FromVector)
104104
ASSERT_TRUE(result.message.get_csi_part2_bits().empty());
105105
}
106106

107+
TEST_P(PucchProcessorFormat0Fixture, FromVectorZeros)
108+
{
109+
// Prepare resource grid.
110+
resource_grid_reader_spy grid(MAX_PORTS, MAX_NSYMB_PER_SLOT, MAX_NOF_PRBS);
111+
std::vector<resource_grid_reader_spy::expected_entry_t> grid_entries = GetParam().grid.read();
112+
for (auto& entry : grid_entries) {
113+
entry.value = cf_t(0, 0);
114+
}
115+
grid.write(grid_entries);
116+
117+
const PucchProcessorFormat0Param& param = GetParam();
118+
119+
const pucch_entry& entry = param.entry;
120+
// Make sure configuration is valid.
121+
ASSERT_TRUE(validator->is_valid(entry.config));
122+
123+
pucch_processor_result result = processor->process(grid, entry.config);
124+
125+
// UCI payload is expected to be invalid.
126+
ASSERT_EQ(result.message.get_status(), uci_status::invalid);
127+
128+
// The resource grid is empty, so SINR, EPRE & RSRP are expected to be -inf (0 in linear scale).
129+
ASSERT_EQ(*result.csi.get_sinr_dB(), -std::numeric_limits<float>::infinity());
130+
ASSERT_EQ(*result.csi.get_epre_dB(), -std::numeric_limits<float>::infinity());
131+
ASSERT_EQ(*result.csi.get_rsrp_dB(), -std::numeric_limits<float>::infinity());
132+
}
133+
107134
TEST_P(PucchProcessorFormat0Fixture, FalseAlarm)
108135
{
109136
std::vector<resource_grid_reader_spy::expected_entry_t> res = GetParam().grid.read();

tests/unittests/phy/upper/channel_processors/pucch/pucch_processor_format1_vectortest.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ TEST_P(PucchProcessorFormat1Fixture, FromVector)
7878
<< fmt::format("The number of ports of the resource grid {} and the number of configured ports {} do not match.",
7979
grid.get_nof_ports(),
8080
tmp_conf.ports.size());
81+
8182
pucch_processor::format1_batch_configuration batch_config(tmp_conf);
8283
batch_config.entries.clear();
8384

@@ -166,6 +167,59 @@ TEST_P(PucchProcessorFormat1Fixture, FromVector)
166167
ASSERT_LE(nof_false_alarms, 1) << "Too many false alarms.";
167168
}
168169

170+
TEST_P(PucchProcessorFormat1Fixture, FromVectorZeros)
171+
{
172+
// Prepare resource grid.
173+
resource_grid_reader_spy grid;
174+
std::vector<resource_grid_reader_spy::expected_entry_t> grid_entries = GetParam().grid.read();
175+
for (auto& entry : grid_entries) {
176+
entry.value = cf_t(0, 0);
177+
}
178+
grid.write(grid_entries);
179+
180+
const PucchProcessorFormat1Param& param = GetParam();
181+
182+
pucch_processor::format1_configuration tmp_conf = param.common_config;
183+
pucch_processor::format1_batch_configuration batch_config(tmp_conf);
184+
batch_config.entries.clear();
185+
186+
for (const auto& mux_pucch : param.payloads) {
187+
tmp_conf.initial_cyclic_shift = mux_pucch.initial_cyclic_shift;
188+
tmp_conf.time_domain_occ = mux_pucch.time_domain_occ;
189+
tmp_conf.nof_harq_ack = mux_pucch.nof_harq_ack;
190+
191+
// Make sure configuration is valid.
192+
ASSERT_TRUE(validator->is_valid(tmp_conf));
193+
194+
batch_config.entries.insert(
195+
tmp_conf.initial_cyclic_shift, tmp_conf.time_domain_occ, {std::nullopt, tmp_conf.nof_harq_ack});
196+
}
197+
198+
const pucch_format1_map<pucch_processor_result>& results = processor->process(grid, batch_config);
199+
200+
ASSERT_EQ(results.size(), batch_config.entries.size())
201+
<< "The number of configured and processed PUCCHs does not match.";
202+
203+
unsigned nof_false_alarms = 0;
204+
for (const auto& mux_pucch : param.payloads) {
205+
unsigned initial_cyclic_shift = mux_pucch.initial_cyclic_shift;
206+
unsigned time_domain_occ = mux_pucch.time_domain_occ;
207+
208+
ASSERT_TRUE(results.contains(initial_cyclic_shift, time_domain_occ)) << fmt::format(
209+
"PUCCH with ICS {} and OCCI {} is missing from the results.", initial_cyclic_shift, time_domain_occ);
210+
211+
const pucch_processor_result& this_result = results.get(initial_cyclic_shift, time_domain_occ);
212+
213+
// UCI payload is expected to be invalid.
214+
ASSERT_EQ(this_result.message.get_status(), uci_status::invalid);
215+
// The resource grid is empty, so SINR, EPRE & RSRP are expected to be -inf (0 in linear scale).
216+
ASSERT_EQ(*this_result.csi.get_sinr_dB(), -std::numeric_limits<float>::infinity());
217+
ASSERT_EQ(*this_result.csi.get_epre_dB(), -std::numeric_limits<float>::infinity());
218+
ASSERT_EQ(*this_result.csi.get_rsrp_dB(), -std::numeric_limits<float>::infinity());
219+
}
220+
ASSERT_LE(nof_false_alarms, 1) << "Too many false alarms.";
221+
}
222+
169223
INSTANTIATE_TEST_SUITE_P(PucchProcessorFormat1,
170224
PucchProcessorFormat1Fixture,
171225
::testing::ValuesIn(pucch_processor_format1_test_data));

tests/unittests/phy/upper/channel_processors/pucch/pucch_processor_format2_vectortest.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,35 @@ TEST_P(PucchProcessorF2Fixture, PucchProcessorF2VectorTest)
7070
ASSERT_EQ(result.message.get_csi_part2_bits(), span<uint8_t>(expected_csi_part_2));
7171
}
7272

73+
TEST_P(PucchProcessorF2Fixture, PucchProcessorF2ZerosTest)
74+
{
75+
const test_case_t& test_case = GetParam();
76+
const context_t& context = test_case.context;
77+
const pucch_processor::format2_configuration& config = context.config;
78+
79+
// Prepare resource grid.
80+
resource_grid_reader_spy grid(MAX_PORTS, MAX_NSYMB_PER_SLOT, MAX_NOF_PRBS);
81+
std::vector<resource_grid_reader_spy::expected_entry_t> grid_entries = GetParam().grid.read();
82+
for (auto& entry : grid_entries) {
83+
entry.value = cf_t(0, 0);
84+
}
85+
grid.write(grid_entries);
86+
87+
// Make sure configuration is valid.
88+
ASSERT_TRUE(validator->is_valid(config));
89+
90+
// Process PUCCH.
91+
pucch_processor_result result = processor->process(grid, config);
92+
93+
// UCI payload is expected to be invalid.
94+
ASSERT_EQ(result.message.get_status(), uci_status::invalid);
95+
96+
// The resource grid is empty, so SINR, EPRE & RSRP are expected to be -inf (0 in linear scale).
97+
ASSERT_EQ(*result.csi.get_sinr_dB(), -std::numeric_limits<float>::infinity());
98+
ASSERT_EQ(*result.csi.get_epre_dB(), -std::numeric_limits<float>::infinity());
99+
ASSERT_EQ(*result.csi.get_rsrp_dB(), -std::numeric_limits<float>::infinity());
100+
}
101+
73102
// Creates test suite that combines all possible parameters.
74103
INSTANTIATE_TEST_SUITE_P(PucchProcessorF2VectorTest,
75104
PucchProcessorF2Fixture,

tests/unittests/phy/upper/channel_processors/pucch/pucch_processor_format3_vectortest.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,38 @@ TEST_P(PucchProcessorF3Fixture, PucchProcessorF3VectorTest)
7777
ASSERT_EQ(span<const uint8_t>(result.message.get_csi_part2_bits()), span<const uint8_t>());
7878
}
7979

80+
TEST_P(PucchProcessorF3Fixture, PucchProcessorF3VectorZerosTest)
81+
{
82+
const test_case_t& test_case = GetParam();
83+
const context_t& context = test_case.context;
84+
const pucch_processor::format3_configuration& config = context.config;
85+
std::vector<uint8_t> uci_bits = test_case.uci_bits.read();
86+
87+
// Prepare resource grid.
88+
resource_grid_reader_spy grid(MAX_PORTS, MAX_NSYMB_PER_SLOT, MAX_NOF_PRBS);
89+
std::vector<resource_grid_reader_spy::expected_entry_t> grid_entries = GetParam().grid.read();
90+
for (auto& entry : grid_entries) {
91+
entry.value = cf_t(0, 0);
92+
}
93+
grid.write(grid_entries);
94+
95+
// Make sure configuration is valid.
96+
error_type<std::string> validation = validator->is_valid(config);
97+
ASSERT_TRUE(validation.has_value()) << fmt::format("PUCCH configuration validation failed with message:\n {}",
98+
validation.error());
99+
100+
// Process PUCCH.
101+
pucch_processor_result result = processor->process(grid, config);
102+
103+
// UCI payload is expected to be invalid.
104+
ASSERT_EQ(result.message.get_status(), uci_status::invalid);
105+
106+
// The resource grid is empty, so SINR, EPRE & RSRP are expected to be -inf (0 in linear scale).
107+
ASSERT_EQ(*result.csi.get_sinr_dB(), -std::numeric_limits<float>::infinity());
108+
ASSERT_EQ(*result.csi.get_epre_dB(), -std::numeric_limits<float>::infinity());
109+
ASSERT_EQ(*result.csi.get_rsrp_dB(), -std::numeric_limits<float>::infinity());
110+
}
111+
80112
// Creates test suite that combines all possible parameters.
81113
INSTANTIATE_TEST_SUITE_P(PucchProcessorF3VectorTest,
82114
PucchProcessorF3Fixture,

tests/unittests/phy/upper/channel_processors/pucch/pucch_processor_format4_vectortest.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,38 @@ TEST_P(PucchProcessorF4Fixture, PucchProcessorF4VectorTest)
7777
ASSERT_EQ(span<const uint8_t>(result.message.get_csi_part2_bits()), span<const uint8_t>());
7878
}
7979

80+
TEST_P(PucchProcessorF4Fixture, PucchProcessorF4VectorZerosTest)
81+
{
82+
const test_case_t& test_case = GetParam();
83+
const context_t& context = test_case.context;
84+
const pucch_processor::format4_configuration& config = context.config;
85+
std::vector<uint8_t> uci_bits = test_case.uci_bits.read();
86+
87+
// Prepare resource grid.
88+
resource_grid_reader_spy grid(MAX_PORTS, MAX_NSYMB_PER_SLOT, MAX_NOF_PRBS);
89+
std::vector<resource_grid_reader_spy::expected_entry_t> grid_entries = GetParam().grid.read();
90+
for (auto& entry : grid_entries) {
91+
entry.value = cf_t(0, 0);
92+
}
93+
grid.write(grid_entries);
94+
95+
// Make sure configuration is valid.
96+
error_type<std::string> validation = validator->is_valid(config);
97+
ASSERT_TRUE(validation.has_value()) << fmt::format("PUCCH configuration validation failed with message:\n {}",
98+
validation.error());
99+
100+
// Process PUCCH.
101+
pucch_processor_result result = processor->process(grid, config);
102+
103+
// UCI payload is expected to be invalid.
104+
ASSERT_EQ(result.message.get_status(), uci_status::invalid);
105+
106+
// The resource grid is empty, so SINR, EPRE & RSRP are expected to be -inf (0 in linear scale).
107+
ASSERT_EQ(*result.csi.get_sinr_dB(), -std::numeric_limits<float>::infinity());
108+
ASSERT_EQ(*result.csi.get_epre_dB(), -std::numeric_limits<float>::infinity());
109+
ASSERT_EQ(*result.csi.get_rsrp_dB(), -std::numeric_limits<float>::infinity());
110+
}
111+
80112
// Creates test suite that combines all possible parameters.
81113
INSTANTIATE_TEST_SUITE_P(PucchProcessorF4VectorTest,
82114
PucchProcessorF4Fixture,

0 commit comments

Comments
 (0)