diff --git a/lib/examples/pdsch_enodeb.c b/lib/examples/pdsch_enodeb.c index f3880eafa..dd8b97b38 100644 --- a/lib/examples/pdsch_enodeb.c +++ b/lib/examples/pdsch_enodeb.c @@ -212,6 +212,9 @@ static void parse_args(int argc, char** argv) case 'Q': use_standard_lte_rate ^= true; break; + case 'E': + cell.cp = SRSRAN_CP_EXT; + break; default: usage(argv[0]); exit(-1); @@ -316,7 +319,7 @@ static void base_init() /* create ifft object */ for (i = 0; i < cell.nof_ports; i++) { - if (srsran_ofdm_tx_init(&ifft[i], SRSRAN_CP_NORM, sf_buffer[i], output_buffer[i], cell.nof_prb)) { + if (srsran_ofdm_tx_init(&ifft[i], cell.cp, sf_buffer[i], output_buffer[i], cell.nof_prb)) { ERROR("Error creating iFFT object"); exit(-1); } @@ -734,7 +737,7 @@ int main(int argc, char** argv) generate_mcch_table(mch_table, mbsfn_sf_mask); } N_id_2 = cell.id % 3; - sf_n_re = 2 * SRSRAN_CP_NORM_NSYMB * cell.nof_prb * SRSRAN_NRE; + sf_n_re = SRSRAN_SF_LEN_RE(cell.nof_prb, cell.cp); sf_n_samples = 2 * SRSRAN_SLOT_LEN(srsran_symbol_sz(cell.nof_prb)); cell.phich_length = SRSRAN_PHICH_NORM; @@ -850,8 +853,8 @@ int main(int argc, char** argv) srsran_vec_cf_zero(sf_symbols[0], sf_n_re); if (sf_idx == 0 || sf_idx == 5) { - srsran_pss_put_slot(pss_signal, sf_symbols[0], cell.nof_prb, SRSRAN_CP_NORM); - srsran_sss_put_slot(sf_idx ? sss_signal5 : sss_signal0, sf_symbols[0], cell.nof_prb, SRSRAN_CP_NORM); + srsran_pss_put_slot(pss_signal, sf_symbols[0], cell.nof_prb, cell.cp); + srsran_sss_put_slot(sf_idx ? sss_signal5 : sss_signal0, sf_symbols[0], cell.nof_prb, cell.cp); } /* Copy zeros, SSS, PSS into the rest of antenna ports */ diff --git a/lib/include/srsran/adt/bounded_bitset.h b/lib/include/srsran/adt/bounded_bitset.h index 86e705ae8..3313a1326 100644 --- a/lib/include/srsran/adt/bounded_bitset.h +++ b/lib/include/srsran/adt/bounded_bitset.h @@ -372,6 +372,13 @@ public: return get_word_(0); } + void from_uint64(uint64_t v) + { + srsran_assert(nof_words_() == 1, "ERROR: cannot convert bitset of size=%zd to uint64_t", size()); + srsran_assert(v < (1U << size()), "ERROR: Provided uint64=%ld does not fit in bitset of size=%zd", v, size()); + buffer[0] = v; + } + template OutputIt to_hex(OutputIt&& mem_buffer) const noexcept { diff --git a/lib/include/srsran/asn1/rrc_nr_utils.h b/lib/include/srsran/asn1/rrc_nr_utils.h index 7d19d31e4..e8c0a5dfe 100644 --- a/lib/include/srsran/asn1/rrc_nr_utils.h +++ b/lib/include/srsran/asn1/rrc_nr_utils.h @@ -25,7 +25,7 @@ #include "srsran/interfaces/mac_interface_types.h" #include "srsran/interfaces/pdcp_interface_types.h" #include "srsran/interfaces/rlc_interface_types.h" -#include "srsran/interfaces/rrc_interface_types.h" +#include "srsran/interfaces/rrc_nr_interface_types.h" #include "srsran/interfaces/sched_interface.h" /************************ @@ -64,6 +64,7 @@ struct zp_csi_rs_res_s; struct nzp_csi_rs_res_s; struct pdsch_serving_cell_cfg_s; struct freq_info_dl_s; +struct serving_cell_cfg_common_s; } // namespace rrc_nr } // namespace asn1 @@ -115,6 +116,8 @@ bool make_phy_zp_csi_rs_resource(const asn1::rrc_nr::zp_csi_rs_res_s& zp_csi_rs_ bool make_phy_nzp_csi_rs_resource(const asn1::rrc_nr::nzp_csi_rs_res_s& nzp_csi_rs_res, srsran_csi_rs_nzp_resource_t* csi_rs_nzp_resource); bool make_phy_carrier_cfg(const asn1::rrc_nr::freq_info_dl_s& freq_info_dl, srsran_carrier_nr_t* carrier_nr); +bool make_phy_ssb_cfg(const asn1::rrc_nr::serving_cell_cfg_common_s& serv_cell_cfg, phy_cfg_nr_t::ssb_cfg_t* ssb); + /*************************** * MAC Config **************************/ diff --git a/lib/include/srsran/common/band_helper.h b/lib/include/srsran/common/band_helper.h index 050c6cbe0..fc593a419 100644 --- a/lib/include/srsran/common/band_helper.h +++ b/lib/include/srsran/common/band_helper.h @@ -22,6 +22,7 @@ #ifndef SRSRAN_BAND_HELPER_H #define SRSRAN_BAND_HELPER_H +#include "srsran/phy/common/phy_common_nr.h" #include #include #include @@ -53,8 +54,77 @@ public: // For bands with 2 possible raster offsets, delta_f_raster needs to be specified std::vector get_bands_nr(uint32_t nr_arfcn, delta_f_raster_t delta_f_raster = DEFAULT); + /** + * @brief Get the lowest band that includes a given Downlink frequency in Hz + * @param dl_freq_Hz Given frequency in Hz + * @return The band number if the frequency is bounded in a band, UINT16_MAX otherwise + */ + uint16_t get_band_from_dl_freq_Hz(double dl_freq_Hz) const; + + /** + * @brief Selects the SSB pattern case according to the band number and subcarrier spacing + * @remark Described by TS 38.101-1 Table 5.4.3.3-1: Applicable SS raster entries per operating band + * @param band NR Band number + * @param scs SSB Subcarrier spacing + * @return The SSB pattern case if band and subcarrier spacing match, SRSRAN_SSB_PATTERN_INVALID otherwise + */ + srsran_ssb_patern_t get_ssb_pattern(uint16_t band, srsran_subcarrier_spacing_t scs) const; + + /** + * @brief gets the NR band duplex mode + * @param band Given band + * @return A valid SRSRAN_DUPLEX_MODE if the band is valid, SRSRAN_DUPLEX_MODE_INVALID otherwise + */ + srsran_duplex_mode_t get_duplex_mode(uint16_t band) const; + private: - // Table 5.4.2.1-1 + // Elements of TS 38.101-1 Table 5.2-1: NR operating bands in FR1 + struct nr_operating_band { + uint16_t band; + uint32_t F_UL_low; // in MHz + uint32_t F_UL_high; // in MHz + uint32_t F_DL_low; // in MHz + uint32_t F_DL_high; // in MHz + srsran_duplex_mode_t duplex_mode; + }; + static const uint32_t nof_nr_operating_band_fr1 = 32; + static constexpr std::array nr_operating_bands_fr1 = {{ + // clang-format off + {1, 1920, 1080, 2110, 2170, SRSRAN_DUPLEX_MODE_FDD}, + {2, 1850, 1810, 1930, 1990, SRSRAN_DUPLEX_MODE_FDD}, + {3, 1710, 1785, 1805, 1880, SRSRAN_DUPLEX_MODE_FDD}, + {5, 824, 849, 869, 894, SRSRAN_DUPLEX_MODE_FDD}, + {7, 2500, 2570, 2620, 2690, SRSRAN_DUPLEX_MODE_FDD}, + {8, 880, 915, 925, 960, SRSRAN_DUPLEX_MODE_FDD}, + {12, 699, 716, 729, 746, SRSRAN_DUPLEX_MODE_FDD}, + {20, 832, 862, 791, 821, SRSRAN_DUPLEX_MODE_FDD}, + {25, 1850, 1915, 1930, 1995, SRSRAN_DUPLEX_MODE_FDD}, + {28, 703, 748, 758, 803, SRSRAN_DUPLEX_MODE_FDD}, + {34, 2010, 2025, 2010, 2025, SRSRAN_DUPLEX_MODE_TDD}, + {38, 2570, 2620, 2570, 2620, SRSRAN_DUPLEX_MODE_TDD}, + {39, 1880, 1920, 1880, 1920, SRSRAN_DUPLEX_MODE_TDD}, + {40, 2300, 2400, 2300, 2400, SRSRAN_DUPLEX_MODE_TDD}, + {41, 2496, 2690, 2496, 2690, SRSRAN_DUPLEX_MODE_TDD}, + {50, 1432, 1517, 1432, 1517, SRSRAN_DUPLEX_MODE_TDD}, + {51, 1427, 1432, 1427, 1432, SRSRAN_DUPLEX_MODE_TDD}, + {66, 1710, 1780, 2110, 2200, SRSRAN_DUPLEX_MODE_FDD}, + {70, 1695, 1710, 1995, 2020, SRSRAN_DUPLEX_MODE_FDD}, + {71, 663, 698, 617, 652, SRSRAN_DUPLEX_MODE_FDD}, + {74, 1427, 1470, 1475, 1518, SRSRAN_DUPLEX_MODE_FDD}, + {75, 0, 0, 1432, 1517, SRSRAN_DUPLEX_MODE_SDL}, + {76, 0, 0, 1427, 1432, SRSRAN_DUPLEX_MODE_SDL}, + {77, 3300, 4200, 3300, 4200, SRSRAN_DUPLEX_MODE_TDD}, + {78, 3300, 3800, 3300, 3800, SRSRAN_DUPLEX_MODE_TDD}, + {79, 4400, 5000, 4400, 5000, SRSRAN_DUPLEX_MODE_TDD}, + {80, 1710, 1785, 0, 0, SRSRAN_DUPLEX_MODE_SUL}, + {81, 880, 915, 0, 0, SRSRAN_DUPLEX_MODE_SUL}, + {82, 832, 862, 0, 0, SRSRAN_DUPLEX_MODE_SUL}, + {83, 703, 748, 0, 0, SRSRAN_DUPLEX_MODE_SUL}, + {84, 1920, 1980, 0, 0, SRSRAN_DUPLEX_MODE_SUL}, + {86, 1710, 1780, 0, 0, SRSRAN_DUPLEX_MODE_SUL} + // clang-format on + }}; + struct nr_raster_params { double delta_F_global_kHz; double F_REF_Offs_MHz; @@ -149,7 +219,7 @@ private: // clang-format on }}; - static const uint32_t nof_nr_bands_fr2 = 36; + static const uint32_t nof_nr_bands_fr2 = 8; static constexpr std::array nr_band_table_fr2 = {{ {257, KHZ_60, 2054166, 1, 2104165, 2054166, 1, 2104165}, {257, KHZ_120, 2054167, 2, 2104165, 2054167, 20, 2104165}, @@ -163,6 +233,48 @@ private: {261, KHZ_60, 2070833, 1, 2084999, 2070833, 1, 2084999}, {261, KHZ_120, 2070833, 2, 2084999, 2070833, 2, 2084999} }}; + +// Elements of TS 38.101-1 Table 5.4.3.3-1 : Applicable SS raster entries per operating band + struct nr_band_ss_raster { + uint16_t band; + srsran_subcarrier_spacing_t scs; + srsran_ssb_patern_t pattern; + uint32_t gscn_first; + uint32_t gscn_step; + uint32_t gscn_last; + }; + static const uint32_t nof_nr_band_ss_raster = 29; + static constexpr std::array nr_band_ss_raster_table = {{ + {1, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 5279, 1, 5419}, + {2, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 4829, 1, 4969}, + {3, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 4517, 1, 4693}, + {5, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 2177, 1, 2230}, + {5, srsran_subcarrier_spacing_30kHz, SRSRAN_SSB_PATTERN_B, 2183, 1, 2224}, + {7, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 6554, 1, 6718}, + {8, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 2318, 1, 2395}, + {12, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 1828, 1, 1858}, + {20, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 1982, 1, 2047}, + {25, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 4829, 1, 4981}, + {28, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 1901, 1, 2002}, + {34, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 5030, 1, 5056}, + {38, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 6431, 1, 6544}, + {39, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 4706, 1, 4795}, + {40, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 5756, 1, 5995}, + {41, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 6246, 3, 6717}, + {41, srsran_subcarrier_spacing_30kHz, SRSRAN_SSB_PATTERN_C, 6252, 3, 6714}, + {50, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 3584, 1, 3787}, + {51, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 3572, 1, 3574}, + {66, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 5279, 1, 5494}, + {66, srsran_subcarrier_spacing_30kHz, SRSRAN_SSB_PATTERN_B, 5285, 1, 5488}, + {70, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 4993, 1, 5044}, + {71, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 1547, 1, 1624}, + {74, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 3692, 1, 3790}, + {75, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 3584, 1, 3787}, + {76, srsran_subcarrier_spacing_15kHz, SRSRAN_SSB_PATTERN_A, 3572, 1, 3574}, + {77, srsran_subcarrier_spacing_30kHz, SRSRAN_SSB_PATTERN_C, 7711, 1, 8329}, + {78, srsran_subcarrier_spacing_30kHz, SRSRAN_SSB_PATTERN_C, 7711, 1, 8051}, + {79, srsran_subcarrier_spacing_30kHz, SRSRAN_SSB_PATTERN_C, 8480, 16, 8880}, + }}; }; } // namespace srsran diff --git a/lib/include/srsran/common/common_lte.h b/lib/include/srsran/common/common_lte.h index 722b52b8b..8bd041e5d 100644 --- a/lib/include/srsran/common/common_lte.h +++ b/lib/include/srsran/common/common_lte.h @@ -59,11 +59,12 @@ enum class lte_srb { srb0, srb1, srb2, count }; const uint32_t MAX_LTE_SRB_ID = 2; enum class lte_drb { drb1 = 1, drb2, drb3, drb4, drb5, drb6, drb7, drb8, drb9, drb10, drb11, invalid }; const uint32_t MAX_LTE_DRB_ID = 11; -const uint32_t MAX_NOF_BEARERS = 14; +const uint32_t MAX_LTE_LCID = 10; // logicalChannelIdentity 3..10 in TS 36.331 v15.3 +const uint32_t INVALID_LCID = 99; // random invalid LCID constexpr bool is_lte_rb(uint32_t lcid) { - return lcid < MAX_NOF_BEARERS; + return lcid <= MAX_LTE_LCID; } constexpr bool is_lte_srb(uint32_t lcid) diff --git a/lib/include/srsran/common/multiqueue.h b/lib/include/srsran/common/multiqueue.h index f8a3e5a50..aa91f91fa 100644 --- a/lib/include/srsran/common/multiqueue.h +++ b/lib/include/srsran/common/multiqueue.h @@ -166,7 +166,6 @@ class multiqueue_handler // To ensure that the consumer noticed that the queue was empty before a push, we store the last // try_pop() return in a member variable. // Doing this reduces the contention of multiple producers for the same condition variable - consumer_notify_needed = false; lock.unlock(); parent->signal_pushed_data(); } diff --git a/lib/include/srsran/common/tti_point.h b/lib/include/srsran/common/tti_point.h index 50400ea34..a25729bc5 100644 --- a/lib/include/srsran/common/tti_point.h +++ b/lib/include/srsran/common/tti_point.h @@ -139,21 +139,22 @@ struct formatter { namespace srsenb { -using tti_point = srsran::tti_point; +using tti_point = srsran::tti_point; +using tti_interval = srsran::tti_interval; -inline srsran::tti_point to_tx_dl(srsran::tti_point t) +inline tti_point to_tx_dl(tti_point t) { return t + TX_ENB_DELAY; } -inline srsran::tti_point to_tx_ul(srsran::tti_point t) +inline tti_point to_tx_ul(tti_point t) { return t + (TX_ENB_DELAY + FDD_HARQ_DELAY_DL_MS); } -inline srsran::tti_point to_tx_dl_ack(srsran::tti_point t) +inline tti_point to_tx_dl_ack(tti_point t) { return to_tx_ul(t); } -inline srsran::tti_point to_tx_ul_ack(srsran::tti_point t) +inline tti_point to_tx_ul_ack(tti_point t) { return to_tx_ul(t) + TX_ENB_DELAY; } diff --git a/lib/include/srsran/interfaces/enb_command_interface.h b/lib/include/srsran/interfaces/enb_command_interface.h index bbc581baa..1e9e252f7 100644 --- a/lib/include/srsran/interfaces/enb_command_interface.h +++ b/lib/include/srsran/interfaces/enb_command_interface.h @@ -34,6 +34,8 @@ public: * @param gain Relative gain */ virtual void cmd_cell_gain(uint32_t cell_id, float gain) = 0; + + virtual void toggle_padding() = 0; }; } // namespace srsenb diff --git a/lib/include/srsran/interfaces/enb_mac_interfaces.h b/lib/include/srsran/interfaces/enb_mac_interfaces.h index c2da852a1..b8d0b783d 100644 --- a/lib/include/srsran/interfaces/enb_mac_interfaces.h +++ b/lib/include/srsran/interfaces/enb_mac_interfaces.h @@ -30,6 +30,7 @@ namespace srsenb { struct mac_args_t { uint32_t nof_prb; ///< Needed to dimension MAC softbuffers for all cells sched_interface::sched_args_t sched; + int lcid_padding; int nr_tb_size = -1; uint32_t nof_prealloc_ues; ///< Number of UE resources to pre-allocate at eNB startup uint32_t max_nof_kos; diff --git a/lib/include/srsran/interfaces/enb_metrics_interface.h b/lib/include/srsran/interfaces/enb_metrics_interface.h index e4db5e00d..a4f2f7ae1 100644 --- a/lib/include/srsran/interfaces/enb_metrics_interface.h +++ b/lib/include/srsran/interfaces/enb_metrics_interface.h @@ -28,7 +28,7 @@ #include "srsenb/hdr/phy/phy_metrics.h" #include "srsenb/hdr/stack/mac/mac_metrics.h" #include "srsenb/hdr/stack/rrc/rrc_metrics.h" -#include "srsenb/hdr/stack/upper/s1ap_metrics.h" +#include "srsenb/hdr/stack/s1ap/s1ap_metrics.h" #include "srsran/common/metrics_hub.h" #include "srsran/radio/radio_metrics.h" #include "srsran/system/sys_metrics.h" diff --git a/lib/include/srsran/interfaces/phy_interface_types.h b/lib/include/srsran/interfaces/phy_interface_types.h index 57516b7e1..3c70b21f8 100644 --- a/lib/include/srsran/interfaces/phy_interface_types.h +++ b/lib/include/srsran/interfaces/phy_interface_types.h @@ -22,6 +22,7 @@ #ifndef SRSRAN_PHY_INTERFACE_TYPES_H #define SRSRAN_PHY_INTERFACE_TYPES_H +#include "srsran/common/common.h" #include "srsran/srsran.h" /// Common types defined by the PHY layer. @@ -61,11 +62,12 @@ struct phy_meas_nr_t { }; struct phy_meas_t { - float rsrp; - float rsrq; - float cfo_hz; - uint32_t earfcn; - uint32_t pci; + srsran::srsran_rat_t rat; ///< LTE or NR + float rsrp; + float rsrq; + float cfo_hz; + uint32_t earfcn; + uint32_t pci; }; struct phy_cell_t { diff --git a/lib/include/srsran/interfaces/rrc_nr_interface_types.h b/lib/include/srsran/interfaces/rrc_nr_interface_types.h index 77ad9cfc8..c39558b57 100644 --- a/lib/include/srsran/interfaces/rrc_nr_interface_types.h +++ b/lib/include/srsran/interfaces/rrc_nr_interface_types.h @@ -24,6 +24,7 @@ #include "srsran/config.h" #include "srsran/srsran.h" +#include #include namespace srsran { @@ -33,6 +34,15 @@ namespace srsran { **************************/ struct phy_cfg_nr_t { + /** + * SSB configuration + */ + struct ssb_cfg_t { + uint32_t periodicity_ms; + std::array position_in_burst; + srsran_subcarrier_spacing_t scs; + }; + srsran_tdd_config_nr_t tdd = {}; srsran_sch_hl_cfg_nr_t pdsch = {}; srsran_sch_hl_cfg_nr_t pusch = {}; @@ -42,6 +52,7 @@ struct phy_cfg_nr_t { srsran_ue_dl_nr_harq_ack_cfg_t harq_ack = {}; srsran_csi_hl_cfg_t csi = {}; srsran_carrier_nr_t carrier = {}; + ssb_cfg_t ssb; phy_cfg_nr_t() {} diff --git a/lib/include/srsran/interfaces/ue_phy_interfaces.h b/lib/include/srsran/interfaces/ue_phy_interfaces.h index b578bfa0e..efd5b13d1 100644 --- a/lib/include/srsran/interfaces/ue_phy_interfaces.h +++ b/lib/include/srsran/interfaces/ue_phy_interfaces.h @@ -93,6 +93,7 @@ struct phy_args_t { uint32_t intra_freq_meas_len_ms = 20; uint32_t intra_freq_meas_period_ms = 200; float force_ul_amplitude = 0.0f; + bool detect_cp = false; float in_sync_rsrp_dbm_th = -130.0f; float in_sync_snr_db_th = 1.0f; diff --git a/lib/include/srsran/phy/ch_estimation/csi_rs.h b/lib/include/srsran/phy/ch_estimation/csi_rs.h index 0bc114b8b..285dca4b3 100644 --- a/lib/include/srsran/phy/ch_estimation/csi_rs.h +++ b/lib/include/srsran/phy/ch_estimation/csi_rs.h @@ -81,7 +81,7 @@ SRSRAN_API int srsran_csi_rs_append_resource_to_pattern(const srsran_carrier_nr_ * @param slot_cfg Provides current slot configuration * @param resource Provides a NZP-CSI-RS resource * @param[out] grid Resource grid - * @return SRSLTE_SUCCESS if the arguments and the resource are valid. SRSLTE_ERROR code otherwise. + * @return SRSRAN_SUCCESS if the arguments and the resource are valid. SRSRAN_ERROR code otherwise. */ SRSRAN_API int srsran_csi_rs_nzp_put_resource(const srsran_carrier_nr_t* carrier, const srsran_slot_cfg_t* slot_cfg, @@ -96,7 +96,7 @@ SRSRAN_API int srsran_csi_rs_nzp_put_resource(const srsran_carrier_nr_t* * @param set Provides a NZP-CSI-RS resource set * @param[out] grid Resource grid * @return The number of NZP-CSI-RS resources that have been scheduled for this slot if the arguments and the resource - * are valid. SRSLTE_ERROR code otherwise. + * are valid. SRSRAN_ERROR code otherwise. */ SRSRAN_API int srsran_csi_rs_nzp_put_set(const srsran_carrier_nr_t* carrier, const srsran_slot_cfg_t* slot_cfg, @@ -132,7 +132,7 @@ SRSRAN_API int srsran_csi_rs_nzp_measure(const srsran_carrier_nr_t* car * @param set Provides NZP-CSI-RS resource * @param grid Resource grid * @param measure Provides measurement - * @return The number of NZP-CSI-RS resources scheduled for this TTI if the configuration is right, SRSLTE_ERROR code if + * @return The number of NZP-CSI-RS resources scheduled for this TTI if the configuration is right, SRSRAN_ERROR code if * the configuration is invalid */ SRSRAN_API int srsran_csi_rs_nzp_measure_trs(const srsran_carrier_nr_t* carrier, @@ -160,7 +160,7 @@ SRSRAN_API uint32_t srsran_csi_rs_measure_info(const srsran_csi_trs_measurements * @param set Provides NZP-CSI-RS resource * @param grid Resource grid * @param measure Provides CSI measurement - * @return The number of NZP-CSI-RS resources scheduled for this slot if the configuration is right, SRSLTE_ERROR code + * @return The number of NZP-CSI-RS resources scheduled for this slot if the configuration is right, SRSRAN_ERROR code * if the configuration is invalid */ SRSRAN_API int srsran_csi_rs_nzp_measure_channel(const srsran_carrier_nr_t* carrier, @@ -182,7 +182,7 @@ SRSRAN_API int srsran_csi_rs_nzp_measure_channel(const srsran_carrier_nr_t* * @param set Provides ZP-CSI-RS resource * @param grid Resource grid * @param measure Provides CSI measurement - * @return The number of ZP-CSI-RS resources scheduled for this slot if the configuration is right, SRSLTE_ERROR code if + * @return The number of ZP-CSI-RS resources scheduled for this slot if the configuration is right, SRSRAN_ERROR code if * the configuration is invalid */ SRSRAN_API int srsran_csi_rs_zp_measure_channel(const srsran_carrier_nr_t* carrier, diff --git a/lib/include/srsran/phy/common/phy_common_nr.h b/lib/include/srsran/phy/common/phy_common_nr.h index 8fd45d81f..031cafb15 100644 --- a/lib/include/srsran/phy/common/phy_common_nr.h +++ b/lib/include/srsran/phy/common/phy_common_nr.h @@ -190,12 +190,22 @@ extern "C" { */ #define SRSRAN_NID_2_NR(N_ID) ((N_ID) % SRSRAN_NOF_NID_2_NR) +/** + * @brief Compute Physical Cell Identifier (PCI) N_id from N_id_1 and N_id_2 + */ +#define SRSRAN_NID_NR(NID_1, NID_2) (SRSRAN_NOF_NID_2_NR * (NID_1) + (NID_2)) + /** * @brief SSB number of resource elements, described in TS 38.211 section 7.4.3.1 Time-frequency structure of an SS/PBCH * block */ #define SRSRAN_SSB_NOF_RE (SRSRAN_SSB_BW_SUBC * SRSRAN_SSB_DURATION_NSYMB) +/** + * @brief Symbol index with extended CP + */ +#define SRSRAN_EXT_CP_SYMBOL(SCS) (7U << (uint32_t)(SCS)) + typedef enum SRSRAN_API { srsran_coreset_mapping_type_non_interleaved = 0, srsran_coreset_mapping_type_interleaved, @@ -332,8 +342,26 @@ typedef enum SRSRAN_API { srsran_subcarrier_spacing_60kHz, srsran_subcarrier_spacing_120kHz, srsran_subcarrier_spacing_240kHz, + srsran_subcarrier_spacing_invalid } srsran_subcarrier_spacing_t; +typedef enum SRSRAN_API { + SRSRAN_SSB_PATTERN_A = 0, // FR1, 15 kHz SCS + SRSRAN_SSB_PATTERN_B, // FR1, 30 kHz SCS + SRSRAN_SSB_PATTERN_C, // FR1, 30 kHz SCS + SRSRAN_SSB_PATTERN_D, // FR2, 120 kHz SCS + SRSRAN_SSB_PATTERN_E, // FR2, 240 kHz SCS + SRSRAN_SSB_PATTERN_INVALID, +} srsran_ssb_patern_t; + +typedef enum SRSRAN_API { + SRSRAN_DUPLEX_MODE_FDD = 0, // Paired + SRSRAN_DUPLEX_MODE_TDD, // Unpaired + SRSRAN_DUPLEX_MODE_SDL, // Supplementary DownLink + SRSRAN_DUPLEX_MODE_SUL, // Supplementary UpLink + SRSRAN_DUPLEX_MODE_INVALID +} srsran_duplex_mode_t; + /** * @brief NR carrier parameters. It is a combination of fixed cell and bandwidth-part (BWP) */ @@ -459,6 +487,13 @@ typedef struct SRSRAN_API { */ SRSRAN_API const char* srsran_rnti_type_str(srsran_rnti_type_t rnti_type); +/** + * @brief Get the short RNTI type name for NR + * @param rnti_type RNTI type name + * @return Constant string with the short RNTI type name + */ +SRSRAN_API const char* srsran_rnti_type_str_short(srsran_rnti_type_t rnti_type); + /** * @brief Get the RNTI type name for NR * @param rnti_type RNTI type name @@ -519,6 +554,15 @@ SRSRAN_API srsran_mcs_table_t srsran_mcs_table_from_str(const char* str); */ SRSRAN_API uint32_t srsran_min_symbol_sz_rb(uint32_t nof_prb); +/** + * @brief Computes the time in seconds between the beginning of the slot and the given symbol + * @remark All symbol size reference and values are taken from TS 38.211 section 5.3 OFDM baseband signal generation + * @param l Given symbol index + * @param scs Subcarrier spacing + * @return Returns the symbol time offset in seconds + */ +SRSRAN_API float srsran_symbol_offset_s(uint32_t l, srsran_subcarrier_spacing_t scs); + /** * @brief Computes the time in seconds between two symbols in a slot * @note l0 is expected to be smaller than l1 @@ -559,6 +603,13 @@ SRSRAN_API int srsran_carrier_to_cell(const srsran_carrier_nr_t* carrier, srsran */ SRSRAN_API uint32_t srsran_csi_meas_info(const srsran_csi_trs_measurements_t* meas, char* str, uint32_t str_len); +/** + * @brief Converts a given string into a subcarrier spacing + * @param str Provides the string + * @return A valid subcarrier if the string is valid, srsran_subcarrier_spacing_invalid otherwise + */ +SRSRAN_API srsran_subcarrier_spacing_t srsran_subcarrier_spacing_from_str(const char* str); + #ifdef __cplusplus } #endif diff --git a/lib/include/srsran/phy/enb/enb_dl.h b/lib/include/srsran/phy/enb/enb_dl.h index d3eac4d0c..2401faf3c 100644 --- a/lib/include/srsran/phy/enb/enb_dl.h +++ b/lib/include/srsran/phy/enb/enb_dl.h @@ -65,7 +65,7 @@ typedef struct SRSRAN_API { srsran_dl_sf_cfg_t dl_sf; cf_t* sf_symbols[SRSRAN_MAX_PORTS]; - + cf_t* out_buffer[SRSRAN_MAX_PORTS]; srsran_ofdm_t ifft[SRSRAN_MAX_PORTS]; srsran_ofdm_t ifft_mbsfn; diff --git a/lib/include/srsran/phy/enb/enb_ul.h b/lib/include/srsran/phy/enb/enb_ul.h index 03c7e776b..1a1dbe793 100644 --- a/lib/include/srsran/phy/enb/enb_ul.h +++ b/lib/include/srsran/phy/enb/enb_ul.h @@ -53,6 +53,7 @@ typedef struct SRSRAN_API { srsran_cell_t cell; cf_t* sf_symbols; + cf_t* in_buffer; srsran_chest_ul_res_t chest_res; srsran_ofdm_t fft; diff --git a/lib/include/srsran/phy/io/filesource.h b/lib/include/srsran/phy/io/filesource.h index 0e008a385..0f6b20b69 100644 --- a/lib/include/srsran/phy/io/filesource.h +++ b/lib/include/srsran/phy/io/filesource.h @@ -44,7 +44,7 @@ typedef struct SRSRAN_API { srsran_datatype_t type; } srsran_filesource_t; -SRSRAN_API int srsran_filesource_init(srsran_filesource_t* q, char* filename, srsran_datatype_t type); +SRSRAN_API int srsran_filesource_init(srsran_filesource_t* q, const char* filename, srsran_datatype_t type); SRSRAN_API void srsran_filesource_free(srsran_filesource_t* q); diff --git a/lib/include/srsran/phy/phch/cqi.h b/lib/include/srsran/phy/phch/cqi.h index ce01c6ccc..1040fb398 100644 --- a/lib/include/srsran/phy/phch/cqi.h +++ b/lib/include/srsran/phy/phch/cqi.h @@ -59,6 +59,7 @@ typedef struct { uint32_t ri_idx; bool ri_idx_present; bool format_is_subband; + uint8_t subband_wideband_ratio; ///< K value in TS 36.331. 0 for wideband reporting, (1..4) otherwise uint32_t subband_size; srsran_cqi_report_mode_t periodic_mode; srsran_cqi_report_mode_t aperiodic_mode; diff --git a/lib/include/srsran/phy/phch/csi.h b/lib/include/srsran/phy/phch/csi.h index 7928bcd4f..6492a356f 100644 --- a/lib/include/srsran/phy/phch/csi.h +++ b/lib/include/srsran/phy/phch/csi.h @@ -31,7 +31,7 @@ * @param measurements Current CSI measurements * @param new_measure New NZP-CSI-RS channel measurement * @param nzp_csi_rs_id NZP-CSI-RS resource set identifier - * @return SRSLTE_SUCCESS if the provided information is valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the provided information is valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_csi_new_nzp_csi_rs_measurement(const srsran_csi_hl_resource_cfg_t csi_resources[SRSRAN_CSI_MAX_NOF_RESOURCES], diff --git a/lib/include/srsran/phy/phch/pbch_nr.h b/lib/include/srsran/phy/phch/pbch_nr.h index 2f3b1a395..751aa60ef 100644 --- a/lib/include/srsran/phy/phch/pbch_nr.h +++ b/lib/include/srsran/phy/phch/pbch_nr.h @@ -28,7 +28,7 @@ * @brief Descibes the NR PBCH message */ typedef struct SRSRAN_API { - // TBD + void* TBD; } srsran_pbch_msg_nr_t; #endif // SRSRAN_PBCH_NR_H diff --git a/lib/include/srsran/phy/sync/pss_nr.h b/lib/include/srsran/phy/sync/pss_nr.h index 2bf3c4c35..81e96e1eb 100644 --- a/lib/include/srsran/phy/sync/pss_nr.h +++ b/lib/include/srsran/phy/sync/pss_nr.h @@ -42,7 +42,7 @@ * @param ssb_grid SSB resource grid * @param N_id_2 Physical cell ID 2 * @param beta PSS power allocation - * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_pss_nr_put(cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id_2, float beta); @@ -51,8 +51,18 @@ SRSRAN_API int srsran_pss_nr_put(cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id * @param ssb_grid received SSB resource grid * @param N_id_2 Physical cell ID 2 * @param lse Provides LSE pointer - * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_pss_nr_extract_lse(const cf_t* ssb_grid, uint32_t N_id_2, cf_t lse[SRSRAN_PSS_NR_LEN]); +/** + * @brief Find the best PSS sequence given the SSB resource grid + * @attention Assumes the SSB is synchronized and the average delay is pre-compensated + * @param ssb_grid The SSB resource grid to search + * @param norm_corr Normalised correlation of the best found sequence + * @param found_N_id_2 The N_id_2 of the best sequence + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise + */ +SRSRAN_API int srsran_pss_nr_find(const cf_t ssb_grid[SRSRAN_SSB_NOF_RE], float* norm_corr, uint32_t* found_N_id_2); + #endif // SRSRAN_PSS_NR_H diff --git a/lib/include/srsran/phy/sync/refsignal_dl_sync.h b/lib/include/srsran/phy/sync/refsignal_dl_sync.h index 874c93651..dda32829d 100644 --- a/lib/include/srsran/phy/sync/refsignal_dl_sync.h +++ b/lib/include/srsran/phy/sync/refsignal_dl_sync.h @@ -44,13 +44,13 @@ typedef struct { uint32_t peak_index; } srsran_refsignal_dl_sync_t; -SRSRAN_API int srsran_refsignal_dl_sync_init(srsran_refsignal_dl_sync_t* q); +SRSRAN_API int srsran_refsignal_dl_sync_init(srsran_refsignal_dl_sync_t* q, srsran_cp_t cp); SRSRAN_API int srsran_refsignal_dl_sync_set_cell(srsran_refsignal_dl_sync_t* q, srsran_cell_t cell); SRSRAN_API void srsran_refsignal_dl_sync_free(srsran_refsignal_dl_sync_t* q); -SRSRAN_API void srsran_refsignal_dl_sync_run(srsran_refsignal_dl_sync_t* q, cf_t* buffer, uint32_t nsamples); +SRSRAN_API int srsran_refsignal_dl_sync_run(srsran_refsignal_dl_sync_t* q, cf_t* buffer, uint32_t nsamples); SRSRAN_API void srsran_refsignal_dl_sync_measure_sf(srsran_refsignal_dl_sync_t* q, cf_t* buffer, diff --git a/lib/include/srsran/phy/sync/ssb.h b/lib/include/srsran/phy/sync/ssb.h index 5eb6d5ecf..a2be06268 100644 --- a/lib/include/srsran/phy/sync/ssb.h +++ b/lib/include/srsran/phy/sync/ssb.h @@ -43,16 +43,21 @@ */ #define SRSRAN_SSB_DEFAULT_BETA 1.0f +/** + * @brief Maximum number of SSB positions in burst. Defined in TS 38.331 ServingCellConfigCommon, ssb-PositionsInBurst + */ +#define SRSRAN_SSB_NOF_POSITION 64 + /** * @brief Describes SSB object initialization arguments */ typedef struct SRSRAN_API { - double max_srate_hz; ///< Maximum sampling rate in Hz (common for gNb and UE), set to zero to use default - srsran_subcarrier_spacing_t min_scs; ///< Minimum subcarrier spacing - bool enable_correlate; ///< Enables PSS/SSS correlation and peak search (UE cell search) - bool enable_encode; ///< Enables PBCH Encoder (intended for gNb) - bool enable_decode; ///< Enables PBCH Decoder (intented for UE) - bool enable_measure; ///< Enables PSS/SSS CSI measurements + double max_srate_hz; ///< Maximum sampling rate in Hz, set to zero to use default + srsran_subcarrier_spacing_t min_scs; ///< Minimum subcarrier spacing + bool enable_search; ///< Enables PSS/SSS blind search + bool enable_measure; ///< Enables PSS/SSS CSI measurements and frequency domain search + bool enable_encode; ///< Enables PBCH Encoder + bool enable_decode; ///< Enables PBCH Decoder } srsran_ssb_args_t; /** @@ -60,12 +65,17 @@ typedef struct SRSRAN_API { */ typedef struct SRSRAN_API { double srate_hz; ///< Current sampling rate in Hz - double freq_offset_hz; ///< SSB base-band frequency offset + double center_freq_hz; ///< Base-band center frequency in Hz + double ssb_freq_hz; ///< SSB center frequency srsran_subcarrier_spacing_t scs; ///< SSB configured Subcarrier spacing - float beta_pss; ////< PSS power allocation - float beta_sss; ////< SSS power allocation - float beta_pbch; ////< PBCH power allocation - float beta_pbch_dmrs; ////< PBCH DMRS power allocation + srsran_ssb_patern_t pattern; ///< SSB pattern as defined in TS 38.313 section 4.1 Cell search + bool position[SRSRAN_SSB_NOF_POSITION]; ///< Indicates the time domain positions of the transmitted SS-blocks + srsran_duplex_mode_t duplex_mode; ///< Set to true if the spectrum is paired (FDD) + uint32_t periodicity_ms; ///< SSB periodicity in ms + float beta_pss; ////< PSS power allocation + float beta_sss; ////< SSS power allocation + float beta_pbch; ////< PBCH power allocation + float beta_pbch_dmrs; ////< PBCH DMRS power allocation } srsran_ssb_cfg_t; /** @@ -76,27 +86,34 @@ typedef struct SRSRAN_API { srsran_ssb_cfg_t cfg; ///< Stores last configuration /// Sampling rate dependent parameters - float scs_hz; ///< Subcarrier spacing in Hz - uint32_t max_symbol_sz; ///< Maximum symbol size given the minimum supported SCS and sampling rate - uint32_t symbol_sz; ///< Current SSB symbol size (for the given base-band sampling rate) - int32_t offset; ///< Current SSB integer offset (multiple of SCS) - uint32_t cp0_sz; ///< First symbol cyclic prefix size - uint32_t cp_sz; ///< Other symbol cyclic prefix size + float scs_hz; ///< Subcarrier spacing in Hz + uint32_t max_symbol_sz; ///< Maximum symbol size given the minimum supported SCS and sampling rate + uint32_t max_corr_sz; ///< Maximum correlation size + uint32_t symbol_sz; ///< Current SSB symbol size (for the given base-band sampling rate) + uint32_t corr_sz; ///< Correlation size + uint32_t corr_window; ///< Correlation window length + int32_t f_offset; ///< Current SSB integer frequency offset (multiple of SCS) + uint32_t t_offset; ///< Current SSB integer time offset (number of samples) + uint32_t cp_sz[SRSRAN_SSB_DURATION_NSYMB]; ///< CP length for each SSB symbol /// Internal Objects - srsran_dft_plan_t ifft; ///< IFFT object for modulating the SSB - srsran_dft_plan_t fft; ///< FFT object for demodulate the SSB. + srsran_dft_plan_t ifft; ///< IFFT object for modulating the SSB + srsran_dft_plan_t fft; ///< FFT object for demodulate the SSB. + srsran_dft_plan_t fft_corr; ///< FFT for correlation + srsran_dft_plan_t ifft_corr; ///< IFFT for correlation /// Frequency/Time domain temporal data - cf_t* tmp_freq; - cf_t* tmp_time; + cf_t* tmp_freq; ///< Temporal frequency domain buffer + cf_t* tmp_time; ///< Temporal time domain buffer + cf_t* tmp_corr; ///< Temporal correlation frequency domain buffer + cf_t* pss_seq[SRSRAN_NOF_NID_2_NR]; ///< Possible frequency domain PSS for find } srsran_ssb_t; /** * @brief Initialises configures NR SSB with the given arguments * @param q SSB object * @param args NR PSS initialization arguments - * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_ssb_init(srsran_ssb_t* q, const srsran_ssb_args_t* args); @@ -110,33 +127,56 @@ SRSRAN_API void srsran_ssb_free(srsran_ssb_t* q); * @brief Sets SSB configuration with the current SSB configuration * @param q SSB object * @param cfg Current SSB configuration - * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg); /** * @brief Decodes PBCH in the given time domain signal * @param q SSB object - * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_ssb_decode_pbch(srsran_ssb_t* q, const cf_t* in, srsran_pbch_msg_nr_t* msg); +/** + * @brief Decides if the SSB object is configured and a given subframe is configured for SSB transmission + * @param q SSB object + * @param sf_idx Subframe index within the radio frame + * @return true if the SSB object is configured and SSB is transmitted, false otherwise + */ +SRSRAN_API bool srsran_ssb_send(srsran_ssb_t* q, uint32_t sf_idx); + /** * @brief Adds SSB to a given signal in time domain * @param q SSB object * @param N_id Physical Cell Identifier * @param msg NR PBCH message to transmit - * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_ssb_add(srsran_ssb_t* q, uint32_t N_id, const srsran_pbch_msg_nr_t* msg, const cf_t* in, cf_t* out); +/** + * @brief Perform cell search and measurement + * @note This function assumes the SSB transmission is aligned with the input base-band signal + * @param q NR PSS object + * @param in Base-band signal buffer + * @param N_id Physical Cell Identifier of the most suitable cell identifier + * @param meas SSB-based CSI measurement of the most suitable cell identifier + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise + */ +SRSRAN_API int srsran_ssb_csi_search(srsran_ssb_t* q, + const cf_t* in, + uint32_t nof_samples, + uint32_t* N_id, + srsran_csi_trs_measurements_t* meas); + /** * @brief Perform Channel State Information (CSI) measurement from the SSB * @param q NR PSS object * @param N_id Physical Cell Identifier * @param in Base-band signal * @param meas SSB-based CSI measurement - * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_ssb_csi_measure(srsran_ssb_t* q, uint32_t N_id, const cf_t* in, srsran_csi_trs_measurements_t* meas); diff --git a/lib/include/srsran/phy/sync/sss_nr.h b/lib/include/srsran/phy/sync/sss_nr.h index 1f0ec0e22..4d5a92ee7 100644 --- a/lib/include/srsran/phy/sync/sss_nr.h +++ b/lib/include/srsran/phy/sync/sss_nr.h @@ -43,7 +43,7 @@ * @param N_id_1 Physical cell ID 1 * @param N_id_2 Physical cell ID 2 * @param beta SSS power allocation - * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_sss_nr_put(cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id_1, uint32_t N_id_2, float beta); @@ -53,7 +53,7 @@ SRSRAN_API int srsran_sss_nr_put(cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id * @param N_id_1 Physical cell ID 1 * @param N_id_2 Physical cell ID 2 * @param lse Provides LSE pointer - * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_sss_nr_extract_lse(const cf_t* ssb_grid, uint32_t N_id_1, uint32_t N_id_2, cf_t lse[SRSRAN_SSS_NR_LEN]); @@ -65,7 +65,7 @@ srsran_sss_nr_extract_lse(const cf_t* ssb_grid, uint32_t N_id_1, uint32_t N_id_2 * @param N_id_2 Fix N_id_2 to search, it reduces the search space 1/3 * @param norm_corr Normalised correlation of the best found sequence * @param found_N_id_1 The N_id_1 of the best sequence - * @return SRSLTE_SUCCESS if the parameters are valid, SRSLTE_ERROR code otherwise + * @return SRSRAN_SUCCESS if the parameters are valid, SRSRAN_ERROR code otherwise */ SRSRAN_API int srsran_sss_nr_find(const cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id_2, float* norm_corr, uint32_t* found_N_id_1); diff --git a/lib/include/srsran/phy/ue/ue_cell_search.h b/lib/include/srsran/phy/ue/ue_cell_search.h index 05e2a5eb3..cfad6a8f5 100644 --- a/lib/include/srsran/phy/ue/ue_cell_search.h +++ b/lib/include/srsran/phy/ue/ue_cell_search.h @@ -99,5 +99,7 @@ SRSRAN_API int srsran_ue_cellsearch_scan(srsran_ue_cellsearch_t* q, SRSRAN_API int srsran_ue_cellsearch_set_nof_valid_frames(srsran_ue_cellsearch_t* q, uint32_t nof_frames); +SRSRAN_API void srsran_set_detect_cp(srsran_ue_cellsearch_t* q, bool enable); + #endif // SRSRAN_UE_CELL_SEARCH_H diff --git a/lib/include/srsran/phy/ue/ue_sync.h b/lib/include/srsran/phy/ue/ue_sync.h index 67c36cd73..83d5c623e 100644 --- a/lib/include/srsran/phy/ue/ue_sync.h +++ b/lib/include/srsran/phy/ue/ue_sync.h @@ -247,6 +247,8 @@ SRSRAN_API uint32_t srsran_ue_sync_get_sfidx(srsran_ue_sync_t* q); SRSRAN_API float srsran_ue_sync_get_cfo(srsran_ue_sync_t* q); +SRSRAN_API void srsran_ue_sync_cp_en(srsran_ue_sync_t* q, bool enabled); + SRSRAN_API float srsran_ue_sync_get_sfo(srsran_ue_sync_t* q); SRSRAN_API int srsran_ue_sync_get_last_sample_offset(srsran_ue_sync_t* q); diff --git a/lib/include/srsran/srslog/detail/log_entry_metadata.h b/lib/include/srsran/srslog/detail/log_entry_metadata.h index ad830a7bc..a14262dd5 100644 --- a/lib/include/srsran/srslog/detail/log_entry_metadata.h +++ b/lib/include/srsran/srslog/detail/log_entry_metadata.h @@ -27,9 +27,6 @@ namespace srslog { -/// This type is used to store small strings without doing any memory allocation. -using small_str_buffer = fmt::basic_memory_buffer; - namespace detail { /// This structure gives the user a way to log generic information as a context. @@ -49,7 +46,6 @@ struct log_entry_metadata { fmt::dynamic_format_arg_store* store; std::string log_name; char log_tag; - small_str_buffer small_str; std::vector hex_dump; }; diff --git a/lib/include/srsran/srslog/detail/support/dyn_arg_store_pool.h b/lib/include/srsran/srslog/detail/support/dyn_arg_store_pool.h index 03adff10f..d9b0fb406 100644 --- a/lib/include/srsran/srslog/detail/support/dyn_arg_store_pool.h +++ b/lib/include/srsran/srslog/detail/support/dyn_arg_store_pool.h @@ -69,8 +69,8 @@ public: return; } - scoped_lock lock(m); p->clear(); + scoped_lock lock(m); free_list.push_back(p); } diff --git a/lib/include/srsran/srslog/detail/support/thread_utils.h b/lib/include/srsran/srslog/detail/support/thread_utils.h index 03d133a90..4120d1851 100644 --- a/lib/include/srsran/srslog/detail/support/thread_utils.h +++ b/lib/include/srsran/srslog/detail/support/thread_utils.h @@ -37,7 +37,14 @@ public: mutex(const mutex&) = delete; mutex& operator=(const mutex&) = delete; - mutex() { ::pthread_mutex_init(&m, nullptr); } + mutex() + { + ::pthread_mutexattr_t mutex_attr; + ::pthread_mutexattr_init(&mutex_attr); + ::pthread_mutexattr_setprotocol(&mutex_attr, PTHREAD_PRIO_INHERIT); + ::pthread_mutex_init(&m, &mutex_attr); + } + ~mutex() { ::pthread_mutex_destroy(&m); } /// Mutex lock. diff --git a/lib/include/srsran/srslog/log_channel.h b/lib/include/srsran/srslog/log_channel.h index d515b43ae..b01356ad1 100644 --- a/lib/include/srsran/srslog/log_channel.h +++ b/lib/include/srsran/srslog/log_channel.h @@ -25,6 +25,7 @@ #include "srsran/srslog/detail/log_backend.h" #include "srsran/srslog/detail/log_entry.h" #include "srsran/srslog/sink.h" +#include namespace srslog { @@ -74,11 +75,11 @@ public: log_channel& operator=(const log_channel& other) = delete; /// Controls when the channel accepts incoming log entries. - void set_enabled(bool enabled) { is_enabled = enabled; } + void set_enabled(bool enabled) { is_enabled.store(enabled, std::memory_order_relaxed); } /// Returns true if the channel is accepting incoming log entries, otherwise /// false. - bool enabled() const { return is_enabled; } + bool enabled() const { return is_enabled.load(std::memory_order_relaxed); } /// Returns the id string of the channel. const std::string& id() const { return log_id; } @@ -117,32 +118,7 @@ public: fmtstr, store, log_name, - log_tag, - small_str_buffer()}}; - backend.push(std::move(entry)); - } - - /// Builds the provided log entry and passes it to the backend. When the - /// channel is disabled the log entry will be discarded. - void operator()(small_str_buffer&& str) - { - if (!enabled()) { - return; - } - - // Send the log entry to the backend. - log_formatter& formatter = log_sink.get_formatter(); - detail::log_entry entry = {&log_sink, - [&formatter](detail::log_entry_metadata&& metadata, fmt::memory_buffer& buffer) { - formatter.format(std::move(metadata), buffer); - }, - {std::chrono::high_resolution_clock::now(), - {ctx_value, should_print_context}, - nullptr, - nullptr, - log_name, - log_tag, - std::move(str)}}; + log_tag}}; backend.push(std::move(entry)); } @@ -179,7 +155,6 @@ public: store, log_name, log_tag, - small_str_buffer(), std::vector(buffer, buffer + len)}}; backend.push(std::move(entry)); } @@ -204,8 +179,7 @@ public: nullptr, nullptr, log_name, - log_tag, - small_str_buffer()}}; + log_tag}}; backend.push(std::move(entry)); } @@ -236,21 +210,20 @@ public: fmtstr, store, log_name, - log_tag, - small_str_buffer()}}; + log_tag}}; backend.push(std::move(entry)); } private: - const std::string log_id; - sink& log_sink; - detail::log_backend& backend; - const std::string log_name; - const char log_tag; - const bool should_print_context; - detail::shared_variable ctx_value; - detail::shared_variable hex_max_size; - detail::shared_variable is_enabled; + const std::string log_id; + sink& log_sink; + detail::log_backend& backend; + const std::string log_name; + const char log_tag; + const bool should_print_context; + std::atomic ctx_value; + std::atomic hex_max_size; + std::atomic is_enabled; }; } // namespace srslog diff --git a/lib/include/srsran/srsran.h b/lib/include/srsran/srsran.h index 8453cd1af..ad68b921d 100644 --- a/lib/include/srsran/srsran.h +++ b/lib/include/srsran/srsran.h @@ -134,6 +134,7 @@ extern "C" { #include "srsran/phy/sync/pss.h" #include "srsran/phy/sync/refsignal_dl_sync.h" #include "srsran/phy/sync/sfo.h" +#include "srsran/phy/sync/ssb.h" #include "srsran/phy/sync/sss.h" #include "srsran/phy/sync/sync.h" diff --git a/lib/include/srsran/upper/rlc_am_lte.h b/lib/include/srsran/upper/rlc_am_lte.h index 02611eeae..3c77c889e 100644 --- a/lib/include/srsran/upper/rlc_am_lte.h +++ b/lib/include/srsran/upper/rlc_am_lte.h @@ -402,7 +402,7 @@ private: void debug_state(); int required_buffer_size(rlc_amd_retx_t retx); - void retransmit_pdu(); + void retransmit_pdu(uint32_t sn); // Helpers bool poll_required(); diff --git a/lib/src/asn1/rrc_nr_utils.cc b/lib/src/asn1/rrc_nr_utils.cc index fb302fd63..9d43d30b2 100644 --- a/lib/src/asn1/rrc_nr_utils.cc +++ b/lib/src/asn1/rrc_nr_utils.cc @@ -1254,11 +1254,36 @@ bool make_phy_nzp_csi_rs_resource(const asn1::rrc_nr::nzp_csi_rs_res_s& asn1_nzp return true; } +static inline srsran_subcarrier_spacing_t make_subcarrier_spacing(const subcarrier_spacing_e& asn1_scs) +{ + switch (asn1_scs) { + case subcarrier_spacing_opts::options::khz15: + return srsran_subcarrier_spacing_15kHz; + case subcarrier_spacing_opts::options::khz30: + return srsran_subcarrier_spacing_30kHz; + case subcarrier_spacing_opts::options::khz60: + return srsran_subcarrier_spacing_60kHz; + case subcarrier_spacing_opts::options::khz120: + return srsran_subcarrier_spacing_120kHz; + case subcarrier_spacing_opts::options::khz240: + return srsran_subcarrier_spacing_240kHz; + case subcarrier_spacing_opts::spare3: + case subcarrier_spacing_opts::spare2: + case subcarrier_spacing_opts::spare1: + case subcarrier_spacing_opts::nulltype: + default: + asn1::log_warning("Not supported subcarrier spacing "); + break; + } + + return srsran_subcarrier_spacing_invalid; +} + bool make_phy_carrier_cfg(const freq_info_dl_s& asn1_freq_info_dl, srsran_carrier_nr_t* out_carrier_nr) { uint32_t absolute_frequency_ssb = 0; if (asn1_freq_info_dl.absolute_freq_ssb_present) { - absolute_frequency_ssb = asn1_freq_info_dl.absolute_freq_ssb_present; + absolute_frequency_ssb = asn1_freq_info_dl.absolute_freq_ssb; } else { asn1::log_warning("Option absolute_freq_ssb not present"); return false; @@ -1268,26 +1293,12 @@ bool make_phy_carrier_cfg(const freq_info_dl_s& asn1_freq_info_dl, srsran_carrie return false; } - srsran_subcarrier_spacing_t scs = srsran_subcarrier_spacing_15kHz; - switch (asn1_freq_info_dl.scs_specific_carrier_list[0].subcarrier_spacing) { - case subcarrier_spacing_opts::options::khz15: - scs = srsran_subcarrier_spacing_15kHz; - break; - case subcarrier_spacing_opts::options::khz30: - scs = srsran_subcarrier_spacing_30kHz; - break; - case subcarrier_spacing_opts::options::khz60: - scs = srsran_subcarrier_spacing_60kHz; - break; - case subcarrier_spacing_opts::options::khz120: - scs = srsran_subcarrier_spacing_120kHz; - break; - case subcarrier_spacing_opts::options::khz240: - scs = srsran_subcarrier_spacing_240kHz; - break; - default: - asn1::log_warning("Not supported subcarrier spacing "); + srsran_subcarrier_spacing_t scs = + make_subcarrier_spacing(asn1_freq_info_dl.scs_specific_carrier_list[0].subcarrier_spacing); + if (scs == srsran_subcarrier_spacing_invalid) { + return false; } + // As the carrier structure requires parameters from different objects, set fields separately out_carrier_nr->absolute_frequency_ssb = absolute_frequency_ssb; out_carrier_nr->absolute_frequency_point_a = asn1_freq_info_dl.absolute_freq_point_a; @@ -1296,6 +1307,63 @@ bool make_phy_carrier_cfg(const freq_info_dl_s& asn1_freq_info_dl, srsran_carrie out_carrier_nr->scs = scs; return true; } + +template +static inline void make_ssb_positions_in_burst(const bitstring_t& ans1_position_in_burst, + std::array& position_in_burst) +{ + for (uint32_t i = 0; i < SRSRAN_SSB_NOF_POSITION; i++) { + if (i < ans1_position_in_burst.length()) { + position_in_burst[i] = ans1_position_in_burst.get(ans1_position_in_burst.length() - 1 - i); + } else { + position_in_burst[i] = false; + } + } +} + +bool make_phy_ssb_cfg(const asn1::rrc_nr::serving_cell_cfg_common_s& serv_cell_cfg, phy_cfg_nr_t::ssb_cfg_t* out_ssb) +{ + phy_cfg_nr_t::ssb_cfg_t ssb = {}; + if (serv_cell_cfg.ssb_positions_in_burst_present) { + switch (serv_cell_cfg.ssb_positions_in_burst.type()) { + case serving_cell_cfg_common_s::ssb_positions_in_burst_c_::types_opts::short_bitmap: + make_ssb_positions_in_burst(serv_cell_cfg.ssb_positions_in_burst.short_bitmap(), ssb.position_in_burst); + break; + case serving_cell_cfg_common_s::ssb_positions_in_burst_c_::types_opts::medium_bitmap: + make_ssb_positions_in_burst(serv_cell_cfg.ssb_positions_in_burst.medium_bitmap(), ssb.position_in_burst); + break; + case serving_cell_cfg_common_s::ssb_positions_in_burst_c_::types_opts::long_bitmap: + make_ssb_positions_in_burst(serv_cell_cfg.ssb_positions_in_burst.long_bitmap(), ssb.position_in_burst); + break; + case serving_cell_cfg_common_s::ssb_positions_in_burst_c_::types_opts::nulltype: + asn1::log_warning("SSB position in burst nulltype"); + return false; + } + } else { + asn1::log_warning("SSB position in burst not present"); + return false; + } + if (serv_cell_cfg.ssb_periodicity_serving_cell_present) { + ssb.periodicity_ms = (uint32_t)serv_cell_cfg.ssb_periodicity_serving_cell.to_number(); + } else { + asn1::log_warning("SSB periodicity not present"); + return false; + } + if (serv_cell_cfg.ssb_subcarrier_spacing_present) { + ssb.scs = make_subcarrier_spacing(serv_cell_cfg.ssb_subcarrier_spacing); + if (ssb.scs == srsran_subcarrier_spacing_invalid) { + return false; + } + } else { + asn1::log_warning("SSB subcarrier spacing not present"); + return false; + } + if (out_ssb != nullptr) { + *out_ssb = ssb; + } + return true; +} + } // namespace srsran namespace srsenb { diff --git a/lib/src/common/band_helper.cc b/lib/src/common/band_helper.cc index f4a7d8791..2fab3b507 100644 --- a/lib/src/common/band_helper.cc +++ b/lib/src/common/band_helper.cc @@ -28,6 +28,10 @@ namespace srsran { constexpr std::array srsran_band_helper::nr_band_table_fr1; constexpr std::array srsran_band_helper::nr_fr_params; +constexpr std::array + srsran_band_helper::nr_operating_bands_fr1; +constexpr std::array + srsran_band_helper::nr_band_ss_raster_table; // Formula in 5.4.2.1 double srsran_band_helper::nr_arfcn_to_freq(uint32_t nr_arfcn) @@ -60,6 +64,55 @@ std::vector srsran_band_helper::get_bands_nr(uint32_t return bands; } +uint16_t srsran_band_helper::get_band_from_dl_freq_Hz(double freq) const +{ + uint32_t freq_MHz = (uint32_t)round(freq / 1e6); + for (const nr_operating_band& band : nr_operating_bands_fr1) { + if (freq_MHz >= band.F_DL_low and freq_MHz <= band.F_DL_high) { + return band.band; + } + } + return UINT16_MAX; +} + +srsran_ssb_patern_t srsran_band_helper::get_ssb_pattern(uint16_t band, srsran_subcarrier_spacing_t scs) const +{ + // Look for the given band and SCS + for (const nr_band_ss_raster& ss_raster : nr_band_ss_raster_table) { + // Check if band and SCS match! + if (ss_raster.band == band && ss_raster.scs == scs) { + return ss_raster.pattern; + } + + // As bands are in ascending order, do not waste more time if the current band is bigger + if (ss_raster.band > band) { + return SRSRAN_SSB_PATTERN_INVALID; + } + } + + // Band is out of range, so consider invalid + return SRSRAN_SSB_PATTERN_INVALID; +} + +srsran_duplex_mode_t srsran_band_helper::get_duplex_mode(uint16_t band) const +{ + // Look for the given band + for (const nr_operating_band& b : nr_operating_bands_fr1) { + // Check if band and SCS match! + if (b.band == band) { + return b.duplex_mode; + } + + // As bands are in ascending order, do not waste more time if the current band is bigger + if (b.band > band) { + return SRSRAN_DUPLEX_MODE_INVALID; + } + } + + // Band is out of range, so consider invalid + return SRSRAN_DUPLEX_MODE_INVALID; +} + srsran_band_helper::nr_raster_params srsran_band_helper::get_raster_params(uint32_t nr_arfcn) { for (auto& fr : nr_fr_params) { diff --git a/lib/src/phy/ch_estimation/csi_rs.c b/lib/src/phy/ch_estimation/csi_rs.c index d0b4b8857..6e3abd6bf 100644 --- a/lib/src/phy/ch_estimation/csi_rs.c +++ b/lib/src/phy/ch_estimation/csi_rs.c @@ -975,7 +975,8 @@ uint32_t srsran_csi_rs_measure_info(const srsran_csi_trs_measurements_t* measure measure->rsrp_dB, measure->epre_dB, measure->n0_dB, - measure->snr_dB); + measure->snr_dB, + measure->delay_us); // Append measured CFO and the maximum CFO that can be measured if (isnormal(measure->cfo_hz_max)) { diff --git a/lib/src/phy/channel/channel.cc b/lib/src/phy/channel/channel.cc index 85ae05723..4ff6ffe25 100644 --- a/lib/src/phy/channel/channel.cc +++ b/lib/src/phy/channel/channel.cc @@ -221,7 +221,7 @@ void channel::run(cf_t* in[SRSRAN_MAX_CHANNELS], // Logging std::stringstream str; - str << "t=" << t.full_secs + t.frac_secs << "s; "; + str << "Channel: t=" << t.full_secs + t.frac_secs << "s; "; if (delay[0]) { str << "delay=" << delay[0]->delay_us << "us; "; } diff --git a/lib/src/phy/common/phy_common_nr.c b/lib/src/phy/common/phy_common_nr.c index 3c85e3708..4e0ac59b9 100644 --- a/lib/src/phy/common/phy_common_nr.c +++ b/lib/src/phy/common/phy_common_nr.c @@ -21,6 +21,7 @@ #include "srsran/phy/common/phy_common_nr.h" #include "srsran/phy/utils/vector.h" +#include #include const char* srsran_rnti_type_str(srsran_rnti_type_t rnti_type) @@ -46,6 +47,29 @@ const char* srsran_rnti_type_str(srsran_rnti_type_t rnti_type) } return "unknown"; } +const char* srsran_rnti_type_str_short(srsran_rnti_type_t rnti_type) +{ + switch (rnti_type) { + case srsran_rnti_type_c: + return "c"; + case srsran_rnti_type_p: + return "p"; + case srsran_rnti_type_si: + return "si"; + case srsran_rnti_type_ra: + return "ra"; + case srsran_rnti_type_tc: + return "tc"; + case srsran_rnti_type_cs: + return "cs"; + case srsran_rnti_type_sp_csi: + return "sp-csi"; + case srsran_rnti_type_mcs_c: + return "mcs-c"; + default:; // Do nothing + } + return "unknown"; +} const char* srsran_dci_format_nr_string(srsran_dci_format_nr_t format) { @@ -161,34 +185,45 @@ uint32_t srsran_min_symbol_sz_rb(uint32_t nof_prb) return 0; } -float srsran_symbol_distance_s(uint32_t l0, uint32_t l1, srsran_subcarrier_spacing_t scs) +float srsran_symbol_offset_s(uint32_t l, srsran_subcarrier_spacing_t scs) { - // l0 must be smaller than l1 - if (l0 >= l1) { - return 0.0f; - } + // Compute at what symbol there is a longer CP + uint32_t cp_boundary = SRSRAN_EXT_CP_SYMBOL(scs); - // Count number of symbols in between - uint32_t count = l1 - l0; + // First symbol CP + uint32_t N = 0; - // Compute at what symbol there is a longer CP - uint32_t cp_boundary = 7U << (uint32_t)scs; + // Symbols in between the first and l + N += (2048 + 144) * l; - // Select whether extra CP shall be added - uint32_t extra_cp = 0; - if (l0 < cp_boundary && l1 >= cp_boundary) { - extra_cp = 16; + // Add extended CP samples from first OFDM symbol + if (l > 0) { + N += 16; } - // Compute reference FFT size - uint32_t N = (2048 + 144) * count + extra_cp; + // Add extra samples at the longer CP boundary + if (l >= cp_boundary) { + N += 16; + } + // Compute time using reference sampling rate float TS = SRSRAN_LTE_TS / (float)(1U << (uint32_t)scs); - // Return symbol distance in microseconds + // Return symbol offset in seconds return (float)N * TS; } +float srsran_symbol_distance_s(uint32_t l0, uint32_t l1, srsran_subcarrier_spacing_t scs) +{ + // l0 must be smaller than l1 + if (l0 >= l1) { + return 0.0f; + } + + // Return symbol distance in seconds + return srsran_symbol_offset_s(l1, scs) - srsran_symbol_offset_s(l0, scs); +} + bool srsran_tdd_nr_is_dl(const srsran_tdd_config_nr_t* cfg, uint32_t numerology, uint32_t slot_idx) { // Protect NULL pointer access @@ -296,3 +331,32 @@ uint32_t srsran_csi_meas_info(const srsran_csi_trs_measurements_t* meas, char* s meas->cfo_hz, meas->delay_us); } + +srsran_subcarrier_spacing_t srsran_subcarrier_spacing_from_str(const char* str) +{ + if (str == NULL) { + return srsran_subcarrier_spacing_invalid; + } + + uint32_t scs = (uint32_t)roundf(strtof(str, NULL)); + switch (scs) { + case 15: + case 15000: + return srsran_subcarrier_spacing_15kHz; + case 30: + case 30000: + return srsran_subcarrier_spacing_30kHz; + case 60: + case 60000: + return srsran_subcarrier_spacing_60kHz; + case 120: + case 120000: + return srsran_subcarrier_spacing_120kHz; + case 240: + case 240000: + return srsran_subcarrier_spacing_240kHz; + default:; // Do nothing + } + + return srsran_subcarrier_spacing_invalid; +} diff --git a/lib/src/phy/enb/enb_dl.c b/lib/src/phy/enb/enb_dl.c index b1f6f4ecb..ed9036636 100644 --- a/lib/src/phy/enb/enb_dl.c +++ b/lib/src/phy/enb/enb_dl.c @@ -50,21 +50,14 @@ int srsran_enb_dl_init(srsran_enb_dl_t* q, cf_t* out_buffer[SRSRAN_MAX_PORTS], u goto clean_exit; } } + for (int i = 0; i < SRSRAN_MAX_PORTS; i++) { + q->out_buffer[i] = out_buffer[i]; + } srsran_ofdm_cfg_t ofdm_cfg = {}; ofdm_cfg.nof_prb = max_prb; - ofdm_cfg.cp = SRSRAN_CP_NORM; + ofdm_cfg.cp = SRSRAN_CP_EXT; ofdm_cfg.normalize = false; - for (int i = 0; i < SRSRAN_MAX_PORTS; i++) { - ofdm_cfg.in_buffer = q->sf_symbols[i]; - ofdm_cfg.out_buffer = out_buffer[i]; - ofdm_cfg.sf_type = SRSRAN_SF_NORM; - if (srsran_ofdm_tx_init_cfg(&q->ifft[i], &ofdm_cfg)) { - ERROR("Error initiating FFT (%d)", i); - goto clean_exit; - } - } - ofdm_cfg.in_buffer = q->sf_symbols[0]; ofdm_cfg.out_buffer = out_buffer[0]; ofdm_cfg.sf_type = SRSRAN_SF_MBSFN; @@ -159,6 +152,19 @@ int srsran_enb_dl_set_cell(srsran_enb_dl_t* q, srsran_cell_t cell) srsran_regs_free(&q->regs); } q->cell = cell; + srsran_ofdm_cfg_t ofdm_cfg = {}; + ofdm_cfg.nof_prb = q->cell.nof_prb; + ofdm_cfg.cp = cell.cp; + ofdm_cfg.normalize = false; + for (int i = 0; i < SRSRAN_MAX_PORTS; i++) { + ofdm_cfg.in_buffer = q->sf_symbols[i]; + ofdm_cfg.out_buffer = q->out_buffer[i]; + ofdm_cfg.sf_type = SRSRAN_SF_NORM; + if (srsran_ofdm_tx_init_cfg(&q->ifft[i], &ofdm_cfg)) { + ERROR("Error initiating FFT (%d)", i); + return SRSRAN_ERROR; + } + } if (srsran_regs_init(&q->regs, q->cell)) { ERROR("Error resizing REGs"); return SRSRAN_ERROR; diff --git a/lib/src/phy/enb/enb_ul.c b/lib/src/phy/enb/enb_ul.c index fdb3c7e6b..054253c10 100644 --- a/lib/src/phy/enb/enb_ul.c +++ b/lib/src/phy/enb/enb_ul.c @@ -46,19 +46,7 @@ int srsran_enb_ul_init(srsran_enb_ul_t* q, cf_t* in_buffer, uint32_t max_prb) perror("malloc"); goto clean_exit; } - - srsran_ofdm_cfg_t ofdm_cfg = {}; - ofdm_cfg.nof_prb = max_prb; - ofdm_cfg.in_buffer = in_buffer; - ofdm_cfg.out_buffer = q->sf_symbols; - ofdm_cfg.cp = SRSRAN_CP_NORM; - ofdm_cfg.freq_shift_f = -0.5f; - ofdm_cfg.normalize = false; - ofdm_cfg.rx_window_offset = 0.5f; - if (srsran_ofdm_rx_init_cfg(&q->fft, &ofdm_cfg)) { - ERROR("Error initiating FFT"); - goto clean_exit; - } + q->in_buffer = in_buffer; if (srsran_pucch_init_enb(&q->pucch)) { ERROR("Error creating PUCCH object"); @@ -117,6 +105,18 @@ int srsran_enb_ul_set_cell(srsran_enb_ul_t* q, if (cell.id != q->cell.id || q->cell.nof_prb == 0) { q->cell = cell; + srsran_ofdm_cfg_t ofdm_cfg = {}; + ofdm_cfg.nof_prb = q->cell.nof_prb; + ofdm_cfg.in_buffer = q->in_buffer; + ofdm_cfg.out_buffer = q->sf_symbols; + ofdm_cfg.cp = q->cell.cp; + ofdm_cfg.freq_shift_f = -0.5f; + ofdm_cfg.normalize = false; + ofdm_cfg.rx_window_offset = 0.5f; + if (srsran_ofdm_rx_init_cfg(&q->fft, &ofdm_cfg)) { + ERROR("Error initiating FFT"); + return SRSRAN_ERROR; + } if (srsran_ofdm_rx_set_prb(&q->fft, q->cell.cp, q->cell.nof_prb)) { ERROR("Error initiating FFT"); return SRSRAN_ERROR; diff --git a/lib/src/phy/io/filesource.c b/lib/src/phy/io/filesource.c index 579afa13e..0db76b97d 100644 --- a/lib/src/phy/io/filesource.c +++ b/lib/src/phy/io/filesource.c @@ -26,7 +26,7 @@ #include "srsran/phy/io/filesource.h" #include "srsran/phy/utils/debug.h" -int srsran_filesource_init(srsran_filesource_t* q, char* filename, srsran_datatype_t type) +int srsran_filesource_init(srsran_filesource_t* q, const char* filename, srsran_datatype_t type) { bzero(q, sizeof(srsran_filesource_t)); q->f = fopen(filename, "r"); diff --git a/lib/src/phy/phch/dci_nr.c b/lib/src/phy/phch/dci_nr.c index bcc89adbd..2d5479431 100644 --- a/lib/src/phy/phch/dci_nr.c +++ b/lib/src/phy/phch/dci_nr.c @@ -1997,7 +1997,13 @@ int srsran_dci_ctx_to_str(const srsran_dci_ctx_t* ctx, char* str, uint32_t str_l uint32_t len = 0; // Print base - len = srsran_print_check(str, str_len, len, "rnti=%04x dci=%s ", ctx->rnti, srsran_dci_format_nr_string(ctx->format)); + len = srsran_print_check(str, + str_len, + len, + "%s-rnti=%04x dci=%s ", + srsran_rnti_type_str_short(ctx->rnti_type), + ctx->rnti, + srsran_dci_format_nr_string(ctx->format)); if (ctx->format != srsran_dci_format_nr_rar) { len = srsran_print_check(str, str_len, len, "L=%d cce=%d ", ctx->location.L, ctx->location.ncce); diff --git a/lib/src/phy/phch/pdsch_nr.c b/lib/src/phy/phch/pdsch_nr.c index 4fcfe7b0e..7fcce240e 100644 --- a/lib/src/phy/phch/pdsch_nr.c +++ b/lib/src/phy/phch/pdsch_nr.c @@ -579,8 +579,7 @@ static uint32_t pdsch_nr_grant_info(const srsran_pdsch_nr_t* q, } // Append time-domain resource mapping - len = srsran_print_check( - str, str_len, len, "rnti=0x%x prb=%d:%d symb=%d:%d ", grant->rnti, first_prb, grant->nof_prb, grant->S, grant->L); + len = srsran_print_check(str, str_len, len, "prb=%d:%d symb=%d:%d ", first_prb, grant->nof_prb, grant->S, grant->L); // Append TB info for (uint32_t i = 0; i < SRSRAN_MAX_TB; i++) { diff --git a/lib/src/phy/phch/pusch_nr.c b/lib/src/phy/phch/pusch_nr.c index 3669959a4..14d23bc4b 100644 --- a/lib/src/phy/phch/pusch_nr.c +++ b/lib/src/phy/phch/pusch_nr.c @@ -1020,8 +1020,7 @@ static uint32_t pusch_nr_grant_info(const srsran_pusch_nr_t* q, } // Append time-domain resource mapping - len = srsran_print_check( - str, str_len, len, "rnti=0x%x prb=%d:%d symb=%d:%d ", grant->rnti, first_prb, grant->nof_prb, grant->S, grant->L); + len = srsran_print_check(str, str_len, len, "prb=%d:%d symb=%d:%d ", first_prb, grant->nof_prb, grant->S, grant->L); // Append TB info for (uint32_t i = 0; i < SRSRAN_MAX_TB; i++) { diff --git a/lib/src/phy/phch/ra_nr.c b/lib/src/phy/phch/ra_nr.c index 3686cb72b..82fb9dbe8 100644 --- a/lib/src/phy/phch/ra_nr.c +++ b/lib/src/phy/phch/ra_nr.c @@ -198,16 +198,12 @@ static ra_nr_table_t ra_nr_select_table_pdsch(srsran_mcs_table_t mcs_tab srsran_search_space_type_t search_space_type, srsran_rnti_type_t rnti_type) { - // Non-implemented parameters - bool sps_config_mcs_table_present = false; - bool is_pdcch_sps = false; - // - the higher layer parameter mcs-Table given by PDSCH-Config is set to 'qam256', and // - the PDSCH is scheduled by a PDCCH with DCI format 1_1 with // - CRC scrambled by C-RNTI if (mcs_table == srsran_mcs_table_256qam && dci_format == srsran_dci_format_nr_1_1 && rnti_type == srsran_rnti_type_c) { - return ra_nr_table_1; + return ra_nr_table_2; } // the UE is not configured with MCS-C-RNTI, @@ -223,10 +219,10 @@ static ra_nr_table_t ra_nr_select_table_pdsch(srsran_mcs_table_t mcs_tab // - the higher layer parameter mcs-Table given by PDSCH-Config is set to 'qam256', // - if the PDSCH is scheduled by a PDCCH with DCI format 1_1 with CRC scrambled by CS-RNTI or // - if the PDSCH is scheduled without corresponding PDCCH transmission using SPS-Config, - if (!sps_config_mcs_table_present && mcs_table == srsran_mcs_table_256qam && - ((dci_format == srsran_dci_format_nr_1_1 && rnti_type == srsran_rnti_type_c) || (!is_pdcch_sps))) { - return ra_nr_table_2; - } + // if (!sps_config_mcs_table_present && mcs_table == srsran_mcs_table_256qam && + // ((dci_format == srsran_dci_format_nr_1_1 && rnti_type == srsran_rnti_type_cs) || (!is_pdcch_sps))) { + // return ra_nr_table_2; + // } // - the UE is configured with the higher layer parameter mcs-Table given by SPS-Config set to 'qam64LowSE' // - if the PDSCH is scheduled by a PDCCH with CRC scrambled by CS-RNTI or diff --git a/lib/src/phy/phch/sch_nr.c b/lib/src/phy/phch/sch_nr.c index 34045cb7c..364b0e07f 100644 --- a/lib/src/phy/phch/sch_nr.c +++ b/lib/src/phy/phch/sch_nr.c @@ -74,6 +74,43 @@ uint32_t sch_nr_n_prb_lbrm(uint32_t nof_prb) return 273; } +static int sch_nr_cbsegm(srsran_basegraph_t bg, uint32_t tbs, srsran_cbsegm_t* cbsegm) +{ + if (bg == BG1) { + if (srsran_cbsegm_ldpc_bg1(cbsegm, tbs) != SRSRAN_SUCCESS) { + ERROR("Error: calculating LDPC BG1 code block segmentation for tbs=%d", tbs); + return SRSRAN_ERROR; + } + } else { + if (srsran_cbsegm_ldpc_bg2(cbsegm, tbs) != SRSRAN_SUCCESS) { + ERROR("Error: calculating LDPC BG1 code block segmentation for tbs=%d", tbs); + return SRSRAN_ERROR; + } + } + + return SRSRAN_SUCCESS; +} + +static int sch_nr_Nref(uint32_t N_rb, srsran_mcs_table_t mcs_table, uint32_t max_mimo_layers) +{ + uint32_t N_re_lbrm = SRSRAN_MAX_NRE_NR * sch_nr_n_prb_lbrm(N_rb); + double TCR_lbrm = 948.0 / 1024.0; + uint32_t Qm_lbrm = (mcs_table == srsran_mcs_table_256qam) ? 8 : 6; + uint32_t TBS_LRBM = srsran_ra_nr_tbs(N_re_lbrm, 1.0, TCR_lbrm, Qm_lbrm, max_mimo_layers); + double R = 2.0 / 3.0; + srsran_basegraph_t bg = srsran_sch_nr_select_basegraph(TBS_LRBM, R); + + // Compute segmentation + srsran_cbsegm_t cbsegm = {}; + int r = sch_nr_cbsegm(bg, TBS_LRBM, &cbsegm); + if (r < SRSRAN_SUCCESS) { + ERROR("Error computing TB segmentation"); + return SRSRAN_ERROR; + } + + return (int)ceil((double)TBS_LRBM / (double)(cbsegm.C * R)); +} + int srsran_sch_nr_fill_tb_info(const srsran_carrier_nr_t* carrier, const srsran_sch_cfg_t* sch_cfg, const srsran_sch_tb_t* tb, @@ -88,16 +125,9 @@ int srsran_sch_nr_fill_tb_info(const srsran_carrier_nr_t* carrier, // Compute code block segmentation srsran_cbsegm_t cbsegm = {}; - if (bg == BG1) { - if (srsran_cbsegm_ldpc_bg1(&cbsegm, tb->tbs) != SRSRAN_SUCCESS) { - ERROR("Error: calculating LDPC BG1 code block segmentation for tbs=%d", tb->tbs); - return SRSRAN_ERROR; - } - } else { - if (srsran_cbsegm_ldpc_bg2(&cbsegm, tb->tbs) != SRSRAN_SUCCESS) { - ERROR("Error: calculating LDPC BG1 code block segmentation for tbs=%d", tb->tbs); - return SRSRAN_ERROR; - } + if (sch_nr_cbsegm(bg, tb->tbs, &cbsegm) < SRSRAN_SUCCESS) { + ERROR("Error calculation TB segmentation"); + return SRSRAN_ERROR; } if (cbsegm.Z > MAX_LIFTSIZE) { @@ -120,13 +150,11 @@ int srsran_sch_nr_fill_tb_info(const srsran_carrier_nr_t* carrier, cfg->Nl = tb->N_L; // Calculate Nref - uint32_t N_re_lbrm = SRSRAN_MAX_NRE_NR * sch_nr_n_prb_lbrm(carrier->nof_prb); - double TCR_lbrm = 948.0 / 1024.0; - uint32_t Qm_lbrm = (sch_cfg->mcs_table == srsran_mcs_table_256qam) ? 8 : 6; - uint32_t max_mimo_layers = SRSRAN_MAX(carrier->max_mimo_layers, 4); - uint32_t TBS_LRBM = srsran_ra_nr_tbs(N_re_lbrm, 1.0, TCR_lbrm, Qm_lbrm, max_mimo_layers); - double R = 2.0 / 3.0; - cfg->Nref = (uint32_t)ceil((double)TBS_LRBM / (double)(cbsegm.C * R)); + int Nref = sch_nr_Nref(carrier->nof_prb, sch_cfg->mcs_table, carrier->max_mimo_layers); + if (Nref < SRSRAN_SUCCESS) { + ERROR("Error computing N_ref"); + } + cfg->Nref = (uint32_t)Nref; // Calculate number of code blocks after applying CBGTI... not implemented, activate all CB for (uint32_t r = 0; r < cbsegm.C; r++) { @@ -665,7 +693,6 @@ static int sch_nr_decode(srsran_sch_nr_t* q, res->avg_iter = NAN; } - // Not all CB are decoded, skip TB union and CRC check if (cb_ok != cfg.C) { return SRSRAN_SUCCESS; diff --git a/lib/src/phy/rf/rf_uhd_generic.h b/lib/src/phy/rf/rf_uhd_generic.h index 8e3bd87d8..8eac4ee48 100644 --- a/lib/src/phy/rf/rf_uhd_generic.h +++ b/lib/src/phy/rf/rf_uhd_generic.h @@ -201,7 +201,7 @@ public: // Set receiver subdev spec if specified if (not rx_subdev.empty()) { - err = set_rx_subdev(tx_subdev); + err = set_rx_subdev(rx_subdev); if (err != UHD_ERROR_NONE) { return err; } diff --git a/lib/src/phy/sync/pss_nr.c b/lib/src/phy/sync/pss_nr.c index 444fdc1d8..0dddd95f1 100644 --- a/lib/src/phy/sync/pss_nr.c +++ b/lib/src/phy/sync/pss_nr.c @@ -103,3 +103,52 @@ int srsran_pss_nr_extract_lse(const cf_t* ssb_grid, uint32_t N_id_2, cf_t lse[SR return SRSRAN_SUCCESS; } + +int srsran_pss_nr_find(const cf_t ssb_grid[SRSRAN_SSB_NOF_RE], float* norm_corr, uint32_t* found_N_id_2) +{ + // Verify inputs + if (ssb_grid == NULL || norm_corr == NULL || found_N_id_2 == NULL) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + const cf_t* pss_ptr = &ssb_grid[SRSRAN_PSS_NR_SYMBOL_IDX * SRSRAN_SSB_BW_SUBC + PSS_NR_SUBC_BEGIN]; + + // Measure PSS region average power + float avg_power = srsran_vec_avg_power_cf(pss_ptr, SRSRAN_PSS_NR_LEN); + + // If no energy detected or invalid, consider zero correlation + if (!isnormal(avg_power)) { + *norm_corr = 0.0f; + *found_N_id_2 = 0; + return SRSRAN_SUCCESS; + } + + // Search state + float max_corr = -INFINITY; //< Stores best correlation + uint32_t N_id_2 = 0; //< Best N_id_2 + + // Iterate over all possible N_id_2 + for (uint32_t N_id_2_candidate = 0; N_id_2_candidate < SRSRAN_NOF_NID_2_NR; N_id_2_candidate++) { + uint32_t m = PSS_NR_SEQUENCE_M(N_id_2_candidate); + cf_t acc = 0.0f; + + // Correlate d sequence fist part + acc += srsran_vec_dot_prod_ccc(&pss_nr_d[m], &pss_ptr[0], SRSRAN_PSS_NR_LEN - m); + + // Correlate d sequence second part + acc += srsran_vec_dot_prod_ccc(&pss_nr_d[0], &pss_ptr[SRSRAN_PSS_NR_LEN - m], m); + + // Correlate + float corr = SRSRAN_CSQABS(acc); + if (corr > max_corr) { + N_id_2 = N_id_2_candidate; + max_corr = corr; + } + } + + // Copy found result + *norm_corr = max_corr / avg_power / SRSRAN_PSS_NR_LEN; + *found_N_id_2 = N_id_2; + + return SRSRAN_SUCCESS; +} diff --git a/lib/src/phy/sync/refsignal_dl_sync.c b/lib/src/phy/sync/refsignal_dl_sync.c index 308817530..6b134d538 100644 --- a/lib/src/phy/sync/refsignal_dl_sync.c +++ b/lib/src/phy/sync/refsignal_dl_sync.c @@ -77,8 +77,14 @@ static inline void refsignal_sf_prepare_correlation(srsran_refsignal_dl_sync_t* { uint32_t sf_len = q->ifft.sf_sz; cf_t* ptr_filt = q->conv_fft_cc.filter_fft; - memcpy(ptr_filt, q->sequences[0], sizeof(cf_t) * sf_len); - srsran_vec_cf_zero(&ptr_filt[sf_len], sf_len); + + // Put first subframe in buffer + srsran_vec_cf_copy(ptr_filt, q->sequences[0], sf_len); + + // Zero the rest of the buffer + srsran_vec_cf_zero(&ptr_filt[sf_len], q->conv_fft_cc.output_len - sf_len); + + // Make correlation sequence in frequency domain srsran_dft_run_c(&q->conv_fft_cc.filter_plan, ptr_filt, ptr_filt); } @@ -142,7 +148,7 @@ static inline void refsignal_dl_pss_sss_strength(srsran_refsignal_dl_sync_t* q, } } -int srsran_refsignal_dl_sync_init(srsran_refsignal_dl_sync_t* q) +int srsran_refsignal_dl_sync_init(srsran_refsignal_dl_sync_t* q, srsran_cp_t cp) { int ret = SRSRAN_ERROR_INVALID_INPUTS; @@ -180,12 +186,12 @@ int srsran_refsignal_dl_sync_init(srsran_refsignal_dl_sync_t* q) // Initiate OFDM modulator if (!ret) { - ret = srsran_ofdm_tx_init(&q->ifft, SRSRAN_CP_NORM, q->ifft_buffer_in, q->ifft_buffer_out, SRSRAN_MAX_PRB); + ret = srsran_ofdm_tx_init(&q->ifft, cp, q->ifft_buffer_in, q->ifft_buffer_out, SRSRAN_MAX_PRB); } // Set PRB if (!ret) { - ret = srsran_ofdm_tx_set_prb(&q->ifft, SRSRAN_CP_NORM, SRSRAN_MAX_PRB); + ret = srsran_ofdm_tx_set_prb(&q->ifft, cp, SRSRAN_MAX_PRB); } // Initiate FFT Convolution @@ -258,7 +264,12 @@ int srsran_refsignal_dl_sync_set_cell(srsran_refsignal_dl_sync_t* q, srsran_cell srsran_ofdm_tx_sf(&q->ifft); // Undo scaling and normalize overall power to 1 - float scale = 1.0f / nof_re; + float scale = 1.0f; + + // Avoid zero division + if (nof_re != 0) { + scale /= (float)nof_re; + } // Copy time domain signal, normalized by number of RE srsran_vec_sc_prod_cfc(q->ifft_buffer_out, scale, q->sequences[i], q->ifft.sf_sz); @@ -298,22 +309,23 @@ void srsran_refsignal_dl_sync_free(srsran_refsignal_dl_sync_t* q) } } -int srsran_refsignal_dl_sync_find_peak(srsran_refsignal_dl_sync_t* q, cf_t* buffer, uint32_t nsamples) +int refsignal_dl_sync_find_peak(srsran_refsignal_dl_sync_t* q, cf_t* buffer, uint32_t nsamples) { - int ret = SRSRAN_ERROR; - float peak_value = 0.0f; - int peak_idx = 0; - float rms_avg = 0; - uint32_t sf_len = q->ifft.sf_sz; + int ret = SRSRAN_ERROR; + float peak_value = 0.0f; + int peak_idx = 0; + float rms_avg = 0; + + uint32_t sf_len = q->ifft.sf_sz; + if (sf_len == 0) { + return SRSRAN_ERROR; + } // Load correlation sequence and convert to frequency domain refsignal_sf_prepare_correlation(q); - // Limit correlate for a frame or less - nsamples = SRSRAN_MIN(nsamples - sf_len, SRSRAN_NOF_SF_X_FRAME * sf_len); - // Correlation - for (int n = 0; n < nsamples; n += sf_len) { + for (uint32_t n = 0; n + q->conv_fft_cc.filter_len < nsamples; n += q->conv_fft_cc.input_len) { // Correlate, find maximum, calculate RMS and peak uint32_t imax = 0; float peak = 0.0f; @@ -336,7 +348,7 @@ int srsran_refsignal_dl_sync_find_peak(srsran_refsignal_dl_sync_t* q, cf_t* buff } // Double check sub-frame selection failure due to high PSS - if (ret > 0) { + if (ret >= 0) { float sss_strength = 0.0f; float sss_strength_false = 0.0f; refsignal_dl_pss_sss_strength(q, &buffer[peak_idx], 0, NULL, &sss_strength, &sss_strength_false); @@ -364,151 +376,154 @@ int srsran_refsignal_dl_sync_find_peak(srsran_refsignal_dl_sync_t* q, cf_t* buff return ret; } -void srsran_refsignal_dl_sync_run(srsran_refsignal_dl_sync_t* q, cf_t* buffer, uint32_t nsamples) +int srsran_refsignal_dl_sync_run(srsran_refsignal_dl_sync_t* q, cf_t* buffer, uint32_t nsamples) { - if (q) { - uint32_t sf_len = q->ifft.sf_sz; - uint32_t sf_count = 0; - float rsrp_lin = 0.0f; - float rsrp_lin_min = +INFINITY; - float rsrp_lin_max = -INFINITY; - float rssi_lin = 0.0f; - float cfo_acc = 0.0f; - float cfo_min = +INFINITY; - float cfo_max = -INFINITY; - float sss_strength_avg = 0.0f; - float sss_strength_false_avg = 0.0f; - float rsrp_false_avg = 0.0f; - bool false_alarm = false; - - // Stage 1: find peak - int peak_idx = srsran_refsignal_dl_sync_find_peak(q, buffer, nsamples); - - // Stage 2: Proccess subframes - if (peak_idx >= 0) { - // Calculate initial subframe index and sample - uint32_t sf_idx_init = (2 * SRSRAN_NOF_SF_X_FRAME - peak_idx / sf_len) % SRSRAN_NOF_SF_X_FRAME; - uint32_t n_init = peak_idx % sf_len; - - for (int sf_idx = sf_idx_init, n = n_init; n < (nsamples - sf_len + 1); - sf_idx = (sf_idx + 1) % SRSRAN_NOF_SF_X_FRAME, n += sf_len) { - cf_t* buf = &buffer[n]; - - // Measure subframe rsrp, rssi and accumulate - float rsrp = 0.0f, rssi = 0.0f, cfo = 0.0f; - srsran_refsignal_dl_sync_measure_sf(q, buf, sf_idx, &rsrp, &rssi, &cfo); - - // Update measurements - rsrp_lin += rsrp; - rsrp_lin_min = SRSRAN_MIN(rsrp_lin_min, rsrp); - rsrp_lin_max = SRSRAN_MAX(rsrp_lin_max, rsrp); - - rssi_lin += rssi; - - cfo_acc += cfo; - cfo_min = SRSRAN_MIN(cfo_min, cfo); - cfo_max = SRSRAN_MAX(cfo_max, cfo); - - // Compute PSS/SSS strength - if (sf_idx % (SRSRAN_NOF_SF_X_FRAME / 2) == 0) { - float sss_strength = 0.0f; - float sss_strength_false = 0.0f; - refsignal_dl_pss_sss_strength(q, buf, sf_idx, NULL, &sss_strength, &sss_strength_false); - - float rsrp_false = 0.0f; - srsran_refsignal_dl_sync_measure_sf(q, buf, sf_idx + 1, &rsrp_false, NULL, NULL); - - sss_strength_avg += sss_strength; - sss_strength_false_avg += sss_strength_false; - rsrp_false_avg += rsrp_false; - } - - // Increment counter - sf_count++; + if (q == NULL || buffer == NULL) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + uint32_t sf_len = q->ifft.sf_sz; + uint32_t sf_count = 0; + float rsrp_lin = 0.0f; + float rsrp_lin_min = +INFINITY; + float rsrp_lin_max = -INFINITY; + float rssi_lin = 0.0f; + float cfo_acc = 0.0f; + float cfo_min = +INFINITY; + float cfo_max = -INFINITY; + float sss_strength_avg = 0.0f; + float sss_strength_false_avg = 0.0f; + float rsrp_false_avg = 0.0f; + bool false_alarm = false; + + // Stage 1: find peak + int peak_idx = refsignal_dl_sync_find_peak(q, buffer, nsamples); + + // Stage 2: Proccess subframes + if (peak_idx >= 0) { + // Calculate initial subframe index and sample + uint32_t sf_idx_init = SRSRAN_NOF_SF_X_FRAME - (peak_idx / sf_len) % SRSRAN_NOF_SF_X_FRAME; + uint32_t n_init = peak_idx % sf_len; + + for (uint32_t sf_idx = sf_idx_init, n = n_init; n < (nsamples - sf_len + 1); + sf_idx = (sf_idx + 1) % SRSRAN_NOF_SF_X_FRAME, n += sf_len) { + cf_t* buf = &buffer[n]; + + // Measure subframe rsrp, rssi and accumulate + float rsrp = 0.0f, rssi = 0.0f, cfo = 0.0f; + srsran_refsignal_dl_sync_measure_sf(q, buf, sf_idx, &rsrp, &rssi, &cfo); + + // Update measurements + rsrp_lin += rsrp; + rsrp_lin_min = SRSRAN_MIN(rsrp_lin_min, rsrp); + rsrp_lin_max = SRSRAN_MAX(rsrp_lin_max, rsrp); + + rssi_lin += rssi; + + cfo_acc += cfo; + cfo_min = SRSRAN_MIN(cfo_min, cfo); + cfo_max = SRSRAN_MAX(cfo_max, cfo); + + // Compute PSS/SSS strength + if (sf_idx % (SRSRAN_NOF_SF_X_FRAME / 2) == 0) { + float sss_strength = 0.0f; + float sss_strength_false = 0.0f; + refsignal_dl_pss_sss_strength(q, buf, sf_idx, NULL, &sss_strength, &sss_strength_false); + + float rsrp_false = 0.0f; + srsran_refsignal_dl_sync_measure_sf(q, buf, sf_idx + 1, &rsrp_false, NULL, NULL); + + sss_strength_avg += sss_strength; + sss_strength_false_avg += sss_strength_false; + rsrp_false_avg += rsrp_false; } - // Average measurements - if (sf_count) { - rsrp_lin /= sf_count; - rssi_lin /= sf_count; - cfo_acc /= sf_count; - sss_strength_avg /= (2.0f * sf_count / SRSRAN_NOF_SF_X_FRAME); - sss_strength_false_avg /= (2.0f * sf_count / SRSRAN_NOF_SF_X_FRAME); - rsrp_false_avg /= (2.0f * sf_count / SRSRAN_NOF_SF_X_FRAME); - } + // Increment counter + sf_count++; + } - // RSRP conversion to dB - float rsrp_dB_min = srsran_convert_power_to_dBm(rsrp_lin_min); - float rsrp_dB_max = srsran_convert_power_to_dBm(rsrp_lin_max); - float rsrp_dB = srsran_convert_power_to_dBm(rsrp_lin); - float rsrp_false_dB = srsran_convert_power_to_dBm(rsrp_false_avg); - - // Stage 3: Final false alarm decision - uint32_t false_count = 0; - if (sss_strength_avg < sss_strength_false_avg * REFSIGNAL_DL_SSS_FALSE_RATIO_SEVERE) { - false_alarm = true; - } else if (sss_strength_avg < sss_strength_false_avg * REFSIGNAL_DL_SSS_FALSE_RATIO_MILD) { - false_count++; - } + // Average measurements + if (sf_count) { + rsrp_lin /= sf_count; + rssi_lin /= sf_count; + cfo_acc /= sf_count; + sss_strength_avg /= (2.0f * sf_count / SRSRAN_NOF_SF_X_FRAME); + sss_strength_false_avg /= (2.0f * sf_count / SRSRAN_NOF_SF_X_FRAME); + rsrp_false_avg /= (2.0f * sf_count / SRSRAN_NOF_SF_X_FRAME); + } - if (cfo_max - cfo_min > REFSIGNAL_DL_CFO_MIN_MAX_SEVERE) { - false_alarm = true; - } else if (cfo_max - cfo_min > REFSIGNAL_DL_CFO_MIN_MAX_MILD) { - false_count++; - } + // RSRP conversion to dB + float rsrp_dB_min = srsran_convert_power_to_dBm(rsrp_lin_min); + float rsrp_dB_max = srsran_convert_power_to_dBm(rsrp_lin_max); + float rsrp_dB = srsran_convert_power_to_dBm(rsrp_lin); + float rsrp_false_dB = srsran_convert_power_to_dBm(rsrp_false_avg); + + // Stage 3: Final false alarm decision + uint32_t false_count = 0; + if (sss_strength_avg < sss_strength_false_avg * REFSIGNAL_DL_SSS_FALSE_RATIO_SEVERE) { + false_alarm = true; + } else if (sss_strength_avg < sss_strength_false_avg * REFSIGNAL_DL_SSS_FALSE_RATIO_MILD) { + false_count++; + } - if (rsrp_dB_max - rsrp_dB_min > REFSIGNAL_DL_RSRP_MIN_MAX_SEVERE) { - false_alarm = true; - } else if (rsrp_dB_max - rsrp_dB_min > REFSIGNAL_DL_RSRP_MIN_MAX_MILD) { - false_count++; - } + if (cfo_max - cfo_min > REFSIGNAL_DL_CFO_MIN_MAX_SEVERE) { + false_alarm = true; + } else if (cfo_max - cfo_min > REFSIGNAL_DL_CFO_MIN_MAX_MILD) { + false_count++; + } - if (rsrp_dB - rsrp_false_dB < REFSIGNAL_DL_RSRP_FALSE_RATIO_SEVERE) { - false_alarm = true; - } else if (rsrp_dB - rsrp_false_dB < REFSIGNAL_DL_RSRP_FALSE_RATIO_MILD) { - false_count++; - } + if (rsrp_dB_max - rsrp_dB_min > REFSIGNAL_DL_RSRP_MIN_MAX_SEVERE) { + false_alarm = true; + } else if (rsrp_dB_max - rsrp_dB_min > REFSIGNAL_DL_RSRP_MIN_MAX_MILD) { + false_count++; + } - // Allow only one check fail - if (false_count > REFSIGNAL_DL_MAX_FAULT_CHECK) { - false_alarm = true; - } + if (rsrp_dB - rsrp_false_dB < REFSIGNAL_DL_RSRP_FALSE_RATIO_SEVERE) { + false_alarm = true; + } else if (rsrp_dB - rsrp_false_dB < REFSIGNAL_DL_RSRP_FALSE_RATIO_MILD) { + false_count++; + } - INFO("-- pci=%03d; rsrp_dB=(%+.1f|%+.1f|%+.1f); rsrp_max-min=%.1f; rsrp_false_ratio=%.1f; " - "cfo=(%.1f|%.1f|%.1f); cfo_max-min=%.1f; sss_ratio=%f; false_count=%d;", - q->refsignal.cell.id, - rsrp_dB_min, - rsrp_dB, - rsrp_dB_max, - rsrp_dB_max - rsrp_dB_min, - rsrp_dB - rsrp_false_dB, - cfo_min, - cfo_acc, - cfo_max, - cfo_max - cfo_min, - sss_strength_avg / sss_strength_false_avg, - false_count); - - if (!false_alarm) { - // Calculate in dBm - q->rsrp_dBfs = rsrp_dB; - - // Calculate RSSI in dBm - q->rssi_dBfs = srsran_convert_power_to_dBm(rssi_lin); - - // Calculate RSRQ - q->rsrq_dB = srsran_convert_power_to_dB(q->refsignal.cell.nof_prb) + q->rsrp_dBfs - q->rssi_dBfs; - - q->found = true; - q->cfo_Hz = cfo_acc; - q->peak_index = peak_idx; - } else { - refsignal_set_results_not_found(q); - } + // Allow only one check fail + if (false_count > REFSIGNAL_DL_MAX_FAULT_CHECK) { + false_alarm = true; + } + + INFO("-- pci=%03d; rsrp_dB=(%+.1f|%+.1f|%+.1f); rsrp_max-min=%.1f; rsrp_false_ratio=%.1f; " + "cfo=(%.1f|%.1f|%.1f); cfo_max-min=%.1f; sss_ratio=%f; false_count=%d;", + q->refsignal.cell.id, + rsrp_dB_min, + rsrp_dB, + rsrp_dB_max, + rsrp_dB_max - rsrp_dB_min, + rsrp_dB - rsrp_false_dB, + cfo_min, + cfo_acc, + cfo_max, + cfo_max - cfo_min, + sss_strength_avg / sss_strength_false_avg, + false_count); + + if (!false_alarm) { + // Calculate in dBm + q->rsrp_dBfs = rsrp_dB; + + // Calculate RSSI in dBm + q->rssi_dBfs = srsran_convert_power_to_dBm(rssi_lin); + + // Calculate RSRQ + q->rsrq_dB = srsran_convert_power_to_dB(q->refsignal.cell.nof_prb) + q->rsrp_dBfs - q->rssi_dBfs; + + q->found = true; + q->cfo_Hz = cfo_acc; + q->peak_index = peak_idx; } else { refsignal_set_results_not_found(q); } + } else { + refsignal_set_results_not_found(q); } + + return SRSRAN_SUCCESS; } void srsran_refsignal_dl_sync_measure_sf(srsran_refsignal_dl_sync_t* q, diff --git a/lib/src/phy/sync/ssb.c b/lib/src/phy/sync/ssb.c index 2c2e0b94b..ad7ef10f2 100644 --- a/lib/src/phy/sync/ssb.c +++ b/lib/src/phy/sync/ssb.c @@ -36,6 +36,32 @@ */ #define SSB_FREQ_OFFSET_MAX_ERROR_HZ 0.01 +/* + * Correlation size in function of the symbol size. It selects a power of two number at least 8 times bigger than the + * given symbol size but not bigger than 2^13 points. + */ +#define SSB_CORR_SZ(SYMB_SZ) SRSRAN_MIN(1U << (uint32_t)ceil(log2((double)(SYMB_SZ)) + 3.0), 1U << 13U) + +static int ssb_init_corr(srsran_ssb_t* q) +{ + // Initialise correlation only if it is enabled + if (!q->args.enable_search) { + return SRSRAN_SUCCESS; + } + + // For each PSS sequence allocate + for (uint32_t N_id_2 = 0; N_id_2 < SRSRAN_NOF_NID_2_NR; N_id_2++) { + // Allocate sequences + q->pss_seq[N_id_2] = srsran_vec_cf_malloc(q->max_corr_sz); + if (q->pss_seq[N_id_2] == NULL) { + ERROR("Malloc"); + return SRSRAN_ERROR; + } + } + + return SRSRAN_SUCCESS; +} + int srsran_ssb_init(srsran_ssb_t* q, const srsran_ssb_args_t* args) { // Verify input parameters @@ -53,15 +79,22 @@ int srsran_ssb_init(srsran_ssb_t* q, const srsran_ssb_args_t* args) q->scs_hz = (float)SRSRAN_SUBC_SPACING_NR(q->args.min_scs); q->max_symbol_sz = (uint32_t)round(q->args.max_srate_hz / q->scs_hz); + q->max_corr_sz = SSB_CORR_SZ(q->max_symbol_sz); // Allocate temporal data - q->tmp_time = srsran_vec_cf_malloc(q->max_symbol_sz); - q->tmp_freq = srsran_vec_cf_malloc(q->max_symbol_sz); - if (q->tmp_time == NULL || q->tmp_time == NULL) { + q->tmp_time = srsran_vec_cf_malloc(q->max_corr_sz); + q->tmp_freq = srsran_vec_cf_malloc(q->max_corr_sz); + q->tmp_corr = srsran_vec_cf_malloc(q->max_corr_sz); + if (q->tmp_time == NULL || q->tmp_freq == NULL || q->tmp_corr == NULL) { ERROR("Malloc"); return SRSRAN_ERROR; } + // Allocate correlation buffers + if (ssb_init_corr(q) < SRSRAN_SUCCESS) { + return SRSRAN_ERROR; + } + return SRSRAN_SUCCESS; } @@ -79,12 +112,272 @@ void srsran_ssb_free(srsran_ssb_t* q) free(q->tmp_freq); } + if (q->tmp_corr != NULL) { + free(q->tmp_corr); + } + + // For each PSS sequence allocate + for (uint32_t N_id_2 = 0; N_id_2 < SRSRAN_NOF_NID_2_NR; N_id_2++) { + if (q->pss_seq[N_id_2] != NULL) { + free(q->pss_seq[N_id_2]); + } + } + srsran_dft_plan_free(&q->ifft); srsran_dft_plan_free(&q->fft); + srsran_dft_plan_free(&q->fft_corr); + srsran_dft_plan_free(&q->ifft_corr); SRSRAN_MEM_ZERO(q, srsran_ssb_t, 1); } +static uint32_t ssb_first_symbol_caseA(const srsran_ssb_cfg_t* cfg, uint32_t indexes[SRSRAN_SSB_NOF_POSITION]) +{ + // Case A - 15 kHz SCS: the first symbols of the candidate SS/PBCH blocks have indexes of { 2 , 8 } + 14 â‹… n . For + // carrier frequencies smaller than or equal to 3 GHz, n = 0 , 1 . For carrier frequencies within FR1 larger than 3 + // GHz, n = 0 , 1 , 2 , 3 . + uint32_t count = 0; + uint32_t base_indexes[2] = {2, 8}; + + uint32_t N = 2; + if (cfg->center_freq_hz > 3e9) { + N = 4; + } + + for (uint32_t n = 0; n < N; n++) { + for (uint32_t i = 0; i < 2; i++) { + indexes[count++] = base_indexes[i] + 14 * n; + } + } + + return count; +} + +static uint32_t ssb_first_symbol_caseB(const srsran_ssb_cfg_t* cfg, uint32_t indexes[SRSRAN_SSB_NOF_POSITION]) +{ + // Case B - 30 kHz SCS: the first symbols of the candidate SS/PBCH blocks have indexes { 4 , 8 , 16 , 20 } + 28 â‹… n . + // For carrier frequencies smaller than or equal to 3 GHz, n = 0 . For carrier frequencies within FR1 larger than 3 + // GHz, n = 0 , 1 . + uint32_t count = 0; + uint32_t base_indexes[4] = {4, 8, 16, 20}; + + uint32_t N = 1; + if (cfg->center_freq_hz > 3e9) { + N = 2; + } + + for (uint32_t n = 0; n < N; n++) { + for (uint32_t i = 0; i < 4; i++) { + indexes[count++] = base_indexes[i] + 28 * n; + } + } + + return count; +} + +static uint32_t ssb_first_symbol_caseC(const srsran_ssb_cfg_t* cfg, uint32_t indexes[SRSRAN_SSB_NOF_POSITION]) +{ + // Case C - 30 kHz SCS: the first symbols of the candidate SS/PBCH blocks have indexes { 2 , 8 } +14 â‹… n . + // - For paired spectrum operation + // - For carrier frequencies smaller than or equal to 3 GHz, n = 0 , 1 . For carrier frequencies within FR1 larger + // than 3 GHz, n = 0 , 1 , 2 , 3 . + // - For unpaired spectrum operation + // - For carrier frequencies smaller than or equal to 2.3 GHz, n = 0 , 1 . For carrier frequencies within FR1 + // larger than 2.3 GHz, n = 0 , 1 , 2 , 3 . + uint32_t count = 0; + uint32_t base_indexes[2] = {2, 8}; + + uint32_t N = 4; + if ((cfg->duplex_mode == SRSRAN_DUPLEX_MODE_FDD && cfg->center_freq_hz <= 3e9) || + (cfg->duplex_mode == SRSRAN_DUPLEX_MODE_TDD && cfg->center_freq_hz <= 2.3e9)) { + N = 2; + } + + for (uint32_t n = 0; n < N; n++) { + for (uint32_t i = 0; i < 2; i++) { + indexes[count++] = base_indexes[i] + 14 * n; + } + } + + return count; +} + +static uint32_t ssb_first_symbol_caseD(const srsran_ssb_cfg_t* cfg, uint32_t indexes[SRSRAN_SSB_NOF_POSITION]) +{ + // Case D - 120 kHz SCS: the first symbols of the candidate SS/PBCH blocks have indexes { 4 , 8 , 16 , 20 } + 28 â‹… n . + // For carrier frequencies within FR2, n = 0 , 1 , 2 , 3 , 5 , 6 , 7 , 8 , 10 , 11 , 12 , 13 , 15 , 16 , 17 , 18 . + uint32_t count = 0; + uint32_t base_indexes[4] = {4, 8, 16, 20}; + uint32_t n_indexes[16] = {0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 15, 16, 17, 18}; + + for (uint32_t j = 0; j < 16; j++) { + for (uint32_t i = 0; i < 4; i++) { + indexes[count++] = base_indexes[i] + 28 * n_indexes[j]; + } + } + + return count; +} + +static uint32_t ssb_first_symbol_caseE(const srsran_ssb_cfg_t* cfg, uint32_t indexes[SRSRAN_SSB_NOF_POSITION]) +{ + // Case E - 240 kHz SCS: the first symbols of the candidate SS/PBCH blocks have indexes + //{ 8 , 12 , 16 , 20 , 32 , 36 , 40 , 44 } + 56 â‹… n . For carrier frequencies within FR2, n = 0 , 1 , 2 , 3 , 5 , 6 , + // 7 , 8 . + uint32_t count = 0; + uint32_t base_indexes[8] = {8, 12, 16, 20, 32, 38, 40, 44}; + uint32_t n_indexes[8] = {0, 1, 2, 3, 5, 6, 7, 8}; + + for (uint32_t j = 0; j < 8; j++) { + for (uint32_t i = 0; i < 8; i++) { + indexes[count++] = base_indexes[i] + 56 * n_indexes[j]; + } + } + + return count; +} + +static int ssb_first_symbol(const srsran_ssb_cfg_t* cfg, uint32_t ssb_i) +{ + uint32_t indexes[SRSRAN_SSB_NOF_POSITION]; + uint32_t Lmax = 0; + + switch (cfg->pattern) { + case SRSRAN_SSB_PATTERN_A: + Lmax = ssb_first_symbol_caseA(cfg, indexes); + break; + case SRSRAN_SSB_PATTERN_B: + Lmax = ssb_first_symbol_caseB(cfg, indexes); + break; + case SRSRAN_SSB_PATTERN_C: + Lmax = ssb_first_symbol_caseC(cfg, indexes); + break; + case SRSRAN_SSB_PATTERN_D: + Lmax = ssb_first_symbol_caseD(cfg, indexes); + break; + case SRSRAN_SSB_PATTERN_E: + Lmax = ssb_first_symbol_caseE(cfg, indexes); + break; + case SRSRAN_SSB_PATTERN_INVALID: + ERROR("Invalid case"); + return SRSRAN_ERROR; + } + + uint32_t ssb_count = 0; + + for (uint32_t i = 0; i < Lmax; i++) { + // There is a SSB transmission opportunity + if (cfg->position[i]) { + // Return the SSB transmission in burst + if (ssb_i == ssb_count) { + return (int)indexes[i]; + } + + ssb_count++; + } + } + + return SRSRAN_ERROR; +} + +// Modulates a given symbol l and stores the time domain signal in q->tmp_time +static void ssb_modulate_symbol(srsran_ssb_t* q, cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t l) +{ + // Select symbol in grid + cf_t* ptr = &ssb_grid[l * SRSRAN_SSB_BW_SUBC]; + + // Initialise frequency domain + srsran_vec_cf_zero(q->tmp_freq, q->symbol_sz); + + // Map grid into frequency domain symbol + if (q->f_offset >= SRSRAN_SSB_BW_SUBC / 2) { + srsran_vec_cf_copy(&q->tmp_freq[q->f_offset - SRSRAN_SSB_BW_SUBC / 2], ptr, SRSRAN_SSB_BW_SUBC); + } else if (q->f_offset <= -SRSRAN_SSB_BW_SUBC / 2) { + srsran_vec_cf_copy(&q->tmp_freq[q->symbol_sz + q->f_offset - SRSRAN_SSB_BW_SUBC / 2], ptr, SRSRAN_SSB_BW_SUBC); + } else { + srsran_vec_cf_copy( + &q->tmp_freq[0], &ptr[SRSRAN_SSB_BW_SUBC / 2 - q->f_offset], SRSRAN_SSB_BW_SUBC / 2 + q->f_offset); + srsran_vec_cf_copy(&q->tmp_freq[q->symbol_sz - SRSRAN_SSB_BW_SUBC / 2 + q->f_offset], + &ptr[0], + SRSRAN_SSB_BW_SUBC / 2 - q->f_offset); + } + + // Convert to time domain + srsran_dft_run_guru_c(&q->ifft); + + // Normalise output + float norm = sqrtf((float)q->symbol_sz); + if (isnormal(norm)) { + srsran_vec_sc_prod_cfc(q->tmp_time, 1.0f / norm, q->tmp_time, q->symbol_sz); + } +} + +static int ssb_setup_corr(srsran_ssb_t* q) +{ + // Skip if disabled + if (!q->args.enable_search) { + return SRSRAN_SUCCESS; + } + + // Compute new correlation size + uint32_t corr_sz = SSB_CORR_SZ(q->symbol_sz); + + // Skip if the symbol size is unchanged + if (q->corr_sz == corr_sz) { + return SRSRAN_SUCCESS; + } + q->corr_sz = corr_sz; + + // Select correlation window, return error if the correlation window is smaller than a symbol + if (corr_sz < 2 * q->symbol_sz) { + ERROR("Correlation size (%d) is not sufficient (min. %d)", corr_sz, q->symbol_sz * 2); + return SRSRAN_ERROR; + } + q->corr_window = corr_sz - q->symbol_sz; + + // Free correlation + srsran_dft_plan_free(&q->fft_corr); + srsran_dft_plan_free(&q->ifft_corr); + + // Prepare correlation FFT + if (srsran_dft_plan_guru_c(&q->fft_corr, (int)corr_sz, SRSRAN_DFT_FORWARD, q->tmp_time, q->tmp_freq, 1, 1, 1, 1, 1) < + SRSRAN_SUCCESS) { + ERROR("Error planning correlation DFT"); + return SRSRAN_ERROR; + } + if (srsran_dft_plan_guru_c( + &q->ifft_corr, (int)corr_sz, SRSRAN_DFT_BACKWARD, q->tmp_corr, q->tmp_time, 1, 1, 1, 1, 1) < SRSRAN_SUCCESS) { + ERROR("Error planning correlation DFT"); + return SRSRAN_ERROR; + } + + // Zero the time domain signal last samples + srsran_vec_cf_zero(&q->tmp_time[q->symbol_sz], q->corr_window); + + // Temporal grid + cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {}; + + // Initialise correlation sequence + for (uint32_t N_id_2 = 0; N_id_2 < SRSRAN_NOF_NID_2_NR; N_id_2++) { + // Put the PSS in SSB grid + if (srsran_pss_nr_put(ssb_grid, N_id_2, 1.0f) < SRSRAN_SUCCESS) { + ERROR("Error putting PDD N_id_2=%d", N_id_2); + return SRSRAN_ERROR; + } + + // Modulate symbol with PSS + ssb_modulate_symbol(q, ssb_grid, SRSRAN_PSS_NR_SYMBOL_IDX); + + // Convert to frequency domain + srsran_dft_run_guru_c(&q->fft_corr); + + // Copy frequency domain sequence + srsran_vec_cf_copy(q->pss_seq[N_id_2], q->tmp_freq, q->corr_sz); + } + + return SRSRAN_SUCCESS; +} + int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg) { // Verify input parameters @@ -93,13 +386,37 @@ int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg) } // Calculate subcarrier spacing in Hz - q->scs_hz = (double)SRSRAN_SUBC_SPACING_NR(cfg->scs); + q->scs_hz = (float)SRSRAN_SUBC_SPACING_NR(cfg->scs); + + // Get first symbol + int l_begin = ssb_first_symbol(cfg, 0); + if (l_begin < SRSRAN_SUCCESS) { + // set it to 2 in case it is not selected + l_begin = 2; + } + + float t_offset_s = srsran_symbol_offset_s((uint32_t)l_begin, cfg->scs); + if (isnan(t_offset_s) || isinf(t_offset_s) || t_offset_s < 0.0f) { + ERROR("Invalid first symbol (l_first=%d)", l_begin); + return SRSRAN_ERROR; + } // Calculate SSB symbol size and integer offset - uint32_t symbol_sz = (uint32_t)round(cfg->srate_hz / q->scs_hz); - q->offset = (uint32_t)(cfg->freq_offset_hz / q->scs_hz); - q->cp0_sz = (160U * symbol_sz) / 2048U; - q->cp_sz = (144U * symbol_sz) / 2048U; + double freq_offset_hz = cfg->ssb_freq_hz - cfg->center_freq_hz; + uint32_t symbol_sz = (uint32_t)round(cfg->srate_hz / q->scs_hz); + q->f_offset = (int32_t)round(freq_offset_hz / q->scs_hz); + q->t_offset = (uint32_t)round(t_offset_s * cfg->srate_hz); + + for (uint32_t l = 0; l < SRSRAN_SSB_DURATION_NSYMB; l++) { + uint32_t l_real = l + (uint32_t)l_begin; + + uint32_t ref_cp_sz = 144U; + if (l_real == 0 || l_real == SRSRAN_EXT_CP_SYMBOL(cfg->scs)) { + ref_cp_sz = 160U; + } + + q->cp_sz[l] = (ref_cp_sz * symbol_sz) / 2048U; + } // Calculate SSB sampling error and check double ssb_srate_error_Hz = ((double)symbol_sz * q->scs_hz) - cfg->srate_hz; @@ -109,9 +426,9 @@ int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg) } // Calculate SSB offset error and check - double ssb_offset_error_Hz = ((double)q->offset * q->scs_hz) - cfg->freq_offset_hz; + double ssb_offset_error_Hz = ((double)q->f_offset * q->scs_hz) - freq_offset_hz; if (fabs(ssb_offset_error_Hz) > SSB_FREQ_OFFSET_MAX_ERROR_HZ) { - ERROR("SSB Offset error exceeds maximum allowed"); + ERROR("SSB Offset (%.1f kHz) error exceeds maximum allowed", freq_offset_hz / 1e3); return SRSRAN_ERROR; } @@ -121,7 +438,7 @@ int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg) } // Replan iFFT - if ((q->args.enable_encode) && q->symbol_sz != symbol_sz) { + if ((q->args.enable_encode || q->args.enable_search) && q->symbol_sz != symbol_sz) { // free the current IFFT, it internally checks if the plan was created srsran_dft_plan_free(&q->ifft); @@ -134,7 +451,7 @@ int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg) } // Replan FFT - if ((q->args.enable_measure || q->args.enable_decode) && q->symbol_sz != symbol_sz) { + if ((q->args.enable_measure || q->args.enable_decode || q->args.enable_search) && q->symbol_sz != symbol_sz) { // free the current FFT, it internally checks if the plan was created srsran_dft_plan_free(&q->fft); @@ -150,6 +467,12 @@ int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg) q->cfg = *cfg; q->symbol_sz = symbol_sz; + // Initialise correlation + if (ssb_setup_corr(q) < SRSRAN_SUCCESS) { + ERROR("Error initialising correlation"); + return SRSRAN_ERROR; + } + if (!isnormal(q->cfg.beta_pss)) { q->cfg.beta_pss = SRSRAN_SSB_DEFAULT_BETA; } @@ -169,6 +492,22 @@ int srsran_ssb_set_cfg(srsran_ssb_t* q, const srsran_ssb_cfg_t* cfg) return SRSRAN_SUCCESS; } +bool srsran_ssb_send(srsran_ssb_t* q, uint32_t sf_idx) +{ + // Verify input + if (q == NULL) { + return false; + } + + // Verify periodicity + if (q->cfg.periodicity_ms == 0) { + return false; + } + + // Check periodicity + return (sf_idx % q->cfg.periodicity_ms == 0); +} + int srsran_ssb_add(srsran_ssb_t* q, uint32_t N_id, const srsran_pbch_msg_nr_t* msg, const cf_t* in, cf_t* out) { // Verify input parameters @@ -203,38 +542,17 @@ int srsran_ssb_add(srsran_ssb_t* q, uint32_t N_id, const srsran_pbch_msg_nr_t* m // Put PBCH payload // ... - // Initialise frequency domain - srsran_vec_cf_zero(q->tmp_freq, q->symbol_sz); + // Select input/ouput pointers considering the time offset in the slot + const cf_t* in_ptr = &in[q->t_offset]; + cf_t* out_ptr = &out[q->t_offset]; - // Modulate - const cf_t* in_ptr = in; - cf_t* out_ptr = out; + // For each SSB symbol, modulate for (uint32_t l = 0; l < SRSRAN_SSB_DURATION_NSYMB; l++) { // Get CP length - uint32_t cp_len = (l == 0) ? q->cp0_sz : q->cp_sz; - - // Select symbol in grid - cf_t* ptr = &ssb_grid[l * SRSRAN_SSB_BW_SUBC]; + uint32_t cp_len = q->cp_sz[l]; - // Map grid into frequency domain symbol - if (q->offset >= SRSRAN_SSB_BW_SUBC / 2) { - srsran_vec_cf_copy(&q->tmp_freq[q->offset - SRSRAN_SSB_BW_SUBC / 2], ptr, SRSRAN_SSB_BW_SUBC); - } else if (q->offset <= -SRSRAN_SSB_BW_SUBC / 2) { - srsran_vec_cf_copy(&q->tmp_freq[q->symbol_sz + q->offset - SRSRAN_SSB_BW_SUBC / 2], ptr, SRSRAN_SSB_BW_SUBC); - } else { - srsran_vec_cf_copy(&q->tmp_freq[0], &ptr[SRSRAN_SSB_BW_SUBC / 2 - q->offset], SRSRAN_SSB_BW_SUBC / 2 + q->offset); - srsran_vec_cf_copy( - &q->tmp_freq[q->symbol_sz - SRSRAN_SSB_BW_SUBC / 2 + q->offset], &ptr[0], SRSRAN_SSB_BW_SUBC / 2 - q->offset); - } - - // Convert to time domain - srsran_dft_run_guru_c(&q->ifft); - - // Normalise output - float norm = sqrtf((float)q->symbol_sz); - if (isnormal(norm)) { - srsran_vec_sc_prod_cfc(q->tmp_time, 1.0f / norm, q->tmp_time, q->symbol_sz); - } + // Map SSB in resource grid and perform IFFT + ssb_modulate_symbol(q, ssb_grid, l); // Add cyclic prefix to input; srsran_vec_sum_ccc(in_ptr, &q->tmp_time[q->symbol_sz - cp_len], out_ptr, cp_len); @@ -250,27 +568,12 @@ int srsran_ssb_add(srsran_ssb_t* q, uint32_t N_id, const srsran_pbch_msg_nr_t* m return SRSRAN_SUCCESS; } -int srsran_ssb_csi_measure(srsran_ssb_t* q, uint32_t N_id, const cf_t* in, srsran_csi_trs_measurements_t* meas) +static int ssb_demodulate(srsran_ssb_t* q, const cf_t* in, uint32_t t_offset, cf_t ssb_grid[SRSRAN_SSB_NOF_RE]) { - // Verify inputs - if (q == NULL || N_id >= SRSRAN_NOF_NID_NR || in == NULL || meas == NULL || !isnormal(q->scs_hz)) { - return SRSRAN_ERROR_INVALID_INPUTS; - } - - if (!q->args.enable_measure) { - ERROR("SSB is not configured for measure"); - return SRSRAN_ERROR; - } - - uint32_t N_id_1 = SRSRAN_NID_1_NR(N_id); - uint32_t N_id_2 = SRSRAN_NID_2_NR(N_id); - cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {}; - - // Demodulate - const cf_t* in_ptr = in; + const cf_t* in_ptr = &in[t_offset]; for (uint32_t l = 0; l < SRSRAN_SSB_DURATION_NSYMB; l++) { // Get CP length - uint32_t cp_len = (l == 0) ? q->cp0_sz : q->cp_sz; + uint32_t cp_len = q->cp_sz[l]; // Advance half CP, to avoid inter symbol interference in_ptr += SRSRAN_FLOOR(cp_len, 2); @@ -289,14 +592,16 @@ int srsran_ssb_csi_measure(srsran_ssb_t* q, uint32_t N_id, const cf_t* in, srsra cf_t* ptr = &ssb_grid[l * SRSRAN_SSB_BW_SUBC]; // Map frequency domain symbol into the SSB grid - if (q->offset >= SRSRAN_SSB_BW_SUBC / 2) { - srsran_vec_cf_copy(ptr, &q->tmp_freq[q->offset - SRSRAN_SSB_BW_SUBC / 2], SRSRAN_SSB_BW_SUBC); - } else if (q->offset <= -SRSRAN_SSB_BW_SUBC / 2) { - srsran_vec_cf_copy(ptr, &q->tmp_freq[q->symbol_sz + q->offset - SRSRAN_SSB_BW_SUBC / 2], SRSRAN_SSB_BW_SUBC); + if (q->f_offset >= SRSRAN_SSB_BW_SUBC / 2) { + srsran_vec_cf_copy(ptr, &q->tmp_freq[q->f_offset - SRSRAN_SSB_BW_SUBC / 2], SRSRAN_SSB_BW_SUBC); + } else if (q->f_offset <= -SRSRAN_SSB_BW_SUBC / 2) { + srsran_vec_cf_copy(ptr, &q->tmp_freq[q->symbol_sz + q->f_offset - SRSRAN_SSB_BW_SUBC / 2], SRSRAN_SSB_BW_SUBC); } else { - srsran_vec_cf_copy(&ptr[SRSRAN_SSB_BW_SUBC / 2 - q->offset], &q->tmp_freq[0], SRSRAN_SSB_BW_SUBC / 2 + q->offset); srsran_vec_cf_copy( - &ptr[0], &q->tmp_freq[q->symbol_sz - SRSRAN_SSB_BW_SUBC / 2 + q->offset], SRSRAN_SSB_BW_SUBC / 2 - q->offset); + &ptr[SRSRAN_SSB_BW_SUBC / 2 - q->f_offset], &q->tmp_freq[0], SRSRAN_SSB_BW_SUBC / 2 + q->f_offset); + srsran_vec_cf_copy(&ptr[0], + &q->tmp_freq[q->symbol_sz - SRSRAN_SSB_BW_SUBC / 2 + q->f_offset], + SRSRAN_SSB_BW_SUBC / 2 - q->f_offset); } // Normalize @@ -306,6 +611,15 @@ int srsran_ssb_csi_measure(srsran_ssb_t* q, uint32_t N_id, const cf_t* in, srsra } } + return SRSRAN_SUCCESS; +} + +static int +ssb_measure(srsran_ssb_t* q, const cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t N_id, srsran_csi_trs_measurements_t* meas) +{ + uint32_t N_id_1 = SRSRAN_NID_1_NR(N_id); + uint32_t N_id_2 = SRSRAN_NID_2_NR(N_id); + // Extract PSS LSE cf_t pss_lse[SRSRAN_PSS_NR_LEN]; cf_t sss_lse[SRSRAN_SSS_NR_LEN]; @@ -322,14 +636,17 @@ int srsran_ssb_csi_measure(srsran_ssb_t* q, uint32_t N_id, const cf_t* in, srsra float delay_avg_us = 1e6f * delay_avg_norm / q->scs_hz; // Pre-compensate delay + cf_t ssb_grid_corrected[SRSRAN_SSB_NOF_RE]; for (uint32_t l = 0; l < SRSRAN_SSB_DURATION_NSYMB; l++) { - srsran_vec_apply_cfo( - &ssb_grid[SRSRAN_SSB_BW_SUBC * l], delay_avg_norm, &ssb_grid[SRSRAN_SSB_BW_SUBC * l], SRSRAN_SSB_BW_SUBC); + srsran_vec_apply_cfo(&ssb_grid[SRSRAN_SSB_BW_SUBC * l], + delay_avg_norm, + &ssb_grid_corrected[SRSRAN_SSB_BW_SUBC * l], + SRSRAN_SSB_BW_SUBC); } // Extract LSE again - if (srsran_pss_nr_extract_lse(ssb_grid, N_id_2, pss_lse) < SRSRAN_SUCCESS || - srsran_sss_nr_extract_lse(ssb_grid, N_id_1, N_id_2, sss_lse) < SRSRAN_SUCCESS) { + if (srsran_pss_nr_extract_lse(ssb_grid_corrected, N_id_2, pss_lse) < SRSRAN_SUCCESS || + srsran_sss_nr_extract_lse(ssb_grid_corrected, N_id_1, N_id_2, sss_lse) < SRSRAN_SUCCESS) { ERROR("Error extracting LSE"); return SRSRAN_ERROR; } @@ -349,19 +666,26 @@ int srsran_ssb_csi_measure(srsran_ssb_t* q, uint32_t N_id, const cf_t* in, srsra float cfo_hz = cargf(corr_pss * conjf(corr_sss)) / (2.0f * M_PI) * cfo_hz_max; // Compute average RSRP - float rsrp = (SRSRAN_CSQABS(corr_pss) + SRSRAN_CSQABS(corr_sss)) / 2.0f; + float rsrp_pss = SRSRAN_CSQABS(corr_pss); + float rsrp_sss = SRSRAN_CSQABS(corr_sss); + float rsrp = (rsrp_pss + rsrp_sss) / 2.0f; // Compute Noise - float n0 = 1e-9; // Almost 0 - if (epre > rsrp) { - n0 = epre - rsrp; + float n0_pss = 1e-9; // Almost 0 + float n0_sss = 1e-9; // Almost 0 + if (epre_pss > rsrp_pss) { + n0_pss = epre - rsrp_pss; + } + if (epre_pss > rsrp_pss) { + n0_sss = epre - rsrp_sss; } + float n0 = (n0_pss + n0_sss) / 2.0f; // Put measurements together meas->epre = epre; meas->epre_dB = srsran_convert_power_to_dB(epre); meas->rsrp = rsrp; - meas->epre_dB = srsran_convert_power_to_dB(rsrp); + meas->rsrp_dB = srsran_convert_power_to_dB(rsrp); meas->n0 = n0; meas->n0_dB = srsran_convert_power_to_dB(n0); meas->snr_dB = meas->rsrp_dB - meas->n0_dB; @@ -371,4 +695,170 @@ int srsran_ssb_csi_measure(srsran_ssb_t* q, uint32_t N_id, const cf_t* in, srsra meas->nof_re = SRSRAN_PSS_NR_LEN + SRSRAN_SSS_NR_LEN; return SRSRAN_SUCCESS; -} \ No newline at end of file +} + +static int +ssb_pss_search(srsran_ssb_t* q, const cf_t* in, uint32_t nof_samples, uint32_t* found_N_id_2, uint32_t* found_delay) +{ + // verify it is initialised + if (q->corr_sz == 0) { + return SRSRAN_ERROR; + } + + // Correlation best sequence + float best_corr = 0; + uint32_t best_delay = 0; + uint32_t best_N_id_2 = 0; + + // Delay in correlation window + uint32_t t_offset = 0; + while ((t_offset + q->symbol_sz) < nof_samples) { + // Number of samples taken in this iteration + uint32_t n = q->corr_sz; + + // Detect if the correlation input exceeds the input length, take the maximum amount of samples + if (t_offset + q->corr_sz > nof_samples) { + n = nof_samples - t_offset; + } + + // Copy the amount of samples + srsran_vec_cf_copy(q->tmp_time, &in[t_offset], n); + + // Append zeros if there is space left + if (n < q->corr_sz) { + srsran_vec_cf_zero(&q->tmp_time[n], q->corr_sz - n); + } + + // Convert to frequency domain + srsran_dft_run_guru_c(&q->fft_corr); + + // Try each N_id_2 sequence + for (uint32_t N_id_2 = 0; N_id_2 < SRSRAN_NOF_NID_2_NR; N_id_2++) { + // Actual correlation in frequency domain + srsran_vec_prod_conj_ccc(q->tmp_freq, q->pss_seq[N_id_2], q->tmp_corr, q->corr_sz); + + // Convert to time domain + srsran_dft_run_guru_c(&q->ifft_corr); + + // Find maximum + uint32_t peak_idx = srsran_vec_max_abs_ci(q->tmp_time, q->corr_window); + + // Average power, skip window if value is invalid (0.0, nan or inf) + float avg_pwr_corr = srsran_vec_avg_power_cf(&q->tmp_time[peak_idx], q->symbol_sz); + if (!isnormal(avg_pwr_corr)) { + continue; + } + + float corr = SRSRAN_CSQABS(q->tmp_time[peak_idx]) / avg_pwr_corr; + if (corr < sqrtf(SRSRAN_PSS_NR_LEN)) { + continue; + } + + // Update if the correlation is better than the current best + if (best_corr < corr) { + best_corr = corr; + best_delay = peak_idx + t_offset; + best_N_id_2 = N_id_2; + } + } + + // Advance time + t_offset += q->corr_window; + } + + // Save findings + *found_delay = best_delay; + *found_N_id_2 = best_N_id_2; + + return SRSRAN_SUCCESS; +} + +int srsran_ssb_csi_search(srsran_ssb_t* q, + const cf_t* in, + uint32_t nof_samples, + uint32_t* N_id, + srsran_csi_trs_measurements_t* meas) +{ + // Verify inputs + if (q == NULL || in == NULL || N_id == NULL || meas == NULL || !isnormal(q->scs_hz)) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + if (!q->args.enable_search) { + ERROR("SSB is not configured for search"); + return SRSRAN_ERROR; + } + + // Search for PSS in time domain + uint32_t N_id_2 = 0; + uint32_t t_offset = 0; + if (ssb_pss_search(q, in, nof_samples, &N_id_2, &t_offset) < SRSRAN_SUCCESS) { + ERROR("Error searching for N_id_2"); + return SRSRAN_ERROR; + } + + // Remove CP offset prior demodulation + if (t_offset >= q->cp_sz[0]) { + t_offset -= q->cp_sz[0]; + } else { + t_offset = 0; + } + + // Demodulate + cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {}; + if (ssb_demodulate(q, in, t_offset, ssb_grid) < SRSRAN_SUCCESS) { + ERROR("Error demodulating"); + return SRSRAN_ERROR; + } + + // Find best N_id_1 + uint32_t N_id_1 = 0; + float sss_corr = 0.0f; + if (srsran_sss_nr_find(ssb_grid, N_id_2, &sss_corr, &N_id_1) < SRSRAN_SUCCESS) { + ERROR("Error searching for N_id_2"); + return SRSRAN_ERROR; + } + + // Select N_id + *N_id = SRSRAN_NID_NR(N_id_1, N_id_2); + + // Measure selected N_id + if (ssb_measure(q, ssb_grid, *N_id, meas)) { + ERROR("Error measuring"); + return SRSRAN_ERROR; + } + + // Add delay to measure + meas->delay_us += (float)(1e6 * t_offset / q->cfg.srate_hz); + + return SRSRAN_SUCCESS; +} + +int srsran_ssb_csi_measure(srsran_ssb_t* q, uint32_t N_id, const cf_t* in, srsran_csi_trs_measurements_t* meas) +{ + // Verify inputs + if (q == NULL || N_id >= SRSRAN_NOF_NID_NR || in == NULL || meas == NULL || !isnormal(q->scs_hz)) { + return SRSRAN_ERROR_INVALID_INPUTS; + } + + if (!q->args.enable_measure) { + ERROR("SSB is not configured for measure"); + return SRSRAN_ERROR; + } + + cf_t ssb_grid[SRSRAN_SSB_NOF_RE] = {}; + + // Demodulate + if (ssb_demodulate(q, in, q->t_offset, ssb_grid) < SRSRAN_SUCCESS) { + ERROR("Error demodulating"); + return SRSRAN_ERROR; + } + + // Actual measurement + if (ssb_measure(q, ssb_grid, N_id, meas)) { + ERROR("Error measuring"); + return SRSRAN_ERROR; + } + + return SRSRAN_SUCCESS; +} diff --git a/lib/src/phy/sync/sss_nr.c b/lib/src/phy/sync/sss_nr.c index a71ba097e..a5450b146 100644 --- a/lib/src/phy/sync/sss_nr.c +++ b/lib/src/phy/sync/sss_nr.c @@ -151,7 +151,7 @@ int srsran_sss_nr_find(const cf_t ssb_grid[SRSRAN_SSB_NOF_RE], uint32_t* found_N_id_1) { // Verify inputs - if (ssb_grid == NULL || N_id_2 >= SRSRAN_NOF_NID_2_NR) { + if (ssb_grid == NULL || N_id_2 >= SRSRAN_NOF_NID_2_NR || norm_corr == NULL || found_N_id_1 == NULL) { return SRSRAN_ERROR_INVALID_INPUTS; } @@ -163,9 +163,8 @@ int srsran_sss_nr_find(const cf_t ssb_grid[SRSRAN_SSB_NOF_RE], // If the measured power is invalid or zero, consider no SSS signal if (!isnormal(avg_power)) { - if (norm_corr) { - *norm_corr = 0.0f; - } + *norm_corr = 0.0f; + *found_N_id_1 = 0; return SRSRAN_SUCCESS; } @@ -185,19 +184,18 @@ int srsran_sss_nr_find(const cf_t ssb_grid[SRSRAN_SSB_NOF_RE], srsran_vec_prod_ccc(&sss_ptr[SRSRAN_SSS_NR_LEN - m1], &sss_nr_d1[0], &sss_seq_m1[SRSRAN_SSS_NR_LEN - m1], m1); // Iterate over all N_id_1 with the given m1 sequence - for (uint32_t N_id_1_blind = m1; N_id_1_blind < SRSRAN_NOF_NID_1; N_id_1_blind += SSS_NR_NOF_M1) { - uint32_t m0 = SSS_NR_SEQUENCE_M0(N_id_1_blind, N_id_2); - - cf_t sss_seq_m0[SRSRAN_SSS_NR_LEN]; + for (uint32_t N_id_1_blind = m1; N_id_1_blind < SRSRAN_NOF_NID_1_NR; N_id_1_blind += SSS_NR_NOF_M1) { + uint32_t m0 = SSS_NR_SEQUENCE_M0(N_id_1_blind, N_id_2); + cf_t acc = 0.0f; - // Apply d0 sequence fist part - srsran_vec_prod_ccc(&sss_seq_m1[0], &sss_nr_d0[m0], &sss_seq_m0[0], SRSRAN_SSS_NR_LEN - m0); + // Correlate d0 sequence fist part + acc += srsran_vec_dot_prod_ccc(&sss_seq_m1[0], &sss_nr_d0[m0], SRSRAN_SSS_NR_LEN - m0); - // Apply d0 sequence second part - srsran_vec_prod_ccc(&sss_seq_m1[SRSRAN_SSS_NR_LEN - m0], &sss_nr_d0[0], &sss_seq_m0[SRSRAN_SSS_NR_LEN - m0], m0); + // Correlate d0 sequence second part + acc += srsran_vec_dot_prod_ccc(&sss_seq_m1[SRSRAN_SSS_NR_LEN - m0], &sss_nr_d0[0], m0); - // Correlate - float corr = SRSRAN_CSQABS(srsran_vec_acc_cc(sss_seq_m0, SRSRAN_SSS_NR_LEN)) / avg_power; + // Normalise + float corr = SRSRAN_CSQABS(acc); if (corr > max_corr) { N_id_1 = N_id_1_blind; max_corr = corr; @@ -205,13 +203,9 @@ int srsran_sss_nr_find(const cf_t ssb_grid[SRSRAN_SSB_NOF_RE], } } - if (norm_corr) { - *norm_corr = max_corr; - } - - if (found_N_id_1) { - *found_N_id_1 = N_id_1; - } + // Copy found result + *norm_corr = max_corr / avg_power / SRSRAN_SSS_NR_LEN; + *found_N_id_1 = N_id_1; return SRSRAN_SUCCESS; } diff --git a/lib/src/phy/sync/test/ssb_measure_test.c b/lib/src/phy/sync/test/ssb_measure_test.c index 148867507..553f43daa 100644 --- a/lib/src/phy/sync/test/ssb_measure_test.c +++ b/lib/src/phy/sync/test/ssb_measure_test.c @@ -46,8 +46,8 @@ static cf_t* buffer = NULL; // Base-band buffer #define RSRP_MAX_ERROR 1.0f #define EPRE_MAX_ERROR 1.0f -#define N0_MAX_ERROR 2.0f -#define SNR_MAX_ERROR 2.0f +#define N0_MAX_ERROR 2.5f +#define SNR_MAX_ERROR 2.5f #define CFO_MAX_ERROR (cfo_hz * 0.3f) #define DELAY_MAX_ERROR (delay_us * 0.1f) @@ -86,13 +86,31 @@ static void run_channel() srsran_channel_awgn_run_c(&awgn, buffer, buffer, sf_len); } +static int assert_measure(const srsran_csi_trs_measurements_t* meas) +{ + TESTASSERT(fabsf(meas->rsrp_dB - 0.0f) < RSRP_MAX_ERROR); + TESTASSERT(fabsf(meas->epre_dB - 0.0f) < EPRE_MAX_ERROR); + TESTASSERT(fabsf(meas->n0_dB - n0_dB) < N0_MAX_ERROR); + TESTASSERT(fabsf(meas->snr_dB + n0_dB) < SNR_MAX_ERROR); + TESTASSERT(fabsf(meas->cfo_hz - cfo_hz) < CFO_MAX_ERROR); + TESTASSERT(fabsf(meas->delay_us + delay_us) < DELAY_MAX_ERROR); + return SRSRAN_SUCCESS; +} + static int test_case_1(srsran_ssb_t* ssb) { - uint64_t t_usec = 0; + // For benchmarking purposes + uint64_t t_find_usec = 0; + uint64_t t_meas_usec = 0; + + // SSB configuration srsran_ssb_cfg_t ssb_cfg = {}; ssb_cfg.srate_hz = srate_hz; - ssb_cfg.freq_offset_hz = 0.0; + ssb_cfg.center_freq_hz = 3.5e9; + ssb_cfg.ssb_freq_hz = 3.5e9 - 960e3; ssb_cfg.scs = ssb_scs; + ssb_cfg.pattern = SRSRAN_SSB_PATTERN_C; + ssb_cfg.position[0] = true; // Rest to false TESTASSERT(srsran_ssb_set_cfg(ssb, &ssb_cfg) == SRSRAN_SUCCESS); @@ -111,29 +129,42 @@ static int test_case_1(srsran_ssb_t* ssb) // Run channel run_channel(); + // Find + gettimeofday(&t[1], NULL); + uint32_t N_id_found = 0; + srsran_csi_trs_measurements_t meas_search = {}; + TESTASSERT(srsran_ssb_csi_search(ssb, buffer, sf_len, &N_id_found, &meas_search) == SRSRAN_SUCCESS); + gettimeofday(&t[2], NULL); + get_time_interval(t); + t_find_usec += t[0].tv_usec + t[0].tv_sec * 1000000UL; + + // Print measurement + char str[512] = {}; + srsran_csi_meas_info(&meas_search, str, sizeof(str)); + INFO("test_case_1 - search pci=%d %s", pci, str); + + // Assert find + TESTASSERT(N_id_found == pci); + // Measure gettimeofday(&t[1], NULL); srsran_csi_trs_measurements_t meas = {}; TESTASSERT(srsran_ssb_csi_measure(ssb, pci, buffer, &meas) == SRSRAN_SUCCESS); gettimeofday(&t[2], NULL); get_time_interval(t); - t_usec += t[0].tv_usec + t[0].tv_sec * 1000000UL; + t_meas_usec += t[0].tv_usec + t[0].tv_sec * 1000000UL; - // Print measurement - char str[512]; srsran_csi_meas_info(&meas, str, sizeof(str)); - INFO("test_case_1 - pci=%d %s", pci, str); + INFO("test_case_1 - measure pci=%d %s", pci, str); // Assert measurements - TESTASSERT(fabsf(meas.rsrp_dB - 0.0f) < RSRP_MAX_ERROR); - TESTASSERT(fabsf(meas.epre_dB - 0.0f) < EPRE_MAX_ERROR); - TESTASSERT(fabsf(meas.n0_dB - n0_dB) < N0_MAX_ERROR); - TESTASSERT(fabsf(meas.snr_dB + n0_dB) < SNR_MAX_ERROR); - TESTASSERT(fabsf(meas.cfo_hz - cfo_hz) < CFO_MAX_ERROR); - TESTASSERT(fabsf(meas.delay_us + delay_us) < DELAY_MAX_ERROR); + TESTASSERT(assert_measure(&meas) == SRSRAN_SUCCESS); } - INFO("test_case_1 - %.1f usec/measurement", (double)t_usec / (double)SRSRAN_NOF_NID_NR); + INFO("test_case_1 - %.1f usec/search; Max srate %.1f MSps; %.1f usec/measurement", + (double)t_find_usec / (double)SRSRAN_NOF_NID_NR, + (double)sf_len * (double)SRSRAN_NOF_NID_NR / (double)t_find_usec, + (double)t_meas_usec / (double)SRSRAN_NOF_NID_NR); return SRSRAN_SUCCESS; } @@ -152,6 +183,7 @@ int main(int argc, char** argv) srsran_ssb_args_t ssb_args = {}; ssb_args.enable_encode = true; ssb_args.enable_measure = true; + ssb_args.enable_search = true; if (buffer == NULL) { ERROR("Malloc"); diff --git a/lib/src/phy/ue/ue_cell_search.c b/lib/src/phy/ue/ue_cell_search.c index 11a302783..5f767c0e3 100644 --- a/lib/src/phy/ue/ue_cell_search.c +++ b/lib/src/phy/ue/ue_cell_search.c @@ -188,6 +188,11 @@ int srsran_ue_cellsearch_set_nof_valid_frames(srsran_ue_cellsearch_t* q, uint32_ } } +void srsran_set_detect_cp(srsran_ue_cellsearch_t* q, bool enable) +{ + srsran_ue_sync_cp_en(&q->ue_sync, enable); +} + /* Decide the most likely cell based on the mode */ static void get_cell(srsran_ue_cellsearch_t* q, uint32_t nof_detected_frames, srsran_ue_cellsearch_result_t* found_cell) { diff --git a/lib/src/phy/ue/ue_sync.c b/lib/src/phy/ue/ue_sync.c index ae3a517d3..ccbac4a45 100644 --- a/lib/src/phy/ue/ue_sync.c +++ b/lib/src/phy/ue/ue_sync.c @@ -339,7 +339,8 @@ int srsran_ue_sync_set_cell(srsran_ue_sync_t* q, srsran_cell_t cell) q->cell = cell; q->fft_size = srsran_symbol_sz(q->cell.nof_prb); q->sf_len = SRSRAN_SF_LEN(q->fft_size); - + srsran_sync_set_cp(&q->sfind, q->cell.cp); + srsran_sync_set_cp(&q->strack, q->cell.cp); if (cell.id == 1000) { /* If the cell is unkown, we search PSS/SSS in 5 ms */ q->nof_recv_sf = 5; @@ -508,6 +509,12 @@ float srsran_ue_sync_get_cfo(srsran_ue_sync_t* q) return 15000 * q->cfo_current_value; } +void srsran_ue_sync_cp_en(srsran_ue_sync_t* q, bool enabled) +{ + srsran_sync_cp_en(&q->strack, enabled); + srsran_sync_cp_en(&q->sfind, enabled); +} + void srsran_ue_sync_copy_cfo(srsran_ue_sync_t* q, srsran_ue_sync_t* src_obj) { // Copy find object internal CFO averages diff --git a/lib/src/phy/utils/vector_simd.c b/lib/src/phy/utils/vector_simd.c index 8528fdc0a..4a0e1e4e8 100644 --- a/lib/src/phy/utils/vector_simd.c +++ b/lib/src/phy/utils/vector_simd.c @@ -1693,21 +1693,21 @@ void srsran_vec_apply_cfo_simd(const cf_t* x, float cfo, cf_t* z, int len) { const float TWOPI = 2.0f * (float)M_PI; int i = 0; + cf_t osc = cexpf(_Complex_I * TWOPI * cfo); + cf_t phase = 1.0f; #if SRSRAN_SIMD_CF_SIZE - srsran_simd_aligned cf_t _osc[SRSRAN_SIMD_CF_SIZE]; + // Load initial phases and oscillator srsran_simd_aligned cf_t _phase[SRSRAN_SIMD_CF_SIZE]; - - if (i < len - SRSRAN_SIMD_CF_SIZE + 1) { - for (int k = 0; k < SRSRAN_SIMD_CF_SIZE; k++) { - _osc[k] = cexpf(_Complex_I * TWOPI * cfo * SRSRAN_SIMD_CF_SIZE); - _phase[k] = cexpf(_Complex_I * TWOPI * cfo * k); - } + _phase[0] = phase; + for (int k = 1; k < SRSRAN_SIMD_CF_SIZE; k++) { + _phase[k] = _phase[k - 1] * osc; } - simd_cf_t _simd_osc = srsran_simd_cfi_load(_osc); + simd_cf_t _simd_osc = srsran_simd_cf_set1(_phase[SRSRAN_SIMD_CF_SIZE - 1] * osc); simd_cf_t _simd_phase = srsran_simd_cfi_load(_phase); if (SRSRAN_IS_ALIGNED(x) && SRSRAN_IS_ALIGNED(z)) { + // For aligned memory for (; i < len - SRSRAN_SIMD_CF_SIZE + 1; i += SRSRAN_SIMD_CF_SIZE) { simd_cf_t a = srsran_simd_cfi_load(&x[i]); @@ -1718,6 +1718,7 @@ void srsran_vec_apply_cfo_simd(const cf_t* x, float cfo, cf_t* z, int len) _simd_phase = srsran_simd_cf_prod(_simd_phase, _simd_osc); } } else { + // For unaligned memory for (; i < len - SRSRAN_SIMD_F_SIZE + 1; i += SRSRAN_SIMD_F_SIZE) { simd_cf_t a = srsran_simd_cfi_loadu(&x[i]); @@ -1728,9 +1729,12 @@ void srsran_vec_apply_cfo_simd(const cf_t* x, float cfo, cf_t* z, int len) _simd_phase = srsran_simd_cf_prod(_simd_phase, _simd_osc); } } + + // Stores the next phase + srsran_simd_cfi_store(_phase, _simd_phase); + phase = _phase[0]; #endif - cf_t osc = cexpf(_Complex_I * TWOPI * cfo); - cf_t phase = cexpf(_Complex_I * TWOPI * cfo * i); + for (; i < len; i++) { z[i] = x[i] * phase; diff --git a/lib/src/srslog/backend_worker.cpp b/lib/src/srslog/backend_worker.cpp index 6b6c5b04f..4e01c4a3d 100644 --- a/lib/src/srslog/backend_worker.cpp +++ b/lib/src/srslog/backend_worker.cpp @@ -141,11 +141,6 @@ void backend_worker::process_log_entry(detail::log_entry&& entry) assert(entry.format_func && "Invalid format function"); fmt_buffer.clear(); - // Already formatted strings in the foreground are passed to the formatter as the fmtstring. - if (entry.metadata.small_str.size()) { - entry.metadata.fmtstring = entry.metadata.small_str.data(); - } - // Save the pointer before moving the entry. auto* arg_store = entry.metadata.store; diff --git a/lib/src/srslog/bundled/fmt/format.cc b/lib/src/srslog/bundled/fmt/format.cc index 4864ac26e..d1fc4f39b 100644 --- a/lib/src/srslog/bundled/fmt/format.cc +++ b/lib/src/srslog/bundled/fmt/format.cc @@ -25,6 +25,44 @@ int format_float(char* buf, std::size_t size, const char* format, int precision, : snprintf_ptr(buf, size, format, precision, value); } +namespace { + +/// This special mutex has priority inheritance to improve latency. +class pi_mutex +{ +public: + pi_mutex(const pi_mutex&) = delete; + pi_mutex& operator=(const pi_mutex&) = delete; + + pi_mutex() + { + ::pthread_mutexattr_t mutex_attr; + ::pthread_mutexattr_init(&mutex_attr); + ::pthread_mutexattr_setprotocol(&mutex_attr, PTHREAD_PRIO_INHERIT); + ::pthread_mutex_init(&m, &mutex_attr); + } + + ~pi_mutex() { ::pthread_mutex_destroy(&m); } + + /// Mutex lock. + void lock() { ::pthread_mutex_lock(&m); } + + /// Mutex unlock. + void unlock() { ::pthread_mutex_unlock(&m); } + + /// Mutex try lock. Returns true if the lock was obtained, false otherwise. + bool try_lock() { return (::pthread_mutex_trylock(&m) == 0); } + + /// Accessor to the raw mutex structure. + pthread_mutex_t* raw() { return &m; } + const pthread_mutex_t* raw() const { return &m; } + +private: + pthread_mutex_t m; +}; + +} + #define NODE_POOL_SIZE (10000u) static constexpr uint8_t memory_heap_tag = 0xAA; class dyn_node_pool @@ -49,7 +87,7 @@ public: void* alloc(std::size_t sz) { assert(sz <= dynamic_arg_list::max_pool_node_size && "Object is too large to fit in the pool"); - std::lock_guard lock(m); + std::lock_guard lock(m); if (free_list.empty()) { // Tag that this allocation was performed by the heap. auto *p = new type; @@ -70,7 +108,6 @@ public: return; } - std::lock_guard lock(m); uint8_t* base_ptr = reinterpret_cast(p) - 1; if (*base_ptr == memory_heap_tag) { // This pointer was allocated using the heap. @@ -78,13 +115,14 @@ public: return; } + std::lock_guard lock(m); free_list.push_back(base_ptr); } private: std::vector pool; std::vector free_list; - mutable std::mutex m; + mutable pi_mutex m; }; static dyn_node_pool node_pool; diff --git a/lib/src/srslog/event_trace.cpp b/lib/src/srslog/event_trace.cpp index abc793480..4064996fa 100644 --- a/lib/src/srslog/event_trace.cpp +++ b/lib/src/srslog/event_trace.cpp @@ -134,9 +134,5 @@ srslog::detail::scoped_complete_event::~scoped_complete_event() return; } - small_str_buffer str; - // Limit the category and name strings to a predefined length so everything fits in a small string. - fmt::format_to(str, "{:.32} {:.16}, {}", category, name, diff.count()); - str.push_back('\0'); - (*tracer)(std::move(str)); + (*tracer)("%s %s, %u", category, name, (unsigned)diff.count()); } diff --git a/lib/src/upper/pdcp.cc b/lib/src/upper/pdcp.cc index c75f5ca4b..18ce0f010 100644 --- a/lib/src/upper/pdcp.cc +++ b/lib/src/upper/pdcp.cc @@ -172,7 +172,7 @@ void pdcp::del_bearer(uint32_t lcid) } if (valid_lcid(lcid)) { pdcp_array.erase(lcid); - logger.warning("Deleted PDCP bearer %s", rrc->get_rb_name(lcid)); + logger.info("Deleted PDCP bearer %s", rrc->get_rb_name(lcid)); } else { logger.warning("Can't delete bearer %s. Bearer doesn't exist.", rrc->get_rb_name(lcid)); } diff --git a/lib/src/upper/rlc_am_lte.cc b/lib/src/upper/rlc_am_lte.cc index 23a182957..6c1639902 100644 --- a/lib/src/upper/rlc_am_lte.cc +++ b/lib/src/upper/rlc_am_lte.cc @@ -565,7 +565,7 @@ int rlc_am_lte::rlc_am_lte_tx::read_pdu(uint8_t* payload, uint32_t nof_bytes) // Section 5.2.2.3 in TS 36.311, if tx_window is full and retx_queue empty, retransmit PDU if (tx_window.size() >= RLC_AM_WINDOW_SIZE && retx_queue.empty()) { - retransmit_pdu(); + retransmit_pdu(vt_a); } // RETX if required @@ -585,11 +585,11 @@ void rlc_am_lte::rlc_am_lte_tx::timer_expired(uint32_t timeout_id) std::unique_lock lock(mutex); if (poll_retx_timer.is_valid() && poll_retx_timer.id() == timeout_id) { logger.debug("%s Poll reTx timer expired after %dms", RB_NAME, poll_retx_timer.duration()); - // Section 5.2.2.3 in TS 36.311, schedule PDU for retransmission if - // (a) both tx and retx buffer are empty, or + // Section 5.2.2.3 in TS 36.322, schedule PDU for retransmission if + // (a) both tx and retx buffer are empty (excluding tx'ed PDU waiting for ack), or // (b) no new data PDU can be transmitted (tx window is full) if ((retx_queue.empty() && tx_sdu_queue.size() == 0) || tx_window.size() >= RLC_AM_WINDOW_SIZE) { - retransmit_pdu(); + retransmit_pdu(vt_a); // TODO: TS says to send vt_s - 1 here } } @@ -600,21 +600,21 @@ void rlc_am_lte::rlc_am_lte_tx::timer_expired(uint32_t timeout_id) } } -void rlc_am_lte::rlc_am_lte_tx::retransmit_pdu() +void rlc_am_lte::rlc_am_lte_tx::retransmit_pdu(uint32_t sn) { if (tx_window.empty()) { - logger.warning("%s No PDU to retransmit.", RB_NAME); + logger.warning("%s No PDU to retransmit", RB_NAME); return; } - if (not tx_window.has_sn(vt_a)) { - logger.warning("%s Can't retransmit unexisting SN=%d.", RB_NAME, vt_a); + if (not tx_window.has_sn(sn)) { + logger.warning("%s Can't retransmit unexisting SN=%d", RB_NAME, sn); return; } // select first PDU in tx window for retransmission - rlc_amd_tx_pdu& pdu = tx_window[vt_a]; - logger.info("%s Schedule SN=%d for reTx.", RB_NAME, pdu.rlc_sn); + rlc_amd_tx_pdu& pdu = tx_window[sn]; + logger.info("%s Schedule SN=%d for reTx", RB_NAME, pdu.rlc_sn); rlc_amd_retx_t& retx = retx_queue.push(); retx.is_segment = false; retx.so_start = 0; @@ -1263,8 +1263,8 @@ void rlc_am_lte::rlc_am_lte_tx::handle_control_pdu(uint8_t* payload, uint32_t no } // Make sure vt_a points to valid SN - if (not tx_window.empty() && not tx_window.has_sn(vt_a)) { - logger.error("%s vt_a=%d points to invalid position in Tx window", RB_NAME, vt_a); + if (not tx_window.empty()) { + srsran_expect(tx_window.has_sn(vt_a), "%s vt_a=%d points to invalid position in Tx window", RB_NAME, vt_a); } debug_state(); diff --git a/lib/test/phy/CMakeLists.txt b/lib/test/phy/CMakeLists.txt index cad583825..1ded8db55 100644 --- a/lib/test/phy/CMakeLists.txt +++ b/lib/test/phy/CMakeLists.txt @@ -30,33 +30,35 @@ target_link_libraries(phy_dl_test srsran_phy srsran_common srsran_phy ${SEC_LIBR set(ue_dl_min_mcs 0) set(ue_dl_max_mcs 28) set(ue_dl_step_mcs 7) - -foreach (cell_n_prb 6 15 25 50 75 100) - foreach (allow_256 0 1) - foreach (ue_dl_tm 1 2 3 4) - foreach (ue_dl_mcs RANGE ${ue_dl_min_mcs} ${ue_dl_max_mcs} ${ue_dl_step_mcs}) - set(phy_dl_test_args "") - - set(phy_dl_test_args ${phy_dl_test_args} -p ${cell_n_prb}) - set(phy_dl_test_args ${phy_dl_test_args} -t ${ue_dl_tm}) - if (${allow_256}) - if (${ue_dl_mcs} EQUAL 28) - if (${cell_n_prb} EQUAL 15) - set(ue_dl_mcs 26) - else (${cell_n_prb} EQUAL 15) - set(ue_dl_mcs 27) - endif (${cell_n_prb} EQUAL 15) - endif (${ue_dl_mcs} EQUAL 28) - - set(phy_dl_test_args ${phy_dl_test_args} -q) - endif (${allow_256}) - set(phy_dl_test_args ${phy_dl_test_args} -m ${ue_dl_mcs}) - string(REGEX REPLACE "\ " "" test_name_args ${phy_dl_test_args}) - add_lte_test(phy_dl_test${test_name_args} phy_dl_test ${phy_dl_test_args}) - endforeach (ue_dl_mcs) - endforeach (ue_dl_tm) - endforeach (allow_256 0 1) -endforeach (cell_n_prb) +foreach (cp 0 1) + foreach (cell_n_prb 6 15 25 50 75 100) + foreach (allow_256 0 1) + foreach (ue_dl_tm 1 2 3 4) + foreach (ue_dl_mcs RANGE ${ue_dl_min_mcs} ${ue_dl_max_mcs} ${ue_dl_step_mcs}) + set(phy_dl_test_args "") + if(NOT ((${cp} EQUAL 1) AND (ue_dl_mcs GREATER 26))) + set(phy_dl_test_args ${phy_dl_test_args} -p ${cell_n_prb}) + set(phy_dl_test_args ${phy_dl_test_args} -t ${ue_dl_tm}) + set(phy_dl_test_args ${phy_dl_test_args} -E ${cp}) + if (${allow_256}) + if (${ue_dl_mcs} EQUAL 28) + if (${cell_n_prb} EQUAL 15) + set(ue_dl_mcs 26) + else (${cell_n_prb} EQUAL 15) + set(ue_dl_mcs 27) + endif (${cell_n_prb} EQUAL 15) + endif (${ue_dl_mcs} EQUAL 28) + set(phy_dl_test_args ${phy_dl_test_args} -q) + endif (${allow_256}) + set(phy_dl_test_args ${phy_dl_test_args} -m ${ue_dl_mcs}) + string(REGEX REPLACE "\ " "" test_name_args ${phy_dl_test_args}) + add_lte_test(phy_dl_test${test_name_args} phy_dl_test ${phy_dl_test_args}) + endif(NOT ((${cp} EQUAL 1) AND (ue_dl_mcs GREATER 26))) + endforeach (ue_dl_mcs) + endforeach (ue_dl_tm) + endforeach (allow_256 0 1) + endforeach (cell_n_prb) +endforeach (cp) add_executable(pucch_ca_test pucch_ca_test.c) target_link_libraries(pucch_ca_test srsran_phy srsran_common srsran_phy ${SEC_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) diff --git a/lib/test/phy/phy_dl_test.c b/lib/test/phy/phy_dl_test.c index 96a6ecf0b..ae2d84309 100644 --- a/lib/test/phy/phy_dl_test.c +++ b/lib/test/phy/phy_dl_test.c @@ -49,6 +49,7 @@ void usage(char* prog) { printf("Usage: %s [cfpndvs]\n", prog); printf("\t-c cell id [Default %d]\n", cell.id); + printf("\t-E extended Cyclic prefix [Default %d]\n", cell.cp); printf("\t-f cfi [Default %d]\n", cfi); printf("\t-p cell.nof_prb [Default %d]\n", cell.nof_prb); printf("\t-s number of subframes to simulate [Default %d]\n", nof_subframes); @@ -93,7 +94,7 @@ void parse_args(int argc, char** argv) nof_rx_ant = 2; } - while ((opt = getopt(argc, argv, "cfapndvqstm")) != -1) { + while ((opt = getopt(argc, argv, "cfapndvqstmE")) != -1) { switch (opt) { case 't': transmission_mode = (uint32_t)strtol(argv[optind], NULL, 10) - 1; @@ -120,6 +121,9 @@ void parse_args(int argc, char** argv) case 's': nof_subframes = (uint32_t)strtol(argv[optind], NULL, 10); break; + case 'E': + cell.cp = ((uint32_t)strtol(argv[optind], NULL, 10)) ? SRSRAN_CP_EXT : SRSRAN_CP_NORM; + break; case 'd': print_dci_table = true; break; diff --git a/lib/test/srslog/json_formatter_test.cpp b/lib/test/srslog/json_formatter_test.cpp index 7e3d5a340..bcc9b4210 100644 --- a/lib/test/srslog/json_formatter_test.cpp +++ b/lib/test/srslog/json_formatter_test.cpp @@ -37,7 +37,7 @@ static detail::log_entry_metadata build_log_entry_metadata(fmt::dynamic_format_a store->push_back(88); } - return {tp, {10, true}, "Text %d", store, "ABC", 'Z', small_str_buffer()}; + return {tp, {10, true}, "Text %d", store, "ABC", 'Z'}; } static bool when_fully_filled_log_entry_then_everything_is_formatted() diff --git a/lib/test/srslog/log_backend_test.cpp b/lib/test/srslog/log_backend_test.cpp index 7dfbf4caa..0fd6c79ac 100644 --- a/lib/test/srslog/log_backend_test.cpp +++ b/lib/test/srslog/log_backend_test.cpp @@ -89,7 +89,7 @@ static detail::log_entry build_log_entry(sink* s, fmt::dynamic_format_arg_store< return {s, [](detail::log_entry_metadata&& metadata, fmt::memory_buffer& buffer) {}, - {tp, {0, false}, "Text %d", store, "", '\0', small_str_buffer()}}; + {tp, {0, false}, "Text %d", store, "", '\0'}}; } static bool when_backend_is_not_started_then_pushed_log_entries_are_ignored() diff --git a/lib/test/srslog/log_channel_test.cpp b/lib/test/srslog/log_channel_test.cpp index 73b632cb2..b6806cbc0 100644 --- a/lib/test/srslog/log_channel_test.cpp +++ b/lib/test/srslog/log_channel_test.cpp @@ -279,30 +279,6 @@ static bool when_logging_with_context_and_message_then_filled_in_log_entry_is_pu return true; } -static bool when_logging_with_small_string_then_filled_in_log_entry_is_pushed_into_the_backend() -{ - backend_spy backend; - test_dummies::sink_dummy s; - - log_channel log("id", s, backend); - - small_str_buffer buf; - fmt::format_to(buf, "A {} {} {}", 1, 2, 3); - buf.push_back('\0'); - log(std::move(buf)); - - ASSERT_EQ(backend.push_invocation_count(), 1); - - const detail::log_entry& entry = backend.last_entry(); - ASSERT_EQ(&s, entry.s); - ASSERT_NE(entry.format_func, nullptr); - ASSERT_NE(entry.metadata.tp.time_since_epoch().count(), 0); - ASSERT_EQ(entry.metadata.hex_dump.empty(), true); - ASSERT_EQ(std::string(entry.metadata.small_str.data()), "A 1 2 3"); - - return true; -} - int main() { TEST_FUNCTION(when_log_channel_is_created_then_id_matches_expected_value); @@ -315,7 +291,6 @@ int main() TEST_FUNCTION(when_hex_array_length_is_less_than_hex_log_max_size_then_array_length_is_used); TEST_FUNCTION(when_logging_with_context_then_filled_in_log_entry_is_pushed_into_the_backend); TEST_FUNCTION(when_logging_with_context_and_message_then_filled_in_log_entry_is_pushed_into_the_backend); - TEST_FUNCTION(when_logging_with_small_string_then_filled_in_log_entry_is_pushed_into_the_backend); return 0; } diff --git a/lib/test/srslog/text_formatter_test.cpp b/lib/test/srslog/text_formatter_test.cpp index efb0f1044..d8d21aaa6 100644 --- a/lib/test/srslog/text_formatter_test.cpp +++ b/lib/test/srslog/text_formatter_test.cpp @@ -37,7 +37,7 @@ static detail::log_entry_metadata build_log_entry_metadata(fmt::dynamic_format_a store->push_back(88); } - return {tp, {10, true}, "Text %d", store, "ABC", 'Z', small_str_buffer()}; + return {tp, {10, true}, "Text %d", store, "ABC", 'Z'}; } static bool when_fully_filled_log_entry_then_everything_is_formatted() diff --git a/lib/test/upper/rlc_am_test.cc b/lib/test/upper/rlc_am_test.cc index 31039f186..8df0dbdac 100644 --- a/lib/test/upper/rlc_am_test.cc +++ b/lib/test/upper/rlc_am_test.cc @@ -92,7 +92,7 @@ public: class ul_writer : public thread { public: - ul_writer(rlc_am_lte* rlc_) : rlc(rlc_), running(false), thread("UL_WRITER") {} + ul_writer(rlc_am_lte* rlc_) : rlc(rlc_), thread("UL_WRITER") {} ~ul_writer() { stop(); } void stop() { @@ -128,8 +128,8 @@ private: running = false; } - rlc_am_lte* rlc; - bool running; + rlc_am_lte* rlc = nullptr; + std::atomic running = {false}; }; int basic_test_tx(rlc_am_lte* rlc, byte_buffer_t pdu_bufs[NBUFS]) diff --git a/lib/test/upper/rlc_stress_test.cc b/lib/test/upper/rlc_stress_test.cc index b565303fe..017f6f837 100644 --- a/lib/test/upper/rlc_stress_test.cc +++ b/lib/test/upper/rlc_stress_test.cc @@ -254,7 +254,7 @@ private: pcap->write_ul_ccch(pdu->msg, pdu_len); } } else { - logger.warning(pdu->msg, pdu->N_bytes, "Dropping RLC PDU (%d B)", pdu->N_bytes); + logger.info(pdu->msg, pdu->N_bytes, "Dropping RLC PDU (%d B)", pdu->N_bytes); skip_action = true; // Avoid drop duplicating this PDU } @@ -263,7 +263,7 @@ private: it++; skip_action = false; // Allow action on the next PDU } else { - logger.warning(pdu->msg, pdu->N_bytes, "Duplicating RLC PDU (%d B)", pdu->N_bytes); + logger.info(pdu->msg, pdu->N_bytes, "Duplicating RLC PDU (%d B)", pdu->N_bytes); skip_action = true; // Avoid drop of this PDU } } @@ -308,7 +308,7 @@ private: rlc_interface_mac* rlc1 = nullptr; rlc_interface_mac* rlc2 = nullptr; - bool run_enable = false; + std::atomic run_enable = {false}; stress_test_args_t args = {}; rlc_pcap* pcap = nullptr; uint32_t lcid = 0; @@ -436,7 +436,8 @@ private: } } - bool run_enable = true; + std::atomic run_enable = {true}; + /// Tx uses thread-local PDCP SN to set SDU content, the Rx uses this variable to check received SDUs uint8_t next_expected_sdu = 0; uint64_t rx_pdus = 0; diff --git a/srsenb/enb.conf.example b/srsenb/enb.conf.example index ca2654dbf..937192caa 100644 --- a/srsenb/enb.conf.example +++ b/srsenb/enb.conf.example @@ -332,6 +332,8 @@ enable = false #max_prach_offset_us = 30 #nof_prealloc_ues = 8 #rlf_release_timer_ms = 4000 +#lcid_padding = 3 #eea_pref_list = EEA0, EEA2, EEA1 #eia_pref_list = EIA2, EIA1, EIA0 #gtpu_tunnel_timeout = 0 +#extended_cp = false diff --git a/srsenb/hdr/common/common_enb.h b/srsenb/hdr/common/common_enb.h index e38cec013..431719dda 100644 --- a/srsenb/hdr/common/common_enb.h +++ b/srsenb/hdr/common/common_enb.h @@ -51,6 +51,10 @@ constexpr uint32_t drb_to_lcid(lte_drb drb_id) { return srb_to_lcid(lte_srb::srb2) + static_cast(drb_id); } +constexpr lte_drb lte_lcid_to_drb(uint32_t lcid) +{ + return srsran::is_lte_drb(lcid) ? static_cast(lcid - srb_to_lcid(lte_srb::srb2)) : lte_drb::invalid; +} // Cat 3 UE - Max number of DL-SCH transport block bits received within a TTI // 3GPP 36.306 Table 4.1.1 diff --git a/srsenb/hdr/enb.h b/srsenb/hdr/enb.h index a2a05921d..eb64a7f2a 100644 --- a/srsenb/hdr/enb.h +++ b/srsenb/hdr/enb.h @@ -146,6 +146,8 @@ public: // eNodeB command interface void cmd_cell_gain(uint32_t cell_id, float gain) override; + void toggle_padding() override; + private: const static int ENB_POOL_SIZE = 1024 * 10; diff --git a/srsenb/hdr/phy/phy_interfaces.h b/srsenb/hdr/phy/phy_interfaces.h index 9d7d25228..eb90fc519 100644 --- a/srsenb/hdr/phy/phy_interfaces.h +++ b/srsenb/hdr/phy/phy_interfaces.h @@ -72,7 +72,7 @@ struct phy_args_t { bool pusch_meas_ta = true; bool pucch_meas_ta = true; uint32_t nof_prach_threads = 1; - + bool extended_cp = false; srsran::channel::args_t dl_channel_args; srsran::channel::args_t ul_channel_args; diff --git a/srsenb/hdr/stack/enb_stack_base.h b/srsenb/hdr/stack/enb_stack_base.h index af17cda14..302738def 100644 --- a/srsenb/hdr/stack/enb_stack_base.h +++ b/srsenb/hdr/stack/enb_stack_base.h @@ -101,6 +101,7 @@ public: virtual void stop() = 0; + virtual void toggle_padding() = 0; // eNB metrics interface virtual bool get_metrics(stack_metrics_t* metrics) = 0; }; diff --git a/srsenb/hdr/stack/enb_stack_lte.h b/srsenb/hdr/stack/enb_stack_lte.h index 25ea20f36..4bd5ef8e9 100644 --- a/srsenb/hdr/stack/enb_stack_lte.h +++ b/srsenb/hdr/stack/enb_stack_lte.h @@ -29,11 +29,11 @@ #include "mac/mac.h" #include "rrc/rrc.h" +#include "s1ap/s1ap.h" #include "srsran/common/task_scheduler.h" #include "upper/gtpu.h" #include "upper/pdcp.h" #include "upper/rlc.h" -#include "upper/s1ap.h" #include "enb_stack_base.h" #include "srsran/common/mac_pcap_net.h" @@ -100,6 +100,7 @@ public: { mac.set_sched_dl_tti_mask(tti_mask, nof_sfs); } + void toggle_padding() override { mac.toggle_padding(); } void tti_clock() override; private: diff --git a/srsenb/hdr/stack/gnb_stack_nr.h b/srsenb/hdr/stack/gnb_stack_nr.h index c6edb170f..3e56992e9 100644 --- a/srsenb/hdr/stack/gnb_stack_nr.h +++ b/srsenb/hdr/stack/gnb_stack_nr.h @@ -27,12 +27,12 @@ #ifndef SRSRAN_GNB_STACK_NR_H #define SRSRAN_GNB_STACK_NR_H +#include "s1ap/s1ap.h" #include "srsenb/hdr/stack/mac/mac_nr.h" #include "srsenb/hdr/stack/rrc/rrc_nr.h" #include "srsenb/hdr/stack/upper/pdcp_nr.h" #include "srsenb/hdr/stack/upper/rlc_nr.h" #include "upper/gtpu.h" -#include "upper/s1ap.h" #include "upper/sdap.h" #include "enb_stack_base.h" @@ -80,6 +80,8 @@ public: // MAC interface to trigger processing of received PDUs void process_pdus() final; + void toggle_padding() { srsran::console("padding not available for NR\n"); } + private: void run_thread() final; void run_tti_impl(uint32_t tti); diff --git a/srsenb/hdr/stack/mac/mac.h b/srsenb/hdr/stack/mac/mac.h index 2bcc0c449..d09df903e 100644 --- a/srsenb/hdr/stack/mac/mac.h +++ b/srsenb/hdr/stack/mac/mac.h @@ -103,6 +103,11 @@ public: bool process_pdus(); void get_metrics(mac_metrics_t& metrics); + + void toggle_padding(); + + void add_padding(); + void write_mcch(const srsran::sib2_mbms_t* sib2_, const srsran::sib13_t* sib13_, const srsran::mcch_msg_t* mcch_, @@ -185,8 +190,9 @@ private: uint8_t mtch_payload_buffer[mtch_payload_len] = {}; // pointer to MAC PCAP object - srsran::mac_pcap* pcap = nullptr; - srsran::mac_pcap_net* pcap_net = nullptr; + srsran::mac_pcap* pcap = nullptr; + srsran::mac_pcap_net* pcap_net = nullptr; + bool do_padding = false; // Number of rach preambles detected for a cc. std::vector detected_rachs; diff --git a/srsenb/hdr/stack/mac/sched_common.h b/srsenb/hdr/stack/mac/sched_common.h index 136f29c61..8da4aa3cf 100644 --- a/srsenb/hdr/stack/mac/sched_common.h +++ b/srsenb/hdr/stack/mac/sched_common.h @@ -90,29 +90,6 @@ public: dl_lb_nof_re_table nof_re_lb_table; }; -//! Bitmask used for CCE allocations -using pdcch_mask_t = srsran::bounded_bitset; - -//! Bitmask that stores the allocared DL RBGs -using rbgmask_t = srsran::bounded_bitset<25, true>; - -//! Bitmask that stores the allocated UL PRBs -using prbmask_t = srsran::bounded_bitset<100, true>; - -//! Struct to express a {min,...,max} range of RBGs -struct prb_interval; -struct rbg_interval : public srsran::interval { - using interval::interval; - static rbg_interval rbgmask_to_rbgs(const rbgmask_t& mask); -}; - -/// Struct to express a {min,...,max} range of PRBs -struct prb_interval : public srsran::interval { - using interval::interval; - static prb_interval rbgs_to_prbs(const rbg_interval& rbgs, uint32_t cell_nof_prb); - static prb_interval riv_to_prbs(uint32_t riv, uint32_t nof_prbs, int nof_vrbs = -1); -}; - /// Type of Allocation stored in PDSCH/PUSCH enum class alloc_type_t { DL_BC, DL_PCCH, DL_RAR, DL_DATA, UL_DATA }; inline bool is_dl_ctrl_alloc(alloc_type_t a) @@ -122,13 +99,4 @@ inline bool is_dl_ctrl_alloc(alloc_type_t a) } // namespace srsenb -namespace fmt { - -template <> -struct formatter : public formatter > {}; -template <> -struct formatter : public formatter > {}; - -} // namespace fmt - #endif // SRSRAN_SCHED_COMMON_H diff --git a/srsenb/hdr/stack/mac/sched_helpers.h b/srsenb/hdr/stack/mac/sched_helpers.h index 33adadac8..f63aeeb7f 100644 --- a/srsenb/hdr/stack/mac/sched_helpers.h +++ b/srsenb/hdr/stack/mac/sched_helpers.h @@ -47,74 +47,6 @@ inline uint32_t get_nof_retx(uint32_t rv_idx) return nof_retxs[rv_idx % 4]; } -/// convert cell nof PRBs to nof RBGs -inline uint32_t cell_nof_prb_to_rbg(uint32_t nof_prbs) -{ - switch (nof_prbs) { - case 6: - return 6; - case 15: - return 8; - case 25: - return 13; - case 50: - return 17; - case 75: - return 19; - case 100: - return 25; - default: - srslog::fetch_basic_logger("MAC").error("Provided nof PRBs not valid"); - return 0; - } -} - -/// convert cell nof RBGs to nof PRBs -inline uint32_t cell_nof_rbg_to_prb(uint32_t nof_rbgs) -{ - switch (nof_rbgs) { - case 6: - return 6; - case 8: - return 15; - case 13: - return 25; - case 17: - return 50; - case 19: - return 75; - case 25: - return 100; - default: - srslog::fetch_basic_logger("MAC").error("Provided nof PRBs not valid"); - return 0; - } -} - -/** - * Count number of PRBs present in a DL RBG mask - * @param cell_nof_prb cell nof prbs - * @param P cell ratio prb/rbg - * @param bitmask DL RBG mask - * @return number of prbs - */ -inline uint32_t count_prb_per_tb(const rbgmask_t& bitmask) -{ - uint32_t Nprb = cell_nof_rbg_to_prb(bitmask.size()); - uint32_t P = srsran_ra_type0_P(Nprb); - uint32_t nof_prb = P * bitmask.count(); - if (bitmask.test(bitmask.size() - 1)) { - nof_prb -= bitmask.size() * P - Nprb; - } - return nof_prb; -} - -inline uint32_t count_prb_per_tb_approx(uint32_t nof_rbgs, uint32_t cell_nof_prb) -{ - uint32_t P = srsran_ra_type0_P(cell_nof_prb); - return std::min(nof_rbgs * P, cell_nof_prb); -} - cce_frame_position_table generate_cce_location_table(uint16_t rnti, const sched_cell_params_t& cell_cfg); /** @@ -150,12 +82,6 @@ uint32_t get_aggr_level(uint32_t nof_bits, uint32_t cell_nof_prb, bool use_tbs_index_alt); -/******************************************************* - * RB mask helper functions - *******************************************************/ - -bool is_contiguous(const rbgmask_t& mask); - /******************************************************* * sched_interface helper functions *******************************************************/ diff --git a/srsenb/hdr/stack/mac/sched_phy_ch/sched_dci.h b/srsenb/hdr/stack/mac/sched_phy_ch/sched_dci.h index b0b692e89..dfcc42652 100644 --- a/srsenb/hdr/stack/mac/sched_phy_ch/sched_dci.h +++ b/srsenb/hdr/stack/mac/sched_phy_ch/sched_dci.h @@ -23,6 +23,7 @@ #define SRSRAN_SCHED_DCI_H #include "../sched_common.h" +#include "srsenb/hdr/stack/mac/sched_phy_ch/sched_phy_resource.h" #include "srsran/adt/bounded_vector.h" namespace srsenb { diff --git a/srsenb/hdr/stack/mac/sched_phy_ch/sched_phy_resource.h b/srsenb/hdr/stack/mac/sched_phy_ch/sched_phy_resource.h new file mode 100644 index 000000000..144948750 --- /dev/null +++ b/srsenb/hdr/stack/mac/sched_phy_ch/sched_phy_resource.h @@ -0,0 +1,174 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSRAN_SCHED_PHY_RESOURCE_H +#define SRSRAN_SCHED_PHY_RESOURCE_H + +#include "srsran/adt/bounded_bitset.h" +#include "srsran/adt/interval.h" +#include "srsran/common/srsran_assert.h" +extern "C" { +#include "srsran/phy/phch/ra.h" +} + +// Description: This file defines the types associated with representing the allocation masks/intervals for RBGs, PRBs +// and PDCCH CCEs, and provides some function helpers and algorithms to handle these types. + +constexpr uint32_t MAX_NOF_RBGS = 25; +constexpr uint32_t MAX_NOF_PRBS = 100; +constexpr uint32_t MAX_NOF_CCES = 128; + +namespace srsenb { + +/// convert cell nof PRBs to nof RBGs +inline uint32_t cell_nof_prb_to_rbg(uint32_t nof_prbs) +{ + switch (nof_prbs) { + case 6: + return 6; + case 15: + return 8; + case 25: + return 13; + case 50: + return 17; + case 75: + return 19; + case 100: + return 25; + default: + srsran_terminate("Provided nof PRBs not valid"); + } + return 0; +} + +/// convert cell nof RBGs to nof PRBs +inline uint32_t cell_nof_rbg_to_prb(uint32_t nof_rbgs) +{ + switch (nof_rbgs) { + case 6: + return 6; + case 8: + return 15; + case 13: + return 25; + case 17: + return 50; + case 19: + return 75; + case 25: + return 100; + default: + srsran_terminate("Provided nof PRBs not valid"); + } + return 0; +} + +/// Bitmask used for CCE allocations +using pdcch_mask_t = srsran::bounded_bitset; + +/// Bitmask that stores the allocared DL RBGs +using rbgmask_t = srsran::bounded_bitset; + +/// Bitmask that stores the allocated UL PRBs +using prbmask_t = srsran::bounded_bitset; + +/// Struct to express a {min,...,max} range of RBGs +struct prb_interval; +struct rbg_interval : public srsran::interval { + using interval::interval; + static rbg_interval find_first_interval(const rbgmask_t& mask); + static rbg_interval prbs_to_rbgs(const prb_interval& prbs, uint32_t cell_nof_prb); +}; + +/// Struct to express a {min,...,max} range of PRBs +struct prb_interval : public srsran::interval { + using interval::interval; + static prb_interval rbgs_to_prbs(const rbg_interval& rbgs, uint32_t cell_nof_prb) + { + uint32_t P = srsran_ra_type0_P(cell_nof_prb); + return prb_interval{rbgs.start() * P, std::min(rbgs.stop() * P, cell_nof_prb)}; + } + static prb_interval riv_to_prbs(uint32_t riv, uint32_t nof_prbs, int nof_vrbs = -1); +}; + +inline rbg_interval rbg_interval::prbs_to_rbgs(const prb_interval& prbs, uint32_t cell_nof_prb) +{ + uint32_t P = srsran_ra_type0_P(cell_nof_prb); + return rbg_interval{prbs.start() / P, srsran::ceil_div(prbs.stop(), P)}; +} + +/******************************************************* + * helper functions + *******************************************************/ + +/// If the RBG mask one bits are all contiguous +inline bool is_contiguous(const rbgmask_t& mask) +{ + return rbg_interval::find_first_interval(mask).length() == mask.count(); +} + +/// Count number of PRBs present in a DL RBG mask +inline uint32_t count_prb_per_tb(const rbgmask_t& bitmask) +{ + uint32_t Nprb = cell_nof_rbg_to_prb(bitmask.size()); + uint32_t P = srsran_ra_type0_P(Nprb); + uint32_t nof_prb = P * bitmask.count(); + if (bitmask.test(bitmask.size() - 1)) { + nof_prb -= bitmask.size() * P - Nprb; + } + return nof_prb; +} + +/// Estimate of number of PRBs in DL grant given Nof RBGs +inline uint32_t count_prb_per_tb_approx(uint32_t nof_rbgs, uint32_t cell_nof_prb) +{ + uint32_t P = srsran_ra_type0_P(cell_nof_prb); + return std::min(nof_rbgs * P, cell_nof_prb); +} + +/** + * Finds a contiguous interval of "zeroed"/available RBG resources + * @param max_nof_rbgs maximum number of RBGs + * @param current_mask bitmask of occupied RBGs, used to search for available RBGs + * @return interval with found RBGs. If a valid interval wasn't found, interval.length() == 0 + */ +rbg_interval find_empty_rbg_interval(uint32_t max_nof_rbgs, const rbgmask_t& current_mask); + +/** + * Finds a bitmask of "zeroed"/available RBG resources + * @param max_nof_rbgs maximum number of RBGs + * @param current_mask bitmask of occupied RBGs, used to search for available RBGs + * @return bitmask of found RBGs. If a valid mask wasn't found, bitmask::size() == 0 + */ +rbgmask_t find_available_rbgmask(uint32_t max_nof_rbgs, bool is_contiguous, const rbgmask_t& current_mask); + +/** + * Finds a range of L contiguous PRBs that are empty + * @param L Max length of the requested UL PRBs + * @param current_mask input PRB mask where to search for available PRBs + * @return found interval of PRBs + */ +prb_interval find_contiguous_ul_prbs(uint32_t L, const prbmask_t& current_mask); + +} // namespace srsenb + +namespace fmt { + +template <> +struct formatter : public formatter > {}; +template <> +struct formatter : public formatter > {}; + +} // namespace fmt + +#endif // SRSRAN_SCHED_PHY_RESOURCE_H diff --git a/srsenb/hdr/stack/mac/sched_phy_ch/sched_result.h b/srsenb/hdr/stack/mac/sched_phy_ch/sched_result.h index 248298124..76c73268b 100644 --- a/srsenb/hdr/stack/mac/sched_phy_ch/sched_result.h +++ b/srsenb/hdr/stack/mac/sched_phy_ch/sched_result.h @@ -23,6 +23,7 @@ #define SRSRAN_SCHED_RESULT_H #include "../sched_common.h" +#include "srsenb/hdr/stack/mac/sched_phy_ch/sched_phy_resource.h" namespace srsenb { diff --git a/srsenb/hdr/stack/mac/sched_ue.h b/srsenb/hdr/stack/mac/sched_ue.h index d111e3675..53cb4e9ba 100644 --- a/srsenb/hdr/stack/mac/sched_ue.h +++ b/srsenb/hdr/stack/mac/sched_ue.h @@ -91,10 +91,11 @@ public: /// Get total pending bytes to be transmitted in DL. /// The amount of CEs to transmit depends on whether enb_cc_idx is UE's PCell - uint32_t get_pending_dl_bytes(uint32_t enb_cc_idx); - rbg_interval get_required_dl_rbgs(uint32_t enb_cc_idx); - uint32_t get_pending_dl_rlc_data() const; - uint32_t get_expected_dl_bitrate(uint32_t enb_cc_idx, int nof_rbgs = -1) const; + uint32_t get_pending_dl_bytes(uint32_t enb_cc_idx); + srsran::interval get_requested_dl_bytes(uint32_t enb_cc_idx); + rbg_interval get_required_dl_rbgs(uint32_t enb_cc_idx); + uint32_t get_pending_dl_rlc_data() const; + uint32_t get_expected_dl_bitrate(uint32_t enb_cc_idx, int nof_rbgs = -1) const; uint32_t get_pending_ul_data_total(tti_point tti_tx_ul, int this_enb_cc_idx); uint32_t get_pending_ul_new_data(tti_point tti_tx_ul, int this_enb_cc_idx); @@ -148,8 +149,6 @@ public: bool pusch_enabled(tti_point tti_rx, uint32_t enb_cc_idx, bool needs_pdcch) const; private: - srsran::interval get_requested_dl_bytes(uint32_t enb_cc_idx); - bool is_sr_triggered(); tbs_info allocate_new_dl_mac_pdu(sched_interface::dl_sched_data_t* data, @@ -162,7 +161,7 @@ private: tbs_info compute_mcs_and_tbs(uint32_t enb_cc_idx, tti_point tti_tx_dl, - uint32_t nof_alloc_prbs, + const rbgmask_t& rbgs, uint32_t cfi, const srsran_dci_dl_t& dci); diff --git a/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_dl_cqi.h b/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_dl_cqi.h new file mode 100644 index 000000000..0479f89a9 --- /dev/null +++ b/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_dl_cqi.h @@ -0,0 +1,240 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#ifndef SRSRAN_SCHED_DL_CQI_H +#define SRSRAN_SCHED_DL_CQI_H + +#include "srsenb/hdr/stack/mac/sched_common.h" +#include "srsenb/hdr/stack/mac/sched_helpers.h" +#include "srsenb/hdr/stack/mac/sched_phy_ch/sched_phy_resource.h" +#include "srsran/adt/accumulators.h" +#include "srsran/common/common_lte.h" +#include "srsran/phy/phch/cqi.h" + +namespace srsenb { + +/** + * Class that handles DL CQI state of a given {rnti,sector} + * - The cell bandwidth is divided into J parts. J = f(nof_cell_prbs) + * - UE reports wideband CQI every H.Np msec, where Np is the CQI period and H=JK + 1, where K is configured in RRC + * - Thus, for K==0, only wideband CQI is active + */ +class sched_dl_cqi +{ +public: + sched_dl_cqi(uint32_t cell_nof_prb_, uint32_t K_, uint32_t init_dl_cqi) : + cell_nof_prb(cell_nof_prb_), + cell_nof_rbg(cell_nof_prb_to_rbg(cell_nof_prb_)), + K(K_), + wb_cqi_avg(init_dl_cqi), + bp_list(nof_bandwidth_parts(cell_nof_prb_), bandwidth_part_context(init_dl_cqi)), + subband_cqi(std::max(1, srsran_cqi_hl_get_no_subbands(cell_nof_prb)), 0) + { + srsran_assert(K <= 4, "K=%d outside of {0, 4}", K); + srsran_assert(K == 0 or cell_nof_prb_ > 6, "K > 0 not allowed for nof_prbs=6"); + } + + /// Set K value from upper layers. See TS 36.331, CQI-ReportPeriodic + void set_K(uint32_t K_) + { + srsran_assert(K <= 4, "K=%d outside of {0, 4}", K); + srsran_assert(K == 0 or cell_nof_prb > 6, "K > 0 not allowed for nof_prbs=6"); + K = K_; + } + + /// Update wideband CQI + void cqi_wb_info(tti_point tti, uint32_t cqi_value) + { + if (cqi_value > 0) { + last_pos_cqi_tti = tti; + } + + last_wb_tti = tti; + wb_cqi_avg = static_cast(cqi_value); + } + + /// Update subband CQI for subband "sb_index" + void cqi_sb_info(tti_point tti, uint32_t sb_index, uint32_t cqi_value) + { + if (cqi_value > 0) { + last_pos_cqi_tti = tti; + } + + uint32_t bp_idx = get_bp_index(sb_index); + bp_list[bp_idx].last_feedback_tti = tti; + bp_list[bp_idx].last_cqi_subband_idx = sb_index; + bp_list[bp_idx].cqi_val = static_cast(cqi_value); + + // just cap all sub-bands in the same bandwidth part + srsran::interval interv = get_bp_sb_indexes(bp_idx); + for (uint32_t sb_index2 = interv.start(); sb_index2 < interv.stop(); ++sb_index2) { + subband_cqi[sb_index2] = bp_list[bp_idx].cqi_val; + } + } + + /// Resets CQI to provided value + void reset_cqi(uint32_t dl_cqi) + { + last_pos_cqi_tti = {}; + last_wb_tti = {}; + wb_cqi_avg = dl_cqi; + for (bandwidth_part_context& bp : bp_list) { + bp = bandwidth_part_context(dl_cqi); + } + } + + int get_avg_cqi() const { return get_grant_avg_cqi(rbg_interval(0, cell_nof_rbg)); } + + /// Get CQI of RBG + int get_rbg_cqi(uint32_t rbg) const + { + if (not subband_cqi_enabled()) { + return static_cast(wb_cqi_avg); + } + uint32_t sb_idx = rbg_to_sb_index(rbg); + return get_subband_cqi_(sb_idx); + } + + /// Get average CQI in given RBG interval + int get_grant_avg_cqi(rbg_interval interv) const + { + if (not subband_cqi_enabled()) { + return static_cast(wb_cqi_avg); + } + float cqi = 0; + uint32_t sbstart = rbg_to_sb_index(interv.start()), sbend = rbg_to_sb_index(interv.stop() - 1) + 1; + for (uint32_t sb = sbstart; sb < sbend; ++sb) { + cqi += get_subband_cqi_(sb); + } + return static_cast(cqi / (sbend - sbstart)); + } + + /// Get average CQI in given PRB interval + int get_grant_avg_cqi(prb_interval prb_interv) const + { + return get_grant_avg_cqi(rbg_interval::prbs_to_rbgs(prb_interv, cell_nof_prb)); + } + + /// Get average CQI in given RBG mask + int get_grant_avg_cqi(const rbgmask_t& mask) const + { + if (not subband_cqi_enabled()) { + return static_cast(wb_cqi_avg); + } + float cqi = 0; + uint32_t count = 0; + for (int rbg = mask.find_lowest(0, mask.size()); rbg != -1; rbg = mask.find_lowest(rbg, mask.size())) { + uint32_t sb = rbg_to_sb_index(rbg); + cqi += get_subband_cqi_(sb); + count++; + rbg = static_cast(((sb + 1U) * cell_nof_rbg + N() - 1U) / N()); // skip to next subband index + } + return static_cast(cqi / count); + } + + /// Get CQI-optimal RBG mask with at most "req_rbgs" RBGs + rbgmask_t get_optim_rbgmask(uint32_t req_rbgs, bool max_min_flag = true) const + { + rbgmask_t rbgmask(cell_nof_rbg); + return get_optim_rbgmask(rbgmask, req_rbgs, max_min_flag); + } + rbgmask_t get_optim_rbgmask(const rbgmask_t& dl_mask, uint32_t req_rbgs, bool max_min_flag = true) const; + + /// TS 36.321, 7.2.2 - Parameter N + uint32_t nof_subbands() const { return subband_cqi.size(); } + + /// TS 36.321, 7.2.2 - Parameter J + uint32_t nof_bandwidth_parts() const { return bp_list.size(); } + + bool subband_cqi_enabled() const { return K > 0; } + + bool is_cqi_info_received() const { return last_pos_cqi_tti.is_valid(); } + + tti_point last_cqi_info_tti() const { return last_pos_cqi_tti; } + + int get_wb_cqi_info() const { return wb_cqi_avg; } + + uint32_t rbg_to_sb_index(uint32_t rbg_index) const { return rbg_index * N() / cell_nof_rbg; } + + /// Get CQI of given subband index + int get_subband_cqi(uint32_t subband_index) const + { + if (subband_cqi_enabled()) { + return get_wb_cqi_info(); + } + return bp_list[get_bp_index(subband_index)].last_feedback_tti.is_valid() ? subband_cqi[subband_index] : wb_cqi_avg; + } + +private: + static const uint32_t max_subband_size = 8; + static const uint32_t max_nof_subbands = 13; + static const uint32_t max_bandwidth_parts = 4; + + /// TS 36.321, Table 7.2.2-2 + static uint32_t nof_bandwidth_parts(uint32_t nof_prb) + { + static const uint32_t nrb[] = {0, 2, 2, 3, 4, 4}; + return nrb[srsran::lte_cell_nof_prb_to_index(nof_prb)]; + } + + uint32_t J() const { return nof_bandwidth_parts(); } + uint32_t N() const { return nof_subbands(); } + + uint32_t get_bp_index(uint32_t sb_index) const { return sb_index * J() / N(); } + + uint32_t prb_to_sb_index(uint32_t prb_index) const { return prb_index * N() / cell_nof_prb; } + + srsran::interval get_bp_sb_indexes(uint32_t bp_idx) const + { + return srsran::interval{bp_idx * N() / J(), (bp_idx + 1) * N() / J()}; + } + + float get_subband_cqi_(uint32_t sb_idx) const + { + return bp_list[get_bp_index(sb_idx)].last_feedback_tti.is_valid() ? subband_cqi[sb_idx] : wb_cqi_avg; + } + + uint32_t cell_nof_prb; + uint32_t cell_nof_rbg; + uint32_t K; ///< set in RRC + + /// context of bandwidth part + struct bandwidth_part_context { + tti_point last_feedback_tti{}; + uint32_t last_cqi_subband_idx; + float cqi_val; + + explicit bandwidth_part_context(uint32_t init_dl_cqi) : cqi_val(init_dl_cqi), last_cqi_subband_idx(max_nof_subbands) + {} + }; + + tti_point last_pos_cqi_tti; + + tti_point last_wb_tti; + float wb_cqi_avg; + + srsran::bounded_vector bp_list; + srsran::bounded_vector subband_cqi; +}; + +/// Get {RBG index, CQI} tuple which correspond to the set RBG with the lowest CQI +std::tuple find_min_cqi_rbg(const rbgmask_t& mask, const sched_dl_cqi& dl_cqi); + +/// Returns the same RBG mask, but with the RBGs of the subband with the lowest CQI reset +rbgmask_t remove_min_cqi_subband(const rbgmask_t& rbgmask, const sched_dl_cqi& dl_cqi); + +/// Returns the same RBG mask, but with the RBG with the lowest CQI reset +rbgmask_t remove_min_cqi_rbg(const rbgmask_t& rbgmask, const sched_dl_cqi& dl_cqi); + +} // namespace srsenb + +#endif // SRSRAN_SCHED_DL_CQI_H diff --git a/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_ue_cell.h b/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_ue_cell.h index 9c619bd7f..89100f1ff 100644 --- a/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_ue_cell.h +++ b/srsenb/hdr/stack/mac/sched_ue_ctrl/sched_ue_cell.h @@ -23,6 +23,7 @@ #define SRSRAN_SCHED_UE_CELL_H #include "../sched_common.h" +#include "sched_dl_cqi.h" #include "sched_harq.h" #include "srsenb/hdr/stack/mac/sched_phy_ch/sched_dci.h" #include "tpc.h" @@ -41,7 +42,7 @@ struct sched_ue_cell { void clear_feedback(); void finish_tti(tti_point tti_rx); - void set_dl_cqi(tti_point tti_rx, uint32_t dl_cqi_); + void set_dl_wb_cqi(tti_point tti_rx, uint32_t dl_cqi_); bool configured() const { return ue_cc_idx >= 0; } int get_ue_cc_idx() const { return ue_cc_idx; } @@ -66,23 +67,21 @@ struct sched_ue_cell { tpc tpc_fsm; /// UCI Feedback - uint32_t dl_ri = 0; - tti_point dl_ri_tti_rx{}; - uint32_t dl_pmi = 0; - tti_point dl_pmi_tti_rx{}; - uint32_t dl_cqi = 1; - tti_point dl_cqi_tti_rx{0}; - uint32_t ul_cqi = 1; - tti_point ul_cqi_tti_rx{}; - bool dl_cqi_rx = false; + const sched_dl_cqi& dl_cqi() const { return dl_cqi_ctxt; } + uint32_t dl_ri = 0; + tti_point dl_ri_tti_rx{}; + uint32_t dl_pmi = 0; + tti_point dl_pmi_tti_rx{}; + uint32_t ul_cqi = 1; + tti_point ul_cqi_tti_rx{}; uint32_t max_mcs_dl = 28, max_mcs_ul = 28; uint32_t max_aggr_level = 3; int fixed_mcs_ul = 0, fixed_mcs_dl = 0; private: - srslog::basic_logger& logger; - + // args + srslog::basic_logger& logger; const sched_interface::ue_cfg_t* ue_cfg = nullptr; tti_point cfg_tti; int ue_cc_idx = -1; @@ -90,17 +89,22 @@ private: // state tti_point current_tti; cc_st cc_state_ = cc_st::idle; + + sched_dl_cqi dl_cqi_ctxt; }; /************************************************************* * TBS/MCS derivation ************************************************************/ +/// Compute DL grant optimal TBS and MCS given UE cell context and DL grant parameters tbs_info cqi_to_tbs_dl(const sched_ue_cell& cell, - uint32_t nof_prb, + const rbgmask_t& rbgs, uint32_t nof_re, srsran_dci_format_t dci_format, - int req_bytes = -1); + uint32_t req_bytes = std::numeric_limits::max()); + +/// Compute UL grant optimal TBS and MCS given UE cell context and UL grant parameters tbs_info cqi_to_tbs_ul(const sched_ue_cell& cell, uint32_t nof_prb, uint32_t nof_re, int req_bytes = -1, int explicit_mcs = -1); @@ -110,6 +114,19 @@ int get_required_prb_dl(const sched_ue_cell& cell, uint32_t req_bytes); uint32_t get_required_prb_ul(const sched_ue_cell& cell, uint32_t req_bytes); +tbs_info compute_mcs_and_tbs_lower_bound(const sched_ue_cell& ue_cell, + tti_point tti_tx_dl, + const rbgmask_t& rbg_mask, + srsran_dci_format_t dci_format); + +bool find_optimal_rbgmask(const sched_ue_cell& ue_cell, + tti_point tti_tx_dl, + const rbgmask_t& dl_mask, + srsran_dci_format_t dci_format, + srsran::interval req_bytes, + tbs_info& tb, + rbgmask_t& newtxmask); + } // namespace srsenb #endif // SRSRAN_SCHED_UE_CELL_H diff --git a/srsenb/hdr/stack/mac/schedulers/sched_base.h b/srsenb/hdr/stack/mac/schedulers/sched_base.h index dcf0ec1ce..e43c5e6ca 100644 --- a/srsenb/hdr/stack/mac/schedulers/sched_base.h +++ b/srsenb/hdr/stack/mac/schedulers/sched_base.h @@ -23,6 +23,7 @@ #define SRSRAN_SCHED_BASE_H #include "srsenb/hdr/stack/mac/sched_grid.h" +#include "srsenb/hdr/stack/mac/sched_phy_ch/sched_phy_resource.h" namespace srsenb { @@ -43,25 +44,6 @@ protected: /**************** Helper methods ****************/ -rbg_interval find_empty_rbg_interval(uint32_t max_nof_rbgs, const rbgmask_t& current_mask); - -/** - * Finds a bitmask of available RBG resources for a given UE in a greedy fashion - * @param ue UE being allocated - * @param is_contiguous whether to find a contiguous range of RBGs - * @param current_mask bitmask of occupied RBGs, where to search for available RBGs - * @return bitmask of found RBGs. If a valid mask wasn't found, bitmask::size() == 0 - */ -rbgmask_t compute_rbgmask_greedy(uint32_t max_nof_rbgs, bool is_contiguous, const rbgmask_t& current_mask); - -/** - * Finds a range of L contiguous PRBs that are empty - * @param L Size of the requested UL PRBs - * @param current_mask input prb mask where to search for available PRBs - * @return found interval of PRBs - */ -prb_interval find_contiguous_ul_prbs(uint32_t L, const prbmask_t& current_mask); - const dl_harq_proc* get_dl_retx_harq(sched_ue& user, sf_sched* tti_sched); const dl_harq_proc* get_dl_newtx_harq(sched_ue& user, sf_sched* tti_sched); const ul_harq_proc* get_ul_retx_harq(sched_ue& user, sf_sched* tti_sched); diff --git a/srsenb/hdr/stack/mac/ue.h b/srsenb/hdr/stack/mac/ue.h index 79e8a0f0e..e7e4fddbf 100644 --- a/srsenb/hdr/stack/mac/ue.h +++ b/srsenb/hdr/stack/mac/ue.h @@ -153,6 +153,7 @@ public: void start_ta() { ta_fsm.start(); }; uint32_t set_ta_us(float ta_us) { return ta_fsm.push_value(ta_us); }; void tic(); + void trigger_padding(int lcid); uint8_t* generate_pdu(uint32_t ue_cc_idx, uint32_t harq_pid, diff --git a/srsenb/hdr/stack/upper/ngap.h b/srsenb/hdr/stack/ngap/ngap.h similarity index 100% rename from srsenb/hdr/stack/upper/ngap.h rename to srsenb/hdr/stack/ngap/ngap.h diff --git a/srsenb/hdr/stack/rrc/rrc.h b/srsenb/hdr/stack/rrc/rrc.h index 47aadb295..3adc43133 100644 --- a/srsenb/hdr/stack/rrc/rrc.h +++ b/srsenb/hdr/stack/rrc/rrc.h @@ -129,34 +129,27 @@ public: uint32_t get_nof_users(); // logging - typedef enum { Rx = 0, Tx, toS1AP, fromS1AP } direction_t; + enum direction_t { Rx = 0, Tx, toS1AP, fromS1AP }; template - void log_rrc_message(const std::string& source, - const direction_t dir, - const srsran::byte_buffer_t* pdu, - const T& msg, - const std::string& msg_type) - { - log_rrc_message(source, dir, srsran::make_span(*pdu), msg, msg_type); - } - template - void log_rrc_message(const std::string& source, - const direction_t dir, + void log_rrc_message(const direction_t dir, + uint16_t rnti, + uint32_t lcid, srsran::const_byte_span pdu, const T& msg, - const std::string& msg_type) + const char* msg_type) { - static const char* dir_str[] = {"Rx", "Tx", "S1AP Tx", "S1AP Rx"}; + log_rxtx_pdu_impl(dir, rnti, lcid, pdu, msg_type); if (logger.debug.enabled()) { asn1::json_writer json_writer; msg.to_json(json_writer); - logger.debug( - pdu.data(), pdu.size(), "%s - %s %s (%zd B)", source.c_str(), dir_str[dir], msg_type.c_str(), pdu.size()); logger.debug("Content:\n%s", json_writer.to_string().c_str()); - } else if (logger.info.enabled()) { - logger.info("%s - %s %s (%zd B)", source.c_str(), dir_str[dir], msg_type.c_str(), pdu.size()); } } + template + void log_broadcast_rrc_message(uint16_t rnti, srsran::const_byte_span pdu, const T& msg, const char* msg_type) + { + log_rrc_message(Tx, rnti, -1, pdu, msg, msg_type); + } private: class ue; @@ -185,19 +178,22 @@ private: int pack_mcch(); void config_mac(); - void parse_ul_dcch(uint16_t rnti, uint32_t lcid, srsran::unique_byte_buffer_t pdu); - void parse_ul_ccch(uint16_t rnti, srsran::unique_byte_buffer_t pdu); + void parse_ul_dcch(ue& ue, uint32_t lcid, srsran::unique_byte_buffer_t pdu); + void parse_ul_ccch(ue& ue, srsran::unique_byte_buffer_t pdu); void send_rrc_connection_reject(uint16_t rnti); const static int mcch_payload_len = 3000; int current_mcch_length = 0; uint8_t mcch_payload_buffer[mcch_payload_len] = {}; - typedef struct { + struct rrc_pdu { uint16_t rnti; uint32_t lcid; uint32_t arg; srsran::unique_byte_buffer_t pdu; - } rrc_pdu; + }; + void log_rx_pdu_fail(uint16_t rnti, uint32_t lcid, srsran::const_byte_span pdu, const char* cause); + void + log_rxtx_pdu_impl(direction_t dir, uint16_t rnti, uint32_t lcid, srsran::const_byte_span pdu, const char* msg_type); const static uint32_t LCID_EXIT = 0xffff0000; const static uint32_t LCID_REM_USER = 0xffff0001; diff --git a/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h b/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h index 268fe59db..16a9a5743 100644 --- a/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h +++ b/srsenb/hdr/stack/rrc/rrc_bearer_cfg.h @@ -78,7 +78,8 @@ public: uint32_t teid_in = 0; uint32_t addr = 0; }; - uint8_t id = 0; + uint8_t id = 0; + uint8_t lcid = 0; asn1::s1ap::erab_level_qos_params_s qos_params; asn1::bounded_bitstring<1, 160, true, true> address; uint32_t teid_out = 0; diff --git a/srsenb/hdr/stack/rrc/rrc_paging.h b/srsenb/hdr/stack/rrc/rrc_paging.h index c4ebe7cf8..5a88a4792 100644 --- a/srsenb/hdr/stack/rrc/rrc_paging.h +++ b/srsenb/hdr/stack/rrc/rrc_paging.h @@ -240,7 +240,7 @@ bool paging_manager::read_pdu_pcch(tti_point tti_tx_dl, const Callable& func) } // Call callable for existing PCCH pdu - if (func(*pending_pcch->pdu, pending_pcch->pcch_msg, pending_pcch->tti_tx_dl.is_valid())) { + if (func(*pending_pcch->pdu, pending_pcch->pcch_msg, not pending_pcch->is_tx())) { pending_pcch->tti_tx_dl = tti_tx_dl; return true; } diff --git a/srsenb/hdr/stack/upper/s1ap.h b/srsenb/hdr/stack/s1ap/s1ap.h similarity index 100% rename from srsenb/hdr/stack/upper/s1ap.h rename to srsenb/hdr/stack/s1ap/s1ap.h diff --git a/srsenb/hdr/stack/upper/s1ap_metrics.h b/srsenb/hdr/stack/s1ap/s1ap_metrics.h similarity index 100% rename from srsenb/hdr/stack/upper/s1ap_metrics.h rename to srsenb/hdr/stack/s1ap/s1ap_metrics.h diff --git a/srsenb/hdr/stack/upper/gtpu.h b/srsenb/hdr/stack/upper/gtpu.h index 1fe05bd4e..2e6bd20f2 100644 --- a/srsenb/hdr/stack/upper/gtpu.h +++ b/srsenb/hdr/stack/upper/gtpu.h @@ -63,7 +63,7 @@ public: struct tunnel { uint16_t rnti = SRSRAN_INVALID_RNTI; - uint32_t lcid = srsran::MAX_NOF_BEARERS; + uint32_t lcid = srsran::INVALID_LCID; uint32_t teid_in = 0; uint32_t teid_out = 0; uint32_t spgw_addr = 0; diff --git a/srsenb/src/CMakeLists.txt b/srsenb/src/CMakeLists.txt index 144e00302..1a9acd52e 100644 --- a/srsenb/src/CMakeLists.txt +++ b/srsenb/src/CMakeLists.txt @@ -38,10 +38,10 @@ target_link_libraries(enb_cfg_parser ${LIBCONFIGPP_LIBRARIES}) add_executable(srsenb main.cc enb.cc metrics_stdout.cc metrics_csv.cc metrics_json.cc) -set(SRSENB_SOURCES srsenb_phy srsenb_stack srsenb_common srsenb_upper srsenb_mac srsenb_rrc srslog system) +set(SRSENB_SOURCES srsenb_phy srsenb_stack srsenb_common srsenb_s1ap srsenb_upper srsenb_mac srsenb_rrc srslog system) set(SRSRAN_SOURCES srsran_common srsran_mac srsran_phy srsran_upper srsran_radio rrc_asn1 s1ap_asn1 enb_cfg_parser srslog system) -set(SRSENB_SOURCES ${SRSENB_SOURCES} srsgnb_phy srsgnb_stack srsgnb_upper srsgnb_mac srsgnb_rrc) +set(SRSENB_SOURCES ${SRSENB_SOURCES} srsgnb_phy srsgnb_stack srsgnb_ngap srsgnb_upper srsgnb_mac srsgnb_rrc) set(SRSRAN_SOURCES ${SRSRAN_SOURCES} rrc_nr_asn1 ngap_nr_asn1) target_link_libraries(srsenb ${SRSENB_SOURCES} diff --git a/srsenb/src/enb.cc b/srsenb/src/enb.cc index bff489806..4f7d6f9bc 100644 --- a/srsenb/src/enb.cc +++ b/srsenb/src/enb.cc @@ -236,4 +236,9 @@ std::string enb::get_build_string() return ss.str(); } +void enb::toggle_padding() +{ + stack->toggle_padding(); +} + } // namespace srsenb diff --git a/srsenb/src/enb_cfg_parser.cc b/srsenb/src/enb_cfg_parser.cc index 113c3313c..7f256eba3 100644 --- a/srsenb/src/enb_cfg_parser.cc +++ b/srsenb/src/enb_cfg_parser.cc @@ -834,7 +834,7 @@ namespace enb_conf_sections { int parse_cell_cfg(all_args_t* args_, srsran_cell_t* cell) { cell->frame_type = SRSRAN_FDD; - cell->cp = SRSRAN_CP_NORM; + cell->cp = args_->phy.extended_cp ? SRSRAN_CP_EXT : SRSRAN_CP_NORM; cell->nof_ports = args_->enb.nof_ports; cell->nof_prb = args_->enb.n_prb; // PCI not configured yet diff --git a/srsenb/src/main.cc b/srsenb/src/main.cc index 6cb9a6c92..a2a72decc 100644 --- a/srsenb/src/main.cc +++ b/srsenb/src/main.cc @@ -225,10 +225,12 @@ void parse_args(all_args_t* args, int argc, char* argv[]) ("expert.eea_pref_list", bpo::value(&args->general.eea_pref_list)->default_value("EEA0, EEA2, EEA1"), "Ordered preference list for the selection of encryption algorithm (EEA) (default: EEA0, EEA2, EEA1).") ("expert.eia_pref_list", bpo::value(&args->general.eia_pref_list)->default_value("EIA2, EIA1, EIA0"), "Ordered preference list for the selection of integrity algorithm (EIA) (default: EIA2, EIA1, EIA0).") ("expert.nof_prealloc_ues", bpo::value(&args->stack.mac.nof_prealloc_ues)->default_value(8), "Number of UE resources to preallocate during eNB initialization") + ("expert.lcid_padding", bpo::value(&args->stack.mac.lcid_padding)->default_value(3), "LCID on which to put MAC padding") ("expert.max_mac_dl_kos", bpo::value(&args->general.max_mac_dl_kos)->default_value(100), "Maximum number of consecutive KOs in DL before triggering the UE's release") ("expert.max_mac_ul_kos", bpo::value(&args->general.max_mac_ul_kos)->default_value(100), "Maximum number of consecutive KOs in UL before triggering the UE's release") ("expert.gtpu_tunnel_timeout", bpo::value(&args->stack.gtpu_indirect_tunnel_timeout_msec)->default_value(0), "Maximum time that GTPU takes to release indirect forwarding tunnel since the last received GTPU PDU. (0 for infinity)") ("expert.rlf_release_timer_ms", bpo::value(&args->general.rlf_release_timer_ms)->default_value(4000), "Time taken by eNB to release UE context after it detects an RLF") + ("expert.extended_cp", bpo::value(&args->phy.extended_cp)->default_value(false), "Use extended cyclic prefix") // eMBMS section @@ -437,6 +439,7 @@ void parse_args(all_args_t* args, int argc, char* argv[]) } static bool do_metrics = false; +static bool do_padding = false; static void* input_loop(metrics_stdout* metrics, srsenb::enb_command_interface* control) { @@ -461,6 +464,14 @@ static void* input_loop(metrics_stdout* metrics, srsenb::enb_command_interface* cout << "Enter t to restart trace." << endl; } metrics->toggle_print(do_metrics); + } else if (cmd[0] == "p") { + do_padding = !do_padding; + if (do_padding) { + cout << "Enter p to stop padding." << endl; + } else { + cout << "Enter p to restart padding." << endl; + } + control->toggle_padding(); } else if (cmd[0] == "q") { raise(SIGTERM); } else if (cmd[0] == "cell_gain") { @@ -480,6 +491,7 @@ static void* input_loop(metrics_stdout* metrics, srsenb::enb_command_interface* cout << " t: starts console trace" << endl; cout << " q: quit srsenb" << endl; cout << " cell_gain: set relative cell gain" << endl; + cout << " p: starts MAC padding" << endl; cout << endl; } } diff --git a/srsenb/src/phy/lte/sf_worker.cc b/srsenb/src/phy/lte/sf_worker.cc index 15c1fe73d..b2efd01be 100644 --- a/srsenb/src/phy/lte/sf_worker.cc +++ b/srsenb/src/phy/lte/sf_worker.cc @@ -218,10 +218,6 @@ void sf_worker::work_imp() } } - // Make sure CFI is in the right range - dl_grants[0].cfi = SRSRAN_MAX(dl_grants[0].cfi, 1); - dl_grants[0].cfi = SRSRAN_MIN(dl_grants[0].cfi, 3); - // Get UL scheduling for the TX TTI from MAC if (stack->get_ul_sched(tti_tx_ul, ul_grants_tx) < 0) { Error("Getting UL scheduling from MAC"); @@ -239,7 +235,11 @@ void sf_worker::work_imp() // Process DL for (uint32_t cc = 0; cc < cc_workers.size(); cc++) { + // Select CFI and make sure it is in the right range dl_sf.cfi = dl_grants[cc].cfi; + dl_sf.cfi = SRSRAN_MAX(dl_sf.cfi, 1); + dl_sf.cfi = SRSRAN_MIN(dl_sf.cfi, 3); + cc_workers[cc]->work_dl(dl_sf, dl_grants[cc], ul_grants_tx[cc], &mbsfn_cfg); } diff --git a/srsenb/src/stack/CMakeLists.txt b/srsenb/src/stack/CMakeLists.txt index cb1997ec4..9c47324c1 100644 --- a/srsenb/src/stack/CMakeLists.txt +++ b/srsenb/src/stack/CMakeLists.txt @@ -20,6 +20,8 @@ add_subdirectory(mac) add_subdirectory(rrc) +add_subdirectory(s1ap) +add_subdirectory(ngap) add_subdirectory(upper) set(SOURCES enb_stack_lte.cc) diff --git a/srsenb/src/stack/mac/CMakeLists.txt b/srsenb/src/stack/mac/CMakeLists.txt index 44e2c93c8..8cee4e4ab 100644 --- a/srsenb/src/stack/mac/CMakeLists.txt +++ b/srsenb/src/stack/mac/CMakeLists.txt @@ -21,7 +21,9 @@ add_subdirectory(schedulers) set(SOURCES mac.cc ue.cc sched.cc sched_carrier.cc sched_grid.cc sched_ue_ctrl/sched_harq.cc sched_ue.cc - sched_ue_ctrl/sched_lch.cc sched_ue_ctrl/sched_ue_cell.cc sched_phy_ch/sf_cch_allocator.cc sched_phy_ch/sched_dci.cc sched_helpers.cc) + sched_ue_ctrl/sched_lch.cc sched_ue_ctrl/sched_ue_cell.cc sched_ue_ctrl/sched_dl_cqi.cc + sched_phy_ch/sf_cch_allocator.cc sched_phy_ch/sched_dci.cc sched_phy_ch/sched_phy_resource.cc + sched_helpers.cc) add_library(srsenb_mac STATIC ${SOURCES} $) set(SOURCES mac_nr.cc) diff --git a/srsenb/src/stack/mac/mac.cc b/srsenb/src/stack/mac/mac.cc index 34a83f9b8..23fa04419 100644 --- a/srsenb/src/stack/mac/mac.cc +++ b/srsenb/src/stack/mac/mac.cc @@ -294,6 +294,21 @@ void mac::get_metrics(mac_metrics_t& metrics) metrics.cc_rach_counter = detected_rachs; } +void mac::toggle_padding() +{ + do_padding = !do_padding; +} + +void mac::add_padding() +{ + for (auto it = ue_db.begin(); it != ue_db.end(); ++it) { + uint16_t cur_rnti = it->first; + auto ue = it; + scheduler.dl_rlc_buffer_state(ue->first, args.lcid_padding, 20e6, 0); + ue->second->trigger_padding(args.lcid_padding); + } +} + /******************************************************** * * PHY interface @@ -603,6 +618,9 @@ int mac::get_dl_sched(uint32_t tti_tx_dl, dl_sched_list_t& dl_sched_res_list) trace_complete_event("mac::get_dl_sched", "total_time"); logger.set_context(TTI_SUB(tti_tx_dl, FDD_HARQ_DELAY_UL_MS)); + if (do_padding) { + add_padding(); + } for (uint32_t enb_cc_idx = 0; enb_cc_idx < cell_config.size(); enb_cc_idx++) { // Run scheduler with current info diff --git a/srsenb/src/stack/mac/sched_grid.cc b/srsenb/src/stack/mac/sched_grid.cc index 94c9d200a..57ab4e143 100644 --- a/srsenb/src/stack/mac/sched_grid.cc +++ b/srsenb/src/stack/mac/sched_grid.cc @@ -504,23 +504,25 @@ alloc_result sf_sched::alloc_dl_user(sched_ue* user, const rbgmask_t& user_mask, return alloc_result::no_rnti_opportunity; } - // Check if allocation would cause segmentation + srsran_dci_format_t dci_format = user->get_dci_format(); + if (dci_format == SRSRAN_DCI_FORMAT1A and not is_contiguous(user_mask)) { + logger.warning("SCHED: Can't use distributed RBGs for DCI format 1A"); + return alloc_result::invalid_grant_params; + } + + // Check if allocation is too small to fit headers, BSR or would cause SRB0 segmentation const dl_harq_proc& h = user->get_dl_harq(pid, cc_cfg->enb_cc_idx); if (h.is_empty()) { // It is newTx - rbg_interval r = user->get_required_dl_rbgs(cc_cfg->enb_cc_idx); - if (r.start() > user_mask.count()) { - logger.debug("SCHED: The number of RBGs allocated to rnti=0x%x will force segmentation", user->get_rnti()); + srsran::interval req_bytes = user->get_requested_dl_bytes(get_enb_cc_idx()); + tbs_info tb = compute_mcs_and_tbs_lower_bound(*cc, get_tti_tx_dl(), user_mask, dci_format); + if ((int)req_bytes.start() > tb.tbs_bytes) { + logger.debug("SCHED: The number of RBGs allocated to rnti=0x%x is too small to fit essential control information", + user->get_rnti()); return alloc_result::invalid_grant_params; } } - srsran_dci_format_t dci_format = user->get_dci_format(); - if (dci_format == SRSRAN_DCI_FORMAT1A and not is_contiguous(user_mask)) { - logger.warning("SCHED: Can't use distributed RBGs for DCI format 1A"); - return alloc_result::invalid_grant_params; - } - bool has_pusch_grant = is_ul_alloc(user->get_rnti()) or cc_results->is_ul_alloc(user->get_rnti()); // Check if there is space in the PUCCH for HARQ ACKs diff --git a/srsenb/src/stack/mac/sched_helpers.cc b/srsenb/src/stack/mac/sched_helpers.cc index 8ef449454..be80149bf 100644 --- a/srsenb/src/stack/mac/sched_helpers.cc +++ b/srsenb/src/stack/mac/sched_helpers.cc @@ -161,48 +161,6 @@ void log_phich_cc_results(srslog::basic_logger& logger, } } -prb_interval prb_interval::rbgs_to_prbs(const rbg_interval& rbgs, uint32_t cell_nof_prb) -{ - uint32_t P = srsran_ra_type0_P(cell_nof_prb); - return prb_interval{rbgs.start() * P, std::min(rbgs.stop() * P, cell_nof_prb)}; -} - -rbg_interval rbg_interval::rbgmask_to_rbgs(const rbgmask_t& mask) -{ - int rb_start = -1; - for (uint32_t i = 0; i < mask.size(); i++) { - if (rb_start == -1) { - if (mask.test(i)) { - rb_start = i; - } - } else { - if (!mask.test(i)) { - return rbg_interval(rb_start, i); - } - } - } - if (rb_start != -1) { - return rbg_interval(rb_start, mask.size()); - } else { - return rbg_interval(); - } -} - -prb_interval prb_interval::riv_to_prbs(uint32_t riv, uint32_t nof_prbs, int nof_vrbs) -{ - if (nof_vrbs < 0) { - nof_vrbs = nof_prbs; - } - uint32_t rb_start, l_crb; - srsran_ra_type2_from_riv(riv, &l_crb, &rb_start, nof_prbs, (uint32_t)nof_vrbs); - return {rb_start, rb_start + l_crb}; -} - -bool is_contiguous(const rbgmask_t& mask) -{ - return rbg_interval::rbgmask_to_rbgs(mask).length() == mask.count(); -} - /******************************************************* * Sched Params *******************************************************/ diff --git a/srsenb/src/stack/mac/sched_phy_ch/sched_dci.cc b/srsenb/src/stack/mac/sched_phy_ch/sched_dci.cc index d0687d702..908b4f149 100644 --- a/srsenb/src/stack/mac/sched_phy_ch/sched_dci.cc +++ b/srsenb/src/stack/mac/sched_phy_ch/sched_dci.cc @@ -150,7 +150,7 @@ tbs_info compute_min_mcs_and_tbs_from_required_bytes(uint32_t nof_prb, { // get max MCS/TBS that meets max coderate requirements tbs_info tb_max = compute_mcs_and_tbs(nof_prb, nof_re, cqi, max_mcs, is_ul, ulqam64_enabled, use_tbs_index_alt); - if (tb_max.tbs_bytes + 8 <= (int)req_bytes or tb_max.mcs == 0 or req_bytes <= 0) { + if (tb_max.tbs_bytes + 8 <= (int)req_bytes or tb_max.mcs == 0) { // if mcs cannot be lowered or a decrease in TBS index won't meet req_bytes requirement return tb_max; } diff --git a/srsenb/src/stack/mac/sched_phy_ch/sched_phy_resource.cc b/srsenb/src/stack/mac/sched_phy_ch/sched_phy_resource.cc new file mode 100644 index 000000000..f71e19879 --- /dev/null +++ b/srsenb/src/stack/mac/sched_phy_ch/sched_phy_resource.cc @@ -0,0 +1,129 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsenb/hdr/stack/mac/sched_phy_ch/sched_phy_resource.h" +extern "C" { +#include "lib/include/srsran/phy/dft/dft_precoding.h" +} + +namespace srsenb { + +rbg_interval rbg_interval::find_first_interval(const rbgmask_t& mask) +{ + int rb_start = mask.find_lowest(0, mask.size()); + if (rb_start != -1) { + int rb_end = mask.find_lowest(rb_start + 1, mask.size(), false); + return rbg_interval(rb_start, rb_end < 0 ? mask.size() : rb_end); + } + return rbg_interval(); +} + +prb_interval prb_interval::riv_to_prbs(uint32_t riv, uint32_t nof_prbs, int nof_vrbs) +{ + if (nof_vrbs < 0) { + nof_vrbs = nof_prbs; + } + uint32_t rb_start, l_crb; + srsran_ra_type2_from_riv(riv, &l_crb, &rb_start, nof_prbs, (uint32_t)nof_vrbs); + return {rb_start, rb_start + l_crb}; +} + +template ::value, prb_interval, rbg_interval>::type> +RBInterval find_contiguous_interval(const RBMask& in_mask, uint32_t max_size) +{ + RBInterval max_interv; + + for (size_t n = 0; n < in_mask.size();) { + int pos = in_mask.find_lowest(n, in_mask.size(), false); + if (pos < 0) { + break; + } + + size_t max_pos = std::min(in_mask.size(), (size_t)pos + max_size); + int pos2 = in_mask.find_lowest(pos + 1, max_pos, true); + RBInterval interv(pos, pos2 < 0 ? max_pos : pos2); + if (interv.length() >= max_size) { + return interv; + } + if (interv.length() > max_interv.length()) { + max_interv = interv; + } + n = interv.stop(); + } + return max_interv; +} + +rbgmask_t find_available_rbgmask(const rbgmask_t& in_mask, uint32_t max_size) +{ + // 1's for free RBs + rbgmask_t localmask = ~(in_mask); + + if (max_size >= localmask.size() or max_size >= localmask.count()) { + // shortcut in case rbg count < max_size + return localmask; + } + + uint32_t i = 0, nof_alloc = 0; + for (; i < localmask.size() and nof_alloc < max_size; ++i) { + if (localmask.test(i)) { + nof_alloc++; + } + } + localmask.fill(i, localmask.size(), false); + return localmask; +} + +rbg_interval find_empty_rbg_interval(uint32_t max_nof_rbgs, const rbgmask_t& current_mask) +{ + return find_contiguous_interval(current_mask, max_nof_rbgs); +} + +rbgmask_t find_available_rbgmask(uint32_t max_nof_rbgs, bool is_contiguous, const rbgmask_t& current_mask) +{ + // Allocate enough RBs that accommodate pending data + rbgmask_t newtx_mask(current_mask.size()); + if (is_contiguous) { + rbg_interval interv = find_contiguous_interval(current_mask, max_nof_rbgs); + newtx_mask.fill(interv.start(), interv.stop()); + } else { + newtx_mask = find_available_rbgmask(current_mask, max_nof_rbgs); + } + return newtx_mask; +} + +prb_interval find_contiguous_ul_prbs(uint32_t L, const prbmask_t& current_mask) +{ + prb_interval prb_interv = find_contiguous_interval(current_mask, L); + if (prb_interv.empty()) { + return prb_interv; + } + + // Make sure L is allowed by SC-FDMA modulation + prb_interval prb_interv2 = prb_interv; + while (not srsran_dft_precoding_valid_prb(prb_interv.length()) and prb_interv.stop() < current_mask.size() and + not current_mask.test(prb_interv.stop())) { + prb_interv.resize_by(1); + } + if (not srsran_dft_precoding_valid_prb(prb_interv.length())) { + // if length increase failed, try to decrease + prb_interv = prb_interv2; + prb_interv.resize_by(-1); + while (not srsran_dft_precoding_valid_prb(prb_interv.length()) and not prb_interv.empty()) { + prb_interv.resize_by(-1); + } + } + return prb_interv; +} + +} // namespace srsenb \ No newline at end of file diff --git a/srsenb/src/stack/mac/sched_phy_ch/sf_cch_allocator.cc b/srsenb/src/stack/mac/sched_phy_ch/sf_cch_allocator.cc index 824d1fbc9..8992e2684 100644 --- a/srsenb/src/stack/mac/sched_phy_ch/sf_cch_allocator.cc +++ b/srsenb/src/stack/mac/sched_phy_ch/sf_cch_allocator.cc @@ -81,7 +81,7 @@ bool sf_cch_allocator::alloc_dci(alloc_type_t alloc_type, uint32_t aggr_idx, sch record.alloc_type = alloc_type; record.pusch_uci = has_pusch_grant; - if (is_dl_ctrl_alloc(alloc_type) and nof_allocs() == 0 and cc_cfg->nof_prb() == 6 and + if (is_dl_ctrl_alloc(alloc_type) and nof_allocs() == 0 and cc_cfg->nof_prb() <= 25 and current_max_cfix > current_cfix) { // Given that CFI is not currently dynamic for ctrl allocs, in case of SIB/RAR alloc and a low number of PRBs, // start with an CFI that maximizes nof potential CCE locs diff --git a/srsenb/src/stack/mac/sched_ue.cc b/srsenb/src/stack/mac/sched_ue.cc index 193f75249..4701309d4 100644 --- a/srsenb/src/stack/mac/sched_ue.cc +++ b/srsenb/src/stack/mac/sched_ue.cc @@ -27,8 +27,6 @@ #include "srsran/common/string_helpers.h" #include "srsran/srslog/bundled/fmt/ranges.h" -using srsran::tti_interval; - namespace srsenb { /****************************************************** @@ -44,9 +42,8 @@ namespace srsenb { *******************************************************/ sched_ue::sched_ue(uint16_t rnti_, const std::vector& cell_list_params_, const ue_cfg_t& cfg_) : - logger(srslog::fetch_basic_logger("MAC")) + logger(srslog::fetch_basic_logger("MAC")), rnti(rnti_) { - rnti = rnti_; cells.reserve(cell_list_params_.size()); for (auto& c : cell_list_params_) { cells.emplace_back(rnti_, c, current_tti); @@ -136,11 +133,6 @@ void sched_ue::rem_bearer(uint32_t lc_id) void sched_ue::phy_config_enabled(tti_point tti_rx, bool enabled) { - for (sched_ue_cell& c : cells) { - if (c.configured()) { - c.dl_cqi_tti_rx = tti_rx; - } - } phy_config_dedicated_enabled = enabled; } @@ -301,7 +293,7 @@ void sched_ue::set_dl_pmi(tti_point tti_rx, uint32_t enb_cc_idx, uint32_t pmi) void sched_ue::set_dl_cqi(tti_point tti_rx, uint32_t enb_cc_idx, uint32_t cqi) { if (cells[enb_cc_idx].cc_state() != cc_st::idle) { - cells[enb_cc_idx].set_dl_cqi(tti_rx, cqi); + cells[enb_cc_idx].set_dl_wb_cqi(tti_rx, cqi); } else { logger.warning("Received DL CQI for invalid enb cell index %d", enb_cc_idx); } @@ -339,8 +331,7 @@ tbs_info sched_ue::allocate_new_dl_mac_pdu(sched::dl_sched_data_t* data, uint32_t tb) { srsran_dci_dl_t* dci = &data->dci; - uint32_t nof_prb = count_prb_per_tb(user_mask); - tbs_info tb_info = compute_mcs_and_tbs(enb_cc_idx, tti_tx_dl, nof_prb, cfi, *dci); + tbs_info tb_info = compute_mcs_and_tbs(enb_cc_idx, tti_tx_dl, user_mask, cfi, *dci); // Allocate MAC PDU (subheaders, CEs, and SDUS) int rem_tbs = tb_info.tbs_bytes; @@ -426,7 +417,7 @@ int sched_ue::generate_format1a(uint32_t pid, dci->alloc_type = SRSRAN_RA_ALLOC_TYPE2; dci->type2_alloc.mode = srsran_ra_type2_t::SRSRAN_RA_TYPE2_LOC; - rbg_interval rbg_int = rbg_interval::rbgmask_to_rbgs(user_mask); + rbg_interval rbg_int = rbg_interval::find_first_interval(user_mask); prb_interval prb_int = prb_interval::rbgs_to_prbs(rbg_int, cell.nof_prb); uint32_t L_crb = prb_int.length(); uint32_t RB_start = prb_int.start(); @@ -487,25 +478,25 @@ int sched_ue::generate_format1(uint32_t pid, * Based on the amount of tx data, allocated PRBs, DCI params, etc. compute a valid MCS and resulting TBS * @param enb_cc_idx user carrier index * @param tti_tx_dl tti when the tx will occur - * @param nof_alloc_prbs number of PRBs that were allocated + * @param rbgs RBG mask * @param cfi Number of control symbols in Subframe * @param dci contains the RBG mask, and alloc type * @return pair with MCS and TBS (in bytes) */ tbs_info sched_ue::compute_mcs_and_tbs(uint32_t enb_cc_idx, tti_point tti_tx_dl, - uint32_t nof_alloc_prbs, + const rbgmask_t& rbg_mask, uint32_t cfi, const srsran_dci_dl_t& dci) { - assert(cells[enb_cc_idx].configured()); + srsran_assert(cells[enb_cc_idx].configured(), "computation of MCS/TBS called for non-configured CC"); srsran::interval req_bytes = get_requested_dl_bytes(enb_cc_idx); // Calculate exact number of RE for this PRB allocation uint32_t nof_re = cells[enb_cc_idx].cell_cfg->get_dl_nof_res(tti_tx_dl, dci, cfi); // Compute MCS+TBS - tbs_info tb = cqi_to_tbs_dl(cells[enb_cc_idx], nof_alloc_prbs, nof_re, dci.format, req_bytes.stop()); + tbs_info tb = cqi_to_tbs_dl(cells[enb_cc_idx], rbg_mask, nof_re, dci.format, req_bytes.stop()); if (tb.tbs_bytes > 0 and tb.tbs_bytes < (int)req_bytes.start()) { logger.info("SCHED: Could not get PRB allocation that avoids MAC CE or RLC SRB0 PDU segmentation"); @@ -733,8 +724,8 @@ bool sched_ue::needs_cqi(uint32_t tti, uint32_t enb_cc_idx, bool will_send) bool ret = false; if (phy_config_dedicated_enabled && cfg.supported_cc_list[0].aperiodic_cqi_period && lch_handler.has_pending_dl_txs()) { - uint32_t interval = srsran_tti_interval(tti, cells[enb_cc_idx].dl_cqi_tti_rx.to_uint()); - bool needscqi = interval >= cfg.supported_cc_list[0].aperiodic_cqi_period; + bool needscqi = tti_point(tti) >= + cells[enb_cc_idx].dl_cqi().last_cqi_info_tti() - cfg.supported_cc_list[0].aperiodic_cqi_period; if (needscqi) { uint32_t interval_sent = srsran_tti_interval(tti, cqi_request_tti); if (interval_sent >= 16) { @@ -765,9 +756,8 @@ rbg_interval sched_ue::get_required_dl_rbgs(uint32_t enb_cc_idx) int pending_prbs = get_required_prb_dl(cells[enb_cc_idx], to_tx_dl(current_tti), get_dci_format(), req_bytes.start()); if (pending_prbs < 0) { // Cannot fit allocation in given PRBs - logger.error("SCHED: DL CQI=%d does now allow fitting %d non-segmentable DL tx bytes into the cell bandwidth. " + logger.error("SCHED: DL CQI does now allow fitting %d non-segmentable DL tx bytes into the cell bandwidth. " "Consider increasing initial CQI value.", - cells[enb_cc_idx].dl_cqi, req_bytes.start()); return {cellparams->nof_prb(), cellparams->nof_prb()}; } @@ -855,7 +845,7 @@ uint32_t sched_ue::get_expected_dl_bitrate(uint32_t enb_cc_idx, int nof_rbgs) co auto& cc = cells[enb_cc_idx]; uint32_t nof_re = cc.cell_cfg->get_dl_lb_nof_re(to_tx_dl(current_tti), count_prb_per_tb_approx(nof_rbgs, cc.cell_cfg->nof_prb())); - float max_coderate = srsran_cqi_to_coderate(std::min(cc.dl_cqi + 1u, 15u), cfg.use_tbs_index_alt); + float max_coderate = srsran_cqi_to_coderate(std::min(cc.dl_cqi().get_avg_cqi() + 1u, 15u), cfg.use_tbs_index_alt); // Inverse of srsran_coderate(tbs, nof_re) uint32_t tbs = max_coderate * nof_re - 24; @@ -924,7 +914,7 @@ uint32_t sched_ue::get_pending_ul_data_total(tti_point tti_tx_ul, int this_enb_c uint32_t max_cqi = 0, max_cc_idx = 0; for (uint32_t cc = 0; cc < cells.size(); ++cc) { if (cells[cc].configured()) { - uint32_t sum_cqi = cells[cc].dl_cqi + cells[cc].ul_cqi; + uint32_t sum_cqi = cells[cc].dl_cqi().get_avg_cqi() + cells[cc].ul_cqi; if (cells[cc].cc_state() == cc_st::active and sum_cqi > max_cqi) { max_cqi = sum_cqi; max_cc_idx = cc; @@ -1017,7 +1007,8 @@ std::pair sched_ue::get_active_cell_index(uint32_t enb_cc_idx) c uint32_t sched_ue::get_aggr_level(uint32_t enb_cc_idx, uint32_t nof_bits) { const auto& cc = cells[enb_cc_idx]; - return srsenb::get_aggr_level(nof_bits, cc.dl_cqi, cc.max_aggr_level, cc.cell_cfg->nof_prb(), cfg.use_tbs_index_alt); + return srsenb::get_aggr_level( + nof_bits, cc.dl_cqi().get_avg_cqi(), cc.max_aggr_level, cc.cell_cfg->nof_prb(), cfg.use_tbs_index_alt); } void sched_ue::finish_tti(tti_point tti_rx, uint32_t enb_cc_idx) diff --git a/srsenb/src/stack/mac/sched_ue_ctrl/sched_dl_cqi.cc b/srsenb/src/stack/mac/sched_ue_ctrl/sched_dl_cqi.cc new file mode 100644 index 000000000..76ef6b59a --- /dev/null +++ b/srsenb/src/stack/mac/sched_ue_ctrl/sched_dl_cqi.cc @@ -0,0 +1,105 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsenb/hdr/stack/mac/sched_ue_ctrl/sched_dl_cqi.h" +#include "srsenb/hdr/stack/mac/schedulers/sched_base.h" + +namespace srsenb { + +rbgmask_t sched_dl_cqi::get_optim_rbgmask(const rbgmask_t& dl_mask, uint32_t req_rbgs, bool max_flag) const +{ + req_rbgs = std::min(req_rbgs, cell_nof_rbg); + if (not subband_cqi_enabled()) { + // in case of wideband, just find any available RBGs + return find_available_rbgmask(req_rbgs, false, dl_mask); + } + + rbgmask_t emptymask = ~dl_mask; + if (emptymask.none() or req_rbgs >= emptymask.size() or emptymask.count() <= req_rbgs) { + return emptymask; + } + + srsran::bounded_vector sorted_cqi_pos; + srsran::bounded_vector sorted_cqis; + for (int pos = emptymask.find_lowest(0, emptymask.size(), true); pos >= 0; + pos = emptymask.find_lowest(pos + 1, emptymask.size(), true)) { + sorted_cqis.push_back(get_rbg_cqi(pos)); + sorted_cqi_pos.push_back(pos); + } + std::stable_sort(sorted_cqi_pos.begin(), sorted_cqi_pos.end(), [&sorted_cqis](uint32_t lhs, uint32_t rhs) { + return sorted_cqis[lhs] > sorted_cqis[rhs]; + }); + if (max_flag) { + for (size_t i = req_rbgs; i < sorted_cqi_pos.size(); ++i) { + emptymask.set(sorted_cqi_pos[i], false); + } + } else { + for (size_t i = 0; i < sorted_cqi_pos.size() - req_rbgs; ++i) { + emptymask.set(sorted_cqi_pos[i], false); + } + } + + return emptymask; +} + +std::tuple find_min_cqi_rbg(const rbgmask_t& mask, const sched_dl_cqi& dl_cqi) +{ + if (mask.none()) { + return std::make_tuple(mask.size(), -1); + } + + int rbg = mask.find_lowest(0, mask.size()); + if (not dl_cqi.subband_cqi_enabled()) { + return std::make_tuple(rbg, dl_cqi.get_wb_cqi_info()); + } + + int min_cqi = std::numeric_limits::max(); + uint32_t min_rbg = mask.size(); + for (; rbg != -1; rbg = mask.find_lowest(rbg, mask.size())) { + uint32_t sb = dl_cqi.rbg_to_sb_index(rbg); + int cqi = dl_cqi.get_subband_cqi(sb); + if (cqi < min_cqi) { + min_cqi = cqi; + min_rbg = rbg; + } + rbg = (int)srsran::ceil_div((sb + 1U) * mask.size(), dl_cqi.nof_subbands()); // skip to next subband index + } + return min_cqi != std::numeric_limits::max() ? std::make_tuple(min_rbg, min_cqi) : std::make_tuple(0u, -1); +} + +rbgmask_t remove_min_cqi_subband(const rbgmask_t& rbgmask, const sched_dl_cqi& dl_cqi) +{ + std::tuple tup = find_min_cqi_rbg(rbgmask, dl_cqi); + if (std::get<1>(tup) < 0) { + return rbgmask_t(rbgmask.size()); + } + uint32_t sb = dl_cqi.rbg_to_sb_index(std::get<0>(tup)); + uint32_t rbg_begin = sb * rbgmask.size() / dl_cqi.nof_subbands(); + uint32_t rbg_end = srsran::ceil_div((sb + 1) * rbgmask.size(), dl_cqi.nof_subbands()); + + rbgmask_t ret(rbgmask); + ret.fill(rbg_begin, rbg_end, false); + return ret; +} + +rbgmask_t remove_min_cqi_rbg(const rbgmask_t& rbgmask, const sched_dl_cqi& dl_cqi) +{ + std::tuple tup = find_min_cqi_rbg(rbgmask, dl_cqi); + if (std::get<1>(tup) < 0) { + return rbgmask_t(rbgmask.size()); + } + rbgmask_t ret(rbgmask); + ret.set(std::get<0>(tup), false); + return ret; +} + +} // namespace srsenb \ No newline at end of file diff --git a/srsenb/src/stack/mac/sched_ue_ctrl/sched_ue_cell.cc b/srsenb/src/stack/mac/sched_ue_ctrl/sched_ue_cell.cc index 0a0b1415a..07fd64298 100644 --- a/srsenb/src/stack/mac/sched_ue_ctrl/sched_ue_cell.cc +++ b/srsenb/src/stack/mac/sched_ue_ctrl/sched_ue_cell.cc @@ -22,6 +22,7 @@ #include "srsenb/hdr/stack/mac/sched_ue_ctrl/sched_ue_cell.h" #include "srsenb/hdr/stack/mac/sched_helpers.h" #include "srsenb/hdr/stack/mac/sched_phy_ch/sched_dci.h" +#include "srsenb/hdr/stack/mac/schedulers/sched_base.h" #include namespace srsenb { @@ -43,7 +44,8 @@ sched_ue_cell::sched_ue_cell(uint16_t rnti_, const sched_cell_params_t& cell_cfg fixed_mcs_dl(cell_cfg_.sched_cfg->pdsch_mcs), fixed_mcs_ul(cell_cfg_.sched_cfg->pusch_mcs), current_tti(current_tti_), - max_aggr_level(cell_cfg_.sched_cfg->max_aggr_level >= 0 ? cell_cfg_.sched_cfg->max_aggr_level : 3) + max_aggr_level(cell_cfg_.sched_cfg->max_aggr_level >= 0 ? cell_cfg_.sched_cfg->max_aggr_level : 3), + dl_cqi_ctxt(cell_cfg_.nof_prb(), 0, 1) { clear_feedback(); } @@ -62,6 +64,7 @@ void sched_ue_cell::set_ue_cfg(const sched_interface::ue_cfg_t& ue_cfg_) } } if (ue_cc_idx < 0 and prev_ue_cc_idx < 0) { + // CC was inactive and remain inactive return; } @@ -75,6 +78,13 @@ void sched_ue_cell::set_ue_cfg(const sched_interface::ue_cfg_t& ue_cfg_) max_mcs_dl = std::min(max_mcs_dl, 27U); } + if (ue_cc_idx >= 0) { + const auto& cc = ue_cfg_.supported_cc_list[ue_cc_idx]; + if (cc.dl_cfg.cqi_report.periodic_configured) { + dl_cqi_ctxt.set_K(cc.dl_cfg.cqi_report.subband_wideband_ratio); + } + } + // If new cell configuration, clear Cell HARQs if (ue_cc_idx != prev_ue_cc_idx) { clear_feedback(); @@ -92,15 +102,17 @@ void sched_ue_cell::set_ue_cfg(const sched_interface::ue_cfg_t& ue_cfg_) case cc_st::active: if (ue_cc_idx < 0 or not ue_cfg->supported_cc_list[ue_cc_idx].active) { cc_state_ = cc_st::deactivating; - logger.info("SCHED: Deactivating rnti=0x%x, SCellIndex=%d...", rnti, ue_cc_idx); + logger.info( + "SCHED: Deactivating SCell, rnti=0x%x, cc=%d, SCellIndex=%d...", rnti, cell_cfg->enb_cc_idx, ue_cc_idx); } break; case cc_st::deactivating: case cc_st::idle: if (ue_cc_idx > 0 and ue_cfg->supported_cc_list[ue_cc_idx].active) { cc_state_ = cc_st::activating; - dl_cqi = 0; - logger.info("SCHED: Activating rnti=0x%x, SCellIndex=%d...", rnti, ue_cc_idx); + dl_cqi_ctxt.reset_cqi(0); + logger.info( + "SCHED: Activating SCell, rnti=0x%x, cc=%d, SCellIndex=%d...", rnti, cell_cfg->enb_cc_idx, ue_cc_idx); } break; default: @@ -130,9 +142,7 @@ void sched_ue_cell::clear_feedback() dl_ri_tti_rx = tti_point{}; dl_pmi = 0; dl_pmi_tti_rx = tti_point{}; - dl_cqi = ue_cc_idx == 0 ? cell_cfg->cfg.initial_dl_cqi : 1; - dl_cqi_tti_rx = tti_point{}; - dl_cqi_rx = false; + dl_cqi_ctxt.reset_cqi(ue_cc_idx == 0 ? cell_cfg->cfg.initial_dl_cqi : 1); ul_cqi = 1; ul_cqi_tti_rx = tti_point{}; } @@ -143,12 +153,10 @@ void sched_ue_cell::finish_tti(tti_point tti_rx) harq_ent.reset_pending_data(tti_rx); } -void sched_ue_cell::set_dl_cqi(tti_point tti_rx, uint32_t dl_cqi_) +void sched_ue_cell::set_dl_wb_cqi(tti_point tti_rx, uint32_t dl_cqi_) { - dl_cqi = dl_cqi_; - dl_cqi_tti_rx = tti_rx; - dl_cqi_rx = dl_cqi_rx or dl_cqi > 0; - if (ue_cc_idx > 0 and cc_state_ == cc_st::activating and dl_cqi_rx) { + dl_cqi_ctxt.cqi_wb_info(tti_rx, dl_cqi_); + if (ue_cc_idx > 0 and cc_state_ == cc_st::activating and dl_cqi_ > 0) { // Wait for SCell to receive a positive CQI before activating it cc_state_ = cc_st::active; logger.info("SCHED: SCell index=%d is now active", ue_cc_idx); @@ -233,29 +241,32 @@ std::tuple false_position_method(int x1, int x2, YType y } tbs_info cqi_to_tbs_dl(const sched_ue_cell& cell, - uint32_t nof_prb, + const rbgmask_t& rbgs, uint32_t nof_re, srsran_dci_format_t dci_format, - int req_bytes) + uint32_t req_bytes) { - bool use_tbs_index_alt = cell.get_ue_cfg()->use_tbs_index_alt and dci_format != SRSRAN_DCI_FORMAT1A; + bool use_tbs_index_alt = cell.get_ue_cfg()->use_tbs_index_alt and dci_format != SRSRAN_DCI_FORMAT1A; + uint32_t nof_prbs = count_prb_per_tb(rbgs); tbs_info ret; - if (cell.fixed_mcs_dl < 0 or not cell.dl_cqi_rx) { - // Dynamic MCS + if (cell.fixed_mcs_dl < 0 or not cell.dl_cqi().is_cqi_info_received()) { + // Dynamic MCS configured or first Tx + uint32_t dl_cqi = std::get<1>(find_min_cqi_rbg(rbgs, cell.dl_cqi())); + ret = compute_min_mcs_and_tbs_from_required_bytes( - nof_prb, nof_re, cell.dl_cqi, cell.max_mcs_dl, req_bytes, false, false, use_tbs_index_alt); + nof_prbs, nof_re, dl_cqi, cell.max_mcs_dl, req_bytes, false, false, use_tbs_index_alt); // If coderate > SRSRAN_MIN(max_coderate, 0.932 * Qm) we should set TBS=0. We don't because it's not correctly // handled by the scheduler, but we might be scheduling undecodable codewords at very low SNR if (ret.tbs_bytes < 0) { ret.mcs = 0; - ret.tbs_bytes = get_tbs_bytes((uint32_t)ret.mcs, nof_prb, use_tbs_index_alt, false); + ret.tbs_bytes = get_tbs_bytes((uint32_t)ret.mcs, nof_prbs, use_tbs_index_alt, false); } } else { - // Fixed MCS + // Fixed MCS configured ret.mcs = cell.fixed_mcs_dl; - ret.tbs_bytes = get_tbs_bytes((uint32_t)cell.fixed_mcs_dl, nof_prb, use_tbs_index_alt, false); + ret.tbs_bytes = get_tbs_bytes((uint32_t)cell.fixed_mcs_dl, nof_prbs, use_tbs_index_alt, false); } return ret; } @@ -293,8 +304,9 @@ int get_required_prb_dl(const sched_ue_cell& cell, uint32_t req_bytes) { auto compute_tbs_approx = [tti_tx_dl, &cell, dci_format](uint32_t nof_prb) { - uint32_t nof_re = cell.cell_cfg->get_dl_lb_nof_re(tti_tx_dl, nof_prb); - tbs_info tb = cqi_to_tbs_dl(cell, nof_prb, nof_re, dci_format, -1); + uint32_t nof_re = cell.cell_cfg->get_dl_lb_nof_re(tti_tx_dl, nof_prb); + rbgmask_t min_cqi_rbgs = cell.dl_cqi().get_optim_rbgmask(nof_prb, false); + tbs_info tb = cqi_to_tbs_dl(cell, min_cqi_rbgs, nof_re, dci_format); return tb.tbs_bytes; }; @@ -338,4 +350,101 @@ uint32_t get_required_prb_ul(const sched_ue_cell& cell, uint32_t req_bytes) return req_prbs; } +/// Computes the minimum TBS/MCS achievable for provided UE cell configuration, RBG mask, TTI, DCI format +tbs_info compute_mcs_and_tbs_lower_bound(const sched_ue_cell& ue_cell, + tti_point tti_tx_dl, + const rbgmask_t& rbg_mask, + srsran_dci_format_t dci_format) +{ + uint32_t nof_prbs = count_prb_per_tb(rbg_mask); + if (nof_prbs == 0) { + return tbs_info{}; + } + uint32_t nof_re_lb = ue_cell.cell_cfg->get_dl_lb_nof_re(tti_tx_dl, nof_prbs); + return cqi_to_tbs_dl(ue_cell, rbg_mask, nof_re_lb, dci_format); +} + +bool find_optimal_rbgmask(const sched_ue_cell& ue_cell, + tti_point tti_tx_dl, + const rbgmask_t& dl_mask, + srsran_dci_format_t dci_format, + srsran::interval req_bytes, + tbs_info& tb, + rbgmask_t& newtxmask) +{ + // Find the largest set of available RBGs possible + newtxmask = find_available_rbgmask(dl_mask.size(), dci_format == SRSRAN_DCI_FORMAT1A, dl_mask); + + // Compute MCS/TBS if all available RBGs were allocated + tb = compute_mcs_and_tbs_lower_bound(ue_cell, tti_tx_dl, newtxmask, dci_format); + + if (not ue_cell.dl_cqi().subband_cqi_enabled()) { + // Wideband CQI case + // NOTE: for wideband CQI, the TBS is directly proportional to the nof_prbs, so we can use an iterative method + // to compute the best mask given "req_bytes" + + if (tb.tbs_bytes < (int)req_bytes.start()) { + // the grant is too small. it may lead to srb0 segmentation or not space for headers + return false; + } + if (tb.tbs_bytes <= (int)req_bytes.stop()) { + // the grant is not sufficiently large to fit max required bytes. Stop search at this point + return true; + } + // Reduce DL grant size to the minimum that can fit the pending DL bytes + srsran::bounded_vector tb_table(newtxmask.count()); + auto compute_tbs_approx = [tti_tx_dl, &ue_cell, dci_format, &tb_table](uint32_t nof_rbgs) { + rbgmask_t search_mask(ue_cell.cell_cfg->nof_rbgs); + search_mask.fill(0, nof_rbgs); + tb_table[nof_rbgs - 1] = compute_mcs_and_tbs_lower_bound(ue_cell, tti_tx_dl, search_mask, dci_format); + return tb_table[nof_rbgs - 1].tbs_bytes; + }; + std::tuple ret = false_position_method( + 1U, tb_table.size(), (int)req_bytes.stop(), compute_tbs_approx, [](int y) { return y == SRSRAN_ERROR; }); + uint32_t upper_nrbg = std::get<2>(ret); + int upper_tbs = std::get<3>(ret); + if (upper_tbs >= (int)req_bytes.stop()) { + tb = tb_table[upper_nrbg - 1]; + int pos = 0; + for (uint32_t n_rbgs = newtxmask.count(); n_rbgs > upper_nrbg; --n_rbgs) { + pos = newtxmask.find_lowest(pos + 1, newtxmask.size()); + } + newtxmask.from_uint64(~((1U << (uint64_t)pos) - 1U) & ((1U << newtxmask.size()) - 1U)); + } + return true; + } + + // Subband CQI case + // NOTE: There is no monotonically increasing guarantee between TBS and nof allocated prbs. + // One single subband CQI could be dropping the CQI of the whole TB. + // We start with largest RBG allocation and continue removing RBGs. However, there is no guarantee this is + // going to be the optimal solution + + // Subtract whole CQI subbands until objective is not met + // TODO: can be optimized + rbgmask_t smaller_mask; + tbs_info tb2; + do { + smaller_mask = remove_min_cqi_subband(newtxmask, ue_cell.dl_cqi()); + tb2 = compute_mcs_and_tbs_lower_bound(ue_cell, tti_tx_dl, smaller_mask, dci_format); + if (tb2.tbs_bytes >= (int)req_bytes.stop() or tb.tbs_bytes <= tb2.tbs_bytes) { + tb = tb2; + newtxmask = smaller_mask; + } + } while (tb2.tbs_bytes > (int)req_bytes.stop()); + if (tb.tbs_bytes <= (int)req_bytes.stop()) { + return true; + } + do { + smaller_mask = remove_min_cqi_rbg(newtxmask, ue_cell.dl_cqi()); + tb2 = compute_mcs_and_tbs_lower_bound(ue_cell, tti_tx_dl, smaller_mask, dci_format); + if (tb2.tbs_bytes >= (int)req_bytes.stop() or tb.tbs_bytes <= tb2.tbs_bytes) { + tb = tb2; + newtxmask = smaller_mask; + } + } while (tb2.tbs_bytes > (int)req_bytes.stop()); + + return true; +} + } // namespace srsenb diff --git a/srsenb/src/stack/mac/schedulers/sched_base.cc b/srsenb/src/stack/mac/schedulers/sched_base.cc index 50cde80c5..0d0bdf963 100644 --- a/srsenb/src/stack/mac/schedulers/sched_base.cc +++ b/srsenb/src/stack/mac/schedulers/sched_base.cc @@ -23,74 +23,6 @@ namespace srsenb { -/********************************* - * Common UL/DL Helper methods - ********************************/ - -template ::value, prb_interval, rbg_interval>::type> -RBInterval find_contiguous_interval(const RBMask& in_mask, uint32_t max_size) -{ - RBInterval max_interv; - - for (size_t n = 0; n < in_mask.size();) { - int pos = in_mask.find_lowest(n, in_mask.size(), false); - if (pos < 0) { - break; - } - - size_t max_pos = std::min(in_mask.size(), (size_t)pos + max_size); - int pos2 = in_mask.find_lowest(pos, max_pos, true); - RBInterval interv(pos, pos2 < 0 ? max_pos : pos2); - if (interv.length() >= max_size) { - return interv; - } - if (interv.length() > max_interv.length()) { - max_interv = interv; - } - n = interv.stop(); - } - return max_interv; -} - -/**************************** - * DL Helper methods - ***************************/ - -rbgmask_t find_available_rb_mask(const rbgmask_t& in_mask, uint32_t max_size) -{ - // 1's for free RBs - rbgmask_t localmask = ~(in_mask); - - uint32_t i = 0, nof_alloc = 0; - for (; i < localmask.size() and nof_alloc < max_size; ++i) { - if (localmask.test(i)) { - nof_alloc++; - } - } - localmask.fill(i, localmask.size(), false); - return localmask; -} - -rbg_interval find_empty_rbg_interval(uint32_t max_nof_rbgs, const rbgmask_t& current_mask) -{ - return find_contiguous_interval(current_mask, max_nof_rbgs); -} - -rbgmask_t compute_rbgmask_greedy(uint32_t max_nof_rbgs, bool is_contiguous, const rbgmask_t& current_mask) -{ - // Allocate enough RBs that accommodate pending data - rbgmask_t newtx_mask(current_mask.size()); - if (is_contiguous) { - rbg_interval interv = find_contiguous_interval(current_mask, max_nof_rbgs); - newtx_mask.fill(interv.start(), interv.stop()); - } else { - newtx_mask = find_available_rb_mask(current_mask, max_nof_rbgs); - } - return newtx_mask; -} - int get_ue_cc_idx_if_pdsch_enabled(const sched_ue& user, sf_sched* tti_sched) { // Do not allocate a user multiple times in the same tti @@ -137,7 +69,7 @@ alloc_result try_dl_retx_alloc(sf_sched& tti_sched, sched_ue& ue, const dl_harq_ // If previous mask does not fit, find another with exact same number of rbgs size_t nof_rbg = retx_mask.count(); bool is_contiguous_alloc = ue.get_dci_format() == SRSRAN_DCI_FORMAT1A; - retx_mask = compute_rbgmask_greedy(nof_rbg, is_contiguous_alloc, tti_sched.get_dl_mask()); + retx_mask = find_available_rbgmask(nof_rbg, is_contiguous_alloc, tti_sched.get_dl_mask()); if (retx_mask.count() == nof_rbg) { return tti_sched.alloc_dl_user(&ue, retx_mask, h.get_id()); } @@ -157,22 +89,25 @@ alloc_result try_dl_newtx_alloc_greedy(sf_sched& tti_sched, sched_ue& ue, const } // If there is no data to transmit, no need to allocate - rbg_interval req_rbgs = ue.get_required_dl_rbgs(tti_sched.get_enb_cc_idx()); - if (req_rbgs.stop() == 0) { + srsran::interval req_bytes = ue.get_requested_dl_bytes(tti_sched.get_enb_cc_idx()); + if (req_bytes.stop() == 0) { return alloc_result::no_rnti_opportunity; } - // Find RBG mask that accommodates pending data - bool is_contiguous_alloc = ue.get_dci_format() == SRSRAN_DCI_FORMAT1A; - rbgmask_t newtxmask = compute_rbgmask_greedy(req_rbgs.stop(), is_contiguous_alloc, current_mask); - if (newtxmask.none() or newtxmask.count() < req_rbgs.start()) { + sched_ue_cell* ue_cell = ue.find_ue_carrier(tti_sched.get_enb_cc_idx()); + srsran_assert(ue_cell != nullptr, "dl newtx alloc called for invalid cell"); + srsran_dci_format_t dci_format = ue.get_dci_format(); + tbs_info tb; + rbgmask_t opt_mask; + if (not find_optimal_rbgmask( + *ue_cell, tti_sched.get_tti_tx_dl(), current_mask, dci_format, req_bytes, tb, opt_mask)) { return alloc_result::no_sch_space; } // empty RBGs were found. Attempt allocation - alloc_result ret = tti_sched.alloc_dl_user(&ue, newtxmask, h.get_id()); + alloc_result ret = tti_sched.alloc_dl_user(&ue, opt_mask, h.get_id()); if (ret == alloc_result::success and result_mask != nullptr) { - *result_mask = newtxmask; + *result_mask = opt_mask; } return ret; } @@ -181,30 +116,6 @@ alloc_result try_dl_newtx_alloc_greedy(sf_sched& tti_sched, sched_ue& ue, const * UL Helpers ****************/ -prb_interval find_contiguous_ul_prbs(uint32_t L, const prbmask_t& current_mask) -{ - prb_interval prb_interv = find_contiguous_interval(current_mask, L); - if (prb_interv.empty()) { - return prb_interv; - } - - // Make sure L is allowed by SC-FDMA modulation - prb_interval prb_interv2 = prb_interv; - while (not srsran_dft_precoding_valid_prb(prb_interv.length()) and prb_interv.stop() < current_mask.size() and - not current_mask.test(prb_interv.stop())) { - prb_interv.resize_by(1); - } - if (not srsran_dft_precoding_valid_prb(prb_interv.length())) { - // if length increase failed, try to decrease - prb_interv = prb_interv2; - prb_interv.resize_by(-1); - while (not srsran_dft_precoding_valid_prb(prb_interv.length()) and not prb_interv.empty()) { - prb_interv.resize_by(-1); - } - } - return prb_interv; -} - int get_ue_cc_idx_if_pusch_enabled(const sched_ue& user, sf_sched* tti_sched, bool needs_pdcch) { // Do not allocate a user multiple times in the same tti diff --git a/srsenb/src/stack/mac/ue.cc b/srsenb/src/stack/mac/ue.cc index d8d82b82e..682611194 100644 --- a/srsenb/src/stack/mac/ue.cc +++ b/srsenb/src/stack/mac/ue.cc @@ -741,4 +741,9 @@ void ue::tic() } } +void ue::trigger_padding(int lcid) +{ + sched->ul_bsr(rnti, lcid, 20e6); +} + } // namespace srsenb diff --git a/srsenb/src/stack/ngap/CMakeLists.txt b/srsenb/src/stack/ngap/CMakeLists.txt new file mode 100644 index 000000000..fd4c1f8bd --- /dev/null +++ b/srsenb/src/stack/ngap/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# Copyright 2013-2021 Software Radio Systems Limited +# +# By using this file, you agree to the terms and conditions set +# forth in the LICENSE file which can be found at the top level of +# the distribution. +# + +set(SOURCES ngap.cc) +add_library(srsgnb_ngap STATIC ${SOURCES}) diff --git a/srsenb/src/stack/upper/ngap.cc b/srsenb/src/stack/ngap/ngap.cc similarity index 99% rename from srsenb/src/stack/upper/ngap.cc rename to srsenb/src/stack/ngap/ngap.cc index 534d88fff..417ad9cb3 100644 --- a/srsenb/src/stack/upper/ngap.cc +++ b/srsenb/src/stack/ngap/ngap.cc @@ -19,7 +19,7 @@ * */ -#include "srsenb/hdr/stack/upper/ngap.h" +#include "srsenb/hdr/stack/ngap/ngap.h" #define procError(fmt, ...) ngap_ptr->logger.error("Proc \"%s\" - " fmt, name(), ##__VA_ARGS__) #define procWarning(fmt, ...) ngap_ptr->logger.warning("Proc \"%s\" - " fmt, name(), ##__VA_ARGS__) diff --git a/srsenb/src/stack/rrc/mac_controller.cc b/srsenb/src/stack/rrc/mac_controller.cc index e4ed7d066..9bc889207 100644 --- a/srsenb/src/stack/rrc/mac_controller.cc +++ b/srsenb/src/stack/rrc/mac_controller.cc @@ -352,9 +352,14 @@ void ue_cfg_apply_phy_cfg_ded(ue_cfg_t& ue_cfg, const asn1::rrc::phys_cfg_ded_s& auto& pcell_cfg = ue_cfg.supported_cc_list[0]; if (phy_cfg.cqi_report_cfg_present) { if (phy_cfg.cqi_report_cfg.cqi_report_periodic_present) { - auto& cqi_cfg = phy_cfg.cqi_report_cfg.cqi_report_periodic.setup(); - ue_cfg.pucch_cfg.n_pucch = cqi_cfg.cqi_pucch_res_idx; - pcell_cfg.dl_cfg.cqi_report.pmi_idx = cqi_cfg.cqi_pmi_cfg_idx; + const auto& cqi_cfg = phy_cfg.cqi_report_cfg.cqi_report_periodic.setup(); + ue_cfg.pucch_cfg.n_pucch = cqi_cfg.cqi_pucch_res_idx; + pcell_cfg.dl_cfg.cqi_report.pmi_idx = cqi_cfg.cqi_pmi_cfg_idx; + pcell_cfg.dl_cfg.cqi_report.subband_wideband_ratio = 0; + if (cqi_cfg.cqi_format_ind_periodic.type().value == + cqi_report_periodic_c::setup_s_::cqi_format_ind_periodic_c_::types_opts::subband_cqi) { + pcell_cfg.dl_cfg.cqi_report.subband_wideband_ratio = cqi_cfg.cqi_format_ind_periodic.subband_cqi().k; + } pcell_cfg.dl_cfg.cqi_report.periodic_configured = true; } else if (phy_cfg.cqi_report_cfg.cqi_report_mode_aperiodic_present) { pcell_cfg.aperiodic_cqi_period = rrc_cfg.cqi_cfg.period; @@ -456,9 +461,10 @@ void ue_cfg_apply_reconf_complete_updates(ue_cfg_t& ue_cfg, if (ul_cfg.cqi_report_cfg_scell_r10.cqi_report_periodic_scell_r10_present and ul_cfg.cqi_report_cfg_scell_r10.cqi_report_periodic_scell_r10.type().value == setup_opts::setup) { // periodic CQI - auto& periodic = ul_cfg.cqi_report_cfg_scell_r10.cqi_report_periodic_scell_r10.setup(); - mac_scell.dl_cfg.cqi_report.periodic_configured = true; - mac_scell.dl_cfg.cqi_report.pmi_idx = periodic.cqi_pmi_cfg_idx; + const auto& periodic = ul_cfg.cqi_report_cfg_scell_r10.cqi_report_periodic_scell_r10.setup(); + mac_scell.dl_cfg.cqi_report.periodic_configured = true; + mac_scell.dl_cfg.cqi_report.pmi_idx = periodic.cqi_pmi_cfg_idx; + mac_scell.dl_cfg.cqi_report.subband_wideband_ratio = 0; } else if (ul_cfg.cqi_report_cfg_scell_r10.cqi_report_mode_aperiodic_r10_present) { // aperiodic CQI mac_scell.dl_cfg.cqi_report.aperiodic_configured = diff --git a/srsenb/src/stack/rrc/rrc.cc b/srsenb/src/stack/rrc/rrc.cc index af8775d8a..f0a23a184 100644 --- a/srsenb/src/stack/rrc/rrc.cc +++ b/srsenb/src/stack/rrc/rrc.cc @@ -27,6 +27,7 @@ #include "srsran/asn1/rrc_utils.h" #include "srsran/common/bcd_helpers.h" #include "srsran/common/standard_streams.h" +#include "srsran/common/string_helpers.h" #include "srsran/interfaces/enb_mac_interfaces.h" #include "srsran/interfaces/enb_pdcp_interfaces.h" #include "srsran/interfaces/enb_rlc_interfaces.h" @@ -270,9 +271,8 @@ void rrc::send_rrc_connection_reject(uint16_t rnti) } pdu->N_bytes = bref.distance_bytes(); - char buf[32] = {}; - sprintf(buf, "SRB0 - rnti=0x%x", rnti); - log_rrc_message(buf, Tx, pdu.get(), dl_ccch_msg, dl_ccch_msg.msg.c1().type().to_string()); + log_rrc_message(Tx, rnti, srb_to_lcid(lte_srb::srb0), *pdu, dl_ccch_msg, dl_ccch_msg.msg.c1().type().to_string()); + rlc->write_sdu(rnti, srb_to_lcid(lte_srb::srb0), std::move(pdu)); } @@ -488,7 +488,7 @@ void rrc::read_pdu_pcch(uint32_t tti_tx_dl, uint8_t* payload, uint32_t buffer_si logger.info("Assembling PCCH payload with %d UE identities, payload_len=%d bytes", msg.msg.c1().paging().paging_record_list.size(), pdu.size()); - log_rrc_message("PCCH-Message", Tx, pdu, msg, msg.msg.c1().type().to_string()); + log_broadcast_rrc_message(SRSRAN_PRNTI, pdu, msg, msg.msg.c1().type().to_string()); } return true; }; @@ -524,55 +524,44 @@ void rrc::set_erab_status(uint16_t rnti, const asn1::s1ap::bearers_subject_to_st from either a public function or the internal thread *******************************************************************************/ -void rrc::parse_ul_ccch(uint16_t rnti, srsran::unique_byte_buffer_t pdu) +void rrc::parse_ul_ccch(ue& ue, srsran::unique_byte_buffer_t pdu) { - if (pdu) { - ul_ccch_msg_s ul_ccch_msg; - asn1::cbit_ref bref(pdu->msg, pdu->N_bytes); - if (ul_ccch_msg.unpack(bref) != asn1::SRSASN_SUCCESS or - ul_ccch_msg.msg.type().value != ul_ccch_msg_type_c::types_opts::c1) { - logger.error("Failed to unpack UL-CCCH message"); - return; - } + srsran_assert(pdu != nullptr, "parse_ul_ccch called for empty message"); - log_rrc_message("SRB0", Rx, pdu.get(), ul_ccch_msg, ul_ccch_msg.msg.c1().type().to_string()); - - auto user_it = users.find(rnti); - switch (ul_ccch_msg.msg.c1().type().value) { - case ul_ccch_msg_type_c::c1_c_::types::rrc_conn_request: - if (user_it != users.end()) { - user_it->second->save_ul_message(std::move(pdu)); - user_it->second->handle_rrc_con_req(&ul_ccch_msg.msg.c1().rrc_conn_request()); - } else { - logger.error("Received ConnectionSetup for rnti=0x%x without context", rnti); - } - break; - case ul_ccch_msg_type_c::c1_c_::types::rrc_conn_reest_request: - if (user_it != users.end()) { - user_it->second->save_ul_message(std::move(pdu)); - user_it->second->handle_rrc_con_reest_req(&ul_ccch_msg.msg.c1().rrc_conn_reest_request()); - } else { - logger.error("Received ConnectionReestablishment for rnti=0x%x without context.", rnti); - } - break; - default: - logger.error("UL CCCH message not recognised"); - break; - } + ul_ccch_msg_s ul_ccch_msg; + asn1::cbit_ref bref(pdu->msg, pdu->N_bytes); + if (ul_ccch_msg.unpack(bref) != asn1::SRSASN_SUCCESS or + ul_ccch_msg.msg.type().value != ul_ccch_msg_type_c::types_opts::c1) { + log_rx_pdu_fail(ue.rnti, srb_to_lcid(lte_srb::srb0), *pdu, "Failed to unpack UL-CCCH message"); + return; + } + + // Log Rx message + log_rrc_message( + Rx, ue.rnti, srsran::srb_to_lcid(lte_srb::srb0), *pdu, ul_ccch_msg, ul_ccch_msg.msg.c1().type().to_string()); + + switch (ul_ccch_msg.msg.c1().type().value) { + case ul_ccch_msg_type_c::c1_c_::types::rrc_conn_request: + ue.save_ul_message(std::move(pdu)); + ue.handle_rrc_con_req(&ul_ccch_msg.msg.c1().rrc_conn_request()); + break; + case ul_ccch_msg_type_c::c1_c_::types::rrc_conn_reest_request: + ue.save_ul_message(std::move(pdu)); + ue.handle_rrc_con_reest_req(&ul_ccch_msg.msg.c1().rrc_conn_reest_request()); + break; + default: + logger.error("Processing UL-CCCH for rnti=0x%x - Unsupported message type %s", + ul_ccch_msg.msg.c1().type().to_string()); + break; } } ///< User mutex must be hold by caller -void rrc::parse_ul_dcch(uint16_t rnti, uint32_t lcid, srsran::unique_byte_buffer_t pdu) -{ - if (pdu) { - auto user_it = users.find(rnti); - if (user_it != users.end()) { - user_it->second->parse_ul_dcch(lcid, std::move(pdu)); - } else { - logger.error("Processing %s: Unknown rnti=0x%x", get_rb_name(lcid), rnti); - } - } +void rrc::parse_ul_dcch(ue& ue, uint32_t lcid, srsran::unique_byte_buffer_t pdu) +{ + srsran_assert(pdu != nullptr, "parse_ul_dcch called for empty message"); + + ue.parse_ul_dcch(lcid, std::move(pdu)); } ///< User mutex must be hold by caller @@ -752,9 +741,13 @@ uint32_t rrc::generate_sibs() cell_ctxt->sib_buffer.push_back(std::move(sib_buffer)); // Log SIBs in JSON format - std::string log_msg("CC" + std::to_string(cc_idx) + " SIB payload"); - log_rrc_message( - log_msg, Tx, cell_ctxt->sib_buffer.back().get(), msg[msg_index], msg[msg_index].msg.c1().type().to_string()); + fmt::memory_buffer membuf; + const char* msg_str = msg[msg_index].msg.c1().type().to_string(); + if (msg[msg_index].msg.c1().type().value != asn1::rrc::bcch_dl_sch_msg_type_c::c1_c_::types_opts::sib_type1) { + msg_str = msg[msg_index].msg.c1().sys_info().crit_exts.type().to_string(); + } + fmt::format_to(membuf, "{}, cc={}", msg_str, cc_idx); + log_broadcast_rrc_message(SRSRAN_SIRNTI, *cell_ctxt->sib_buffer.back(), msg[msg_index], srsran::to_c_str(membuf)); } if (cfg.sibs[6].type() == asn1::rrc::sys_info_r8_ies_s::sib_type_and_info_item_c_::types::sib7) { @@ -916,26 +909,26 @@ void rrc::tti_clock() // pop cmds from queue rrc_pdu p; while (rx_pdu_queue.try_pop(p)) { - // print Rx PDU - if (p.pdu != nullptr) { - logger.info(p.pdu->msg, p.pdu->N_bytes, "Rx %s PDU", get_rb_name(p.lcid)); - } - // check if user exists auto user_it = users.find(p.rnti); if (user_it == users.end()) { - logger.warning("Discarding PDU for removed rnti=0x%x", p.rnti); + if (p.pdu != nullptr) { + log_rx_pdu_fail(p.rnti, p.lcid, *p.pdu, "unknown rnti"); + } else { + logger.warning("Ignoring rnti=0x%x command. Cause: unknown rnti", p.rnti); + } continue; } + ue& ue = *user_it->second; // handle queue cmd switch (p.lcid) { case srb_to_lcid(lte_srb::srb0): - parse_ul_ccch(p.rnti, std::move(p.pdu)); + parse_ul_ccch(ue, std::move(p.pdu)); break; case srb_to_lcid(lte_srb::srb1): case srb_to_lcid(lte_srb::srb2): - parse_ul_dcch(p.rnti, p.lcid, std::move(p.pdu)); + parse_ul_dcch(ue, p.lcid, std::move(p.pdu)); break; case LCID_REM_USER: rem_user(p.rnti); @@ -965,4 +958,32 @@ void rrc::tti_clock() } } +void rrc::log_rx_pdu_fail(uint16_t rnti, uint32_t lcid, srsran::const_byte_span pdu, const char* cause_str) +{ + logger.error( + pdu.data(), pdu.size(), "Rx %s PDU, rnti=0x%x - Discarding. Cause: %s", get_rb_name(lcid), rnti, cause_str); +} + +void rrc::log_rxtx_pdu_impl(direction_t dir, + uint16_t rnti, + uint32_t lcid, + srsran::const_byte_span pdu, + const char* msg_type) +{ + static const char* dir_str[] = {"Rx", "Tx", "Tx S1AP", "Rx S1AP"}; + fmt::memory_buffer membuf; + fmt::format_to(membuf, "{} ", dir_str[dir]); + if (rnti != SRSRAN_PRNTI and rnti != SRSRAN_SIRNTI) { + if (dir == Tx or dir == Rx) { + fmt::format_to(membuf, "{} ", srsran::get_srb_name(srsran::lte_lcid_to_srb(lcid))); + } + fmt::format_to(membuf, "PDU, rnti=0x{:x} ", rnti); + } else { + fmt::format_to(membuf, "Broadcast PDU "); + } + fmt::format_to(membuf, "- {} ({} B)", msg_type, pdu.size()); + + logger.info(pdu.data(), pdu.size(), "%s", srsran::to_c_str(membuf)); +} + } // namespace srsenb diff --git a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc index 60eff67b8..358919cc7 100644 --- a/srsenb/src/stack/rrc/rrc_bearer_cfg.cc +++ b/srsenb/src/stack/rrc/rrc_bearer_cfg.cc @@ -233,8 +233,26 @@ int bearer_cfg_handler::add_erab(uint8_t cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::unknown_erab_id; return SRSRAN_ERROR; } - uint8_t lcid = erab_id - 2; // Map e.g. E-RAB 5 to LCID 3 (==DRB1) - uint8_t drbid = erab_id - 4; + + uint8_t lcid = 3; // first E-RAB with DRB1 gets LCID3 + for (const auto& drb : current_drbs) { + if (drb.lc_ch_id == lcid) { + lcid++; + } + } + if (lcid > srsran::MAX_LTE_LCID) { + logger->error("Can't allocate LCID for ERAB id=%d", erab_id); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::radio_res_not_available; + return SRSRAN_ERROR; + } + + // We currently have this static mapping between LCID->DRB ID + uint8_t drbid = lcid - 2; + if (drbid > srsran::MAX_LTE_DRB_ID) { + logger->error("Can't allocate DRB ID for ERAB id=%d", erab_id); + cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::radio_res_not_available; + return SRSRAN_ERROR; + } auto qci_it = cfg->qci_cfg.find(qos.qci); if (qci_it == cfg->qci_cfg.end() or not qci_it->second.configured) { @@ -250,6 +268,7 @@ int bearer_cfg_handler::add_erab(uint8_t const rrc_cfg_qci_t& qci_cfg = qci_it->second; erabs[erab_id].id = erab_id; + erabs[erab_id].lcid = lcid; erabs[erab_id].qos_params = qos; erabs[erab_id].address = addr; erabs[erab_id].teid_out = teid_out; @@ -317,9 +336,8 @@ int bearer_cfg_handler::release_erab(uint8_t erab_id) return SRSRAN_ERROR; } - uint8_t drb_id = erab_id - 4; - - srsran::rem_rrc_obj_id(current_drbs, drb_id); + lte_drb drb_id = lte_lcid_to_drb(it->second.lcid); + srsran::rem_rrc_obj_id(current_drbs, (uint8_t)drb_id); rem_gtpu_bearer(erab_id); @@ -385,7 +403,7 @@ srsran::expected bearer_cfg_handler::add_gtpu_bearer(uint32_t erab_t::gtpu_tunnel bearer; bearer.teid_out = teid_out; bearer.addr = addr; - srsran::expected teidin = gtpu->add_bearer(rnti, erab.id - 2, addr, teid_out, props); + srsran::expected teidin = gtpu->add_bearer(rnti, erab.lcid, addr, teid_out, props); if (teidin.is_error()) { logger->error("Adding erab_id=%d to GTPU", erab_id); return srsran::default_error_t(); @@ -397,7 +415,12 @@ srsran::expected bearer_cfg_handler::add_gtpu_bearer(uint32_t void bearer_cfg_handler::rem_gtpu_bearer(uint32_t erab_id) { - gtpu->rem_bearer(rnti, erab_id - 2); + auto it = erabs.find(erab_id); + if (it == erabs.end()) { + logger->warning("Removing erab_id=%d from GTPU", erab_id); + return; + } + gtpu->rem_bearer(rnti, it->second.lcid); } void bearer_cfg_handler::fill_pending_nas_info(asn1::rrc::rrc_conn_recfg_r8_ies_s* msg) @@ -414,14 +437,17 @@ void bearer_cfg_handler::fill_pending_nas_info(asn1::rrc::rrc_conn_recfg_r8_ies_ // Add E-RAB info message for the E-RABs if (msg->rr_cfg_ded.drb_to_add_mod_list_present) { for (const drb_to_add_mod_s& drb : msg->rr_cfg_ded.drb_to_add_mod_list) { - uint8_t erab_id = drb.drb_id + 4; - auto it = erab_info_list.find(erab_id); - if (it != erab_info_list.end()) { - const std::vector& erab_info = it->second; + uint32_t lcid = drb_to_lcid((lte_drb)drb.drb_id); + auto erab_it = std::find_if( + erabs.begin(), erabs.end(), [lcid](const std::pair& e) { return e.second.lcid == lcid; }); + uint32_t erab_id = erab_it->second.id; + auto info_it = erab_info_list.find(erab_id); + if (info_it != erab_info_list.end()) { + const std::vector& erab_info = info_it->second; logger->info(&erab_info[0], erab_info.size(), "connection_reconf erab_info -> nas_info rnti 0x%x", rnti); msg->ded_info_nas_list[idx].resize(erab_info.size()); memcpy(msg->ded_info_nas_list[idx].data(), &erab_info[0], erab_info.size()); - erab_info_list.erase(it); + erab_info_list.erase(info_it); } else { logger->debug("Not adding NAS message to connection reconfiguration. E-RAB id %d", erab_id); } diff --git a/srsenb/src/stack/rrc/rrc_mobility.cc b/srsenb/src/stack/rrc/rrc_mobility.cc index d5e6da594..4ba904aa3 100644 --- a/srsenb/src/stack/rrc/rrc_mobility.cc +++ b/srsenb/src/stack/rrc/rrc_mobility.cc @@ -582,7 +582,7 @@ rrc::ue::rrc_mobility::s1_source_ho_st::start_enb_status_transfer(const asn1::s1 for (const auto& erab_pair : rrc_ue->bearer_list.get_erabs()) { s1ap_interface_rrc::bearer_status_info b = {}; - uint8_t lcid = erab_pair.second.id - 2u; + uint8_t lcid = erab_pair.second.lcid; b.erab_id = erab_pair.second.id; srsran::pdcp_lte_state_t pdcp_state = {}; if (not rrc_enb->pdcp->get_bearer_state(rrc_ue->rnti, lcid, &pdcp_state)) { @@ -741,7 +741,7 @@ void rrc::ue::rrc_mobility::handle_ho_requested(idle_st& s, const ho_req_rx_ev& trigger(ho_failure_ev{cause}); return; } - rrc_enb->log_rrc_message("HandoverPreparation", direction_t::fromS1AP, rrc_container, hoprep, "HandoverPreparation"); + rrc_enb->log_rrc_message(direction_t::fromS1AP, rrc_ue->rnti, -1, rrc_container, hoprep, "HandoverPreparation"); /* Setup UE current state in TeNB based on HandoverPreparation message */ const ho_prep_info_r8_ies_s& hoprep_r8 = hoprep.crit_exts.c1().ho_prep_info_r8(); @@ -788,7 +788,7 @@ void rrc::ue::rrc_mobility::handle_ho_requested(idle_st& s, const ho_req_rx_ev& return; } ho_cmd_pdu->N_bytes = bref2.distance_bytes(); - rrc_enb->log_rrc_message("RRC container", direction_t::toS1AP, ho_cmd_pdu.get(), dl_dcch_msg, "HandoverCommand"); + rrc_enb->log_rrc_message(direction_t::toS1AP, rrc_ue->rnti, -1, *ho_cmd_pdu, dl_dcch_msg, "HandoverCommand"); asn1::rrc::ho_cmd_s ho_cmd; asn1::rrc::ho_cmd_r8_ies_s& ho_cmd_r8 = ho_cmd.crit_exts.set_c1().set_ho_cmd_r8(); @@ -925,6 +925,7 @@ bool rrc::ue::rrc_mobility::apply_ho_prep_cfg(const ho_prep_info_r8_ies_s& erabs_failed_to_setup.back().erab_id = erab.erab_id; erabs_failed_to_setup.back().cause.set_transport().value = asn1::s1ap::cause_transport_opts::transport_res_unavailable; + rrc_ue->bearer_list.release_erab(erab.erab_id); continue; } } @@ -997,12 +998,12 @@ void rrc::ue::rrc_mobility::handle_status_transfer(s1_target_ho_st& s, const sta logger.warning("The E-RAB Id=%d is not recognized", erab_item.erab_id); continue; } - const auto& drbs = rrc_ue->bearer_list.get_established_drbs(); - uint8_t drbid = erab_item.erab_id - 4; - auto drb_it = - std::find_if(drbs.begin(), drbs.end(), [drbid](const drb_to_add_mod_s& drb) { return drb.drb_id == drbid; }); + const auto& drbs = rrc_ue->bearer_list.get_established_drbs(); + lte_drb drbid = lte_lcid_to_drb(erab_it->second.lcid); + auto drb_it = std::find_if( + drbs.begin(), drbs.end(), [drbid](const drb_to_add_mod_s& drb) { return (lte_drb)drb.drb_id == drbid; }); if (drb_it == drbs.end()) { - logger.warning("The DRB id=%d does not exist", erab_item.erab_id - 4); + logger.warning("The DRB id=%d does not exist", drbid); } srsran::pdcp_lte_state_t drb_state{}; diff --git a/srsenb/src/stack/rrc/rrc_ue.cc b/srsenb/src/stack/rrc/rrc_ue.cc index a9152d7a5..4ad857ae5 100644 --- a/srsenb/src/stack/rrc/rrc_ue.cc +++ b/srsenb/src/stack/rrc/rrc_ue.cc @@ -20,13 +20,12 @@ */ #include "srsenb/hdr/stack/rrc/rrc_ue.h" +#include "srsenb/hdr/common/common_enb.h" #include "srsenb/hdr/stack/rrc/mac_controller.h" #include "srsenb/hdr/stack/rrc/rrc_mobility.h" #include "srsenb/hdr/stack/rrc/ue_rr_cfg.h" -#include "srsran/adt/pool/mem_pool.h" #include "srsran/asn1/rrc_utils.h" #include "srsran/common/enb_events.h" -#include "srsran/common/int_helpers.h" #include "srsran/common/standard_streams.h" #include "srsran/interfaces/enb_pdcp_interfaces.h" #include "srsran/interfaces/enb_rlc_interfaces.h" @@ -180,7 +179,9 @@ void rrc::ue::activity_timer_expired(const activity_timeout_type_t type) if (parent->s1ap->user_exists(rnti)) { if (type == UE_INACTIVITY_TIMEOUT) { - parent->s1ap->user_release(rnti, asn1::s1ap::cause_radio_network_opts::user_inactivity); + if (not parent->s1ap->user_release(rnti, asn1::s1ap::cause_radio_network_opts::user_inactivity)) { + parent->rem_user_thread(rnti); + } con_release_result = procedure_result_code::activity_timeout; } else if (type == MSG3_RX_TIMEOUT) { // MSG3 timeout, no need to notify S1AP, just remove UE @@ -206,22 +207,22 @@ void rrc::ue::activity_timer_expired(const activity_timeout_type_t type) void rrc::ue::rlf_timer_expired(uint32_t timeout_id) { activity_timer.stop(); - if (parent) { - if (timeout_id == phy_dl_rlf_timer.id()) { - parent->logger.info("DL RLF timer for rnti=0x%x expired after %d ms", rnti, phy_dl_rlf_timer.time_elapsed()); - } else if (timeout_id == phy_ul_rlf_timer.id()) { - parent->logger.info("UL RLF timer for rnti=0x%x expired after %d ms", rnti, phy_ul_rlf_timer.time_elapsed()); - } else if (timeout_id == rlc_rlf_timer.id()) { - parent->logger.info("RLC RLF timer for rnti=0x%x expired after %d ms", rnti, rlc_rlf_timer.time_elapsed()); - } - - if (parent->s1ap->user_exists(rnti)) { - parent->s1ap->user_release(rnti, asn1::s1ap::cause_radio_network_opts::radio_conn_with_ue_lost); - con_release_result = procedure_result_code::radio_conn_with_ue_lost; - } else { - if (rnti != SRSRAN_MRNTI) { - parent->rem_user(rnti); - } + if (timeout_id == phy_dl_rlf_timer.id()) { + parent->logger.info("DL RLF timer for rnti=0x%x expired after %d ms", rnti, phy_dl_rlf_timer.time_elapsed()); + } else if (timeout_id == phy_ul_rlf_timer.id()) { + parent->logger.info("UL RLF timer for rnti=0x%x expired after %d ms", rnti, phy_ul_rlf_timer.time_elapsed()); + } else if (timeout_id == rlc_rlf_timer.id()) { + parent->logger.info("RLC RLF timer for rnti=0x%x expired after %d ms", rnti, rlc_rlf_timer.time_elapsed()); + } + + if (parent->s1ap->user_release(rnti, asn1::s1ap::cause_radio_network_opts::radio_conn_with_ue_lost)) { + con_release_result = procedure_result_code::radio_conn_with_ue_lost; + phy_ul_rlf_timer.stop(); + phy_dl_rlf_timer.stop(); + rlc_rlf_timer.stop(); + } else { + if (rnti != SRSRAN_MRNTI) { + parent->rem_user(rnti); } } @@ -281,11 +282,12 @@ void rrc::ue::parse_ul_dcch(uint32_t lcid, srsran::unique_byte_buffer_t pdu) asn1::cbit_ref bref(pdu->msg, pdu->N_bytes); if (ul_dcch_msg.unpack(bref) != asn1::SRSASN_SUCCESS or ul_dcch_msg.msg.type().value != ul_dcch_msg_type_c::types_opts::c1) { - parent->logger.error("Failed to unpack UL-DCCH message"); + parent->log_rx_pdu_fail(rnti, lcid, *pdu, "Failed to unpack UL-DCCH message"); return; } - parent->log_rrc_message(get_rb_name(lcid), Rx, pdu.get(), ul_dcch_msg, ul_dcch_msg.msg.c1().type().to_string()); + // Log Rx message + parent->log_rrc_message(Rx, rnti, lcid, *pdu, ul_dcch_msg, ul_dcch_msg.msg.c1().type().to_string()); srsran::unique_byte_buffer_t original_pdu = std::move(pdu); pdu = srsran::make_byte_buffer(); @@ -401,7 +403,9 @@ void rrc::ue::handle_rrc_con_req(rrc_conn_request_s* msg) if (user.first != rnti && user.second->has_tmsi && user.second->mmec == mmec && user.second->m_tmsi == m_tmsi) { parent->logger.info("RRC connection request: UE context already exists. M-TMSI=%d", m_tmsi); user.second->state = RRC_STATE_IDLE; // Set old rnti to IDLE so that enb doesn't send RRC Connection Release - parent->s1ap->user_release(user.first, asn1::s1ap::cause_radio_network_opts::radio_conn_with_ue_lost); + if (not parent->s1ap->user_release(user.first, asn1::s1ap::cause_radio_network_opts::radio_conn_with_ue_lost)) { + parent->rem_user_thread(user.first); + } break; } } @@ -584,7 +588,7 @@ void rrc::ue::handle_rrc_con_reest_req(rrc_conn_reest_request_s* msg) // Get PDCP entity state (required when using RLC AM) for (const auto& erab_pair : old_ue->bearer_list.get_erabs()) { - uint16_t lcid = erab_pair.second.id - 2; + uint16_t lcid = erab_pair.second.lcid; old_reest_pdcp_state[lcid] = {}; parent->pdcp->get_bearer_state(old_rnti, lcid, &old_reest_pdcp_state[lcid]); @@ -1089,6 +1093,7 @@ int rrc::ue::setup_erab(uint16_t erab_ } if (bearer_list.add_gtpu_bearer(erab_id) != SRSRAN_SUCCESS) { cause.set_radio_network().value = asn1::s1ap::cause_radio_network_opts::radio_res_not_available; + bearer_list.release_erab(erab_id); return SRSRAN_ERROR; } return SRSRAN_SUCCESS; @@ -1153,9 +1158,9 @@ void rrc::ue::send_dl_ccch(dl_ccch_msg_s* dl_ccch_msg, std::string* octet_str) } pdu->N_bytes = (uint32_t)bref.distance_bytes(); - char buf[32] = {}; - sprintf(buf, "SRB0 - rnti=0x%x", rnti); - parent->log_rrc_message(buf, Tx, pdu.get(), *dl_ccch_msg, dl_ccch_msg->msg.c1().type().to_string()); + // Log Tx message + parent->log_rrc_message( + Tx, rnti, srb_to_lcid(lte_srb::srb0), *pdu, *dl_ccch_msg, dl_ccch_msg->msg.c1().type().to_string()); // Encode the pdu as an octet string if the user passed a valid pointer. if (octet_str) { @@ -1170,38 +1175,37 @@ void rrc::ue::send_dl_ccch(dl_ccch_msg_s* dl_ccch_msg, std::string* octet_str) bool rrc::ue::send_dl_dcch(const dl_dcch_msg_s* dl_dcch_msg, srsran::unique_byte_buffer_t pdu, std::string* octet_str) { - if (!pdu) { + if (pdu == nullptr) { pdu = srsran::make_byte_buffer(); - } - if (pdu) { - asn1::bit_ref bref(pdu->msg, pdu->get_tailroom()); - if (dl_dcch_msg->pack(bref) == asn1::SRSASN_ERROR_ENCODE_FAIL) { - parent->logger.error("Failed to encode DL-DCCH-Msg"); + if (pdu == nullptr) { + parent->logger.error("Allocating pdu"); return false; } - pdu->N_bytes = (uint32_t)bref.distance_bytes(); + } - lte_srb rb = lte_srb::srb1; - if (dl_dcch_msg->msg.c1().type() == dl_dcch_msg_type_c::c1_c_::types_opts::dl_info_transfer) { - // send messages with NAS on SRB2 if user is fully registered (after RRC reconfig complete) - rb = (parent->rlc->has_bearer(rnti, srb_to_lcid(lte_srb::srb2)) && state == RRC_STATE_REGISTERED) ? lte_srb::srb2 - : lte_srb::srb1; - } + asn1::bit_ref bref(pdu->msg, pdu->get_tailroom()); + if (dl_dcch_msg->pack(bref) == asn1::SRSASN_ERROR_ENCODE_FAIL) { + parent->logger.error("Failed to encode DL-DCCH-Msg for rnti=0x%x", rnti); + return false; + } + pdu->N_bytes = (uint32_t)bref.distance_bytes(); - char buf[32] = {}; - sprintf(buf, "%s - rnti=0x%x", srsran::get_srb_name(rb), rnti); - parent->log_rrc_message(buf, Tx, pdu.get(), *dl_dcch_msg, dl_dcch_msg->msg.c1().type().to_string()); + lte_srb rb = lte_srb::srb1; + if (dl_dcch_msg->msg.c1().type() == dl_dcch_msg_type_c::c1_c_::types_opts::dl_info_transfer) { + // send messages with NAS on SRB2 if user is fully registered (after RRC reconfig complete) + rb = (parent->rlc->has_bearer(rnti, srb_to_lcid(lte_srb::srb2)) && state == RRC_STATE_REGISTERED) ? lte_srb::srb2 + : lte_srb::srb1; + } - // Encode the pdu as an octet string if the user passed a valid pointer. - if (octet_str) { - *octet_str = asn1::octstring_to_string(pdu->msg, pdu->N_bytes); - } + // Log Tx message + parent->log_rrc_message(Tx, rnti, srb_to_lcid(rb), *pdu, *dl_dcch_msg, dl_dcch_msg->msg.c1().type().to_string()); - parent->pdcp->write_sdu(rnti, srb_to_lcid(rb), std::move(pdu)); - } else { - parent->logger.error("Allocating pdu"); - return false; + // Encode the pdu as an octet string if the user passed a valid pointer. + if (octet_str != nullptr) { + *octet_str = asn1::octstring_to_string(pdu->msg, pdu->N_bytes); } + + parent->pdcp->write_sdu(rnti, srb_to_lcid(rb), std::move(pdu)); return true; } @@ -1336,7 +1340,7 @@ void rrc::ue::apply_pdcp_srb_updates(const rr_cfg_ded_s& pending_rr_cfg) void rrc::ue::apply_pdcp_drb_updates(const rr_cfg_ded_s& pending_rr_cfg) { for (uint8_t drb_id : pending_rr_cfg.drb_to_release_list) { - parent->pdcp->del_bearer(rnti, drb_id + 2); + parent->pdcp->del_bearer(rnti, drb_to_lcid((lte_drb)drb_id)); } for (const drb_to_add_mod_s& drb : pending_rr_cfg.drb_to_add_mod_list) { // Configure DRB1 in PDCP @@ -1358,7 +1362,7 @@ void rrc::ue::apply_pdcp_drb_updates(const rr_cfg_ded_s& pending_rr_cfg) // If reconf due to reestablishment, recover PDCP state if (state == RRC_STATE_REESTABLISHMENT_COMPLETE) { for (const auto& erab_pair : bearer_list.get_erabs()) { - uint16_t lcid = erab_pair.second.id - 2; + uint16_t lcid = erab_pair.second.lcid; bool is_am = parent->cfg.qci_cfg[erab_pair.second.qos_params.qci].rlc_cfg.type().value == asn1::rrc::rlc_cfg_c::types_opts::am; if (is_am) { @@ -1386,7 +1390,7 @@ void rrc::ue::apply_rlc_rb_updates(const rr_cfg_ded_s& pending_rr_cfg) } if (pending_rr_cfg.drb_to_release_list.size() > 0) { for (uint8_t drb_id : pending_rr_cfg.drb_to_release_list) { - parent->rlc->del_bearer(rnti, drb_id + 2); + parent->rlc->del_bearer(rnti, drb_to_lcid((lte_drb)drb_id)); } } for (const drb_to_add_mod_s& drb : pending_rr_cfg.drb_to_add_mod_list) { diff --git a/srsenb/src/stack/s1ap/CMakeLists.txt b/srsenb/src/stack/s1ap/CMakeLists.txt new file mode 100644 index 000000000..c84077f04 --- /dev/null +++ b/srsenb/src/stack/s1ap/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# Copyright 2013-2021 Software Radio Systems Limited +# +# By using this file, you agree to the terms and conditions set +# forth in the LICENSE file which can be found at the top level of +# the distribution. +# + +set(SOURCES s1ap.cc) +add_library(srsenb_s1ap STATIC ${SOURCES}) diff --git a/srsenb/src/stack/upper/s1ap.cc b/srsenb/src/stack/s1ap/s1ap.cc similarity index 99% rename from srsenb/src/stack/upper/s1ap.cc rename to srsenb/src/stack/s1ap/s1ap.cc index df294c27f..fd885bf49 100644 --- a/srsenb/src/stack/upper/s1ap.cc +++ b/srsenb/src/stack/s1ap/s1ap.cc @@ -19,7 +19,7 @@ * */ -#include "srsenb/hdr/stack/upper/s1ap.h" +#include "srsenb/hdr/stack/s1ap/s1ap.h" #include "srsran/adt/scope_exit.h" #include "srsran/common/bcd_helpers.h" #include "srsran/common/enb_events.h" @@ -431,7 +431,6 @@ bool s1ap::user_release(uint16_t rnti, asn1::s1ap::cause_radio_network_e cause_r if (not u->send_uectxtreleaserequest(cause)) { users.erase(u); - rrc->release_ue(rnti); return false; } return true; diff --git a/srsenb/src/stack/upper/CMakeLists.txt b/srsenb/src/stack/upper/CMakeLists.txt index c1505e5f3..43ab73e8c 100644 --- a/srsenb/src/stack/upper/CMakeLists.txt +++ b/srsenb/src/stack/upper/CMakeLists.txt @@ -18,8 +18,8 @@ # and at http://www.gnu.org/licenses/. # -set(SOURCES gtpu.cc pdcp.cc rlc.cc s1ap.cc) +set(SOURCES gtpu.cc pdcp.cc rlc.cc) add_library(srsenb_upper STATIC ${SOURCES}) -set(SOURCES pdcp_nr.cc rlc_nr.cc sdap.cc ngap.cc) +set(SOURCES pdcp_nr.cc rlc_nr.cc sdap.cc) add_library(srsgnb_upper STATIC ${SOURCES}) diff --git a/srsenb/test/CMakeLists.txt b/srsenb/test/CMakeLists.txt index 26ca5543f..3369bc58c 100644 --- a/srsenb/test/CMakeLists.txt +++ b/srsenb/test/CMakeLists.txt @@ -22,6 +22,7 @@ add_subdirectory(mac) add_subdirectory(phy) add_subdirectory(upper) add_subdirectory(rrc) +add_subdirectory(s1ap) add_executable(enb_metrics_test enb_metrics_test.cc ../src/metrics_stdout.cc ../src/metrics_csv.cc) target_link_libraries(enb_metrics_test srsran_phy srsran_common) diff --git a/srsenb/test/mac/CMakeLists.txt b/srsenb/test/mac/CMakeLists.txt index 9c4cb5e55..aad17f6ed 100644 --- a/srsenb/test/mac/CMakeLists.txt +++ b/srsenb/test/mac/CMakeLists.txt @@ -78,3 +78,11 @@ add_test(sched_ue_cell_test sched_ue_cell_test) add_executable(sched_benchmark_test sched_benchmark.cc) target_link_libraries(sched_benchmark_test srsran_common srsenb_mac srsran_mac sched_test_common) add_test(sched_benchmark_test sched_benchmark_test) + +add_executable(sched_cqi_test sched_cqi_test.cc) +target_link_libraries(sched_cqi_test srsran_common srsenb_mac srsran_mac sched_test_common) +add_test(sched_cqi_test sched_cqi_test) + +add_executable(sched_phy_resource_test sched_phy_resource_test.cc) +target_link_libraries(sched_phy_resource_test srsran_common srsenb_mac srsran_mac sched_test_common) +add_test(sched_phy_resource_test sched_phy_resource_test) diff --git a/srsenb/test/mac/sched_benchmark.cc b/srsenb/test/mac/sched_benchmark.cc index 83db01cf4..c0e43f50b 100644 --- a/srsenb/test/mac/sched_benchmark.cc +++ b/srsenb/test/mac/sched_benchmark.cc @@ -37,7 +37,7 @@ struct run_params { struct run_params_range { std::vector nof_prbs{srsran::lte_cell_nof_prbs.begin(), srsran::lte_cell_nof_prbs.end()}; - std::vector nof_ues = {1, 2, 5}; + std::vector nof_ues = {1, 2, 5, 32}; uint32_t nof_ttis = 10000; std::vector cqi = {5, 10, 15}; std::vector sched_policy = {"time_rr", "time_pf"}; @@ -89,8 +89,9 @@ public: std::vector ul_result; struct throughput_stats { - srsran::rolling_average mean_dl_tbs, mean_ul_tbs, avg_dl_mcs, avg_ul_mcs; - srsran::rolling_average avg_latency; + srsran::rolling_average mean_dl_tbs, mean_ul_tbs, avg_dl_mcs, avg_ul_mcs; + srsran::rolling_average avg_latency; + std::vector latency_samples; }; throughput_stats total_stats; @@ -107,6 +108,7 @@ public: std::chrono::time_point tp2 = std::chrono::steady_clock::now(); std::chrono::nanoseconds tdur = std::chrono::duration_cast(tp2 - tp); total_stats.avg_latency.push(tdur.count()); + total_stats.latency_samples.push_back(tdur.count()); } sf_output_res_t sf_out{get_cell_params(), tti_rx, ul_result, dl_result}; @@ -164,6 +166,7 @@ struct run_data { float avg_dl_mcs; float avg_ul_mcs; std::chrono::microseconds avg_latency; + std::chrono::microseconds q0_9_latency; }; int run_benchmark_scenario(run_params params, std::vector& run_results) @@ -202,12 +205,14 @@ int run_benchmark_scenario(run_params params, std::vector& run_results tester.advance_tti(); ue_db_ctxt = tester.get_enb_ctxt().ue_db; } - tester.total_stats = {}; // Run benchmark + tester.total_stats = {}; + tester.total_stats.latency_samples.reserve(params.nof_ttis); for (uint32_t count = 0; count < params.nof_ttis; ++count) { tester.advance_tti(); } + std::sort(tester.total_stats.latency_samples.begin(), tester.total_stats.latency_samples.end()); run_data run_result = {}; run_result.params = params; @@ -215,7 +220,9 @@ int run_benchmark_scenario(run_params params, std::vector& run_results run_result.avg_ul_throughput = tester.total_stats.mean_ul_tbs.value() * 8.0F / 1e-3F; run_result.avg_dl_mcs = tester.total_stats.avg_dl_mcs.value(); run_result.avg_ul_mcs = tester.total_stats.avg_ul_mcs.value(); - run_result.avg_latency = std::chrono::microseconds(static_cast(tester.total_stats.avg_latency.value() / 1000)); + run_result.avg_latency = std::chrono::microseconds(static_cast(tester.total_stats.avg_latency.value() / 1000)); + run_result.q0_9_latency = std::chrono::microseconds( + tester.total_stats.latency_samples[static_cast(tester.total_stats.latency_samples.size() * 0.9)] / 1000); run_results.push_back(run_result); return SRSRAN_SUCCESS; @@ -243,11 +250,11 @@ run_data expected_run_result(run_params params) ret.avg_ul_throughput *= 0.75; break; case 15: - ret.avg_dl_throughput *= 0.95; + ret.avg_dl_throughput *= 0.94; ret.avg_ul_throughput *= 0.7; break; default: - ret.avg_dl_throughput *= 0.97; + ret.avg_dl_throughput *= 0.96; ret.avg_ul_throughput *= 0.85; break; } @@ -257,9 +264,9 @@ run_data expected_run_result(run_params params) void print_benchmark_results(const std::vector& run_results) { srslog::flush(); - fmt::print("run | Nprb | cqi | sched pol | Nue | DL/UL [Mbps] | DL/UL mcs | DL/UL OH [%] | latency " + fmt::print("run | Nprb | cqi | sched pol | Nue | DL/UL [Mbps] | DL/UL mcs | DL/UL OH [%] | latency | latency q0.9 " "[usec]\n"); - fmt::print("---------------------------------------------------------------------------------------" + fmt::print("------------------------------------------------------------------------------------------------------" "------\n"); for (uint32_t i = 0; i < run_results.size(); ++i) { const run_data& r = run_results[i]; @@ -272,7 +279,7 @@ void print_benchmark_results(const std::vector& run_results) tbs = srsran_ra_tbs_from_idx(tbs_idx, nof_pusch_prbs); float ul_rate_overhead = 1.0F - r.avg_ul_throughput / (static_cast(tbs) * 1e3F); - fmt::print("{:>3d}{:>6d}{:>6d}{:>12}{:>6d}{:>9.2}/{:>4.2}{:>9.1f}/{:>4.1f}{:9.1f}/{:>4.1f}{:12d}\n", + fmt::print("{:>3d}{:>6d}{:>6d}{:>12}{:>6d}{:>9.2}/{:>4.2}{:>9.1f}/{:>4.1f}{:9.1f}/{:>4.1f}{:>9d}{:12d}\n", i, r.params.nof_prbs, r.params.cqi, @@ -284,7 +291,8 @@ void print_benchmark_results(const std::vector& run_results) r.avg_ul_mcs, dl_rate_overhead * 100, ul_rate_overhead * 100, - r.avg_latency.count()); + r.avg_latency.count(), + r.q0_9_latency.count()); } } diff --git a/srsenb/test/mac/sched_common_test_suite.h b/srsenb/test/mac/sched_common_test_suite.h index 8594d20d2..a55306395 100644 --- a/srsenb/test/mac/sched_common_test_suite.h +++ b/srsenb/test/mac/sched_common_test_suite.h @@ -23,7 +23,7 @@ #define SRSRAN_SCHED_COMMON_TEST_SUITE_H #include "srsenb/hdr/stack/mac/sched_common.h" -#include "srsran/adt/bounded_bitset.h" +#include "srsenb/hdr/stack/mac/sched_phy_ch/sched_phy_resource.h" #include "srsran/adt/span.h" #include "srsran/common/tti_point.h" #include "srsran/interfaces/sched_interface.h" diff --git a/srsenb/test/mac/sched_cqi_test.cc b/srsenb/test/mac/sched_cqi_test.cc new file mode 100644 index 000000000..99a16dd06 --- /dev/null +++ b/srsenb/test/mac/sched_cqi_test.cc @@ -0,0 +1,93 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsenb/hdr/stack/mac/sched_ue_ctrl/sched_dl_cqi.h" +#include "srsran/common/test_common.h" + +namespace srsenb { + +void test_sched_cqi_one_subband_cqi() +{ + // 50 PRBs, K=4 + sched_dl_cqi ue_cqi(50, 4, 0); + + // J == 3, N == 9 + TESTASSERT(ue_cqi.nof_bandwidth_parts() == 3); + TESTASSERT(ue_cqi.nof_subbands() == 9); + + // Ni = 0 -> cqi=5 + ue_cqi.cqi_sb_info(tti_point(0), 0, 5); + + // TEST: updated part has positive cqi. Non-updated cqi didn't change + TESTASSERT(ue_cqi.get_grant_avg_cqi(rbg_interval(0, 1)) == 5); + for (uint32_t i = 1; i < 5; ++i) { + TESTASSERT(ue_cqi.get_grant_avg_cqi(rbg_interval(i, i + 1)) > 0); + } + TESTASSERT(ue_cqi.get_grant_avg_cqi(rbg_interval(6, cell_nof_prb_to_rbg(50))) == 0); + + // TEST: Check average cqi over a mask of RBGs + rbgmask_t mask(cell_nof_prb_to_rbg(50)); + mask.fill(10, mask.size()); + TESTASSERT(ue_cqi.get_grant_avg_cqi(mask) == 0); + mask.reset(); + mask.set(1); + TESTASSERT(ue_cqi.get_grant_avg_cqi(mask) == 5); + mask.fill(0, mask.size()); + TESTASSERT(ue_cqi.get_grant_avg_cqi(mask) > 0 and ue_cqi.get_grant_avg_cqi(mask) < 5); + + // TEST: Get optimal RBG mask in terms of CQI + mask = ue_cqi.get_optim_rbgmask(5); + TESTASSERT(mask.count() == 5); + for (uint32_t i = 0; i < 5; ++i) { + TESTASSERT(mask.test(i) > 0); + } +} + +void test_sched_cqi_wideband_cqi() +{ + uint32_t nof_prb = 50; + uint32_t nof_rbgs = cell_nof_prb_to_rbg(nof_prb); + + sched_dl_cqi ue_cqi(nof_prb, 0, 0); + + ue_cqi.cqi_wb_info(tti_point(0), 5); + + // TEST: all bandwidth has positive cqi. + for (uint32_t i = 0; i < nof_rbgs; ++i) { + TESTASSERT(ue_cqi.get_grant_avg_cqi(rbg_interval(i, i + 1)) == 5); + } + TESTASSERT(ue_cqi.get_grant_avg_cqi(rbg_interval(0, nof_rbgs)) == 5); + + // TEST: Check average cqi over a mask of RBGs + rbgmask_t mask(cell_nof_prb_to_rbg(50)); + mask.fill(10, mask.size()); + TESTASSERT(ue_cqi.get_grant_avg_cqi(mask) == 5); + + // TEST: Get optimal RBG mask in terms of CQI + mask = ue_cqi.get_optim_rbgmask(5); + TESTASSERT(mask.count() == 5); + for (uint32_t i = 0; i < 5; ++i) { + TESTASSERT(mask.test(i) > 0); + } +} + +} // namespace srsenb + +int main(int argc, char** argv) +{ + srsran::test_init(argc, argv); + + srsenb::test_sched_cqi_one_subband_cqi(); + srsenb::test_sched_cqi_wideband_cqi(); + + return SRSRAN_SUCCESS; +} diff --git a/srsenb/test/mac/sched_phy_resource_test.cc b/srsenb/test/mac/sched_phy_resource_test.cc new file mode 100644 index 000000000..0d9c85ff9 --- /dev/null +++ b/srsenb/test/mac/sched_phy_resource_test.cc @@ -0,0 +1,69 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsenb/hdr/stack/mac/sched_phy_ch/sched_phy_resource.h" +#include "srsran/common/common_lte.h" +#include "srsran/common/test_common.h" +#include + +namespace srsenb { + +std::random_device rd; +std::mt19937 rand_gen(rd()); + +uint32_t get_rand_Nrbg() +{ + return cell_nof_prb_to_rbg(srsran::lte_cell_nof_prbs[std::uniform_int_distribution{ + 0, srsran::lte_cell_nof_prbs.size() - 1}(rand_gen)]); +} + +void test_rbg_mask_helpers() +{ + rbgmask_t rbgs(MAX_NOF_RBGS); + + // TEST: Find contiguous range of zero RBGs in RBG mask + rbgs.set(0); + rbgs.set(2); + rbg_interval interv = find_empty_rbg_interval(1, rbgs); + TESTASSERT(not interv.empty() and interv.length() == 1 and interv.start() == 1); + interv = find_empty_rbg_interval(2, rbgs); + TESTASSERT(not interv.empty() and interv.length() == 2 and interv.start() == 3); + interv = find_empty_rbg_interval(rbgs.size(), rbgs); + TESTASSERT(interv.length() + 3 == rbgs.size() and interv.start() == 3); + + // TEST: find mask of zero RBGs in RBG mask + rbgmask_t empty_rbgs = find_available_rbgmask(1, false, rbgs); + TESTASSERT(empty_rbgs.count() == 1 and empty_rbgs.test(1)); + empty_rbgs = find_available_rbgmask(5, false, rbgs); + TESTASSERT(empty_rbgs.count() == 5 and empty_rbgs.test(1) and empty_rbgs.test(3) and not empty_rbgs.test(2)); + + // TEST: find mask of zero RBGs in random RBG mask + std::bernoulli_distribution dist{0.5}; + rbgs = rbgmask_t(get_rand_Nrbg()); + for (size_t i = 0; i < rbgs.size(); ++i) { + rbgs.set(i, dist(rand_gen)); + } + empty_rbgs = find_available_rbgmask(rbgs.size(), false, rbgs); + TESTASSERT(empty_rbgs == ~rbgs); + uint32_t L = std::uniform_int_distribution{1, (uint32_t)rbgs.size() - 1}(rand_gen); + empty_rbgs = find_available_rbgmask(L, false, rbgs); + TESTASSERT(empty_rbgs.count() <= L and (empty_rbgs & rbgs).none()); + uint32_t nprb = count_prb_per_tb(rbgs); + TESTASSERT(nprb <= MAX_NOF_PRBS); +} + +} // namespace srsenb + +int main() +{ + srsenb::test_rbg_mask_helpers(); +} diff --git a/srsenb/test/phy/enb_phy_test.cc b/srsenb/test/phy/enb_phy_test.cc index e9ae85bd8..6e34d81f6 100644 --- a/srsenb/test/phy/enb_phy_test.cc +++ b/srsenb/test/phy/enb_phy_test.cc @@ -513,17 +513,16 @@ public: // Notify test engine notify_get_dl_sched(); - /// Make sure it writes the first cell always - dl_sched_res[0].cfi = cfi; + // Make sure it writes the CFI in all cells + for (dl_sched_t& dl_sched : dl_sched_res) { + dl_sched.cfi = cfi; + } // Iterate for each carrier uint32_t ue_cc_idx = 0; for (uint32_t& cc_idx : active_cell_list) { auto& dl_sched = dl_sched_res[cc_idx]; - // Required - dl_sched.cfi = cfi; - // Default TB scheduling bool sched_tb[SRSRAN_MAX_TB] = {}; @@ -1148,6 +1147,7 @@ public: uint32_t tm_u32 = 1; uint32_t period_pcell_rotate = 0; srsran_tm_t tm = SRSRAN_TM1; + bool extended_cp = false; args_t() { cell.nof_prb = 6; @@ -1224,6 +1224,7 @@ public: q.cell = args.cell; q.cell.id = i; q.cell_id = i; + q.cell.cp = args.extended_cp ? SRSRAN_CP_EXT : SRSRAN_CP_NORM; q.dl_freq_hz = 0.0f; ///< Frequencies are irrelevant in this test q.ul_freq_hz = 0.0f; q.root_seq_idx = 25 + i; ///< Different PRACH root sequences @@ -1426,6 +1427,7 @@ int parse_args(int argc, char** argv, phy_test_bench::args_t& args) ("ack_mode", bpo::value(&args.ack_mode), "HARQ ACK/NACK mode: normal, pucch3, cs") ("cell.nof_prb", bpo::value(&args.cell.nof_prb)->default_value(args.cell.nof_prb), "eNb Cell/Carrier bandwidth") ("cell.nof_ports", bpo::value(&args.cell.nof_ports)->default_value(args.cell.nof_ports), "eNb Cell/Carrier number of ports") + ("cell.cp", bpo::value(&args.extended_cp)->default_value(false), "use extended CP") ("tm", bpo::value(&args.tm_u32)->default_value(args.tm_u32), "Transmission mode") ("rotation", bpo::value(&args.period_pcell_rotate), "Serving cells rotation period in ms, set to zero to disable") ; diff --git a/srsenb/test/s1ap/CMakeLists.txt b/srsenb/test/s1ap/CMakeLists.txt new file mode 100644 index 000000000..accc3eb73 --- /dev/null +++ b/srsenb/test/s1ap/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright 2013-2021 Software Radio Systems Limited +# +# By using this file, you agree to the terms and conditions set +# forth in the LICENSE file which can be found at the top level of +# the distribution. +# + +add_executable(s1ap_test s1ap_test.cc) +target_link_libraries(s1ap_test srsran_common s1ap_asn1 srsenb_s1ap srsenb_upper srsran_upper s1ap_asn1 ${SCTP_LIBRARIES}) +add_test(s1ap_test s1ap_test) + diff --git a/srsenb/test/upper/s1ap_test.cc b/srsenb/test/s1ap/s1ap_test.cc similarity index 99% rename from srsenb/test/upper/s1ap_test.cc rename to srsenb/test/s1ap/s1ap_test.cc index 629dce670..85647868e 100644 --- a/srsenb/test/upper/s1ap_test.cc +++ b/srsenb/test/s1ap/s1ap_test.cc @@ -19,7 +19,7 @@ * */ -#include "srsenb/hdr/stack/upper/s1ap.h" +#include "srsenb/hdr/stack/s1ap/s1ap.h" #include "srsenb/test/common/dummy_classes.h" #include "srsran/common/network_utils.h" #include "srsran/common/test_common.h" diff --git a/srsenb/test/upper/CMakeLists.txt b/srsenb/test/upper/CMakeLists.txt index 8ced20e9e..1ec2e9cb9 100644 --- a/srsenb/test/upper/CMakeLists.txt +++ b/srsenb/test/upper/CMakeLists.txt @@ -37,9 +37,6 @@ target_link_libraries(rrc_meascfg_test test_helpers) add_executable(gtpu_test gtpu_test.cc) target_link_libraries(gtpu_test srsran_common s1ap_asn1 srsenb_upper srsran_upper ${SCTP_LIBRARIES}) -add_executable(s1ap_test s1ap_test.cc) -target_link_libraries(s1ap_test srsran_common s1ap_asn1 srsenb_upper srsran_upper s1ap_asn1 ${SCTP_LIBRARIES}) - add_test(rrc_mobility_test rrc_mobility_test -i ${CMAKE_CURRENT_SOURCE_DIR}/../..) add_test(erab_setup_test erab_setup_test -i ${CMAKE_CURRENT_SOURCE_DIR}/../..) add_test(rrc_meascfg_test rrc_meascfg_test -i ${CMAKE_CURRENT_SOURCE_DIR}/../..) diff --git a/srsue/hdr/metrics_json.h b/srsue/hdr/metrics_json.h new file mode 100644 index 000000000..fcd863b6a --- /dev/null +++ b/srsue/hdr/metrics_json.h @@ -0,0 +1,42 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +/****************************************************************************** + * File: metrics_json.h + * Description: Metrics class printing to a json file. + *****************************************************************************/ + +#ifndef SRSUE_METRICS_JSON_H +#define SRSUE_METRICS_JSON_H + +#include "srsran/srslog/log_channel.h" +#include "ue_metrics_interface.h" + +namespace srsue { + +class metrics_json : public srsran::metrics_listener +{ +public: + explicit metrics_json(srslog::log_channel& c) : log_c(c) {} + + void set_metrics(const ue_metrics_t& m, const uint32_t period_usec) override; + void set_ue_handle(ue_metrics_interface* ue_); + void stop() override {} + +private: + srslog::log_channel& log_c; + ue_metrics_interface* ue = nullptr; +}; + +} // namespace srsue + +#endif // SRSUE_METRICS_JSON_H diff --git a/srsue/hdr/phy/nr/cc_worker.h b/srsue/hdr/phy/nr/cc_worker.h index ef856c120..20940ca87 100644 --- a/srsue/hdr/phy/nr/cc_worker.h +++ b/srsue/hdr/phy/nr/cc_worker.h @@ -59,6 +59,7 @@ private: std::array tx_buffer = {}; uint32_t buffer_sz = 0; state* phy = nullptr; + srsran_ssb_t ssb = {}; srsran_ue_dl_nr_t ue_dl = {}; srsran_ue_ul_nr_t ue_ul = {}; srslog::basic_logger& logger; diff --git a/srsue/hdr/phy/scell/intra_measure.h b/srsue/hdr/phy/scell/intra_measure.h deleted file mode 100644 index af03a3a62..000000000 --- a/srsue/hdr/phy/scell/intra_measure.h +++ /dev/null @@ -1,227 +0,0 @@ -/** - * Copyright 2013-2021 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN 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. - * - * srsRAN 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/. - * - */ -#ifndef SRSUE_INTRA_MEASURE_H -#define SRSUE_INTRA_MEASURE_H - -#include -#include -#include - -#include "scell_recv.h" - -namespace srsue { -namespace scell { - -// Class to perform intra-frequency measurements -class intra_measure : public srsran::thread -{ - /* - * The intra-cell measurment has 5 different states: - * - idle: it has been initiated and it is waiting to get configured to start capturing samples. From any state - * except quit can transition to idle. - * - wait: waits for at least intra_freq_meas_period_ms since last receive start and goes to receive. - * - receive: captures base-band samples for intra_freq_meas_len_ms and goes to measure. - * - measure: enables the inner thread to start the measuring function. The asynchronous buffer will transition to - * wait as soon as it has read the data from the buffer. - * - quit: stops the inner thread and quits. Transition from any state measure state. - * - * FSM abstraction: - * - * +------+ set_cells_to_meas +------+ intra_freq_meas_period_ms +---------+ - * | Idle | --------------------->| Wait |------------------------------>| Receive | - * +------+ +------+ +---------+ - * ^ ^ | stop +------+ - * | Read buffer | | ----->| Quit | - * init +---------+ intra_freq_meas_len_ms | +------+ - * meas_stop | Measure |<----------------------------------+ - * +---------+ - */ -public: - // Interface for reporting new cell measurements - class meas_itf - { - public: - virtual void cell_meas_reset(uint32_t cc_idx) = 0; - virtual void new_cell_meas(uint32_t cc_idx, const std::vector& meas) = 0; - }; - - /** - * Constructor - */ - intra_measure(srslog::basic_logger& logger); - - /** - * Destructor - */ - ~intra_measure(); - - /** - * Initiation function, necessary to configure main parameters - * @param common SRSUE phy_common instance pointer for providing intra_freq_meas_len_ms and intra_freq_meas_period_ms - * @param rrc SRSUE PHY->RRC interface for supplying the RRC with the measurements - */ - void init(uint32_t cc_idx, phy_common* common, meas_itf* new_cell_itf); - - /** - * Stops the operation of this component - */ - void stop(); - - /** - * Sets the primary cell, configures the cell bandwidth and sampling rate - * @param earfcn Frequency the component is receiving base-band from. Used only for reporting the EARFCN to the RRC - * @param cell Actual cell configuration - */ - void set_primary_cell(uint32_t earfcn, srsran_cell_t cell); - - /** - * Sets receiver gain offset to convert estimated dBFs to dBm in RSRP - * @param rx_gain_offset Gain offset in dB - */ - void set_rx_gain_offset(float rx_gain_offset_db); - - /** - * Sets the PCI list of the cells this components needs to measure and starts the FSM for measuring - * @param pci is the list of PCIs to measure - */ - void set_cells_to_meas(const std::set& pci); - - /** - * Stops the measurment FSM, setting the inner state to idle. - */ - void meas_stop(); - - /** - * Inputs the baseband IQ samples into the component, internal state dictates whether it will be written or not. - * @param tti The current physical layer TTI, used for calculating the buffer write - * @param data buffer with baseband IQ samples - * @param nsamples number of samples to write - */ - void write(uint32_t tti, cf_t* data, uint32_t nsamples); - - /** - * Get EARFCN of this component - * @return EARFCN - */ - uint32_t get_earfcn() { return current_earfcn; }; - - /** - * Synchronous wait mechanism, blocks the writer thread while it is in measure state. If the asynchonous thread is too - * slow, use this method for stalling the writing thread and wait the asynchronous thread to clear the buffer. - */ - void wait_meas() - { // Only used by scell_search_test - state.wait_change(internal_state::measure); - } - -private: - class internal_state ///< Internal state class, provides thread safe state management - { - public: - typedef enum { - idle = 0, ///< Initial state, internal thread runs, it does not capture data - wait, ///< Wait for the period time to pass - receive, ///< Accumulate samples in ring buffer - measure, ///< Module is busy measuring - quit ///< Quit thread, no transitions are allowed - } state_t; - - private: - state_t state = idle; - std::mutex mutex; - std::condition_variable cvar; - - public: - /** - * Get the internal state - * @return protected state - */ - state_t get_state() { return state; } - - /** - * Transitions to a different state, all transitions are allowed except from quit - * @param new_state - */ - void set_state(state_t new_state) - { - std::unique_lock lock(mutex); - // Do not allow transition from quit - if (state != quit) { - state = new_state; - } - - // Notifies to the inner thread about the change of state - cvar.notify_all(); - } - - /** - * Waits for a state transition to a state different than the provided, used for blocking the inner thread - */ - void wait_change(state_t s) - { - std::unique_lock lock(mutex); - while (state == s) { - cvar.wait(lock); - } - } - }; - - internal_state state; - - /** - * Measurement process helper method. Encapusulates the neighbour cell measurement functionality - */ - void measure_proc(); - - /** - * Internal asynchronous low priority thread, waits for measure internal state to execute the measurement process. It - * stops when the internal state transitions to quit. - */ - void run_thread() override; - - ///< Internal Thread priority, low by default - const static int INTRA_FREQ_MEAS_PRIO = DEFAULT_PRIORITY + 5; - - scell_recv scell; - meas_itf* new_cell_itf = nullptr; - srslog::basic_logger& logger; - uint32_t cc_idx = 0; - uint32_t current_earfcn = 0; - uint32_t current_sflen = 0; - srsran_cell_t serving_cell = {}; - std::set active_pci = {}; - std::mutex active_pci_mutex = {}; - uint32_t last_measure_tti = 0; - uint32_t intra_freq_meas_len_ms = 20; - uint32_t intra_freq_meas_period_ms = 200; - uint32_t rx_gain_offset_db = 0; - - cf_t* search_buffer = nullptr; - - srsran_ringbuffer_t ring_buffer = {}; - - srsran_refsignal_dl_sync_t refsignal_dl_sync = {}; -}; - -} // namespace scell -} // namespace srsue - -#endif // SRSUE_INTRA_MEASURE_H diff --git a/srsue/hdr/phy/scell/intra_measure_base.h b/srsue/hdr/phy/scell/intra_measure_base.h new file mode 100644 index 000000000..df6cd2a0e --- /dev/null +++ b/srsue/hdr/phy/scell/intra_measure_base.h @@ -0,0 +1,291 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ +#ifndef SRSUE_INTRA_MEASURE_BASE_H +#define SRSUE_INTRA_MEASURE_BASE_H + +#include "srsran/interfaces/ue_phy_interfaces.h" +#include +#include +#include +#include +#include +#include + +namespace srsue { +namespace scell { + +/** + * @brief Describes a generic base class to perform intra-frequency measurements + */ +class intra_measure_base : public srsran::thread +{ + /* + * The intra-cell measurement has 5 different states: + * - idle: it has been initiated and it is waiting to get configured to start capturing samples. From any state + * except quit can transition to idle. + * - wait: waits for the TTI trigger to transition to receive + * - receive: captures base-band samples for intra_freq_meas_len_ms and goes to measure. + * - measure: enables the inner thread to start the measuring function. The asynchronous buffer will transition to + * wait as soon as it has read the data from the buffer. + * - quit: stops the inner thread and quits. Transition from any state measure state. + * + * FSM abstraction: + * + * +------+ set_cells_to_meas +------+ receive_tti_trigger +---------+ + * | Idle | --------------------->| Wait |------------------------------>| Receive | + * +------+ +------+ +---------+ + * ^ ^ | stop +------+ + * | Read buffer | | ----->| Quit | + * init +---------+ intra_freq_meas_len_ms | +------+ + * meas_stop | Measure |<----------------------------------+ + * +---------+ + * + * This class has been designed to be thread safe. Any method can be called from different threads as long as + * init_generic is called when the FSM is in idle. + */ +public: + /** + * @brief Describes an interface for reporting new cell measurements + */ + class meas_itf + { + public: + virtual void cell_meas_reset(uint32_t cc_idx) = 0; + virtual void new_cell_meas(uint32_t cc_idx, const std::vector& meas) = 0; + }; + + /** + * @brief Describes the default generic configuration arguments + */ + struct args_t { + double srate_hz = 0.0; ///< Sampling rate in Hz, optional for LTE, compulsory for NR + uint32_t len_ms = 20; ///< Amount of time to accumulate + uint32_t period_ms = 200; ///< Minimum time interval between measurements, set to 0 for free-run + uint32_t tti_period = 0; ///< Measurement TTI trigger period, set to 0 to trigger at any TTI + uint32_t tti_offset = 0; ///< Measurement TTI trigger offset + float rx_gain_offset_db = 0.0f; ///< Gain offset, for calibrated measurements + }; + + /** + * @brief Stops the operation of this component and it cannot be started again + * @note use meas_stop() method to stop measurements temporally + */ + void stop(); + + /** + * @brief Updates the receiver gain offset to convert estimated dBFs to dBm in RSRP + * @param rx_gain_offset Gain offset in dB + */ + void set_rx_gain_offset(float rx_gain_offset_db); + + /** + * @brief Sets the PCI list of the cells this components needs to measure and starts the FSM for measuring + * @param pci is the list of PCIs to measure + */ + void set_cells_to_meas(const std::set& pci); + + /** + * @brief Stops the measurement FSM, setting the inner state to idle. + */ + void meas_stop(); + + /** + * @brief Inputs the baseband IQ samples into the component, internal state dictates whether it will be written or + * not. + * @param tti The current physical layer TTI, used for calculating the buffer write + * @param data buffer with baseband IQ samples + * @param nsamples number of samples to write + */ + void run_tti(uint32_t tti, cf_t* data, uint32_t nsamples); + + /** + * @brief Get EARFCN of this component + * @return EARFCN + */ + virtual uint32_t get_earfcn() const = 0; + + /** + * @brief Synchronous wait mechanism, blocks the writer thread while it is in measure state. If the asynchronous + * thread is too slow, use this method for stalling the writing thread and wait the asynchronous thread to clear the + * buffer. + */ + void wait_meas() + { // Only used by scell_search_test + state.wait_change(internal_state::measure); + } + +protected: + struct measure_context_t { + uint32_t cc_idx = 0; ///< Component carrier index + float rx_gain_offset_db = 0.0f; ///< Current gain offset + std::set active_pci = {}; ///< Set with the active PCIs + uint32_t sf_len = 0; ///< Subframe length in samples + uint32_t meas_len_ms = 20; ///< Measure length in milliseconds/sub-frames + uint32_t meas_period_ms = 200; ///< Minimum time between measurements + uint32_t trigger_tti_period = 0; ///< Measurement TTI trigger period + uint32_t trigger_tti_offset = 0; ///< Measurement TTI trigger offset + meas_itf& new_cell_itf; + + explicit measure_context_t(meas_itf& new_cell_itf_) : new_cell_itf(new_cell_itf_) {} + }; + + /** + * @brief Generic initialization method, necessary to configure main parameters + * @param cc_idx_ Indicates the component carrier index linked to the intra frequency measurement instance + * @param args Generic configuration arguments + */ + void init_generic(uint32_t cc_idx_, const args_t& args); + + /** + * @brief Constructor is only accessible through inherited classes + */ + intra_measure_base(srslog::basic_logger& logger, meas_itf& new_cell_itf_); + + /** + * @brief Destructor is only accessible through inherited classes + */ + ~intra_measure_base() override; + + /** + * @brief Subframe length setter, the inherited class shall set the subframe length + * @param new_sf_len New subframe length + */ + void set_current_sf_len(uint32_t new_sf_len) { context.sf_len = new_sf_len; } + +private: + /** + * @brief Describes the internal state class, provides thread safe state management + */ + class internal_state + { + public: + typedef enum { + initial = 0, /// Initial state, it transitions to idle once the internal thread has started + idle, ///< Internal thread runs, it does not capture data + wait_first, ///< Wait for the TTI trigger (if configured) + wait, ///< Wait for the period time to pass + receive, ///< Accumulate samples in ring buffer + measure, ///< Module is busy measuring + quit ///< Quit thread, no transitions are allowed + } state_t; + + private: + state_t state = initial; + std::mutex mutex; + std::condition_variable cvar; + + public: + /** + * @brief Get the internal state + * @return protected state + */ + state_t get_state() const { return state; } + + /** + * @brief Transitions to a different state, all transitions are allowed except from quit + * @param new_state + */ + void set_state(state_t new_state) + { + std::unique_lock lock(mutex); + // Do not allow transition from quit + if (state != quit) { + state = new_state; + } + + // Notifies to the inner thread about the change of state + cvar.notify_all(); + } + + /** + * @brief Waits for a state transition to a state different than the provided, used for blocking the inner thread + */ + void wait_change(state_t s) + { + std::unique_lock lock(mutex); + while (state == s) { + cvar.wait(lock); + } + } + }; + + /** + * @brief Computes the measurement trigger based on TTI and the last TTI trigger + */ + bool receive_tti_trigger(uint32_t tti) const + { + // If the elapsed time does not satisfy with the minimum time, do not trigger + uint32_t elapsed_tti = TTI_SUB(tti, last_measure_tti); + if (elapsed_tti < context.meas_period_ms and state.get_state() != internal_state::wait_first) { + return false; + } + + // If the TTI period is not configured, it will be always true + if (context.trigger_tti_period == 0) { + return true; + } + + // Check if trigger condition is satisfied + return tti % context.trigger_tti_period == context.trigger_tti_offset; + } + + /** + * @brief Writes baseband data in the internal soft-buffer + * @param data Provides baseband data + * @param nsamples Number of samples to write + */ + void write(cf_t* data, uint32_t nsamples); + + /** + * @brief Get the Radio Access Technology (RAT) that is being measured + * @return The measured RAT + */ + virtual srsran::srsran_rat_t get_rat() const = 0; + + /** + * @brief Pure virtual function to perform measurements + * @note The context is pass-by-value to protect it from concurrency. However, the buffer is pass-by-reference + * as it is protected by the state. + * @param context Provides current measurement context + * @param buffer Provides current measurement context + * @return True if the measurement functions are executed without errors, otherwise false + */ + virtual bool measure_rat(measure_context_t context, std::vector& buffer) = 0; + + /** + * @brief Measurement process helper method. Encapsulates the neighbour cell measurement functionality + */ + void measure_proc(); + + /** + * @brief Internal asynchronous low priority thread, waits for measure internal state to execute the measurement + * process. It stops when the internal state transitions to quit. + */ + void run_thread() override; + + ///< Internal Thread priority, low by default + const static int INTRA_FREQ_MEAS_PRIO = DEFAULT_PRIORITY + 5; + + internal_state state; + srslog::basic_logger& logger; + mutable std::mutex active_pci_mutex = {}; + uint32_t last_measure_tti = 0; + measure_context_t context; + + std::vector search_buffer; + srsran_ringbuffer_t ring_buffer = {}; +}; + +} // namespace scell +} // namespace srsue + +#endif // SRSUE_INTRA_MEASURE_BASE_H diff --git a/srsue/hdr/phy/scell/intra_measure_lte.h b/srsue/hdr/phy/scell/intra_measure_lte.h new file mode 100644 index 000000000..92692e6d1 --- /dev/null +++ b/srsue/hdr/phy/scell/intra_measure_lte.h @@ -0,0 +1,86 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ +#ifndef SRSRAN_INTRA_MEASURE_LTE_H +#define SRSRAN_INTRA_MEASURE_LTE_H + +#include "intra_measure_base.h" +#include "scell_recv.h" +#include + +namespace srsue { +namespace scell { + +/** + * @brief Describes a class for performing LTE intra-frequency cell search and measurement + */ +class intra_measure_lte : public intra_measure_base +{ +public: + /** + * @brief Constructor + * @param logger Logging object + * @param new_meas_itf_ Interface to report measurement to higher layers + */ + intra_measure_lte(srslog::basic_logger& logger, meas_itf& new_meas_itf_); + + /** + * @brief Destructor + */ + ~intra_measure_lte() override; + + /** + * @brief Initialises LTE specific measurement objects + * @param args Configuration arguments + */ + void init(uint32_t cc_idx, const args_t& args); + + /** + * @brief Sets the primary cell and selects LTE operation mode, configures the cell bandwidth and sampling rate + * @param earfcn Frequency the component is receiving base-band from. Used only for reporting the EARFCN to the RRC + * @param cell Actual cell configuration + */ + void set_primary_cell(uint32_t earfcn, srsran_cell_t cell); + + /** + * @brief Get EARFCN of this component + * @return EARFCN + */ + uint32_t get_earfcn() const override { return current_earfcn; }; + +private: + /** + * @brief Provides with the RAT to the base class + * @return The RAT measured by this class which is LTE + */ + srsran::srsran_rat_t get_rat() const override { return srsran::srsran_rat_t::lte; } + + /** + * @brief LTE specific measurement process + * @param context Measurement context + * @param buffer Provides the baseband buffer to perform the measurements + * @return True if no error happens, otherwise false + */ + bool measure_rat(measure_context_t context, std::vector& buffer) override; + + srslog::basic_logger& logger; + srsran_cell_t serving_cell = {}; ///< Current serving cell in the EARFCN, to avoid reporting it + uint32_t current_earfcn; ///< Current EARFCN + + /// LTE-based measuring objects + scell_recv scell_rx; ///< Secondary cell searcher + srsran_refsignal_dl_sync_t refsignal_dl_sync = {}; ///< Reference signal based measurement +}; + +} // namespace scell +} // namespace srsue + +#endif // SRSRAN_INTRA_MEASURE_LTE_H diff --git a/srsue/hdr/phy/scell/intra_measure_nr.h b/srsue/hdr/phy/scell/intra_measure_nr.h new file mode 100644 index 000000000..6a112d2c9 --- /dev/null +++ b/srsue/hdr/phy/scell/intra_measure_nr.h @@ -0,0 +1,131 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ +#ifndef SRSRAN_INTRA_MEASURE_NR_H +#define SRSRAN_INTRA_MEASURE_NR_H + +#include "intra_measure_base.h" +#include + +namespace srsue { +namespace scell { + +/** + * @brief Describes a class for performing LTE intra-frequency cell search and measurement + */ +class intra_measure_nr : public intra_measure_base +{ +public: + /** + * @brief Describes initialization arguments. It is used to preallocate all memory and avoiding performing memory + * allocation when the configuration is set + */ + struct args_t { + float rx_gain_offset_dB = 0.0f; + uint32_t max_len_ms = 1; + double max_srate_hz = 61.44e6; + srsran_subcarrier_spacing_t min_scs = srsran_subcarrier_spacing_15kHz; + float thr_snr_db = 5.0f; ///< minimum SNR threshold + }; + + /** + * @brief Describes the required configuration arguments to start measurements + */ + struct config_t : public intra_measure_base::args_t { + /// Additional fields to the base arguments + uint32_t arfcn; ///< Carrier frequency in ARFCN + double center_freq_hz = 0.0; ///< Base-band center frequency in Hz + double ssb_freq_hz = 0.0; ///< SSB center frequency + srsran_subcarrier_spacing_t scs = srsran_subcarrier_spacing_30kHz; ///< SSB configured Subcarrier spacing + int serving_cell_pci = -1; ///< Current serving cell PCI, set to -1 if no + ///< serving cell has been configured for this + ///< carrier + }; + + /** + * @brief Constructor + * @param logger Logging object + * @param new_meas_itf_ Interface to report measurement to higher layers + */ + intra_measure_nr(srslog::basic_logger& logger, meas_itf& new_meas_itf_); + + /** + * @brief Destructor + */ + ~intra_measure_nr() override; + + /** + * @brief Initialises LTE specific measurement objects + * @param args Configuration arguments + * @return True if initialization is successful, false otherwise + */ + bool init(uint32_t cc_idx, const args_t& args); + + /** + * @brief Sets the primary cell and selects NR operation mode, configures the cell bandwidth and sampling rate + * @param arfcn Frequency the component is receiving base-band from. Used only for reporting the ARFCN to the RRC + * @param cfg Actual configuration + * @return True if configuration is successful, false otherwise + */ + bool set_config(uint32_t arfcn, const config_t& cfg); + + /** + * @brief Get current frequency number + * @return the current ARFCN + */ + uint32_t get_earfcn() const override { return current_arfcn; }; + + /** + * @brief Computes the average measurement performance since last configuration + * @return The performance in Millions of samples per second + */ + uint32_t get_perf() const + { + if (perf_count_us == 0) { + return 0; + } + return (uint32_t)(perf_count_samples / perf_count_us); + }; + +private: + /** + * @brief Provides with the RAT to the base class + * @return The RAT measured by this class which is NR + */ + srsran::srsran_rat_t get_rat() const override { return srsran::srsran_rat_t::nr; } + + /** + * @brief NR specific measurement process + * @attention It searches and measures the SSB with best SNR + * @param context Measurement context + * @param buffer Provides the baseband buffer to perform the measurements + * @return True if no error happen, otherwise false + */ + bool measure_rat(measure_context_t context, std::vector& buffer) override; + + srslog::basic_logger& logger; + uint32_t cc_idx = 0; + uint32_t current_arfcn = 0; + float thr_snr_db = 5.0f; + int serving_cell_pci = -1; + + /// Performance + uint64_t perf_count_us = 0; ///< Counts execution time in microseconds + uint64_t perf_count_samples = 0; ///< Counts the number samples + + /// NR-based measuring objects + srsran_ssb_t ssb = {}; ///< SS/PBCH Block +}; + +} // namespace scell +} // namespace srsue + +#endif // SRSRAN_INTRA_MEASURE_NR_H diff --git a/srsue/hdr/phy/scell/scell_recv.h b/srsue/hdr/phy/scell/scell_recv.h index 499c9240b..e935df195 100644 --- a/srsue/hdr/phy/scell/scell_recv.h +++ b/srsue/hdr/phy/scell/scell_recv.h @@ -35,18 +35,26 @@ class scell_recv public: explicit scell_recv(srslog::basic_logger& logger) : logger(logger) {} - void init(uint32_t max_sf_window); - void deinit(); - void reset(); - std::set find_cells(const cf_t* input_buffer, const srsran_cell_t serving_cell, const uint32_t nof_sf); + void init(uint32_t max_sf_window); + void deinit(); + void reset(); -private: - // 36.133 9.1.2.1 for band 7 - constexpr static float ABSOLUTE_RSRP_THRESHOLD_DBM = -125; + /** + * @brief Find neighbour cells in a given buffer + * @param input_buffer Provides the baseband samples + * @param serving_cell Current serving cell + * @param nof_sf Number of subframes contained in the baseband buffer + * @param found_cell_ids Provides a set where to insert the found cell identifiers (PCIs) + */ + void find_cells(const cf_t* input_buffer, + const srsran_cell_t& serving_cell, + const uint32_t& nof_sf, + std::set& found_cell_ids); - cf_t* sf_buffer[SRSRAN_MAX_PORTS]; +private: + cf_t* sf_buffer[SRSRAN_MAX_PORTS] = {}; srslog::basic_logger& logger; - srsran_sync_t sync_find; + srsran_sync_t sync_find = {}; uint32_t current_fft_sz; }; diff --git a/srsue/hdr/phy/search.h b/srsue/hdr/phy/search.h index 9d8f18f58..b065e36cc 100644 --- a/srsue/hdr/phy/search.h +++ b/srsue/hdr/phy/search.h @@ -50,6 +50,7 @@ public: float get_last_cfo(); void set_agc_enable(bool enable); ret_code run(srsran_cell_t* cell, std::array& bch_payload); + void set_cp_en(bool enable); private: search_callback* p = nullptr; diff --git a/srsue/hdr/phy/sync.h b/srsue/hdr/phy/sync.h index 871ec1907..084134a85 100644 --- a/srsue/hdr/phy/sync.h +++ b/srsue/hdr/phy/sync.h @@ -29,7 +29,7 @@ #include "phy_common.h" #include "prach.h" -#include "scell/intra_measure.h" +#include "scell/intra_measure_lte.h" #include "scell/scell_sync.h" #include "search.h" #include "sfn_sync.h" @@ -51,7 +51,7 @@ class sync : public srsran::thread, public rsrp_insync_itf, public search_callback, public scell::sync_callback, - public scell::intra_measure::meas_itf + public scell::intra_measure_base::meas_itf { public: sync(srslog::basic_logger& phy_logger, srslog::basic_logger& phy_lib_logger) : @@ -205,9 +205,9 @@ private: bool forced_rx_time_init = true; // Rx time sync after first receive from radio // Objects for internal use - search search_p; - sfn_sync sfn_p; - std::vector > intra_freq_meas; + search search_p; + sfn_sync sfn_p; + std::vector > intra_freq_meas; // Pointers to other classes stack_interface_phy_lte* stack = nullptr; diff --git a/srsue/hdr/ue.h b/srsue/hdr/ue.h index 3c495380f..12d9667b5 100644 --- a/srsue/hdr/ue.h +++ b/srsue/hdr/ue.h @@ -70,6 +70,8 @@ typedef struct { bool metrics_csv_append; int metrics_csv_flush_period_sec; std::string metrics_csv_filename; + bool metrics_json_enable; + std::string metrics_json_filename; bool tracing_enable; std::string tracing_filename; std::size_t tracing_buffcapacity; diff --git a/srsue/src/CMakeLists.txt b/srsue/src/CMakeLists.txt index 37a3a40a3..0a4ddec4a 100644 --- a/srsue/src/CMakeLists.txt +++ b/srsue/src/CMakeLists.txt @@ -30,7 +30,7 @@ if (RPATH) set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) endif (RPATH) -add_executable(srsue main.cc ue.cc metrics_stdout.cc metrics_csv.cc) +add_executable(srsue main.cc ue.cc metrics_stdout.cc metrics_csv.cc metrics_json.cc) set(SRSUE_SOURCES srsue_phy srsue_stack srsue_upper srsue_mac srsue_rrc srslog system) set(SRSRAN_SOURCES srsran_common srsran_mac srsran_phy srsran_radio srsran_upper rrc_asn1 srslog system) diff --git a/srsue/src/main.cc b/srsue/src/main.cc index 865aec32c..5995b6f05 100644 --- a/srsue/src/main.cc +++ b/srsue/src/main.cc @@ -29,6 +29,7 @@ #include "srsran/srsran.h" #include "srsran/version.h" #include "srsue/hdr/metrics_csv.h" +#include "srsue/hdr/metrics_json.h" #include "srsue/hdr/metrics_stdout.h" #include "srsue/hdr/ue.h" #include @@ -384,6 +385,10 @@ static int parse_args(all_args_t* args, int argc, char* argv[]) bpo::value(&args->phy.force_ul_amplitude)->default_value(0.0), "Forces the peak amplitude in the PUCCH, PUSCH and SRS (set 0.0 to 1.0, set to 0 or negative for disabling)") + ("phy.detect_cp", + bpo::value(&args->phy.detect_cp)->default_value(false), + "enable CP length detection") + ("phy.in_sync_rsrp_dbm_th", bpo::value(&args->phy.in_sync_rsrp_dbm_th)->default_value(-130.0f), "RSRP threshold (in dBm) above which the UE considers to be in-sync") @@ -438,6 +443,14 @@ static int parse_args(all_args_t* args, int argc, char* argv[]) bpo::value(&args->general.metrics_csv_flush_period_sec)->default_value(-1), "Periodicity in s to flush CSV file to disk (-1 for auto)") + ("general.metrics_json_enable", + bpo::value(&args->general.metrics_json_enable)->default_value(false), + "Write UE metrics to a JSON file") + + ("general.metrics_json_filename", + bpo::value(&args->general.metrics_json_filename)->default_value("/tmp/ue_metrics.json"), + "Metrics JSON filename") + ("general.tracing_enable", bpo::value(&args->general.tracing_enable)->default_value(false), "Events tracing") @@ -713,6 +726,18 @@ int main(int argc, char* argv[]) } } + // Set up the JSON log channel used by metrics. + srslog::sink& json_sink = + srslog::fetch_file_sink(args.general.metrics_json_filename, 0, srslog::create_json_formatter()); + srslog::log_channel& json_channel = srslog::fetch_log_channel("JSON_channel", json_sink, {}); + json_channel.set_enabled(args.general.metrics_json_enable); + + srsue::metrics_json json_metrics(json_channel); + if (args.general.metrics_json_enable) { + metricshub.add_listener(&json_metrics); + json_metrics.set_ue_handle(&ue); + } + pthread_t input; pthread_create(&input, nullptr, &input_loop, &args); diff --git a/srsue/src/metrics_json.cc b/srsue/src/metrics_json.cc new file mode 100644 index 000000000..a012a8a74 --- /dev/null +++ b/srsue/src/metrics_json.cc @@ -0,0 +1,229 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsue/hdr/metrics_json.h" +#include "srsran/srslog/context.h" + +using namespace srsue; + +void metrics_json::set_ue_handle(ue_metrics_interface* ue_) +{ + ue = ue_; +} + +namespace { + +/// Shared metrics between containers. +DECLARE_METRIC("pci", metric_pci, uint32_t, ""); +DECLARE_METRIC("dl_bitrate", metric_dl_brate, float, ""); +DECLARE_METRIC("ul_bitrate", metric_ul_brate, float, ""); +DECLARE_METRIC("rsrp", metric_rsrp, float, ""); + +/// MAC container. +DECLARE_METRIC("dl_bler", metric_dl_bler, float, ""); +DECLARE_METRIC("ul_bler", metric_ul_bler, float, ""); +DECLARE_METRIC("ul_buff", metric_ul_buff, uint32_t, ""); +DECLARE_METRIC_SET("mac_container", + mset_mac_container, + metric_dl_brate, + metric_dl_bler, + metric_ul_brate, + metric_ul_bler, + metric_ul_buff); + +/// Carrier container. +DECLARE_METRIC("earfcn", metric_earfcn, uint32_t, ""); +DECLARE_METRIC("pathloss", metric_pathloss, float, ""); +DECLARE_METRIC("cfo", metric_cfo, float, ""); +DECLARE_METRIC("dl_snr", metric_dl_snr, float, ""); +DECLARE_METRIC("dl_mcs", metric_dl_mcs, float, ""); +DECLARE_METRIC("ul_mcs", metric_ul_mcs, float, ""); +DECLARE_METRIC("ul_ta", metric_ul_ta, float, ""); +DECLARE_METRIC("distance_km", metric_distance_km, float, ""); +DECLARE_METRIC("speed_kmph", metric_speed_kmph, float, ""); +DECLARE_METRIC_SET("carrier_container", + mset_carrier_container, + metric_earfcn, + metric_pci, + metric_rsrp, + metric_pathloss, + metric_cfo, + metric_dl_snr, + metric_dl_mcs, + metric_ul_mcs, + metric_ul_ta, + metric_distance_km, + metric_speed_kmph, + mset_mac_container); +DECLARE_METRIC_LIST("carrier_list", mlist_carriers, std::vector); + +/// GW container. +DECLARE_METRIC_SET("gw_container", mset_gw_container, metric_dl_brate, metric_ul_brate); + +/// RRC container. +DECLARE_METRIC("rrc_state", metric_rrc_state, std::string, ""); +DECLARE_METRIC_SET("rrc_container", mset_rrc_container, metric_rrc_state); + +/// Neighbour cell list. +DECLARE_METRIC_SET("neighbour_cell_container", mset_neighbour_cell_container, metric_pci, metric_rsrp, metric_cfo); +DECLARE_METRIC_LIST("neighbour_cell_list", mlist_neighbours, std::vector); + +/// NAS container. +DECLARE_METRIC("emm_state", metric_emm_state, std::string, ""); +DECLARE_METRIC_SET("nas_container", mset_nas_container, metric_emm_state); + +/// RF container. +DECLARE_METRIC("rf_o", metric_rf_o, uint32_t, ""); +DECLARE_METRIC("rf_u", metric_rf_u, uint32_t, ""); +DECLARE_METRIC("rf_l", metric_rf_l, uint32_t, ""); +DECLARE_METRIC_SET("rf_container", mset_rf_container, metric_rf_o, metric_rf_u, metric_rf_l); + +/// System memory container. +DECLARE_METRIC("proc_realmem_percent", metric_proc_rmem_percent, uint32_t, ""); +DECLARE_METRIC("proc_realmem_kB", metric_proc_rmem_kB, uint32_t, ""); +DECLARE_METRIC("proc_vmem_kB", metric_proc_vmem_kB, uint32_t, ""); +DECLARE_METRIC("sys_mem_usage_percent", metric_sys_mem_percent, uint32_t, ""); +DECLARE_METRIC_SET("sys_memory_container", + mset_sys_mem_container, + metric_proc_rmem_percent, + metric_proc_rmem_kB, + metric_proc_vmem_kB, + metric_sys_mem_percent); + +/// System CPU container +DECLARE_METRIC("proc_cpu_usage", metric_proc_cpu_usage, uint32_t, ""); +DECLARE_METRIC("proc_thread_count", metric_thread_count, uint32_t, ""); +DECLARE_METRIC("sys_core_usage", metric_proc_core_usage, uint32_t, ""); +DECLARE_METRIC_SET("cpu_core_container", mset_cpu_core_container, metric_proc_core_usage); +DECLARE_METRIC_LIST("cpu_core_list", mlist_cpu_core_list, std::vector); +DECLARE_METRIC_SET("sys_cpu_container", + mset_sys_cpu_container, + metric_proc_cpu_usage, + metric_thread_count, + mlist_cpu_core_list); + +/// Metrics root object. +DECLARE_METRIC("type", metric_type_tag, std::string, ""); +DECLARE_METRIC("timestamp", metric_timestamp_tag, double, ""); + +/// Metrics context. +using metric_context_t = srslog::build_context_type; + +} // namespace + +/// Returns the current time in seconds with ms precision since UNIX epoch. +static double get_time_stamp() +{ + auto tp = std::chrono::system_clock::now().time_since_epoch(); + return std::chrono::duration_cast(tp).count() * 1e-3; +} + +void metrics_json::set_metrics(const ue_metrics_t& metrics, const uint32_t period_usec) +{ + if (!ue) { + return; + } + + metric_context_t ctx("JSON Metrics"); + + // Fill root object. + ctx.write("metrics"); + + // Fill cc container. + auto& carrier_list = ctx.get(); + carrier_list.resize(metrics.phy.nof_active_cc); + for (uint32_t i = 0, e = carrier_list.size(); i != e; ++i) { + auto& carrier = carrier_list[i]; + + // PHY. + carrier.write(metrics.phy.info[i].dl_earfcn); + carrier.write(metrics.phy.info[i].pci); + + carrier.write(metrics.phy.ch[i].rsrp); + carrier.write(metrics.phy.ch[i].pathloss); + + carrier.write(metrics.phy.sync[i].cfo); + + carrier.write(metrics.phy.ch[i].sinr); + carrier.write(metrics.phy.dl[i].mcs); + carrier.write(metrics.phy.ul[i].mcs); + carrier.write(metrics.phy.sync[i].ta_us); + carrier.write(metrics.phy.sync[i].distance_km); + carrier.write(metrics.phy.sync[i].speed_kmph); + + // MAC + carrier.get().write(metrics.stack.mac[i].rx_brate / + metrics.stack.mac[i].nof_tti * 1e-3); + carrier.get().write( + (metrics.stack.mac[i].rx_pkts > 0) ? (float)100 * metrics.stack.mac[i].rx_errors / metrics.stack.mac[i].rx_pkts + : 0); + carrier.get().write(metrics.stack.mac[i].tx_brate / + metrics.stack.mac[i].nof_tti * 1e-3); + carrier.get().write( + (metrics.stack.mac[i].tx_pkts > 0) ? (float)100 * metrics.stack.mac[i].tx_errors / metrics.stack.mac[i].tx_pkts + : 0); + carrier.get().write(metrics.stack.mac[i].ul_buffer); + } + + // Fill GW container. + ctx.get().write(metrics.gw.dl_tput_mbps); + ctx.get().write(metrics.gw.ul_tput_mbps); + + // Fill RRC container. + ctx.get().write(rrc_state_text[metrics.stack.rrc.state]); + + // Fill neighbour list. + auto& neighbour_list = ctx.get(); + neighbour_list.resize(metrics.stack.rrc.neighbour_cells.size()); + for (uint32_t i = 0, e = neighbour_list.size(); i != e; ++i) { + auto& neigbour = neighbour_list[i]; + neigbour.write(metrics.stack.rrc.neighbour_cells[i].pci); + neigbour.write(metrics.stack.rrc.neighbour_cells[i].rsrp); + neigbour.write(metrics.stack.rrc.neighbour_cells[i].cfo_hz); + } + + // Fill NAS container. + ctx.get().write(emm_state_text(metrics.stack.nas.state)); + + // Fill RF container. + ctx.get().write(metrics.rf.rf_o); + ctx.get().write(metrics.rf.rf_u); + ctx.get().write(metrics.rf.rf_l); + + // Fill system memory container. + ctx.get().write(metrics.sys.process_realmem); + ctx.get().write(metrics.sys.process_realmem_kB); + ctx.get().write(metrics.sys.process_virtualmem_kB); + ctx.get().write(metrics.sys.system_mem); + + // Fill system CPU container. + ctx.get().write(metrics.sys.process_cpu_usage); + ctx.get().write(metrics.sys.thread_count); + auto& core_list = ctx.get().get(); + core_list.resize(metrics.sys.cpu_count); + for (uint32_t i = 0, e = core_list.size(); i != e; ++i) { + core_list[i].write(metrics.sys.cpu_load[i]); + } + + // Log the context. + ctx.write(get_time_stamp()); + log_c(ctx); +} diff --git a/srsue/src/phy/nr/cc_worker.cc b/srsue/src/phy/nr/cc_worker.cc index 3ba1f7e9b..98f4953de 100644 --- a/srsue/src/phy/nr/cc_worker.cc +++ b/srsue/src/phy/nr/cc_worker.cc @@ -20,6 +20,7 @@ */ #include "srsue/hdr/phy/nr/cc_worker.h" +#include "srsran/common/band_helper.h" #include "srsran/common/buffer_pool.h" #include "srsran/srsran.h" @@ -47,12 +48,20 @@ cc_worker::cc_worker(uint32_t cc_idx_, srslog::basic_logger& log, state* phy_sta ERROR("Error initiating UE DL NR"); return; } + + srsran_ssb_args_t ssb_args = {}; + ssb_args.enable_measure = true; + if (srsran_ssb_init(&ssb, &ssb_args) < SRSRAN_SUCCESS) { + ERROR("Error initiating SSB"); + return; + } } cc_worker::~cc_worker() { srsran_ue_dl_nr_free(&ue_dl); srsran_ue_ul_nr_free(&ue_ul); + srsran_ssb_free(&ssb); for (cf_t* p : rx_buffer) { if (p != nullptr) { free(p); @@ -83,6 +92,29 @@ bool cc_worker::update_cfg() return false; } + double abs_freq_point_a_freq = + srsran::srsran_band_helper().nr_arfcn_to_freq(phy->cfg.carrier.absolute_frequency_point_a); + double abs_freq_ssb_freq = srsran::srsran_band_helper().nr_arfcn_to_freq(phy->cfg.carrier.absolute_frequency_ssb); + + double carrier_center_freq = abs_freq_point_a_freq + (phy->cfg.carrier.nof_prb / 2 * + SRSRAN_SUBC_SPACING_NR(phy->cfg.carrier.scs) * SRSRAN_NRE); + uint16_t band = srsran::srsran_band_helper().get_band_from_dl_freq_Hz(carrier_center_freq); + + srsran_ssb_cfg_t ssb_cfg = {}; + ssb_cfg.srate_hz = srsran_min_symbol_sz_rb(phy->cfg.carrier.nof_prb) * SRSRAN_SUBC_SPACING_NR(phy->cfg.carrier.scs); + ssb_cfg.center_freq_hz = carrier_center_freq; + ssb_cfg.ssb_freq_hz = abs_freq_ssb_freq; + ssb_cfg.scs = phy->cfg.ssb.scs; + ssb_cfg.pattern = srsran::srsran_band_helper().get_ssb_pattern(band, phy->cfg.ssb.scs); + memcpy(ssb_cfg.position, phy->cfg.ssb.position_in_burst.data(), sizeof(bool) * SRSRAN_SSB_NOF_POSITION); + ssb_cfg.duplex_mode = srsran::srsran_band_helper().get_duplex_mode(band); + ssb_cfg.periodicity_ms = phy->cfg.ssb.periodicity_ms; + + if (srsran_ssb_set_cfg(&ssb, &ssb_cfg) < SRSRAN_SUCCESS) { + logger.error("Error setting SSB configuration"); + return false; + } + configured = true; return true; @@ -307,6 +339,37 @@ bool cc_worker::decode_pdsch_dl() bool cc_worker::measure_csi() { + // Measure SSB CSI + if (srsran_ssb_send(&ssb, dl_slot_cfg.idx)) { + srsran_csi_trs_measurements_t meas = {}; + + if (srsran_ssb_csi_measure(&ssb, phy->cfg.carrier.pci, rx_buffer[0], &meas) < SRSRAN_SUCCESS) { + logger.error("Error measuring SSB"); + return false; + } + + if (logger.debug.enabled()) { + std::array str = {}; + srsran_csi_meas_info(&meas, str.data(), (uint32_t)str.size()); + 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); + } + // Iterate all NZP-CSI-RS marked as TRS and perform channel measurements 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 @@ -330,10 +393,10 @@ bool cc_worker::measure_csi() continue; } - if (logger.info.enabled()) { + if (logger.debug.enabled()) { std::array str = {}; srsran_csi_meas_info(&trs_measurements, str.data(), (uint32_t)str.size()); - logger.info("NZP-CSI-RS (TRS): id=%d %s", resource_set_id, str.data()); + logger.debug("NZP-CSI-RS (TRS): id=%d %s", resource_set_id, str.data()); } // Compute channel metrics and push it @@ -385,11 +448,11 @@ bool cc_worker::measure_csi() continue; } - logger.info("NZP-CSI-RS: id=%d, rsrp=%+.1f epre=%+.1f snr=%+.1f", - resource_set_id, - measurements.wideband_rsrp_dBm, - measurements.wideband_epre_dBm, - measurements.wideband_snr_db); + logger.debug("NZP-CSI-RS: id=%d, rsrp=%+.1f epre=%+.1f snr=%+.1f", + resource_set_id, + measurements.wideband_rsrp_dBm, + measurements.wideband_epre_dBm, + measurements.wideband_snr_db); // Report new measurement to the PHY state phy->new_nzp_csi_rs_channel_measurement(measurements, resource_set_id); @@ -425,7 +488,7 @@ bool cc_worker::work_dl() return false; } - // Measure CSI-RS + // Measure CSI if (not measure_csi()) { logger.error("Error measuring, aborting work DL"); return false; diff --git a/srsue/src/phy/scell/intra_measure.cc b/srsue/src/phy/scell/intra_measure.cc deleted file mode 100644 index ee8c9284e..000000000 --- a/srsue/src/phy/scell/intra_measure.cc +++ /dev/null @@ -1,257 +0,0 @@ -/** - * Copyright 2013-2021 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN 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. - * - * srsRAN 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 "srsue/hdr/phy/scell/intra_measure.h" - -#define Error(fmt, ...) \ - if (SRSRAN_DEBUG_ENABLED) \ - logger.error(fmt, ##__VA_ARGS__) -#define Warning(fmt, ...) \ - if (SRSRAN_DEBUG_ENABLED) \ - logger.warning(fmt, ##__VA_ARGS__) -#define Info(fmt, ...) \ - if (SRSRAN_DEBUG_ENABLED) \ - logger.info(fmt, ##__VA_ARGS__) -#define Debug(fmt, ...) \ - if (SRSRAN_DEBUG_ENABLED) \ - logger.debug(fmt, ##__VA_ARGS__) - -namespace srsue { -namespace scell { - -intra_measure::intra_measure(srslog::basic_logger& logger) : scell(logger), logger(logger), thread("SYNC_INTRA_MEASURE") -{} - -intra_measure::~intra_measure() -{ - srsran_ringbuffer_free(&ring_buffer); - scell.deinit(); - free(search_buffer); -} - -void intra_measure::init(uint32_t cc_idx_, phy_common* common, meas_itf* new_cell_itf_) -{ - cc_idx = cc_idx_; - new_cell_itf = new_cell_itf_; - - if (common) { - intra_freq_meas_len_ms = common->args->intra_freq_meas_len_ms; - intra_freq_meas_period_ms = common->args->intra_freq_meas_period_ms; - rx_gain_offset_db = common->args->rx_gain_offset; - } - - // Initialise Reference signal measurement - srsran_refsignal_dl_sync_init(&refsignal_dl_sync); - - // Start scell - scell.init(intra_freq_meas_len_ms); - - search_buffer = srsran_vec_cf_malloc(intra_freq_meas_len_ms * SRSRAN_SF_LEN_PRB(SRSRAN_MAX_PRB)); - - // Initialise buffer for the maximum number of PRB - uint32_t max_required_bytes = (uint32_t)sizeof(cf_t) * intra_freq_meas_len_ms * SRSRAN_SF_LEN_PRB(SRSRAN_MAX_PRB); - if (srsran_ringbuffer_init(&ring_buffer, max_required_bytes)) { - return; - } - - state.set_state(internal_state::idle); - start(INTRA_FREQ_MEAS_PRIO); -} - -void intra_measure::stop() -{ - // Notify quit to asynchronous thread. If it is measuring, it will first finish the measure, report to stack and - // then it will finish - state.set_state(internal_state::quit); - - // Wait for the asynchronous thread to finish - wait_thread_finish(); - - srsran_ringbuffer_stop(&ring_buffer); - srsran_refsignal_dl_sync_free(&refsignal_dl_sync); -} - -void intra_measure::set_primary_cell(uint32_t earfcn, srsran_cell_t cell) -{ - current_earfcn = earfcn; - current_sflen = (uint32_t)SRSRAN_SF_LEN_PRB(cell.nof_prb); - serving_cell = cell; -} - -void intra_measure::set_rx_gain_offset(float rx_gain_offset_db_) -{ - rx_gain_offset_db = rx_gain_offset_db_; -} - -void intra_measure::meas_stop() -{ - // Transition state to idle - // Ring-buffer shall not be reset, it will automatically be reset as soon as the FSM transitions to receive - state.set_state(internal_state::idle); - Info("INTRA: Disabled neighbour cell search for EARFCN %d", get_earfcn()); -} - -void intra_measure::set_cells_to_meas(const std::set& pci) -{ - active_pci_mutex.lock(); - active_pci = pci; - active_pci_mutex.unlock(); - state.set_state(internal_state::receive); - logger.info("INTRA: Received list of %zd neighbour cells to measure in EARFCN %d.", pci.size(), current_earfcn); -} - -void intra_measure::write(uint32_t tti, cf_t* data, uint32_t nsamples) -{ - int nbytes = (int)(nsamples * sizeof(cf_t)); - int required_nbytes = (int)(intra_freq_meas_len_ms * current_sflen * sizeof(cf_t)); - uint32_t elapsed_tti = TTI_SUB(tti, last_measure_tti); - - switch (state.get_state()) { - - case internal_state::idle: - case internal_state::measure: - case internal_state::quit: - // Do nothing - break; - case internal_state::wait: - if (elapsed_tti >= intra_freq_meas_period_ms) { - state.set_state(internal_state::receive); - last_measure_tti = tti; - srsran_ringbuffer_reset(&ring_buffer); - } - break; - case internal_state::receive: - // As nbytes might not match the sub-frame size, make sure that buffer does not overflow - nbytes = SRSRAN_MIN(srsran_ringbuffer_space(&ring_buffer), nbytes); - - // Try writing in the buffer - if (srsran_ringbuffer_write(&ring_buffer, data, nbytes) < nbytes) { - Warning("INTRA: Error writing to ringbuffer (EARFCN=%d)", current_earfcn); - - // Transition to wait, so it can keep receiving without stopping the component operation - state.set_state(internal_state::wait); - } else { - // As soon as there are enough samples in the buffer, transition to measure - if (srsran_ringbuffer_status(&ring_buffer) >= required_nbytes) { - state.set_state(internal_state::measure); - } - } - break; - } -} - -void intra_measure::measure_proc() -{ - std::set cells_to_measure = {}; - - // Load cell list to measure - active_pci_mutex.lock(); - cells_to_measure = active_pci; - active_pci_mutex.unlock(); - - // Read data from buffer and find cells in it - srsran_ringbuffer_read(&ring_buffer, search_buffer, (int)intra_freq_meas_len_ms * current_sflen * sizeof(cf_t)); - - // Go to receive before finishing, so new samples can be enqueued before the thread finishes - if (state.get_state() == internal_state::measure) { - // Prevents transition to wait if state has changed while reading the ring-buffer - state.set_state(internal_state::wait); - } - - // Detect new cells using PSS/SSS - std::set detected_cells = scell.find_cells(search_buffer, serving_cell, intra_freq_meas_len_ms); - - // Add detected cells to the list of cells to measure - for (const uint32_t& c : detected_cells) { - cells_to_measure.insert(c); - } - - // Initialise empty neighbour cell list - std::vector neighbour_cells = {}; - - new_cell_itf->cell_meas_reset(cc_idx); - - // Use Cell Reference signal to measure cells in the time domain for all known active PCI - for (const uint32_t& id : cells_to_measure) { - // Do not measure serving cell here since it's measured by workers - if (id == serving_cell.id) { - continue; - } - srsran_cell_t cell = serving_cell; - cell.id = id; - - srsran_refsignal_dl_sync_set_cell(&refsignal_dl_sync, cell); - srsran_refsignal_dl_sync_run(&refsignal_dl_sync, search_buffer, intra_freq_meas_len_ms * current_sflen); - - if (refsignal_dl_sync.found) { - phy_meas_t m = {}; - m.pci = cell.id; - m.earfcn = current_earfcn; - m.rsrp = refsignal_dl_sync.rsrp_dBfs - rx_gain_offset_db; - m.rsrq = refsignal_dl_sync.rsrq_dB; - m.cfo_hz = refsignal_dl_sync.cfo_Hz; - neighbour_cells.push_back(m); - - Info("INTRA: Found neighbour cell: EARFCN=%d, PCI=%03d, RSRP=%5.1f dBm, RSRQ=%5.1f, peak_idx=%5d, " - "CFO=%+.1fHz", - m.earfcn, - m.pci, - m.rsrp, - m.rsrq, - refsignal_dl_sync.peak_index, - refsignal_dl_sync.cfo_Hz); - } - } - - // Send measurements to RRC if any cell found - if (not neighbour_cells.empty()) { - new_cell_itf->new_cell_meas(cc_idx, neighbour_cells); - } -} - -void intra_measure::run_thread() -{ - bool quit = false; - - do { - // Get state - internal_state::state_t s = state.get_state(); - switch (s) { - - case internal_state::idle: - case internal_state::wait: - case internal_state::receive: - // Wait for a different state - state.wait_change(s); - break; - case internal_state::measure: - // Run the measurement process - measure_proc(); - break; - case internal_state::quit: - // Quit loop - quit = true; - break; - } - } while (not quit); -} - -} // namespace scell -} // namespace srsue diff --git a/srsue/src/phy/scell/intra_measure_base.cc b/srsue/src/phy/scell/intra_measure_base.cc new file mode 100644 index 000000000..e47c6ca81 --- /dev/null +++ b/srsue/src/phy/scell/intra_measure_base.cc @@ -0,0 +1,216 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ +#include "srsue/hdr/phy/scell/intra_measure_base.h" + +#define Log(level, fmt, ...) \ + do { \ + logger.level("INTRA-%s-%d: " fmt, to_string(get_rat()).c_str(), get_earfcn(), ##__VA_ARGS__); \ + } while (false) + +namespace srsue { +namespace scell { + +intra_measure_base::intra_measure_base(srslog::basic_logger& logger, meas_itf& new_cell_itf_) : + logger(logger), context(new_cell_itf_), thread("SYNC_INTRA_MEASURE") +{} + +intra_measure_base::~intra_measure_base() +{ + srsran_ringbuffer_free(&ring_buffer); +} + +void intra_measure_base::init_generic(uint32_t cc_idx_, const args_t& args) +{ + context.cc_idx = cc_idx_; + + context.meas_len_ms = args.len_ms; + context.meas_period_ms = args.period_ms; + context.trigger_tti_period = args.tti_period; + context.trigger_tti_offset = args.tti_offset; + context.rx_gain_offset_db = args.rx_gain_offset_db; + + // Compute subframe length from the sampling rate if available + if (std::isnormal(args.srate_hz)) { + context.sf_len = (uint32_t)round(args.srate_hz / 1000.0); + } else if (get_rat() == srsran::srsran_rat_t::lte) { + // Select maximum subframe size for LTE + context.sf_len = SRSRAN_SF_LEN_PRB(SRSRAN_MAX_PRB); + } else { + // No maximum subframe length is defined for other RATs + ERROR("A sampling rate was expected for %s. Undefined behaviour.", srsran::to_string(get_rat()).c_str()); + return; + } + + // Calculate the new required bytes + int max_required_bytes = (int)(sizeof(cf_t) * context.meas_len_ms * context.sf_len); + + // Reallocate only if the required capacity exceds the new requirement + if (ring_buffer.capacity < max_required_bytes) { + search_buffer.resize(context.meas_len_ms * context.sf_len); + + srsran_ringbuffer_free(&ring_buffer); + + // Initialise buffer for the maximum number of PRB + if (srsran_ringbuffer_init(&ring_buffer, max_required_bytes) < SRSRAN_SUCCESS) { + ERROR("Error initiating ringbuffer"); + return; + } + } + + if (state.get_state() == internal_state::initial) { + state.set_state(internal_state::idle); + start(INTRA_FREQ_MEAS_PRIO); + } +} + +void intra_measure_base::stop() +{ + // Notify quit to asynchronous thread. If it is measuring, it will first finish the measure, report to stack and + // then it will finish + state.set_state(internal_state::quit); + + // Wait for the asynchronous thread to finish + wait_thread_finish(); + + srsran_ringbuffer_stop(&ring_buffer); +} + +void intra_measure_base::set_rx_gain_offset(float rx_gain_offset_db_) +{ + context.rx_gain_offset_db = rx_gain_offset_db_; +} + +void intra_measure_base::meas_stop() +{ + // Transition state to idle + // Ring-buffer shall not be reset, it will automatically be reset as soon as the FSM transitions to receive + state.set_state(internal_state::idle); + Log(info, "Disabled neighbour cell search"); +} + +void intra_measure_base::set_cells_to_meas(const std::set& pci) +{ + active_pci_mutex.lock(); + context.active_pci = pci; + active_pci_mutex.unlock(); + state.set_state(internal_state::wait_first); + Log(info, "Received list of %zd neighbour cells to measure", pci.size()); +} + +void intra_measure_base::write(cf_t* data, uint32_t nsamples) +{ + int nbytes = (int)(nsamples * sizeof(cf_t)); + int required_nbytes = (int)(context.meas_len_ms * context.sf_len * sizeof(cf_t)); + + // As nbytes might not match the sub-frame size, make sure that buffer does not overflow + nbytes = SRSRAN_MIN(srsran_ringbuffer_space(&ring_buffer), nbytes); + + // Try writing in the buffer + if (srsran_ringbuffer_write(&ring_buffer, data, nbytes) < nbytes) { + Log(warning, "Error writing to ringbuffer"); + + // Transition to wait, so it can keep receiving without stopping the component operation + state.set_state(internal_state::wait); + } else { + // As soon as there are enough samples in the buffer, transition to measure + if (srsran_ringbuffer_status(&ring_buffer) >= required_nbytes) { + Log(debug, "Starting search and measurements"); + state.set_state(internal_state::measure); + } + } +} + +void intra_measure_base::run_tti(uint32_t tti, cf_t* data, uint32_t nsamples) +{ + logger.set_context(tti); + + switch (state.get_state()) { + case internal_state::initial: + case internal_state::idle: + case internal_state::measure: + case internal_state::quit: + // Do nothing + break; + case internal_state::wait: + case internal_state::wait_first: + // Check measurement trigger condition + if (receive_tti_trigger(tti)) { + state.set_state(internal_state::receive); + last_measure_tti = tti; + srsran_ringbuffer_reset(&ring_buffer); + + // Write baseband to ensure measurement starts in the right TTI + Log(info, "Start writing"); + write(data, nsamples); + } + break; + case internal_state::receive: + // Write baseband + write(data, nsamples); + break; + } +} + +void intra_measure_base::measure_proc() +{ + // Read data from buffer and find cells in it + int ret = srsran_ringbuffer_read_timed( + &ring_buffer, search_buffer.data(), (int)(context.meas_len_ms * context.sf_len * sizeof(cf_t)), 1000); + + // As this function is called once the ring-buffer has enough data to process, it is not expected to fail + if (ret < SRSRAN_SUCCESS) { + Log(error, "Ringbuffer read returned %d", ret); + return; + } + + // Go to receive before finishing, so new samples can be enqueued before the thread finishes + if (state.get_state() == internal_state::measure) { + // Prevents transition to wait if state has changed while reading the ring-buffer + state.set_state(internal_state::wait); + } + + // Perform measurements for the actual RAT + if (not measure_rat(context, search_buffer)) { + Log(error, "Error measuring RAT"); + } +} + +void intra_measure_base::run_thread() +{ + bool quit = false; + + do { + // Get state + internal_state::state_t s = state.get_state(); + switch (s) { + case internal_state::initial: + case internal_state::idle: + case internal_state::wait: + case internal_state::wait_first: + case internal_state::receive: + // Wait for a different state + state.wait_change(s); + break; + case internal_state::measure: + // Run the measurement process + measure_proc(); + break; + case internal_state::quit: + // Quit loop + quit = true; + break; + } + } while (not quit); +} + +} // namespace scell +} // namespace srsue diff --git a/srsue/src/phy/scell/intra_measure_lte.cc b/srsue/src/phy/scell/intra_measure_lte.cc new file mode 100644 index 000000000..a68af2991 --- /dev/null +++ b/srsue/src/phy/scell/intra_measure_lte.cc @@ -0,0 +1,112 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ +#include "srsue/hdr/phy/scell/intra_measure_lte.h" + +namespace srsue { +namespace scell { + +#define Log(level, fmt, ...) \ + do { \ + logger.level("INTRA-%s-%d: " fmt, to_string(get_rat()).c_str(), get_earfcn(), ##__VA_ARGS__); \ + } while (false) + +intra_measure_lte::intra_measure_lte(srslog::basic_logger& logger_, meas_itf& new_cell_itf_) : + logger(logger_), scell_rx(logger_), intra_measure_base(logger_, new_cell_itf_) +{} + +intra_measure_lte::~intra_measure_lte() +{ + scell_rx.deinit(); + srsran_refsignal_dl_sync_free(&refsignal_dl_sync); +} + +void intra_measure_lte::init(uint32_t cc_idx, const args_t& args) +{ + init_generic(cc_idx, args); + + // Initialise Reference signal measurement + srsran_refsignal_dl_sync_init(&refsignal_dl_sync, SRSRAN_CP_NORM); + + // Start scell + scell_rx.init(args.len_ms); +} + +void intra_measure_lte::set_primary_cell(uint32_t earfcn, srsran_cell_t cell) +{ + current_earfcn = earfcn; + serving_cell = cell; + set_current_sf_len((uint32_t)SRSRAN_SF_LEN_PRB(cell.nof_prb)); +} + +bool intra_measure_lte::measure_rat(measure_context_t context, std::vector& buffer) +{ + std::set cells_to_measure = context.active_pci; + + // Detect new cells using PSS/SSS + scell_rx.find_cells(buffer.data(), serving_cell, context.meas_len_ms, cells_to_measure); + + // Initialise empty neighbour cell list + std::vector neighbour_cells = {}; + + context.new_cell_itf.cell_meas_reset(context.cc_idx); + + // Use Cell Reference signal to measure cells in the time domain for all known active PCI + for (const uint32_t& id : cells_to_measure) { + // Do not measure serving cell here since it's measured by workers + if (id == serving_cell.id) { + continue; + } + srsran_cell_t cell = serving_cell; + cell.id = id; + + if (srsran_refsignal_dl_sync_set_cell(&refsignal_dl_sync, cell) < SRSRAN_SUCCESS) { + Log(error, "Error setting refsignal DL cell"); + return false; + } + + if (srsran_refsignal_dl_sync_run(&refsignal_dl_sync, buffer.data(), context.meas_len_ms * context.sf_len) < + SRSRAN_SUCCESS) { + Log(error, "Error running refsignal DL measurements"); + return false; + } + + if (refsignal_dl_sync.found) { + phy_meas_t m = {}; + m.rat = srsran::srsran_rat_t::lte; + m.pci = cell.id; + m.earfcn = current_earfcn; + m.rsrp = refsignal_dl_sync.rsrp_dBfs - context.rx_gain_offset_db; + m.rsrq = refsignal_dl_sync.rsrq_dB; + m.cfo_hz = refsignal_dl_sync.cfo_Hz; + neighbour_cells.push_back(m); + + Log(info, + "Found neighbour cell: PCI=%03d, RSRP=%5.1f dBm, RSRQ=%5.1f, peak_idx=%5d, " + "CFO=%+.1fHz", + m.pci, + m.rsrp, + m.rsrq, + refsignal_dl_sync.peak_index, + refsignal_dl_sync.cfo_Hz); + } + } + + // Send measurements to RRC if any cell found + if (not neighbour_cells.empty()) { + context.new_cell_itf.new_cell_meas(context.cc_idx, neighbour_cells); + } + + return true; +} + +} // namespace scell +} // namespace srsue diff --git a/srsue/src/phy/scell/intra_measure_nr.cc b/srsue/src/phy/scell/intra_measure_nr.cc new file mode 100644 index 000000000..ffe6deb25 --- /dev/null +++ b/srsue/src/phy/scell/intra_measure_nr.cc @@ -0,0 +1,133 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ +#include "srsue/hdr/phy/scell/intra_measure_nr.h" + +#define Log(level, fmt, ...) \ + do { \ + logger.level("INTRA-%s-%d: " fmt, to_string(get_rat()).c_str(), get_earfcn(), ##__VA_ARGS__); \ + } while (false) + +namespace srsue { +namespace scell { + +intra_measure_nr::intra_measure_nr(srslog::basic_logger& logger_, meas_itf& new_meas_itf_) : + logger(logger_), intra_measure_base(logger_, new_meas_itf_) +{} + +intra_measure_nr::~intra_measure_nr() +{ + srsran_ssb_free(&ssb); +} + +bool intra_measure_nr::init(uint32_t cc_idx_, const args_t& args) +{ + cc_idx = cc_idx_; + thr_snr_db = args.thr_snr_db; + + // Initialise generic side + intra_measure_base::args_t base_args = {}; + base_args.srate_hz = args.max_srate_hz; + base_args.len_ms = args.max_len_ms; + base_args.period_ms = 20; // Hard-coded, it does not make a difference at this stage + base_args.rx_gain_offset_db = args.rx_gain_offset_dB; + init_generic(cc_idx, base_args); + + // Initialise SSB + srsran_ssb_args_t ssb_args = {}; + ssb_args.max_srate_hz = args.max_srate_hz; + ssb_args.min_scs = args.min_scs; + ssb_args.enable_search = true; + if (srsran_ssb_init(&ssb, &ssb_args) < SRSRAN_SUCCESS) { + Log(error, "Error initiating SSB"); + return false; + } + + return true; +} + +bool intra_measure_nr::set_config(uint32_t arfcn, const config_t& cfg) +{ + // Update ARFCN + current_arfcn = arfcn; + serving_cell_pci = cfg.serving_cell_pci; + + // Reset performance measurement + perf_count_samples = 0; + perf_count_us = 0; + + // Re-configure generic side + init_generic(cc_idx, cfg); + + // Configure SSB + srsran_ssb_cfg_t ssb_cfg = {}; + ssb_cfg.srate_hz = cfg.srate_hz; + ssb_cfg.center_freq_hz = cfg.center_freq_hz; + ssb_cfg.ssb_freq_hz = cfg.ssb_freq_hz; + ssb_cfg.scs = cfg.scs; + if (srsran_ssb_set_cfg(&ssb, &ssb_cfg) < SRSRAN_SUCCESS) { + Log(error, "Error configuring SSB"); + return false; + } + + return true; +} + +bool intra_measure_nr::measure_rat(const measure_context_t context, std::vector& buffer) +{ + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + + // Search and measure the best cell + srsran_csi_trs_measurements_t meas = {}; + uint32_t N_id = 0; + if (srsran_ssb_csi_search(&ssb, buffer.data(), context.sf_len * context.meas_len_ms, &N_id, &meas) < SRSRAN_SUCCESS) { + Log(error, "Error searching for SSB"); + return false; + } + + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + perf_count_us += std::chrono::duration_cast(end - begin).count(); + perf_count_samples += context.sf_len * context.meas_len_ms; + + // Early return if the found PCI matches with the serving cell ID + if (serving_cell_pci == (int)N_id) { + return true; + } + + // Take valid decision if SNR threshold is exceeded + bool valid = (meas.snr_dB >= thr_snr_db); + + // Log finding + if ((logger.info.enabled() and valid) or logger.debug.enabled()) { + std::array str_info = {}; + srsran_csi_rs_measure_info(&meas, str_info.data(), (uint32_t)str_info.size()); + Log(info, "%s neighbour cell: PCI=%03d %s", valid ? "Found" : "Best", N_id, str_info.data()); + } + + // Check threshold + if (valid) { + // Prepare found measurements + std::vector meas_list(1); + meas_list[0].rat = get_rat(); + meas_list[0].rsrp = meas.rsrp_dB + context.rx_gain_offset_db; + meas_list[0].cfo_hz = meas.cfo_hz; + meas_list[0].earfcn = get_earfcn(); + meas_list[0].pci = N_id; + + // Push measurements to higher layers + context.new_cell_itf.new_cell_meas(cc_idx, meas_list); + } + + return true; +} + +} // namespace scell +} // namespace srsue \ No newline at end of file diff --git a/srsue/src/phy/scell/scell_recv.cc b/srsue/src/phy/scell/scell_recv.cc index e8a26cdd0..3c9d62342 100644 --- a/srsue/src/phy/scell/scell_recv.cc +++ b/srsue/src/phy/scell/scell_recv.cc @@ -77,18 +77,18 @@ void scell_recv::reset() current_fft_sz = 0; } -std::set -scell_recv::find_cells(const cf_t* input_buffer, const srsran_cell_t serving_cell, const uint32_t nof_sf) +void scell_recv::find_cells(const cf_t* input_buffer, + const srsran_cell_t& serving_cell, + const uint32_t& nof_sf, + std::set& found_cell_ids) { - std::set found_cell_ids = {}; - uint32_t fft_sz = srsran_symbol_sz(serving_cell.nof_prb); uint32_t sf_len = SRSRAN_SF_LEN(fft_sz); if (fft_sz != current_fft_sz) { if (srsran_sync_resize(&sync_find, nof_sf * sf_len, 5 * sf_len, fft_sz)) { logger.error("Error resizing sync nof_sf=%d, sf_len=%d, fft_sz=%d", nof_sf, sf_len, fft_sz); - return found_cell_ids; + return; } current_fft_sz = fft_sz; } @@ -97,7 +97,6 @@ scell_recv::find_cells(const cf_t* input_buffer, const srsran_cell_t serving_cel int cell_id = 0; for (uint32_t n_id_2 = 0; n_id_2 < 3; n_id_2++) { - if (n_id_2 != (serving_cell.id % 3)) { srsran_sync_set_N_id_2(&sync_find, n_id_2); @@ -117,7 +116,7 @@ scell_recv::find_cells(const cf_t* input_buffer, const srsran_cell_t serving_cel sync_res = srsran_sync_find(&sync_find, input_buffer, sf5_cnt * 5 * sf_len, &peak_idx); if (sync_res == SRSRAN_SYNC_ERROR) { logger.error("INTRA: Error calling sync_find()"); - return found_cell_ids; + return; } if (sync_find.peak_value > max_peak && sync_res == SRSRAN_SYNC_FOUND && srsran_sync_sss_detected(&sync_find)) { @@ -153,7 +152,7 @@ scell_recv::find_cells(const cf_t* input_buffer, const srsran_cell_t serving_cel } } } - return found_cell_ids; + return; } } // namespace scell diff --git a/srsue/src/phy/search.cc b/srsue/src/phy/search.cc index e1ab7d942..b708cacf1 100644 --- a/srsue/src/phy/search.cc +++ b/srsue/src/phy/search.cc @@ -76,6 +76,11 @@ void search::init(srsran::rf_buffer_t& buffer_, uint32_t nof_rx_channels, search force_N_id_2 = force_N_id_2_; } +void search::set_cp_en(bool enable) +{ + srsran_set_detect_cp(&cs, enable); +} + void search::reset() { srsran_ue_sync_reset(&ue_mib_sync.ue_sync); @@ -163,11 +168,12 @@ search::ret_code search::run(srsran_cell_t* cell_, std::arrayargs->detect_cp); if (worker_com->args->dl_channel_args.enable) { channel_emulator = @@ -91,15 +92,19 @@ void sync::init(srsran::radio_interface_phy* _radio, // Initialize cell searcher search_p.init(sf_buffer, nof_rf_channels, this, worker_com->args->force_N_id_2); - + search_p.set_cp_en(worker_com->args->detect_cp); // Initialize SFN synchronizer, it uses only pcell buffer sfn_p.init(&ue_sync, worker_com->args, sf_buffer, sf_buffer.size()); // Start intra-frequency measurement for (uint32_t i = 0; i < worker_com->args->nof_lte_carriers; i++) { - scell::intra_measure* q = new scell::intra_measure(phy_logger); - q->init(i, worker_com, this); - intra_freq_meas.push_back(std::unique_ptr(q)); + scell::intra_measure_lte* q = new scell::intra_measure_lte(phy_logger, *this); + scell::intra_measure_base::args_t args = {}; + args.len_ms = worker_com->args->intra_freq_meas_len_ms; + args.period_ms = worker_com->args->intra_freq_meas_period_ms; + args.rx_gain_offset_db = worker_com->args->rx_gain_offset; + q->init(i, args); + intra_freq_meas.push_back(std::unique_ptr(q)); } // Allocate Secondary serving cell synchronization @@ -955,7 +960,7 @@ int sync::radio_recv_fnc(srsran::rf_buffer_t& data, srsran_timestamp_t* rx_time) if (srsran_cell_isvalid(&cell)) { for (uint32_t i = 0; (uint32_t)i < intra_freq_meas.size(); i++) { // Feed the exact number of base-band samples for avoiding an invalid buffer read - intra_freq_meas[i]->write(tti, data.get(i, 0, worker_com->args->nof_rx_ant), data.get_nof_samples()); + intra_freq_meas[i]->run_tti(tti, data.get(i, 0, worker_com->args->nof_rx_ant), data.get_nof_samples()); // Update RX gain intra_freq_meas[i]->set_rx_gain_offset(worker_com->get_rx_gain_offset()); diff --git a/srsue/src/stack/rrc/rrc_nr.cc b/srsue/src/stack/rrc/rrc_nr.cc index 9f64f3184..bdd8a988f 100644 --- a/srsue/src/stack/rrc/rrc_nr.cc +++ b/srsue/src/stack/rrc/rrc_nr.cc @@ -379,16 +379,19 @@ void rrc_nr::get_nr_capabilities(srsran::byte_buffer_t* nr_caps_pdu) for (const auto& band : args.supported_bands_nr) { band_nr_s band_nr; - band_nr.band_nr = band; - band_nr.ue_pwr_class_present = true; - band_nr.ue_pwr_class = band_nr_s::ue_pwr_class_opts::pc3; + band_nr.band_nr = band; + band_nr.ue_pwr_class_present = true; + band_nr.ue_pwr_class = band_nr_s::ue_pwr_class_opts::pc3; + band_nr.pusch_minus256_qam_present = true; nr_cap.rf_params.supported_band_list_nr.push_back(band_nr); } - nr_cap.rlc_params_present = true; - nr_cap.rlc_params.um_with_short_sn_present = true; - nr_cap.rlc_params.um_with_long_sn_present = true; - nr_cap.pdcp_params.short_sn_present = args.pdcp_short_sn_support; + nr_cap.rlc_params_present = true; + nr_cap.rlc_params.um_with_short_sn_present = true; + nr_cap.rlc_params.um_with_long_sn_present = true; + nr_cap.pdcp_params.short_sn_present = args.pdcp_short_sn_support; + nr_cap.phy_params.phy_params_fr1_present = true; + nr_cap.phy_params.phy_params_fr1.pdsch_minus256_qam_fr1_present = true; // Pack nr_caps asn1::bit_ref bref(nr_caps_pdu->msg, nr_caps_pdu->get_tailroom()); @@ -1051,7 +1054,7 @@ bool rrc_nr::apply_sp_cell_ded_ul_pusch(const asn1::rrc_nr::pusch_cfg_s& pusch_c phy_cfg.pusch.mcs_table = srsran_mcs_table_qam64LowSE; break; case pusch_cfg_s::mcs_table_opts::nulltype: - logger.warning("Warning while selecting pdsch mcs_table"); + logger.warning("Warning while selecting pusch mcs_table"); return false; } } else { @@ -1147,6 +1150,14 @@ bool rrc_nr::apply_sp_cell_cfg(const sp_cell_cfg_s& sp_cell_cfg) logger.warning("DL cfg common not present"); return false; } + phy_cfg_nr_t::ssb_cfg_t ssb_cfg = {}; + if (make_phy_ssb_cfg(recfg_with_sync.sp_cell_cfg_common, &ssb_cfg) == true) { + phy_cfg.ssb = ssb_cfg; + } else { + logger.warning("Warning while building ssb structure"); + return false; + } + if (recfg_with_sync.sp_cell_cfg_common.tdd_ul_dl_cfg_common_present) { srsran_tdd_config_nr_t tdd; if (make_phy_tdd_cfg(recfg_with_sync.sp_cell_cfg_common.tdd_ul_dl_cfg_common, &tdd) == true) { diff --git a/srsue/test/phy/CMakeLists.txt b/srsue/test/phy/CMakeLists.txt index cc9338e08..bc2be245f 100644 --- a/srsue/test/phy/CMakeLists.txt +++ b/srsue/test/phy/CMakeLists.txt @@ -50,4 +50,24 @@ target_link_libraries(scell_search_test srsran_radio ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) -add_lte_test(scell_search_test scell_search_test --duration=5 --cell.nof_prb=6 --active_cell_list=2,3,4,5,6 --simulation_cell_list=1,2,3,4,5,6 --channel_period_s=30 --channel.hst.fd=750 --channel.delay_max=10000) + +# Test LTE cell search with a complex environment and an odd measurement period +add_lte_test(scell_search_test scell_search_test --duration=5 --cell.nof_prb=6 --active_cell_list=2,3,4,5,6 --simulation_cell_list=1,2,3,4,5,6 --channel_period_s=30 --channel.hst.fd=750 --channel.delay_max=10000 --intra_freq_meas_period_ms=199) + +add_executable(nr_cell_search_test nr_cell_search_test.cc) +target_link_libraries(nr_cell_search_test + srsue_phy + srsran_common + srsran_phy + srsran_radio + ${CMAKE_THREAD_LIBS_INIT} + ${Boost_LIBRARIES}) + +# Test NR cell search without delay +# This test checks the search starts in the configured TTI and the NR PSS is detected correctly inside the SF +add_nr_test(nr_cell_search_test nr_cell_search_test --duration=1 --ssb_period=20 --meas_period_ms=20 --meas_len_ms=1 --simulation_cell_list=500) + +# Test NR cell search with up 1000us delay +# This test checks the search is capable to find a cell with a broad delay +add_nr_test(nr_cell_search_test_delay nr_cell_search_test --duration=1 --ssb_period=20 --meas_period_ms=100 --meas_len_ms=30 --channel.delay_min=0 --channel.delay_max=1000 --simulation_cell_list=500) + diff --git a/srsue/test/phy/nr_cell_search_test.cc b/srsue/test/phy/nr_cell_search_test.cc new file mode 100644 index 000000000..162d94aaa --- /dev/null +++ b/srsue/test/phy/nr_cell_search_test.cc @@ -0,0 +1,540 @@ +/** + * + * \section COPYRIGHT + * + * Copyright 2013-2021 Software Radio Systems Limited + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the distribution. + * + */ + +#include "srsran/common/band_helper.h" +#include "srsran/common/string_helpers.h" +#include "srsran/common/test_common.h" +#include "srsran/interfaces/phy_interface_types.h" +#include "srsran/radio/radio.h" +#include "srsran/srslog/srslog.h" +#include "srsue/hdr/phy/scell/intra_measure_nr.h" +#include +#include +#include +#include +#include +#include + +// Test gNb class +class test_gnb +{ +private: + uint32_t pci; + uint32_t sf_len = 0; + srsran_ssb_t ssb = {}; + std::vector signal_buffer = {}; + srslog::basic_logger& logger; + srsran::channel channel; + std::vector buffer; + +public: + struct args_t { + uint32_t pci = 500; + double srate_hz = 11.52e6; + double center_freq_hz = 3.5e9; + double ssb_freq_hz = 3.5e9 - 960e3; + srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_30kHz; + uint32_t ssb_period_ms = 20; + uint16_t band; + srsran::channel::args_t channel; + std::string log_level = "error"; + + srsran_ssb_patern_t get_ssb_pattern() const { return srsran::srsran_band_helper().get_ssb_pattern(band, ssb_scs); } + srsran_duplex_mode_t get_duplex_mode() const { return srsran::srsran_band_helper().get_duplex_mode(band); } + }; + + test_gnb(const args_t& args) : + logger(srslog::fetch_basic_logger("PCI=" + std::to_string(args.pci))), channel(args.channel, 1, logger) + { + logger.set_level(srslog::str_to_basic_level(args.log_level)); + + // Initialise internals + pci = args.pci; + sf_len = (uint32_t)round(args.srate_hz / 1000); + + // Allocate buffer + buffer.resize(sf_len); + + // Initialise SSB + srsran_ssb_args_t ssb_args = {}; + ssb_args.max_srate_hz = args.srate_hz; + ssb_args.min_scs = args.ssb_scs; + ssb_args.enable_encode = true; + if (srsran_ssb_init(&ssb, &ssb_args) < SRSRAN_SUCCESS) { + logger.error("Error initialising SSB"); + return; + } + + // Configure SSB + srsran_ssb_cfg_t ssb_cfg = {}; + ssb_cfg.srate_hz = args.srate_hz; + ssb_cfg.center_freq_hz = args.center_freq_hz; + ssb_cfg.ssb_freq_hz = args.ssb_freq_hz; + ssb_cfg.scs = args.ssb_scs; + ssb_cfg.pattern = args.get_ssb_pattern(); + ssb_cfg.position[0] = true; + ssb_cfg.duplex_mode = args.get_duplex_mode(); + ssb_cfg.periodicity_ms = args.ssb_period_ms; + if (srsran_ssb_set_cfg(&ssb, &ssb_cfg) < SRSRAN_SUCCESS) { + logger.error("Error configuring SSB"); + return; + } + + // Configure channel + channel.set_srate((uint32_t)args.srate_hz); + } + + int work(uint32_t sf_idx, std::vector& baseband_buffer, const srsran::rf_timestamp_t& ts) + { + logger.set_context(sf_idx); + + // Zero buffer + srsran_vec_cf_zero(buffer.data(), (uint32_t)buffer.size()); + + // Check if SSB needs to be sent + if (srsran_ssb_send(&ssb, sf_idx)) { + // Prepare PBCH message + srsran_pbch_msg_nr_t msg = {}; + + // Add SSB + if (srsran_ssb_add(&ssb, pci, &msg, buffer.data(), buffer.data()) < SRSRAN_SUCCESS) { + logger.error("Error adding SSB"); + return SRSRAN_ERROR; + } + } + + // Run channel + cf_t* in[SRSRAN_MAX_CHANNELS] = {}; + cf_t* out[SRSRAN_MAX_CHANNELS] = {}; + in[0] = buffer.data(); + out[0] = buffer.data(); + channel.run(in, out, (uint32_t)buffer.size(), ts.get(0)); + + // Add buffer to baseband buffer + srsran_vec_sum_ccc(baseband_buffer.data(), buffer.data(), baseband_buffer.data(), (uint32_t)buffer.size()); + + return SRSRAN_SUCCESS; + } + + ~test_gnb() { srsran_ssb_free(&ssb); } +}; + +struct args_t { + // General + std::string log_level = "warning"; + uint32_t duration_s = 1; + double srate_hz = 11.52e6; + uint32_t carier_arfcn = 634240; + uint32_t ssb_arfcn = 634176; + std::string ssb_scs_str = "30"; + + // Measurement parameters + std::set pcis_to_meas; + uint32_t meas_len_ms = 1; + uint32_t meas_period_ms = 20; + float thr_snr_db = 5.0f; + srsran_subcarrier_spacing_t ssb_scs = srsran_subcarrier_spacing_30kHz; + + // Simulation parameters + std::set pcis_to_simulate; + uint32_t ssb_period_ms = 20; + float channel_delay_min = 0.0f; // Set to non-zero value to stir the delay from zero to this value in usec + float channel_delay_max = 0.0f; // Set to non-zero value to stir the delay from zero to this value in usec + + // On the Fly parameters + std::string radio_device_name = "auto"; + std::string radio_device_args = "auto"; + std::string radio_log_level = "info"; + float rx_gain = 60.0f; + + // File parameters + std::string filename = ""; +}; + +class meas_itf_listener : public srsue::scell::intra_measure_base::meas_itf +{ +public: + typedef struct { + float rsrp_avg; + float rsrp_min; + float rsrp_max; + float rsrq_avg; + float rsrq_min; + float rsrq_max; + uint32_t count; + } cell_meas_t; + + std::map cells; + + void cell_meas_reset(uint32_t cc_idx) override {} + void new_cell_meas(uint32_t cc_idx, const std::vector& meas) override + { + for (const srsue::phy_meas_t& m : meas) { + uint32_t pci = m.pci; + if (!cells.count(pci)) { + cells[pci].rsrp_min = m.rsrp; + cells[pci].rsrp_max = m.rsrp; + cells[pci].rsrp_avg = m.rsrp; + cells[pci].rsrq_min = m.rsrq; + cells[pci].rsrq_max = m.rsrq; + cells[pci].rsrq_avg = m.rsrq; + cells[pci].count = 1; + } else { + cells[pci].rsrp_min = SRSRAN_MIN(cells[pci].rsrp_min, m.rsrp); + cells[pci].rsrp_max = SRSRAN_MAX(cells[pci].rsrp_max, m.rsrp); + cells[pci].rsrp_avg = (m.rsrp + cells[pci].rsrp_avg * cells[pci].count) / (cells[pci].count + 1); + + cells[pci].rsrq_min = SRSRAN_MIN(cells[pci].rsrq_min, m.rsrq); + cells[pci].rsrq_max = SRSRAN_MAX(cells[pci].rsrq_max, m.rsrq); + cells[pci].rsrq_avg = (m.rsrq + cells[pci].rsrq_avg * cells[pci].count) / (cells[pci].count + 1); + cells[pci].count++; + } + } + } + + bool print_stats(args_t args) + { + printf("\n-- Statistics:\n"); + uint32_t true_counts = 0; + uint32_t false_counts = 0; + uint32_t tti_count = (1000 * args.duration_s) / args.meas_period_ms; + uint32_t ideal_true_counts = args.pcis_to_simulate.size() * tti_count; + uint32_t ideal_false_counts = tti_count * cells.size() - ideal_true_counts; + + for (auto& e : cells) { + bool false_alarm = args.pcis_to_simulate.find(e.first) == args.pcis_to_simulate.end(); + + if (args.pcis_to_simulate.empty()) { + false_alarm = (args.pcis_to_meas.count(e.first) == 0); + ideal_true_counts = args.pcis_to_meas.size() * tti_count; + } + + if (false_alarm) { + false_counts += e.second.count; + } else { + true_counts += e.second.count; + } + + printf(" pci=%03d; count=%3d; false=%s; rsrp=%+.1f|%+.1f|%+.1fdBfs; rsrq=%+.1f|%+.1f|%+.1fdB;\n", + e.first, + e.second.count, + false_alarm ? "y" : "n", + e.second.rsrp_min, + e.second.rsrp_avg, + e.second.rsrp_max, + e.second.rsrq_min, + e.second.rsrq_avg, + e.second.rsrq_max); + } + + float prob_detection = (ideal_true_counts) ? (float)true_counts / (float)ideal_true_counts : 0.0f; + float prob_false_alarm = (ideal_false_counts) ? (float)false_counts / (float)ideal_false_counts : 0.0f; + printf("\n"); + printf(" Probability of detection: %.6f\n", prob_detection); + printf(" Probability of false alarm: %.6f\n", prob_false_alarm); + + return (prob_detection >= 0.9f && prob_false_alarm <= 0.1f); + } +}; + +static void pci_list_parse_helper(std::string& list_str, std::set& list) +{ + if (list_str == "all") { + // Add all possible cells + for (int i = 0; i < SRSRAN_NOF_NID_NR; i++) { + list.insert(i); + } + } else if (list_str == "none") { + list.clear(); + } else if (not list_str.empty()) { + // Remove spaces from neightbour cell list + list_str = srsran::string_remove_char(list_str, ' '); + + // Add cell to known cells + srsran::string_parse_list(list_str, ',', list); + } +} + +// shorten boost program options namespace +namespace bpo = boost::program_options; + +int parse_args(int argc, char** argv, args_t& args) +{ + int ret = SRSRAN_SUCCESS; + + std::string active_cell_list = "500"; + std::string simulation_cell_list = ""; + std::string ssb_scs = "30"; + + bpo::options_description options("General options"); + bpo::options_description measure("Measurement options"); + bpo::options_description over_the_air("Mode 1: Over the air options (Default)"); + bpo::options_description simulation("Mode 2: Simulation options (enabled if simulation_cell_list is not empty)"); + bpo::options_description file("Mode 3: File (enabled if filename is provided)"); + + // clang-format off + measure.add_options() + ("meas_len_ms", bpo::value(&args.meas_len_ms)->default_value(args.meas_len_ms), "Measurement length") + ("meas_period_ms", bpo::value(&args.meas_period_ms)->default_value(args.meas_period_ms), "Measurement period") + ("active_cell_list", bpo::value(&active_cell_list)->default_value(active_cell_list), "Comma separated PCI cell list to measure") + ("thr_snr_db", bpo::value(&args.thr_snr_db)->default_value(args.thr_snr_db), "Detection threshold for SNR in dB") + ; + + over_the_air.add_options() + ("rf.device_name", bpo::value(&args.radio_device_name)->default_value(args.radio_device_name), "RF Device Name") + ("rf.device_args", bpo::value(&args.radio_device_args)->default_value(args.radio_device_args), "RF Device arguments") + ("rf.log_level", bpo::value(&args.radio_log_level)->default_value(args.radio_log_level), "RF Log level (none, warning, info, debug)") + ("rf.rx_gain", bpo::value(&args.rx_gain)->default_value(args.rx_gain), "RF Receiver gain in dB") + ; + + simulation.add_options() + ("simulation_cell_list", bpo::value(&simulation_cell_list)->default_value(simulation_cell_list), "Comma separated PCI cell list to simulate") + ("ssb_period", bpo::value(&args.ssb_period_ms)->default_value(args.ssb_period_ms), "SSB period in ms") + ("channel.delay_min", bpo::value(&args.channel_delay_min)->default_value(args.channel_delay_min), "Channel delay minimum in usec.") + ("channel.delay_max", bpo::value(&args.channel_delay_max)->default_value(args.channel_delay_max), "Channel delay maximum in usec. Set to 0 to disable, otherwise it will steer the delay for the duration of the simulation") + ; + + file.add_options() + ("file.name", bpo::value(&args.filename)->default_value(args.filename), "File name providing baseband") + ; + + options.add(measure).add(over_the_air).add(simulation).add(file).add_options() + ("help,h", "Show this message") + ("log_level", bpo::value(&args.log_level)->default_value(args.log_level), "Intra measurement log level (none, warning, info, debug)") + ("duration", bpo::value(&args.duration_s)->default_value(args.duration_s), "Duration of the test in seconds") + ("srate", bpo::value(&args.srate_hz)->default_value(args.srate_hz), "Sampling Rate in Hz") + ("carrier_arfcn", bpo::value(&args.carier_arfcn)->default_value(args.carier_arfcn), "Carrier center frequency ARFCN") + ("ssb_arfcn", bpo::value(&args.ssb_arfcn)->default_value(args.ssb_arfcn), "SSB center frequency in ARFCN") + ("ssb_scs", bpo::value(&ssb_scs)->default_value(ssb_scs), "SSB subcarrier spacing in kHz") + ; + // clang-format on + + bpo::variables_map vm; + try { + bpo::store(bpo::command_line_parser(argc, argv).options(options).run(), vm); + bpo::notify(vm); + } catch (bpo::error& e) { + std::cerr << e.what() << std::endl; + ret = SRSRAN_ERROR; + } + + // help option was given or error - print usage and exit + if (vm.count("help") || ret) { + std::cout << "Usage: " << argv[0] << " [OPTIONS] config_file" << std::endl << std::endl; + std::cout << options << std::endl << std::endl; + ret = SRSRAN_ERROR; + } + + // Parse PCI lists + pci_list_parse_helper(active_cell_list, args.pcis_to_meas); + pci_list_parse_helper(simulation_cell_list, args.pcis_to_simulate); + + // Parse SSB SCS + args.ssb_scs = srsran_subcarrier_spacing_from_str(ssb_scs.c_str()); + if (args.ssb_scs == srsran_subcarrier_spacing_invalid) { + ret = SRSRAN_ERROR; + } + + return ret; +} + +int main(int argc, char** argv) +{ + int ret; + + // Parse args + args_t args = {}; + if (parse_args(argc, argv, args) < SRSRAN_SUCCESS) { + return SRSRAN_ERROR; + } + + // Initiate logging + srslog::init(); + srslog::basic_logger& logger = srslog::fetch_basic_logger("PHY"); + logger.set_level(srslog::str_to_basic_level(args.log_level)); + + // Deduce base-band parameters + double srate_hz = args.srate_hz; + uint32_t sf_len = (uint32_t)round(srate_hz / 1000.0); + double center_freq_hz = srsran::srsran_band_helper().nr_arfcn_to_freq(args.carier_arfcn); + double ssb_freq_hz = srsran::srsran_band_helper().nr_arfcn_to_freq(args.ssb_arfcn); + uint16_t band = srsran::srsran_band_helper().get_band_from_dl_freq_Hz(center_freq_hz); + logger.debug("Band: %d; srate: %.2f MHz; center_freq: %.1f MHz; ssb_freq: %.1f MHz;", + band, + srate_hz / 1e6, + center_freq_hz / 1e6, + ssb_freq_hz / 1e6); + + // Allocate buffer + std::vector baseband_buffer(sf_len); + + // Create measurement callback + meas_itf_listener rrc; + + // Create measurement instance + srsue::scell::intra_measure_nr intra_measure(logger, rrc); + + // Initialise measurement instance + srsue::scell::intra_measure_nr::args_t meas_args = {}; + meas_args.rx_gain_offset_dB = 0.0f; + meas_args.max_len_ms = args.meas_len_ms; + meas_args.max_srate_hz = srate_hz; + meas_args.min_scs = args.ssb_scs; + meas_args.thr_snr_db = args.thr_snr_db; + TESTASSERT(intra_measure.init(0, meas_args)); + + // Setup measurement + srsue::scell::intra_measure_nr::config_t meas_cfg = {}; + meas_cfg.arfcn = args.carier_arfcn; + meas_cfg.srate_hz = srate_hz; + meas_cfg.len_ms = args.meas_len_ms; + meas_cfg.period_ms = args.meas_period_ms; + meas_cfg.tti_period = args.meas_period_ms; + meas_cfg.tti_offset = 0; + meas_cfg.rx_gain_offset_db = 0; + meas_cfg.center_freq_hz = center_freq_hz; + meas_cfg.ssb_freq_hz = ssb_freq_hz; + meas_cfg.scs = srsran_subcarrier_spacing_30kHz; + meas_cfg.serving_cell_pci = -1; + TESTASSERT(intra_measure.set_config(args.carier_arfcn, meas_cfg)); + + // Simulation only + std::vector > test_gnb_v; + + // Over-the-air only + std::unique_ptr radio = nullptr; + + // File read only + srsran_filesource_t filesource = {}; + + // Setup raio if the list of PCIs to simulate is empty + if (not args.filename.empty()) { + if (srsran_filesource_init(&filesource, args.filename.c_str(), SRSRAN_COMPLEX_FLOAT) < SRSRAN_SUCCESS) { + return SRSRAN_ERROR; + } + } else if (args.pcis_to_simulate.empty()) { + // Create radio log + auto& radio_logger = srslog::fetch_basic_logger("RF", false); + radio_logger.set_level(srslog::str_to_basic_level(args.radio_log_level)); + + // Create radio + radio = std::unique_ptr(new srsran::radio); + + // Init radio + srsran::rf_args_t radio_args = {}; + radio_args.device_args = args.radio_device_args; + radio_args.device_name = args.radio_device_name; + radio_args.nof_carriers = 1; + radio_args.nof_antennas = 1; + radio->init(radio_args, nullptr); + + // Set sampling rate + radio->set_rx_srate(srate_hz); + + // Set frequency + radio->set_rx_freq(0, center_freq_hz); + + } else { + // Create test eNb's if radio is not available + for (const uint32_t& pci : args.pcis_to_simulate) { + // Initialise channel and push back + test_gnb::args_t gnb_args = {}; + gnb_args.pci = pci; + gnb_args.srate_hz = srate_hz; + gnb_args.center_freq_hz = center_freq_hz; + gnb_args.ssb_freq_hz = ssb_freq_hz; + gnb_args.ssb_scs = args.ssb_scs; + gnb_args.ssb_period_ms = args.ssb_period_ms; + gnb_args.band = band; + gnb_args.log_level = args.log_level; + gnb_args.channel.delay_enable = std::isnormal(args.channel_delay_max); + gnb_args.channel.delay_min_us = args.channel_delay_min; + gnb_args.channel.delay_max_us = args.channel_delay_max; + gnb_args.channel.delay_period_s = args.duration_s; + gnb_args.channel.delay_init_time_s = 0.0f; + gnb_args.channel.enable = (gnb_args.channel.delay_enable || gnb_args.channel.awgn_enable || + gnb_args.channel.fading_enable || gnb_args.channel.hst_enable); + test_gnb_v.push_back(std::unique_ptr(new test_gnb(gnb_args))); + } + } + + // pass cells to measure to intra_measure object + intra_measure.set_cells_to_meas(args.pcis_to_meas); + + // Run loop + srsran::rf_timestamp_t ts = {}; + for (uint32_t sf_idx = 0; sf_idx < args.duration_s * 1000; sf_idx++) { + logger.set_context(sf_idx); + + // Clean buffer + srsran_vec_cf_zero(baseband_buffer.data(), sf_len); + + if (not args.filename.empty()) { + if (srsran_filesource_read(&filesource, baseband_buffer.data(), (int)sf_len) < SRSRAN_SUCCESS) { + ERROR("Error reading from file"); + srsran_filesource_free(&filesource); + return SRSRAN_ERROR; + } + } else if (radio) { + // Receive radio + srsran::rf_buffer_t radio_buffer(baseband_buffer.data(), sf_len); + radio->rx_now(radio_buffer, ts); + } else { + // Run gNb simulator + for (auto& gnb : test_gnb_v) { + gnb->work(sf_idx, baseband_buffer, ts); + } + + // if it measuring, wait for avoiding overflowing + intra_measure.wait_meas(); + + // Increase Time counter + ts.add(0.001); + } + + // Give data to intra measure component + intra_measure.run_tti(sf_idx % 10240, baseband_buffer.data(), sf_len); + if (sf_idx % 1000 == 0) { + logger.info("Done %.1f%%", (double)sf_idx * 100.0 / ((double)args.duration_s * 1000.0)); + } + } + + // make sure last measurement has been received before stopping + if (not radio) { + intra_measure.wait_meas(); + } + + // Stop, it will block until the asynchronous thread quits + intra_measure.stop(); + + logger.warning("NR intra frequency performance %d Msps\n", intra_measure.get_perf()); + + ret = rrc.print_stats(args) ? SRSRAN_SUCCESS : SRSRAN_ERROR; + + if (radio) { + radio->stop(); + } + + if (not args.filename.empty()) { + srsran_filesource_free(&filesource); + } + + srslog::flush(); + + if (ret == SRSRAN_SUCCESS) { + printf("Ok\n"); + } else { + printf("Error\n"); + } + + return ret; +} diff --git a/srsue/test/phy/scell_search_test.cc b/srsue/test/phy/scell_search_test.cc index a37bba344..494326a8a 100644 --- a/srsue/test/phy/scell_search_test.cc +++ b/srsue/test/phy/scell_search_test.cc @@ -21,7 +21,7 @@ #include "srsran/interfaces/phy_interface_types.h" #include "srsran/srslog/srslog.h" -#include "srsue/hdr/phy/scell/intra_measure.h" +#include "srsue/hdr/phy/scell/intra_measure_lte.h" #include #include #include @@ -221,7 +221,7 @@ public: } }; -class meas_itf_listener : public srsue::scell::intra_measure::meas_itf +class meas_itf_listener : public srsue::scell::intra_measure_base::meas_itf { public: typedef struct { @@ -405,11 +405,10 @@ int main(int argc, char** argv) srslog::basic_logger& logger = srslog::fetch_basic_logger("intra_measure"); srslog::init(); - cf_t* baseband_buffer = srsran_vec_cf_malloc(SRSRAN_SF_LEN_MAX); - srsran::rf_timestamp_t ts = {}; - srsue::scell::intra_measure intra_measure(logger); - meas_itf_listener rrc; - srsue::phy_common common(logger); + cf_t* baseband_buffer = srsran_vec_cf_malloc(SRSRAN_SF_LEN_MAX); + srsran::rf_timestamp_t ts = {}; + meas_itf_listener rrc; + srsue::scell::intra_measure_lte intra_measure(logger, rrc); // Simulation only std::vector > test_enb_v; @@ -420,7 +419,6 @@ int main(int argc, char** argv) std::unique_ptr radio = nullptr; // Set Receiver args - common.args = &phy_args; phy_args.estimator_fil_auto = false; phy_args.estimator_fil_order = 4; phy_args.estimator_fil_stddev = 1.0f; @@ -478,7 +476,12 @@ int main(int argc, char** argv) logger.set_level(srslog::str_to_basic_level(intra_meas_log_level)); - intra_measure.init(0, &common, &rrc); + srsue::scell::intra_measure_base::args_t args = {}; + args.len_ms = phy_args.intra_freq_meas_len_ms; + args.period_ms = phy_args.intra_freq_meas_period_ms; + args.rx_gain_offset_db = phy_args.rx_gain_offset; + + intra_measure.init(0, args); intra_measure.set_primary_cell(SRSRAN_MAX(earfcn_dl, 0), cell_base); if (earfcn_dl >= 0) { @@ -643,7 +646,7 @@ int main(int argc, char** argv) ts.add(0.001); // Give data to intra measure component - intra_measure.write(sf_idx % 10240, baseband_buffer, SRSRAN_SF_LEN_PRB(cell_base.nof_prb)); + intra_measure.run_tti(sf_idx % 10240, baseband_buffer, SRSRAN_SF_LEN_PRB(cell_base.nof_prb)); if (sf_idx % 1000 == 0) { printf("Done %.1f%%\n", (double)sf_idx * 100.0 / ((double)duration_execution_s * 1000.0)); } diff --git a/srsue/test/upper/rrc_meas_test.cc b/srsue/test/upper/rrc_meas_test.cc index 81523a2d9..5a69e53a6 100644 --- a/srsue/test/upper/rrc_meas_test.cc +++ b/srsue/test/upper/rrc_meas_test.cc @@ -683,18 +683,18 @@ int meas_obj_test() rrc_meas_logger.info("Test7: PHY finds new neighbours in frequency 1 and 2, check RRC instructs to search them"); std::vector phy_meas = {}; - phy_meas.push_back({0, 0, 0.0f, 1, 31}); - phy_meas.push_back({-1, 0, 0.0f, 1, 32}); - phy_meas.push_back({-2, 0, 0.0f, 1, 33}); - phy_meas.push_back({-3, 0, 0.0f, 1, 34}); + phy_meas.push_back({srsran::srsran_rat_t::lte, 0, 0, 0.0f, 1, 31}); + phy_meas.push_back({srsran::srsran_rat_t::lte, -1, 0, 0.0f, 1, 32}); + phy_meas.push_back({srsran::srsran_rat_t::lte, -2, 0, 0.0f, 1, 33}); + phy_meas.push_back({srsran::srsran_rat_t::lte, -3, 0, 0.0f, 1, 34}); rrctest.new_cell_meas(phy_meas); rrctest.run_tti(1); phy_meas = {}; - phy_meas.push_back({-4, 0, 0.0f, 1, 35}); - phy_meas.push_back({-5, 0, 0.0f, 1, 36}); - phy_meas.push_back({-6, 0, 0.0f, 1, 37}); - phy_meas.push_back({1, 0, 0.0f, 1, 30}); - phy_meas.push_back({0, 0, 0.0f, 2, 31}); + phy_meas.push_back({srsran::srsran_rat_t::lte, -4, 0, 0.0f, 1, 35}); + phy_meas.push_back({srsran::srsran_rat_t::lte, -5, 0, 0.0f, 1, 36}); + phy_meas.push_back({srsran::srsran_rat_t::lte, -6, 0, 0.0f, 1, 37}); + phy_meas.push_back({srsran::srsran_rat_t::lte, 1, 0, 0.0f, 1, 30}); + phy_meas.push_back({srsran::srsran_rat_t::lte, 0, 0, 0.0f, 2, 31}); rrctest.new_cell_meas(phy_meas); rrctest.run_tti(1); @@ -815,7 +815,7 @@ void send_report(rrc_test& rrctest, if (earfcn.size() == pci.size()) { e = earfcn[i]; } - phy_meas.push_back({r, -5, 0.0f, e, pci[i]}); + phy_meas.push_back({srsran::srsran_rat_t::lte, r, -5, 0.0f, e, pci[i]}); } rrctest.new_cell_meas(phy_meas); rrctest.run_tti(1); diff --git a/srsue/ue.conf.example b/srsue/ue.conf.example index 7c1cf96e9..0b868aaf5 100644 --- a/srsue/ue.conf.example +++ b/srsue/ue.conf.example @@ -367,6 +367,7 @@ enable = false #pdsch_csi_enabled = true #pdsch_8bit_decoder = false #force_ul_amplitude = 0 +#detect_cp = false #in_sync_rsrp_dbm_th = -130.0 #in_sync_snr_db_th = 3.0 @@ -391,26 +392,32 @@ enable = false ##################################################################### # General configuration options # -# metrics_csv_enable: Write UE metrics to CSV file. +# metrics_csv_enable: Write UE metrics to CSV file. # -# metrics_period_secs: Sets the period at which metrics are requested from the UE. +# metrics_period_secs: Sets the period at which metrics are requested from the UE. # -# metrics_csv_filename: File path to use for CSV metrics. +# metrics_csv_filename: File path to use for CSV metrics. # -# tracing_enable: Write source code tracing information to a file. +# tracing_enable: Write source code tracing information to a file. # -# tracing_filename: File path to use for tracing information. +# tracing_filename: File path to use for tracing information. # -# tracing_buffcapacity: Maximum capacity in bytes the tracing framework can store. +# tracing_buffcapacity: Maximum capacity in bytes the tracing framework can store. # -# have_tti_time_stats: Calculate TTI execution statistics using system clock +# have_tti_time_stats: Calculate TTI execution statistics using system clock +# +# metrics_json_enable: Write UE metrics to JSON file. +# +# metrics_json_filename: File path to use for JSON metrics. # ##################################################################### [general] -#metrics_csv_enable = false -#metrics_period_secs = 1 -#metrics_csv_filename = /tmp/ue_metrics.csv -#have_tti_time_stats = true -#tracing_enable = true -#tracing_filename = /tmp/ue_tracing.log -#tracing_buffcapacity = 1000000 +#metrics_csv_enable = false +#metrics_period_secs = 1 +#metrics_csv_filename = /tmp/ue_metrics.csv +#have_tti_time_stats = true +#tracing_enable = true +#tracing_filename = /tmp/ue_tracing.log +#tracing_buffcapacity = 1000000 +#metrics_json_enable = false +#metrics_json_filename = /tmp/ue_metrics.json