Merge branch 'next' into agpl_next

# Conflicts:
#	srsue/hdr/phy/ue_lte_phy_base.h
#	srsue/hdr/phy/ue_nr_phy_base.h
master
Codebot 3 years ago committed by Your Name
commit 21525935ab

@ -30,7 +30,7 @@ endif(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
########################################################################
# Project setup
########################################################################
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.10)
project( SRSRAN )
message( STATUS "CMAKE_SYSTEM: " ${CMAKE_SYSTEM} )
message( STATUS "CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR} )

@ -41,7 +41,8 @@ protected:
template <typename Obj>
class iterator_impl
{
using It = iterator_impl<Obj>;
using It = iterator_impl<Obj>;
using parent_t = typename std::conditional<std::is_const<Obj>::value, const base_t, base_t>::type;
public:
using iterator_category = std::forward_iterator_tag;
@ -51,7 +52,7 @@ protected:
using reference = Obj&;
iterator_impl() = default;
iterator_impl(base_t* parent_, size_t idx_) : parent(parent_), idx(idx_)
iterator_impl(parent_t* parent_, size_t idx_) : parent(parent_), idx(idx_)
{
if (idx < parent->vec.size() and not parent->contains(idx)) {
++(*this);
@ -80,8 +81,8 @@ protected:
private:
friend base_t;
base_t* parent = nullptr;
size_t idx = std::numeric_limits<size_t>::max();
parent_t* parent = nullptr;
size_t idx = std::numeric_limits<size_t>::max();
};
size_t nof_elems = 0;

@ -64,9 +64,13 @@ struct nzp_csi_rs_res_s;
struct pdsch_serving_cell_cfg_s;
struct freq_info_dl_s;
struct serving_cell_cfg_common_s;
struct serving_cell_cfg_common_sib_s;
struct serving_cell_cfg_s;
struct pdcch_cfg_common_s;
struct pdcch_cfg_s;
struct pdsch_cfg_common_s;
struct pucch_cfg_common_s;
struct pusch_cfg_common_s;
struct mib_s;
struct srb_to_add_mod_s;
@ -133,17 +137,23 @@ bool make_pdsch_cfg_from_serv_cell(const asn1::rrc_nr::serving_cell_cfg_s& serv_
bool make_csi_cfg_from_serv_cell(const asn1::rrc_nr::serving_cell_cfg_s& serv_cell, srsran_csi_hl_cfg_t* csi_hl);
bool make_duplex_cfg_from_serv_cell(const asn1::rrc_nr::serving_cell_cfg_common_s& serv_cell,
srsran_duplex_config_nr_t* duplex_cfg);
bool fill_phy_pdcch_cfg_common(const asn1::rrc_nr::pdcch_cfg_common_s& pdcch_cfg, srsran_pdcch_cfg_nr_t* pdcch);
void fill_phy_pdcch_cfg_common(const asn1::rrc_nr::pdcch_cfg_common_s& pdcch_cfg, srsran_pdcch_cfg_nr_t* pdcch);
bool fill_phy_pdcch_cfg(const asn1::rrc_nr::pdcch_cfg_s& pdcch_cfg, srsran_pdcch_cfg_nr_t* pdcch);
bool fill_phy_pdsch_cfg_common(const asn1::rrc_nr::pdsch_cfg_common_s& pdsch_cfg, srsran_sch_hl_cfg_nr_t* pdsch);
void fill_phy_pucch_cfg_common(const asn1::rrc_nr::pucch_cfg_common_s& pucch_cfg, srsran_pucch_nr_common_cfg_t* pucch);
bool fill_phy_pusch_cfg_common(const asn1::rrc_nr::pusch_cfg_common_s& pusch_cfg, srsran_sch_hl_cfg_nr_t* pusch);
void fill_phy_carrier_cfg(const asn1::rrc_nr::serving_cell_cfg_common_sib_s& serv_cell_cfg,
srsran_carrier_nr_t* carrier_nr);
void fill_phy_ssb_cfg(const asn1::rrc_nr::serving_cell_cfg_common_sib_s& serv_cell_cfg, phy_cfg_nr_t::ssb_cfg_t* ssb);
/***************************
* MAC Config
**************************/
logical_channel_config_t make_mac_logical_channel_cfg_t(uint8_t lcid, const asn1::rrc_nr::lc_ch_cfg_s& asn1_type);
rach_nr_cfg_t make_mac_rach_cfg(const asn1::rrc_nr::rach_cfg_common_s& asn1_type);
bool make_mac_phr_cfg_t(const asn1::rrc_nr::phr_cfg_s& asn1_type, phr_cfg_nr_t* phr_cfg_nr);
bool make_mac_dl_harq_cfg_nr_t(const asn1::rrc_nr::pdsch_serving_cell_cfg_s& asn1_type,
dl_harq_cfg_nr_t* out_dl_harq_cfg_nr);
void make_mac_rach_cfg(const asn1::rrc_nr::rach_cfg_common_s& asn1_type, rach_cfg_nr_t* rach_cfg_nr);
bool make_mac_phr_cfg_t(const asn1::rrc_nr::phr_cfg_s& asn1_type, phr_cfg_nr_t* phr_cfg_nr);
bool make_mac_dl_harq_cfg_nr_t(const asn1::rrc_nr::pdsch_serving_cell_cfg_s& asn1_type,
dl_harq_cfg_nr_t* out_dl_harq_cfg_nr);
/***************************
* RLC Config
**************************/

@ -67,14 +67,6 @@ struct rf_args_t {
std::array<rf_args_band_t, SRSRAN_MAX_CARRIERS> ch_tx_bands;
};
struct vnf_args_t {
std::string type;
std::string bind_addr;
uint16_t bind_port;
std::string log_level;
int log_hex_limit;
};
class srsran_gw_config_t
{
public:
@ -89,8 +81,6 @@ public:
virtual uint32_t read_pdu(uint32_t lcid, uint8_t* payload, uint32_t requested_bytes) = 0;
};
class stack_interface_phy_nr
{};
} // namespace srsran

@ -23,8 +23,6 @@
#include "srsran/common/interfaces_common.h"
#include "srsran/srsran.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#ifndef SRSRAN_ENB_INTERFACES_H
#define SRSRAN_ENB_INTERFACES_H

@ -0,0 +1,39 @@
/**
*
* \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_ENB_RRC_INTERFACE_MAC_H
#define SRSRAN_ENB_RRC_INTERFACE_MAC_H
#include "srsenb/hdr/stack/mac/sched_interface.h"
namespace srsenb {
/// RRC interface for MAC
class rrc_interface_mac
{
public:
/* Radio Link failure */
virtual int add_user(uint16_t rnti, const sched_interface::ue_cfg_t& init_ue_cfg) = 0;
virtual void upd_user(uint16_t new_rnti, uint16_t old_rnti) = 0;
virtual void set_activity_user(uint16_t rnti) = 0;
virtual void set_radiolink_dl_state(uint16_t rnti, bool crc_res) = 0;
virtual void set_radiolink_ul_state(uint16_t rnti, bool crc_res) = 0;
virtual bool is_paging_opportunity(uint32_t tti_tx_dl, uint32_t* payload_len) = 0;
virtual void read_pdu_pcch(uint32_t tti_tx_dl, uint8_t* payload, uint32_t payload_size) = 0;
///< Provide packed SIB to MAC (buffer is managed by RRC)
virtual uint8_t* read_pdu_bcch_dlsch(const uint8_t enb_cc_idx, const uint32_t sib_index) = 0;
};
} // namespace srsenb
#endif // SRSRAN_ENB_RRC_INTERFACE_MAC_H

@ -0,0 +1,30 @@
/**
*
* \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_ENB_RRC_INTERFACE_PDCP_H
#define SRSRAN_ENB_RRC_INTERFACE_PDCP_H
#include "srsran/common/byte_buffer.h"
namespace srsenb {
/// RRC interface for PDCP
class rrc_interface_pdcp
{
public:
virtual void write_pdu(uint16_t rnti, uint32_t lcid, srsran::unique_byte_buffer_t pdu) = 0;
virtual void notify_pdcp_integrity_error(uint16_t rnti, uint32_t lcid) = 0;
};
} // namespace srsenb
#endif // SRSRAN_ENB_RRC_INTERFACE_PDCP_H

@ -0,0 +1,31 @@
/**
*
* \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_ENB_RRC_INTERFACE_RLC_H
#define SRSRAN_ENB_RRC_INTERFACE_RLC_H
#include "srsran/common/byte_buffer.h"
namespace srsenb {
/// RRC interface for RLC
class rrc_interface_rlc
{
public:
virtual void max_retx_attempted(uint16_t rnti) = 0;
virtual void protocol_failure(uint16_t rnti) = 0;
virtual void write_pdu(uint16_t rnti, uint32_t lcid, srsran::unique_byte_buffer_t sdu) = 0;
};
} // namespace srsenb
#endif // SRSRAN_ENB_RRC_INTERFACE_RLC_H

@ -19,7 +19,6 @@
*
*/
#include "srsenb/hdr/stack/mac/sched_interface.h"
#include "srsran/asn1/s1ap_utils.h"
#include "srsran/interfaces/enb_rrc_interface_types.h"
@ -95,40 +94,6 @@ public:
virtual void set_erab_status(uint16_t rnti, const asn1::s1ap::bearers_subject_to_status_transfer_list_l& erabs) = 0;
};
/// RRC interface for RLC
class rrc_interface_rlc
{
public:
virtual void max_retx_attempted(uint16_t rnti) = 0;
virtual void protocol_failure(uint16_t rnti) = 0;
virtual void write_pdu(uint16_t rnti, uint32_t lcid, srsran::unique_byte_buffer_t sdu) = 0;
};
/// RRC interface for MAC
class rrc_interface_mac
{
public:
/* Radio Link failure */
virtual int add_user(uint16_t rnti, const sched_interface::ue_cfg_t& init_ue_cfg) = 0;
virtual void upd_user(uint16_t new_rnti, uint16_t old_rnti) = 0;
virtual void set_activity_user(uint16_t rnti) = 0;
virtual void set_radiolink_dl_state(uint16_t rnti, bool crc_res) = 0;
virtual void set_radiolink_ul_state(uint16_t rnti, bool crc_res) = 0;
virtual bool is_paging_opportunity(uint32_t tti_tx_dl, uint32_t* payload_len) = 0;
virtual void read_pdu_pcch(uint32_t tti_tx_dl, uint8_t* payload, uint32_t payload_size) = 0;
///< Provide packed SIB to MAC (buffer is managed by RRC)
virtual uint8_t* read_pdu_bcch_dlsch(const uint8_t enb_cc_idx, const uint32_t sib_index) = 0;
};
/// RRC interface for PDCP
class rrc_interface_pdcp
{
public:
virtual void write_pdu(uint16_t rnti, uint32_t lcid, srsran::unique_byte_buffer_t pdu) = 0;
virtual void notify_pdcp_integrity_error(uint16_t rnti, uint32_t lcid) = 0;
};
} // namespace srsenb
#endif // SRSRAN_ENB_RRC_INTERFACES_H

@ -28,9 +28,9 @@
#include "srsran/common/security.h"
#include "srsran/interfaces/pdcp_interface_types.h"
#include "srsran/interfaces/rlc_interface_types.h"
#include "srsran/interfaces/rrc_interface_types.h"
// EUTRA interfaces that are used unmodified
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interface_pdcp.h"
#include "srsran/interfaces/enb_rrc_interface_rlc.h"
namespace srsenb {
@ -305,7 +305,7 @@ public:
virtual void rach_detected(const rach_info_t& rach_info) = 0;
};
class stack_interface_phy_nr : public mac_interface_phy_nr, public srsran::stack_interface_phy_nr
class stack_interface_phy_nr : public mac_interface_phy_nr
{};
} // namespace srsenb

@ -133,7 +133,7 @@ struct rach_cfg_t {
};
// 38.321 5.1.1 Not complete yet
struct rach_nr_cfg_t {
struct rach_cfg_nr_t {
uint32_t prach_ConfigurationIndex;
int PreambleReceivedTargetPower;
uint32_t preambleTransMax;
@ -141,7 +141,7 @@ struct rach_nr_cfg_t {
uint32_t ra_responseWindow;
uint32_t ra_ContentionResolutionTimer;
rach_nr_cfg_t() { reset(); }
rach_cfg_nr_t() { reset(); }
void reset()
{
prach_ConfigurationIndex = 0;
@ -224,6 +224,33 @@ struct mac_cfg_t {
int time_alignment_timer = -1;
};
struct mac_cfg_nr_t {
// Default constructor with default values as in 36.331 9.2.2
mac_cfg_nr_t() { set_defaults(); }
void set_defaults()
{
rach_cfg.reset();
sr_cfg.reset();
set_mac_main_cfg_default();
}
void set_mac_main_cfg_default()
{
bsr_cfg.reset();
phr_cfg.reset();
harq_cfg.reset();
time_alignment_timer = -1;
}
bsr_cfg_t bsr_cfg;
phr_cfg_nr_t phr_cfg;
sr_cfg_t sr_cfg;
rach_cfg_nr_t rach_cfg;
ul_harq_cfg_t harq_cfg;
int time_alignment_timer = -1;
};
} // namespace srsran
#endif // SRSRAN_MAC_INTERFACE_TYPES_H

@ -36,8 +36,23 @@ class rrc_interface_phy_nr
public:
virtual void in_sync() = 0;
virtual void out_of_sync() = 0;
virtual void run_tti(const uint32_t tti) = 0;
virtual void set_phy_config_complete(bool status) = 0;
/**
* @brief Describes a cell search result
*/
struct cell_search_result_t {
bool cell_found = false;
uint32_t pci = 0; ///< Physical Cell Identifier
srsran_pbch_msg_nr_t pbch_msg; ///< Packed PBCH message for the upper layers
srsran_csi_trs_measurements_t measurements = {}; ///< Measurements from SSB block
};
/**
* @brief Informs RRC about cell search process completion
* @param result Cell search result completion
*/
virtual void cell_search_found_cell(const cell_search_result_t& result) = 0;
};
class mac_interface_phy_nr
@ -93,8 +108,6 @@ public:
tb_ul_t tb; // only single TB in UL
} tb_action_ul_t;
virtual int sf_indication(const uint32_t tti) = 0; ///< TODO: rename to slot indication
// Query the MAC for the current RNTI to look for
struct sched_rnti_t {
uint16_t id;
@ -162,7 +175,7 @@ public:
virtual int set_config(const srsran::bsr_cfg_nr_t& bsr_cfg) = 0;
virtual int set_config(const srsran::sr_cfg_nr_t& sr_cfg) = 0;
virtual int set_config(const srsran::dl_harq_cfg_nr_t& dl_hrq_cfg) = 0;
virtual void set_config(const srsran::rach_nr_cfg_t& rach_cfg) = 0;
virtual void set_config(const srsran::rach_cfg_nr_t& rach_cfg_nr) = 0;
virtual int add_tag_config(const srsran::tag_cfg_nr_t& tag_cfg) = 0;
virtual int set_config(const srsran::phr_cfg_nr_t& phr_cfg) = 0;
virtual int remove_tag_config(const uint32_t tag_id) = 0;
@ -218,20 +231,12 @@ struct phy_args_nr_t {
class phy_interface_mac_nr
{
public:
typedef struct {
uint32_t tti;
uint32_t tb_len;
uint8_t* data; // always a pointer in our case
} tx_request_t;
// MAC informs PHY about UL grant included in RAR PDU
virtual int set_ul_grant(uint32_t rar_slot_idx,
std::array<uint8_t, SRSRAN_RAR_UL_GRANT_NBITS> packed_ul_grant,
uint16_t rnti,
srsran_rnti_type_t rnti_type) = 0;
// MAC instructs PHY to transmit MAC TB at the given TTI
virtual int tx_request(const tx_request_t& request) = 0;
virtual int set_rar_grant(uint32_t rar_slot_idx,
std::array<uint8_t, SRSRAN_RAR_UL_GRANT_NBITS> packed_ul_grant,
uint16_t rnti,
srsran_rnti_type_t rnti_type) = 0;
/// Instruct PHY to send PRACH in the next occasion.
virtual void send_prach(const uint32_t prach_occasion,
@ -256,13 +261,70 @@ class phy_interface_rrc_nr
{
public:
virtual bool set_config(const srsran::phy_cfg_nr_t& cfg) = 0;
/**
* @brief Describe the possible NR standalone physical layer possible states
*/
typedef enum {
PHY_NR_STATE_IDLE = 0, ///< There is no process going on
PHY_NR_STATE_CELL_SEARCH, ///< Cell search is currently in progress
PHY_NR_STATE_CELL_SELECT, ///< Cell selection is in progress or it is camped on a cell
PHY_NR_STATE_CAMPING
} phy_nr_state_t;
/**
* @brief Retrieves the physical layer state
* @return
*/
virtual phy_nr_state_t get_state() = 0;
/**
* @brief Stops the ongoing process and transitions to IDLE
*/
virtual void reset_nr() = 0;
/**
* @brief Describes cell search arguments
*/
struct cell_search_args_t {
double srate_hz;
double center_freq_hz;
double ssb_freq_hz;
srsran_subcarrier_spacing_t ssb_scs;
srsran_ssb_patern_t ssb_pattern;
srsran_duplex_mode_t duplex_mode;
};
/**
* @brief Start cell search
* @param args Cell Search arguments
* @return true if the physical layer started successfully the cell search process
*/
virtual bool start_cell_search(const cell_search_args_t& req) = 0;
/**
* @brief Describes cell select arguments
*/
struct cell_select_args_t {
srsran_ssb_cfg_t ssb_cfg;
srsran_carrier_nr_t carrier;
};
/**
* @brief Start cell search
* @param args Cell Search arguments
* @return true if the physical layer started successfully the cell search process
*/
virtual bool start_cell_select(const cell_select_args_t& req) = 0;
};
// Combined interface for PHY to access stack (MAC and RRC)
class stack_interface_phy_nr : public mac_interface_phy_nr,
public rrc_interface_phy_nr,
public srsran::stack_interface_phy_nr
{};
class stack_interface_phy_nr : public mac_interface_phy_nr, public rrc_interface_phy_nr
{
public:
/* Indicate new TTI */
virtual void run_tti(const uint32_t tti, const uint32_t tti_jump) = 0;
};
// Combined interface for stack (MAC and RRC) to access PHY
class phy_interface_stack_nr : public phy_interface_mac_nr, public phy_interface_rrc_nr

@ -60,7 +60,7 @@ struct phy_args_t {
uint32_t nof_lte_carriers = 1;
uint32_t nof_nr_carriers = 0;
uint32_t nr_max_nof_prb = 106;
uint32_t nr_max_nof_prb = 52;
uint32_t nof_rx_ant = 1;
std::string equalizer_mode = "mmse";
int cqi_max = 15;
@ -105,7 +105,6 @@ struct phy_args_t {
srsran::channel::args_t dl_channel_args;
srsran::channel::args_t ul_channel_args;
srsran::vnf_args_t vnf_args;
};
/* RAT agnostic Interface MAC -> PHY */

@ -25,6 +25,7 @@
#include "phy_interface_types.h"
#include "rrc_interface_types.h"
#include "srsran/asn1/asn1_utils.h"
#include "srsran/asn1/rrc_nr.h"
#include "srsran/common/byte_buffer.h"
#include "srsran/common/tti_point.h"
@ -108,19 +109,13 @@ public:
class rrc_nr_interface_rrc
{
public:
virtual int get_eutra_nr_capabilities(srsran::byte_buffer_t* eutra_nr_caps) = 0;
virtual int get_nr_capabilities(srsran::byte_buffer_t* nr_cap) = 0;
virtual void phy_set_cells_to_meas(uint32_t carrier_freq_r15) = 0;
virtual void phy_meas_stop() = 0;
virtual bool rrc_reconfiguration(bool endc_release_and_add_r15,
bool nr_secondary_cell_group_cfg_r15_present,
asn1::dyn_octstring nr_secondary_cell_group_cfg_r15,
bool sk_counter_r15_present,
uint32_t sk_counter_r15,
bool nr_radio_bearer_cfg1_r15_present,
asn1::dyn_octstring nr_radio_bearer_cfg1_r15) = 0;
virtual void rrc_release() = 0;
virtual bool is_config_pending() = 0;
virtual int get_eutra_nr_capabilities(srsran::byte_buffer_t* eutra_nr_caps) = 0;
virtual int get_nr_capabilities(srsran::byte_buffer_t* nr_cap) = 0;
virtual void phy_set_cells_to_meas(uint32_t carrier_freq_r15) = 0;
virtual void phy_meas_stop() = 0;
virtual bool rrc_reconfiguration(bool endc_release_and_add_r15, const asn1::rrc_nr::rrc_recfg_s& rrc_nr_reconf) = 0;
virtual void rrc_release() = 0;
virtual bool is_config_pending() = 0;
};
class rrc_nr_interface_nas_5g

@ -107,6 +107,7 @@ public:
static const uint8_t ue_con_res_id_len = 6;
typedef std::array<uint8_t, ue_con_res_id_len> ue_con_res_id_t;
ue_con_res_id_t get_ue_con_res_id_ce();
uint64_t get_ue_con_res_id_ce_packed();
// setters
void set_sdu(const uint32_t lcid_, const uint8_t* payload_, const uint32_t len_);

@ -396,8 +396,9 @@ typedef struct SRSRAN_API {
#define SRSRAN_DEFAULT_CARRIER_NR \
{ \
.pci = 500, .dl_center_frequency_hz = 3.5e9, .ul_center_frequency_hz = 3.5e9, .ssb_center_freq_hz = 3.5e9, \
.offset_to_carrier = 0, .scs = srsran_subcarrier_spacing_15kHz, .nof_prb = 52, .start = 0, .max_mimo_layers = 1 \
.pci = 500, .dl_center_frequency_hz = 117000 * 30e3, .ul_center_frequency_hz = 117000 * 30e3, \
.ssb_center_freq_hz = 3.5e9, .offset_to_carrier = 0, .scs = srsran_subcarrier_spacing_15kHz, .nof_prb = 52, \
.start = 0, .max_mimo_layers = 1 \
}
/**

@ -125,9 +125,10 @@ typedef struct SRSRAN_API {
* @note if pbch.crc is true, SSB transmission is found and decoded. Otherwise, no SSB transmission has been decoded
*/
typedef struct {
uint32_t N_id; ///< Most suitable physical cell identifier
uint32_t t_offset; ///< Time offset in the input samples
srsran_pbch_msg_nr_t pbch_msg; ///< Physical broadcast channel message of the most suitable SSB candidate
uint32_t N_id; ///< Most suitable physical cell identifier
uint32_t t_offset; ///< Time offset in the input samples
srsran_pbch_msg_nr_t pbch_msg; ///< Physical broadcast channel message of the most suitable SSB candidate
srsran_csi_trs_measurements_t measurements; ///< Measurements
} srsran_ssb_search_res_t;
/**

@ -109,10 +109,15 @@ public:
void stop() final;
bool inside_tx_window(uint32_t sn);
private:
rlc_am* parent = nullptr;
rlc_am_nr_rx* rx = nullptr;
uint32_t mod_nr = 4096;
inline uint32_t tx_mod_base_nr(uint32_t sn) const;
/****************************************************************************
* Configurable parameters
* Ref: 3GPP TS 38.322 v16.2.0 Section 7.4
@ -131,10 +136,39 @@ private:
public:
// Getters/Setters
void set_tx_state(const rlc_am_nr_tx_state_t& st_) { st = st_; } // This should only be used for testing.
rlc_am_nr_tx_state_t get_tx_state() { return st; } // This should only be used for testing.
uint32_t get_tx_window_size() { return tx_window.size(); } // This should only be used for testing.
};
/****************************************************************************
* RX State Variables
* Ref: 3GPP TS 38.322 v16.2.0 Section 7.1
***************************************************************************/
struct rlc_am_nr_rx_state_t {
/*
* RX_Next: This state variable holds the value of the SN following the last in-sequence completely received RLC
* SDU, and it serves as the lower edge of the receiving window. It is initially set to 0, and is updated whenever
* the AM RLC entity receives an RLC SDU with SN = RX_Next.
*/
uint32_t rx_next = 0;
/*
* RX_Next_Status_Trigger: This state variable holds the value of the SN following the SN of the RLC SDU which
* triggered t-Reassembly.
*/
uint32_t rx_next_status_trigger = 0;
/*
* RX_Next_Highest: This state variable holds the highest possible value of the SN which can be indicated by
*"ACK_SN" when a STATUS PDU needs to be constructed. It is initially set to 0.
*/
uint32_t rx_highest_status = 0;
/*
* RX_Next_Highest: This state variable holds the value of the SN following the SN of the RLC SDU with the
* highest SN among received RLC SDUs. It is initially set to 0.
*/
uint32_t rx_next_highest = 0;
};
// Receiver sub-class
class rlc_am_nr_rx : public rlc_am::rlc_am_base_rx
{
@ -175,38 +209,15 @@ private:
rlc_am_nr_tx* tx = nullptr;
byte_buffer_pool* pool = nullptr;
uint32_t mod_nr = 4096;
uint32_t rx_mod_base_nr(uint32_t sn) const;
// RX Window
rlc_ringbuffer_t<rlc_amd_rx_sdu_nr_t, RLC_AM_WINDOW_SIZE> rx_window;
// Mutexes
std::mutex mutex;
/****************************************************************************
* State Variables
* Ref: 3GPP TS 38.322 v16.2.0 Section 7.1
***************************************************************************/
/*
* RX_Next: This state variable holds the value of the SN following the last in-sequence completely received RLC
* SDU, and it serves as the lower edge of the receiving window. It is initially set to 0, and is updated whenever
* the AM RLC entity receives an RLC SDU with SN = RX_Next.
*/
uint32_t rx_next = 0;
/*
* RX_Next_Status_Trigger: This state variable holds the value of the SN following the SN of the RLC SDU which
* triggered t-Reassembly.
*/
uint32_t rx_next_status_trigger = 0;
/*
* RX_Next_Highest: This state variable holds the highest possible value of the SN which can be indicated by
*"ACK_SN" when a STATUS PDU needs to be constructed. It is initially set to 0.
*/
uint32_t rx_highest_status = 0;
/*
* RX_Next_Highest: This state variable holds the value of the SN following the SN of the RLC SDU with the
* highest SN among received RLC SDUs. It is initially set to 0.
*/
uint32_t rx_next_highest = 0;
/****************************************************************************
* Rx timers
* Ref: 3GPP TS 38.322 v16.2.0 Section 7.3
@ -219,6 +230,18 @@ private:
* Ref: 3GPP TS 38.322 v16.2.0 Section 7.4
***************************************************************************/
rlc_am_nr_config_t cfg = {};
/****************************************************************************
* Tx state variables
* Ref: 3GPP TS 38.322 v16.2.0 Section 7.1
***************************************************************************/
struct rlc_am_nr_rx_state_t st = {};
public:
// Getters/Setters
void set_rx_state(const rlc_am_nr_rx_state_t& st_) { st = st_; } // This should only be used for testing.
rlc_am_nr_rx_state_t get_rx_state() { return st; } // This should only be used for testing.
uint32_t get_rx_window_size() { return rx_window.size(); } // This should only be used for testing.
};
} // namespace srsran

@ -122,6 +122,7 @@ extern "C" {
#include "srsran/phy/ue/ue_dl_nr.h"
#include "srsran/phy/ue/ue_mib.h"
#include "srsran/phy/ue/ue_sync.h"
#include "srsran/phy/ue/ue_sync_nr.h"
#include "srsran/phy/ue/ue_ul.h"
#include "srsran/phy/ue/ue_ul_nr.h"

@ -65,6 +65,15 @@
*/
#define srsran_assert(condition, fmt, ...) srsran_assert_ifdef(ASSERTS_ENABLED, condition, fmt, ##__VA_ARGS__)
/**
* Specialization of "srsran_assert_ifdef" for the SANITY_CHECKS_ENABLED flag
*/
#ifndef NDEBUG
#define SANITY_CHECKS_ENABLED
#endif
#define srsran_sanity_check(condition, fmt, ...) \
srsran_assert_ifdef(SANITY_CHECKS_ENABLED, condition, fmt, ##__VA_ARGS__)
#ifdef STOP_ON_WARNING
#define srsran_expect(condition, fmt, ...) srsran_assert(condition, fmt, ##__VA_ARGS__)

@ -153,7 +153,7 @@ SRSASN_CODE bit_ref::pack(uint64_t val, uint32_t n_bits)
uint64_t mask;
while (n_bits > 0) {
if (ptr >= max_ptr) {
log_error("Buffer size limit was achieved");
log_error("pack: Buffer size limit was achieved");
return SRSASN_ERROR_ENCODE_FAIL;
}
mask = ((1ul << n_bits) - 1ul);
@ -185,7 +185,7 @@ SRSASN_CODE unpack_bits(T& val, Ptr& ptr, uint8_t& offset, const uint8_t* max_pt
val = 0;
while (n_bits > 0) {
if (ptr >= max_ptr) {
log_error("Buffer size limit was achieved");
log_error("unpack_bits: Buffer size limit was achieved");
return SRSASN_ERROR_DECODE_FAIL;
}
if ((uint32_t)(8 - offset) > n_bits) {
@ -205,35 +205,35 @@ SRSASN_CODE unpack_bits(T& val, Ptr& ptr, uint8_t& offset, const uint8_t* max_pt
}
template SRSASN_CODE
unpack_bits<bool, uint8_t*>(bool& val, uint8_t*& ptr, uint8_t& offset, const uint8_t* max_ptr, uint32_t n_bits);
unpack_bits<bool, uint8_t*>(bool& val, uint8_t*& ptr, uint8_t& offset, const uint8_t* max_ptr, uint32_t n_bits);
template SRSASN_CODE unpack_bits<bool, const uint8_t*>(bool& val,
const uint8_t*& ptr,
uint8_t& offset,
const uint8_t* max_ptr,
uint32_t n_bits);
template SRSASN_CODE
unpack_bits<uint8_t, uint8_t*>(uint8_t& val, uint8_t*& ptr, uint8_t& offset, const uint8_t* max_ptr, uint32_t n_bits);
unpack_bits<uint8_t, uint8_t*>(uint8_t& val, uint8_t*& ptr, uint8_t& offset, const uint8_t* max_ptr, uint32_t n_bits);
template SRSASN_CODE unpack_bits<uint8_t, const uint8_t*>(uint8_t& val,
const uint8_t*& ptr,
uint8_t& offset,
const uint8_t* max_ptr,
uint32_t n_bits);
template SRSASN_CODE
unpack_bits<uint16_t, uint8_t*>(uint16_t& val, uint8_t*& ptr, uint8_t& offset, const uint8_t* max_ptr, uint32_t n_bits);
unpack_bits<uint16_t, uint8_t*>(uint16_t& val, uint8_t*& ptr, uint8_t& offset, const uint8_t* max_ptr, uint32_t n_bits);
template SRSASN_CODE unpack_bits<uint16_t, const uint8_t*>(uint16_t& val,
const uint8_t*& ptr,
uint8_t& offset,
const uint8_t* max_ptr,
uint32_t n_bits);
template SRSASN_CODE
unpack_bits<uint32_t, uint8_t*>(uint32_t& val, uint8_t*& ptr, uint8_t& offset, const uint8_t* max_ptr, uint32_t n_bits);
unpack_bits<uint32_t, uint8_t*>(uint32_t& val, uint8_t*& ptr, uint8_t& offset, const uint8_t* max_ptr, uint32_t n_bits);
template SRSASN_CODE unpack_bits<uint32_t, const uint8_t*>(uint32_t& val,
const uint8_t*& ptr,
uint8_t& offset,
const uint8_t* max_ptr,
uint32_t n_bits);
template SRSASN_CODE
unpack_bits<uint64_t, uint8_t*>(uint64_t& val, uint8_t*& ptr, uint8_t& offset, const uint8_t* max_ptr, uint32_t n_bits);
unpack_bits<uint64_t, uint8_t*>(uint64_t& val, uint8_t*& ptr, uint8_t& offset, const uint8_t* max_ptr, uint32_t n_bits);
template SRSASN_CODE unpack_bits<uint64_t, const uint8_t*>(uint64_t& val,
const uint8_t*& ptr,
uint8_t& offset,
@ -249,7 +249,7 @@ SRSASN_CODE bit_ref_impl<Ptr>::unpack_bytes(uint8_t* buf, uint32_t n_bytes)
if (offset == 0) {
// Aligned case
if (ptr + n_bytes > max_ptr) {
log_error("Buffer size limit was achieved");
log_error("unpack_bytes (aligned): Buffer size limit was achieved");
return SRSASN_ERROR_DECODE_FAIL;
}
memcpy(buf, ptr, n_bytes);
@ -257,7 +257,7 @@ SRSASN_CODE bit_ref_impl<Ptr>::unpack_bytes(uint8_t* buf, uint32_t n_bytes)
} else {
// Unaligned case
if (ptr + n_bytes >= max_ptr) {
log_error("Buffer size limit was achieved");
log_error("unpack_bytes (unaligned): Buffer size limit was achieved");
return SRSASN_ERROR_DECODE_FAIL;
}
for (uint32_t i = 0; i < n_bytes; ++i) {
@ -273,7 +273,7 @@ SRSASN_CODE bit_ref_impl<Ptr>::align_bytes()
if (offset == 0)
return SRSASN_SUCCESS;
if (ptr >= max_ptr) {
log_error("Buffer size limit was achieved");
log_error("align_bytes: Buffer size limit was achieved");
return SRSASN_ERROR_DECODE_FAIL;
}
offset = 0;
@ -289,7 +289,7 @@ SRSASN_CODE bit_ref_impl<Ptr>::advance_bits(uint32_t n_bits)
uint32_t bytes_offset = floorf((offset + n_bits) / 8.0f);
if (ptr + bytes_required > max_ptr) {
log_error("Buffer size limit was achieved");
log_error("advance_bytes: Buffer size limit was achieved");
return SRSASN_ERROR_DECODE_FAIL;
}
ptr += bytes_offset;
@ -315,7 +315,7 @@ SRSASN_CODE bit_ref::pack_bytes(const uint8_t* buf, uint32_t n_bytes)
return SRSASN_SUCCESS;
}
if (ptr + n_bytes >= max_ptr) {
log_error("Buffer size limit was achieved");
log_error("pack_bytes: Buffer size limit was achieved");
return SRSASN_ERROR_ENCODE_FAIL;
}
if (offset == 0) {
@ -335,7 +335,7 @@ SRSASN_CODE bit_ref::align_bytes_zero()
if (offset == 0)
return SRSASN_SUCCESS;
if (ptr >= max_ptr) {
log_error("Buffer size limit was achieved");
log_error("align_bytes_zero: Buffer size limit was achieved");
return SRSASN_ERROR_ENCODE_FAIL;
}
auto mask = static_cast<uint8_t>(256u - (1u << (8u - offset)));
@ -849,7 +849,7 @@ pack_integer<uint16_t>(bit_ref& bref, uint16_t n, uint16_t lb, uint16_t ub, bool
template SRSASN_CODE
pack_integer<uint32_t>(bit_ref& bref, uint32_t n, uint32_t lb, uint32_t ub, bool has_ext, bool aligned);
template SRSASN_CODE
pack_integer<uint64_t>(bit_ref& bref, uint64_t n, uint64_t lb, uint64_t ub, bool has_ext, bool aligned);
pack_integer<uint64_t>(bit_ref& bref, uint64_t n, uint64_t lb, uint64_t ub, bool has_ext, bool aligned);
template SRSASN_CODE pack_integer<int8_t>(bit_ref& bref, int8_t n, int8_t lb, int8_t ub, bool has_ext, bool aligned);
template SRSASN_CODE
pack_integer<int16_t>(bit_ref& bref, int16_t n, int16_t lb, int16_t ub, bool has_ext, bool aligned);

@ -99,16 +99,14 @@ bool make_mac_phr_cfg_t(const phr_cfg_s& asn1_type, phr_cfg_nr_t* phr_cfg_nr)
return true;
}
rach_nr_cfg_t make_mac_rach_cfg(const rach_cfg_common_s& asn1_type)
void make_mac_rach_cfg(const rach_cfg_common_s& asn1_type, rach_cfg_nr_t* rach_cfg_nr)
{
rach_nr_cfg_t rach_nr_cfg = {};
rach_nr_cfg.powerRampingStep = asn1_type.rach_cfg_generic.pwr_ramp_step.to_number();
rach_nr_cfg.ra_responseWindow = asn1_type.rach_cfg_generic.ra_resp_win.to_number();
rach_nr_cfg.prach_ConfigurationIndex = asn1_type.rach_cfg_generic.prach_cfg_idx;
rach_nr_cfg.PreambleReceivedTargetPower = asn1_type.rach_cfg_generic.preamb_rx_target_pwr;
rach_nr_cfg.preambleTransMax = asn1_type.rach_cfg_generic.preamb_trans_max.to_number();
rach_nr_cfg.ra_ContentionResolutionTimer = asn1_type.ra_contention_resolution_timer.to_number();
return rach_nr_cfg;
rach_cfg_nr->powerRampingStep = asn1_type.rach_cfg_generic.pwr_ramp_step.to_number();
rach_cfg_nr->ra_responseWindow = asn1_type.rach_cfg_generic.ra_resp_win.to_number();
rach_cfg_nr->prach_ConfigurationIndex = asn1_type.rach_cfg_generic.prach_cfg_idx;
rach_cfg_nr->PreambleReceivedTargetPower = asn1_type.rach_cfg_generic.preamb_rx_target_pwr;
rach_cfg_nr->preambleTransMax = asn1_type.rach_cfg_generic.preamb_trans_max.to_number();
rach_cfg_nr->ra_ContentionResolutionTimer = asn1_type.ra_contention_resolution_timer.to_number();
};
int make_rlc_config_t(const rlc_cfg_c& asn1_type, uint8_t bearer_id, rlc_config_t* cfg_out)
@ -1633,7 +1631,7 @@ bool fill_phy_pdcch_cfg(const asn1::rrc_nr::pdcch_cfg_s& pdcch_cfg, srsran_pdcch
return true;
}
bool fill_phy_pdcch_cfg_common(const asn1::rrc_nr::pdcch_cfg_common_s& pdcch_cfg, srsran_pdcch_cfg_nr_t* pdcch)
void fill_phy_pdcch_cfg_common(const asn1::rrc_nr::pdcch_cfg_common_s& pdcch_cfg, srsran_pdcch_cfg_nr_t* pdcch)
{
if (pdcch_cfg.common_ctrl_res_set_present) {
pdcch->coreset_present[pdcch_cfg.common_ctrl_res_set.ctrl_res_set_id] = true;
@ -1649,10 +1647,91 @@ bool fill_phy_pdcch_cfg_common(const asn1::rrc_nr::pdcch_cfg_common_s& pdcch_cfg
}
}
}
}
void fill_phy_pucch_cfg_common(const asn1::rrc_nr::pucch_cfg_common_s& pucch_cfg, srsran_pucch_nr_common_cfg_t* pucch)
{
if (pucch_cfg.pucch_res_common_present) {
pucch->resource_common = pucch_cfg.pucch_res_common;
}
if (pucch_cfg.hop_id_present) {
pucch->hopping_id_present = true;
pucch->hopping_id = pucch_cfg.hop_id;
}
if (pucch_cfg.p0_nominal_present) {
pucch->p0_nominal = pucch_cfg.p0_nominal;
}
switch (pucch_cfg.pucch_group_hop) {
case pucch_cfg_common_s::pucch_group_hop_opts::enable:
pucch->group_hopping = SRSRAN_PUCCH_NR_GROUP_HOPPING_ENABLE;
break;
case pucch_cfg_common_s::pucch_group_hop_opts::disable:
pucch->group_hopping = SRSRAN_PUCCH_NR_GROUP_HOPPING_DISABLE;
break;
default:
pucch->group_hopping = SRSRAN_PUCCH_NR_GROUP_HOPPING_NEITHER;
break;
}
}
bool fill_phy_pdsch_cfg_common(const asn1::rrc_nr::pdsch_cfg_common_s& pdsch_cfg, srsran_sch_hl_cfg_nr_t* pdsch)
{
for (uint32_t i = 0; i < pdsch_cfg.pdsch_time_domain_alloc_list.size(); i++) {
srsran_sch_time_ra_t common_time_ra;
if (make_phy_common_time_ra(pdsch_cfg.pdsch_time_domain_alloc_list[i], &common_time_ra) == true) {
pdsch->common_time_ra[i] = common_time_ra;
pdsch->nof_common_time_ra = i + 1;
} else {
asn1::log_warning("Warning while building common_time_ra structure");
return false;
}
}
return true;
}
bool fill_phy_pusch_cfg_common(const asn1::rrc_nr::pusch_cfg_common_s& pusch_cfg, srsran_sch_hl_cfg_nr_t* pusch)
{
for (uint32_t i = 0; i < pusch_cfg.pusch_time_domain_alloc_list.size(); i++) {
srsran_sch_time_ra_t common_time_ra;
if (make_phy_common_time_ra(pusch_cfg.pusch_time_domain_alloc_list[i], &common_time_ra) == true) {
pusch->common_time_ra[i] = common_time_ra;
pusch->nof_common_time_ra = i + 1;
} else {
asn1::log_warning("Warning while building common_time_ra structure");
return false;
}
}
return true;
}
void fill_phy_carrier_cfg(const asn1::rrc_nr::serving_cell_cfg_common_sib_s& serv_cell_cfg,
srsran_carrier_nr_t* out_carrier_nr)
{
// TODO: Currently ony one carrier is supported
auto& freq_info_dl = serv_cell_cfg.dl_cfg_common.freq_info_dl;
out_carrier_nr->offset_to_carrier = freq_info_dl.scs_specific_carrier_list[0].offset_to_carrier;
out_carrier_nr->scs = make_subcarrier_spacing(freq_info_dl.scs_specific_carrier_list[0].subcarrier_spacing);
out_carrier_nr->nof_prb = freq_info_dl.scs_specific_carrier_list[0].carrier_bw;
auto& freq_info_ul = serv_cell_cfg.ul_cfg_common.freq_info_ul;
srsran::srsran_band_helper bands;
out_carrier_nr->ul_center_frequency_hz = bands.get_center_freq_from_abs_freq_point_a(
freq_info_ul.scs_specific_carrier_list[0].carrier_bw, freq_info_ul.absolute_freq_point_a);
}
void fill_phy_ssb_cfg(const asn1::rrc_nr::serving_cell_cfg_common_sib_s& serv_cell_cfg,
phy_cfg_nr_t::ssb_cfg_t* out_ssb)
{
out_ssb->periodicity_ms = serv_cell_cfg.ssb_periodicity_serving_cell.to_number();
if (serv_cell_cfg.ssb_positions_in_burst.group_presence_present) {
make_ssb_positions_in_burst(serv_cell_cfg.ssb_positions_in_burst.group_presence, out_ssb->position_in_burst);
} else {
make_ssb_positions_in_burst(serv_cell_cfg.ssb_positions_in_burst.in_one_group, out_ssb->position_in_burst);
}
}
/**************************
* Asn1 Obj Id
*************************/

@ -26,7 +26,6 @@
namespace srsran {
srsran_dci_cfg_nr_t phy_cfg_nr_t::get_dci_cfg() const
{
srsran_dci_cfg_nr_t dci_cfg = {};

@ -181,6 +181,9 @@ void phy_cfg_nr_default_t::make_pdsch_default(srsran_sch_hl_cfg_nr_t& pdsch)
pdsch.common_time_ra[0].sliv = srsran_ra_type2_to_riv(SRSRAN_NSYMB_PER_SLOT_NR - 1, 1, SRSRAN_NSYMB_PER_SLOT_NR);
pdsch.nof_common_time_ra = 1;
// Set contiguous PRBs as default
pdsch.alloc = srsran_resource_alloc_type1;
// Setup PDSCH DMRS type A position
pdsch.typeA_pos = srsran_dmrs_sch_typeA_pos_2;
}

@ -341,6 +341,17 @@ mac_sch_subpdu_nr::ue_con_res_id_t mac_sch_subpdu_nr::get_ue_con_res_id_ce()
return id;
}
uint64_t mac_sch_subpdu_nr::get_ue_con_res_id_ce_packed()
{
if (!parent->is_ulsch() && lcid == CON_RES_ID) {
const uint8_t* payload = sdu.ptr();
return le64toh(((uint64_t)payload[5]) | (((uint64_t)payload[4]) << 8) | (((uint64_t)payload[3]) << 16) |
(((uint64_t)payload[2]) << 24) | (((uint64_t)payload[1]) << 32) | (((uint64_t)payload[0]) << 40));
} else {
return 0;
}
}
uint32_t mac_sch_subpdu_nr::sizeof_ce(uint32_t lcid, bool is_ul)
{
if (is_ul) {

@ -286,6 +286,9 @@ clean_exit:
if (output_buffer) {
free(output_buffer);
}
if (help_buffer) {
free(help_buffer);
}
#ifdef ENABLE_GUI
if (fft_out) {

@ -1130,10 +1130,17 @@ int srsran_ssb_search(srsran_ssb_t* q, const cf_t* in, uint32_t nof_samples, srs
return SRSRAN_ERROR;
}
srsran_csi_trs_measurements_t measurements = {};
if (ssb_measure(q, ssb_grid, N_id, &measurements) < SRSRAN_SUCCESS) {
ERROR("Error measuring");
return SRSRAN_ERROR;
}
// Save result
res->N_id = N_id;
res->t_offset = t_offset;
res->pbch_msg = pbch_msg;
res->N_id = N_id;
res->t_offset = t_offset;
res->pbch_msg = pbch_msg;
res->measurements = measurements;
return SRSRAN_SUCCESS;
}

@ -47,7 +47,7 @@ int fdd_tests(uint32_t max_cc)
ue_dl.cell.frame_type = SRSRAN_FDD;
for (uint32_t nof_cc = 1; nof_cc <= max_cc; nof_cc++) {
for (uint32_t nof_tb = 1; nof_tb <= SRSRAN_MAX_CODEWORDS; nof_tb++) {
for (uint8_t nof_tb = 1; nof_tb <= SRSRAN_MAX_CODEWORDS; nof_tb++) {
for (uint32_t nof_active_cc = 1; nof_active_cc <= nof_cc; nof_active_cc++) {
for (uint32_t nof_active_tb = 1; nof_active_tb <= nof_tb; nof_active_tb++) {
srsran_pdsch_ack_t ack_info = {};
@ -62,7 +62,7 @@ int fdd_tests(uint32_t max_cc)
ack_info.cc[cc_idx].m[0].present = cc_idx < nof_active_cc;
ack_info.cc[cc_idx].m[0].resource.n_cce = cc_idx + 1;
if (ack_info.cc[cc_idx].m[0].present) {
for (uint32_t j = 0; j < nof_tb; j++) {
for (uint8_t j = 0; j < nof_tb; j++) {
ack_info.cc[cc_idx].m[0].value[j] = j < nof_active_tb ? 1 : 2;
}
} else {

@ -142,9 +142,12 @@ int srsran_ue_ul_nr_encode_pusch(srsran_ue_ul_nr_t* q,
// Generate signal
srsran_ofdm_tx_sf(&q->ifft);
// Scale iFFT output to compensate for iFFT amplification (due to FFTW implementation).
float scaling = 1 / sqrtf(q->ifft.cfg.symbol_sz);
srsran_vec_sc_prod_cfc(q->ifft.cfg.out_buffer, scaling, q->ifft.cfg.out_buffer, q->ifft.sf_sz);
// Normalise to peak
uint32_t max_idx = srsran_vec_max_abs_ci(q->ifft.cfg.out_buffer, q->ifft.sf_sz);
float max_peak = cabsf(q->ifft.cfg.out_buffer[max_idx]);
if (isnormal(max_peak)) {
srsran_vec_sc_prod_cfc(q->ifft.cfg.out_buffer, 0.99f / max_peak, q->ifft.cfg.out_buffer, q->ifft.sf_sz);
}
// Apply frequency offset
if (isnormal(q->freq_offset_hz)) {

@ -402,23 +402,28 @@ void rlc_am_lte_tx::retransmit_pdu(uint32_t sn)
bool rlc_am_lte_tx::poll_required()
{
if (cfg.poll_pdu > 0 && pdu_without_poll > static_cast<uint32_t>(cfg.poll_pdu)) {
logger->debug("Poll required. Cause: PDU_WITHOUT_POLL > pollPdu.");
return true;
}
if (cfg.poll_byte > 0 && byte_without_poll > static_cast<uint32_t>(cfg.poll_byte)) {
logger->debug("Poll required. Cause: BYTE_WITHOUT_POLL > pollByte.");
return true;
}
if (poll_retx_timer.is_valid() && poll_retx_timer.is_expired()) {
// re-arming of timer is handled by caller
logger->debug("Poll required. Cause: t-PollRetransmission expired.");
return true;
}
if (tx_window.size() >= RLC_AM_WINDOW_SIZE) {
logger->debug("Poll required. Cause: TX window full.");
return true;
}
if (tx_sdu_queue.size() == 0 && retx_queue.empty()) {
logger->debug("Poll required. Cause: Empty TX and ReTX queues.");
return true;
}
@ -514,6 +519,7 @@ int rlc_am_lte_tx::build_retx_pdu(uint8_t* payload, uint32_t nof_bytes)
byte_without_poll = 0;
if (poll_retx_timer.is_valid()) {
// re-arm timer (will be stopped when status PDU is received)
logger->debug("%s re-arming retx timer", RB_NAME);
poll_retx_timer.run();
}
}
@ -565,17 +571,7 @@ int rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_r
new_header.lsf = 0;
new_header.so = retx.so_start;
new_header.N_li = 0;
new_header.p = 0;
if (poll_required()) {
logger->debug("%s setting poll bit to request status", RB_NAME);
new_header.p = 1;
// vt_s won't change for reTx, so don't update poll_sn
pdu_without_poll = 0;
byte_without_poll = 0;
if (poll_retx_timer.is_valid()) {
poll_retx_timer.run();
}
}
new_header.p = 0; // Poll Requriments are done later after updating RETX queue
uint32_t head_len = 0;
uint32_t pdu_space = 0;
@ -681,6 +677,18 @@ int rlc_am_lte_tx::build_segment(uint8_t* payload, uint32_t nof_bytes, rlc_amd_r
retx_queue.front().so_start = retx.so_end;
}
// Check POLL requeriments for segment
if (poll_required()) {
logger->debug("%s setting poll bit to request status", RB_NAME);
new_header.p = 1;
// vt_s won't change for reTx, so don't update poll_sn
pdu_without_poll = 0;
byte_without_poll = 0;
if (poll_retx_timer.is_valid()) {
poll_retx_timer.run();
}
}
// Write header and pdu
uint8_t* ptr = payload;
rlc_am_write_data_pdu_header(&new_header, &ptr);

@ -29,9 +29,6 @@
#include <iostream>
#define RLC_AM_NR_WINDOW_SIZE 2048
#define MOD_NR 4096
#define RX_MOD_BASE_NR(x) (((x)-rx_next) % MOD_NR)
//#define TX_MOD_BASE_NR(x) (((x)-vt_a) % MOD_NR)
namespace srsran {
@ -61,6 +58,9 @@ bool rlc_am_nr_tx::configure(const rlc_config_t& cfg_)
return false;
}
*/
mod_nr = (cfg.tx_sn_field_length == rlc_am_nr_sn_size_t::size12bits) ? 4096 : 262144;
tx_enabled = true;
return true;
@ -390,6 +390,11 @@ uint8_t rlc_am_nr_tx::get_pdu_poll()
return poll;
}
bool rlc_am_nr_tx::do_status()
{
return rx->get_do_status();
}
void rlc_am_nr_tx::reestablish()
{
stop();
@ -404,12 +409,20 @@ bool rlc_am_nr_tx::sdu_queue_is_full()
void rlc_am_nr_tx::empty_queue() {}
bool rlc_am_nr_tx::do_status()
void rlc_am_nr_tx::stop() {}
/*
* Window helpers
*/
uint32_t rlc_am_nr_tx::tx_mod_base_nr(uint32_t sn) const
{
return rx->get_do_status();
return (sn - st.tx_next_ack) % mod_nr;
}
void rlc_am_nr_tx::stop() {}
bool rlc_am_nr_tx::inside_tx_window(uint32_t sn)
{
// TX_Next_Ack <= SN < TX_Next_Ack + AM_Window_Size
return tx_mod_base_nr(sn) < RLC_AM_NR_WINDOW_SIZE;
}
/****************************************************************************
* Rx subclass implementation
@ -438,6 +451,7 @@ bool rlc_am_nr_rx::configure(const rlc_config_t& cfg_)
logger->info("Configured reassembly timer. t-Reassembly=%d ms", cfg.t_reassembly);
}
mod_nr = (cfg.rx_sn_field_length == rlc_am_nr_sn_size_t::size12bits) ? 4096 : 262144;
return true;
}
@ -462,8 +476,8 @@ void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
logger->info("%s SN=%d outside rx window [%d:%d] - discarding",
parent->rb_name,
header.sn,
rx_next,
rx_next + RLC_AM_NR_WINDOW_SIZE);
st.rx_next,
st.rx_next + RLC_AM_NR_WINDOW_SIZE);
return;
}
@ -518,8 +532,8 @@ void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
// 5.2.3.2.3 Actions when an AMD PDU is placed in the reception buffer
// Update Rx_Next_Highest
if (RX_MOD_BASE_NR(header.sn) >= RX_MOD_BASE_NR(rx_next_highest)) {
rx_next_highest = (header.sn + 1) % MOD;
if (rx_mod_base_nr(header.sn) >= rx_mod_base_nr(st.rx_next_highest)) {
st.rx_next_highest = (header.sn + 1) % MOD;
}
// Update RX_Highest_Status
@ -528,10 +542,10 @@ void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
* - update RX_Highest_Status to the SN of the first RLC SDU with SN > current RX_Highest_Status for which not
* all bytes have been received.
*/
if (RX_MOD_BASE_NR(header.sn) == RX_MOD_BASE_NR(rx_highest_status)) {
if (rx_mod_base_nr(header.sn) == rx_mod_base_nr(st.rx_highest_status)) {
uint32_t sn_upd = 0;
uint32_t window_top = rx_next + RLC_AM_WINDOW_SIZE;
for (sn_upd = rx_highest_status; sn_upd < window_top; ++sn_upd) {
uint32_t window_top = st.rx_next + RLC_AM_WINDOW_SIZE;
for (sn_upd = st.rx_highest_status; sn_upd < window_top; ++sn_upd) {
if (rx_window.has_sn(sn_upd)) {
if (not rx_window[sn_upd].fully_received) {
break; // first SDU not fully received
@ -542,7 +556,7 @@ void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
}
// Update to the SN of the first SDU with missing bytes.
// If it not exists, update to the end of the rx_window.
rx_highest_status = sn_upd;
st.rx_highest_status = sn_upd;
}
/*
@ -550,10 +564,10 @@ void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
* - update RX_Next to the SN of the first RLC SDU with SN > current RX_Next for which not all bytes
* have been received.
*/
if (RX_MOD_BASE_NR(header.sn) == RX_MOD_BASE_NR(rx_next)) {
if (rx_mod_base_nr(header.sn) == rx_mod_base_nr(st.rx_next)) {
uint32_t sn_upd = 0;
uint32_t window_top = rx_next + RLC_AM_WINDOW_SIZE;
for (sn_upd = rx_next; sn_upd < window_top; ++sn_upd) {
uint32_t window_top = st.rx_next + RLC_AM_WINDOW_SIZE;
for (sn_upd = st.rx_next; sn_upd < window_top; ++sn_upd) {
if (rx_window.has_sn(sn_upd)) {
if (not rx_window[sn_upd].fully_received) {
break; // first SDU not fully received
@ -567,7 +581,7 @@ void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
}
// Update to the SN of the first SDU with missing bytes.
// If it not exists, update to the end of the rx_window.
rx_next = sn_upd;
st.rx_next = sn_upd;
}
if (reassembly_timer.is_running()) {
@ -589,26 +603,20 @@ void rlc_am_nr_rx::handle_data_pdu(uint8_t* payload, uint32_t nof_bytes)
* - set RX_Next_Status_Trigger to RX_Next_Highest.
*/
bool restart_reassembly_timer = false;
if (rx_next_highest > rx_next + 1) {
if (st.rx_next_highest > st.rx_next + 1) {
restart_reassembly_timer = true;
}
if (rx_next_highest == rx_next + 1 &&
rx_window[rx_next + 1].fully_received == false) { // TODO: does the last by need to be received?
if (st.rx_next_highest == st.rx_next + 1 &&
rx_window[st.rx_next + 1].fully_received == false) { // TODO: does the last by need to be received?
restart_reassembly_timer = true;
}
if (restart_reassembly_timer) {
reassembly_timer.run();
rx_next_status_trigger = rx_next_highest;
st.rx_next_status_trigger = st.rx_next_highest;
}
}
}
bool rlc_am_nr_rx::inside_rx_window(uint32_t sn)
{
return (RX_MOD_BASE_NR(sn) >= RX_MOD_BASE_NR(rx_next)) &&
(RX_MOD_BASE_NR(sn) < RX_MOD_BASE_NR(rx_next + RLC_AM_NR_WINDOW_SIZE));
}
/*
* Status PDU
*/
@ -620,13 +628,12 @@ uint32_t rlc_am_nr_rx::get_status_pdu(rlc_am_nr_status_pdu_t* status, uint32_t m
}
status->N_nack = 0;
status->ack_sn = rx_next; // Start with the lower end of the window
status->ack_sn = st.rx_next; // Start with the lower end of the window
byte_buffer_t tmp_buf;
uint32_t len;
uint32_t i = status->ack_sn;
while (RX_MOD_BASE_NR(i) <= RX_MOD_BASE_NR(rx_highest_status)) {
if (rx_window.has_sn(i) || i == rx_highest_status) {
while (rx_mod_base_nr(i) <= rx_mod_base_nr(st.rx_highest_status)) {
if (rx_window.has_sn(i) || i == st.rx_highest_status) {
// only update ACK_SN if this SN has been received, or if we reached the maximum possible SN
status->ack_sn = i;
} else {
@ -683,22 +690,23 @@ void rlc_am_nr_rx::timer_expired(uint32_t timeout_id)
* - start t-Reassembly;
* - set RX_Next_Status_Trigger to RX_Next_Highest.
*/
for (uint32_t tmp_sn = rx_next_status_trigger; tmp_sn < rx_next_status_trigger + RLC_AM_WINDOW_SIZE; tmp_sn++) {
for (uint32_t tmp_sn = st.rx_next_status_trigger; tmp_sn < st.rx_next_status_trigger + RLC_AM_WINDOW_SIZE;
tmp_sn++) {
if (not rx_window.has_sn(tmp_sn) || not rx_window[tmp_sn].fully_received) {
rx_highest_status = tmp_sn;
st.rx_highest_status = tmp_sn;
break;
}
}
bool restart_reassembly_timer = false;
if (rx_next_highest > rx_highest_status + 1) {
if (st.rx_next_highest > st.rx_highest_status + 1) {
restart_reassembly_timer = true;
}
if (rx_next_highest == rx_highest_status + 1 && not rx_window[rx_next_highest].fully_received) {
if (st.rx_next_highest == st.rx_highest_status + 1 && not rx_window[st.rx_next_highest].fully_received) {
restart_reassembly_timer = true;
}
if (restart_reassembly_timer) {
reassembly_timer.run();
rx_next_status_trigger = rx_next_highest;
st.rx_next_status_trigger = st.rx_next_highest;
}
/* 5.3.4 Status reporting:
@ -720,6 +728,20 @@ void rlc_am_nr_rx::write_to_upper_layers(uint32_t lcid, unique_byte_buffer_t sdu
parent->metrics.num_rx_sdu_bytes += nof_bytes;
}
/*
* Window Helpers
*/
uint32_t rlc_am_nr_rx::rx_mod_base_nr(uint32_t sn) const
{
return (sn - st.rx_next) % mod_nr;
}
bool rlc_am_nr_rx::inside_rx_window(uint32_t sn)
{
// RX_Next <= SN < RX_Next + AM_Window_Size
return rx_mod_base_nr(sn) < RLC_AM_NR_WINDOW_SIZE;
}
/*
* Metrics
*/
@ -734,14 +756,14 @@ uint32_t rlc_am_nr_rx::get_rx_buffered_bytes()
}
/*
* Helpers
* Debug Helpers
*/
void rlc_am_nr_rx::debug_state()
{
logger->debug("RX entity state: Rx_Next %d, Rx_Next_Status_Trigger %d, Rx_Highest_Status %d, Rx_Next_Highest",
rx_next,
rx_next_status_trigger,
rx_highest_status,
rx_next_highest);
st.rx_next,
st.rx_next_status_trigger,
st.rx_highest_status,
st.rx_next_highest);
}
} // namespace srsran

@ -67,13 +67,14 @@ int test_mac_rach_common_config()
rach_common_config_asn1.to_json(jw);
srslog::fetch_basic_logger("RRC").info("MAC NR RACH Common config: \n %s", jw.to_string().c_str());
rach_nr_cfg_t rach_nr_cfg = make_mac_rach_cfg(rach_common_config_asn1);
TESTASSERT(rach_nr_cfg.ra_responseWindow == 10);
TESTASSERT(rach_nr_cfg.ra_ContentionResolutionTimer == 64);
TESTASSERT(rach_nr_cfg.prach_ConfigurationIndex == 160);
TESTASSERT(rach_nr_cfg.PreambleReceivedTargetPower == -110);
TESTASSERT(rach_nr_cfg.preambleTransMax == 7);
TESTASSERT(rach_nr_cfg.powerRampingStep == 4);
rach_cfg_nr_t rach_cfg_nr = {};
make_mac_rach_cfg(rach_common_config_asn1, &rach_cfg_nr);
TESTASSERT(rach_cfg_nr.ra_responseWindow == 10);
TESTASSERT(rach_cfg_nr.ra_ContentionResolutionTimer == 64);
TESTASSERT(rach_cfg_nr.prach_ConfigurationIndex == 160);
TESTASSERT(rach_cfg_nr.PreambleReceivedTargetPower == -110);
TESTASSERT(rach_cfg_nr.preambleTransMax == 7);
TESTASSERT(rach_cfg_nr.powerRampingStep == 4);
return SRSRAN_SUCCESS;
}
@ -677,6 +678,182 @@ int make_phy_nzp_csi_rs_resource_test()
return SRSRAN_SUCCESS;
}
int fill_phy_pdsch_cfg_common_test()
{
// "pdsch-ConfigCommon":
// "setup":
// "pdsch-TimeDomainAllocationList": [
// "mappingType": "typeA",
// "startSymbolAndLength": 40
// ]
asn1::rrc_nr::pdsch_cfg_common_s pdsch_cfg = {};
pdsch_cfg.pdsch_time_domain_alloc_list_present = true;
pdsch_cfg.pdsch_time_domain_alloc_list.resize(1);
pdsch_cfg.pdsch_time_domain_alloc_list[0].map_type =
asn1::rrc_nr::pdsch_time_domain_res_alloc_s::map_type_opts::options::type_a;
pdsch_cfg.pdsch_time_domain_alloc_list[0].k0_present = false;
pdsch_cfg.pdsch_time_domain_alloc_list[0].start_symbol_and_len = 40;
srsran_sch_hl_cfg_nr_t pdsch;
fill_phy_pdsch_cfg_common(pdsch_cfg, &pdsch);
TESTASSERT(pdsch.nof_common_time_ra == 1);
TESTASSERT(pdsch.common_time_ra[0].k == 0);
TESTASSERT(pdsch.common_time_ra[0].mapping_type == srsran_sch_mapping_type_A);
TESTASSERT(pdsch.common_time_ra[0].sliv == 40);
return SRSRAN_SUCCESS;
}
int fill_phy_pucch_cfg_common_test()
{
// "pucch-ConfigCommon":
// "setup":
// "pucch-ResourceCommon": 11,
// "pucch-GroupHopping": "neither",
// "p0-nominal": -90
asn1::rrc_nr::pucch_cfg_common_s pucch_cfg = {};
pucch_cfg.pucch_res_common_present = true;
pucch_cfg.pucch_res_common = 11;
pucch_cfg.hop_id_present = false;
pucch_cfg.p0_nominal_present = true;
pucch_cfg.p0_nominal = -90;
pucch_cfg.pucch_group_hop = pucch_cfg_common_s::pucch_group_hop_opts::neither;
srsran_pucch_nr_common_cfg_t pucch = {};
fill_phy_pucch_cfg_common(pucch_cfg, &pucch);
TESTASSERT(pucch.resource_common == 11);
TESTASSERT(pucch.p0_nominal == -90);
TESTASSERT(pucch.group_hopping == SRSRAN_PUCCH_NR_GROUP_HOPPING_NEITHER);
return SRSRAN_SUCCESS;
}
int fill_phy_pusch_cfg_common_test()
{
// "pusch-ConfigCommon":
// "setup": {
// "pusch-TimeDomainAllocationList": [
// "k2": 4,
// "mappingType": "typeA",
// "startSymbolAndLength": 27
// ],
// "p0-NominalWithGrant": -76
asn1::rrc_nr::pusch_cfg_common_s pusch_cfg = {};
pusch_cfg.pusch_time_domain_alloc_list_present = true;
pusch_cfg.pusch_time_domain_alloc_list.resize(1);
pusch_cfg.pusch_time_domain_alloc_list[0].map_type =
asn1::rrc_nr::pusch_time_domain_res_alloc_s::map_type_opts::options::type_a;
pusch_cfg.pusch_time_domain_alloc_list[0].k2_present = true;
pusch_cfg.pusch_time_domain_alloc_list[0].k2 = 4;
pusch_cfg.pusch_time_domain_alloc_list[0].start_symbol_and_len = 27;
pusch_cfg.p0_nominal_with_grant_present = true;
pusch_cfg.p0_nominal_with_grant = -76;
srsran_sch_hl_cfg_nr_t pusch;
fill_phy_pusch_cfg_common(pusch_cfg, &pusch);
TESTASSERT(pusch.nof_common_time_ra == 1);
TESTASSERT(pusch.common_time_ra[0].k == 4);
TESTASSERT(pusch.common_time_ra[0].mapping_type == srsran_sch_mapping_type_A);
TESTASSERT(pusch.common_time_ra[0].sliv == 27);
return SRSRAN_SUCCESS;
}
int fill_phy_carrier_cfg_test()
{
// "frequencyInfoDL":
// "frequencyBandList": [
// "freqBandIndicatorNR": 3
// ],
// "offsetToPointA": 13,
// "scs-SpecificCarrierList": [
// "offsetToCarrier": 0,
// "subcarrierSpacing": "kHz15",
// "carrierBandwidth": 52
// ]
//
// ...
//
// "frequencyInfoUL":
// "frequencyBandList": [
// "freqBandIndicatorNR": 3
// ],
// "absoluteFrequencyPointA": 348564,
// "scs-SpecificCarrierList": [
// "offsetToCarrier": 0,
// "subcarrierSpacing": "kHz15",
// "carrierBandwidth": 52
// ],
// "p-Max": 10
asn1::rrc_nr::serving_cell_cfg_common_sib_s serv_cell_cfg = {};
serv_cell_cfg.dl_cfg_common.freq_info_dl.freq_band_list.resize(1);
serv_cell_cfg.dl_cfg_common.freq_info_dl.freq_band_list[0].freq_band_ind_nr_present = true;
serv_cell_cfg.dl_cfg_common.freq_info_dl.freq_band_list[0].freq_band_ind_nr = 3;
serv_cell_cfg.dl_cfg_common.freq_info_dl.offset_to_point_a = 13;
serv_cell_cfg.dl_cfg_common.freq_info_dl.scs_specific_carrier_list.resize(1);
serv_cell_cfg.dl_cfg_common.freq_info_dl.scs_specific_carrier_list[0].offset_to_carrier = 0;
serv_cell_cfg.dl_cfg_common.freq_info_dl.scs_specific_carrier_list[0].subcarrier_spacing =
asn1::rrc_nr::subcarrier_spacing_opts::options::khz15;
serv_cell_cfg.dl_cfg_common.freq_info_dl.scs_specific_carrier_list[0].carrier_bw = 52;
serv_cell_cfg.ul_cfg_common.freq_info_ul.freq_band_list_present = true;
serv_cell_cfg.ul_cfg_common.freq_info_ul.freq_band_list.resize(1);
serv_cell_cfg.ul_cfg_common.freq_info_ul.freq_band_list[0].freq_band_ind_nr_present = true;
serv_cell_cfg.ul_cfg_common.freq_info_ul.freq_band_list[0].freq_band_ind_nr = 3;
serv_cell_cfg.ul_cfg_common.freq_info_ul.absolute_freq_point_a_present = true;
serv_cell_cfg.ul_cfg_common.freq_info_ul.absolute_freq_point_a = 348564;
serv_cell_cfg.ul_cfg_common.freq_info_ul.scs_specific_carrier_list.resize(1);
serv_cell_cfg.ul_cfg_common.freq_info_ul.scs_specific_carrier_list[0].offset_to_carrier = 0;
serv_cell_cfg.ul_cfg_common.freq_info_ul.scs_specific_carrier_list[0].subcarrier_spacing =
asn1::rrc_nr::subcarrier_spacing_opts::options::khz15;
serv_cell_cfg.ul_cfg_common.freq_info_ul.scs_specific_carrier_list[0].carrier_bw = 52;
serv_cell_cfg.ul_cfg_common.freq_info_ul.p_max_present = true;
serv_cell_cfg.ul_cfg_common.freq_info_ul.p_max = 10;
srsran_carrier_nr_t carrier_nr = {};
fill_phy_carrier_cfg(serv_cell_cfg, &carrier_nr);
TESTASSERT(carrier_nr.offset_to_carrier == 0);
TESTASSERT(carrier_nr.scs == srsran_subcarrier_spacing_15kHz);
TESTASSERT(carrier_nr.nof_prb == 52);
TESTASSERT(carrier_nr.ul_center_frequency_hz == 1747.5e6);
return SRSRAN_SUCCESS;
}
int fill_phy_ssb_cfg_test()
{
// "ssb-PositionsInBurst":
// "inOneGroup": "10000000"
// "ssb-PeriodicityServingCell": "ms20",
asn1::rrc_nr::serving_cell_cfg_common_sib_s serv_cell_cfg = {};
serv_cell_cfg.ssb_periodicity_serving_cell =
asn1::rrc_nr::serving_cell_cfg_common_sib_s::ssb_periodicity_serving_cell_opts::options::ms20;
serv_cell_cfg.ssb_positions_in_burst.group_presence_present = false;
serv_cell_cfg.ssb_positions_in_burst.in_one_group.from_number(128);
phy_cfg_nr_t::ssb_cfg_t ssb = {};
fill_phy_ssb_cfg(serv_cell_cfg, &ssb);
TESTASSERT(ssb.periodicity_ms == 20);
uint64_t position_in_burst = 0;
for (uint64_t i = 0; i < 8; i++) {
position_in_burst = position_in_burst << 1 | ssb.position_in_burst[i];
}
TESTASSERT(position_in_burst == 128);
return SRSRAN_SUCCESS;
}
int main()
{
auto& asn1_logger = srslog::fetch_basic_logger("ASN1", false);
@ -705,6 +882,11 @@ int main()
TESTASSERT(make_phy_pusch_scaling_test() == SRSRAN_SUCCESS);
TESTASSERT(make_phy_zp_csi_rs_resource_test() == SRSRAN_SUCCESS);
TESTASSERT(make_phy_nzp_csi_rs_resource_test() == SRSRAN_SUCCESS);
TESTASSERT(fill_phy_pdsch_cfg_common_test() == SRSRAN_SUCCESS);
TESTASSERT(fill_phy_pucch_cfg_common_test() == SRSRAN_SUCCESS);
TESTASSERT(fill_phy_pusch_cfg_common_test() == SRSRAN_SUCCESS);
TESTASSERT(fill_phy_carrier_cfg_test() == SRSRAN_SUCCESS);
TESTASSERT(fill_phy_ssb_cfg_test() == SRSRAN_SUCCESS);
srslog::flush();
printf("Success\n");

@ -3622,6 +3622,150 @@ bool discard_test()
return SRSRAN_SUCCESS;
}
// This test checks wether re-transmissions are triggered correctly in case the t-PollRetranmission expires.
// It checks if the poll retx timer is re-armed upon receiving an ACK for POLL_SN
bool poll_retx_expiry_test()
{
rlc_config_t config = rlc_config_t::default_rlc_am_config();
// [I] SRB1 configured: t_poll_retx=65, poll_pdu=-1, poll_byte=-1, max_retx_thresh=6, t_reordering=55,
// t_status_prohibit=0
config.am.t_poll_retx = 65;
config.am.poll_pdu = -1;
config.am.poll_byte = -1;
config.am.max_retx_thresh = 6;
config.am.t_reordering = 55;
config.am.t_status_prohibit = 55;
#if HAVE_PCAP
rlc_pcap pcap;
pcap.open("rlc_am_poll_rext_expiry_test.pcap", config);
rlc_am_tester tester(&pcap);
#else
rlc_am_tester tester(NULL);
#endif
srsran::timer_handler timers(8);
rlc_am rlc1(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers);
rlc_am rlc2(srsran_rat_t::lte, srslog::fetch_basic_logger("RLC_AM_2"), 1, &tester, &tester, &timers);
srslog::fetch_basic_logger("RLC_AM_1").set_hex_dump_max_size(100);
srslog::fetch_basic_logger("RLC_AM_2").set_hex_dump_max_size(100);
srslog::fetch_basic_logger("RLC").set_hex_dump_max_size(100);
if (not rlc1.configure(config)) {
return -1;
}
if (not rlc2.configure(config)) {
return -1;
}
// [I] SRB1 Tx SDU (135 B, tx_sdu_queue_len=1)
// [I] SRB1 Tx PDU SN=3 (91 B)
// [I] SRB1 Tx PDU SN=4 (48 B)
{
// Initial Tx
uint32_t num_tx_pdus = 1;
for (uint32_t i = 0; i < num_tx_pdus; ++i) {
// Write SDU
unique_byte_buffer_t sdu = srsran::make_byte_buffer();
TESTASSERT(sdu != nullptr);
sdu->N_bytes = 135;
for (uint32_t k = 0; k < sdu->N_bytes; ++k) {
sdu->msg[k] = i; // Write the index into the buffer
}
sdu->md.pdcp_sn = i;
rlc1.write_sdu(std::move(sdu));
}
unique_byte_buffer_t pdu1 = srsran::make_byte_buffer();
TESTASSERT(pdu1 != nullptr);
pdu1->N_bytes = rlc1.read_pdu(pdu1->msg, 91);
unique_byte_buffer_t pdu2 = srsran::make_byte_buffer();
TESTASSERT(pdu2 != nullptr);
pdu2->N_bytes = rlc1.read_pdu(pdu2->msg, 48);
// Deliver PDU2 to RLC2. PDU1 is lost
rlc2.write_pdu(pdu2->msg, pdu2->N_bytes);
}
// Step timers until t-PollRetransmission timer expires on RLC1
// t-Reordering timer also will expire on RLC2, so we can get an status report.
// [I] SRB1 Schedule SN=3 for reTx
for (int cnt = 0; cnt < 65; cnt++) {
timers.step_all();
}
uint32_t status_size = rlc2.get_buffer_state();
srslog::flush();
TESTASSERT(4 == status_size);
// Read status PDU from RLC2
unique_byte_buffer_t status_buf = srsran::make_byte_buffer();
TESTASSERT(status_buf != nullptr);
int len = rlc2.read_pdu(status_buf->msg, status_size);
status_buf->N_bytes = len;
TESTASSERT(0 == rlc2.get_buffer_state());
// Assert status is correct
rlc_status_pdu_t status_check = {};
rlc_am_read_status_pdu(status_buf->msg, status_buf->N_bytes, &status_check);
TESTASSERT(status_check.ack_sn == 2); // 2 is the SN after the largest SN received.
TESTASSERT(status_check.N_nack == 1); // 1 PDU lost
TESTASSERT(rlc_am_is_valid_status_pdu(status_check));
// [I] SRB1 Retx PDU segment SN=3 [so=0] (83 B) (attempt 2/6)
{
unique_byte_buffer_t pdu = srsran::make_byte_buffer();
TESTASSERT(pdu != nullptr);
pdu->N_bytes = rlc1.read_pdu(pdu->msg, 83);
}
// [I] SRB1 Retx PDU segment SN=3 [so=79] (14 B) (attempt 2/6)
{
unique_byte_buffer_t pdu = srsran::make_byte_buffer();
TESTASSERT(pdu != nullptr);
pdu->N_bytes = rlc1.read_pdu(pdu->msg, 79);
}
// Deliver status PDU after ReTX to RLC1. This should restart t-PollRetransmission
TESTASSERT_EQ(false, rlc1.has_data());
rlc1.write_pdu(status_buf->msg, status_buf->N_bytes);
TESTASSERT_EQ(true, rlc1.has_data());
// [I] SRB1 Retx PDU segment SN=3 [so=0] (83 B) (attempt 3/6) (received a NACK and retx...)
// [I] SRB1 Retx PDU segment SN=3 [so=79] (14 B) (attempt 3/6)
{
unique_byte_buffer_t pdu1 = srsran::make_byte_buffer();
TESTASSERT(pdu1 != nullptr);
pdu1->N_bytes = rlc1.read_pdu(pdu1->msg, 83);
unique_byte_buffer_t pdu2 = srsran::make_byte_buffer();
TESTASSERT(pdu2 != nullptr);
pdu2->N_bytes = rlc1.read_pdu(pdu2->msg, 14);
}
TESTASSERT_EQ(false, rlc1.has_data());
// Step timers until t-PollRetransmission timer expires on RLC1
// [I] SRB1 Schedule SN=3 for reTx
for (int cnt = 0; cnt < 66; cnt++) {
timers.step_all();
}
TESTASSERT_EQ(true, rlc1.has_data());
srslog::fetch_basic_logger("TEST").info("t-Poll Retransmssion successfully restarted.");
#if HAVE_PCAP
pcap.close();
#endif
return SRSRAN_SUCCESS;
}
int main(int argc, char** argv)
{
// Setup the log message spy to intercept error and warning log entries from RLC
@ -3827,5 +3971,9 @@ int main(int argc, char** argv)
exit(-1);
};
if (poll_retx_expiry_test()) {
printf("poll_retx_expiry_test failed\n");
exit(-1);
};
return SRSRAN_SUCCESS;
}

@ -60,6 +60,69 @@ int basic_test_tx(rlc_am* rlc, byte_buffer_t pdu_bufs[NBUFS])
return SRSRAN_SUCCESS;
}
/*
* Test the limits of the TX/RX window checkers
*
* This will test
*/
int window_checker_test()
{
rlc_am_tester tester;
timer_handler timers(8);
auto& test_logger = srslog::fetch_basic_logger("TESTER ");
test_delimit_logger delimiter("window checkers");
rlc_am rlc1(srsran_rat_t::nr, srslog::fetch_basic_logger("RLC_AM_1"), 1, &tester, &tester, &timers);
rlc_am_nr_tx* tx = dynamic_cast<rlc_am_nr_tx*>(rlc1.get_tx());
rlc_am_nr_rx* rx = dynamic_cast<rlc_am_nr_rx*>(rlc1.get_rx());
if (not rlc1.configure(rlc_config_t::default_rlc_am_nr_config())) {
return SRSRAN_ERROR;
}
{
// RLC1 RX_NEXT == 0 and RLC2 TX_NEXT_ACK == 0
uint32_t sn_inside_below = 0;
uint32_t sn_inside_above = 2047;
uint32_t sn_outside_below = 4095;
uint32_t sn_outside_above = 2048;
TESTASSERT_EQ(true, rx->inside_rx_window(sn_inside_below));
TESTASSERT_EQ(true, rx->inside_rx_window(sn_inside_above));
TESTASSERT_EQ(false, rx->inside_rx_window(sn_outside_below));
TESTASSERT_EQ(false, rx->inside_rx_window(sn_outside_above));
TESTASSERT_EQ(true, tx->inside_tx_window(sn_inside_below));
TESTASSERT_EQ(true, tx->inside_tx_window(sn_inside_above));
TESTASSERT_EQ(false, tx->inside_tx_window(sn_outside_below));
TESTASSERT_EQ(false, tx->inside_tx_window(sn_outside_above));
}
rlc_am_nr_rx_state_t rx_st = {};
rx_st.rx_next = 4095;
rlc_am_nr_tx_state_t tx_st = {};
tx_st.tx_next_ack = 4095;
rx->set_rx_state(rx_st);
tx->set_tx_state(tx_st);
{
// RX_NEXT == 4095 TX_NEXT_ACK == 4095
uint32_t sn_inside_below = 0;
uint32_t sn_inside_above = 2046;
uint32_t sn_outside_below = 4094;
uint32_t sn_outside_above = 2048;
TESTASSERT_EQ(true, rx->inside_rx_window(sn_inside_below));
TESTASSERT_EQ(true, rx->inside_rx_window(sn_inside_above));
TESTASSERT_EQ(false, rx->inside_rx_window(sn_outside_below));
TESTASSERT_EQ(false, rx->inside_rx_window(sn_outside_above));
TESTASSERT_EQ(true, tx->inside_tx_window(sn_inside_below));
TESTASSERT_EQ(true, tx->inside_tx_window(sn_inside_above));
TESTASSERT_EQ(false, tx->inside_tx_window(sn_outside_below));
TESTASSERT_EQ(false, tx->inside_tx_window(sn_outside_above));
}
return SRSRAN_SUCCESS;
}
/*
* Test the transmission and acknowledgement of 5 SDUs.
*
@ -312,6 +375,7 @@ int main(int argc, char** argv)
// start log backend
srslog::init();
TESTASSERT(window_checker_test() == SRSRAN_SUCCESS);
TESTASSERT(basic_test() == SRSRAN_SUCCESS);
TESTASSERT(lost_pdu_test() == SRSRAN_SUCCESS);

@ -65,8 +65,6 @@ struct phy_args_t {
bool extended_cp = false;
srsran::channel::args_t dl_channel_args;
srsran::channel::args_t ul_channel_args;
srsran::vnf_args_t vnf_args;
};
struct phy_cfg_t {

@ -34,7 +34,10 @@
#include "srsran/common/stack_procedure.h"
#include "srsran/common/task_scheduler.h"
#include "srsran/common/timeout.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interface_mac.h"
#include "srsran/interfaces/enb_rrc_interface_pdcp.h"
#include "srsran/interfaces/enb_rrc_interface_rlc.h"
#include "srsran/interfaces/enb_rrc_interface_s1ap.h"
#include "srsran/interfaces/enb_x2_interfaces.h"
#include "srsran/srslog/srslog.h"
#include <map>
@ -49,14 +52,6 @@ class phy_interface_rrc_lte;
class paging_manager;
static const char rrc_state_text[RRC_STATE_N_ITEMS][100] = {"IDLE",
"WAIT FOR CON SETUP COMPLETE",
"WAIT FOR SECURITY MODE COMPLETE",
"WAIT FOR UE CAPABILITIY INFORMATION",
"WAIT FOR CON RECONF COMPLETE",
"RRC CONNECTED",
"RELEASE REQUEST"};
class rrc final : public rrc_interface_pdcp,
public rrc_interface_mac,
public rrc_interface_rlc,

@ -270,13 +270,6 @@ void parse_args(all_args_t* args, int argc, char* argv[])
("scheduler.nr_pdsch_mcs", bpo::value<int>(&args->nr_stack.mac.sched_cfg.fixed_dl_mcs)->default_value(28), "Fixed NR DL MCS (-1 for dynamic).")
("scheduler.nr_pusch_mcs", bpo::value<int>(&args->nr_stack.mac.sched_cfg.fixed_ul_mcs)->default_value(28), "Fixed NR UL MCS (-1 for dynamic).")
("expert.nr_pusch_max_its", bpo::value<uint32_t>(&args->phy.nr_pusch_max_its)->default_value(10), "Maximum number of LDPC iterations for NR.")
// VNF params
("vnf.type", bpo::value<string>(&args->phy.vnf_args.type)->default_value("gnb"), "VNF instance type [gnb,ue].")
("vnf.addr", bpo::value<string>(&args->phy.vnf_args.bind_addr)->default_value("localhost"), "Address to bind VNF interface.")
("vnf.port", bpo::value<uint16_t>(&args->phy.vnf_args.bind_port)->default_value(3333), "Bind port.")
("log.vnf_level", bpo::value<string>(&args->phy.vnf_args.log_level), "VNF log level.")
("log.vnf_hex_limit", bpo::value<int>(&args->phy.vnf_args.log_hex_limit), "VNF log hex dump limit.")
;
// Positional options - config file location

@ -29,7 +29,7 @@
#include "srsran/common/time_prof.h"
#include "srsran/interfaces/enb_phy_interfaces.h"
#include "srsran/interfaces/enb_rlc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interface_mac.h"
#include "srsran/srslog/event_trace.h"
// #define WRITE_SIB_PCAP
@ -894,9 +894,9 @@ int mac::get_mch_sched(uint32_t tti, bool is_mcch, dl_sched_list_t& dl_sched_res
int requested_bytes = (mcs_data.tbs / 8 > (int)mch.mtch_sched[mtch_index].lcid_buffer_size)
? (mch.mtch_sched[mtch_index].lcid_buffer_size)
: ((mcs_data.tbs / 8) - 2);
int bytes_received = ue_db[SRSRAN_MRNTI]->read_pdu(current_lcid, mtch_payload_buffer, requested_bytes);
mch.pdu[0].lcid = current_lcid;
mch.pdu[0].nbytes = bytes_received;
int bytes_received = ue_db[SRSRAN_MRNTI]->read_pdu(current_lcid, mtch_payload_buffer, requested_bytes);
mch.pdu[0].lcid = current_lcid;
mch.pdu[0].nbytes = bytes_received;
mch.mtch_sched[0].mtch_payload = mtch_payload_buffer;
dl_sched_res->pdsch[0].dci.rnti = SRSRAN_MRNTI;
if (bytes_received) {

@ -25,7 +25,7 @@
#include "srsenb/hdr/stack/mac/schedulers/sched_time_rr.h"
#include "srsran/common/standard_streams.h"
#include "srsran/common/string_helpers.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interface_mac.h"
namespace srsenb {

@ -28,7 +28,7 @@
#include "srsran/common/string_helpers.h"
#include "srsran/interfaces/enb_phy_interfaces.h"
#include "srsran/interfaces/enb_rlc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interface_mac.h"
namespace srsenb {

@ -25,7 +25,7 @@
#include "srsran/common/enb_events.h"
#include "srsran/common/int_helpers.h"
#include "srsran/common/standard_streams.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interface_s1ap.h"
#include <arpa/inet.h> //for inet_ntop()
#include <inttypes.h>

@ -23,7 +23,7 @@
#include "srsenb/hdr/common/common_enb.h"
#include "srsran/interfaces/enb_gtpu_interfaces.h"
#include "srsran/interfaces/enb_rlc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interface_pdcp.h"
namespace srsenb {

@ -23,7 +23,7 @@
#include "srsenb/hdr/common/common_enb.h"
#include "srsran/interfaces/enb_mac_interfaces.h"
#include "srsran/interfaces/enb_pdcp_interfaces.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interface_rlc.h"
namespace srsenb {

@ -27,7 +27,7 @@
#include "srsran/interfaces/enb_mac_interfaces.h"
#include "srsran/interfaces/enb_phy_interfaces.h"
#include "srsran/interfaces/enb_rlc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interface_s1ap.h"
#include "srsran/interfaces/enb_s1ap_interfaces.h"
namespace srsenb {

@ -25,7 +25,7 @@
#include "sched_sim_ue.h"
#include "sched_test_utils.h"
#include "srsenb/hdr/stack/mac/sched.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interface_mac.h"
#include "srsran/srslog/srslog.h"
#include <random>

@ -50,10 +50,10 @@ public:
private:
struct pending_rar_t {
uint16_t ra_rnti = 0;
slot_point prach_slot;
slot_interval rar_win;
srsran::bounded_vector<dl_sched_rar_info_t, sched_interface::MAX_RAR_LIST> msg3_grant;
uint16_t ra_rnti = 0;
slot_point prach_slot;
slot_interval rar_win;
srsran::bounded_vector<dl_sched_rar_info_t, MAX_GRANTS> msg3_grant;
};
alloc_result

@ -29,10 +29,7 @@
namespace srsenb {
const static size_t SCHED_NR_MAX_USERS = SRSENB_MAX_UES;
const static size_t SCHED_NR_NOF_SUBFRAMES = 10;
const static size_t SCHED_NR_NOF_HARQS = 16;
static const size_t MAX_NOF_AGGR_LEVELS = 5;
static const size_t MAX_NOF_AGGR_LEVELS = 5;
namespace sched_nr_impl {
@ -45,6 +42,7 @@ using pusch_t = mac_interface_phy_nr::pusch_t;
using pucch_t = mac_interface_phy_nr::pucch_t;
using pdcch_dl_list_t = srsran::bounded_vector<pdcch_dl_t, MAX_GRANTS>;
using pdcch_ul_list_t = srsran::bounded_vector<pdcch_ul_t, MAX_GRANTS>;
using pdsch_list_t = srsran::bounded_vector<pdsch_t, MAX_GRANTS>;
using pucch_list_t = srsran::bounded_vector<pucch_t, MAX_GRANTS>;
using pusch_list_t = srsran::bounded_vector<pusch_t, MAX_GRANTS>;
using nzp_csi_rs_list = srsran::bounded_vector<srsran_csi_rs_nzp_resource_t, mac_interface_phy_nr::MAX_NZP_CSI_RS>;
@ -105,19 +103,23 @@ struct bwp_params_t {
bwp_params_t(const cell_cfg_t& cell, const sched_args_t& sched_cfg_, uint32_t cc, uint32_t bwp_id);
const prb_bitmap& used_prbs(uint32_t ss_id, srsran_dci_format_nr_t dci_fmt) const
prb_interval coreset_prb_range(uint32_t cs_id) const { return coresets[cs_id].prb_limits; }
prb_interval dci_fmt_1_0_prb_lims(uint32_t cs_id) const { return coresets[cs_id].dci_1_0_prb_limits; }
bwp_rb_bitmap dci_fmt_1_0_excluded_prbs(uint32_t cs_id) const { return coresets[cs_id].usable_common_ss_prb_mask; }
const srsran_search_space_t* get_ss(uint32_t ss_id) const
{
if (used_common_prb_masks.contains(ss_id)) {
if (dci_fmt == srsran_dci_format_nr_1_0) {
return used_common_prb_masks[ss_id];
}
}
return cached_empty_prb_mask;
return cfg.pdcch.search_space_present[ss_id] ? &cfg.pdcch.search_space[ss_id] : nullptr;
}
private:
prb_bitmap cached_empty_prb_mask;
srsran::optional_vector<prb_bitmap> used_common_prb_masks;
bwp_rb_bitmap cached_empty_prb_mask;
struct coreset_cached_params {
prb_interval prb_limits;
prb_interval dci_1_0_prb_limits; /// See TS 38.214, section 5.1.2.2
bwp_rb_bitmap usable_common_ss_prb_mask;
};
srsran::optional_vector<coreset_cached_params> coresets;
};
/// Structure packing a single cell config params, and sched args
@ -157,6 +159,16 @@ public:
const srsran::phy_cfg_nr_t& phy() const { return cfg_->phy_cfg; }
const bwp_params_t& active_bwp() const { return *bwp_cfg; }
/// Get SearchSpace based on SearchSpaceId
const srsran_search_space_t* get_ss(uint32_t ss_id) const
{
if (phy().pdcch.search_space_present[ss_id]) {
// UE-dedicated SearchSpace
return &bwp_cfg->cfg.pdcch.search_space[ss_id];
}
return nullptr;
}
srsran::const_span<uint32_t> cce_pos_list(uint32_t search_id, uint32_t slot_idx, uint32_t aggr_idx) const
{
if (cce_positions_list.size() > ss_id_to_cce_idx[search_id]) {
@ -180,12 +192,18 @@ public:
int fixed_pdsch_mcs() const { return bwp_cfg->sched_cfg.fixed_dl_mcs; }
int fixed_pusch_mcs() const { return bwp_cfg->sched_cfg.fixed_ul_mcs; }
const srsran_dci_cfg_nr_t& get_dci_cfg() const { return cached_dci_cfg; }
int find_ss_id(srsran_dci_format_nr_t dci_fmt) const;
private:
const ue_cfg_t* cfg_ = nullptr;
const bwp_params_t* bwp_cfg = nullptr;
// derived
std::vector<bwp_cce_pos_list> cce_positions_list;
std::array<uint32_t, SRSRAN_UE_DL_NR_MAX_NOF_SEARCH_SPACE> ss_id_to_cce_idx;
srsran_dci_cfg_nr_t cached_dci_cfg;
};
} // namespace sched_nr_impl

@ -26,6 +26,7 @@
#include "sched_nr_helpers.h"
#include "sched_nr_interface.h"
#include "sched_nr_pdcch.h"
#include "sched_nr_sch.h"
#include "sched_nr_ue.h"
#include "srsenb/hdr/stack/mac/sched_common.h"
@ -37,9 +38,6 @@ using dl_sched_rar_info_t = sched_nr_interface::rar_info_t;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const static size_t MAX_CORESET_PER_BWP = 3;
using slot_coreset_list = std::array<srsran::optional<coreset_region>, MAX_CORESET_PER_BWP>;
using pdsch_list_t = srsran::bounded_vector<pdsch_t, MAX_GRANTS>;
using sched_rar_list_t = sched_nr_interface::sched_rar_list_t;
using pucch_list_t = srsran::bounded_vector<pucch_t, MAX_GRANTS>;
@ -56,26 +54,20 @@ struct bwp_slot_grid {
uint32_t slot_idx = 0;
const bwp_params_t* cfg = nullptr;
bwp_rb_bitmap dl_prbs;
bwp_rb_bitmap ul_prbs;
dl_sched_res_t dl;
ul_sched_t ul;
slot_coreset_list coresets;
harq_ack_list_t pending_acks;
dl_sched_res_t dl;
ul_sched_t ul;
harq_ack_list_t pending_acks;
bwp_pdcch_allocator pdcchs; /// slot PDCCH resource allocator
pdsch_allocator pdschs; /// slot PDSCH resource allocator
pusch_allocator puschs; /// slot PUSCH resource allocator
srsran::unique_pool_ptr<tx_harq_softbuffer> rar_softbuffer;
bwp_slot_grid() = default;
explicit bwp_slot_grid(const bwp_params_t& bwp_params, uint32_t slot_idx_);
void reset();
bool is_dl() const { return cfg->slots[slot_idx].is_dl; }
bool is_ul() const { return cfg->slots[slot_idx].is_ul; }
prb_bitmap used_prbs(uint32_t ss_id, srsran_dci_format_nr_t dci_fmt) const
{
return dl_prbs.prbs() | cfg->used_prbs(ss_id, dci_fmt);
}
};
struct bwp_res_grid {
@ -113,8 +105,8 @@ public:
uint32_t aggr_idx,
prb_interval interv,
srsran::const_span<dl_sched_rar_info_t> pending_rars);
alloc_result alloc_pdsch(slot_ue& ue, prb_grant dl_grant);
alloc_result alloc_pusch(slot_ue& ue, prb_grant dl_mask);
alloc_result alloc_pdsch(slot_ue& ue, uint32_t ss_id, const prb_grant& dl_grant);
alloc_result alloc_pusch(slot_ue& ue, const prb_grant& grant);
slot_point get_pdcch_tti() const { return pdcch_slot; }
slot_point get_tti_rx() const { return pdcch_slot - TX_ENB_DELAY; }
@ -122,14 +114,17 @@ public:
const bwp_slot_grid& tx_slot_grid() const { return bwp_grid[pdcch_slot]; }
bwp_slot_grid& tx_slot_grid() { return bwp_grid[pdcch_slot]; }
prb_bitmap occupied_dl_prbs(slot_point sl_tx, uint32_t ss_id, srsran_dci_format_nr_t dci_fmt) const
{
return bwp_grid[sl_tx].pdschs.occupied_prbs(ss_id, dci_fmt);
}
const prb_bitmap& occupied_ul_prbs(slot_point sl_tx) const { return bwp_grid[sl_tx].puschs.occupied_prbs(); }
srslog::basic_logger& logger;
const bwp_params_t& cfg;
private:
alloc_result
verify_pdsch_space(bwp_slot_grid& pdsch_grid, bwp_slot_grid& pdcch_grid, bwp_slot_grid* uci_grid = nullptr) const;
alloc_result verify_pusch_space(bwp_slot_grid& pusch_grid, bwp_slot_grid* pdcch_grid = nullptr) const;
alloc_result verify_ue_cfg(const ue_carrier_params_t& ue_cfg, harq_proc* harq) const;
alloc_result verify_uci_space(const bwp_slot_grid& uci_grid) const;
bwp_res_grid& bwp_grid;
@ -137,6 +132,8 @@ private:
slot_ue_map_t& slot_ues;
};
prb_grant find_optimal_dl_grant(bwp_slot_allocator& slot_alloc, const slot_ue& ue, uint32_t ss_id);
} // namespace sched_nr_impl
} // namespace srsenb

@ -57,8 +57,6 @@ public:
bool clear_if_maxretx(slot_point slot_rx);
void reset();
bool new_tx(slot_point slot_tx, slot_point slot_ack, const prb_grant& grant, uint32_t mcs, uint32_t max_retx);
bool new_retx(slot_point slot_tx, slot_point slot_ack, const prb_grant& grant);
bool new_retx(slot_point slot_tx, slot_point slot_ack);
// NOTE: Has to be used before first tx is dispatched
@ -67,7 +65,10 @@ public:
const uint32_t pid;
private:
protected:
bool new_tx(slot_point slot_tx, slot_point slot_ack, const prb_grant& grant, uint32_t mcs, uint32_t max_retx);
bool new_retx(slot_point slot_tx, slot_point slot_ack, const prb_grant& grant);
struct tb_t {
bool active = false;
bool ack_state = false;
@ -92,9 +93,18 @@ public:
tx_harq_softbuffer& get_softbuffer() { return *softbuffer; }
srsran::unique_byte_buffer_t* get_tx_pdu() { return &pdu; }
bool new_tx(slot_point slot_tx, slot_point slot_ack, const prb_grant& grant, uint32_t mcs, uint32_t max_retx);
bool new_tx(slot_point slot_tx,
slot_point slot_ack,
const prb_grant& grant,
uint32_t mcs,
uint32_t max_retx,
srsran_dci_dl_nr_t& dci);
bool new_retx(slot_point slot_tx, slot_point slot_ack, const prb_grant& grant, srsran_dci_dl_nr_t& dci);
private:
void fill_dci(srsran_dci_dl_nr_t& dci);
srsran::unique_pool_ptr<tx_harq_softbuffer> softbuffer;
srsran::unique_byte_buffer_t pdu;
};
@ -106,6 +116,10 @@ public:
harq_proc(id_), softbuffer(harq_softbuffer_pool::get_instance().get_rx(nprb))
{}
bool new_tx(slot_point slot_tx, const prb_grant& grant, uint32_t mcs, uint32_t max_retx, srsran_dci_ul_nr_t& dci);
bool new_retx(slot_point slot_tx, const prb_grant& grant, srsran_dci_ul_nr_t& dci);
rx_harq_softbuffer& get_softbuffer() { return *softbuffer; }
bool set_tbs(uint32_t tbs)
@ -115,6 +129,8 @@ public:
}
private:
void fill_dci(srsran_dci_ul_nr_t& dci);
srsran::unique_pool_ptr<rx_harq_softbuffer> softbuffer;
};

@ -33,35 +33,29 @@ class slot_ue;
class ul_harq_proc;
struct bwp_res_grid;
/// In case of Common SearchSpace, not all PRBs might be available
void reduce_to_dl_coreset_bw(const bwp_params_t& bwp_cfg,
uint32_t ss_id,
srsran_dci_format_nr_t dci_fmt,
prb_grant& grant);
bool fill_dci_sib(prb_interval interv,
uint32_t sib_idx,
uint32_t si_ntx,
const bwp_params_t& bwp_cfg,
srsran_dci_dl_nr_t& dci);
bool fill_dci_rar(prb_interval interv, uint16_t ra_rnti, const bwp_params_t& bwp_cfg, srsran_dci_dl_nr_t& dci);
bool fill_dci_msg3(const slot_ue& ue, const bwp_params_t& bwp_cfg, srsran_dci_ul_nr_t& dci);
/// Generate PDCCH DL DCI fields
void fill_dl_dci_ue_fields(const slot_ue& ue,
const bwp_params_t& bwp_cfg,
uint32_t ss_id,
srsran_dci_location_t dci_pos,
srsran_dci_dl_nr_t& dci);
/// Generate PDCCH UL DCI fields
void fill_ul_dci_ue_fields(const slot_ue& ue,
const bwp_params_t& bwp_cfg,
uint32_t ss_id,
srsran_dci_location_t dci_pos,
srsran_dci_ul_nr_t& dci);
/// Helper function to verify if RNTI type can be placed in specified search space
/// Based on 38.213, Section 10.1
inline bool is_rnti_type_valid_in_search_space(srsran_rnti_type_t rnti_type, srsran_search_space_type_t ss_type)
{
switch (ss_type) {
case srsran_search_space_type_common_0: // fall-through
case srsran_search_space_type_common_0A: // Other SIBs
return rnti_type == srsran_rnti_type_si;
case srsran_search_space_type_common_1:
return rnti_type == srsran_rnti_type_ra or rnti_type == srsran_rnti_type_tc or
/* in case of Pcell -> */ rnti_type == srsran_rnti_type_c;
case srsran_search_space_type_common_2:
return rnti_type == srsran_rnti_type_p;
case srsran_search_space_type_common_3:
return rnti_type == srsran_rnti_type_c; // TODO: Fix
case srsran_search_space_type_ue:
return rnti_type == srsran_rnti_type_c or rnti_type == srsran_rnti_type_cs or
rnti_type == srsran_rnti_type_sp_csi;
default:
break;
}
return false;
}
/// Log UE state for slot being scheduled
void log_sched_slot_ues(srslog::basic_logger& logger,

@ -22,6 +22,7 @@
#ifndef SRSRAN_SCHED_NR_INTERFACE_H
#define SRSRAN_SCHED_NR_INTERFACE_H
#include "srsenb/hdr/stack/mac/common/sched_config.h"
#include "srsran/adt/bounded_bitset.h"
#include "srsran/adt/bounded_vector.h"
#include "srsran/adt/optional.h"

@ -27,7 +27,7 @@
namespace srsenb {
// Helpers to handle PHY struct types
//////////////////////////////////// Search Space Helpers ////////////////////////////////////////////
/// Get a range of active search spaces in a PDCCH configuration
inline srsran::split_optional_span<srsran_search_space_t> view_active_search_spaces(srsran_pdcch_cfg_nr_t& pdcch)
@ -40,6 +40,36 @@ view_active_search_spaces(const srsran_pdcch_cfg_nr_t& pdcch)
return srsran::split_optional_span<const srsran_search_space_t>{pdcch.search_space, pdcch.search_space_present};
}
inline bool contains_dci_format(const srsran_search_space_t& ss, srsran_dci_format_nr_t dci_fmt)
{
auto is_dci_fmt = [dci_fmt](const srsran_dci_format_nr_t& f) { return f == dci_fmt; };
return std::any_of(&ss.formats[0], &ss.formats[ss.nof_formats], is_dci_fmt);
}
//////////////////////////////////// CORESET Helpers ////////////////////////////////////////////
/// Get a range of active coresets in a PDCCH configuration
inline srsran::split_optional_span<srsran_coreset_t> view_active_coresets(srsran_pdcch_cfg_nr_t& pdcch)
{
return srsran::split_optional_span<srsran_coreset_t>{pdcch.coreset, pdcch.coreset_present};
}
inline srsran::split_optional_span<const srsran_coreset_t> view_active_coresets(const srsran_pdcch_cfg_nr_t& pdcch)
{
return srsran::split_optional_span<const srsran_coreset_t>{pdcch.coreset, pdcch.coreset_present};
}
/// Get number of CCEs available in CORESET for PDCCH
uint32_t coreset_nof_cces(const srsran_coreset_t& coreset);
//////////////////////////////////// Sched Output Helpers ////////////////////////////////////////////
inline bool operator==(srsran_dci_location_t lhs, srsran_dci_location_t rhs)
{
return lhs.ncce == rhs.ncce and lhs.L == rhs.L;
}
//////////////////////////////////// UE configuration Helpers ////////////////////////////////////////////
srsran::phy_cfg_nr_t get_common_ue_phy_cfg(const sched_nr_interface::cell_cfg_t& cfg);
} // namespace srsenb

@ -22,6 +22,7 @@
#ifndef SRSRAN_SCHED_NR_PDCCH_H
#define SRSRAN_SCHED_NR_PDCCH_H
#include "srsenb/hdr/stack/mac/sched_common.h"
#include "srsgnb/hdr/stack/mac/sched_nr_cfg.h"
#include "srsran/adt/bounded_bitset.h"
#include "srsran/adt/bounded_vector.h"
@ -32,41 +33,34 @@ namespace srsenb {
namespace sched_nr_impl {
using coreset_bitmap = srsran::bounded_bitset<SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE * SRSRAN_CORESET_DURATION_MAX, true>;
enum class pdcch_grant_type_t { sib, rar, dl_data, ul_data };
/// Helper function to fill DCI with BWP params
void fill_dci_from_cfg(const bwp_params_t& bwp_cfg, srsran_dci_dl_nr_t& dci);
void fill_dci_from_cfg(const bwp_params_t& bwp_cfg, srsran_dci_ul_nr_t& dci);
class slot_ue;
using coreset_bitmap = srsran::bounded_bitset<SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE * SRSRAN_CORESET_DURATION_MAX, true>;
class coreset_region
{
public:
coreset_region(const bwp_params_t& bwp_cfg_,
uint32_t coreset_id_,
uint32_t slot_idx,
pdcch_dl_list_t& pdcch_dl_list,
pdcch_ul_list_t& pdcch_ul_list);
coreset_region(const bwp_params_t& bwp_cfg_, uint32_t coreset_id_, uint32_t slot_idx);
void reset();
/**
* Allocates DCI space in PDCCH, avoiding in the process collisions with other users
* @param pdcch_grant_type_t allocation type (e.g. DL data, UL data, SIB)
* @param aggr_idx Aggregation level index (0..4)
* @param user UE object or null in case of broadcast/RAR/paging allocation
* @return if the allocation was successful
*/
bool alloc_dci(pdcch_grant_type_t alloc_type,
uint32_t aggr_idx,
uint32_t search_space_id,
const ue_carrier_params_t* user = nullptr);
bool alloc_pdcch(srsran_rnti_type_t rnti_type,
bool is_dl,
uint32_t aggr_idx,
uint32_t search_space_id,
const ue_carrier_params_t* user,
srsran_dci_ctx_t& dci);
void rem_last_dci();
void rem_last_pdcch();
uint32_t get_td_symbols() const { return coreset_cfg->duration; }
uint32_t get_freq_resources() const { return nof_freq_res; }
uint32_t nof_cces() const { return nof_freq_res * get_td_symbols(); }
size_t nof_allocs() const { return dfs_tree.size(); }
void print_allocations(fmt::memory_buffer& fmtbuf) const;
private:
const srsran_coreset_t* coreset_cfg;
uint32_t coreset_id;
@ -80,13 +74,11 @@ private:
struct alloc_record {
uint32_t aggr_idx;
uint32_t ss_id;
uint32_t idx;
pdcch_grant_type_t alloc_type;
srsran_dci_ctx_t* dci;
bool is_dl;
const ue_carrier_params_t* ue;
};
srsran::bounded_vector<alloc_record, 2 * MAX_GRANTS> dci_list;
pdcch_dl_list_t& pdcch_dl_list;
pdcch_ul_list_t& pdcch_ul_list;
// DFS decision tree of PDCCH grants
struct tree_node {
@ -105,6 +97,117 @@ private:
bool get_next_dfs();
};
using pdcch_dl_alloc_result = srsran::expected<pdcch_dl_t*, alloc_result>;
using pdcch_ul_alloc_result = srsran::expected<pdcch_ul_t*, alloc_result>;
/**
* Class to handle the allocation of REs for a BWP PDCCH in a specific slot
*/
class bwp_pdcch_allocator
{
public:
bwp_pdcch_allocator(const bwp_params_t& bwp_cfg_,
uint32_t slot_idx,
pdcch_dl_list_t& pdcch_dl_list,
pdcch_ul_list_t& pdcch_ul_list);
/**
* Clear current slot allocations
*/
void reset();
/**
* Allocates RE space for RAR DCI in PDCCH, avoiding in the process collisions with other PDCCH allocations
* Fills DCI context with RAR PDCCH allocation information
* @param ra_rnti RA-RNTI of RAR allocation
* @param aggr_idx Aggregation level index (0..4)
* @return PDCCH object with dci context filled if the allocation was successful. nullptr otherwise
*/
pdcch_dl_alloc_result alloc_rar_pdcch(uint16_t ra_rnti, uint32_t aggr_idx);
/**
* Allocates RE space for SI DCI in PDCCH, avoiding in the process collisions with other PDCCH allocations
* Fills DCI context with SI PDCCH allocation information
* @param ss_id Search space ID
* @param aggr_idx Aggregation level index (0..4)
* @return PDCCH object with dci context filled if the allocation was successful. nullptr otherwise
*/
pdcch_dl_alloc_result alloc_si_pdcch(uint32_t ss_id, uint32_t aggr_idx);
/**
* Allocates RE space for UE DL DCI in PDCCH, avoiding in the process collisions with other PDCCH allocations
* Fills DCI context with PDCCH allocation information
* @param rnti_type type of UE RNTI (e.g. C, TC)
* @param ss_id Search space ID
* @param aggr_idx Aggregation level index (0..4)
* @param user UE object parameters
* @return PDCCH object with dci context filled if the allocation was successful. nullptr otherwise
*/
pdcch_dl_alloc_result
alloc_dl_pdcch(srsran_rnti_type_t rnti_type, uint32_t ss_id, uint32_t aggr_idx, const ue_carrier_params_t& user);
/**
* @brief Allocates RE space for UL DCI in PDCCH, avoiding in the process collisions with other PDCCH allocations
* Fills DCI context with PDCCH allocation information
* @param ss_id Search space ID
* @param aggr_idx Aggregation level index (0..4)
* @param user UE object parameters
* @return PDCCH object with dci context filled if the allocation was successful. nullptr otherwise
*/
pdcch_ul_alloc_result alloc_ul_pdcch(uint32_t ss_id, uint32_t aggr_idx, const ue_carrier_params_t& user);
/**
* Cancel and remove last PDCCH allocation. It should only be called once after each alloc_dl_pdcch/alloc_ul_pdcch
*/
void cancel_last_pdcch();
/// Returns the number of PDCCH allocations made in the slot
uint32_t nof_allocations() const;
/// Number of CCEs in given coreset
uint32_t nof_cces(uint32_t coreset_id) const;
void print_allocations(fmt::memory_buffer& fmtbuf) const;
std::string print_allocations() const;
private:
using slot_coreset_list = srsran::optional_array<coreset_region, SRSRAN_UE_DL_NR_MAX_NOF_CORESET>;
pdcch_dl_alloc_result alloc_dl_pdcch_common(srsran_rnti_type_t rnti_type,
uint16_t rnti,
uint32_t ss_id,
uint32_t aggr_idx,
srsran_dci_format_nr_t dci_fmt,
const ue_carrier_params_t* user = nullptr);
/// Helper function to verify valid inputs
alloc_result check_args_valid(srsran_rnti_type_t rnti_type,
uint16_t rnti,
uint32_t ss_id,
uint32_t aggr_idx,
srsran_dci_format_nr_t dci_fmt,
const ue_carrier_params_t* user,
bool is_dl) const;
/// Fill DCI context of allocated PDCCH
void fill_dci_ctx_common(srsran_dci_ctx_t& dci,
srsran_rnti_type_t rnti_type,
uint16_t rnti,
const srsran_search_space_t& ss,
srsran_dci_format_nr_t dci_fmt,
const ue_carrier_params_t* ue);
// args
const bwp_params_t& bwp_cfg;
srslog::basic_logger& logger;
const uint32_t slot_idx;
pdcch_dl_list_t& pdcch_dl_list;
pdcch_ul_list_t& pdcch_ul_list;
slot_coreset_list coresets;
const srsran_dci_ctx_t* pending_dci = nullptr; /// Saves last PDCCH allocation, in case it needs to be aborted
};
} // namespace sched_nr_impl
} // namespace srsenb

@ -207,6 +207,23 @@ public:
uint32_t prb_to_rbg_idx(uint32_t prb_idx) const;
bwp_rb_bitmap& operator|=(const bwp_rb_bitmap& other)
{
prbs_ |= other.prbs_;
rbgs_ |= other.rbgs_;
return *this;
}
bwp_rb_bitmap& operator|=(const rbg_bitmap& other)
{
add(other);
return *this;
}
bwp_rb_bitmap& operator|=(const prb_bitmap& other)
{
add(other);
return *this;
}
private:
prb_bitmap prbs_;
rbg_bitmap rbgs_;
@ -219,6 +236,12 @@ private:
void add_rbgs_to_prbs(const rbg_bitmap& grant);
};
template <typename Other>
bwp_rb_bitmap operator|(const bwp_rb_bitmap& lhs, const Other& rhs)
{
return bwp_rb_bitmap(lhs) |= rhs;
}
inline prb_interval
find_next_empty_interval(const prb_bitmap& mask, size_t start_prb_idx = 0, size_t last_prb_idx = SRSRAN_MAX_PRB_NR)
{

@ -0,0 +1,180 @@
/**
*
* \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_NR_SCH_H
#define SRSRAN_SCHED_NR_SCH_H
#include "srsenb/hdr/stack/mac/sched_common.h"
#include "srsgnb/hdr/stack/mac/sched_nr_cfg.h"
namespace srsenb {
namespace sched_nr_impl {
using pdsch_alloc_result = srsran::expected<pdsch_t*, alloc_result>;
class pdsch_allocator
{
public:
pdsch_allocator(const bwp_params_t& cfg_, uint32_t sl_index, pdsch_list_t& pdsch_lst);
/// Get available RBGs for allocation
rbg_bitmap occupied_rbgs() const
{
// Note: in case, RBGs are used, dci format is not 1_0
return dl_prbs.rbgs();
}
/// Get available PRBs for allocation
prb_bitmap occupied_prbs(uint32_t ss_id, srsran_dci_format_nr_t dci_fmt) const
{
if (dci_fmt == srsran_dci_format_nr_1_0) {
const srsran_search_space_t* ss = bwp_cfg.get_ss(ss_id);
if (ss != nullptr and SRSRAN_SEARCH_SPACE_IS_COMMON(ss->type)) {
return (dl_prbs | bwp_cfg.dci_fmt_1_0_excluded_prbs(ss->coreset_id)).prbs();
}
}
return dl_prbs.prbs();
}
/// Verifies if the input arguments are valid for an SI allocation and grant doesnt collide with other grants
alloc_result is_si_grant_valid(uint32_t ss_id, const prb_grant& grant) const;
/// Verifies if the input arguments are valid for an RAR allocation and grant doesnt collide with other grants
alloc_result is_rar_grant_valid(const prb_grant& grant) const;
/// Verifies if the input arguments are valid for an UE allocation and grant doesnt collide with other grants
alloc_result is_ue_grant_valid(const ue_carrier_params_t& ue,
uint32_t ss_id,
srsran_dci_format_nr_t dci_fmt,
const prb_grant& grant) const;
/**
* @brief Tries to allocate UE PDSCH grant. Ensures that there are no collisions with other previous PDSCH allocations
* @param ss_id[in] SearchSpaceId used for allocation
* @param dci_fmt[in] Chosen DL DCI format
* @param grant[in] PRBs used for the grant
* @param ue[in] UE carrier parameters
* @param dci[out] DCI where frequency_assignment and time_assignment get stored.
* @return pdsch_t* of allocated PDSCH in case of success. alloc_result error code in case of failure
*/
pdsch_alloc_result alloc_ue_pdsch(uint32_t ss_id,
srsran_dci_format_nr_t dci_fmt,
const prb_grant& grant,
const ue_carrier_params_t& ue,
srsran_dci_dl_nr_t& dci);
/// Similar to alloc_ue_pdsch, but it doesn't verify if input parameters are valid
pdsch_t& alloc_ue_pdsch_unchecked(uint32_t ss_id,
srsran_dci_format_nr_t dci_fmt,
const prb_grant& grant,
const ue_carrier_params_t& ue,
srsran_dci_dl_nr_t& dci);
/**
* @brief Tries to allocate SI PDSCH grant. Ensures that there are no collisions with other previous PDSCH allocations
* @param ss_id[in] SearchSpaceId used for allocation
* @param grant[in] PRBs used for the grant
* @param dci[out] DCI where frequency_assignment and time_assignment get stored.
* @return pdsch_t* of allocated PDSCH in case of success. alloc_result error code in case of failure
*/
pdsch_alloc_result alloc_si_pdsch(uint32_t ss_id, const prb_grant& grant, srsran_dci_dl_nr_t& dci);
/// Similar to alloc_si_pdsch, but it doesn't verify if input parameters are valid
pdsch_t& alloc_si_pdsch_unchecked(uint32_t ss_id, const prb_grant& grant, srsran_dci_dl_nr_t& dci);
/**
* @brief Tries to allocate RAR PDSCH grant. Ensures that there are no collisions with other previous PDSCH
* allocations
* @param grant[in] PRBs used for the grant
* @param dci[out] DCI where frequency_assignment and time_assignment get stored.
* @return pdsch_t* of allocated PDSCH in case of success. alloc_result error code in case of failure
*/
pdsch_alloc_result alloc_rar_pdsch(const prb_grant& grant, srsran_dci_dl_nr_t& dci);
/// Similar to alloc_rar_pdsch, but it doesn't verify if input parameters are valid
pdsch_t& alloc_rar_pdsch_unchecked(const prb_grant& grant, srsran_dci_dl_nr_t& dci);
/// Cancel last PDSCH allocation
void cancel_last_pdsch();
/// Clear all PDSCHs
void reset();
private:
alloc_result is_grant_valid_common(srsran_search_space_type_t ss_type,
srsran_dci_format_nr_t dci_fmt,
uint32_t coreset_id,
const prb_grant& grant) const;
pdsch_t& alloc_pdsch_unchecked(uint32_t coreset_id,
srsran_search_space_type_t ss_type,
srsran_dci_format_nr_t dci_fmt,
const prb_grant& grant,
srsran_dci_dl_nr_t& dci);
const bwp_params_t& bwp_cfg;
uint32_t slot_idx = 0;
pdsch_list_t& pdschs;
bwp_rb_bitmap dl_prbs;
};
using pusch_alloc_result = srsran::expected<pusch_t*, alloc_result>;
class pusch_allocator
{
public:
pusch_allocator(const bwp_params_t& cfg_, uint32_t sl_index, pusch_list_t& pusch_lst);
/// Get available RBGs for allocation
const rbg_bitmap& occupied_rbgs() const { return ul_prbs.rbgs(); }
/// Get available PRBs for allocation
const prb_bitmap& occupied_prbs() const { return ul_prbs.prbs(); }
alloc_result has_grant_space(uint32_t nof_grants = 1, bool verbose = true) const;
/// Checks if provided PDSCH arguments produce a valid PDSCH that fits into cell PRBs and does not collide with other
/// allocations
alloc_result is_grant_valid(srsran_search_space_type_t ss_type, const prb_grant& grant, bool verbose = true) const;
/**
* @brief Tries to allocate PDSCH grant. Ensures that there are no collisions with other previous PDSCH allocations
* @param ss_type[in] PDCCH chosen search space type
* @param grant[in] PRBs used for the grant
* @param pdcch[out] DCI where frequency_assignment and time_assignment get stored.
* @return pdsch_t object pointer in case of success. alloc_result error code in case of failure
*/
pusch_alloc_result
alloc_pusch(const srsran_search_space_type_t ss_type, const prb_grant& grant, srsran_dci_ul_nr_t& dci);
/**
* @brief Allocates PDSCH grant without verifying for collisions. Useful to avoid redundant is_grant_valid(...) calls
* @param dci_ctx[in] PDCCH DL DCI context information
* @param grant[in] PRBs used for the grant
* @param pdcch[out] DCI where frequency and time assignment get stored.
*/
pusch_t& alloc_pusch_unchecked(const prb_grant& grant, srsran_dci_ul_nr_t& dci);
void cancel_last_pusch();
void reset();
private:
const bwp_params_t& bwp_cfg;
uint32_t slot_idx = 0;
pusch_list_t& puschs;
bwp_rb_bitmap ul_prbs;
};
} // namespace sched_nr_impl
} // namespace srsenb
#endif // SRSRAN_SCHED_NR_SCH_H

@ -74,10 +74,12 @@ public:
uint32_t cc = SRSRAN_MAX_CARRIERS;
ue_buffer_manager* parent = nullptr;
};
};
private:
/// Update of buffers is mutexed when carrier aggreg. is in place
std::mutex mutex;
/// Class containing context of UE that is common to all carriers
struct ue_context_common {
uint32_t pending_dl_bytes = 0;
uint32_t pending_ul_bytes = 0;
};
class slot_ue;
@ -88,6 +90,7 @@ public:
ue_carrier(uint16_t rnti,
const ue_cfg_t& cfg,
const cell_params_t& cell_params_,
const ue_context_common& ctxt,
const ue_buffer_manager::pdu_builder& pdu_builder_);
void set_cfg(const ue_cfg_t& ue_cfg);
@ -111,6 +114,9 @@ public:
// metrics
mac_ue_metrics_t metrics = {};
// common context
const ue_context_common& common_ctxt;
private:
friend class slot_ue;
@ -136,7 +142,7 @@ public:
/// UE state feedback
void ul_bsr(uint32_t lcg, uint32_t bsr_val) { buffers.ul_bsr(lcg, bsr_val); }
void ul_sr_info() { last_sr_slot = last_pdcch_slot - TX_ENB_DELAY; }
void ul_sr_info() { last_sr_slot = last_tx_slot - TX_ENB_DELAY; }
bool has_ca() const
{
@ -155,9 +161,9 @@ private:
ue_cfg_t ue_cfg;
slot_point last_pdcch_slot;
slot_point last_sr_slot;
int ul_pending_bytes = 0, dl_pending_bytes = 0;
slot_point last_tx_slot;
slot_point last_sr_slot;
ue_context_common common_ctxt;
ue_buffer_manager buffers;
};
@ -166,25 +172,29 @@ class slot_ue
{
public:
slot_ue() = default;
explicit slot_ue(ue_carrier& ue, slot_point slot_tx_, uint32_t dl_pending_bytes, uint32_t ul_pending_bytes);
explicit slot_ue(ue_carrier& ue, slot_point slot_tx_);
slot_ue(slot_ue&&) noexcept = default;
slot_ue& operator=(slot_ue&&) noexcept = default;
bool empty() const { return ue == nullptr; }
void release() { ue = nullptr; }
const ue_carrier_params_t& cfg() const { return ue->bwp_cfg; }
const ue_carrier_params_t& operator*() const { return ue->bwp_cfg; }
const ue_carrier_params_t* operator->() const { return &ue->bwp_cfg; }
// mutable interface to ue_carrier state
/// Find available HARQs
dl_harq_proc* find_empty_dl_harq() { return ue->harq_ent.find_empty_dl_harq(); }
ul_harq_proc* find_empty_ul_harq() { return ue->harq_ent.find_empty_ul_harq(); }
/// Build PDU with MAC CEs and MAC SDUs
void build_pdu(uint32_t rem_bytes, sched_nr_interface::dl_pdu_t& pdu)
{
ue->pdu_builder.alloc_subpdus(rem_bytes, pdu);
}
/// Channel Information Getters
uint32_t dl_cqi() const { return ue->dl_cqi; }
uint32_t ul_cqi() const { return ue->ul_cqi; }
// UE parameters common to all sectors
uint32_t dl_bytes = 0, ul_bytes = 0;

@ -51,7 +51,7 @@ public:
virtual ~ue_nr();
void reset();
void ue_cfg(const sched_interface::ue_cfg_t& ue_cfg);
void ue_cfg(const sched_nr_interface::ue_cfg_t& ue_cfg);
void set_tti(uint32_t tti);
uint16_t get_rnti() const { return rnti; }

@ -35,7 +35,6 @@
#include "srsran/common/timeout.h"
#include "srsran/interfaces/enb_pdcp_interfaces.h"
#include "srsran/interfaces/enb_rlc_interfaces.h"
#include "srsran/interfaces/enb_rrc_interfaces.h"
#include "srsran/interfaces/enb_x2_interfaces.h"
#include "srsran/interfaces/gnb_interfaces.h"
#include "srsran/interfaces/gnb_mac_interfaces.h"

@ -26,6 +26,7 @@ set(SOURCES mac_nr.cc
sched_nr_grant_allocator.cc
sched_nr_harq.cc
sched_nr_pdcch.cc
sched_nr_sch.cc
sched_nr_cfg.cc
sched_nr_helpers.cc
sched_nr_bwp.cc
@ -39,4 +40,4 @@ add_library(srsgnb_mac STATIC ${SOURCES})
target_link_libraries(srsgnb_mac srsenb_mac_common srsran_mac)
include_directories(${PROJECT_SOURCE_DIR})
add_subdirectory(test)
add_subdirectory(test)

@ -34,8 +34,8 @@ alloc_result
ra_sched::allocate_pending_rar(bwp_slot_allocator& slot_grid, const pending_rar_t& rar, uint32_t& nof_grants_alloc)
{
const uint32_t rar_aggr_level = 2;
prb_bitmap prbs = slot_grid.res_grid()[slot_grid.get_pdcch_tti()].used_prbs(bwp_cfg->cfg.pdcch.ra_search_space.id,
srsran_dci_format_nr_1_0);
prb_bitmap prbs = slot_grid.res_grid()[slot_grid.get_pdcch_tti()].pdschs.occupied_prbs(
bwp_cfg->cfg.pdcch.ra_search_space.id, srsran_dci_format_nr_1_0);
alloc_result ret = alloc_result::other_cause;
srsran::const_span<dl_sched_rar_info_t> msg3_grants{rar.msg3_grant};

@ -53,14 +53,34 @@ bwp_params_t::bwp_params_t(const cell_cfg_t& cell, const sched_args_t& sched_cfg
cc(cc_),
bwp_id(bwp_id_),
cfg(cell.bwps[bwp_id_]),
logger(srslog::fetch_basic_logger(sched_cfg_.logger_name))
logger(srslog::fetch_basic_logger(sched_cfg_.logger_name)),
cached_empty_prb_mask(cell.bwps[bwp_id_].rb_width,
cell.bwps[bwp_id_].start_rb,
cell.bwps[bwp_id_].pdsch.rbg_size_cfg_1)
{
srsran_assert(cfg.pdcch.ra_search_space_present, "BWPs without RA search space not supported");
const uint32_t ra_coreset_id = cfg.pdcch.ra_search_space.coreset_id;
P = get_P(cfg.rb_width, cfg.pdsch.rbg_size_cfg_1);
N_rbg = get_nof_rbgs(cfg.rb_width, cfg.start_rb, cfg.pdsch.rbg_size_cfg_1);
cached_empty_prb_mask.resize(cfg.rb_width);
for (const srsran_coreset_t& cs : view_active_coresets(cfg.pdcch)) {
coresets.emplace(cs.id);
uint32_t rb_start = srsran_coreset_start_rb(&cs);
coresets[cs.id].prb_limits = prb_interval{rb_start, rb_start + srsran_coreset_get_bw(&cs)};
coresets[cs.id].usable_common_ss_prb_mask = cached_empty_prb_mask;
// TS 38.214, 5.1.2.2 - For DCI format 1_0 and common search space, lowest RB of the CORESET is the RB index = 0
coresets[cs.id].usable_common_ss_prb_mask |= prb_interval(0, rb_start);
coresets[cs.id].dci_1_0_prb_limits = prb_interval{rb_start, cfg.rb_width};
// TS 38.214, 5.1.2.2.2 - when DCI format 1_0, common search space and CORESET#0 is configured for the cell,
// RA type 1 allocs shall be within the CORESET#0 region
if (cfg.pdcch.coreset_present[0]) {
coresets[cs.id].dci_1_0_prb_limits = coresets[cs.id].prb_limits;
coresets[cs.id].usable_common_ss_prb_mask |= prb_interval(coresets[cs.id].prb_limits.stop(), cfg.rb_width);
}
}
// Derive params of individual slots
uint32_t nof_slots = SRSRAN_NSLOTS_PER_FRAME_NR(cfg.numerology_idx);
@ -117,16 +137,6 @@ bwp_params_t::bwp_params_t(const cell_cfg_t& cell, const sched_args_t& sched_cfg
ss_cce_list[sl][agg_idx].resize(n);
}
}
if (SRSRAN_SEARCH_SPACE_IS_COMMON(ss.type)) {
used_common_prb_masks.emplace(ss_id, cached_empty_prb_mask);
uint32_t coreset_start = srsran_coreset_start_rb(&cfg.pdcch.coreset[ss.coreset_id]);
used_common_prb_masks[ss_id].fill(0, coreset_start, true);
if (ss.coreset_id == 0) {
uint32_t coreset0_bw = srsran_coreset_get_bw(&cfg.pdcch.coreset[0]);
used_common_prb_masks[ss_id].fill(coreset_start + coreset0_bw, cfg.rb_width, true);
}
}
}
}
@ -149,7 +159,7 @@ sched_params_t::sched_params_t(const sched_args_t& sched_cfg_) : sched_cfg(sched
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ue_carrier_params_t::ue_carrier_params_t(uint16_t rnti_, const bwp_params_t& bwp_cfg_, const ue_cfg_t& uecfg_) :
rnti(rnti_), cc(bwp_cfg_.cc), cfg_(&uecfg_), bwp_cfg(&bwp_cfg_)
rnti(rnti_), cc(bwp_cfg_.cc), cfg_(&uecfg_), bwp_cfg(&bwp_cfg_), cached_dci_cfg(uecfg_.phy_cfg.get_dci_cfg())
{
std::fill(ss_id_to_cce_idx.begin(), ss_id_to_cce_idx.end(), SRSRAN_UE_DL_NR_MAX_NOF_SEARCH_SPACE);
const auto& pdcch = phy().pdcch;
@ -166,5 +176,29 @@ ue_carrier_params_t::ue_carrier_params_t(uint16_t rnti_, const bwp_params_t& bwp
}
}
int ue_carrier_params_t::find_ss_id(srsran_dci_format_nr_t dci_fmt) const
{
static const uint32_t aggr_idx = 2; // TODO: Make it dynamic
static const srsran_rnti_type_t rnti_type = srsran_rnti_type_c; // TODO: Use TC-RNTI for Msg4
auto active_ss_lst = view_active_search_spaces(phy().pdcch);
for (const srsran_search_space_t& ss : active_ss_lst) {
// Prioritize UE-dedicated SearchSpaces
if (ss.type == srsran_search_space_type_ue and ss.nof_candidates[aggr_idx] > 0 and
contains_dci_format(ss, dci_fmt) and is_rnti_type_valid_in_search_space(rnti_type, ss.type)) {
return ss.id;
}
}
// Search Common SearchSpaces
for (const srsran_search_space_t& ss : active_ss_lst) {
if (SRSRAN_SEARCH_SPACE_IS_COMMON(ss.type) and ss.nof_candidates[aggr_idx] > 0 and
contains_dci_format(ss, dci_fmt) and is_rnti_type_valid_in_search_space(rnti_type, ss.type)) {
return ss.id;
}
}
return -1;
}
} // namespace sched_nr_impl
} // namespace srsenb

@ -40,7 +40,7 @@ candidate_ss_list_t find_ss(const srsran_pdcch_cfg_nr_t& pdcch,
auto contains_dci_fmt = [prio_dcis, aggr_idx](const srsran_search_space_t& ss) {
if (ss.nof_candidates[aggr_idx] > 0 and ss.nof_formats > 0) {
for (uint32_t i = 0; i < prio_dcis.size(); ++i) {
for (uint32_t j = 0; j < prio_dcis.size(); ++j) {
for (uint32_t j = 0; j < ss.nof_formats; ++j) {
if (ss.formats[j] == prio_dcis[i]) {
return true;
}
@ -85,38 +85,24 @@ candidate_ss_list_t find_ss(const srsran_pdcch_cfg_nr_t& pdcch,
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bwp_slot_grid::bwp_slot_grid(const bwp_params_t& bwp_cfg_, uint32_t slot_idx_) :
dl_prbs(bwp_cfg_.cfg.rb_width, bwp_cfg_.cfg.start_rb, bwp_cfg_.cfg.pdsch.rbg_size_cfg_1),
ul_prbs(bwp_cfg_.cfg.rb_width, bwp_cfg_.cfg.start_rb, bwp_cfg_.cfg.pdsch.rbg_size_cfg_1),
slot_idx(slot_idx_),
cfg(&bwp_cfg_),
pdcchs(bwp_cfg_, slot_idx_, dl.phy.pdcch_dl, dl.phy.pdcch_ul),
pdschs(bwp_cfg_, slot_idx_, dl.phy.pdsch),
puschs(bwp_cfg_, slot_idx_, ul.pusch),
rar_softbuffer(harq_softbuffer_pool::get_instance().get_tx(bwp_cfg_.cfg.rb_width))
{
for (uint32_t cs_idx = 0; cs_idx < SRSRAN_UE_DL_NR_MAX_NOF_CORESET; ++cs_idx) {
if (cfg->cfg.pdcch.coreset_present[cs_idx]) {
uint32_t cs_id = cfg->cfg.pdcch.coreset[cs_idx].id;
coresets[cs_id].emplace(*cfg, cs_id, slot_idx_, dl.phy.pdcch_dl, dl.phy.pdcch_ul);
}
}
}
{}
void bwp_slot_grid::reset()
{
for (auto& coreset : coresets) {
if (coreset.has_value()) {
coreset->reset();
}
}
dl_prbs.reset();
ul_prbs.reset();
pdcchs.reset();
pdschs.reset();
puschs.reset();
dl.phy.ssb.clear();
dl.phy.nzp_csi_rs.clear();
dl.phy.pdcch_dl.clear();
dl.phy.pdcch_ul.clear();
dl.phy.pdsch.clear();
dl.data.clear();
dl.rar.clear();
dl.sib_idxs.clear();
ul.pusch.clear();
ul.pucch.clear();
pending_acks.clear();
}
@ -124,7 +110,7 @@ void bwp_slot_grid::reset()
bwp_res_grid::bwp_res_grid(const bwp_params_t& bwp_cfg_) : cfg(&bwp_cfg_)
{
for (uint32_t sl = 0; sl < slots.capacity(); ++sl) {
slots.emplace_back(*cfg, sl % static_cast<uint32_t>(SRSRAN_NSLOTS_PER_FRAME_NR(0u)));
slots.emplace_back(*cfg, sl % static_cast<uint32_t>(SRSRAN_NSLOTS_PER_FRAME_NR(bwp_cfg_.cell_cfg.carrier.scs)));
}
}
@ -140,43 +126,42 @@ alloc_result bwp_slot_allocator::alloc_si(uint32_t aggr_idx,
const prb_interval& prbs,
tx_harq_softbuffer& softbuffer)
{
static const uint32_t ss_id = 0;
static const srsran_dci_format_nr_t dci_fmt = srsran_dci_format_nr_1_0;
bwp_slot_grid& bwp_pdcch_slot = bwp_grid[pdcch_slot];
alloc_result ret = verify_pdsch_space(bwp_pdcch_slot, bwp_pdcch_slot);
// Verify there is space in PDSCH
alloc_result ret = bwp_pdcch_slot.pdschs.is_si_grant_valid(ss_id, prbs);
if (ret != alloc_result::success) {
return ret;
}
if (bwp_pdcch_slot.dl_prbs.collides(prbs)) {
return alloc_result::sch_collision;
}
const uint32_t coreset_id = 0;
const uint32_t ss_id = 0;
if (not bwp_pdcch_slot.coresets[coreset_id]->alloc_dci(pdcch_grant_type_t::sib, aggr_idx, ss_id)) {
logger.warning("SCHED: Cannot allocate SIB1 due to lack of PDCCH space.");
return alloc_result::no_cch_space;
// Allocate PDCCH
auto pdcch_result = bwp_pdcch_slot.pdcchs.alloc_si_pdcch(ss_id, aggr_idx);
if (pdcch_result.is_error()) {
logger.warning("SCHED: Cannot allocate SIB due to lack of PDCCH space.");
return pdcch_result.error();
}
pdcch_dl_t& pdcch = *pdcch_result.value();
// Allocate PDSCH (no need to verify again if there is space in PDSCH)
pdsch_t& pdsch = bwp_pdcch_slot.pdschs.alloc_si_pdsch_unchecked(ss_id, prbs, pdcch.dci);
// RAR allocation successful.
bwp_pdcch_slot.dl_prbs |= prbs;
// Generate DCI for SIB
pdcch_dl_t& pdcch = bwp_pdcch_slot.dl.phy.pdcch_dl.back();
pdcch.dci_cfg.coreset0_bw = srsran_coreset_get_bw(&cfg.cfg.pdcch.coreset[0]);
if (not fill_dci_sib(prbs, si_idx, si_ntx, *bwp_grid.cfg, pdcch.dci)) {
// Cancel on-going PDCCH allocation
bwp_pdcch_slot.coresets[coreset_id]->rem_last_dci();
return alloc_result::invalid_coderate;
}
pdcch.dci.mcs = 5;
pdcch.dci.rv = 0;
pdcch.dci.sii = si_idx == 0 ? 0 : 1;
// Generate PDSCH
bwp_pdcch_slot.dl.phy.pdsch.emplace_back();
pdsch_t& pdsch = bwp_pdcch_slot.dl.phy.pdsch.back();
srsran_slot_cfg_t slot_cfg;
slot_cfg.idx = pdcch_slot.to_uint();
int code = srsran_ra_dl_dci_to_grant_nr(
&cfg.cell_cfg.carrier, &slot_cfg, &cfg.cfg.pdsch, &pdcch.dci, &pdsch.sch, &pdsch.sch.grant);
if (code != SRSRAN_SUCCESS) {
logger.warning("Error generating SIB PDSCH grant.");
bwp_pdcch_slot.coresets[coreset_id]->rem_last_dci();
bwp_pdcch_slot.pdcchs.cancel_last_pdcch();
bwp_pdcch_slot.dl.phy.pdsch.pop_back();
return alloc_result::other_cause;
}
@ -193,28 +178,18 @@ alloc_result bwp_slot_allocator::alloc_rar_and_msg3(uint16_t
prb_interval interv,
srsran::const_span<dl_sched_rar_info_t> pending_rachs)
{
static const uint32_t msg3_nof_prbs = 3, m = 0;
static const uint32_t msg3_nof_prbs = 3, m = 0;
static const srsran_dci_format_nr_t dci_fmt = srsran_dci_format_nr_1_0;
bwp_slot_grid& bwp_pdcch_slot = bwp_grid[pdcch_slot];
slot_point msg3_slot = pdcch_slot + cfg.pusch_ra_list[m].msg3_delay;
bwp_slot_grid& bwp_msg3_slot = bwp_grid[msg3_slot];
alloc_result ret = verify_pusch_space(bwp_msg3_slot, nullptr);
if (ret != alloc_result::success) {
return ret;
}
ret = verify_pdsch_space(bwp_pdcch_slot, bwp_pdcch_slot);
// Verify there is space in PDSCH for RAR
alloc_result ret = bwp_pdcch_slot.pdschs.is_rar_grant_valid(interv);
if (ret != alloc_result::success) {
return ret;
}
if (not bwp_pdcch_slot.dl.phy.ssb.empty()) {
// TODO: support concurrent PDSCH and SSB
logger.debug("SCHED: skipping RAR allocation. Cause: concurrent PDSCH and SSB not yet supported");
return alloc_result::no_sch_space;
}
if (pending_rachs.size() > bwp_pdcch_slot.dl.rar.capacity() - bwp_pdcch_slot.dl.rar.size()) {
logger.error("SCHED: Trying to allocate too many Msg3 grants in a single slot (%zd)", pending_rachs.size());
return alloc_result::invalid_grant_params;
}
for (auto& rach : pending_rachs) {
auto ue_it = slot_ues.find(rach.temp_crnti);
if (ue_it == slot_ues.end()) {
@ -223,54 +198,51 @@ alloc_result bwp_slot_allocator::alloc_rar_and_msg3(uint16_t
return alloc_result::no_rnti_opportunity;
}
}
// Check DL RB collision
if (bwp_pdcch_slot.dl_prbs.collides(interv)) {
logger.debug("SCHED: Provided RBG mask collides with allocation previously made.");
return alloc_result::sch_collision;
srsran_sanity_check(not bwp_pdcch_slot.dl.rar.full(), "The #RARs should be below #PDSCHs");
if (not bwp_pdcch_slot.dl.phy.ssb.empty()) {
// TODO: support concurrent PDSCH and SSB
logger.debug("SCHED: skipping RAR allocation. Cause: concurrent PDSCH and SSB not yet supported");
return alloc_result::no_sch_space;
}
// Verify there is space in PUSCH for Msg3s
ret = bwp_msg3_slot.puschs.has_grant_space(pending_rachs.size());
if (ret != alloc_result::success) {
return ret;
}
// Check Msg3 RB collision
uint32_t total_ul_nof_prbs = msg3_nof_prbs * pending_rachs.size();
uint32_t total_ul_nof_rbgs = srsran::ceil_div(total_ul_nof_prbs, get_P(bwp_grid.nof_prbs(), false));
prb_interval msg3_rbs = find_empty_interval_of_length(bwp_msg3_slot.ul_prbs.prbs(), total_ul_nof_rbgs);
if (msg3_rbs.length() < total_ul_nof_rbgs) {
uint32_t total_msg3_nof_prbs = msg3_nof_prbs * pending_rachs.size();
prb_interval all_msg3_rbs =
find_empty_interval_of_length(bwp_msg3_slot.puschs.occupied_prbs(), total_msg3_nof_prbs, 0);
if (all_msg3_rbs.length() < total_msg3_nof_prbs) {
logger.debug("SCHED: No space in PUSCH for Msg3.");
return alloc_result::sch_collision;
}
// Find PDCCH position
const uint32_t coreset_id = cfg.cfg.pdcch.ra_search_space.coreset_id;
const uint32_t search_space_id = cfg.cfg.pdcch.ra_search_space.id;
if (not bwp_pdcch_slot.coresets[coreset_id]->alloc_dci(pdcch_grant_type_t::rar, aggr_idx, search_space_id, nullptr)) {
// Allocate PDCCH position for RAR
auto pdcch_result = bwp_pdcch_slot.pdcchs.alloc_rar_pdcch(ra_rnti, aggr_idx);
if (pdcch_result.is_error()) {
// Could not find space in PDCCH
logger.debug("SCHED: No space in PDCCH for DL tx.");
return alloc_result::no_cch_space;
return pdcch_result.error();
}
pdcch_dl_t& pdcch = *pdcch_result.value();
pdcch.dci_cfg = slot_ues[pending_rachs[0].temp_crnti]->get_dci_cfg();
pdcch.dci.mcs = 5;
// RAR allocation successful.
bwp_pdcch_slot.dl_prbs |= interv;
// Generate DCI for RAR with given RA-RNTI
pdcch_dl_t& pdcch = bwp_pdcch_slot.dl.phy.pdcch_dl.back();
if (not fill_dci_rar(interv, ra_rnti, *bwp_grid.cfg, pdcch.dci)) {
// Cancel on-going PDCCH allocation
bwp_pdcch_slot.coresets[coreset_id]->rem_last_dci();
return alloc_result::invalid_coderate;
}
auto& phy_cfg = slot_ues[pending_rachs[0].temp_crnti]->phy();
pdcch.dci_cfg = phy_cfg.get_dci_cfg();
// Generate RAR PDSCH
// Allocate RAR PDSCH
pdsch_t& pdsch = bwp_pdcch_slot.pdschs.alloc_rar_pdsch_unchecked(interv, pdcch.dci);
// Fill RAR PDSCH content
// TODO: Properly fill Msg3 grants
bwp_pdcch_slot.dl.phy.pdsch.emplace_back();
pdsch_t& pdsch = bwp_pdcch_slot.dl.phy.pdsch.back();
srsran_slot_cfg_t slot_cfg;
slot_cfg.idx = pdcch_slot.to_uint();
bool success = phy_cfg.get_pdsch_cfg(slot_cfg, pdcch.dci, pdsch.sch);
srsran_assert(success, "Error converting DCI to grant");
int code = srsran_ra_dl_dci_to_grant_nr(
&cfg.cell_cfg.carrier, &slot_cfg, &cfg.cfg.pdsch, &pdcch.dci, &pdsch.sch, &pdsch.sch.grant);
srsran_assert(code == SRSRAN_SUCCESS, "Error converting DCI to grant");
pdsch.sch.grant.tb[0].softbuffer.tx = bwp_pdcch_slot.rar_softbuffer->get();
// Generate Msg3 grants in PUSCH
uint32_t last_msg3 = msg3_rbs.start();
uint32_t last_msg3 = all_msg3_rbs.start();
const int mcs = 0, max_harq_msg3_retx = 4;
slot_cfg.idx = msg3_slot.to_uint();
bwp_pdcch_slot.dl.rar.emplace_back();
@ -282,42 +254,59 @@ alloc_result bwp_slot_allocator::alloc_rar_and_msg3(uint16_t
rar_out.grants.emplace_back();
auto& rar_grant = rar_out.grants.back();
rar_grant.data = grant;
fill_dci_from_cfg(cfg, rar_grant.msg3_dci);
// Fill Msg3 DCI context
rar_grant.msg3_dci.ctx.coreset_id = pdcch.dci.ctx.coreset_id;
rar_grant.msg3_dci.ctx.rnti_type = srsran_rnti_type_tc;
rar_grant.msg3_dci.ctx.rnti = ue->rnti;
rar_grant.msg3_dci.ctx.ss_type = srsran_search_space_type_rar;
rar_grant.msg3_dci.ctx.format = srsran_dci_format_nr_rar;
// Allocate Msg3 PUSCH allocation
prb_interval msg3_interv{last_msg3, last_msg3 + msg3_nof_prbs};
last_msg3 += msg3_nof_prbs;
pusch_t& pusch = bwp_msg3_slot.puschs.alloc_pusch_unchecked(msg3_interv, rar_grant.msg3_dci);
// Allocate UL HARQ
ue.h_ul = ue.find_empty_ul_harq();
success = ue.h_ul->new_tx(msg3_slot, msg3_slot, msg3_interv, mcs, max_harq_msg3_retx);
srsran_assert(success, "Failed to allocate Msg3");
fill_dci_msg3(ue, *bwp_grid.cfg, rar_grant.msg3_dci);
// Generate PUSCH
bwp_msg3_slot.ul.pusch.emplace_back();
pusch_t& pusch = bwp_msg3_slot.ul.pusch.back();
success = ue->phy().get_pusch_cfg(slot_cfg, rar_grant.msg3_dci, pusch.sch);
srsran_sanity_check(ue.h_ul != nullptr, "Failed to allocate Msg3");
bool success = ue.h_ul->new_tx(msg3_slot, msg3_interv, mcs, max_harq_msg3_retx, rar_grant.msg3_dci);
srsran_sanity_check(success, "Failed to allocate Msg3");
// Generate PUSCH content
success = ue->phy().get_pusch_cfg(slot_cfg, rar_grant.msg3_dci, pusch.sch);
srsran_assert(success, "Error converting DCI to PUSCH grant");
pusch.sch.grant.tb[0].softbuffer.rx = ue.h_ul->get_softbuffer().get();
ue.h_ul->set_tbs(pusch.sch.grant.tb[0].tbs);
}
bwp_msg3_slot.ul_prbs.add(msg3_rbs);
return alloc_result::success;
}
// ue is the UE (1 only) that will be allocated
// func computes the grant allocation for this UE
alloc_result bwp_slot_allocator::alloc_pdsch(slot_ue& ue, prb_grant dl_grant)
alloc_result bwp_slot_allocator::alloc_pdsch(slot_ue& ue, uint32_t ss_id, const prb_grant& dl_grant)
{
static const uint32_t aggr_idx = 2;
static const std::array<srsran_dci_format_nr_t, 2> dci_fmt_list{srsran_dci_format_nr_1_1, srsran_dci_format_nr_1_0};
static const uint32_t aggr_idx = 2;
static const srsran_dci_format_nr_t dci_fmt = srsran_dci_format_nr_1_0;
static const srsran_rnti_type_t rnti_type = srsran_rnti_type_c;
bwp_slot_grid& bwp_pdcch_slot = bwp_grid[ue.pdcch_slot];
bwp_slot_grid& bwp_pdsch_slot = bwp_grid[ue.pdsch_slot];
bwp_slot_grid& bwp_uci_slot = bwp_grid[ue.uci_slot]; // UCI : UL control info
alloc_result result = verify_pdsch_space(bwp_pdsch_slot, bwp_pdcch_slot, &bwp_uci_slot);
// Verify there is space in PDSCH
alloc_result ret = bwp_pdcch_slot.pdschs.is_ue_grant_valid(ue.cfg(), ss_id, dci_fmt, dl_grant);
if (ret != alloc_result::success) {
return ret;
}
alloc_result result = verify_uci_space(bwp_uci_slot);
if (result != alloc_result::success) {
return result;
}
result = verify_ue_cfg(ue.cfg(), ue.h_dl);
if (result != alloc_result::success) {
if (ue.h_dl == nullptr) {
logger.warning("SCHED: Trying to allocate rnti=0x%x with no available DL HARQs", ue->rnti);
return result;
}
if (not bwp_pdsch_slot.dl.phy.ssb.empty()) {
@ -325,125 +314,91 @@ alloc_result bwp_slot_allocator::alloc_pdsch(slot_ue& ue, prb_grant dl_grant)
logger.debug("SCHED: skipping PDSCH allocation. Cause: concurrent PDSCH and SSB not yet supported");
return alloc_result::no_sch_space;
}
if (bwp_pdsch_slot.dl_prbs.collides(dl_grant)) {
return alloc_result::sch_collision;
}
// Find space in PUCCH
// Check space in PUCCH/PUSCH for UCI
// TODO
// Choose SearchSpace + DCI format
srsran_rnti_type_t rnti_type =
ue->ue_cfg().ue_bearers[1].direction == mac_lc_ch_cfg_t::IDLE ? srsran_rnti_type_tc : srsran_rnti_type_c;
// Choose the ss_id the highest number of candidates
candidate_ss_list_t ss_candidates = find_ss(ue->phy().pdcch, aggr_idx, rnti_type, dci_fmt_list);
if (ss_candidates.empty()) {
// Could not find space in PDCCH
logger.warning("SCHED: No PDCCH candidates for any of the rnti=0x%x search spaces", ue->rnti);
return alloc_result::no_cch_space;
}
const srsran_search_space_t& ss = *ss_candidates[0];
// Find space and allocate PDCCH
uint32_t coreset_id = ss.coreset_id;
if (not bwp_pdcch_slot.coresets[coreset_id]->alloc_dci(pdcch_grant_type_t::dl_data, aggr_idx, ss.id, &ue.cfg())) {
auto pdcch_result = bwp_pdcch_slot.pdcchs.alloc_dl_pdcch(rnti_type, ss_id, aggr_idx, ue.cfg());
if (pdcch_result.is_error()) {
// Could not find space in PDCCH
return alloc_result::no_cch_space;
return pdcch_result.error();
}
pdcch_dl_t& pdcch = *pdcch_result.value();
pdcch.dci_cfg = ue->get_dci_cfg();
pdcch.dci.pucch_resource = 0;
pdcch.dci.dai = std::count_if(bwp_uci_slot.pending_acks.begin(),
bwp_uci_slot.pending_acks.end(),
[&ue](const harq_ack_t& p) { return p.res.rnti == ue->rnti; });
pdcch.dci.dai %= 4;
// Update PRB grant based on the start and end of CORESET RBs
reduce_to_dl_coreset_bw(cfg, ss.id, srsran_dci_format_nr_1_0, dl_grant);
// Allocate PDSCH
pdsch_t& pdsch = bwp_pdcch_slot.pdschs.alloc_ue_pdsch_unchecked(ss_id, dci_fmt, dl_grant, ue.cfg(), pdcch.dci);
// Allocate HARQ
int mcs = ue->fixed_pdsch_mcs();
if (ue.h_dl->empty()) {
bool ret = ue.h_dl->new_tx(ue.pdsch_slot, ue.uci_slot, dl_grant, mcs, 4);
srsran_assert(ret, "Failed to allocate DL HARQ");
bool success = ue.h_dl->new_tx(ue.pdsch_slot, ue.uci_slot, dl_grant, mcs, 4, pdcch.dci);
srsran_assert(success, "Failed to allocate DL HARQ");
} else {
bool ret = ue.h_dl->new_retx(ue.pdsch_slot, ue.uci_slot, dl_grant);
mcs = ue.h_dl->mcs();
srsran_assert(ret, "Failed to allocate DL HARQ retx");
bool success = ue.h_dl->new_retx(ue.pdsch_slot, ue.uci_slot, dl_grant, pdcch.dci);
mcs = ue.h_dl->mcs();
srsran_assert(success, "Failed to allocate DL HARQ retx");
}
// Allocation Successful
const static float max_R = 0.93;
srsran_slot_cfg_t slot_cfg;
slot_cfg.idx = ue.pdsch_slot.to_uint();
const static float max_R = 0.95;
while (true) {
// Generate PDCCH
pdcch_dl_t& pdcch = bwp_pdcch_slot.dl.phy.pdcch_dl.back();
fill_dl_dci_ue_fields(ue, *bwp_grid.cfg, ss.id, pdcch.dci.ctx.location, pdcch.dci);
pdcch.dci.pucch_resource = 0;
pdcch.dci.dai = std::count_if(bwp_uci_slot.pending_acks.begin(),
bwp_uci_slot.pending_acks.end(),
[&ue](const harq_ack_t& p) { return p.res.rnti == ue->rnti; });
pdcch.dci.dai %= 4;
pdcch.dci_cfg = ue->phy().get_dci_cfg();
// Generate PUCCH
bwp_uci_slot.pending_acks.emplace_back();
bwp_uci_slot.pending_acks.back().phy_cfg = &ue->phy();
srsran_assert(ue->phy().get_pdsch_ack_resource(pdcch.dci, bwp_uci_slot.pending_acks.back().res),
"Error getting ack resource");
// Generate PDSCH
bwp_pdsch_slot.dl_prbs |= dl_grant;
bwp_pdsch_slot.dl.phy.pdsch.emplace_back();
pdsch_t& pdsch = bwp_pdsch_slot.dl.phy.pdsch.back();
srsran_slot_cfg_t slot_cfg;
slot_cfg.idx = ue.pdsch_slot.to_uint();
bool ret = ue->phy().get_pdsch_cfg(slot_cfg, pdcch.dci, pdsch.sch);
srsran_assert(ret, "Error converting DCI to grant");
pdsch.sch.grant.tb[0].softbuffer.tx = ue.h_dl->get_softbuffer().get();
pdsch.data[0] = ue.h_dl->get_tx_pdu()->get();
if (ue.h_dl->nof_retx() == 0) {
ue.h_dl->set_tbs(pdsch.sch.grant.tb[0].tbs); // update HARQ with correct TBS
} else {
bool success = ue->phy().get_pdsch_cfg(slot_cfg, pdcch.dci, pdsch.sch);
srsran_assert(success, "Error converting DCI to grant");
if (ue.h_dl->nof_retx() != 0) {
srsran_assert(pdsch.sch.grant.tb[0].tbs == (int)ue.h_dl->tbs(), "The TBS did not remain constant in retx");
}
if (ue.h_dl->nof_retx() > 0 or bwp_pdsch_slot.dl.phy.pdsch.back().sch.grant.tb[0].R_prime < max_R or mcs <= 0) {
if (ue.h_dl->nof_retx() > 0 or pdsch.sch.grant.tb[0].R_prime < max_R or mcs <= 0) {
break;
}
// Decrease MCS if first tx and rate is too high
mcs--;
ue.h_dl->set_mcs(mcs);
bwp_pdsch_slot.dl.phy.pdsch.pop_back();
bwp_uci_slot.pending_acks.pop_back();
pdcch.dci.mcs = mcs;
}
if (mcs == 0) {
logger.warning("Couldn't find mcs that leads to R<0.9");
logger.warning("Couldn't find mcs that leads to R<0.95");
}
ue.h_dl->set_mcs(mcs);
ue.h_dl->set_tbs(pdsch.sch.grant.tb[0].tbs); // set HARQ TBS
pdsch.sch.grant.tb[0].softbuffer.tx = ue.h_dl->get_softbuffer().get();
pdsch.data[0] = ue.h_dl->get_tx_pdu()->get();
// Select scheduled LCIDs and update UE buffer state
bwp_pdsch_slot.dl.data.emplace_back();
ue.build_pdu(ue.h_dl->tbs(), bwp_pdsch_slot.dl.data.back());
// Generate PUCCH
bwp_uci_slot.pending_acks.emplace_back();
bwp_uci_slot.pending_acks.back().phy_cfg = &ue->phy();
srsran_assert(ue->phy().get_pdsch_ack_resource(pdcch.dci, bwp_uci_slot.pending_acks.back().res),
"Error getting ack resource");
return alloc_result::success;
}
alloc_result bwp_slot_allocator::alloc_pusch(slot_ue& ue, prb_grant ul_grant)
alloc_result bwp_slot_allocator::alloc_pusch(slot_ue& ue, const prb_grant& ul_grant)
{
static const uint32_t aggr_idx = 2;
static const std::array<srsran_dci_format_nr_t, 2> dci_fmt_list{srsran_dci_format_nr_0_1, srsran_dci_format_nr_0_0};
static const srsran_rnti_type_t rnti_type = srsran_rnti_type_c;
auto& bwp_pdcch_slot = bwp_grid[ue.pdcch_slot];
auto& bwp_pusch_slot = bwp_grid[ue.pusch_slot];
alloc_result ret = verify_pusch_space(bwp_pusch_slot, &bwp_pdcch_slot);
if (ret != alloc_result::success) {
return ret;
}
ret = verify_ue_cfg(ue.cfg(), ue.h_ul);
if (ret != alloc_result::success) {
return ret;
}
pdcch_ul_list_t& pdcchs = bwp_pdcch_slot.dl.phy.pdcch_ul;
if (bwp_pusch_slot.ul_prbs.collides(ul_grant)) {
return alloc_result::sch_collision;
auto& bwp_pdcch_slot = bwp_grid[ue.pdcch_slot];
auto& bwp_pusch_slot = bwp_grid[ue.pusch_slot];
if (ue.h_ul == nullptr) {
logger.warning("SCHED: Trying to allocate rnti=0x%x with no available UL HARQs", ue->rnti);
return alloc_result::no_rnti_opportunity;
}
// Choose SearchSpace + DCI format
srsran_rnti_type_t rnti_type =
ue->ue_cfg().ue_bearers[1].direction == mac_lc_ch_cfg_t::IDLE ? srsran_rnti_type_tc : srsran_rnti_type_c;
candidate_ss_list_t ss_candidates = find_ss(ue->phy().pdcch, aggr_idx, rnti_type, dci_fmt_list);
if (ss_candidates.empty()) {
// Could not find space in PDCCH
@ -452,32 +407,35 @@ alloc_result bwp_slot_allocator::alloc_pusch(slot_ue& ue, prb_grant ul_grant)
}
const srsran_search_space_t& ss = *ss_candidates[0];
uint32_t coreset_id = ss.coreset_id;
if (not bwp_pdcch_slot.coresets[coreset_id].value().alloc_dci(
pdcch_grant_type_t::ul_data, aggr_idx, ss.id, &ue.cfg())) {
// Verify if PUSCH allocation is valid
alloc_result ret = bwp_pusch_slot.puschs.is_grant_valid(ss.type, ul_grant);
if (ret != alloc_result::success) {
return ret;
}
auto pdcch_result = bwp_pdcch_slot.pdcchs.alloc_ul_pdcch(ss.id, aggr_idx, ue.cfg());
if (pdcch_result.is_error()) {
// Could not find space in PDCCH
return alloc_result::no_cch_space;
return pdcch_result.error();
}
// Allocation Successful
pdcch_ul_t& pdcch = *pdcch_result.value();
pdcch.dci_cfg = ue->get_dci_cfg();
// Allocate PUSCH
pusch_t& pusch = bwp_pusch_slot.puschs.alloc_pusch_unchecked(ul_grant, pdcch.dci);
if (ue.h_ul->empty()) {
int mcs = ue->fixed_pusch_mcs();
bool success = ue.h_ul->new_tx(ue.pusch_slot, ue.pusch_slot, ul_grant, mcs, ue->ue_cfg().maxharq_tx);
bool success = ue.h_ul->new_tx(ue.pusch_slot, ul_grant, mcs, ue->ue_cfg().maxharq_tx, pdcch.dci);
srsran_assert(success, "Failed to allocate UL HARQ");
} else {
bool success = ue.h_ul->new_retx(ue.pusch_slot, ue.pusch_slot, ul_grant);
bool success = ue.h_ul->new_retx(ue.pusch_slot, ul_grant, pdcch.dci);
srsran_assert(success, "Failed to allocate UL HARQ retx");
}
// Generate PDCCH
pdcch_ul_t& pdcch = pdcchs.back();
fill_ul_dci_ue_fields(ue, *bwp_grid.cfg, ss.id, pdcch.dci.ctx.location, pdcch.dci);
pdcch.dci_cfg = ue->phy().get_dci_cfg();
// Generate PUSCH
bwp_pusch_slot.ul_prbs |= ul_grant;
bwp_pusch_slot.ul.pusch.emplace_back();
pusch_t& pusch = bwp_pusch_slot.ul.pusch.back();
// Generate PUSCH content
srsran_slot_cfg_t slot_cfg;
slot_cfg.idx = ue.pusch_slot.to_uint();
pusch.pid = ue.h_ul->pid;
@ -493,67 +451,26 @@ alloc_result bwp_slot_allocator::alloc_pusch(slot_ue& ue, prb_grant ul_grant)
return alloc_result::success;
}
alloc_result bwp_slot_allocator::verify_pdsch_space(bwp_slot_grid& pdsch_grid,
bwp_slot_grid& pdcch_grid,
bwp_slot_grid* uci_grid) const
alloc_result bwp_slot_allocator::verify_uci_space(const bwp_slot_grid& uci_grid) const
{
if (not pdsch_grid.is_dl() or not pdcch_grid.is_dl()) {
logger.warning("SCHED: Trying to allocate PDSCH in TDD non-DL slot index=%d", pdsch_grid.slot_idx);
return alloc_result::no_sch_space;
}
if (pdcch_grid.dl.phy.pdcch_dl.full()) {
logger.warning("SCHED: Maximum number of DL PDCCH allocations reached");
return alloc_result::no_cch_space;
}
if (pdsch_grid.dl.phy.pdsch.full()) {
logger.warning("SCHED: Maximum number of DL PDSCH grants reached");
return alloc_result::no_sch_space;
}
if (uci_grid != nullptr) {
if (uci_grid->pending_acks.full()) {
logger.warning("SCHED: No space for ACK.");
return alloc_result::no_grant_space;
}
}
return alloc_result::success;
}
alloc_result bwp_slot_allocator::verify_pusch_space(bwp_slot_grid& pusch_grid, bwp_slot_grid* pdcch_grid) const
{
if (not pusch_grid.is_ul()) {
logger.warning("SCHED: Trying to allocate PUSCH in TDD non-UL slot index=%d", pusch_grid.slot_idx);
return alloc_result::no_sch_space;
}
if (pdcch_grid != nullptr) {
// DCI needed
if (not pdcch_grid->is_dl()) {
logger.warning("SCHED: Trying to allocate PDCCH in TDD non-DL slot index=%d", pdcch_grid->slot_idx);
return alloc_result::no_sch_space;
}
if (pdcch_grid->dl.phy.pdcch_ul.full()) {
logger.warning("SCHED: Maximum number of PUSCH allocations reached");
return alloc_result::no_grant_space;
}
}
if (pusch_grid.ul.pusch.full()) {
logger.warning("SCHED: Maximum number of PUSCH allocations reached");
if (uci_grid.pending_acks.full()) {
logger.warning("SCHED: No space for ACK.");
return alloc_result::no_grant_space;
}
return alloc_result::success;
}
alloc_result bwp_slot_allocator::verify_ue_cfg(const ue_carrier_params_t& ue_cfg, harq_proc* harq) const
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
prb_grant find_optimal_dl_grant(bwp_slot_allocator& slot_alloc, const slot_ue& ue, uint32_t ss_id)
{
if (ue_cfg.active_bwp().bwp_id != cfg.bwp_id) {
logger.warning(
"SCHED: Trying to allocate rnti=0x%x in inactive BWP id=%d", ue_cfg.rnti, ue_cfg.active_bwp().bwp_id);
return alloc_result::no_rnti_opportunity;
}
if (harq == nullptr) {
logger.warning("SCHED: Trying to allocate rnti=0x%x with no available HARQs", ue_cfg.rnti);
return alloc_result::no_rnti_opportunity;
}
return alloc_result::success;
static const srsran_dci_format_nr_t dci_fmt = srsran_dci_format_nr_1_0; // TODO: Support more DCI formats
prb_bitmap used_prb_mask = slot_alloc.occupied_dl_prbs(ue.pdsch_slot, ss_id, dci_fmt);
prb_interval prb_interv = find_empty_interval_of_length(used_prb_mask, used_prb_mask.size(), 0);
return prb_interv;
}
} // namespace sched_nr_impl

@ -124,14 +124,76 @@ dl_harq_proc::dl_harq_proc(uint32_t id_, uint32_t nprb) :
harq_proc(id_), softbuffer(harq_softbuffer_pool::get_instance().get_tx(nprb)), pdu(srsran::make_byte_buffer())
{}
bool dl_harq_proc::new_tx(slot_point slot_tx,
slot_point slot_ack,
const prb_grant& grant,
uint32_t mcs,
uint32_t max_retx)
void dl_harq_proc::fill_dci(srsran_dci_dl_nr_t& dci)
{
if (harq_proc::new_tx(slot_tx, slot_ack, grant, mcs, max_retx)) {
const static uint32_t rv_idx[4] = {0, 2, 3, 1};
dci.pid = pid;
dci.ndi = ndi();
dci.mcs = mcs();
dci.rv = rv_idx[nof_retx() % 4];
if (dci.ctx.format == srsran_dci_format_nr_1_0) {
dci.harq_feedback = (slot_ack - slot_tx) - 1;
} else {
dci.harq_feedback = slot_tx.to_uint();
}
}
bool dl_harq_proc::new_tx(slot_point slot_tx,
slot_point slot_ack,
const prb_grant& grant,
uint32_t mcs_,
uint32_t max_retx,
srsran_dci_dl_nr_t& dci)
{
const static uint32_t rv_idx[4] = {0, 2, 3, 1};
if (harq_proc::new_tx(slot_tx, slot_ack, grant, mcs_, max_retx)) {
pdu->clear();
fill_dci(dci);
return true;
}
return false;
}
bool dl_harq_proc::new_retx(slot_point slot_tx, slot_point slot_ack, const prb_grant& grant, srsran_dci_dl_nr_t& dci)
{
if (harq_proc::new_retx(slot_tx, slot_ack, grant)) {
fill_dci(dci);
return true;
}
return false;
}
void ul_harq_proc::fill_dci(srsran_dci_ul_nr_t& dci)
{
const static uint32_t rv_idx[4] = {0, 2, 3, 1};
dci.pid = pid;
dci.ndi = ndi();
dci.mcs = mcs();
dci.rv = rv_idx[nof_retx() % 4];
}
bool ul_harq_proc::new_tx(slot_point slot_tx,
const prb_grant& grant,
uint32_t mcs_,
uint32_t max_retx,
srsran_dci_ul_nr_t& dci)
{
const static uint32_t rv_idx[4] = {0, 2, 3, 1};
if (harq_proc::new_tx(slot_tx, slot_tx, grant, mcs_, max_retx)) {
fill_dci(dci);
return true;
}
return false;
}
bool ul_harq_proc::new_retx(slot_point slot_tx, const prb_grant& grant, srsran_dci_ul_nr_t& dci)
{
if (harq_proc::new_retx(slot_tx, slot_tx, grant)) {
fill_dci(dci);
return true;
}
return false;

@ -28,168 +28,6 @@
namespace srsenb {
namespace sched_nr_impl {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void reduce_to_dl_coreset_bw(const bwp_params_t& bwp_cfg,
uint32_t ss_id,
srsran_dci_format_nr_t dci_fmt,
prb_grant& grant)
{
const srsran_search_space_t& ss =
dci_fmt == srsran_dci_format_nr_rar ? bwp_cfg.cfg.pdcch.ra_search_space : bwp_cfg.cfg.pdcch.search_space[ss_id];
if (not SRSRAN_SEARCH_SPACE_IS_COMMON(ss.type)) {
return;
}
uint32_t rb_start = 0, nof_prbs = bwp_cfg.nof_prb();
if (dci_fmt == srsran_dci_format_nr_1_0) {
rb_start = srsran_coreset_start_rb(&bwp_cfg.cfg.pdcch.coreset[ss.coreset_id]);
}
if (ss.coreset_id == 0) {
nof_prbs = srsran_coreset_get_bw(&bwp_cfg.cfg.pdcch.coreset[0]);
}
grant &= prb_interval{rb_start, rb_start + nof_prbs};
}
void fill_dci_common(const bwp_params_t& bwp_cfg, srsran_dci_dl_nr_t& dci)
{
dci.bwp_id = bwp_cfg.bwp_id;
dci.cc_id = bwp_cfg.cc;
dci.tpc = 1;
dci.coreset0_bw = bwp_cfg.cfg.pdcch.coreset_present[0] ? srsran_coreset_get_bw(&bwp_cfg.cfg.pdcch.coreset[0]) : 0;
}
void fill_dci_common(const bwp_params_t& bwp_cfg, srsran_dci_ul_nr_t& dci)
{
dci.bwp_id = bwp_cfg.bwp_id;
dci.cc_id = bwp_cfg.cc;
dci.tpc = 1;
}
template <typename DciDlOrUl>
void fill_dci_harq(const slot_ue& ue, DciDlOrUl& dci)
{
const static uint32_t rv_idx[4] = {0, 2, 3, 1};
harq_proc* h = std::is_same<DciDlOrUl, srsran_dci_dl_nr_t>::value ? static_cast<harq_proc*>(ue.h_dl)
: static_cast<harq_proc*>(ue.h_ul);
dci.pid = h->pid;
dci.ndi = h->ndi();
dci.mcs = h->mcs();
dci.rv = rv_idx[h->nof_retx() % 4];
}
void fill_dci_grant(const bwp_params_t& bwp_cfg, const prb_grant& grant, srsran_dci_dl_nr_t& dci)
{
dci.time_domain_assigment = 0;
if (grant.is_alloc_type0()) {
srsran_assert(not SRSRAN_SEARCH_SPACE_IS_COMMON(dci.ctx.ss_type), "AllocType0 for common search space");
dci.freq_domain_assigment = grant.rbgs().to_uint64();
} else {
uint32_t rb_start = 0, nof_prb = bwp_cfg.nof_prb();
if (dci.ctx.format == srsran_dci_format_nr_1_0 && SRSRAN_SEARCH_SPACE_IS_COMMON(dci.ctx.ss_type)) {
rb_start = dci.ctx.coreset_start_rb;
}
if (dci.ctx.coreset_id == 0 and SRSRAN_SEARCH_SPACE_IS_COMMON(dci.ctx.ss_type)) {
nof_prb = dci.coreset0_bw;
}
srsran_assert(grant.prbs().start() >= rb_start, "Invalid PRB index=%d < %d", grant.prbs().start(), rb_start);
uint32_t grant_start = grant.prbs().start() - rb_start;
dci.freq_domain_assigment = srsran_ra_nr_type1_riv(nof_prb, grant_start, grant.prbs().length());
}
}
void fill_dci_grant(const bwp_params_t& bwp_cfg, const prb_grant& grant, srsran_dci_ul_nr_t& dci)
{
dci.time_domain_assigment = 0;
if (grant.is_alloc_type0()) {
dci.freq_domain_assigment = grant.rbgs().to_uint64();
} else {
uint32_t nof_prb = bwp_cfg.nof_prb();
dci.freq_domain_assigment = srsran_ra_nr_type1_riv(nof_prb, grant.prbs().start(), grant.prbs().length());
}
}
void fill_rar_dci_context(const bwp_params_t& bwp_cfg, uint16_t ra_rnti, srsran_dci_ctx_t& dci_ctx)
{
uint32_t cs_id = bwp_cfg.cfg.pdcch.ra_search_space.coreset_id;
dci_ctx.format = srsran_dci_format_nr_1_0;
dci_ctx.ss_type = srsran_search_space_type_common_1;
dci_ctx.rnti_type = srsran_rnti_type_ra;
dci_ctx.rnti = ra_rnti;
dci_ctx.coreset_id = cs_id;
dci_ctx.coreset_start_rb = srsran_coreset_start_rb(&bwp_cfg.cfg.pdcch.coreset[cs_id]);
}
bool fill_dci_rar(prb_interval interv, uint16_t ra_rnti, const bwp_params_t& bwp_cfg, srsran_dci_dl_nr_t& dci)
{
fill_rar_dci_context(bwp_cfg, ra_rnti, dci.ctx);
dci.mcs = 5;
fill_dci_common(bwp_cfg, dci);
fill_dci_grant(bwp_cfg, interv, dci);
// TODO: Fill
return true;
}
bool fill_dci_msg3(const slot_ue& ue, const bwp_params_t& bwp_cfg, srsran_dci_ul_nr_t& msg3_dci)
{
// Fill DCI context
msg3_dci.ctx.coreset_id = ue->phy().pdcch.ra_search_space.coreset_id;
msg3_dci.ctx.rnti_type = srsran_rnti_type_tc;
msg3_dci.ctx.rnti = ue->rnti;
msg3_dci.ctx.ss_type = srsran_search_space_type_rar;
if (ue.h_ul->nof_retx() == 0) {
msg3_dci.ctx.format = srsran_dci_format_nr_rar;
} else {
msg3_dci.ctx.format = srsran_dci_format_nr_0_0;
}
// Fill DCI content
fill_dci_common(bwp_cfg, msg3_dci);
fill_dci_harq(ue, msg3_dci);
fill_dci_grant(bwp_cfg, ue.h_ul->prbs(), msg3_dci);
return true;
}
void fill_dl_dci_ue_fields(const slot_ue& ue,
const bwp_params_t& bwp_cfg,
uint32_t ss_id,
srsran_dci_location_t dci_pos,
srsran_dci_dl_nr_t& dci)
{
// Note: DCI location may not be the final one, as scheduler may rellocate the UE PDCCH. However, the remaining DCI
// params are independent of the exact DCI location
bool ret = ue->phy().get_dci_ctx_pdsch_rnti_c(ss_id, dci_pos, ue->rnti, dci.ctx);
srsran_assert(ret, "Invalid DL DCI format");
fill_dci_common(bwp_cfg, dci);
fill_dci_harq(ue, dci);
fill_dci_grant(bwp_cfg, ue.h_dl->prbs(), dci);
if (dci.ctx.format == srsran_dci_format_nr_1_0) {
dci.harq_feedback = (ue.uci_slot - ue.pdsch_slot) - 1;
} else {
dci.harq_feedback = ue.pdsch_slot.slot_idx();
}
}
void fill_ul_dci_ue_fields(const slot_ue& ue,
const bwp_params_t& bwp_cfg,
uint32_t ss_id,
srsran_dci_location_t dci_pos,
srsran_dci_ul_nr_t& dci)
{
bool ret = ue->phy().get_dci_ctx_pusch_rnti_c(ss_id, dci_pos, ue->rnti, dci.ctx);
srsran_assert(ret, "Invalid DL DCI format");
fill_dci_common(bwp_cfg, dci);
fill_dci_harq(ue, dci);
fill_dci_grant(bwp_cfg, ue.h_ul->prbs(), dci);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void log_sched_slot_ues(srslog::basic_logger& logger, slot_point pdcch_slot, uint32_t cc, const slot_ue_map_t& slot_ues)
@ -205,7 +43,14 @@ void log_sched_slot_ues(srslog::basic_logger& logger, slot_point pdcch_slot, uin
for (const auto& ue_pair : slot_ues) {
auto& ue = ue_pair->second;
fmt::format_to(fmtbuf, "{}{{rnti=0x{:x}, dl_bs={}, ul_bs={}}}", use_comma, ue->rnti, ue.dl_bytes, ue.ul_bytes);
fmt::format_to(fmtbuf, "{}{{rnti=0x{:x}", use_comma, ue->rnti);
if (ue.dl_active) {
fmt::format_to(fmtbuf, ", dl_bs={}", ue.dl_bytes);
}
if (ue.ul_active) {
fmt::format_to(fmtbuf, ", ul_bs={}", ue.ul_bytes);
}
fmt::format_to(fmtbuf, "}}");
use_comma = ", ";
}

@ -23,6 +23,13 @@
namespace srsenb {
uint32_t coreset_nof_cces(const srsran_coreset_t& coreset)
{
const bool* res_active = &coreset.freq_resources[0];
uint32_t nof_freq_res = std::count(res_active, res_active + SRSRAN_CORESET_FREQ_DOMAIN_RES_SIZE, true);
return nof_freq_res * coreset.duration;
}
srsran::phy_cfg_nr_t get_common_ue_phy_cfg(const sched_nr_interface::cell_cfg_t& cfg)
{
srsran::phy_cfg_nr_t ue_phy_cfg;

@ -20,20 +20,56 @@
*/
#include "srsgnb/hdr/stack/mac/sched_nr_pdcch.h"
#include "srsgnb/hdr/stack/mac/sched_nr_helpers.h"
#include "srsran/common/string_helpers.h"
namespace srsenb {
namespace sched_nr_impl {
coreset_region::coreset_region(const bwp_params_t& bwp_cfg_,
uint32_t coreset_id_,
uint32_t slot_idx_,
pdcch_dl_list_t& dl_list_,
pdcch_ul_list_t& ul_list_) :
template <typename... Args>
void log_pdcch_alloc_failure(srslog::log_channel& log_ch,
srsran_rnti_type_t rnti_type,
uint32_t ss_id,
uint16_t rnti,
const char* cause_fmt,
Args&&... args)
{
if (not log_ch.enabled()) {
return;
}
// Log PDCCH allocation failure
fmt::memory_buffer fmtbuf;
fmt::format_to(fmtbuf,
"SCHED: Failure to allocate PDCCH for {}-rnti=0x{:x}, SS#{}. Cause: ",
srsran_rnti_type_str_short(rnti_type),
rnti,
ss_id);
fmt::format_to(fmtbuf, cause_fmt, std::forward<Args>(args)...);
log_ch("%s", srsran::to_c_str(fmtbuf));
}
void fill_dci_from_cfg(const bwp_params_t& bwp_cfg, srsran_dci_dl_nr_t& dci)
{
dci.bwp_id = bwp_cfg.bwp_id;
dci.cc_id = bwp_cfg.cc;
dci.tpc = 1;
dci.coreset0_bw = bwp_cfg.cfg.pdcch.coreset_present[0] ? bwp_cfg.coreset_prb_range(0).length() : 0;
}
void fill_dci_from_cfg(const bwp_params_t& bwp_cfg, srsran_dci_ul_nr_t& dci)
{
dci.bwp_id = bwp_cfg.bwp_id;
dci.cc_id = bwp_cfg.cc;
dci.tpc = 1;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
coreset_region::coreset_region(const bwp_params_t& bwp_cfg_, uint32_t coreset_id_, uint32_t slot_idx_) :
coreset_cfg(&bwp_cfg_.cfg.pdcch.coreset[coreset_id_]),
coreset_id(coreset_id_),
slot_idx(slot_idx_),
pdcch_dl_list(dl_list_),
pdcch_ul_list(ul_list_),
rar_cce_list(bwp_cfg_.rar_cce_list),
common_cce_list(bwp_cfg_.common_cce_list)
{
@ -42,7 +78,7 @@ coreset_region::coreset_region(const bwp_params_t& bwp_cfg_,
srsran_assert(get_td_symbols() <= SRSRAN_CORESET_DURATION_MAX,
"Possible number of time-domain OFDM symbols in CORESET must be within {1,2,3}");
srsran_assert(nof_freq_res <= bwp_cfg_.cell_cfg.carrier.nof_prb,
"The number of frequency resources=%d of coreset_id=%d exceeds BWP bandwidth",
"The number of frequency resources=%d of CORESET#%d exceeds BWP bandwidth",
nof_freq_res,
coreset_id);
}
@ -52,33 +88,24 @@ void coreset_region::reset()
dfs_tree.clear();
saved_dfs_tree.clear();
dci_list.clear();
pdcch_dl_list.clear();
pdcch_ul_list.clear();
}
bool coreset_region::alloc_dci(pdcch_grant_type_t alloc_type,
uint32_t aggr_idx,
uint32_t search_space_id,
const ue_carrier_params_t* user)
bool coreset_region::alloc_pdcch(srsran_rnti_type_t rnti_type,
bool is_dl,
uint32_t aggr_idx,
uint32_t search_space_id,
const ue_carrier_params_t* user,
srsran_dci_ctx_t& dci)
{
srsran_assert(aggr_idx <= 4, "Invalid DCI aggregation level=%d", 1U << aggr_idx);
srsran_assert((user == nullptr) xor
(alloc_type == pdcch_grant_type_t::dl_data or alloc_type == pdcch_grant_type_t::ul_data),
"UE should be only provided for DL or UL data allocations");
saved_dfs_tree.clear();
alloc_record record;
record.ue = user;
record.aggr_idx = aggr_idx;
record.ss_id = search_space_id;
record.alloc_type = alloc_type;
if (record.alloc_type == pdcch_grant_type_t::ul_data) {
record.idx = pdcch_ul_list.size();
pdcch_ul_list.emplace_back();
} else {
record.idx = pdcch_dl_list.size();
pdcch_dl_list.emplace_back();
}
record.dci = &dci;
record.ue = user;
record.aggr_idx = aggr_idx;
record.ss_id = search_space_id;
record.is_dl = is_dl;
record.dci->rnti_type = rnti_type;
// Try to allocate grant. If it fails, attempt the same grant, but using a different permutation of past grant DCI
// positions
@ -96,25 +123,15 @@ bool coreset_region::alloc_dci(pdcch_grant_type_t alloc_type,
// Revert steps to initial state, before dci record allocation was attempted
dfs_tree.swap(saved_dfs_tree);
if (record.alloc_type == pdcch_grant_type_t::ul_data) {
pdcch_ul_list.pop_back();
} else {
pdcch_dl_list.pop_back();
}
return false;
}
void coreset_region::rem_last_dci()
void coreset_region::rem_last_pdcch()
{
srsran_assert(not dci_list.empty(), "%s called when no PDCCH have yet been allocated", __FUNCTION__);
// Remove DCI record
dfs_tree.pop_back();
if (dci_list.back().alloc_type == pdcch_grant_type_t::ul_data) {
pdcch_ul_list.pop_back();
} else {
pdcch_dl_list.pop_back();
}
dci_list.pop_back();
}
@ -171,13 +188,7 @@ bool coreset_region::alloc_dfs_node(const alloc_record& record, uint32_t start_d
// Allocation successful
node.total_mask |= node.current_mask;
alloc_dfs.push_back(node);
if (record.alloc_type == pdcch_grant_type_t::ul_data) {
pdcch_ul_t& pdcch_ul = pdcch_ul_list[record.idx];
pdcch_ul.dci.ctx.location = node.dci_pos;
} else {
pdcch_dl_t& pdcch_dl = pdcch_dl_list[record.idx];
pdcch_dl.dci.ctx.location = node.dci_pos;
}
record.dci->location = node.dci_pos;
return true;
}
@ -186,19 +197,324 @@ bool coreset_region::alloc_dfs_node(const alloc_record& record, uint32_t start_d
srsran::span<const uint32_t> coreset_region::get_cce_loc_table(const alloc_record& record) const
{
switch (record.alloc_type) {
case pdcch_grant_type_t::dl_data:
case pdcch_grant_type_t::ul_data:
return record.ue->cce_pos_list(record.ss_id, slot_idx, record.aggr_idx);
case pdcch_grant_type_t::rar:
switch (record.dci->rnti_type) {
case srsran_rnti_type_ra:
return rar_cce_list[slot_idx][record.aggr_idx];
case pdcch_grant_type_t::sib:
case srsran_rnti_type_si:
return common_cce_list[record.ss_id][slot_idx][record.aggr_idx];
case srsran_rnti_type_c:
case srsran_rnti_type_tc:
case srsran_rnti_type_mcs_c:
case srsran_rnti_type_sp_csi:
return record.ue->cce_pos_list(record.ss_id, slot_idx, record.aggr_idx);
default:
srsran_terminate("Invalid RNTI type=%s", srsran_rnti_type_str(record.dci->rnti_type));
break;
}
return {};
}
void coreset_region::print_allocations(fmt::memory_buffer& fmtbuf) const
{
if (not dci_list.empty()) {
fmt::format_to(fmtbuf, "CORESET#{} (#CCEs={}):\n", coreset_id, nof_cces());
}
for (const alloc_record& dci : dci_list) {
fmt::format_to(fmtbuf,
" {}-rnti=0x{:x}: ({}, {})\n",
srsran_rnti_type_str_short(dci.dci->rnti_type),
dci.dci->rnti,
dci.dci->location.ncce,
dci.dci->location.L);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bwp_pdcch_allocator::bwp_pdcch_allocator(const bwp_params_t& bwp_cfg_,
uint32_t slot_idx_,
pdcch_dl_list_t& dl_pdcchs,
pdcch_ul_list_t& ul_pdcchs) :
bwp_cfg(bwp_cfg_), pdcch_dl_list(dl_pdcchs), pdcch_ul_list(ul_pdcchs), slot_idx(slot_idx_), logger(bwp_cfg_.logger)
{
for (uint32_t cs_idx = 0; cs_idx < SRSRAN_UE_DL_NR_MAX_NOF_CORESET; ++cs_idx) {
if (bwp_cfg.cfg.pdcch.coreset_present[cs_idx]) {
uint32_t cs_id = bwp_cfg.cfg.pdcch.coreset[cs_idx].id;
coresets.emplace(cs_id, bwp_cfg, cs_id, slot_idx);
}
}
}
void bwp_pdcch_allocator::fill_dci_ctx_common(srsran_dci_ctx_t& dci,
srsran_rnti_type_t rnti_type,
uint16_t rnti,
const srsran_search_space_t& ss,
srsran_dci_format_nr_t dci_fmt,
const ue_carrier_params_t* ue)
{
// Note: Location is filled by coreset_region class.
dci.ss_type = ss.type;
dci.coreset_id = ss.coreset_id;
const srsran_coreset_t* coreset =
ue == nullptr ? &bwp_cfg.cfg.pdcch.coreset[ss.coreset_id] : &ue->phy().pdcch.coreset[ss.coreset_id];
dci.coreset_start_rb = srsran_coreset_start_rb(coreset);
dci.rnti_type = rnti_type;
dci.rnti = rnti;
dci.format = dci_fmt;
}
pdcch_dl_alloc_result bwp_pdcch_allocator::alloc_rar_pdcch(uint16_t ra_rnti, uint32_t aggr_idx)
{
srsran_assert(bwp_cfg.cfg.pdcch.ra_search_space_present, "Allocating RAR PDCCH in BWP without RA SearchSpace");
return alloc_dl_pdcch_common(
srsran_rnti_type_ra, ra_rnti, bwp_cfg.cfg.pdcch.ra_search_space.id, aggr_idx, srsran_dci_format_nr_1_0, nullptr);
}
pdcch_dl_alloc_result bwp_pdcch_allocator::alloc_si_pdcch(uint32_t ss_id, uint32_t aggr_idx)
{
return alloc_dl_pdcch_common(srsran_rnti_type_si, SRSRAN_SIRNTI, ss_id, aggr_idx, srsran_dci_format_nr_1_0, nullptr);
}
pdcch_dl_alloc_result bwp_pdcch_allocator::alloc_dl_pdcch(srsran_rnti_type_t rnti_type,
uint32_t ss_id,
uint32_t aggr_idx,
const ue_carrier_params_t& user)
{
static const srsran_dci_format_nr_t dci_fmt = srsran_dci_format_nr_1_0; // TODO: make it configurable
srsran_assert(rnti_type == srsran_rnti_type_c or rnti_type == srsran_rnti_type_tc,
"Invalid RNTI type=%s for UE-specific PDCCH",
srsran_rnti_type_str_short(rnti_type));
return alloc_dl_pdcch_common(rnti_type, user.rnti, ss_id, aggr_idx, dci_fmt, &user);
}
pdcch_dl_alloc_result bwp_pdcch_allocator::alloc_dl_pdcch_common(srsran_rnti_type_t rnti_type,
uint16_t rnti,
uint32_t ss_id,
uint32_t aggr_idx,
srsran_dci_format_nr_t dci_fmt,
const ue_carrier_params_t* user)
{
alloc_result r = check_args_valid(rnti_type, rnti, ss_id, aggr_idx, dci_fmt, user, true);
if (r != alloc_result::success) {
return {r};
}
const srsran_search_space_t& ss =
(user == nullptr)
? (rnti_type == srsran_rnti_type_ra ? bwp_cfg.cfg.pdcch.ra_search_space : *bwp_cfg.get_ss(ss_id))
: *user->get_ss(ss_id);
// Add new DL PDCCH to sched result
pdcch_dl_list.emplace_back();
bool success =
coresets[ss.coreset_id].alloc_pdcch(rnti_type, true, aggr_idx, ss_id, user, pdcch_dl_list.back().dci.ctx);
if (not success) {
// Remove failed PDCCH allocation
pdcch_dl_list.pop_back();
// Log PDCCH allocation failure
srslog::log_channel& ch = user == nullptr ? logger.warning : logger.debug;
log_pdcch_alloc_failure(ch, rnti_type, ss_id, rnti, "No available PDCCH position");
return {alloc_result::no_cch_space};
}
// PDCCH allocation was successful
pdcch_dl_t& pdcch = pdcch_dl_list.back();
// Fill DCI with semi-static config
fill_dci_from_cfg(bwp_cfg, pdcch.dci);
// Fill DCI context information
fill_dci_ctx_common(pdcch.dci.ctx, rnti_type, rnti, ss, dci_fmt, user);
// register last PDCCH coreset, in case it needs to be aborted
pending_dci = &pdcch.dci.ctx;
return {&pdcch};
}
pdcch_ul_alloc_result
bwp_pdcch_allocator::alloc_ul_pdcch(uint32_t ss_id, uint32_t aggr_idx, const ue_carrier_params_t& user)
{
static const srsran_dci_format_nr_t dci_fmt = srsran_dci_format_nr_0_0; // TODO: make it configurable
alloc_result r = check_args_valid(srsran_rnti_type_c, user.rnti, ss_id, aggr_idx, dci_fmt, &user, false);
if (r != alloc_result::success) {
return {r};
}
const srsran_search_space_t& ss = *user.get_ss(ss_id);
// Add new UL PDCCH to sched result
pdcch_ul_list.emplace_back();
bool success = coresets[ss.coreset_id].alloc_pdcch(
srsran_rnti_type_c, false, aggr_idx, ss_id, &user, pdcch_ul_list.back().dci.ctx);
if (not success) {
// Remove failed PDCCH allocation
pdcch_ul_list.pop_back();
// Log PDCCH allocation failure
log_pdcch_alloc_failure(logger.debug, srsran_rnti_type_c, ss_id, user.rnti, "No available PDCCH position");
return {alloc_result::no_cch_space};
}
// PDCCH allocation was successful
pdcch_ul_t& pdcch = pdcch_ul_list.back();
// Fill DCI with semi-static config
fill_dci_from_cfg(bwp_cfg, pdcch.dci);
// Fill DCI context information
fill_dci_ctx_common(pdcch.dci.ctx, srsran_rnti_type_c, user.rnti, ss, dci_fmt, &user);
// register last PDCCH coreset, in case it needs to be aborted
pending_dci = &pdcch.dci.ctx;
return {&pdcch};
}
void bwp_pdcch_allocator::cancel_last_pdcch()
{
srsran_assert(pending_dci != nullptr, "Trying to abort PDCCH allocation that does not exist");
uint32_t cs_id = pending_dci->coreset_id;
if (&pdcch_dl_list.back().dci.ctx == pending_dci) {
pdcch_dl_list.pop_back();
} else if (&pdcch_ul_list.back().dci.ctx == pending_dci) {
pdcch_ul_list.pop_back();
} else {
logger.error("Invalid DCI context provided to be removed");
return;
}
coresets[cs_id].rem_last_pdcch();
pending_dci = nullptr;
}
void bwp_pdcch_allocator::reset()
{
pending_dci = nullptr;
pdcch_dl_list.clear();
pdcch_ul_list.clear();
for (coreset_region& coreset : coresets) {
coreset.reset();
}
}
uint32_t bwp_pdcch_allocator::nof_allocations() const
{
uint32_t count = 0;
for (const coreset_region& coreset : coresets) {
count += coreset.nof_allocs();
}
return count;
}
uint32_t bwp_pdcch_allocator::nof_cces(uint32_t coreset_id) const
{
return coresets[coreset_id].nof_cces();
}
alloc_result bwp_pdcch_allocator::check_args_valid(srsran_rnti_type_t rnti_type,
uint16_t rnti,
uint32_t ss_id,
uint32_t aggr_idx,
srsran_dci_format_nr_t dci_fmt,
const ue_carrier_params_t* user,
bool is_dl) const
{
srsran_assert(ss_id < SRSRAN_UE_DL_NR_MAX_NOF_SEARCH_SPACE, "Invalid SearchSpace#%d", ss_id);
srsran_assert(
aggr_idx < SRSRAN_SEARCH_SPACE_NOF_AGGREGATION_LEVELS_NR, "Invalid aggregation level index=%d", aggr_idx);
// DL must be active in given slot
if (not bwp_cfg.slots[slot_idx].is_dl) {
log_pdcch_alloc_failure(logger.error, rnti_type, ss_id, rnti, "DL is disabled for slot={}", slot_idx);
return alloc_result::no_cch_space;
}
// Verify SearchSpace validity
const srsran_search_space_t* ss =
(user == nullptr)
? (rnti_type == srsran_rnti_type_ra ? &bwp_cfg.cfg.pdcch.ra_search_space : bwp_cfg.get_ss(ss_id))
: user->get_ss(ss_id);
if (ss == nullptr) {
// Couldn't find SearchSpace
log_pdcch_alloc_failure(logger.error, rnti_type, ss_id, rnti, "SearchSpace has not been configured");
return alloc_result::invalid_grant_params;
}
if (ss->nof_candidates[aggr_idx] == 0) {
// No valid DCI position candidates given aggregation level
log_pdcch_alloc_failure(
logger.warning, rnti_type, ss_id, rnti, "Chosen SearchSpace doesn't have CCE candidates for L={}", aggr_idx);
return alloc_result::invalid_grant_params;
}
if (not is_rnti_type_valid_in_search_space(rnti_type, ss->type)) {
// RNTI type doesnt match SearchSpace type
log_pdcch_alloc_failure(logger.warning,
rnti_type,
ss_id,
rnti,
"Chosen SearchSpace type \"{}\" does not match rnti_type.",
srsran_ss_type_str(ss->type));
return alloc_result::invalid_grant_params;
}
auto dci_fmt_equal = [dci_fmt](srsran_dci_format_nr_t f) { return f == dci_fmt; };
if (std::none_of(&ss->formats[0], &ss->formats[ss->nof_formats], dci_fmt_equal)) {
log_pdcch_alloc_failure(logger.warning,
rnti_type,
ss_id,
rnti,
"Chosen SearchSpace does not support chosen dci format={}",
srsran_dci_format_nr_string(dci_fmt));
return alloc_result::invalid_grant_params;
}
if (is_dl) {
if (pdcch_dl_list.full()) {
log_pdcch_alloc_failure(
logger.warning, rnti_type, ss_id, rnti, "Maximum number of allocations={} reached", pdcch_dl_list.size());
return alloc_result::no_cch_space;
}
} else if (pdcch_ul_list.full()) {
log_pdcch_alloc_failure(
logger.warning, rnti_type, ss_id, rnti, "Maximum number of UL allocations={} reached", pdcch_ul_list.size());
return alloc_result::no_cch_space;
}
if (user != nullptr) {
if (user->active_bwp().bwp_id != bwp_cfg.bwp_id) {
log_pdcch_alloc_failure(logger.warning,
rnti_type,
ss_id,
rnti,
"Trying to allocate BWP#{} which is inactive for the UE.",
user->active_bwp().bwp_id);
return alloc_result::no_rnti_opportunity;
}
}
srsran_sanity_check(pdcch_dl_list.size() + pdcch_ul_list.size() == nof_allocations(), "Invalid PDCCH state");
return alloc_result::success;
}
void bwp_pdcch_allocator::print_allocations(fmt::memory_buffer& fmtbuf) const
{
fmt::format_to(
fmtbuf, "PDCCH allocations: ({} active coresets):{}\n", coresets.size(), nof_allocations() == 0 ? " None" : "");
for (const coreset_region& cs : coresets) {
cs.print_allocations(fmtbuf);
}
}
std::string bwp_pdcch_allocator::print_allocations() const
{
fmt::memory_buffer fmtbuf;
print_allocations(fmtbuf);
return fmt::to_string(fmtbuf);
}
} // namespace sched_nr_impl
} // namespace srsenb

@ -0,0 +1,362 @@
/**
*
* \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 "srsgnb/hdr/stack/mac/sched_nr_sch.h"
#include "srsran/common/string_helpers.h"
namespace srsenb {
namespace sched_nr_impl {
template <typename... Args>
void log_alloc_failure(srslog::log_channel& log_ch, const char* cause_fmt, Args&&... args)
{
if (not log_ch.enabled()) {
return;
}
// Log allocation failure
fmt::memory_buffer fmtbuf;
fmt::format_to(fmtbuf, "SCHED: Failure to allocate PDSCH. Cause: ");
fmt::format_to(fmtbuf, cause_fmt, std::forward<Args>(args)...);
log_ch("%s", srsran::to_c_str(fmtbuf));
}
pdsch_allocator::pdsch_allocator(const bwp_params_t& cfg_, uint32_t slot_index, pdsch_list_t& pdsch_lst) :
bwp_cfg(cfg_),
slot_idx(slot_index),
pdschs(pdsch_lst),
dl_prbs(bwp_cfg.cfg.rb_width, bwp_cfg.cfg.start_rb, bwp_cfg.cfg.pdsch.rbg_size_cfg_1)
{}
void pdsch_allocator::reset()
{
pdschs.clear();
dl_prbs.reset();
}
alloc_result pdsch_allocator::is_grant_valid_common(srsran_search_space_type_t ss_type,
srsran_dci_format_nr_t dci_fmt,
uint32_t coreset_id,
const prb_grant& grant) const
{
// DL must be active in given slot
if (not bwp_cfg.slots[slot_idx].is_dl) {
log_alloc_failure(bwp_cfg.logger.error, "DL is disabled for slot={}", slot_idx);
return alloc_result::no_sch_space;
}
// No space in Scheduler PDSCH output list
if (pdschs.full()) {
log_alloc_failure(bwp_cfg.logger.warning, "Maximum number of PDSCHs={} reached.", pdschs.size());
return alloc_result::no_sch_space;
}
// TS 38.214, 5.1.2.2 - "The UE shall assume that when the scheduling grant is received with DCI format 1_0, then
// downlink resource allocation type 1 is used."
if (dci_fmt == srsran_dci_format_nr_1_0 and not grant.is_alloc_type1()) {
log_alloc_failure(bwp_cfg.logger.warning, "DL Resource Allocation type 1 must be used in case of DCI format 1_0.");
return alloc_result::invalid_grant_params;
}
// TS 38.214 - 5.1.2.2 - For DCI format 1_0 and Common Search Space, the list of available PRBs is limited by the
// rb_start and bandwidth of the coreset
if (dci_fmt == srsran_dci_format_nr_1_0 and SRSRAN_SEARCH_SPACE_IS_COMMON(ss_type)) {
// Grant PRBs do not collide with CORESET PRB limits (in case of common SearchSpace)
if (bwp_cfg.dci_fmt_1_0_excluded_prbs(coreset_id).collides(grant)) {
log_alloc_failure(
bwp_cfg.logger.debug, "Provided PRB grant={:x} falls outside common CORESET PRB boundaries.", grant);
return alloc_result::sch_collision;
}
}
// Grant PRBs do not collide with previous PDSCH allocations
if (dl_prbs.collides(grant)) {
log_alloc_failure(
bwp_cfg.logger.debug, "Provided PRB grant={:x} collides with allocations previously made.", grant);
return alloc_result::sch_collision;
}
return alloc_result::success;
}
alloc_result pdsch_allocator::is_si_grant_valid(uint32_t ss_id, const prb_grant& grant) const
{
// Verify SearchSpace validity
const srsran_search_space_t* ss = bwp_cfg.get_ss(ss_id);
if (ss == nullptr) {
// Couldn't find SearchSpace
log_alloc_failure(bwp_cfg.logger.error, "SearchSpace has not been configured.");
return alloc_result::invalid_grant_params;
}
return is_grant_valid_common(ss->type, srsran_dci_format_nr_1_0, ss->coreset_id, grant);
}
alloc_result pdsch_allocator::is_rar_grant_valid(const prb_grant& grant) const
{
srsran_sanity_check(bwp_cfg.cfg.pdcch.ra_search_space_present,
"Attempting RAR allocation in BWP with no raSearchSpace");
return is_grant_valid_common(bwp_cfg.cfg.pdcch.ra_search_space.type,
srsran_dci_format_nr_1_0,
bwp_cfg.cfg.pdcch.ra_search_space.coreset_id,
grant);
}
alloc_result pdsch_allocator::is_ue_grant_valid(const ue_carrier_params_t& ue,
uint32_t ss_id,
srsran_dci_format_nr_t dci_fmt,
const prb_grant& grant) const
{
const srsran_search_space_t* ss = ue.get_ss(ss_id);
if (ss == nullptr) {
// Couldn't find SearchSpace
log_alloc_failure(bwp_cfg.logger.error, "rnti=0x%x,SearchSpaceId={} has not been configured.", ue.rnti, ss_id);
return alloc_result::invalid_grant_params;
}
alloc_result ret = is_grant_valid_common(ss->type, dci_fmt, ss->coreset_id, grant);
if (ret != alloc_result::success) {
return ret;
}
// TS 38.214, 5.1.2.2 - "the UE shall use the downlink frequency resource allocation type as defined by the higher
// layer parameter resourceAllocation"
if (ue.phy().pdsch.alloc != srsran_resource_alloc_dynamic) {
if ((ue.phy().pdsch.alloc == srsran_resource_alloc_type0) != grant.is_alloc_type0()) {
log_alloc_failure(bwp_cfg.logger.warning,
"UE rnti=0x{:x} PDSCH RA configuration type {} doesn't match grant type",
ue.rnti,
grant.is_alloc_type0() ? 0 : 1);
return alloc_result::invalid_grant_params;
}
}
return alloc_result::success;
}
pdsch_alloc_result pdsch_allocator::alloc_si_pdsch(uint32_t ss_id, const prb_grant& grant, srsran_dci_dl_nr_t& dci)
{
alloc_result code = is_si_grant_valid(ss_id, grant);
if (code != alloc_result::success) {
return code;
}
return {&alloc_si_pdsch_unchecked(ss_id, grant, dci)};
}
pdsch_t& pdsch_allocator::alloc_si_pdsch_unchecked(uint32_t ss_id, const prb_grant& grant, srsran_dci_dl_nr_t& dci)
{
// Verify SearchSpace validity
const srsran_search_space_t* ss = bwp_cfg.get_ss(ss_id);
srsran_sanity_check(ss != nullptr, "SearchSpace has not been configured");
return alloc_pdsch_unchecked(ss->coreset_id, ss->type, srsran_dci_format_nr_1_0, grant, dci);
}
pdsch_alloc_result pdsch_allocator::alloc_rar_pdsch(const prb_grant& grant, srsran_dci_dl_nr_t& dci)
{
alloc_result code = is_rar_grant_valid(grant);
if (code != alloc_result::success) {
return code;
}
return {&alloc_rar_pdsch_unchecked(grant, dci)};
}
pdsch_t& pdsch_allocator::alloc_rar_pdsch_unchecked(const prb_grant& grant, srsran_dci_dl_nr_t& dci)
{
// TS 38.213, 8.2 - "In response to a PRACH transmission, a UE attempts to detect a DCI format 1_0"
const static srsran_dci_format_nr_t dci_fmt = srsran_dci_format_nr_1_0;
return alloc_pdsch_unchecked(
bwp_cfg.cfg.pdcch.ra_search_space.coreset_id, bwp_cfg.cfg.pdcch.ra_search_space.type, dci_fmt, grant, dci);
}
pdsch_alloc_result pdsch_allocator::alloc_ue_pdsch(uint32_t ss_id,
srsran_dci_format_nr_t dci_fmt,
const prb_grant& grant,
const ue_carrier_params_t& ue,
srsran_dci_dl_nr_t& dci)
{
alloc_result code = is_ue_grant_valid(ue, ss_id, dci_fmt, grant);
if (code != alloc_result::success) {
return code;
}
return {&alloc_ue_pdsch_unchecked(ss_id, dci_fmt, grant, ue, dci)};
}
pdsch_t& pdsch_allocator::alloc_ue_pdsch_unchecked(uint32_t ss_id,
srsran_dci_format_nr_t dci_fmt,
const prb_grant& grant,
const ue_carrier_params_t& ue,
srsran_dci_dl_nr_t& dci)
{
const srsran_search_space_t* ss = ue.get_ss(ss_id);
srsran_sanity_check(ss != nullptr, "SearchSpace has not been configured");
return alloc_pdsch_unchecked(ss->coreset_id, ss->type, dci_fmt, grant, dci);
}
pdsch_t& pdsch_allocator::alloc_pdsch_unchecked(uint32_t coreset_id,
srsran_search_space_type_t ss_type,
srsran_dci_format_nr_t dci_fmt,
const prb_grant& grant,
srsran_dci_dl_nr_t& out_dci)
{
// Create new PDSCH entry in output PDSCH list
pdschs.emplace_back();
pdsch_t& pdsch = pdschs.back();
// Register allocated PRBs in accumulated bitmap
dl_prbs |= grant;
// Fill DCI with PDSCH freq/time allocation information
out_dci.time_domain_assigment = 0;
if (grant.is_alloc_type0()) {
out_dci.freq_domain_assigment = grant.rbgs().to_uint64();
} else {
uint32_t rb_start = grant.prbs().start(), nof_prb = bwp_cfg.nof_prb();
if (SRSRAN_SEARCH_SPACE_IS_COMMON(ss_type)) {
prb_interval lims = bwp_cfg.coreset_prb_range(coreset_id);
if (dci_fmt == srsran_dci_format_nr_1_0) {
srsran_sanity_check(rb_start >= lims.start(), "Invalid PRB grant");
rb_start -= lims.start();
}
if (coreset_id == 0) {
nof_prb = lims.length();
}
}
srsran_sanity_check(rb_start + grant.prbs().length() <= nof_prb, "Invalid PRB grant");
out_dci.freq_domain_assigment = srsran_ra_nr_type1_riv(nof_prb, rb_start, grant.prbs().length());
}
return pdsch;
}
void pdsch_allocator::cancel_last_pdsch()
{
srsran_assert(not pdschs.empty(), "Trying to abort PDSCH allocation that does not exist");
pdschs.pop_back();
// TODO: clear bitmap allocated RBs
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename... Args>
void log_pusch_alloc_failure(srslog::log_channel& log_ch, const char* cause_fmt, Args&&... args)
{
if (not log_ch.enabled()) {
return;
}
// Log allocation failure
fmt::memory_buffer fmtbuf;
fmt::format_to(fmtbuf, "SCHED: Failure to allocate PUSCH. Cause: ");
fmt::format_to(fmtbuf, cause_fmt, std::forward<Args>(args)...);
log_ch("%s", srsran::to_c_str(fmtbuf));
}
pusch_allocator::pusch_allocator(const bwp_params_t& cfg_, uint32_t sl_index, pusch_list_t& pusch_lst) :
bwp_cfg(cfg_),
slot_idx(sl_index),
puschs(pusch_lst),
ul_prbs(bwp_cfg.cfg.rb_width, bwp_cfg.cfg.start_rb, bwp_cfg.cfg.pdsch.rbg_size_cfg_1)
{}
void pusch_allocator::reset()
{
puschs.clear();
ul_prbs.reset();
}
alloc_result pusch_allocator::has_grant_space(uint32_t nof_grants, bool verbose) const
{
// UL must be active in given slot
if (not bwp_cfg.slots[slot_idx].is_ul) {
if (verbose) {
log_pusch_alloc_failure(bwp_cfg.logger.error, "UL is disabled for slot={}", slot_idx);
}
return alloc_result::no_sch_space;
}
// No space in Scheduler PDSCH output list
if (puschs.size() + nof_grants > puschs.capacity()) {
if (verbose) {
log_pusch_alloc_failure(bwp_cfg.logger.warning, "Maximum number of PUSCHs={} reached.", puschs.capacity());
}
return alloc_result::no_sch_space;
}
return alloc_result::success;
}
alloc_result
pusch_allocator::is_grant_valid(srsran_search_space_type_t ss_type, const prb_grant& grant, bool verbose) const
{
alloc_result ret = has_grant_space(1, verbose);
if (ret != alloc_result::success) {
return ret;
}
if (SRSRAN_SEARCH_SPACE_IS_COMMON(ss_type)) {
// In case of common SearchSpaces, the PRBs must be contiguous
if (grant.is_alloc_type0()) {
log_pusch_alloc_failure(bwp_cfg.logger.warning, "AllocType0 not allowed in common SearchSpace.");
return alloc_result::invalid_grant_params;
}
}
// Grant PRBs do not collide with previous PDSCH allocations
if (ul_prbs.collides(grant)) {
if (verbose) {
log_pusch_alloc_failure(bwp_cfg.logger.debug, "SCHED: Provided UL PRB mask collides with previous allocations.");
}
return alloc_result::sch_collision;
}
return alloc_result::success;
}
pusch_alloc_result
pusch_allocator::alloc_pusch(const srsran_search_space_type_t ss_type, const prb_grant& grant, srsran_dci_ul_nr_t& dci)
{
alloc_result code = is_grant_valid(ss_type, grant);
if (code != alloc_result::success) {
return code;
}
return {&alloc_pusch_unchecked(grant, dci)};
}
pusch_t& pusch_allocator::alloc_pusch_unchecked(const prb_grant& grant, srsran_dci_ul_nr_t& out_dci)
{
// Create new PUSCH entry in output PUSCH list
puschs.emplace_back();
pusch_t& pusch = puschs.back();
// Register allocated PRBs in accumulated bitmap
ul_prbs |= grant;
// Fill DCI with PUSCH freq/time allocation information
out_dci.time_domain_assigment = 0;
if (grant.is_alloc_type0()) {
out_dci.freq_domain_assigment = grant.rbgs().to_uint64();
} else {
uint32_t nof_prb = bwp_cfg.nof_prb();
out_dci.freq_domain_assigment = srsran_ra_nr_type1_riv(nof_prb, grant.prbs().start(), grant.prbs().length());
}
return pusch;
}
void pusch_allocator::cancel_last_pusch()
{
srsran_assert(not puschs.empty(), "Trying to abort PUSCH allocation that does not exist");
puschs.pop_back();
}
} // namespace sched_nr_impl
} // namespace srsenb

@ -111,32 +111,6 @@ void sched_dl_signalling(bwp_slot_allocator& bwp_alloc)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool fill_dci_sib(prb_interval interv,
uint32_t sib_id,
uint32_t si_ntx,
const bwp_params_t& bwp_cfg,
srsran_dci_dl_nr_t& dci)
{
dci.mcs = 5;
dci.ctx.format = srsran_dci_format_nr_1_0;
dci.ctx.ss_type = srsran_search_space_type_common_0;
dci.ctx.rnti_type = srsran_rnti_type_si;
dci.ctx.rnti = SRSRAN_SIRNTI;
dci.ctx.coreset_id = 0;
dci.ctx.coreset_start_rb = bwp_cfg.cfg.pdcch.coreset[0].offset_rb;
dci.coreset0_bw = srsran_coreset_get_bw(&bwp_cfg.cfg.pdcch.coreset[0]);
dci.freq_domain_assigment =
srsran_ra_nr_type1_riv(srsran_coreset_get_bw(&bwp_cfg.cfg.pdcch.coreset[0]), interv.start(), interv.length());
dci.time_domain_assigment = 0;
dci.tpc = 1;
dci.bwp_id = bwp_cfg.bwp_id;
dci.cc_id = bwp_cfg.cc;
dci.rv = 0;
dci.sii = sib_id == 0 ? 0 : 1;
return true;
}
si_sched::si_sched(const bwp_params_t& bwp_cfg_) :
bwp_cfg(&bwp_cfg_), logger(srslog::fetch_basic_logger(bwp_cfg_.sched_cfg.logger_name))
{
@ -158,9 +132,10 @@ void si_sched::run_slot(bwp_slot_allocator& bwp_alloc)
// TODO: provide proper config
return;
}
const uint32_t si_aggr_level = 2;
slot_point sl_pdcch = bwp_alloc.get_pdcch_tti();
const prb_bitmap& prbs = bwp_alloc.res_grid()[sl_pdcch].dl_prbs.prbs();
const uint32_t si_aggr_level = 2;
const uint32_t ss_id = 0;
slot_point sl_pdcch = bwp_alloc.get_pdcch_tti();
prb_bitmap prbs = bwp_alloc.res_grid()[sl_pdcch].pdschs.occupied_prbs(ss_id, srsran_dci_format_nr_1_0);
// Update SI windows
uint32_t N = bwp_cfg->slots.size();

@ -24,6 +24,13 @@
namespace srsenb {
namespace sched_nr_impl {
/**
* @brief Algorithm to select next UE to allocate in a time-domain RR fashion
* @param ue_db map of "slot_ue"
* @param rr_count starting index to select next UE
* @param p callable with signature "bool(slot_ue&)" that returns true if UE allocation was successful
* @return true if a UE was allocated
*/
template <typename Predicate>
bool round_robin_apply(slot_ue_map_t& ue_db, uint32_t rr_count, Predicate p)
{
@ -34,6 +41,7 @@ bool round_robin_apply(slot_ue_map_t& ue_db, uint32_t rr_count, Predicate p)
std::advance(it, (rr_count % ue_db.size()));
for (uint32_t count = 0; count < ue_db.size(); ++count, ++it) {
if (it == ue_db.end()) {
// wrap-around
it = ue_db.begin();
}
if (p(it->second)) {
@ -46,28 +54,35 @@ bool round_robin_apply(slot_ue_map_t& ue_db, uint32_t rr_count, Predicate p)
void sched_nr_time_rr::sched_dl_users(slot_ue_map_t& ue_db, bwp_slot_allocator& slot_alloc)
{
// Start with retxs
if (round_robin_apply(ue_db, slot_alloc.get_pdcch_tti().to_uint(), [&slot_alloc](slot_ue& ue) {
if (ue.h_dl != nullptr and ue.h_dl->has_pending_retx(slot_alloc.get_tti_rx())) {
alloc_result res = slot_alloc.alloc_pdsch(ue, ue.h_dl->prbs());
if (res == alloc_result::success) {
return true;
}
}
return false;
})) {
auto retx_ue_function = [&slot_alloc](slot_ue& ue) {
if (ue.h_dl != nullptr and ue.h_dl->has_pending_retx(slot_alloc.get_tti_rx())) {
alloc_result res = slot_alloc.alloc_pdsch(ue, ue->find_ss_id(srsran_dci_format_nr_1_0), ue.h_dl->prbs());
if (res == alloc_result::success) {
return true;
}
}
return false;
};
if (round_robin_apply(ue_db, slot_alloc.get_pdcch_tti().to_uint(), retx_ue_function)) {
return;
}
// Move on to new txs
round_robin_apply(ue_db, slot_alloc.get_pdcch_tti().to_uint(), [&slot_alloc](slot_ue& ue) {
auto newtx_ue_function = [&slot_alloc](slot_ue& ue) {
if (ue.dl_bytes > 0 and ue.h_dl != nullptr and ue.h_dl->empty()) {
alloc_result res = slot_alloc.alloc_pdsch(ue, prb_interval{0, slot_alloc.cfg.cfg.rb_width});
int ss_id = ue->find_ss_id(srsran_dci_format_nr_1_0);
if (ss_id < 0) {
return false;
}
prb_grant prbs = find_optimal_dl_grant(slot_alloc, ue, ss_id);
alloc_result res = slot_alloc.alloc_pdsch(ue, ss_id, prbs);
if (res == alloc_result::success) {
return true;
}
}
return false;
});
};
round_robin_apply(ue_db, slot_alloc.get_pdcch_tti().to_uint(), newtx_ue_function);
}
void sched_nr_time_rr::sched_ul_users(slot_ue_map_t& ue_db, bwp_slot_allocator& slot_alloc)

@ -20,7 +20,7 @@
*/
#include "srsgnb/hdr/stack/mac/sched_nr_ue.h"
#include "srsgnb/hdr/stack/mac/sched_nr_pdcch.h"
#include "srsgnb/hdr/stack/mac/sched_nr_helpers.h"
#include "srsran/common/string_helpers.h"
#include "srsran/mac/mac_sch_pdu_nr.h"
@ -62,8 +62,7 @@ void ue_buffer_manager::pdu_builder::alloc_subpdus(uint32_t rem_bytes, sched_nr_
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
slot_ue::slot_ue(ue_carrier& ue_, slot_point slot_tx_, uint32_t dl_pending_bytes, uint32_t ul_pending_bytes) :
ue(&ue_), pdcch_slot(slot_tx_)
slot_ue::slot_ue(ue_carrier& ue_, slot_point slot_tx_) : ue(&ue_), pdcch_slot(slot_tx_)
{
const uint32_t k0 = 0;
pdsch_slot = pdcch_slot + k0;
@ -74,17 +73,17 @@ slot_ue::slot_ue(ue_carrier& ue_, slot_point slot_tx_, uint32_t dl_pending_bytes
const srsran_duplex_config_nr_t& tdd_cfg = ue->cell_params.cfg.duplex;
dl_active = srsran_duplex_nr_is_dl(&tdd_cfg, 0, pdsch_slot.slot_idx());
dl_active = ue->cell_params.bwps[0].slots[pdsch_slot.slot_idx()].is_dl;
if (dl_active) {
dl_bytes = dl_pending_bytes;
dl_bytes = ue->common_ctxt.pending_dl_bytes;
h_dl = ue->harq_ent.find_pending_dl_retx();
if (h_dl == nullptr) {
h_dl = ue->harq_ent.find_empty_dl_harq();
}
}
ul_active = srsran_duplex_nr_is_ul(&tdd_cfg, 0, pusch_slot.slot_idx());
ul_active = ue->cell_params.bwps[0].slots[pusch_slot.slot_idx()].is_ul;
if (ul_active) {
ul_bytes = ul_pending_bytes;
ul_bytes = ue->common_ctxt.pending_ul_bytes;
h_ul = ue->harq_ent.find_pending_ul_retx();
if (h_ul == nullptr) {
h_ul = ue->harq_ent.find_empty_ul_harq();
@ -97,6 +96,7 @@ slot_ue::slot_ue(ue_carrier& ue_, slot_point slot_tx_, uint32_t dl_pending_bytes
ue_carrier::ue_carrier(uint16_t rnti_,
const ue_cfg_t& uecfg_,
const cell_params_t& cell_params_,
const ue_context_common& ctxt,
const ue_buffer_manager::pdu_builder& pdu_builder_) :
rnti(rnti_),
cc(cell_params_.cc),
@ -104,6 +104,7 @@ ue_carrier::ue_carrier(uint16_t rnti_,
bwp_cfg(rnti_, cell_params_.bwps[0], uecfg_),
cell_params(cell_params_),
pdu_builder(pdu_builder_),
common_ctxt(ctxt),
harq_ent(rnti_, cell_params_.nof_prb(), SCHED_NR_MAX_HARQ, cell_params_.bwps[0].logger)
{}
@ -151,8 +152,11 @@ void ue::set_cfg(const ue_cfg_t& cfg)
for (auto& ue_cc_cfg : cfg.carriers) {
if (ue_cc_cfg.active) {
if (carriers[ue_cc_cfg.cc] == nullptr) {
carriers[ue_cc_cfg.cc].reset(new ue_carrier(
rnti, ue_cfg, sched_cfg.cells[ue_cc_cfg.cc], ue_buffer_manager::pdu_builder{ue_cc_cfg.cc, buffers}));
carriers[ue_cc_cfg.cc].reset(new ue_carrier(rnti,
ue_cfg,
sched_cfg.cells[ue_cc_cfg.cc],
common_ctxt,
ue_buffer_manager::pdu_builder{ue_cc_cfg.cc, buffers}));
} else {
carriers[ue_cc_cfg.cc]->set_cfg(ue_cfg);
}
@ -184,7 +188,7 @@ void ue::rlc_buffer_state(uint32_t lcid, uint32_t newtx, uint32_t priotx)
void ue::new_slot(slot_point pdcch_slot)
{
last_pdcch_slot = pdcch_slot;
last_tx_slot = pdcch_slot;
for (std::unique_ptr<ue_carrier>& cc : carriers) {
if (cc != nullptr) {
@ -194,18 +198,18 @@ void ue::new_slot(slot_point pdcch_slot)
// Compute pending DL/UL bytes for {rnti, pdcch_slot}
if (sched_cfg.sched_cfg.auto_refill_buffer) {
dl_pending_bytes = 1000000;
ul_pending_bytes = 1000000;
common_ctxt.pending_dl_bytes = 1000000;
common_ctxt.pending_ul_bytes = 1000000;
} else {
dl_pending_bytes = buffers.get_dl_tx_total();
ul_pending_bytes = buffers.get_bsr();
common_ctxt.pending_dl_bytes = buffers.get_dl_tx_total();
common_ctxt.pending_ul_bytes = buffers.get_bsr();
for (auto& ue_cc_cfg : ue_cfg.carriers) {
auto& cc = carriers[ue_cc_cfg.cc];
if (cc != nullptr) {
// Discount UL HARQ pending bytes to BSR
for (uint32_t pid = 0; pid < cc->harq_ent.nof_ul_harqs(); ++pid) {
if (not cc->harq_ent.ul_harq(pid).empty()) {
ul_pending_bytes -= cc->harq_ent.ul_harq(pid).tbs();
common_ctxt.pending_ul_bytes -= std::min(cc->harq_ent.ul_harq(pid).tbs(), common_ctxt.pending_ul_bytes);
if (last_sr_slot.valid() and cc->harq_ent.ul_harq(pid).harq_slot_tx() > last_sr_slot) {
last_sr_slot.clear();
}
@ -213,10 +217,9 @@ void ue::new_slot(slot_point pdcch_slot)
}
}
}
ul_pending_bytes = std::max(0, ul_pending_bytes);
if (ul_pending_bytes == 0 and last_sr_slot.valid()) {
if (common_ctxt.pending_ul_bytes == 0 and last_sr_slot.valid()) {
// If unanswered SR is pending
ul_pending_bytes = 512;
common_ctxt.pending_ul_bytes = 512;
}
}
}
@ -224,7 +227,7 @@ void ue::new_slot(slot_point pdcch_slot)
slot_ue ue::make_slot_ue(slot_point pdcch_slot, uint32_t cc)
{
srsran_assert(carriers[cc] != nullptr, "make_slot_ue() called for inexistent rnti=0x%x,cc=%d", rnti, cc);
return slot_ue(*carriers[cc], pdcch_slot, dl_pending_bytes, ul_pending_bytes);
return slot_ue(*carriers[cc], pdcch_slot);
}
} // namespace sched_nr_impl

@ -18,6 +18,8 @@
# and at http://www.gnu.org/licenses/.
#
set_directory_properties(PROPERTIES LABELS "sched;nr")
add_library(sched_nr_test_suite sched_nr_common_test.cc sched_nr_ue_ded_test_suite.cc sched_nr_sim_ue.cc)
target_link_libraries(sched_nr_test_suite srsgnb_mac srsran_common)
@ -38,6 +40,14 @@ target_link_libraries(sched_nr_prb_test
${Boost_LIBRARIES})
add_nr_test(sched_nr_prb_test sched_nr_prb_test)
add_executable(sched_nr_pdcch_test sched_nr_pdcch_test.cc)
target_link_libraries(sched_nr_pdcch_test srsgnb_mac sched_nr_test_suite srsran_common)
add_nr_test(sched_nr_pdcch_test sched_nr_pdcch_test)
add_executable(sched_nr_sch_test sched_nr_sch_test.cc)
target_link_libraries(sched_nr_sch_test srsgnb_mac sched_nr_test_suite srsran_common)
add_nr_test(sched_nr_sch_test sched_nr_sch_test)
add_executable(sched_nr_rar_test sched_nr_rar_test.cc)
target_link_libraries(sched_nr_rar_test srsgnb_mac sched_nr_test_suite srsran_common)
add_nr_test(sched_nr_rar_test sched_nr_rar_test)

@ -39,6 +39,19 @@ inline srsran_coreset_t get_default_coreset0(uint32_t nof_prb)
return coreset;
}
inline srsran_search_space_t get_default_search_space0()
{
srsran_search_space_t ss{};
ss.coreset_id = 0;
ss.nof_formats = 1;
ss.formats[0] = srsran_dci_format_nr_1_0;
ss.type = srsran_search_space_type_common_0;
ss.id = 0;
ss.nof_candidates[2] = 1;
ss.duration = 1;
return ss;
}
inline sched_nr_interface::cell_cfg_t get_default_cell_cfg(
const srsran::phy_cfg_nr_t& phy_cfg = srsran::phy_cfg_nr_default_t{srsran::phy_cfg_nr_default_t::reference_cfg_t{}})
{
@ -117,6 +130,64 @@ inline sched_nr_interface::ue_cfg_t get_default_ue_cfg(
return uecfg;
}
inline sched_nr_interface::cell_cfg_t get_default_sa_cell_cfg_common()
{
srsran::phy_cfg_nr_default_t::reference_cfg_t ref;
ref.duplex = srsran::phy_cfg_nr_default_t::reference_cfg_t::R_DUPLEX_FDD;
sched_nr_interface::cell_cfg_t cell_cfg = get_default_cell_cfg(srsran::phy_cfg_nr_default_t{ref});
cell_cfg.bwps[0].pdcch.coreset_present[0] = true;
cell_cfg.bwps[0].pdcch.coreset[0] = get_default_coreset0(52);
cell_cfg.bwps[0].pdcch.coreset[0].offset_rb = 1;
cell_cfg.bwps[0].pdcch.search_space_present[0] = true;
cell_cfg.bwps[0].pdcch.search_space[0] = get_default_search_space0();
cell_cfg.bwps[0].pdcch.coreset_present[1] = false;
cell_cfg.bwps[0].pdcch.search_space[1].coreset_id = 0;
cell_cfg.bwps[0].pdcch.search_space[1].type = srsran_search_space_type_common_1;
cell_cfg.bwps[0].pdcch.search_space[1].nof_candidates[2] = 1;
cell_cfg.bwps[0].pdcch.search_space[1].nof_formats = 2;
cell_cfg.bwps[0].pdcch.search_space[1].formats[0] = srsran_dci_format_nr_1_0;
cell_cfg.bwps[0].pdcch.search_space[1].formats[1] = srsran_dci_format_nr_0_0;
cell_cfg.bwps[0].pdcch.ra_search_space = cell_cfg.bwps[0].pdcch.search_space[1];
return cell_cfg;
}
// Generate default UE-dedicated CORESET config
inline srsran_coreset_t get_default_ue_specific_coreset(uint32_t id, uint32_t pci)
{
srsran_coreset_t coreset = {};
coreset.id = id;
coreset.mapping_type = srsran_coreset_mapping_type_non_interleaved;
coreset.duration = 1;
for (uint32_t i = 0; i < 8; ++i) {
coreset.freq_resources[i] = true;
}
coreset.dmrs_scrambling_id_present = false;
coreset.precoder_granularity = srsran_coreset_precoder_granularity_reg_bundle;
coreset.interleaver_size = srsran_coreset_bundle_size_n2;
coreset.reg_bundle_size = srsran_coreset_bundle_size_n6;
coreset.shift_index = pci;
coreset.offset_rb = 0;
return coreset;
}
inline srsran_search_space_t get_default_ue_specific_search_space(uint32_t id, uint32_t coreset_id)
{
srsran_search_space_t ss = {};
ss.id = id;
ss.coreset_id = coreset_id;
ss.duration = 1;
ss.type = srsran_search_space_type_ue;
ss.nof_formats = 2;
ss.formats[0] = srsran_dci_format_nr_1_0;
ss.formats[1] = srsran_dci_format_nr_0_0;
ss.nof_candidates[0] = 2;
ss.nof_candidates[1] = 2;
ss.nof_candidates[2] = 2;
ss.nof_candidates[3] = 1;
ss.nof_candidates[4] = 0;
return ss;
}
} // namespace srsenb
#endif // SRSRAN_SCHED_NR_CFG_GENERATORS_H

@ -21,29 +21,96 @@
#include "sched_nr_common_test.h"
#include "srsgnb/hdr/stack/mac/sched_nr_cfg.h"
#include "srsgnb/hdr/stack/mac/sched_nr_helpers.h"
#include "srsran/support/srsran_test.h"
namespace srsenb {
using namespace sched_nr_impl;
void test_dci_ctx_consistency(const srsran_pdcch_cfg_nr_t& pdcch_cfg, const srsran_dci_ctx_t& dci_ctx)
{
TESTASSERT(dci_ctx.coreset_id < SRSRAN_UE_DL_NR_MAX_NOF_CORESET);
TESTASSERT(pdcch_cfg.coreset_present[dci_ctx.coreset_id]);
const srsran_coreset_t& coreset = pdcch_cfg.coreset[dci_ctx.coreset_id];
// DCI location
TESTASSERT(dci_ctx.location.L < SRSRAN_SEARCH_SPACE_NOF_AGGREGATION_LEVELS_NR);
TESTASSERT(dci_ctx.location.ncce + (1U << dci_ctx.location.L) <= coreset_nof_cces(coreset));
// RNTI type, SearchSpace type, DCI format
TESTASSERT(sched_nr_impl::is_rnti_type_valid_in_search_space(dci_ctx.rnti_type, dci_ctx.ss_type));
switch (dci_ctx.rnti_type) {
case srsran_rnti_type_si:
TESTASSERT_EQ(srsran_dci_format_nr_1_0, dci_ctx.format);
TESTASSERT_EQ(srsran_search_space_type_common_0, dci_ctx.ss_type);
TESTASSERT_EQ(SRSRAN_SIRNTI, dci_ctx.rnti);
break;
case srsran_rnti_type_ra:
TESTASSERT_EQ(dci_ctx.format, srsran_dci_format_nr_1_0);
TESTASSERT_EQ(dci_ctx.ss_type, srsran_search_space_type_common_1);
TESTASSERT(pdcch_cfg.ra_search_space_present);
TESTASSERT_EQ(pdcch_cfg.ra_search_space.coreset_id, dci_ctx.coreset_id);
TESTASSERT(pdcch_cfg.ra_search_space.nof_candidates[dci_ctx.location.L] > 0);
break;
case srsran_rnti_type_tc:
TESTASSERT_EQ(srsran_dci_format_nr_1_0, dci_ctx.format);
TESTASSERT_EQ(srsran_search_space_type_common_1, dci_ctx.ss_type);
break;
case srsran_rnti_type_c:
TESTASSERT(dci_ctx.format == srsran_dci_format_nr_1_0 or dci_ctx.format == srsran_dci_format_nr_1_1 or
dci_ctx.format == srsran_dci_format_nr_0_0 or dci_ctx.format == srsran_dci_format_nr_0_1);
break;
default:
srsran_terminate("rnti type=%d not supported", dci_ctx.rnti_type);
}
// CORESET position
TESTASSERT_EQ(srsran_coreset_start_rb(&coreset), dci_ctx.coreset_start_rb);
}
void test_pdcch_collisions(const srsran_pdcch_cfg_nr_t& pdcch_cfg,
srsran::const_span<pdcch_dl_t> dl_pdcchs,
srsran::const_span<pdcch_ul_t> ul_pdcchs)
{
srsran::optional_vector<coreset_bitmap> coreset_bitmaps;
for (const srsran_coreset_t& coreset : view_active_coresets(pdcch_cfg)) {
coreset_bitmaps.emplace(coreset.id, coreset_bitmap(coreset_nof_cces(coreset)));
}
for (const pdcch_dl_t& pdcch : dl_pdcchs) {
coreset_bitmap& total_bitmap = coreset_bitmaps[pdcch.dci.ctx.coreset_id];
coreset_bitmap alloc(total_bitmap.size());
alloc.fill(pdcch.dci.ctx.location.ncce, pdcch.dci.ctx.location.ncce + (1U << pdcch.dci.ctx.location.L));
TESTASSERT((alloc & total_bitmap).none());
total_bitmap |= alloc;
}
for (const pdcch_ul_t& pdcch : ul_pdcchs) {
coreset_bitmap& total_bitmap = coreset_bitmaps[pdcch.dci.ctx.coreset_id];
coreset_bitmap alloc(total_bitmap.size());
alloc.fill(pdcch.dci.ctx.location.ncce, pdcch.dci.ctx.location.ncce + (1U << pdcch.dci.ctx.location.L));
TESTASSERT((alloc & total_bitmap).none());
total_bitmap |= alloc;
}
}
void test_dl_pdcch_consistency(const sched_nr_interface::cell_cfg_t& cell_cfg,
srsran::const_span<sched_nr_impl::pdcch_dl_t> dl_pdcchs)
{
for (const auto& pdcch : dl_pdcchs) {
TESTASSERT(pdcch.dci.bwp_id < cell_cfg.bwps.size());
const srsran_pdcch_cfg_nr_t& pdcch_cfg = cell_cfg.bwps[pdcch.dci.bwp_id].pdcch;
TESTASSERT(pdcch_cfg.coreset_present[pdcch.dci.ctx.coreset_id]);
test_dci_ctx_consistency(pdcch_cfg, pdcch.dci.ctx);
if (pdcch.dci.ctx.rnti_type == srsran_rnti_type_ra) {
TESTASSERT_EQ(pdcch.dci.ctx.format, srsran_dci_format_nr_1_0);
TESTASSERT_EQ(pdcch.dci.ctx.ss_type, srsran_search_space_type_common_1);
TESTASSERT(pdcch.dci.ctx.location.L < SRSRAN_SEARCH_SPACE_NOF_AGGREGATION_LEVELS_NR);
const srsran_coreset_t& coreset = pdcch_cfg.coreset[pdcch.dci.ctx.coreset_id];
if (pdcch.dci.ctx.coreset_id == 0) {
TESTASSERT_EQ(srsran_coreset_get_bw(&pdcch_cfg.coreset[0]), pdcch.dci.coreset0_bw);
}
// check consistency with cell_cfg
TESTASSERT(pdcch_cfg.ra_search_space_present);
TESTASSERT_EQ(pdcch_cfg.ra_search_space.coreset_id, pdcch.dci.ctx.coreset_id);
TESTASSERT(pdcch_cfg.ra_search_space.nof_candidates[pdcch.dci.ctx.location.L] > 0);
} else if (pdcch.dci.ctx.rnti_type == srsran_rnti_type_c) {
TESTASSERT(pdcch.dci.ctx.format == srsran_dci_format_nr_1_0 or pdcch.dci.ctx.format == srsran_dci_format_nr_1_1);
switch (pdcch.dci.ctx.rnti_type) {
case srsran_rnti_type_si:
TESTASSERT(pdcch.dci.sii != 0 or pdcch.dci.ctx.coreset_id == 0); // sii=0 must go in CORESET#0
break;
default:
break;
}
}
}

@ -27,6 +27,14 @@
namespace srsenb {
/// Test DCI context consistency
void test_dci_ctx_consistency(const srsran_pdcch_cfg_nr_t& pdcch_cfg, const srsran_dci_ctx_t& dci);
/// Test PDCCH collisions
void test_pdcch_collisions(const srsran_pdcch_cfg_nr_t& pdcch_cfg,
srsran::const_span<sched_nr_impl::pdcch_dl_t> dl_pdcchs,
srsran::const_span<sched_nr_impl::pdcch_ul_t> ul_pddchs);
void test_dl_pdcch_consistency(const sched_nr_interface::cell_cfg_t& cell_cfg,
srsran::const_span<sched_nr_impl::pdcch_dl_t> dl_pdcch);
void test_pdsch_consistency(srsran::const_span<mac_interface_phy_nr::pdsch_t> dl_pdcch);

@ -51,8 +51,8 @@ public:
bool is_dl_slot = srsran_duplex_nr_is_dl(&cell_params[cc_out.res.cc].cfg.duplex, 0, current_slot_tx.slot_idx());
if (is_dl_slot) {
if (cc_out.res.dl->phy.ssb.empty()) {
TESTASSERT(slot_ctxt.ue_db.empty() or cc_out.res.dl->phy.pdcch_dl.size() == 1);
if (cc_out.res.dl->phy.ssb.empty() and not slot_ctxt.ue_db.empty()) {
TESTASSERT(slot_ctxt.ue_db.empty() or cc_out.res.dl->phy.pdcch_dl.size() >= 1);
} else {
TESTASSERT(cc_out.res.dl->phy.pdcch_dl.size() == 0);
}
@ -95,6 +95,7 @@ void run_sched_nr_test(uint32_t nof_workers)
slot_point slot_tx = slot_rx + TX_ENB_DELAY;
if (slot_rx.to_uint() == 9) {
sched_nr_interface::ue_cfg_t uecfg = get_default_ue_cfg(nof_sectors);
uecfg.ue_bearers[1].direction = mac_lc_ch_cfg_t::BOTH;
tester.add_user(rnti, uecfg, slot_rx, 0);
}
tester.run_slot(slot_tx);

@ -0,0 +1,309 @@
/**
*
* \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 "sched_nr_cfg_generators.h"
#include "sched_nr_common_test.h"
#include "srsgnb/hdr/stack/mac/sched_nr_interface_utils.h"
#include "srsgnb/hdr/stack/mac/sched_nr_pdcch.h"
#include "srsran/common/test_common.h"
namespace srsenb {
using namespace sched_nr_impl;
/**
* Test for the case CORESET#0 is active.
* Given only one PDCCH candidate position is supported, only one PDCCH allocation should take place per slot
* The test additionally verifies that the DCI context content is correct for each PDCCH allocation
*/
void test_coreset0_cfg()
{
const uint32_t aggr_idx = 2;
srsran::test_delimit_logger delimiter{"Test PDCCH Allocation in CORESET#0"};
sched_nr_impl::cell_cfg_t cell_cfg = get_default_sa_cell_cfg_common();
sched_nr_interface::sched_args_t sched_args;
bwp_params_t bwp_params{cell_cfg, sched_args, 0, 0};
// UE config
ue_cfg_t uecfg = get_rach_ue_cfg(0);
uecfg.phy_cfg.pdcch = cell_cfg.bwps[0].pdcch; // Starts without UE-specific PDCCH
ue_carrier_params_t ue_cc{0x46, bwp_params, uecfg};
pdcch_dl_list_t dl_pdcchs;
pdcch_ul_list_t ul_pdcchs;
pdcch_dl_alloc_result dl_pdcch_result;
pdcch_ul_alloc_result ul_pdcch_result;
pdcch_dl_t* dl_pdcch = nullptr;
pdcch_ul_t* ul_pdcch = nullptr;
bwp_pdcch_allocator pdcch_sched(bwp_params, 0, dl_pdcchs, ul_pdcchs);
for (const srsran_coreset_t& cs : view_active_coresets(cell_cfg.bwps[0].pdcch)) {
// Verify nof CCEs is correctly computed
TESTASSERT_EQ(coreset_nof_cces(cs), pdcch_sched.nof_cces(cs.id));
}
// Slot with SIB1
TESTASSERT_EQ(0, pdcch_sched.nof_allocations());
// SIB1 allocation should be successful
dl_pdcch_result = pdcch_sched.alloc_si_pdcch(0, aggr_idx);
TESTASSERT(dl_pdcch_result.has_value());
dl_pdcch = dl_pdcch_result.value();
TESTASSERT_EQ(1, pdcch_sched.nof_allocations());
TESTASSERT_EQ(srsran_rnti_type_si, dl_pdcch->dci.ctx.rnti_type);
TESTASSERT_EQ(0, dl_pdcch->dci.ctx.coreset_id);
test_dci_ctx_consistency(bwp_params.cfg.pdcch, dl_pdcch->dci.ctx);
// No space for RAR, UE PDSCH/PUSCH
TESTASSERT(pdcch_sched.alloc_rar_pdcch(0x2, aggr_idx).error() == alloc_result::no_cch_space);
TESTASSERT(pdcch_sched.alloc_dl_pdcch(srsran_rnti_type_c, 1, aggr_idx, ue_cc).error() == alloc_result::no_cch_space);
TESTASSERT(pdcch_sched.alloc_ul_pdcch(1, aggr_idx, ue_cc).error() == alloc_result::no_cch_space);
srslog::fetch_basic_logger("TEST").info("%s", pdcch_sched.print_allocations());
// Slot with RAR
pdcch_sched.reset();
// RAR allocation should be successful
dl_pdcch_result = pdcch_sched.alloc_rar_pdcch(0x2, aggr_idx);
TESTASSERT(dl_pdcch_result.has_value());
dl_pdcch = dl_pdcch_result.value();
TESTASSERT_EQ(1, pdcch_sched.nof_allocations());
TESTASSERT_EQ(srsran_rnti_type_ra, dl_pdcch->dci.ctx.rnti_type);
TESTASSERT_EQ(0, dl_pdcch->dci.ctx.coreset_id);
TESTASSERT_EQ(srsran_search_space_type_common_1, dl_pdcch->dci.ctx.ss_type);
test_dci_ctx_consistency(bwp_params.cfg.pdcch, dl_pdcch->dci.ctx);
// No space for RAR, UE PDSCH/PUSCH
TESTASSERT(pdcch_sched.alloc_dl_pdcch(srsran_rnti_type_c, 1, aggr_idx, ue_cc).error() == alloc_result::no_cch_space);
TESTASSERT(pdcch_sched.alloc_ul_pdcch(1, aggr_idx, ue_cc).error() == alloc_result::no_cch_space);
srslog::fetch_basic_logger("TEST").info("%s", pdcch_sched.print_allocations());
// Slot with DL PDSCH
pdcch_sched.reset();
// 1st PDCCH allocation for DL should be successful
dl_pdcch_result = pdcch_sched.alloc_dl_pdcch(srsran_rnti_type_c, 1, aggr_idx, ue_cc);
TESTASSERT(dl_pdcch_result.has_value());
dl_pdcch = dl_pdcch_result.value();
TESTASSERT_EQ(1, pdcch_sched.nof_allocations());
TESTASSERT_EQ(srsran_rnti_type_c, dl_pdcch->dci.ctx.rnti_type);
TESTASSERT_EQ(0u, dl_pdcch->dci.ctx.coreset_id);
TESTASSERT_EQ(srsran_search_space_type_common_1, dl_pdcch->dci.ctx.ss_type);
test_dci_ctx_consistency(bwp_params.cfg.pdcch, dl_pdcch->dci.ctx);
// No space for 2nd PDCCH allocation
TESTASSERT(pdcch_sched.alloc_ul_pdcch(1, aggr_idx, ue_cc).error() == alloc_result::no_cch_space);
srslog::fetch_basic_logger("TEST").info("%s", pdcch_sched.print_allocations());
// Slot with UL PDSCH
pdcch_sched.reset();
// 1st PDCCH allocation for UL should be successful
ul_pdcch_result = pdcch_sched.alloc_ul_pdcch(1, aggr_idx, ue_cc);
TESTASSERT(ul_pdcch_result.has_value());
ul_pdcch = ul_pdcch_result.value();
TESTASSERT_EQ(1, pdcch_sched.nof_allocations());
TESTASSERT_EQ(srsran_rnti_type_c, ul_pdcch->dci.ctx.rnti_type);
TESTASSERT_EQ(0u, ul_pdcch->dci.ctx.coreset_id);
TESTASSERT_EQ(srsran_search_space_type_common_1, ul_pdcch->dci.ctx.ss_type);
test_dci_ctx_consistency(bwp_params.cfg.pdcch, ul_pdcch->dci.ctx);
// No space for 2nd PDCCH allocation
TESTASSERT(pdcch_sched.alloc_dl_pdcch(srsran_rnti_type_c, 1, aggr_idx, ue_cc).error() == alloc_result::no_cch_space);
srslog::fetch_basic_logger("TEST").info("%s", pdcch_sched.print_allocations());
}
/**
* Test for the case CORESET#2 is active.
* The PDCCH allocator should find enough space to fit SIB1/RAR (in CORESET#0) and UE-dedicated PDCCHs in (CORESET#2)
* The test additionally verifies that the DCI context content is correct for each PDCCH allocation and there are no
* collisions between PDCCH CCE allocations
*/
void test_coreset2_cfg()
{
const uint32_t aggr_idx = 2;
srsran::test_delimit_logger delimiter{"Test PDCCH Allocation in CORESET#0 and CORESET#2"};
sched_nr_impl::cell_cfg_t cell_cfg = get_default_sa_cell_cfg_common();
cell_cfg.bwps[0].pdcch.search_space_present[2] = true;
cell_cfg.bwps[0].pdcch.search_space[2] = get_default_ue_specific_search_space(2, 2);
cell_cfg.bwps[0].pdcch.coreset_present[2] = true;
cell_cfg.bwps[0].pdcch.coreset[2] = get_default_ue_specific_coreset(2, cell_cfg.carrier.pci);
sched_nr_interface::sched_args_t sched_args;
bwp_params_t bwp_params{cell_cfg, sched_args, 0, 0};
// UE config
ue_cfg_t uecfg = get_rach_ue_cfg(0);
uecfg.phy_cfg = get_common_ue_phy_cfg(cell_cfg);
uecfg.phy_cfg.pdcch = cell_cfg.bwps[0].pdcch; // Starts with UE-specific PDCCH
ue_carrier_params_t ue_cc{0x46, bwp_params, uecfg};
pdcch_dl_list_t dl_pdcchs;
pdcch_ul_list_t ul_pdcchs;
pdcch_dl_t* dl_pdcch = nullptr;
pdcch_ul_t* ul_pdcch = nullptr;
bwp_pdcch_allocator pdcch_sched(bwp_params, 0, dl_pdcchs, ul_pdcchs);
for (const srsran_coreset_t& cs : view_active_coresets(cell_cfg.bwps[0].pdcch)) {
// Verify nof CCEs is correctly computed
TESTASSERT_EQ(coreset_nof_cces(cs), pdcch_sched.nof_cces(cs.id));
}
// Slot with SIB1 + DL PDCCH and UL PDCCH
TESTASSERT_EQ(0, pdcch_sched.nof_allocations());
// SIB1 allocation should be successful
dl_pdcch = pdcch_sched.alloc_si_pdcch(0, aggr_idx).value();
TESTASSERT(dl_pdcch != nullptr);
TESTASSERT_EQ(1, pdcch_sched.nof_allocations());
TESTASSERT_EQ(srsran_rnti_type_si, dl_pdcch->dci.ctx.rnti_type);
TESTASSERT_EQ(0, dl_pdcch->dci.ctx.coreset_id);
srsran_dci_location_t expected_loc{aggr_idx, 0};
TESTASSERT(dl_pdcch->dci.ctx.location == expected_loc);
test_dci_ctx_consistency(bwp_params.cfg.pdcch, dl_pdcch->dci.ctx);
// No space for RAR or PDSCH in SS#1
TESTASSERT(pdcch_sched.alloc_rar_pdcch(0x2, aggr_idx).error() == alloc_result::no_cch_space);
TESTASSERT(pdcch_sched.alloc_dl_pdcch(srsran_rnti_type_c, 1, aggr_idx, ue_cc).error() == alloc_result::no_cch_space);
TESTASSERT(pdcch_sched.alloc_ul_pdcch(1, aggr_idx, ue_cc).error() == alloc_result::no_cch_space);
// there is space for UE DL PDCCH in SS#2
dl_pdcch = pdcch_sched.alloc_dl_pdcch(srsran_rnti_type_c, 2, aggr_idx, ue_cc).value();
TESTASSERT(dl_pdcch != nullptr);
TESTASSERT_EQ(2, pdcch_sched.nof_allocations());
TESTASSERT_EQ(srsran_rnti_type_c, dl_pdcch->dci.ctx.rnti_type);
TESTASSERT_EQ(2u, dl_pdcch->dci.ctx.coreset_id);
TESTASSERT_EQ(srsran_search_space_type_ue, dl_pdcch->dci.ctx.ss_type);
expected_loc = srsran_dci_location_t{aggr_idx, 0};
TESTASSERT(dl_pdcch->dci.ctx.location == expected_loc);
test_dci_ctx_consistency(bwp_params.cfg.pdcch, dl_pdcch->dci.ctx);
// there is space for UE UL PDCCH in SS#2
ul_pdcch = pdcch_sched.alloc_ul_pdcch(2, aggr_idx, ue_cc).value();
TESTASSERT(ul_pdcch != nullptr);
TESTASSERT_EQ(3, pdcch_sched.nof_allocations());
TESTASSERT_EQ(srsran_rnti_type_c, ul_pdcch->dci.ctx.rnti_type);
TESTASSERT_EQ(srsran_dci_format_nr_0_0, ul_pdcch->dci.ctx.format);
TESTASSERT_EQ(2u, ul_pdcch->dci.ctx.coreset_id);
TESTASSERT_EQ(srsran_search_space_type_ue, ul_pdcch->dci.ctx.ss_type);
expected_loc = srsran_dci_location_t{aggr_idx, 4};
TESTASSERT(ul_pdcch->dci.ctx.location == expected_loc);
test_dci_ctx_consistency(bwp_params.cfg.pdcch, ul_pdcch->dci.ctx);
// No space for 3rd PDCCH allocation in SS#2
TESTASSERT(pdcch_sched.alloc_dl_pdcch(srsran_rnti_type_c, 2, aggr_idx, ue_cc).error() == alloc_result::no_cch_space);
// Verify there are no PDCCH collisions
TESTASSERT_EQ(3, pdcch_sched.nof_allocations());
TESTASSERT_EQ(2, dl_pdcchs.size());
TESTASSERT_EQ(1, ul_pdcchs.size());
test_pdcch_collisions(bwp_params.cfg.pdcch, dl_pdcchs, ul_pdcchs);
srslog::fetch_basic_logger("TEST").info("%s", pdcch_sched.print_allocations());
// Verify all coresets are correctly cleaned up
pdcch_sched.reset();
TESTASSERT_EQ(0, pdcch_sched.nof_allocations());
TESTASSERT_EQ(0, dl_pdcchs.size());
TESTASSERT_EQ(0, ul_pdcchs.size());
}
void test_invalid_params()
{
const uint32_t aggr_idx = 2;
srsran::test_delimit_logger delimiter{"Test PDCCH Allocation with Invalid Arguments"};
sched_nr_impl::cell_cfg_t cell_cfg = get_default_sa_cell_cfg_common();
cell_cfg.bwps[0].pdcch.search_space_present[2] = true;
cell_cfg.bwps[0].pdcch.search_space[2] = get_default_ue_specific_search_space(2, 2);
cell_cfg.bwps[0].pdcch.coreset_present[2] = true;
cell_cfg.bwps[0].pdcch.coreset[2] = get_default_ue_specific_coreset(2, cell_cfg.carrier.pci);
cell_cfg.bwps[0].pdcch.search_space_present[3] = true;
cell_cfg.bwps[0].pdcch.search_space[3] = get_default_ue_specific_search_space(3, 2);
cell_cfg.bwps[0].pdcch.search_space[3].nof_formats = 1; // only DL
cell_cfg.bwps[0].pdcch.search_space[3].formats[0] = srsran_dci_format_nr_1_0;
sched_nr_interface::sched_args_t sched_args;
bwp_params_t bwp_params{cell_cfg, sched_args, 0, 0};
// UE config
ue_cfg_t uecfg = get_rach_ue_cfg(0);
uecfg.phy_cfg = get_common_ue_phy_cfg(cell_cfg);
uecfg.phy_cfg.pdcch = cell_cfg.bwps[0].pdcch; // Starts with UE-specific PDCCH
ue_carrier_params_t ue_cc{0x46, bwp_params, uecfg};
pdcch_dl_list_t dl_pdcchs;
pdcch_ul_list_t ul_pdcchs;
pdcch_dl_alloc_result dl_res;
pdcch_ul_alloc_result ul_res;
bwp_pdcch_allocator pdcch_sched(bwp_params, 0, dl_pdcchs, ul_pdcchs);
// Slot with SIB1 + DL PDCCH and UL PDCCH
TESTASSERT_EQ(0, pdcch_sched.nof_allocations());
// Pass UE search space for SI alloc
dl_res = pdcch_sched.alloc_si_pdcch(2, aggr_idx);
TESTASSERT(dl_res.is_error() and dl_res.error() == alloc_result::invalid_grant_params);
// Pass aggregation index for which there are no candidates
dl_res = pdcch_sched.alloc_si_pdcch(2, 4);
TESTASSERT(dl_res.is_error() and dl_res.error() == alloc_result::invalid_grant_params);
// SearchSpace must exist
dl_res = pdcch_sched.alloc_dl_pdcch(srsran_rnti_type_c, 4, aggr_idx, ue_cc);
TESTASSERT(dl_res.is_error() and dl_res.error() == alloc_result::invalid_grant_params);
// TC-RNTI cannot be allocated in Common SearchSpace Type1
dl_res = pdcch_sched.alloc_dl_pdcch(srsran_rnti_type_tc, 2, aggr_idx, ue_cc);
TESTASSERT(dl_res.is_error() and dl_res.error() == alloc_result::invalid_grant_params);
// C-RNTI cannot be allocated in Common SearchSpace Type0
dl_res = pdcch_sched.alloc_dl_pdcch(srsran_rnti_type_c, 0, aggr_idx, ue_cc);
TESTASSERT(dl_res.is_error() and dl_res.error() == alloc_result::invalid_grant_params);
// UL allocation cannot be made in SearchSpace without DCI format 0_0
ul_res = pdcch_sched.alloc_ul_pdcch(3, aggr_idx, ue_cc);
TESTASSERT(ul_res.is_error() and ul_res.error() == alloc_result::invalid_grant_params);
// Success case
TESTASSERT(pdcch_sched.nof_allocations() == 0);
ul_res = pdcch_sched.alloc_ul_pdcch(2, aggr_idx, ue_cc);
TESTASSERT(ul_res.has_value() and ul_res.value()->dci.ctx.format == srsran_dci_format_nr_0_0);
TESTASSERT(pdcch_sched.nof_allocations() == 1);
}
} // namespace srsenb
int main()
{
auto& test_logger = srslog::fetch_basic_logger("TEST");
test_logger.set_level(srslog::basic_levels::info);
auto& mac_nr_logger = srslog::fetch_basic_logger("MAC-NR");
mac_nr_logger.set_level(srslog::basic_levels::debug);
auto& pool_logger = srslog::fetch_basic_logger("POOL");
pool_logger.set_level(srslog::basic_levels::debug);
// Start the log backend.
srslog::init();
srsenb::test_coreset0_cfg();
srsenb::test_coreset2_cfg();
srsenb::test_invalid_params();
}

@ -113,7 +113,7 @@ void test_single_prach()
result = run_slot();
if (bwpparams.slots[current_slot.slot_idx()].is_dl and
bwpparams.slots[(current_slot + bwpparams.pusch_ra_list[0].msg3_delay).slot_idx()].is_ul) {
TESTASSERT_EQ(result->dl.phy.pdcch_dl.size(), 1);
TESTASSERT_EQ(1, result->dl.phy.pdcch_dl.size());
const auto& pdcch = result->dl.phy.pdcch_dl[0];
TESTASSERT_EQ(pdcch.dci.ctx.rnti, ra_rnti);
TESTASSERT_EQ(pdcch.dci.ctx.rnti_type, srsran_rnti_type_ra);

@ -0,0 +1,482 @@
/**
*
* \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 "sched_nr_cfg_generators.h"
#include "srsgnb/hdr/stack/mac/sched_nr_interface_utils.h"
#include "srsgnb/hdr/stack/mac/sched_nr_sch.h"
#include "srsran/common/test_common.h"
extern "C" {
#include "srsran/phy/common/sliv.h"
}
namespace srsenb {
using namespace sched_nr_impl;
sched_nr_interface::cell_cfg_t get_cell_cfg()
{
sched_nr_impl::cell_cfg_t cell_cfg = get_default_sa_cell_cfg_common();
cell_cfg.bwps[0].pdcch.search_space_present[2] = true;
cell_cfg.bwps[0].pdcch.search_space[2] = get_default_ue_specific_search_space(2, 2);
cell_cfg.bwps[0].pdcch.coreset_present[2] = true;
cell_cfg.bwps[0].pdcch.coreset[2] = get_default_ue_specific_coreset(2, cell_cfg.carrier.pci);
return cell_cfg;
}
sched_nr_interface::ue_cfg_t get_ue_cfg(const sched_nr_interface::cell_cfg_t& cell_cfg)
{
ue_cfg_t uecfg = get_rach_ue_cfg(0);
uecfg.phy_cfg = get_common_ue_phy_cfg(cell_cfg);
uecfg.phy_cfg.pdcch = cell_cfg.bwps[0].pdcch; // Starts with UE-specific PDCCH
return uecfg;
}
srsran_dci_ctx_t generate_dci_ctx(const srsran_pdcch_cfg_nr_t& pdcch,
uint32_t ss_id,
srsran_rnti_type_t rnti_type,
uint16_t rnti,
srsran_dci_format_nr_t dci_fmt = srsran_dci_format_nr_1_0)
{
const srsran_search_space_t& ss = pdcch.search_space[ss_id];
const srsran_coreset_t& cs = pdcch.coreset[ss.coreset_id];
srsran_dci_ctx_t ctx;
ctx.location = {2, 4};
ctx.ss_type = ss.type;
ctx.coreset_id = ss.coreset_id;
ctx.coreset_start_rb = srsran_coreset_start_rb(&cs);
ctx.rnti_type = rnti_type;
ctx.format = dci_fmt;
ctx.rnti = rnti;
return ctx;
}
void test_dci_freq_assignment(const bwp_params_t& bwp_params, prb_interval grant, const pdcch_dl_t& pdcch)
{
// Compute BWP PRB limits
prb_interval lims{0, bwp_params.nof_prb()};
if (SRSRAN_SEARCH_SPACE_IS_COMMON(pdcch.dci.ctx.ss_type) and pdcch.dci.ctx.format == srsran_dci_format_nr_1_0) {
lims = bwp_params.dci_fmt_1_0_prb_lims(pdcch.dci.ctx.coreset_id);
}
// RB indexing should start from the first PRB of CORESET
uint32_t expected_freq_assignment =
srsran_ra_nr_type1_riv(lims.length(), grant.start() - lims.start(), grant.length());
TESTASSERT_EQ(expected_freq_assignment, pdcch.dci.freq_domain_assigment);
uint32_t st, len;
srsran_sliv_to_s_and_l(lims.length(), pdcch.dci.freq_domain_assigment, &st, &len);
prb_interval allocated_prbs{st + lims.start(), st + lims.start() + len};
TESTASSERT(allocated_prbs == grant);
}
void test_si()
{
srsran::test_delimit_logger delimiter{"Test PDSCH SI Allocation"};
static const uint32_t ss_id = 0;
// Create Cell and UE configs
sched_nr_impl::cell_cfg_t cell_cfg = get_cell_cfg();
sched_nr_interface::sched_args_t sched_args;
bwp_params_t bwp_params{cell_cfg, sched_args, 0, 0};
pdsch_list_t pdschs;
pdsch_alloc_result alloc_res;
pdsch_allocator pdsch_sched(bwp_params, 0, pdschs);
pdcch_dl_t pdcch;
pdcch.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, ss_id, srsran_rnti_type_si, SRSRAN_SIRNTI);
uint32_t min_prb = bwp_params.dci_fmt_1_0_prb_lims(pdcch.dci.ctx.coreset_id).start();
uint32_t max_prb = bwp_params.dci_fmt_1_0_prb_lims(pdcch.dci.ctx.coreset_id).stop();
std::array<prb_interval, 3> grant_list = {
prb_interval{2, 4}, prb_interval{min_prb, max_prb}, prb_interval{0, bwp_params.nof_prb()}};
for (uint32_t i = 0; i < grant_list.size(); ++i) {
pdsch_sched.reset();
TESTASSERT_EQ(0, pdschs.size());
prb_interval grant = grant_list[i];
bool success_expected = grant.start() >= min_prb and grant.stop() <= max_prb;
alloc_result check_ret = pdsch_sched.is_si_grant_valid(ss_id, grant);
prb_bitmap avail_prbs = pdsch_sched.occupied_prbs(ss_id, srsran_dci_format_nr_1_0);
TESTASSERT_EQ((int)min_prb, avail_prbs.find_lowest(0, avail_prbs.size(), false));
TESTASSERT_EQ((int)max_prb, avail_prbs.find_lowest(min_prb, avail_prbs.size(), true));
printf("Attempt %d should be %ssuccessful\n", i, success_expected ? "" : "un");
alloc_res = pdsch_sched.alloc_si_pdsch(ss_id, grant, pdcch.dci);
if (success_expected) {
// SIB1 allocation doesnt go outside CORESET#0 BW
TESTASSERT(alloc_res.has_value());
TESTASSERT_EQ(1, pdschs.size());
TESTASSERT(&pdschs.back() == alloc_res.value());
TESTASSERT_EQ(0, pdcch.dci.time_domain_assigment);
TESTASSERT(not avail_prbs.any(grant.start(), grant.stop()));
test_dci_freq_assignment(bwp_params, grant, pdcch);
} else {
TESTASSERT(alloc_res.is_error());
TESTASSERT(check_ret == alloc_res.error());
TESTASSERT_EQ(0, pdschs.size());
TESTASSERT(avail_prbs.any(grant.start(), grant.stop()));
}
}
}
void test_rar()
{
srsran::test_delimit_logger delimiter{"Test PDSCH RAR Allocation"};
static const uint32_t ss_id = 1;
// Create Cell and UE configs
sched_nr_impl::cell_cfg_t cell_cfg = get_cell_cfg();
sched_nr_interface::sched_args_t sched_args;
bwp_params_t bwp_params{cell_cfg, sched_args, 0, 0};
pdsch_list_t pdschs;
pdsch_alloc_result alloc_res;
pdsch_allocator pdsch_sched(bwp_params, 0, pdschs);
pdcch_dl_t pdcch;
pdcch.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, ss_id, srsran_rnti_type_ra, 0x2);
uint32_t min_prb = bwp_params.dci_fmt_1_0_prb_lims(pdcch.dci.ctx.coreset_id).start();
uint32_t max_prb = bwp_params.dci_fmt_1_0_prb_lims(pdcch.dci.ctx.coreset_id).stop();
std::array<prb_interval, 3> grant_list = {
prb_interval{2, 4}, prb_interval{min_prb, max_prb}, prb_interval{0, bwp_params.nof_prb()}};
for (uint32_t i = 0; i < grant_list.size(); ++i) {
pdsch_sched.reset();
TESTASSERT_EQ(0, pdschs.size());
prb_interval grant = grant_list[i];
bool success_expected = grant.start() >= min_prb and grant.stop() <= max_prb;
alloc_result check_ret = pdsch_sched.is_rar_grant_valid(grant);
prb_bitmap avail_prbs = pdsch_sched.occupied_prbs(ss_id, srsran_dci_format_nr_1_0);
TESTASSERT_EQ((int)min_prb, avail_prbs.find_lowest(0, avail_prbs.size(), false));
TESTASSERT_EQ((int)max_prb, avail_prbs.find_lowest(min_prb, avail_prbs.size(), true));
printf("Attempt %d should be %ssuccessful\n", i, success_expected ? "" : "un");
alloc_res = pdsch_sched.alloc_rar_pdsch(grant, pdcch.dci);
if (success_expected) {
// SIB1 allocation doesnt go outside CORESET#0 BW
TESTASSERT(alloc_res.has_value());
TESTASSERT_EQ(1, pdschs.size());
TESTASSERT(&pdschs.back() == alloc_res.value());
TESTASSERT_EQ(0, pdcch.dci.time_domain_assigment);
TESTASSERT(not avail_prbs.any(grant.start(), grant.stop()));
test_dci_freq_assignment(bwp_params, grant, pdcch);
} else {
TESTASSERT(alloc_res.is_error());
TESTASSERT(check_ret == alloc_res.error());
TESTASSERT_EQ(0, pdschs.size());
TESTASSERT(avail_prbs.any(grant.start(), grant.stop()));
}
}
}
void test_ue_pdsch()
{
srsran::test_delimit_logger delimiter{"Test PDSCH UE Allocation"};
// Create Cell and UE configs
sched_nr_impl::cell_cfg_t cell_cfg = get_cell_cfg();
sched_nr_impl::ue_cfg_t uecfg = get_ue_cfg(cell_cfg);
sched_nr_interface::sched_args_t sched_args;
bwp_params_t bwp_params{cell_cfg, sched_args, 0, 0};
ue_carrier_params_t ue_cc{0x4601, bwp_params, uecfg};
pdsch_list_t pdschs;
pdsch_alloc_result alloc_res;
pdsch_allocator pdsch_sched(bwp_params, 0, pdschs);
pdcch_dl_t pdcch_common, pdcch_ue;
pdcch_common.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 1, srsran_rnti_type_c, 0x4601);
pdcch_ue.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 2, srsran_rnti_type_c, 0x4601);
prb_interval lims_common = bwp_params.dci_fmt_1_0_prb_lims(pdcch_common.dci.ctx.coreset_id);
prb_interval lims_ue{0, bwp_params.nof_prb()};
std::array<std::pair<uint32_t, prb_interval>, 4> grant_list = {std::make_pair(1, prb_interval{2, 4}),
std::make_pair(1, lims_common),
std::make_pair(1, lims_ue),
std::make_pair(2, lims_common)};
for (uint32_t i = 0; i < grant_list.size(); ++i) {
pdsch_sched.reset();
TESTASSERT_EQ(0, pdschs.size());
auto g = grant_list[i];
uint32_t ss_id = g.first;
prb_interval grant = g.second;
prb_interval lims = ss_id == 1 ? lims_common : lims_ue;
pdcch_dl_t& pdcch = ss_id == 1 ? pdcch_common : pdcch_ue;
bool success_expected = grant.start() >= lims.start() and grant.stop() <= lims.stop();
alloc_result check_ret = pdsch_sched.is_ue_grant_valid(ue_cc, ss_id, srsran_dci_format_nr_1_0, grant);
prb_bitmap avail_prbs = pdsch_sched.occupied_prbs(ss_id, srsran_dci_format_nr_1_0);
int pos = avail_prbs.find_lowest(0, avail_prbs.size(), false);
TESTASSERT_EQ((int)lims.start(), pos);
pos = avail_prbs.find_lowest(lims.start(), avail_prbs.size(), true);
TESTASSERT_EQ((int)lims.stop(), (pos < 0 ? (int)avail_prbs.size() : pos));
printf("Attempt %d should be %ssuccessful\n", i, success_expected ? "" : "un");
alloc_res = pdsch_sched.alloc_ue_pdsch(ss_id, srsran_dci_format_nr_1_0, grant, ue_cc, pdcch.dci);
TESTASSERT(success_expected == alloc_res.has_value());
if (success_expected) {
// SIB1 allocation doesnt go outside CORESET#0 BW
TESTASSERT_EQ(1, pdschs.size());
TESTASSERT(&pdschs.back() == alloc_res.value());
TESTASSERT_EQ(0, pdcch.dci.time_domain_assigment);
TESTASSERT(not avail_prbs.any(grant.start(), grant.stop()));
test_dci_freq_assignment(bwp_params, grant, pdcch);
} else {
TESTASSERT(check_ret == alloc_res.error());
TESTASSERT_EQ(0, pdschs.size());
TESTASSERT(avail_prbs.any(grant.start(), grant.stop()));
}
}
}
void test_pdsch_fail()
{
srsran::test_delimit_logger delimiter{"Test PDSCH Allocation Failure"};
// Create Cell and UE configs
sched_nr_impl::cell_cfg_t cell_cfg = get_cell_cfg();
sched_nr_impl::ue_cfg_t uecfg = get_ue_cfg(cell_cfg);
sched_nr_interface::sched_args_t sched_args;
bwp_params_t bwp_params{cell_cfg, sched_args, 0, 0};
ue_carrier_params_t ue_cc{0x4601, bwp_params, uecfg};
pdsch_list_t pdschs;
pdsch_alloc_result alloc_res;
pdsch_allocator pdsch_sched(bwp_params, 0, pdschs);
pdcch_dl_t pdcch_common, pdcch_ue, pdcch_rar, pdcch_si, pdcch;
pdcch_si.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 0, srsran_rnti_type_si, SRSRAN_SIRNTI);
pdcch_rar.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 1, srsran_rnti_type_ra, 0x2);
pdcch_common.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 1, srsran_rnti_type_c, 0x4601);
pdcch_ue.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 2, srsran_rnti_type_c, 0x4601);
// Allocations of type 0 are not compatible with DCI format 1_0
rbg_bitmap rbgs(bwp_params.N_rbg);
rbgs.set(1);
rbgs.set(3);
prb_grant grant_type0 = rbgs;
TESTASSERT_EQ(alloc_result::invalid_grant_params, pdsch_sched.alloc_si_pdsch(0, grant_type0, pdcch_si.dci).error());
TESTASSERT_EQ(alloc_result::invalid_grant_params, pdsch_sched.alloc_rar_pdsch(grant_type0, pdcch_rar.dci).error());
TESTASSERT_EQ(alloc_result::invalid_grant_params,
pdsch_sched.alloc_ue_pdsch(1, srsran_dci_format_nr_1_0, grant_type0, ue_cc, pdcch.dci).error());
TESTASSERT_EQ(alloc_result::invalid_grant_params,
pdsch_sched.alloc_ue_pdsch(2, srsran_dci_format_nr_1_0, grant_type0, ue_cc, pdcch.dci).error());
// Resource Allocation type must be compatible with UE PDSCH configuration
TESTASSERT_EQ(alloc_result::invalid_grant_params,
pdsch_sched.alloc_ue_pdsch(2, srsran_dci_format_nr_1_1, grant_type0, ue_cc, pdcch.dci).error());
// Allocations of DCI format 1_0 should start from CORESET first RB and their BW should be limited by CORESET#0 BW
prb_grant grant_type1 = prb_interval{0, bwp_params.coreset_prb_range(0).stop()};
TESTASSERT(pdsch_sched.alloc_ue_pdsch(1, srsran_dci_format_nr_1_0, grant_type1, ue_cc, pdcch.dci).is_error());
grant_type1 = prb_interval{bwp_params.coreset_prb_range(0).start(), bwp_params.nof_prb()};
TESTASSERT(pdsch_sched.alloc_ue_pdsch(1, srsran_dci_format_nr_1_0, grant_type1, ue_cc, pdcch.dci).is_error());
TESTASSERT(pdsch_sched.alloc_ue_pdsch(2, srsran_dci_format_nr_1_0, grant_type1, ue_cc, pdcch.dci).has_value());
// PRB collisions are detected
TESTASSERT(pdsch_sched.alloc_ue_pdsch(2, srsran_dci_format_nr_1_0, prb_interval{5, 6}, ue_cc, pdcch.dci).is_error());
}
void test_multi_pdsch()
{
srsran::test_delimit_logger delimiter{"Test Multiple PDSCH Allocations"};
// Create Cell and UE configs
sched_nr_impl::cell_cfg_t cell_cfg = get_cell_cfg();
sched_nr_impl::ue_cfg_t uecfg = get_ue_cfg(cell_cfg);
sched_nr_interface::sched_args_t sched_args;
bwp_params_t bwp_params{cell_cfg, sched_args, 0, 0};
ue_carrier_params_t ue_cc{0x4601, bwp_params, uecfg};
ue_carrier_params_t ue_cc2{0x4602, bwp_params, uecfg};
pdsch_list_t pdschs;
pdsch_alloc_result alloc_res;
pdsch_allocator pdsch_sched(bwp_params, 0, pdschs);
pdcch_dl_t pdcch_sib, pdcch_common, pdcch_ue;
pdcch_sib.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 0, srsran_rnti_type_si, SRSRAN_SIRNTI);
pdcch_common.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 1, srsran_rnti_type_c, 0x4601);
pdcch_ue.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 2, srsran_rnti_type_c, 0x4602);
// Allocate SIB1
uint32_t ss_id = 0;
pdcch_dl_t* pdcch = &pdcch_sib;
prb_bitmap used_prbs = pdsch_sched.occupied_prbs(ss_id, srsran_dci_format_nr_1_0);
fmt::print("No allocations yet. Occupied PRBs for common SearchSpace: {:b}\n", used_prbs);
uint32_t sib1_grant_size = 4;
prb_bitmap sib_prbs = ~used_prbs;
int first_prb = sib_prbs.find_lowest(0, sib_prbs.size(), true);
prb_interval sib_grant{(uint32_t)first_prb, sib1_grant_size};
TESTASSERT_EQ(alloc_result::success, pdsch_sched.is_si_grant_valid(ss_id, sib_grant));
alloc_res = pdsch_sched.alloc_si_pdsch(ss_id, sib_grant, pdcch->dci);
TESTASSERT(alloc_res.has_value());
test_dci_freq_assignment(bwp_params, sib_grant, *pdcch);
prb_bitmap used_prbs_sib1 = pdsch_sched.occupied_prbs(ss_id, srsran_dci_format_nr_1_0);
TESTASSERT_EQ(used_prbs_sib1.count(), used_prbs.count() + sib_grant.length());
TESTASSERT_EQ(alloc_result::sch_collision, pdsch_sched.is_si_grant_valid(ss_id, sib_grant));
prb_bitmap last_prb_bitmap(used_prbs.size());
last_prb_bitmap.fill(sib_grant.start(), sib_grant.stop());
fmt::print("SIB1 allocated. Occupied PRBs:\n{:b} -> {:b}\n", last_prb_bitmap, used_prbs_sib1);
// Allocate UE in common SearchSpace
ss_id = 1;
pdcch = &pdcch_common;
prb_bitmap ue_prbs = ~used_prbs_sib1;
first_prb = ue_prbs.find_lowest(0, ue_prbs.size(), true);
uint32_t ue_grant_size = 10;
prb_interval ue_grant{(uint32_t)first_prb, ue_grant_size};
TESTASSERT_EQ(alloc_result::success, pdsch_sched.is_ue_grant_valid(ue_cc, ss_id, srsran_dci_format_nr_1_0, ue_grant));
alloc_res = pdsch_sched.alloc_ue_pdsch(ss_id, srsran_dci_format_nr_1_0, ue_grant, ue_cc, pdcch->dci);
TESTASSERT(alloc_res.has_value());
test_dci_freq_assignment(bwp_params, ue_grant, *pdcch);
prb_bitmap used_prbs_ue = pdsch_sched.occupied_prbs(ss_id, srsran_dci_format_nr_1_0);
TESTASSERT_EQ(used_prbs_ue.count(), used_prbs_sib1.count() + ue_grant.length());
TESTASSERT_EQ(alloc_result::sch_collision,
pdsch_sched.is_ue_grant_valid(ue_cc, ss_id, srsran_dci_format_nr_1_0, ue_grant));
last_prb_bitmap.reset();
last_prb_bitmap.fill(ue_grant.start(), ue_grant.stop());
fmt::print("C-RNTI allocated in Common SearchSpace. Occupied PRBs:\n{:b} -> {:b}\n", last_prb_bitmap, used_prbs_ue);
// Allocate UE in UE SearchSpace
ss_id = 2;
pdcch = &pdcch_ue;
used_prbs = pdsch_sched.occupied_prbs(ss_id, srsran_dci_format_nr_1_0);
prb_interval ue_grant2 = find_empty_interval_of_length(used_prbs, used_prbs_ue.size(), 0);
TESTASSERT_EQ(bwp_params.nof_prb(), ue_grant2.stop());
TESTASSERT_EQ(alloc_result::success,
pdsch_sched.is_ue_grant_valid(ue_cc, ss_id, srsran_dci_format_nr_1_0, ue_grant2));
alloc_res = pdsch_sched.alloc_ue_pdsch(ss_id, srsran_dci_format_nr_1_0, ue_grant2, ue_cc, pdcch->dci);
TESTASSERT(alloc_res.has_value());
test_dci_freq_assignment(bwp_params, ue_grant2, *pdcch);
prb_bitmap used_prbs_ue2 = pdsch_sched.occupied_prbs(ss_id, srsran_dci_format_nr_1_0);
TESTASSERT_EQ(used_prbs_ue2.count(), used_prbs.count() + ue_grant2.length());
TESTASSERT_EQ(alloc_result::sch_collision,
pdsch_sched.is_ue_grant_valid(ue_cc, ss_id, srsran_dci_format_nr_1_0, ue_grant2));
last_prb_bitmap.reset();
last_prb_bitmap.fill(ue_grant2.start(), ue_grant2.stop());
fmt::print("C-RNTI allocated in UE-dedicated common SearchSpace. Occupied PRBs:\n{:b} -> {:b}\n",
last_prb_bitmap,
used_prbs_ue2);
TESTASSERT_EQ(3, pdschs.size());
pdsch_sched.reset();
TESTASSERT_EQ(0, pdschs.size());
TESTASSERT_EQ(0, pdsch_sched.occupied_prbs(2, srsran_dci_format_nr_1_0).count());
}
void test_multi_pusch()
{
srsran::test_delimit_logger delimiter{"Test Multiple PUSCH Allocations"};
// Create Cell and UE configs
sched_nr_impl::cell_cfg_t cell_cfg = get_cell_cfg();
sched_nr_impl::ue_cfg_t uecfg = get_ue_cfg(cell_cfg);
sched_nr_interface::sched_args_t sched_args;
bwp_params_t bwp_params{cell_cfg, sched_args, 0, 0};
ue_carrier_params_t ue_cc{0x4601, bwp_params, uecfg};
ue_carrier_params_t ue_cc2{0x4602, bwp_params, uecfg};
pusch_list_t puschs;
pusch_alloc_result alloc_res;
pusch_allocator pusch_sched(bwp_params, 0, puschs);
pdcch_ul_t pdcch_ue1, pdcch_ue2;
pdcch_ue1.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 1, srsran_rnti_type_c, 0x4601);
pdcch_ue2.dci.ctx = generate_dci_ctx(bwp_params.cfg.pdcch, 2, srsran_rnti_type_c, 0x4602);
// Allocate UE in common SearchSpace
uint32_t ss_id = 1;
pdcch_ul_t* pdcch = &pdcch_ue1;
prb_bitmap used_prbs = pusch_sched.occupied_prbs();
uint32_t ue_grant_size = 10;
prb_interval ue_grant = find_empty_interval_of_length(used_prbs, ue_grant_size);
TESTASSERT_EQ(alloc_result::success, pusch_sched.is_grant_valid(srsran_search_space_type_common_1, ue_grant));
alloc_res = pusch_sched.alloc_pusch(pdcch->dci.ctx.ss_type, ue_grant, pdcch->dci);
TESTASSERT(alloc_res.has_value());
prb_bitmap used_prbs_ue1 = pusch_sched.occupied_prbs();
TESTASSERT_EQ(used_prbs_ue1.count(), used_prbs.count() + ue_grant.length());
TESTASSERT_EQ(alloc_result::sch_collision,
pusch_sched.is_grant_valid(srsran_search_space_type_common_1, ue_grant, false));
prb_bitmap last_prb_bitmap(used_prbs.size());
last_prb_bitmap.fill(ue_grant.start(), ue_grant.stop());
fmt::print("C-RNTI allocated in Common SearchSpace. Occupied PRBs:\n{:b} -> {:b}\n", last_prb_bitmap, used_prbs_ue1);
// Allocate UE in dedicated SearchSpace
ss_id = 2;
pdcch = &pdcch_ue2;
used_prbs = pusch_sched.occupied_prbs();
prb_interval ue2_grant = find_empty_interval_of_length(used_prbs, used_prbs.size());
TESTASSERT_EQ(alloc_result::success, pusch_sched.is_grant_valid(srsran_search_space_type_ue, ue2_grant));
alloc_res = pusch_sched.alloc_pusch(pdcch->dci.ctx.ss_type, ue2_grant, pdcch->dci);
TESTASSERT(alloc_res.has_value());
prb_bitmap used_prbs_ue2 = pusch_sched.occupied_prbs();
TESTASSERT_EQ(used_prbs_ue2.count(), used_prbs.count() + ue2_grant.length());
TESTASSERT_EQ(alloc_result::sch_collision, pusch_sched.is_grant_valid(srsran_search_space_type_ue, ue2_grant, false));
last_prb_bitmap.reset();
last_prb_bitmap.fill(ue2_grant.start(), ue2_grant.stop());
fmt::print("C-RNTI allocated in Common SearchSpace. Occupied PRBs:\n{:b} -> {:b}\n", last_prb_bitmap, used_prbs_ue2);
}
} // namespace srsenb
int main()
{
auto& test_logger = srslog::fetch_basic_logger("TEST");
test_logger.set_level(srslog::basic_levels::info);
auto& mac_nr_logger = srslog::fetch_basic_logger("MAC-NR");
mac_nr_logger.set_level(srslog::basic_levels::debug);
auto& pool_logger = srslog::fetch_basic_logger("POOL");
pool_logger.set_level(srslog::basic_levels::debug);
// Start the log backend.
srslog::init();
srsenb::test_si();
srsenb::test_rar();
srsenb::test_ue_pdsch();
srsenb::test_pdsch_fail();
srsenb::test_multi_pdsch();
srsenb::test_multi_pusch();
}

@ -87,7 +87,11 @@ void sched_nr_ue_sim::update_dl_harqs(const sched_nr_cc_result_view& cc_out)
h.ndi = data.dci.ndi;
h.first_slot_tx = cc_out.slot;
h.dci_loc = data.dci.ctx.location;
h.tbs = 100; // TODO
for (const sched_nr_impl::pdsch_t& pdsch : cc_out.dl->phy.pdsch) {
if (pdsch.sch.grant.rnti == data.dci.ctx.rnti) {
h.tbs = pdsch.sch.grant.tb[0].tbs / 8u;
}
}
} else {
// it is retx
h.nof_retxs++;
@ -238,6 +242,12 @@ void sched_nr_base_tester::user_cfg(uint16_t rnti, const sched_nr_interface::ue_
sched_ptr->ue_cfg(rnti, ue_cfg_);
}
void sched_nr_base_tester::add_rlc_dl_bytes(uint16_t rnti, uint32_t lcid, uint32_t pdu_size_bytes)
{
TESTASSERT(ue_db.count(rnti) > 0);
dl_buffer_state_diff(rnti, lcid, pdu_size_bytes);
}
void sched_nr_base_tester::run_slot(slot_point slot_tx)
{
srsran_assert(not stopped.load(std::memory_order_relaxed), "Running scheduler when it has already been stopped");
@ -322,6 +332,83 @@ void sched_nr_base_tester::process_results()
for (auto& u : ue_db) {
u.second.update(cc_out);
}
// Update scheduler buffers
update_sched_buffer_state(cc_out);
}
}
void sched_nr_base_tester::dl_buffer_state_diff(uint16_t rnti, uint32_t lcid, int newtx)
{
auto& lch = gnb_ue_db[rnti].logical_channels[lcid];
lch.rlc_unacked = std::max(0, (int)lch.rlc_unacked + newtx);
update_sched_buffer_state(rnti);
logger.debug("STATUS: rnti=0x%x, lcid=%d DL buffer state is (unacked=%d, newtx=%d)",
rnti,
lcid,
lch.rlc_unacked,
lch.rlc_newtx);
}
void sched_nr_base_tester::dl_buffer_state_diff(uint16_t rnti, int diff_bs)
{
if (diff_bs == 0) {
return;
}
if (diff_bs > 0) {
const auto& ue_bearers = ue_db.at(rnti).get_ctxt().ue_cfg.ue_bearers;
for (int i = ue_bearers.size() - 1; i >= 0; --i) {
if (ue_bearers[i].is_dl()) {
dl_buffer_state_diff(rnti, i, diff_bs);
return;
}
}
srsran_terminate("Updating UE RLC buffer state but no bearers are active");
}
for (auto& lch : gnb_ue_db[rnti].logical_channels) {
if (not ue_db.at(rnti).get_ctxt().ue_cfg.ue_bearers[lch.first].is_dl()) {
continue;
}
int max_diff = -std::min((int)lch.second.rlc_unacked, -diff_bs);
if (max_diff != 0) {
dl_buffer_state_diff(rnti, lch.first, max_diff);
diff_bs -= max_diff;
}
}
}
void sched_nr_base_tester::update_sched_buffer_state(uint16_t rnti)
{
auto& u = ue_db.at(rnti);
for (auto& lch : gnb_ue_db[rnti].logical_channels) {
int newtx = lch.second.rlc_unacked;
for (auto& cc : u.get_ctxt().cc_list) {
if (newtx <= 0) {
break;
}
for (auto& dl_h : cc.dl_harqs) {
int tbs = dl_h.active ? dl_h.tbs : 0;
newtx = std::max(0, newtx - tbs);
}
}
if (newtx != (int)lch.second.rlc_newtx) {
sched_ptr->dl_buffer_state(rnti, lch.first, newtx, 0);
lch.second.rlc_newtx = newtx;
logger.debug("STATUS: rnti=0x%x, lcid=%d DL buffer state is (unacked=%d, newtx=%d)",
rnti,
lch.first,
lch.second.rlc_unacked,
lch.second.rlc_newtx);
}
}
}
void sched_nr_base_tester::update_sched_buffer_state(const sched_nr_cc_result_view& cc_out)
{
for (auto& dl : cc_out.dl->phy.pdcch_dl) {
if (dl.dci.ctx.rnti_type == srsran_rnti_type_c or dl.dci.ctx.rnti_type == srsran_rnti_type_tc) {
update_sched_buffer_state(dl.dci.ctx.rnti);
}
}
}
@ -368,8 +455,13 @@ int sched_nr_base_tester::apply_slot_events(sim_nr_ue_ctxt_t& ue_ctxt, const ue_
auto& h = ue_ctxt.cc_list[enb_cc_idx].dl_harqs[ack.pid];
if (ack.ack) {
logger.info(
"DL ACK rnti=0x%x slot_dl_tx=%u cc=%d pid=%d", ue_ctxt.rnti, h.last_slot_tx.to_uint(), enb_cc_idx, ack.pid);
logger.info("EVENT: DL ACK rnti=0x%x slot_dl_tx=%u cc=%d pid=%d, tbs=%d",
ue_ctxt.rnti,
h.last_slot_tx.to_uint(),
enb_cc_idx,
ack.pid,
h.tbs);
dl_buffer_state_diff(ue_ctxt.rnti, -(int)h.tbs);
}
// update scheduler
@ -394,7 +486,7 @@ int sched_nr_base_tester::apply_slot_events(sim_nr_ue_ctxt_t& ue_ctxt, const ue_
if (h.is_msg3) {
logger.info("STATUS: rnti=0x%x received Msg3", ue_ctxt.rnti);
sched_ptr->dl_buffer_state(ue_ctxt.rnti, 0, 150, 0); // Schedule RRC setup
dl_buffer_state_diff(ue_ctxt.rnti, 0, 150); // Schedule RRC setup
}
}
}

@ -138,6 +138,8 @@ public:
void user_cfg(uint16_t rnti, const sched_nr_interface::ue_cfg_t& ue_cfg_);
void add_rlc_dl_bytes(uint16_t rnti, uint32_t lcid, uint32_t pdu_size_bytes);
srsran::const_span<sched_nr_impl::cell_params_t> get_cell_params() { return cell_params; }
// configurable by simulator concrete implementation
@ -150,6 +152,11 @@ protected:
void generate_cc_result(uint32_t cc);
sim_nr_enb_ctxt_t get_enb_ctxt() const;
void dl_buffer_state_diff(uint16_t rnti, uint32_t lcid, int newtx);
void dl_buffer_state_diff(uint16_t rnti, int newtx);
void update_sched_buffer_state(uint16_t rnti);
void update_sched_buffer_state(const sched_nr_cc_result_view& cc_out);
int set_default_slot_events(const sim_nr_ue_ctxt_t& ue_ctxt, ue_nr_slot_events& pending_events);
int apply_slot_events(sim_nr_ue_ctxt_t& ue_ctxt, const ue_nr_slot_events& events);
@ -164,8 +171,19 @@ protected:
std::vector<std::unique_ptr<srsran::task_worker> > cc_workers;
// UE context from the UE's point-of-view
std::map<uint16_t, sched_nr_ue_sim> ue_db;
// gNB point-of-view of UE state
struct gnb_ue_ctxt {
struct channel_ctxt {
uint32_t rlc_unacked = 0;
uint32_t rlc_newtx = 0;
};
std::map<uint32_t, channel_ctxt> logical_channels;
};
std::map<uint16_t, gnb_ue_ctxt> gnb_ue_db;
// slot-specific
slot_point current_slot_tx;
std::chrono::steady_clock::time_point slot_start_tp;

@ -26,6 +26,50 @@
namespace srsenb {
class sched_tester : public sched_nr_base_tester
{
public:
using sched_nr_base_tester::sched_nr_base_tester;
void process_slot_result(const sim_nr_enb_ctxt_t& enb_ctxt, srsran::const_span<cc_result_t> cc_out) override
{
for (auto& cc : cc_out) {
for (auto& pdsch : cc.res.dl->phy.pdsch) {
if (pdsch.sch.grant.rnti_type == srsran_rnti_type_c or pdsch.sch.grant.rnti_type == srsran_rnti_type_tc) {
ue_metrics[pdsch.sch.grant.rnti].nof_dl_txs++;
ue_metrics[pdsch.sch.grant.rnti].nof_dl_bytes += pdsch.sch.grant.tb[0].tbs / 8u;
}
}
for (auto& pusch : cc.res.ul->pusch) {
if (pusch.sch.grant.rnti_type == srsran_rnti_type_c or pusch.sch.grant.rnti_type == srsran_rnti_type_tc) {
ue_metrics[pusch.sch.grant.rnti].nof_ul_txs++;
ue_metrics[pusch.sch.grant.rnti].nof_ul_bytes += pusch.sch.grant.tb[0].tbs / 8u;
}
}
}
}
void print_results()
{
srslog::flush();
fmt::print("SCHED UE metrics:\n");
for (auto& u : ue_metrics) {
fmt::print(" 0x{:x}: nof_txs=({}, {}), nof_bytes=({}, {})\n",
u.first,
u.second.nof_dl_txs,
u.second.nof_ul_txs,
u.second.nof_dl_bytes,
u.second.nof_ul_bytes);
}
}
struct sched_ue_metrics {
uint32_t nof_dl_txs = 0, nof_ul_txs = 0;
uint64_t nof_dl_bytes = 0, nof_ul_bytes = 0;
};
std::map<uint16_t, sched_ue_metrics> ue_metrics;
};
struct sched_event_t {
uint32_t slot_count;
std::function<void(sched_nr_base_tester&)> run;
@ -45,7 +89,13 @@ sched_event_t ue_cfg(uint32_t slot_count, uint16_t rnti, const sched_nr_ue_cfg_t
return sched_event_t{slot_count, task};
}
void run_sched_nr_test()
sched_event_t add_rlc_dl_bytes(uint32_t slot_count, uint16_t rnti, uint32_t lcid, uint32_t pdu_size)
{
auto task = [rnti, pdu_size, lcid](sched_nr_base_tester& tester) { tester.add_rlc_dl_bytes(rnti, lcid, pdu_size); };
return sched_event_t{slot_count, task};
}
void test_sched_nr_no_data()
{
uint32_t max_nof_ttis = 1000, nof_sectors = 1;
uint16_t rnti = 0x4601;
@ -54,8 +104,8 @@ void run_sched_nr_test()
cfg.auto_refill_buffer = false;
std::vector<sched_nr_interface::cell_cfg_t> cells_cfg = get_default_cells_cfg(nof_sectors);
std::string test_name = "Serialized Test";
sched_nr_base_tester tester(cfg, cells_cfg, test_name);
std::string test_name = "Test with no data";
sched_tester tester(cfg, cells_cfg, test_name);
/* Set events */
std::deque<sched_event_t> events;
@ -76,6 +126,55 @@ void run_sched_nr_test()
// call sched
tester.run_slot(slot_tx);
}
tester.print_results();
// Since DL buffers were not externally updated, we should only see Msg4 as DL tx
TESTASSERT_EQ(1, tester.ue_metrics[rnti].nof_dl_txs);
// Since UL buffers were not externally updated, we should only see Msg3 as UL tx
TESTASSERT_EQ(1, tester.ue_metrics[rnti].nof_ul_txs);
}
void test_sched_nr_data()
{
uint32_t max_nof_ttis = 1000, nof_sectors = 1;
uint16_t rnti = 0x4601;
uint32_t nof_dl_bytes_to_tx = 2e6;
sched_nr_interface::sched_args_t cfg;
cfg.auto_refill_buffer = false;
std::vector<sched_nr_interface::cell_cfg_t> cells_cfg = get_default_cells_cfg(nof_sectors);
std::string test_name = "Test with data";
sched_tester tester(cfg, cells_cfg, test_name);
/* Set events */
std::deque<sched_event_t> events;
events.push_back(add_user(9, rnti, 0));
events.push_back(ue_cfg(20, rnti, get_default_ue_cfg(1)));
events.push_back(add_rlc_dl_bytes(50, rnti, 0, nof_dl_bytes_to_tx));
/* Run Test */
for (uint32_t nof_slots = 0; nof_slots < max_nof_ttis; ++nof_slots) {
slot_point slot_rx(0, nof_slots % 10240);
slot_point slot_tx = slot_rx + TX_ENB_DELAY;
// run events
while (not events.empty() and events.front().slot_count <= nof_slots) {
events.front().run(tester);
events.pop_front();
}
// call sched
tester.run_slot(slot_tx);
}
tester.print_results();
TESTASSERT(tester.ue_metrics[rnti].nof_dl_txs > 1);
TESTASSERT(tester.ue_metrics[rnti].nof_dl_bytes >= nof_dl_bytes_to_tx);
// Since UL buffers were not externally updated, we should only see Msg3 as UL tx
TESTASSERT_EQ(1, tester.ue_metrics[rnti].nof_ul_txs);
}
} // namespace srsenb
@ -92,5 +191,6 @@ int main()
// Start the log backend.
srslog::init();
srsenb::run_sched_nr_test();
srsenb::test_sched_nr_no_data();
srsenb::test_sched_nr_data();
}

@ -58,7 +58,7 @@ void ue_nr::reset()
nof_failures = 0;
}
void ue_nr::ue_cfg(const sched_interface::ue_cfg_t& ue_cfg)
void ue_nr::ue_cfg(const sched_nr_interface::ue_cfg_t& ue_cfg)
{
// nop
}

@ -21,5 +21,9 @@
add_library(rrc_nr_test_helpers rrc_nr_test_helpers.cc)
add_executable(rrc_nr_test rrc_nr_test.cc)
target_link_libraries(rrc_nr_test srsgnb_rrc srsgnb_rrc_config_utils srsran_common rrc_nr_asn1 rrc_asn1 rrc_nr_test_helpers ${ATOMIC_LIBS})
add_test(rrc_nr_test rrc_nr_test)
target_link_libraries(rrc_nr_test srsgnb_rrc srsgnb_rrc_config_utils srsran_common rrc_nr_asn1 rrc_nr_test_helpers ${ATOMIC_LIBS})
add_test(rrc_nr_test rrc_nr_test)
add_executable(rrc_nr_core_test rrc_nr_core_test.cc)
target_link_libraries(rrc_nr_core_test srsgnb_rrc srsgnb_rrc_config_utils srsran_common rrc_nr_asn1 test_helpers rrc_nr_test_helpers srsgnb_ngap ngap_nr_asn1 srsran_gtpu srsenb_upper ${SCTP_LIBRARIES} ${ATOMIC_LIBS} ${Boost_LIBRARIES})

@ -0,0 +1,224 @@
/**
*
* \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 "rrc_nr_test_helpers.h"
#include "srsenb/hdr/enb.h"
#include "srsenb/hdr/stack/upper/gtpu.h"
#include "srsenb/test/rrc/test_helpers.h"
#include "srsgnb/hdr/stack/ngap/ngap.h"
#include "srsgnb/hdr/stack/rrc/rrc_nr_config_utils.h"
#include "srsgnb/src/stack/mac/test/sched_nr_cfg_generators.h"
#include "srsran/common/network_utils.h"
#include "srsran/common/test_common.h"
#include "srsran/interfaces/gnb_rrc_nr_interfaces.h"
#include "srsran/upper/gtpu.h"
#include <boost/program_options.hpp>
#include <boost/program_options/parsers.hpp>
#include <iostream>
using namespace asn1::rrc_nr;
namespace bpo = boost::program_options;
namespace srsenb { // namespace srsenb
void parse_args(ngap_args_t* ngap_args, int argc, char* argv[])
{
// temporary helpers for conversion
std::string config_file;
std::string gnb_id{};
std::string gnb_cell_id{};
std::string gnb_tac{};
std::string gnb_mcc{};
std::string gnb_mnc{};
// Command line only options
bpo::options_description general("General options");
general.add_options()("help,h", "Produce help message");
// Command line or config file options
bpo::options_description common("Configuration options");
// clang-format off
common.add_options()
("gnb_id", bpo::value<std::string>(&gnb_id)->default_value("0x0"), "gnb ID")
("name", bpo::value<std::string>(&ngap_args->gnb_name)->default_value("srsgnb01"), "gnb Name")
("cell_id", bpo::value<std::string>(&gnb_cell_id)->default_value("0x0"), "Cell ID")
("tac", bpo::value<std::string>(&gnb_tac)->default_value("0x0"), "Tracking Area Code")
("mcc", bpo::value<std::string>(&gnb_mcc)->default_value("001"), "Mobile Country Code")
("mnc", bpo::value<std::string>(&gnb_mnc)->default_value("01"), "Mobile Network Code")
("amf_addr", bpo::value<std::string>(&ngap_args->amf_addr)->default_value("127.0.0.1"), "IP address of AMF for NG connection")
("n1c_bind_addr", bpo::value<std::string>(&ngap_args->ngc_bind_addr)->default_value("192.168.3.1"), "Local IP address to bind for NGAP connection")
("gtp_bind_addr", bpo::value<std::string>(&ngap_args->gtp_bind_addr)->default_value("192.168.3.1"), "Local IP address to bind for GTP connection");
bpo::options_description position("Positional options");
// clang-format on
bpo::positional_options_description p{};
p.add("config_file", -1);
bpo::options_description cmdline_options;
cmdline_options.add(common).add(position).add(general);
bpo::variables_map vm{};
try {
bpo::store(bpo::command_line_parser(argc, argv).options(cmdline_options).positional(p).run(), vm);
bpo::notify(vm);
} catch (bpo::error& e) {
std::cerr << e.what() << std::endl;
exit(1);
}
if (vm.count("help")) {
std::cout << common << std::endl << general << std::endl;
exit(0);
}
// Convert hex strings
{
std::stringstream sstr{};
sstr << std::hex << vm["gnb_id"].as<std::string>();
sstr >> ngap_args->gnb_id;
}
{
std::stringstream sstr{};
sstr << std::hex << vm["cell_id"].as<std::string>();
uint16_t tmp; // Need intermediate uint16_t as uint8_t is treated as char
sstr >> tmp;
ngap_args->cell_id = tmp;
}
{
std::stringstream sstr{};
sstr << std::hex << vm["tac"].as<std::string>();
sstr >> ngap_args->tac;
}
// Convert MCC/MNC strings
if (!srsran::string_to_mcc(gnb_mcc, &ngap_args->mcc)) {
std::cout << "Error parsing mcc:" << gnb_mcc << " - must be a 3-digit string." << std::endl;
}
if (!srsran::string_to_mnc(gnb_mnc, &ngap_args->mnc)) {
std::cout << "Error parsing mnc:" << gnb_mnc << " - must be a 2 or 3-digit string." << std::endl;
}
}
void test_rrc_sa_ngap_integration(ngap_args_t ngap_args)
{
// This takes the existing RRC-NR tests and exercises the NGAP integration with a real core network.
// The test currently runs down untill the RRCReconfiguration.
srsran::task_scheduler task_sched;
phy_nr_dummy phy_obj;
mac_nr_dummy mac_obj;
rlc_nr_rrc_tester rlc_obj;
pdcp_nr_rrc_tester pdcp_obj;
rrc_nr rrc_obj(&task_sched);
enb_bearer_manager bearer_mapper;
// NGAP Setup
auto& ngap_logger = srslog::fetch_basic_logger("NGAP");
ngap_logger.set_level(srslog::basic_levels::debug);
ngap_logger.set_hex_dump_max_size(-1);
auto& gtpu_logger = srslog::fetch_basic_logger("GTPU");
gtpu_logger.set_level(srslog::basic_levels::debug);
gtpu_logger.set_hex_dump_max_size(-1);
srsran::socket_manager rx_sockets;
srsenb::ngap ngap_obj(&task_sched, ngap_logger, &rx_sockets);
srsenb::gtpu gtpu_obj(&task_sched, gtpu_logger, &rx_sockets);
gtpu_args_t gtpu_args;
gtpu_args.embms_enable = false;
gtpu_args.mme_addr = ngap_args.amf_addr;
gtpu_args.gtp_bind_addr = ngap_args.gtp_bind_addr;
TESTASSERT(gtpu_obj.init(gtpu_args, &pdcp_obj) == SRSRAN_SUCCESS);
TESTASSERT(ngap_obj.init(ngap_args, &rrc_obj, &gtpu_obj) == SRSRAN_SUCCESS);
task_sched.run_next_task();
// set cfg
rrc_nr_cfg_t rrc_cfg_nr = rrc_nr_cfg_t{};
rrc_cfg_nr.cell_list.emplace_back();
generate_default_nr_cell(rrc_cfg_nr.cell_list[0]);
rrc_cfg_nr.cell_list[0].phy_cell.carrier.pci = 500;
rrc_cfg_nr.cell_list[0].dl_arfcn = 368500;
rrc_cfg_nr.cell_list[0].band = 3;
rrc_cfg_nr.cell_list[0].phy_cell.carrier.nof_prb = 52;
rrc_cfg_nr.cell_list[0].duplex_mode = SRSRAN_DUPLEX_MODE_FDD;
rrc_cfg_nr.is_standalone = true;
set_derived_nr_cell_params(rrc_cfg_nr.is_standalone, rrc_cfg_nr.cell_list[0]);
srsran_assert(check_rrc_nr_cfg_valid(rrc_cfg_nr) == SRSRAN_SUCCESS, "Invalid RRC NR configuration");
TESTASSERT(rrc_obj.init(rrc_cfg_nr, &phy_obj, &mac_obj, &rlc_obj, &pdcp_obj, &ngap_obj, bearer_mapper, nullptr) ==
SRSRAN_SUCCESS);
sched_nr_ue_cfg_t uecfg = get_default_ue_cfg(1);
uecfg.phy_cfg.pdcch = rrc_cfg_nr.cell_list[0].phy_cell.pdcch;
uecfg.phy_cfg.pdcch.search_space_present[2] = false;
TESTASSERT_SUCCESS(rrc_obj.add_user(0x4601, uecfg));
// RRCSetupComplete triggers NGAP Initial UE Message with NAS-PDU: Registration Request
ngap_rrc_tester ngap_dummy;
test_rrc_nr_connection_establishment(task_sched, rrc_obj, rlc_obj, mac_obj, ngap_dummy, 0x4601);
task_sched.run_next_task();
// ULInformationTransfer -> UplinkNASTransport(NAS Authentication Response)
srsran::unique_byte_buffer_t auth_resp_pdu;
auth_resp_pdu = srsran::make_byte_buffer();
asn1::bit_ref bref_ar{auth_resp_pdu->data(), auth_resp_pdu->get_tailroom()};
ul_dcch_msg_s ul_dcch_msg_auth_resp;
ul_dcch_msg_auth_resp.msg.set_c1().set_ul_info_transfer().crit_exts.set_ul_info_transfer();
ul_dcch_msg_auth_resp.msg.c1().ul_info_transfer().crit_exts.ul_info_transfer().ded_nas_msg_present = true;
ul_dcch_msg_auth_resp.msg.c1().ul_info_transfer().crit_exts.ul_info_transfer().ded_nas_msg.from_string(
"7e00572d10db165fffdb7b74c326e3fc3f154117fe");
TESTASSERT_SUCCESS(ul_dcch_msg_auth_resp.pack(bref_ar));
auth_resp_pdu->N_bytes = bref_ar.distance_bytes();
rrc_obj.write_pdu(0x4601, 1, std::move(auth_resp_pdu));
task_sched.run_next_task();
// ULInformationTransfer -> UplinkNASTransport(NAS Security Mode Complete)
srsran::unique_byte_buffer_t sec_complete_pdu;
sec_complete_pdu = srsran::make_byte_buffer();
asn1::bit_ref bref_smc{sec_complete_pdu->data(), sec_complete_pdu->get_tailroom()};
ul_dcch_msg_s ul_dcch_msg_smc;
ul_dcch_msg_smc.msg.set_c1().set_ul_info_transfer().crit_exts.set_ul_info_transfer();
ul_dcch_msg_smc.msg.c1().ul_info_transfer().crit_exts.ul_info_transfer().ded_nas_msg_present = true;
ul_dcch_msg_smc.msg.c1().ul_info_transfer().crit_exts.ul_info_transfer().ded_nas_msg.from_string(
"7e046b3737af017e005e7700093535940096783351f37100237e004179000d0100f11000000000103254760810030000002e02e0602f0201"
"01530100");
TESTASSERT_SUCCESS(ul_dcch_msg_smc.pack(bref_smc));
sec_complete_pdu->N_bytes = bref_smc.distance_bytes();
rrc_obj.write_pdu(0x4601, 1, std::move(sec_complete_pdu));
task_sched.run_next_task();
test_rrc_nr_security_mode_cmd(task_sched, rrc_obj, pdcp_obj, 0x4601);
}
} // namespace srsenb
int main(int argc, char** argv)
{
auto& logger = srslog::fetch_basic_logger("ASN1");
logger.set_level(srslog::basic_levels::info);
auto& rrc_logger = srslog::fetch_basic_logger("RRC-NR");
rrc_logger.set_level(srslog::basic_levels::debug);
srslog::init();
ngap_args_t ngap_args = {};
srsenb::parse_args(&ngap_args, argc, argv);
srsenb::test_rrc_sa_ngap_integration(ngap_args);
return SRSRAN_SUCCESS;
}

@ -0,0 +1,76 @@
/**
*
* \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_DUMMY_PHY_H
#define SRSRAN_DUMMY_PHY_H
#include "srsran/interfaces/phy_interface_types.h"
#include "srsue/hdr/phy/ue_phy_base.h"
namespace srsue {
class dummy_phy final : public ue_phy_base, public phy_interface_stack_lte
{
public:
// ue_phy_base
std::string get_type() final { return "dummy_phy"; }
void stop() final {}
void wait_initialize() final {}
bool is_initialized() final { return false; }
void start_plot() final {}
void get_metrics(const srsran::srsran_rat_t& rat, phy_metrics_t* m) final {}
// phy_interface_stack_lte
void prach_send(uint32_t preamble_idx, int allowed_subframe, float target_power_dbm, float ta_base_sec = 0.0f) final
{}
prach_info_t prach_get_info() final { return {}; }
/* Indicates the transmission of a SR signal in the next opportunity */
void sr_send() final {}
int sr_last_tx_tti() final { return 0; }
void set_mch_period_stop(uint32_t stop) final {}
bool set_config(const srsran::phy_cfg_t& config, uint32_t cc_idx = 0) final { return false; }
bool set_scell(srsran_cell_t cell_info, uint32_t cc_idx, uint32_t earfcn) final { return false; }
void set_config_tdd(srsran_tdd_config_t& tdd_config) final {}
void set_config_mbsfn_sib2(srsran::mbsfn_sf_cfg_t* cfg_list, uint32_t nof_cfgs) final {}
void set_config_mbsfn_sib13(const srsran::sib13_t& sib13) final {}
void set_config_mbsfn_mcch(const srsran::mcch_msg_t& mcch) final {}
void deactivate_scells() final {}
/* Measurements interface */
void set_cells_to_meas(uint32_t earfcn, const std::set<uint32_t>& pci) final {}
void meas_stop() final {}
/* Cell search and selection procedures */
bool cell_search() final { return false; }
bool cell_select(phy_cell_t cell) final { return false; }
bool cell_is_camping() final { return false; }
/* Time advance commands */
void set_timeadv_rar(uint32_t tti, uint32_t ta_cmd) final {}
void set_timeadv(uint32_t tti, uint32_t ta_cmd) final {}
/* Activate / Disactivate SCell*/
void set_activation_deactivation_scell(uint32_t cmd, uint32_t tti) final {}
/* Sets RAR dci payload */
void set_rar_grant(uint8_t grant_payload[SRSRAN_RAR_GRANT_LEN], uint16_t rnti) final {}
uint32_t get_current_tti() final { return 0; }
float get_phr() final { return 0.0; }
float get_pathloss_db() final { return 0.0; }
};
} // namespace srsue
#endif // SRSRAN_DUMMY_PHY_H

@ -0,0 +1,61 @@
/**
*
* \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_CELL_SEARCH_H
#define SRSUE_CELL_SEARCH_H
#include "srsran/interfaces/radio_interfaces.h"
#include "srsran/interfaces/ue_nr_interfaces.h"
#include "srsran/srsran.h"
namespace srsue {
namespace nr {
class cell_search
{
public:
struct args_t {
double max_srate_hz;
srsran_subcarrier_spacing_t ssb_min_scs = srsran_subcarrier_spacing_15kHz;
};
struct cfg_t {
double srate_hz;
double center_freq_hz;
double ssb_freq_hz;
srsran_subcarrier_spacing_t ssb_scs;
srsran_ssb_patern_t ssb_pattern;
srsran_duplex_mode_t duplex_mode;
};
struct ret_t {
enum { CELL_FOUND = 1, CELL_NOT_FOUND = 0, ERROR = -1 } result;
srsran_ssb_search_res_t ssb_res;
};
cell_search(srslog::basic_logger& logger);
~cell_search();
bool init(const args_t& args);
bool start(const cfg_t& cfg);
ret_t run_slot(const cf_t* buffer, uint32_t slot_sz);
void reset();
private:
srslog::basic_logger& logger;
srsran_ssb_t ssb = {};
};
} // namespace nr
} // namespace srsue
#endif // SRSUE_CELL_SEARCH_H

@ -0,0 +1,62 @@
/**
*
* \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_SLOT_SYNC_H
#define SRSUE_SLOT_SYNC_H
#include "srsran/interfaces/radio_interfaces.h"
#include "srsran/interfaces/ue_nr_interfaces.h"
#include "srsran/radio/rf_buffer.h"
#include "srsran/radio/rf_timestamp.h"
#include "srsran/srsran.h"
namespace srsue {
namespace nr {
class slot_sync
{
public:
struct args_t {
double max_srate_hz = 1.92e6;
uint32_t nof_rx_channels = 1;
srsran_subcarrier_spacing_t ssb_min_scs = srsran_subcarrier_spacing_15kHz;
bool disable_cfo = false;
float pbch_dmrs_thr = 0.0f; ///< PBCH DMRS correlation detection threshold (0 means auto)
float cfo_alpha = 0.0f; ///< CFO averaging alpha (0 means auto)
int thread_priority = -1;
};
slot_sync(srslog::basic_logger& logger);
~slot_sync();
bool init(const args_t& args, stack_interface_phy_nr* stack_, srsran::radio_interface_phy* radio_);
int recv_callback(srsran::rf_buffer_t& rf_buffer, srsran_timestamp_t* timestamp);
void run_stack_tti();
private:
const static int MIN_TTI_JUMP = 1; ///< Time gap reported to stack after receiving subframe
const static int MAX_TTI_JUMP = 1000; ///< Maximum time gap tolerance in RF stream metadata
enum { SEARCHING = 0, CAMPING } state = SEARCHING;
srslog::basic_logger& logger;
stack_interface_phy_nr* stack = nullptr;
srsran::radio_interface_phy* radio = nullptr;
srsran::rf_timestamp_t last_rx_time;
srsran_ue_sync_nr_t ue_sync_nr = {};
srsran_timestamp_t stack_tti_ts_new = {};
srsran_timestamp_t stack_tti_ts = {};
bool forced_rx_time_init = true; // Rx time sync after first receive from radio
uint32_t tti = 0;
};
} // namespace nr
} // namespace srsue
#endif // SRSUE_SLOT_SYNC_H

@ -0,0 +1,122 @@
/**
*
* \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_SYNC_NR_SA_H
#define SRSUE_SYNC_NR_SA_H
#include "cell_search.h"
#include "slot_sync.h"
#include "srsran/common/threads.h"
#include "srsran/interfaces/radio_interfaces.h"
#include "srsran/interfaces/ue_nr_interfaces.h"
#include "srsran/radio/rf_buffer.h"
#include "srsran/radio/rf_timestamp.h"
#include "srsran/srslog/logger.h"
#include "srsran/srsran.h"
#include "srsue/hdr/phy/sync_state.h"
#include "worker_pool.h"
#include <condition_variable>
#include <mutex>
#include <srsran/common/tti_sempahore.h>
namespace srsue {
namespace nr {
/**
* @brief NR Standalone synchronization class
*/
class sync_sa : public srsran::thread, public srsran::phy_common_interface
{
public:
struct args_t {
double srate_hz = 61.44e6;
srsran_subcarrier_spacing_t ssb_min_scs = srsran_subcarrier_spacing_15kHz;
uint32_t nof_rx_channels = 1;
bool disable_cfo = false;
float pbch_dmrs_thr = 0.0f; ///< PBCH DMRS correlation detection threshold (0 means auto)
float cfo_alpha = 0.0f; ///< CFO averaging alpha (0 means auto)
int thread_priority = 1;
cell_search::args_t get_cell_search() const
{
cell_search::args_t ret = {};
ret.max_srate_hz = srate_hz;
return ret;
}
slot_sync::args_t get_slot_sync() const
{
slot_sync::args_t ret = {};
ret.max_srate_hz = srate_hz;
ret.nof_rx_channels = nof_rx_channels;
ret.ssb_min_scs = ssb_min_scs;
return ret;
}
};
sync_sa(srslog::basic_logger& logger, worker_pool& workers_);
~sync_sa();
bool init(const args_t& args_, stack_interface_phy_nr* stack_, srsran::radio_interface_phy* radio_);
bool reset();
void stop();
sync_state::state_t get_state();
// The following methods control the SYNC state machine
void cell_go_idle();
cell_search::ret_t cell_search_run(const cell_search::cfg_t& cfg);
bool cell_select_run(const phy_interface_rrc_nr::cell_select_args_t& req);
void worker_end(const worker_context_t& w_ctx, const bool& tx_enable, srsran::rf_buffer_t& buffer) override;
private:
stack_interface_phy_nr* stack = nullptr; ///< Stand-Alone RRC interface
srsran::radio_interface_phy* radio = nullptr; ///< Radio object
srslog::basic_logger& logger; ///< General PHY logger
worker_pool& workers;
// FSM that manages RRC commands for cell search/select/sync procedures
std::mutex rrc_mutex;
enum { PROC_IDLE = 0, PROC_SELECT_RUNNING, PROC_SEARCH_RUNNING } rrc_proc_state = PROC_IDLE;
sync_state phy_state;
std::atomic<bool> running = {false};
cf_t* rx_buffer = nullptr;
uint32_t slot_sz = 0; ///< Subframe size (1-ms)
uint32_t tti = 0;
srsran::tti_semaphore<void*> tti_semaphore;
srsran::rf_timestamp_t last_rx_time;
bool is_pending_tx_end = false;
uint32_t cell_search_nof_trials = 0;
const static uint32_t cell_search_max_trials = 100;
cell_search::ret_t cs_ret;
cell_search searcher;
slot_sync slot_synchronizer;
// FSM States
bool wait_idle();
void run_state_idle();
void run_state_cell_search();
void run_state_cell_select();
void run_state_cell_camping();
int radio_recv_fnc(srsran::rf_buffer_t& data, srsran_timestamp_t* rx_time);
void run_stack_tti();
void run_thread() override;
};
} // namespace nr
} // namespace srsue
#endif // SRSUE_SYNC_NR_SA_H

@ -29,7 +29,7 @@
namespace srsue {
namespace nr {
class worker_pool : public srsue::phy_interface_stack_nr
class worker_pool
{
private:
srslog::basic_logger& logger;
@ -49,7 +49,7 @@ private:
public:
sf_worker* operator[](std::size_t pos) { return workers.at(pos).get(); }
worker_pool(uint32_t max_workers);
worker_pool(srslog::basic_logger& logger_, uint32_t max_workers);
bool init(const phy_args_nr_t& args_, srsran::phy_common_interface& common, stack_interface_phy_nr* stack_, int prio);
sf_worker* wait_worker(uint32_t tti);
void start_worker(sf_worker* w);
@ -57,16 +57,15 @@ public:
void send_prach(const uint32_t prach_occasion,
const int preamble_index,
const float preamble_received_target_power,
const float ta_base_sec = 0.0f) override;
int set_ul_grant(uint32_t rx_tti,
std::array<uint8_t, SRSRAN_RAR_UL_GRANT_NBITS> array,
uint16_t rnti,
srsran_rnti_type_t rnti_type) override;
bool set_config(const srsran::phy_cfg_nr_t& cfg) override;
bool has_valid_sr_resource(uint32_t sr_id) override;
void clear_pending_grants() override;
const float ta_base_sec = 0.0f);
int set_rar_grant(uint32_t rx_tti,
std::array<uint8_t, SRSRAN_RAR_UL_GRANT_NBITS> array,
uint16_t rnti,
srsran_rnti_type_t rnti_type);
bool set_config(const srsran::phy_cfg_nr_t& cfg);
bool has_valid_sr_resource(uint32_t sr_id);
void clear_pending_grants();
void get_metrics(phy_metrics_t& m);
int tx_request(const tx_request_t& request) override;
/**
* @brief Sets external CFO to compensate UL signal frequency offset

@ -34,53 +34,25 @@
#include "srsran/srsran.h"
#include "srsue/hdr/phy/lte/worker_pool.h"
#include "srsue/hdr/phy/nr/worker_pool.h"
#include "srsue/hdr/phy/ue_lte_phy_base.h"
#include "srsue/hdr/phy/ue_nr_phy_base.h"
#include "srsue/hdr/phy/ue_phy_base.h"
#include "sync.h"
namespace srsue {
typedef _Complex float cf_t;
class phy_cmd_proc : public srsran::thread
{
public:
phy_cmd_proc() : thread("PHY_CMD") { start(); }
~phy_cmd_proc() { stop(); }
void add_cmd(std::function<void(void)> cmd) { cmd_queue.push(cmd); }
void stop()
{
if (running) {
add_cmd([this]() { running = false; });
wait_thread_finish();
}
}
private:
void run_thread()
{
std::function<void(void)> cmd;
while (running) {
cmd = cmd_queue.wait_pop();
cmd();
}
}
bool running = true;
// Queue for commands
srsran::block_queue<std::function<void(void)> > cmd_queue;
};
class phy final : public ue_lte_phy_base, public ue_nr_phy_base, public srsran::thread
class phy final : public ue_phy_base,
public phy_interface_stack_lte,
public phy_interface_stack_nr,
public srsran::phy_interface_radio,
public srsran::thread
{
public:
explicit phy() :
logger_phy(srslog::fetch_basic_logger("PHY")),
logger_phy_lib(srslog::fetch_basic_logger("PHY_LIB")),
lte_workers(MAX_WORKERS),
nr_workers(MAX_WORKERS),
nr_workers(logger_phy, MAX_WORKERS),
common(logger_phy),
sfsync(logger_phy, logger_phy_lib),
prach_buffer(logger_phy),
@ -89,16 +61,13 @@ public:
~phy() final { stop(); }
// Init defined in base class
int init(const phy_args_t& args_) final;
// Init for LTE PHYs
int init(const phy_args_t& args_, stack_interface_phy_lte* stack_, srsran::radio_interface_phy* radio_) final;
int init(const phy_args_t& args_, stack_interface_phy_lte* stack_, srsran::radio_interface_phy* radio_);
void stop() final;
void wait_initialize() final;
bool is_initiated();
bool is_initialized() final;
void get_metrics(const srsran::srsran_rat_t& rat, phy_metrics_t* m) final;
void srsran_phy_logger(phy_logger_level_t log_level, char* str);
@ -172,20 +141,24 @@ public:
std::string get_type() final { return "lte_soft"; }
int init(const phy_args_nr_t& args_, stack_interface_phy_nr* stack_, srsran::radio_interface_phy* radio_) final;
/********** NR INTERFACE ********************/
int init(const phy_args_nr_t& args_, stack_interface_phy_nr* stack_, srsran::radio_interface_phy* radio_);
bool set_config(const srsran::phy_cfg_nr_t& cfg) final;
int set_ul_grant(uint32_t rx_tti,
std::array<uint8_t, SRSRAN_RAR_UL_GRANT_NBITS> packed_ul_grant,
uint16_t rnti,
srsran_rnti_type_t rnti_type) final;
void send_prach(const uint32_t prach_occasion,
const int preamble_index,
const float preamble_received_target_power,
const float ta_base_sec = 0.0f) final;
int tx_request(const tx_request_t& request) final;
void set_earfcn(std::vector<uint32_t> earfcns) final;
void set_earfcn(std::vector<uint32_t> earfcns);
bool has_valid_sr_resource(uint32_t sr_id) final;
void clear_pending_grants() final;
int set_rar_grant(uint32_t rar_slot_idx,
std::array<uint8_t, SRSRAN_RAR_UL_GRANT_NBITS> packed_ul_grant,
uint16_t rnti,
srsran_rnti_type_t rnti_type) final;
phy_nr_state_t get_state() override { return PHY_NR_STATE_IDLE; };
void reset_nr() override{};
bool start_cell_search(const cell_search_args_t& req) override { return false; };
bool start_cell_select(const cell_select_args_t& req) override { return false; };
private:
void run_thread() final;

@ -24,7 +24,9 @@
#include "phy_metrics.h"
#include "srsran/adt/circular_array.h"
#include "srsran/common/block_queue.h"
#include "srsran/common/gen_mch_tables.h"
#include "srsran/common/threads.h"
#include "srsran/common/tti_sempahore.h"
#include "srsran/interfaces/phy_common_interface.h"
#include "srsran/interfaces/phy_interface_types.h"
@ -55,6 +57,37 @@ public:
virtual void set_cfo(float cfo) = 0;
};
class phy_cmd_proc : public srsran::thread
{
public:
phy_cmd_proc() : thread("PHY_CMD") { start(); }
~phy_cmd_proc() { stop(); }
void add_cmd(std::function<void(void)> cmd) { cmd_queue.push(cmd); }
void stop()
{
if (running) {
add_cmd([this]() { running = false; });
wait_thread_finish();
}
}
private:
void run_thread()
{
std::function<void(void)> cmd;
while (running) {
cmd = cmd_queue.wait_pop();
cmd();
}
}
bool running = true;
// Queue for commands
srsran::block_queue<std::function<void(void)> > cmd_queue;
};
/* Subclass that manages variables common to all workers */
class phy_common : public srsran::phy_common_interface
{

@ -0,0 +1,99 @@
/**
*
* \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_PHY_NR_SA_H
#define SRSUE_PHY_NR_SA_H
#include "phy_common.h"
#include "srsran/interfaces/ue_nr_interfaces.h"
#include "srsue/hdr/phy/nr/sync_sa.h"
#include "srsue/hdr/phy/ue_phy_base.h"
namespace srsue {
/**
* @brief NR Standalone PHY
*/
class phy_nr_sa final : public ue_phy_base, public phy_interface_stack_nr, public srsran::phy_interface_radio
{
public:
phy_nr_sa(const char* logname);
~phy_nr_sa() final { stop(); }
int init(const phy_args_nr_t& args_, stack_interface_phy_nr* stack_, srsran::radio_interface_phy* radio_);
void wait_initialize() final;
bool is_initialized() final;
void stop() final;
void reset_nr() final;
void start_plot() final {}
void radio_overflow() final {}
void radio_failure() final {}
std::string get_type() final { return "nr_soft"; }
int set_rar_grant(uint32_t rar_slot_idx,
std::array<uint8_t, SRSRAN_RAR_UL_GRANT_NBITS> packed_ul_grant,
uint16_t rnti,
srsran_rnti_type_t rnti_type) final;
void send_prach(const uint32_t prach_occasion,
const int preamble_index,
const float preamble_received_target_power,
const float ta_base_sec) final;
void set_earfcn(std::vector<uint32_t> earfcns);
bool has_valid_sr_resource(uint32_t sr_id) final;
void clear_pending_grants() final;
bool set_config(const srsran::phy_cfg_nr_t& cfg) final;
phy_nr_state_t get_state() final;
bool start_cell_search(const cell_search_args_t& req) final;
bool start_cell_select(const cell_select_args_t& req) final;
void get_metrics(const srsran::srsran_rat_t& rat, phy_metrics_t* m) final{};
void srsran_phy_logger(phy_logger_level_t log_level, char* str);
private:
srslog::basic_logger& logger;
srslog::basic_logger& logger_phy_lib;
nr::worker_pool workers;
nr::sync_sa sync;
srsran::phy_cfg_nr_t config_nr = {};
phy_args_nr_t args = {};
srsran_carrier_nr_t selected_cell = {};
srsran::radio_interface_phy* radio = nullptr;
stack_interface_phy_nr* stack = nullptr;
std::atomic<bool> is_configured = {false};
// Since cell_search/cell_select operations take a lot of time, we use another queue to process the other commands
// in parallel and avoid accumulating in the queue
phy_cmd_proc cmd_worker_cell, cmd_worker;
// Run initialization functions in the background
void init_background();
std::thread init_thread;
const static int SF_RECV_THREAD_PRIO = 0;
const static int WORKERS_THREAD_PRIO = 2;
const static int MAX_WORKERS = 4;
const static int DEFAULT_WORKERS = 4;
static void set_default_args(phy_args_nr_t& args);
bool check_args(const phy_args_nr_t& args);
};
} // namespace srsue
#endif // SRSUE_PHY_NR_SA_H

@ -22,6 +22,9 @@
#ifndef SRSUE_SYNC_STATE_H
#define SRSUE_SYNC_STATE_H
#include <condition_variable>
#include <mutex>
namespace srsue {
class sync_state
@ -67,6 +70,12 @@ public:
next_state = SFN_SYNC;
}
state_t get_state()
{
std::lock_guard<std::mutex> lock(mutex);
return cur_state;
}
/* Functions to be called from outside the STM thread to instruct the STM to switch state.
* The functions change the state and wait until it has changed it.
*

@ -1,57 +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/.
*
*/
/******************************************************************************
* File: ue_lte_phy_base.h
* Description: Base class for UE LTE PHYs.
*****************************************************************************/
#ifndef SRSUE_UE_LTE_PHY_BASE_H
#define SRSUE_UE_LTE_PHY_BASE_H
#include "srsran/interfaces/radio_interfaces.h"
#include "srsue/hdr/phy/ue_phy_base.h"
namespace srsue {
class stack_interface_phy_lte;
class ue_lte_phy_base : public ue_phy_base, public phy_interface_stack_lte, public srsran::phy_interface_radio
{
public:
ue_lte_phy_base(){};
virtual ~ue_lte_phy_base(){};
virtual std::string get_type() = 0;
virtual int init(const phy_args_t& args_) = 0;
virtual int init(const phy_args_t& args_, stack_interface_phy_lte* stack_, srsran::radio_interface_phy* radio_) = 0;
virtual void stop() = 0;
virtual void wait_initialize() = 0;
virtual void start_plot() = 0;
virtual void get_metrics(const srsran::srsran_rat_t& rat, phy_metrics_t* m) = 0;
};
} // namespace srsue
#endif // SRSUE_UE_LTE_PHY_BASE_H

@ -1,55 +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/.
*
*/
/******************************************************************************
* File: ue_nr_phy_base.h
* Description: Base class for UE NR PHYs.
*****************************************************************************/
#ifndef SRSUE_UE_NR_PHY_BASE_H
#define SRSUE_UE_NR_PHY_BASE_H
#include "srsran/interfaces/radio_interfaces.h"
#include "srsran/interfaces/ue_nr_interfaces.h"
#include "srsue/hdr/phy/ue_phy_base.h"
namespace srsue {
class ue_nr_phy_base : public phy_interface_stack_nr
{
public:
ue_nr_phy_base(){};
virtual ~ue_nr_phy_base() {}
virtual std::string get_type() = 0;
virtual int init(const phy_args_t& args_) = 0;
virtual int init(const phy_args_nr_t& args_, stack_interface_phy_nr* stack_, srsran::radio_interface_phy* radio_) = 0;
virtual void stop() = 0;
virtual void set_earfcn(std::vector<uint32_t> earfcns) = 0;
virtual void get_metrics(const srsran::srsran_rat_t& rat, phy_metrics_t* m) = 0;
};
} // namespace srsue
#endif // SRSUE_UE_NR_PHY_BASE_H

@ -40,11 +40,10 @@ public:
virtual std::string get_type() = 0;
virtual int init(const phy_args_t& args_) = 0;
virtual void stop() = 0;
virtual void wait_initialize() = 0;
virtual bool is_initialized() = 0;
virtual void start_plot() = 0;
virtual void get_metrics(const srsran::srsran_rat_t& rat, phy_metrics_t* m) = 0;

@ -49,19 +49,24 @@ public:
void process_pdus(); /// Called by MAC to process received PDUs
// HARQ interface
void push_pdu(srsran::unique_byte_buffer_t pdu, uint32_t tti);
void push_pdu(srsran::unique_byte_buffer_t pdu, uint32_t tti);
void push_pdu_temp_crnti(srsran::unique_byte_buffer_t pdu, uint32_t tti);
uint64_t get_received_crueid();
private:
// internal helpers
void handle_pdu(srsran::unique_byte_buffer_t pdu);
void handle_pdu(srsran::mac_sch_pdu_nr& pdu_buffer, srsran::unique_byte_buffer_t pdu);
srslog::basic_logger& logger;
rlc_interface_mac* rlc = nullptr;
uint64_t received_crueid = 0;
///< currently only DCH PDUs supported (add BCH, PCH, etc)
srsran::block_queue<srsran::unique_byte_buffer_t> pdu_queue;
srsran::mac_sch_pdu_nr rx_pdu;
srsran::mac_sch_pdu_nr rx_pdu_tcrnti;
};
} // namespace srsue

@ -87,7 +87,7 @@ public:
int setup_lcid(const srsran::logical_channel_config_t& config);
int set_config(const srsran::bsr_cfg_nr_t& bsr_cfg);
int set_config(const srsran::sr_cfg_nr_t& sr_cfg);
void set_config(const srsran::rach_nr_cfg_t& rach_cfg);
void set_config(const srsran::rach_cfg_nr_t& rach_cfg_nr);
int set_config(const srsran::dl_harq_cfg_nr_t& dl_hrq_cfg);
void set_contention_id(const uint64_t ue_identity);
bool set_crnti(const uint16_t crnti);
@ -97,7 +97,7 @@ public:
void start_ra_procedure();
/// Interface for internal procedures (RA, MUX, HARQ)
uint64_t get_contention_id();
bool received_contention_id(uint64_t rx_contention_id);
uint16_t get_crnti();
uint16_t get_temp_crnti();
uint16_t get_csrnti() { return SRSRAN_INVALID_RNTI; }; // SPS not supported
@ -159,6 +159,7 @@ private:
std::atomic<bool> started = {false};
ue_rnti rntis; // thread-safe helper to store RNTIs, contention ID, etc
bool contention_res_successful;
std::array<mac_metrics_t, SRSRAN_MAX_CARRIERS> metrics = {};

@ -33,7 +33,6 @@ class mac_interface_proc_ra_nr
{
public:
// Functions for identity handling, e.g., contention id and c-rnti
virtual uint64_t get_contention_id() = 0;
virtual uint16_t get_crnti() = 0;
virtual bool set_crnti(uint16_t c_rnti) = 0;
@ -89,6 +88,9 @@ public:
// MAC also provides Temp C-RNTI (through RA proc)
virtual uint16_t get_temp_crnti() = 0;
// HARQ can query MAC for current C-RNTI
virtual bool received_contention_id(uint64_t rx_contention_id) = 0;
// MAC provides the Currently Scheduled RNTI (for SPS)
virtual uint16_t get_csrnti() = 0;
};
@ -100,7 +102,9 @@ class demux_interface_harq_nr
{
public:
/// Inform demux unit about a newly decoded TB.
virtual void push_pdu(srsran::unique_byte_buffer_t pdu, uint32_t tti) = 0;
virtual void push_pdu(srsran::unique_byte_buffer_t pdu, uint32_t tti) = 0;
virtual void push_pdu_temp_crnti(srsran::unique_byte_buffer_t pdu, uint32_t tti) = 0;
virtual uint64_t get_received_crueid() = 0;
};
} // namespace srsue

@ -41,7 +41,7 @@ public:
~proc_ra_nr(){};
void init(phy_interface_mac_nr* phy_h_, srsran::ext_task_sched_handle* task_sched_);
void set_config(const srsran::rach_nr_cfg_t& rach_cfg);
void set_config(const srsran::rach_cfg_nr_t& rach_cfg_nr);
bool is_contention_resolution();
bool is_rar_opportunity(uint32_t tti);
@ -49,6 +49,7 @@ public:
uint16_t get_rar_rnti();
bool has_temp_crnti();
uint16_t get_temp_crnti();
void received_contention_resolution(bool is_successful);
// PHY interfaces
void prach_sent(uint32_t tti, uint32_t s_id, uint32_t t_id, uint32_t f_id, uint32_t ul_carrier_id);
@ -73,9 +74,10 @@ private:
int ra_window_length = -1, ra_window_start = -1;
uint16_t rar_rnti = SRSRAN_INVALID_RNTI;
uint16_t temp_crnti = SRSRAN_INVALID_RNTI;
uint16_t transmitted_crnti = SRSRAN_INVALID_RNTI;
std::mutex mutex;
srsran::rach_nr_cfg_t rach_cfg = {};
srsran::rach_cfg_nr_t rach_cfg = {};
bool configured = false;
enum ra_state_t {
@ -119,8 +121,7 @@ private:
void ra_resource_selection();
void ra_preamble_transmission();
void ra_response_reception(const mac_interface_phy_nr::tb_action_dl_result_t& tb);
void ra_contention_resolution();
void ra_contention_resolution(uint64_t rx_contention_id);
void ra_contention_resolution(bool received_con_res_matches_ue_id);
void ra_completion();
void ra_error();
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save