diff --git a/lib/include/srsran/interfaces/gnb_interfaces.h b/lib/include/srsran/interfaces/gnb_interfaces.h index a78c1b50b..9c6387f7d 100644 --- a/lib/include/srsran/interfaces/gnb_interfaces.h +++ b/lib/include/srsran/interfaces/gnb_interfaces.h @@ -206,9 +206,11 @@ public: class mac_interface_phy_nr { public: - const static int MAX_SSB = 4; - const static int MAX_GRANTS = 64; - const static int MAX_NZP_CSI_RS = 4; + const static int MAX_SSB = 4; + const static int MAX_GRANTS = 64; + const static int MAX_PUCCH_MSG = 64; + const static int MAX_PUCCH_CANDIDATES = 2; + const static int MAX_NZP_CSI_RS = 4; struct pdcch_dl_t { srsran_dci_cfg_nr_t dci_cfg = {}; @@ -255,8 +257,8 @@ public: }; struct pucch_t { - srsran_pucch_nr_common_cfg_t pucch_cfg; ///< UE dedicated PUCCH configuration - srsran::bounded_vector candidates; ///< PUCCH candidates to decode + srsran_pucch_nr_common_cfg_t pucch_cfg; ///< UE dedicated PUCCH configuration + srsran::bounded_vector candidates; ///< PUCCH candidates to decode }; struct ul_sched_t { @@ -265,8 +267,8 @@ public: }; struct pucch_info_t { - srsran_uci_data_nr_t uci_data; ///< RNTI is available under cfg->pucch->rnti - // ... add signal measurements here + srsran_uci_data_nr_t uci_data; ///< RNTI is available under cfg->pucch->rnti + srsran_csi_trs_measurements_t csi; ///< DMRS based signal Channel State Information (CSI) }; struct pusch_info_t { diff --git a/lib/include/srsran/phy/gnb/gnb_ul.h b/lib/include/srsran/phy/gnb/gnb_ul.h index c0fbcfa13..95c37116e 100644 --- a/lib/include/srsran/phy/gnb/gnb_ul.h +++ b/lib/include/srsran/phy/gnb/gnb_ul.h @@ -57,7 +57,8 @@ SRSRAN_API int srsran_gnb_ul_get_pucch(srsran_gnb_ul_t* q, const srsran_pucch_nr_common_cfg_t* cfg, const srsran_pucch_nr_resource_t* resource, const srsran_uci_cfg_nr_t* uci_cfg, - srsran_uci_value_nr_t* uci_value); + srsran_uci_value_nr_t* uci_value, + srsran_csi_trs_measurements_t* meas); SRSRAN_API uint32_t srsran_gnb_ul_pucch_info(srsran_gnb_ul_t* q, const srsran_pucch_nr_resource_t* resource, diff --git a/lib/src/common/phy_cfg_nr.cc b/lib/src/common/phy_cfg_nr.cc index 1ab321feb..907beaacd 100644 --- a/lib/src/common/phy_cfg_nr.cc +++ b/lib/src/common/phy_cfg_nr.cc @@ -290,7 +290,18 @@ bool phy_cfg_nr_t::get_uci_cfg(const srsran_slot_cfg_t& slot_cfg, } // Generate configuration for SR - // ... + uint32_t sr_resource_id[SRSRAN_PUCCH_MAX_NOF_SR_RESOURCES] = {}; + int n = srsran_ue_ul_nr_sr_send_slot(pucch.sr_resources, slot_cfg.idx, sr_resource_id); + if (n < SRSRAN_SUCCESS) { + ERROR("Calculating SR opportunities"); + return false; + } + + if (n > 0) { + uci_cfg.pucch.sr_resource_id = sr_resource_id[0]; + uci_cfg.o_sr = srsran_ra_ul_nr_nof_sr_bits((uint32_t)n); + uci_cfg.sr_positive_present = true; + } // Generate configuration for CSI reports // ... @@ -324,4 +335,4 @@ bool phy_cfg_nr_t::get_pusch_uci_cfg(const srsran_slot_cfg_t& slot_cfg, return true; } -} // namespace srsran \ No newline at end of file +} // namespace srsran diff --git a/lib/src/phy/gnb/gnb_ul.c b/lib/src/phy/gnb/gnb_ul.c index 22bc57ee5..aa7727dd1 100644 --- a/lib/src/phy/gnb/gnb_ul.c +++ b/lib/src/phy/gnb/gnb_ul.c @@ -210,6 +210,11 @@ static int gnb_ul_decode_pucch_format1(srsran_gnb_ul_t* q, return SRSRAN_ERROR; } + // As format 1 with positive SR is not encoded with any payload, set SR to 1 + if (uci_cfg->sr_positive_present) { + uci_value->sr = 1; + } + // Take valid decision uci_value->valid = (norm_corr > 0.5f); @@ -248,7 +253,8 @@ int srsran_gnb_ul_get_pucch(srsran_gnb_ul_t* q, const srsran_pucch_nr_common_cfg_t* cfg, const srsran_pucch_nr_resource_t* resource, const srsran_uci_cfg_nr_t* uci_cfg, - srsran_uci_value_nr_t* uci_value) + srsran_uci_value_nr_t* uci_value, + srsran_csi_trs_measurements_t* meas) { if (q == NULL || slot_cfg == NULL || cfg == NULL || resource == NULL || uci_cfg == NULL || uci_value == NULL) { return SRSRAN_ERROR_INVALID_INPUTS; @@ -257,17 +263,39 @@ int srsran_gnb_ul_get_pucch(srsran_gnb_ul_t* q, // Estimate channel switch (resource->format) { case SRSRAN_PUCCH_NR_FORMAT_1: - return gnb_ul_decode_pucch_format1(q, slot_cfg, cfg, resource, uci_cfg, uci_value); + if (gnb_ul_decode_pucch_format1(q, slot_cfg, cfg, resource, uci_cfg, uci_value) < SRSRAN_SUCCESS) { + return SRSRAN_ERROR; + } + break; case SRSRAN_PUCCH_NR_FORMAT_2: - return gnb_ul_decode_pucch_format2(q, slot_cfg, cfg, resource, uci_cfg, uci_value); + if (gnb_ul_decode_pucch_format2(q, slot_cfg, cfg, resource, uci_cfg, uci_value) < SRSRAN_SUCCESS) { + return SRSRAN_ERROR; + } + break; case SRSRAN_PUCCH_NR_FORMAT_0: case SRSRAN_PUCCH_NR_FORMAT_3: case SRSRAN_PUCCH_NR_FORMAT_4: case SRSRAN_PUCCH_NR_FORMAT_ERROR: ERROR("Invalid or not implemented PUCCH-NR format %d", (int)resource->format); + return SRSRAN_ERROR; } - return SRSRAN_ERROR; + // Copy DMRS measurements + if (meas != NULL) { + meas->rsrp = q->chest_pucch.rsrp; + meas->rsrp_dB = q->chest_pucch.rsrp_dBfs; + meas->epre = q->chest_pucch.epre; + meas->epre_dB = q->chest_pucch.epre_dBfs; + meas->n0 = q->chest_pucch.noise_estimate; + meas->n0_dB = q->chest_pucch.noise_estimate_dbm; + meas->snr_dB = q->chest_pucch.snr_db; + meas->cfo_hz = q->chest_pucch.cfo_hz; + meas->cfo_hz_max = NAN; // Unavailable + meas->delay_us = q->chest_pucch.ta_us; + meas->nof_re = 0; // Unavailable + } + + return SRSRAN_SUCCESS; } uint32_t srsran_gnb_ul_pucch_info(srsran_gnb_ul_t* q, diff --git a/lib/src/phy/phch/ra_ul_nr.c b/lib/src/phy/phch/ra_ul_nr.c index 4654d759f..23ce7226e 100644 --- a/lib/src/phy/phch/ra_ul_nr.c +++ b/lib/src/phy/phch/ra_ul_nr.c @@ -478,17 +478,21 @@ static int ra_ul_nr_pucch_resource_default(uint32_t r_pucch, srsran_pucch_nr_res } static int ra_ul_nr_pucch_resource_hl(const srsran_pucch_nr_hl_cfg_t* cfg, - uint32_t O_uci, + const srsran_uci_cfg_nr_t* uci_cfg, uint32_t pucch_resource_id, srsran_pucch_nr_resource_t* resource) { - uint32_t N2 = cfg->sets[1].max_payload_size > 0 ? cfg->sets[1].max_payload_size : SRSRAN_UCI_NR_MAX_NOF_BITS; - uint32_t N3 = cfg->sets[2].max_payload_size > 0 ? cfg->sets[2].max_payload_size : SRSRAN_UCI_NR_MAX_NOF_BITS; + uint32_t O_uci = srsran_uci_nr_total_bits(uci_cfg); + uint32_t N2 = cfg->sets[1].max_payload_size > 0 ? cfg->sets[1].max_payload_size : SRSRAN_UCI_NR_MAX_NOF_BITS; + uint32_t N3 = cfg->sets[2].max_payload_size > 0 ? cfg->sets[2].max_payload_size : SRSRAN_UCI_NR_MAX_NOF_BITS; // If the UE transmits O UCI UCI information bits, that include HARQ-ACK information bits, the UE determines a PUCCH // resource set to be... uint32_t resource_set_id = 3; - if (O_uci <= 2 && cfg->sets[0].nof_resources > 0) { + if (uci_cfg->nof_csi == 0 && uci_cfg->ack.count <= 2) { + // a first set of PUCCH resources with pucch-ResourceSetId = 0 if O_UCI ≤ 2 including 1 or 2 HARQ-ACK + // information bits and a positive or negative SR on one SR transmission occasion if transmission of HARQ-ACK + // information and SR occurs simultaneously, or resource_set_id = 0; } else if (O_uci <= N2 && cfg->sets[1].nof_resources > 0) { resource_set_id = 1; @@ -524,8 +528,6 @@ int srsran_ra_ul_nr_pucch_resource(const srsran_pucch_nr_hl_cfg_t* pucch_cfg, return SRSRAN_ERROR_INVALID_INPUTS; } - uint32_t O_uci = srsran_uci_nr_total_bits(uci_cfg); - // Use SR PUCCH resource // - At least one positive SR // - No HARQ-ACK @@ -553,7 +555,7 @@ int srsran_ra_ul_nr_pucch_resource(const srsran_pucch_nr_hl_cfg_t* pucch_cfg, // - At least one positive SR // - up to 2 HARQ-ACK // - No CSI report - if (uci_cfg->sr_positive_present > 0 && uci_cfg->ack.count <= SRSRAN_PUCCH_NR_FORMAT1_MAX_NOF_BITS && + if (uci_cfg->sr_positive_present && uci_cfg->ack.count <= SRSRAN_PUCCH_NR_FORMAT1_MAX_NOF_BITS && uci_cfg->nof_csi == 0) { uint32_t sr_resource_id = uci_cfg->pucch.sr_resource_id; if (sr_resource_id >= SRSRAN_PUCCH_MAX_NOF_SR_RESOURCES) { @@ -571,7 +573,8 @@ int srsran_ra_ul_nr_pucch_resource(const srsran_pucch_nr_hl_cfg_t* pucch_cfg, // Select PUCCH resource for HARQ-ACK srsran_pucch_nr_resource_t resource_harq = {}; - if (ra_ul_nr_pucch_resource_hl(pucch_cfg, O_uci, uci_cfg->pucch.resource_id, &resource_harq) < SRSRAN_SUCCESS) { + if (ra_ul_nr_pucch_resource_hl(pucch_cfg, uci_cfg, uci_cfg->pucch.resource_id, &resource_harq) < SRSRAN_SUCCESS) { + ERROR("Error selecting HARQ-ACK resource"); return SRSRAN_ERROR; } @@ -595,6 +598,7 @@ int srsran_ra_ul_nr_pucch_resource(const srsran_pucch_nr_hl_cfg_t* pucch_cfg, } // The impossible happened... + ERROR("The impossible happened..."); return SRSRAN_ERROR; } @@ -603,7 +607,7 @@ int srsran_ra_ul_nr_pucch_resource(const srsran_pucch_nr_hl_cfg_t* pucch_cfg, // - More than 2 HARQ-ACK // - No CSI report if (uci_cfg->o_sr > 0 && uci_cfg->ack.count > SRSRAN_PUCCH_NR_FORMAT1_MAX_NOF_BITS && uci_cfg->nof_csi == 0) { - return ra_ul_nr_pucch_resource_hl(pucch_cfg, O_uci, uci_cfg->pucch.resource_id, resource); + return ra_ul_nr_pucch_resource_hl(pucch_cfg, uci_cfg, uci_cfg->pucch.resource_id, resource); } // Use format 2, 3 or 4 CSI report resource from higher layers @@ -622,7 +626,7 @@ int srsran_ra_ul_nr_pucch_resource(const srsran_pucch_nr_hl_cfg_t* pucch_cfg, uint32_t r_pucch = (2 * uci_cfg->pucch.n_cce_0) + 2 * uci_cfg->pucch.resource_id; return ra_ul_nr_pucch_resource_default(r_pucch, resource); } - return ra_ul_nr_pucch_resource_hl(pucch_cfg, O_uci, uci_cfg->pucch.resource_id, resource); + return ra_ul_nr_pucch_resource_hl(pucch_cfg, uci_cfg, uci_cfg->pucch.resource_id, resource); } uint32_t srsran_ra_ul_nr_nof_sr_bits(uint32_t K) diff --git a/srsenb/src/phy/nr/slot_worker.cc b/srsenb/src/phy/nr/slot_worker.cc index 64bcead67..50949e1c4 100644 --- a/srsenb/src/phy/nr/slot_worker.cc +++ b/srsenb/src/phy/nr/slot_worker.cc @@ -148,22 +148,37 @@ bool slot_worker::work_ul() // For each PUCCH... for (stack_interface_phy_nr::pucch_t& pucch : ul_sched.pucch) { - stack_interface_phy_nr::pucch_info_t pucch_info = {}; - pucch_info.uci_data.cfg = pucch.candidates[0].uci_cfg; - - // Decode PUCCH - if (srsran_gnb_ul_get_pucch(&gnb_ul, - &ul_slot_cfg, - &pucch.pucch_cfg, - &pucch.candidates[0].resource, - &pucch_info.uci_data.cfg, - &pucch_info.uci_data.value) < SRSRAN_SUCCESS) { - logger.error("Error getting PUCCH"); - return false; + srsran::bounded_vector + pucch_info(pucch.candidates.size()); + + // For each candidate decode PUCCH + for (uint32_t i = 0; i < (uint32_t)pucch.candidates.size(); i++) { + pucch_info[i].uci_data.cfg = pucch.candidates[i].uci_cfg; + + // Decode PUCCH + if (srsran_gnb_ul_get_pucch(&gnb_ul, + &ul_slot_cfg, + &pucch.pucch_cfg, + &pucch.candidates[i].resource, + &pucch_info[i].uci_data.cfg, + &pucch_info[i].uci_data.value, + &pucch_info[i].csi) < SRSRAN_SUCCESS) { + logger.error("Error getting PUCCH"); + return false; + } + } + + // Find most suitable PUCCH candidate + uint32_t best_candidate = 0; + for (uint32_t i = 1; i < (uint32_t)pucch_info.size(); i++) { + // Select candidate if exceeds the previous best candidate SNR + if (pucch_info[i].csi.snr_dB > pucch_info[best_candidate].csi.snr_dB) { + best_candidate = i; + } } // Inform stack - if (stack.pucch_info(ul_slot_cfg, pucch_info) < SRSRAN_SUCCESS) { + if (stack.pucch_info(ul_slot_cfg, pucch_info[best_candidate]) < SRSRAN_SUCCESS) { logger.error("Error pushing PUCCH information to stack"); return false; } @@ -171,8 +186,11 @@ bool slot_worker::work_ul() // Log PUCCH decoding if (logger.info.enabled()) { std::array str; - srsran_gnb_ul_pucch_info( - &gnb_ul, &pucch.candidates[0].resource, &pucch_info.uci_data, str.data(), (uint32_t)str.size()); + srsran_gnb_ul_pucch_info(&gnb_ul, + &pucch.candidates[0].resource, + &pucch_info[best_candidate].uci_data, + str.data(), + (uint32_t)str.size()); logger.info("PUCCH: %s", str.data()); } diff --git a/srsenb/src/stack/mac/nr/sched_nr_worker.cc b/srsenb/src/stack/mac/nr/sched_nr_worker.cc index 7c25a02fd..af7191a47 100644 --- a/srsenb/src/stack/mac/nr/sched_nr_worker.cc +++ b/srsenb/src/stack/mac/nr/sched_nr_worker.cc @@ -322,9 +322,28 @@ bool sched_worker_manager::save_sched_result(tti_point pdcch_tti, uint32_t cc, d // Put UCI configuration in PUCCH config ul_res.pucch.emplace_back(); pucch_t& pucch = ul_res.pucch.back(); - pucch.uci_cfg = uci_cfg; - bool ret = phy_cfg->get_pucch_uci_cfg(slot_cfg, pucch.uci_cfg, pucch.pucch_cfg, pucch.resource); - srsran_assert(ret, "Error getting PUCCH UCI cfg"); + pucch.candidates.emplace_back(); + pucch.candidates.back().uci_cfg = uci_cfg; + srsran_assert(phy_cfg->get_pucch_uci_cfg( + slot_cfg, pucch.candidates.back().uci_cfg, pucch.pucch_cfg, pucch.candidates.back().resource), + "Error getting PUCCH UCI cfg"); + + // If this slot has a SR opportunity and the selected PUCCH format is 1, consider positive SR. + if (uci_cfg.sr_positive_present and uci_cfg.ack.count > 0 and + pucch.candidates.back().resource.format == SRSRAN_PUCCH_NR_FORMAT_1) { + // Set SR negative + if (uci_cfg.o_sr > 0) { + uci_cfg.sr_positive_present = false; + } + + // Append new resource + pucch.candidates.emplace_back(); + pucch.candidates.back().uci_cfg = uci_cfg; + srsran_assert( + phy_cfg->get_pucch_uci_cfg( + slot_cfg, pucch.candidates.back().uci_cfg, pucch.pucch_cfg, pucch.candidates.back().resource), + "Error getting PUCCH UCI cfg"); + } } } } @@ -336,4 +355,4 @@ bool sched_worker_manager::save_sched_result(tti_point pdcch_tti, uint32_t cc, d } } // namespace sched_nr_impl -} // namespace srsenb \ No newline at end of file +} // namespace srsenb diff --git a/srsue/src/phy/nr/sf_worker.cc b/srsue/src/phy/nr/sf_worker.cc index 8a6bf8030..0e0a31e69 100644 --- a/srsue/src/phy/nr/sf_worker.cc +++ b/srsue/src/phy/nr/sf_worker.cc @@ -110,7 +110,7 @@ void sf_worker::work_imp() // Perform UL processing for (auto& w : cc_workers) { - w->work_ul(); + w.get()->work_ul(); } // Set Tx buffers diff --git a/test/phy/CMakeLists.txt b/test/phy/CMakeLists.txt index bfce9bf25..a89a27329 100644 --- a/test/phy/CMakeLists.txt +++ b/test/phy/CMakeLists.txt @@ -27,14 +27,17 @@ if (RF_FOUND AND ENABLE_SRSUE AND ENABLE_SRSENB) add_nr_test(nr_phy_test_10MHz_dl_only nr_phy_test --duration=100 --gnb.stack.pdsch.slots=\"0,1,2,3,4,5\" - --gnb.stack.pusch.slots=\"\" + --gnb.stack.pdsch.start=0 # Start at RB 0 + --gnb.stack.pdsch.length=52 # Full 10 MHz BW + --gnb.stack.pdsch.mcs=28 # Maximum MCS + --gnb.stack.pusch.slots=none --gnb.phy.nof_threads=${NR_PHY_TEST_GNB_NOF_THREADS} --ue.phy.nof_threads=${NR_PHY_TEST_UE_NOF_THREADS} ) add_nr_test(nr_phy_test_10MHz_ul_only nr_phy_test --duration=100 # 100 slots - --gnb.stack.pdsch.slots=6 # No PDSCH + --gnb.stack.pdsch.slots=none --gnb.stack.pusch.slots=6,7,8,9 # All possible UL slots --gnb.stack.pusch.start=0 # Start at RB 0 --gnb.stack.pusch.length=52 # Full 10 MHz BW @@ -59,11 +62,32 @@ if (RF_FOUND AND ENABLE_SRSUE AND ENABLE_SRSENB) add_nr_test(nr_phy_test_10MHz_prach nr_phy_test --duration=1000 # 100 slots - --gnb.stack.pdsch.slots=6 # No PDSCH - --gnb.stack.pusch.slots=0 # No PUSCH + --gnb.stack.pdsch.slots=none # No PDSCH + --gnb.stack.pusch.slots=none # No PUSCH --gnb.phy.nof_threads=${NR_PHY_TEST_GNB_NOF_THREADS} --ue.stack.prach.period=30 # Transmit PRACH every 30 radio frames --ue.stack.prach.preamble=10 # Use preamble 10 --ue.phy.nof_threads=${NR_PHY_TEST_UE_NOF_THREADS} ) + + add_nr_test(nr_phy_test_10MHz_sr nr_phy_test + --duration=1000 # 100 slots + --gnb.stack.pdsch.slots=none # No PDSCH + --gnb.stack.pusch.slots=none # No PUSCH + --gnb.phy.nof_threads=${NR_PHY_TEST_GNB_NOF_THREADS} + --ue.stack.sr.period=1 # Transmit SR every candidate + --ue.phy.nof_threads=${NR_PHY_TEST_UE_NOF_THREADS} + ) + + add_nr_test(nr_phy_test_10MHz_dl_sr nr_phy_test + --duration=100 + --gnb.stack.pdsch.slots=\"0,1,2,3,4,5\" + --gnb.stack.pdsch.start=0 # Start at RB 0 + --gnb.stack.pdsch.length=2 # Full 10 MHz BW + --gnb.stack.pdsch.mcs=1 # Minimum MCS + --gnb.stack.pusch.slots=none + --gnb.phy.nof_threads=${NR_PHY_TEST_GNB_NOF_THREADS} + --ue.stack.sr.period=1 # Transmit SR every candidate + --ue.phy.nof_threads=${NR_PHY_TEST_UE_NOF_THREADS} + ) endif () diff --git a/test/phy/dummy_gnb_stack.h b/test/phy/dummy_gnb_stack.h index ad2a23fb2..d2ce46c4b 100644 --- a/test/phy/dummy_gnb_stack.h +++ b/test/phy/dummy_gnb_stack.h @@ -33,10 +33,27 @@ public: uint32_t count; float avg_ta; }; + struct pucch_metrics_t { + float epre_db_avg = 0.0f; + float epre_db_min = +INFINITY; + float epre_db_max = -INFINITY; + float rsrp_db_avg = 0.0f; + float rsrp_db_min = +INFINITY; + float rsrp_db_max = -INFINITY; + float snr_db_avg = 0.0f; + float snr_db_min = +INFINITY; + float snr_db_max = -INFINITY; + float ta_us_avg = 0.0f; + float ta_us_min = +INFINITY; + float ta_us_max = -INFINITY; + uint32_t count = 0; + }; struct metrics_t { - std::map prach = {}; ///< PRACH metrics indexed with premable index - srsenb::mac_ue_metrics_t mac = {}; ///< MAC metrics + std::map prach = {}; ///< PRACH metrics indexed with premable index + srsenb::mac_ue_metrics_t mac = {}; ///< MAC metrics + uint32_t sr_count = 0; ///< SR counter + pucch_metrics_t pucch = {}; }; private: @@ -44,10 +61,10 @@ private: bool use_dummy_sched = true; const uint16_t rnti = 0x1234; struct { - srsran::circular_array dci_location; - uint32_t mcs; - uint32_t freq_res = 0; - std::set slots; + srsran::circular_array dci_location = {}; + uint32_t mcs = 0; + uint32_t freq_res = 0; + std::set slots = {}; } dl, ul; srsran::circular_array dl_data_to_ul_ack; uint32_t ss_id = 0; @@ -259,6 +276,7 @@ private: { std::unique_lock lock(metrics_mutex); + // Process HARQ-ACK for (uint32_t i = 0; i < cfg.ack.count; i++) { const srsran_harq_ack_bit_t* ack_bit = &cfg.ack.bits[i]; bool is_ok = (value.ack[i] == 1) and value.valid; @@ -271,6 +289,11 @@ private: } } + // Process SR + if (value.valid and value.sr > 0) { + metrics.sr_count++; + } + return true; } @@ -314,8 +337,12 @@ public: dl.mcs = args.pdsch.mcs; ul.mcs = args.pusch.mcs; - srsran::string_parse_list(args.pdsch.slots, ',', dl.slots); - srsran::string_parse_list(args.pusch.slots, ',', ul.slots); + if (args.pdsch.slots != "none" and not args.pdsch.slots.empty()) { + srsran::string_parse_list(args.pdsch.slots, ',', dl.slots); + } + if (args.pusch.slots != "none" and not args.pusch.slots.empty()) { + srsran::string_parse_list(args.pusch.slots, ',', ul.slots); + } // Select DCI locations for (uint32_t slot = 0; slot < SRSRAN_NOF_SF_X_FRAME; slot++) { @@ -474,14 +501,35 @@ public: // If any UCI information is triggered, schedule PUCCH if (uci_cfg.ack.count > 0 || uci_cfg.nof_csi > 0 || uci_cfg.o_sr > 0) { - mac_interface_phy_nr::pucch_t pucch = {}; - pucch.candidates[0].uci_cfg = uci_cfg; - if (not phy_cfg.get_pucch_uci_cfg(slot_cfg, uci_cfg, pucch.pucch_cfg, pucch.candidates[0].resource)) { + ul_sched.pucch.emplace_back(); + + uci_cfg.pucch.rnti = rnti; + + mac_interface_phy_nr::pucch_t& pucch = ul_sched.pucch.back(); + pucch.candidates.emplace_back(); + pucch.candidates.back().uci_cfg = uci_cfg; + if (not phy_cfg.get_pucch_uci_cfg(slot_cfg, uci_cfg, pucch.pucch_cfg, pucch.candidates.back().resource)) { logger.error("Error getting UCI CFG"); return SRSRAN_ERROR; } - ul_sched.pucch.push_back(pucch); + // If this slot has a SR opportunity and the selected PUCCH format is 1, consider positive SR. + if (uci_cfg.o_sr > 0 and uci_cfg.ack.count > 0 and + pucch.candidates.back().resource.format == SRSRAN_PUCCH_NR_FORMAT_1) { + // Set SR negative + if (uci_cfg.o_sr > 0) { + uci_cfg.sr_positive_present = false; + } + + // Append new resource + pucch.candidates.emplace_back(); + pucch.candidates.back().uci_cfg = uci_cfg; + if (not phy_cfg.get_pucch_uci_cfg(slot_cfg, uci_cfg, pucch.pucch_cfg, pucch.candidates.back().resource)) { + logger.error("Error getting UCI CFG"); + return SRSRAN_ERROR; + } + } + return SRSRAN_SUCCESS; } @@ -498,7 +546,19 @@ public: } // Handle PHY metrics - // ... + metrics.pucch.epre_db_avg = SRSRAN_VEC_CMA(pucch_info.csi.epre_dB, metrics.pucch.epre_db_avg, metrics.pucch.count); + metrics.pucch.epre_db_min = SRSRAN_MIN(metrics.pucch.epre_db_min, pucch_info.csi.epre_dB); + metrics.pucch.epre_db_max = SRSRAN_MAX(metrics.pucch.epre_db_max, pucch_info.csi.epre_dB); + metrics.pucch.rsrp_db_avg = SRSRAN_VEC_CMA(pucch_info.csi.rsrp_dB, metrics.pucch.rsrp_db_avg, metrics.pucch.count); + metrics.pucch.rsrp_db_min = SRSRAN_MIN(metrics.pucch.rsrp_db_min, pucch_info.csi.rsrp_dB); + metrics.pucch.rsrp_db_max = SRSRAN_MAX(metrics.pucch.rsrp_db_max, pucch_info.csi.rsrp_dB); + metrics.pucch.snr_db_avg = SRSRAN_VEC_CMA(pucch_info.csi.snr_dB, metrics.pucch.snr_db_avg, metrics.pucch.count); + metrics.pucch.snr_db_min = SRSRAN_MIN(metrics.pucch.snr_db_min, pucch_info.csi.snr_dB); + metrics.pucch.snr_db_max = SRSRAN_MAX(metrics.pucch.snr_db_max, pucch_info.csi.snr_dB); + metrics.pucch.ta_us_avg = SRSRAN_VEC_CMA(pucch_info.csi.delay_us, metrics.pucch.ta_us_avg, metrics.pucch.count); + metrics.pucch.ta_us_min = SRSRAN_MIN(metrics.pucch.ta_us_min, pucch_info.csi.delay_us); + metrics.pucch.ta_us_max = SRSRAN_MAX(metrics.pucch.ta_us_max, pucch_info.csi.delay_us); + metrics.pucch.count++; return SRSRAN_SUCCESS; } diff --git a/test/phy/dummy_ue_stack.h b/test/phy/dummy_ue_stack.h index 51580aba8..e3d7eced9 100644 --- a/test/phy/dummy_ue_stack.h +++ b/test/phy/dummy_ue_stack.h @@ -23,7 +23,8 @@ public: }; struct metrics_t { - std::map prach = {}; ///< PRACH metrics indexed with premable index + std::map prach = {}; ///< PRACH metrics indexed with premable index + uint32_t sr_count = 0; ///< Counts number of transmitted SR }; private: @@ -95,11 +96,14 @@ public: return false; } - bool ret = (sr_count % sr_period == 0); + if (sr_count >= (sr_period - 1) and not ul_sch_tx) { + metrics.sr_count++; + sr_count = 0; + return true; + } sr_count++; - - return ret; + return false; } bool is_valid() const { return valid; } diff --git a/test/phy/nr_phy_test.cc b/test/phy/nr_phy_test.cc index 6ba631cc1..e742af6f7 100644 --- a/test/phy/nr_phy_test.cc +++ b/test/phy/nr_phy_test.cc @@ -225,14 +225,54 @@ int main(int argc, char** argv) TESTASSERT(metrics.ue_stack.prach.count(p.first) > 0); } srsran::console(" +------------+------------+------------+\n\n"); + } + + // Print PUCCH + if (metrics.gnb_stack.pucch.count > 0) { + srsran::console("PUCCH DMRS Receiver metrics:\n"); + srsran::console(" +------------+------------+------------+------------+\n"); + srsran::console(" | %10s | %10s | %10s | %10s |\n", "Measure", "Average", "Min", "Max"); + srsran::console(" +------------+------------+------------+------------+\n"); + srsran::console(" | %10s | %+10.2f | %+10.2f | %+10.2f |\n", + "EPRE (dB)", + metrics.gnb_stack.pucch.epre_db_avg, + metrics.gnb_stack.pucch.epre_db_min, + metrics.gnb_stack.pucch.epre_db_min); + srsran::console(" | %10s | %+10.2f | %+10.2f | %+10.2f |\n", + "RSRP (dB)", + metrics.gnb_stack.pucch.rsrp_db_avg, + metrics.gnb_stack.pucch.rsrp_db_min, + metrics.gnb_stack.pucch.rsrp_db_max); + srsran::console(" | %10s | %+10.2f | %+10.2f | %+10.2f |\n", + "SINR (dB)", + metrics.gnb_stack.pucch.snr_db_avg, + metrics.gnb_stack.pucch.snr_db_min, + metrics.gnb_stack.pucch.snr_db_max); + srsran::console(" | %10s | %+10.2f | %+10.2f | %+10.2f |\n", + "TA (us)", + metrics.gnb_stack.pucch.ta_us_avg, + metrics.gnb_stack.pucch.ta_us_min, + metrics.gnb_stack.pucch.ta_us_max); + srsran::console(" +------------+------------+------------+------------+\n"); } else { - // In this case no PRACH should + // In this case the gNb should not have detected any TESTASSERT(metrics.gnb_stack.prach.empty()); } + // Print SR + if (metrics.ue_stack.sr_count > 0) { + srsran::console("SR:\n"); + srsran::console(" +------------+------------+\n"); + srsran::console(" | %10s | %10s |\n", "Transmit'd", "Received"); + srsran::console(" +------------+------------+\n"); + srsran::console(" | %10d | %10d |\n", metrics.ue_stack.sr_count, metrics.gnb_stack.sr_count); + srsran::console(" +------------+------------+\n"); + } + // Assert metrics TESTASSERT(metrics.gnb_stack.mac.tx_errors == 0); TESTASSERT(metrics.gnb_stack.mac.rx_errors == 0); + TESTASSERT(metrics.ue_stack.sr_count == metrics.gnb_stack.sr_count); // If reached here, the test is successful return SRSRAN_SUCCESS;