diff --git a/apps/examples/radio/radio_util_sample.cpp b/apps/examples/radio/radio_util_sample.cpp index 621336469a..56b12b7608 100644 --- a/apps/examples/radio/radio_util_sample.cpp +++ b/apps/examples/radio/radio_util_sample.cpp @@ -297,7 +297,7 @@ int main(int argc, char** argv) config.clock.clock = radio_configuration::clock_sources::source::DEFAULT; config.sampling_rate_hz = sampling_rate_hz; config.otw_format = otw_format; - config.tx_mode = enable_discontinuous_tx ? radio_configuration::transmission_mode::discontinuous + config.tx_mode = enable_discontinuous_tx ? radio_configuration::transmission_mode::discontinuous_idle : radio_configuration::transmission_mode::continuous; config.power_ramping_us = power_ramping_us; config.args = device_arguments; diff --git a/apps/units/flexible_du/split_8/helpers/ru_sdr_config.h b/apps/units/flexible_du/split_8/helpers/ru_sdr_config.h index b97ed4626b..870f2357ca 100644 --- a/apps/units/flexible_du/split_8/helpers/ru_sdr_config.h +++ b/apps/units/flexible_du/split_8/helpers/ru_sdr_config.h @@ -28,6 +28,27 @@ namespace srsran { +/// GPIO TX indication sector configuration. +struct ru_sdr_unit_expert_config_gpio_tx_sector { + /// \brief GPIO pin to indicate TX status on (optional) + std::optional gpio_index; + /// \brief Sense of the GPIO pin for indicating TX status + /// + /// True outputs a high when transmitting, false outputs a low when + /// transmitting. + bool sense = false; + /// \brief Source of the GPIO signal, either "idle" or "config" + std::string source = "idle"; + /// \brief Amount of time to put GPIO in TX mode early in microseconds. + float prelude = 0.0F; +}; + +/// GPIO TX indication cell configuration. +struct ru_sdr_unit_expert_config_gpio_tx_cell { + // Per sector config + std::vector sectors; +}; + /// Expert SDR Radio Unit configuration. struct ru_sdr_unit_expert_config { /// System time-based throttling. See \ref lower_phy_configuration::system_time_throttling for more information. @@ -36,8 +57,10 @@ struct ru_sdr_unit_expert_config { /// /// Selects the radio transmission mode between the available options: /// - continuous: The radio keeps the transmitter chain active, even when there are no transmission requests. - /// - discontinuous: The transmitter stops when there is no data to transmit. - /// - same-port: like discontinuous mode, but using the same port to transmit and receive. + /// - discontinuous-idle: The transmitter stops when there is no data to transmit. + /// - discontinuous-config: The transmitter stops when not in TX portion of TDD config. + /// - same-port-idle: like discontinuous-idle mode, but using the same port to transmit and receive. + /// - same-port-config: like discontinuous-config mode, but using the same port to transmit and receive. /// /// \remark The discontinuous and same-port transmission modes may not be supported for some radio devices. std::string transmission_mode = "continuous"; @@ -58,6 +81,15 @@ struct ru_sdr_unit_expert_config { /// \note Powering up the transmitter ahead of time requires starting the transmission earlier, and reduces the time /// window for the radio to transmit the provided samples. float power_ramping_time_us = 0.0F; + /// \brief Time the PPS goes high. Usually 0.0, but on some radios later. + /// + /// E.g. on an N310 this can be 101.3 us. + float pps_time_offset_us = 0.0f; + /// Number of samples to offset the tx/rx time by, used to compensate for + /// radio offsets. Positive means start tx later/report rx as being later. + int sample_offset = 0; + /// \brief The per sector per cell configuration of the TX GPIOs + std::vector gpio_tx_cells; /// \brief Lower PHY downlink baseband buffer size policy. /// /// Selects the size policy of the baseband buffers that pass DL samples from the lower PHY to the radio. diff --git a/apps/units/flexible_du/split_8/helpers/ru_sdr_config_cli11_schema.cpp b/apps/units/flexible_du/split_8/helpers/ru_sdr_config_cli11_schema.cpp index aab667ff2c..66c7eb2a3c 100644 --- a/apps/units/flexible_du/split_8/helpers/ru_sdr_config_cli11_schema.cpp +++ b/apps/units/flexible_du/split_8/helpers/ru_sdr_config_cli11_schema.cpp @@ -41,6 +41,55 @@ static void configure_cli11_amplitude_control_args(CLI::App& app, amplitude_cont ->capture_default_str(); } +static void configure_cli11_ru_sdr_expert_gpio_tx_sector_args(CLI::App& app, ru_sdr_unit_expert_config_gpio_tx_sector& config) { + add_option(app, + "--gpio_index", + config.gpio_index, + "GPIO pin to indicate TX status on (optional)") + ->capture_default_str(); + + add_option(app, + "--sense", + config.sense, + "Sense of the GPIO pin for indicating TX status.\n" + "True outputs a high when transmitting, false outputs a low when " + "transmitting.") + ->capture_default_str(); + + add_option(app, + "--source", + config.source, + "Source of the GPIO value, either 'idle' or 'config'.") + ->capture_default_str(); + + add_option(app, + "--prelude", + config.prelude, + "Amount of time to put GPIO in TX mode early in microseconds.") + ->capture_default_str(); +} + +static void configure_cli11_ru_sdr_expert_gpio_tx_cell_args(CLI::App& app, ru_sdr_unit_expert_config_gpio_tx_cell& config) { + add_option_cell(app, + "--sectors", + [&config](const std::vector& values) { + config.sectors.resize(values.size()); + + for (unsigned i = 0, e = values.size(); i != e; ++i) { + CLI::App subapp("SDR Expert GPIO TX Cell Sector Config", + "SDR Expert GPIO TX Cell Sector Config, item #" + + std::to_string(i)); + subapp.config_formatter(create_yaml_config_parser()); + subapp.allow_config_extras(); + configure_cli11_ru_sdr_expert_gpio_tx_sector_args(subapp, config.sectors[i]); + std::istringstream ss(values[i]); + subapp.parse_from_stream(ss); + } + }, + "GPIO TX sector description") + ->capture_default_str(); +} + static void configure_cli11_ru_sdr_expert_args(CLI::App& app, ru_sdr_unit_expert_config& config) { auto buffer_size_policy_check = [](const std::string& value) -> std::string { @@ -52,10 +101,10 @@ static void configure_cli11_ru_sdr_expert_args(CLI::App& app, ru_sdr_unit_expert }; auto tx_mode_check = [](const std::string& value) -> std::string { - if (value == "continuous" || value == "discontinuous" || value == "same-port") { + if (value == "continuous" || value == "discontinuous-idle" || value == "discontinuous-config" || value == "same-port") { return {}; } - return "Invalid transmission mode. Accepted values [continuous,discontinuous,same-port]"; + return "Invalid transmission mode. Accepted values [continuous,discontinuous-idle,discontinuous-config,same-port-idle,same-port-config]"; }; add_option(app, @@ -67,9 +116,11 @@ static void configure_cli11_ru_sdr_expert_args(CLI::App& app, ru_sdr_unit_expert "--tx_mode", config.transmission_mode, "Selects a radio transmission mode. Discontinuous modes are not supported by all radios.\n" - " continuous: the TX chain is always active.\n" - " discontinuous: the transmitter stops when there is no data to transmit.\n" - " same-port: the radio transmits and receives from the same antenna port.\n") + " continuous: the TX chain is always active.\n" + " discontinuous-idle: the transmitter stops when there is no data to transmit.\n" + " discontinuous-config: the transmitter stops when not in TX of TDD config.\n" + " same-port-idle: the radio transmits and receives from the same antenna port, switching based on transmit data.\n" + " same-port-config: the radio transmits and receives from the same antenna port, switching based on TDD config.\n") ->capture_default_str() ->check(tx_mode_check); @@ -85,6 +136,36 @@ static void configure_cli11_ru_sdr_expert_args(CLI::App& app, ru_sdr_unit_expert "Selects the size policy of the baseband buffers that pass DL samples from the lower PHY to the radio.") ->capture_default_str() ->check(buffer_size_policy_check); + + add_option(app, + "--pps_time_offset_us", + config.pps_time_offset_us, + "Time at which the radio sense the PPS going high. For radios with alignment problems.") + ->capture_default_str(); + + add_option(app, + "--sample_offset", + config.sample_offset, + "Number of samples to offset the tx/rx time by, used to compensate for radio offsets. Positive means start tx later/report rx as being later.") + ->capture_default_str(); + + add_option_cell( + app, + "--gpio_tx_cells", + [&config](const std::vector& values) { + config.gpio_tx_cells.resize(values.size()); + + for (unsigned i = 0, e = values.size(); i != e; ++i) { + CLI::App subapp("SDR Expert GPIO TX Cell Config", + "SDR Expert GPIO TX Cel Config, item #" + std::to_string(i)); + subapp.config_formatter(create_yaml_config_parser()); + subapp.allow_config_extras(); + configure_cli11_ru_sdr_expert_gpio_tx_cell_args(subapp, config.gpio_tx_cells[i]); + std::istringstream ss(values[i]); + subapp.parse_from_stream(ss); + } + }, + "Set the GPIO TX configuration on a per cell basis"); } static void configure_cli11_ru_sdr_args(CLI::App& app, ru_sdr_unit_config& config) diff --git a/apps/units/flexible_du/split_8/helpers/ru_sdr_config_translator.cpp b/apps/units/flexible_du/split_8/helpers/ru_sdr_config_translator.cpp index 4b06a377e3..b833f09d2a 100644 --- a/apps/units/flexible_du/split_8/helpers/ru_sdr_config_translator.cpp +++ b/apps/units/flexible_du/split_8/helpers/ru_sdr_config_translator.cpp @@ -26,6 +26,7 @@ #include "apps/units/flexible_du/du_low/du_low_config.h" #include "ru_sdr_config.h" #include "srsran/du/du_cell_config.h" +#include "srsran/ran/duplex_mode.h" using namespace srsran; @@ -112,6 +113,9 @@ static lower_phy_configuration generate_low_phy_config(const srs_du::du_cell_con sector_config.ul_freq_hz = band_helper::nr_arfcn_to_freq(config.ul_carrier.arfcn_f_ref); sector_config.nof_rx_ports = config.ul_carrier.nof_ant; sector_config.nof_tx_ports = config.dl_carrier.nof_ant; + if (band_helper::get_duplex_mode(config.dl_carrier.band) == srsran::duplex_mode::TDD) { + sector_config.tdd_config.emplace(config.tdd_ul_dl_cfg_common.value()); + } out_cfg.sectors.push_back(sector_config); if (!is_valid_lower_phy_config(out_cfg)) { @@ -169,14 +173,16 @@ static void generate_radio_config(radio_configuration::radio& out_cfg, const ru_sdr_unit_config& ru_cfg, span du_cells) { - out_cfg.args = ru_cfg.device_arguments; - out_cfg.log_level = ru_cfg.loggers.radio_level; - out_cfg.sampling_rate_hz = ru_cfg.srate_MHz * 1e6; - out_cfg.otw_format = radio_configuration::to_otw_format(ru_cfg.otw_format); - out_cfg.clock.clock = radio_configuration::to_clock_source(ru_cfg.clock_source); - out_cfg.clock.sync = radio_configuration::to_clock_source(ru_cfg.synch_source); - out_cfg.tx_mode = radio_configuration::to_transmission_mode(ru_cfg.expert_cfg.transmission_mode); - out_cfg.power_ramping_us = ru_cfg.expert_cfg.power_ramping_time_us; + out_cfg.args = ru_cfg.device_arguments; + out_cfg.log_level = ru_cfg.loggers.radio_level; + out_cfg.sampling_rate_hz = ru_cfg.srate_MHz * 1e6; + out_cfg.otw_format = radio_configuration::to_otw_format(ru_cfg.otw_format); + out_cfg.clock.clock = radio_configuration::to_clock_source(ru_cfg.clock_source); + out_cfg.clock.sync = radio_configuration::to_clock_source(ru_cfg.synch_source); + out_cfg.tx_mode = radio_configuration::to_transmission_mode(ru_cfg.expert_cfg.transmission_mode); + out_cfg.power_ramping_us = ru_cfg.expert_cfg.power_ramping_time_us; + out_cfg.pps_time_offset_us = ru_cfg.expert_cfg.pps_time_offset_us; + out_cfg.sample_offset = ru_cfg.expert_cfg.sample_offset; const std::vector& zmq_tx_addr = extract_zmq_ports(ru_cfg.device_arguments, "tx_port"); const std::vector& zmq_rx_addr = extract_zmq_ports(ru_cfg.device_arguments, "rx_port"); @@ -190,6 +196,15 @@ static void generate_radio_config(radio_configuration::radio& out_cfg, radio_configuration::stream tx_stream_config; radio_configuration::stream rx_stream_config; + std::vector gpio_tx_sectors; + if (sector_id < ru_cfg.expert_cfg.gpio_tx_cells.size()) { + gpio_tx_sectors = ru_cfg.expert_cfg.gpio_tx_cells[sector_id].sectors; + } + + // When multiple sectors are supported outside of lower phy (see + // generate_low_phy_config) change this to support that + gpio_tx_sectors.resize(1); + // Deduce center frequencies. const double cell_tx_freq_Hz = band_helper::nr_arfcn_to_freq(cell.dl_carrier.arfcn_f_ref); const double cell_rx_freq_Hz = band_helper::nr_arfcn_to_freq(cell.ul_carrier.arfcn_f_ref); @@ -218,6 +233,15 @@ static void generate_radio_config(radio_configuration::radio& out_cfg, } tx_ch_config.gain_dB = ru_cfg.tx_gain_dB; + // For now there's only one sector, in future have to work out sector to + // stream map + tx_stream_config.gpio_tx_index = gpio_tx_sectors[0].gpio_index; + tx_stream_config.gpio_tx_sense = gpio_tx_sectors[0].sense; + tx_stream_config.gpio_tx_source = + srsran::radio_configuration:: + to_gpio_source(gpio_tx_sectors[0].source); + tx_stream_config.gpio_tx_prelude = gpio_tx_sectors[0].prelude; + // Add the TX ports. if (ru_cfg.device_driver == "zmq") { if (sector_id * cell.dl_carrier.nof_ant + port_id >= zmq_tx_addr.size()) { diff --git a/apps/units/flexible_du/split_8/helpers/ru_sdr_config_validator.cpp b/apps/units/flexible_du/split_8/helpers/ru_sdr_config_validator.cpp index 4a82f4ef68..fa04492a7d 100644 --- a/apps/units/flexible_du/split_8/helpers/ru_sdr_config_validator.cpp +++ b/apps/units/flexible_du/split_8/helpers/ru_sdr_config_validator.cpp @@ -102,7 +102,9 @@ static bool validate_ru_sdr_appconfig(const ru_sdr_unit_config& const bool discontinuous_transmission = (config.expert_cfg.transmission_mode != "continuous"); for (const auto& cell : cell_config) { - if ((config.expert_cfg.transmission_mode == "same-port") && (cell.dplx_mode == duplex_mode::FDD)) { + if ((config.expert_cfg.transmission_mode == "same-port-idle" + || config.expert_cfg.transmission_mode == "same-port-config") + && (cell.dplx_mode == duplex_mode::FDD)) { fmt::print("same-port transmission mode cannot be used with FDD cell configurations.\n"); return false; } diff --git a/apps/units/flexible_du/split_8/helpers/ru_sdr_config_yaml_writer.cpp b/apps/units/flexible_du/split_8/helpers/ru_sdr_config_yaml_writer.cpp index 3f727ff815..3f03720f59 100644 --- a/apps/units/flexible_du/split_8/helpers/ru_sdr_config_yaml_writer.cpp +++ b/apps/units/flexible_du/split_8/helpers/ru_sdr_config_yaml_writer.cpp @@ -115,6 +115,31 @@ static void fill_ru_sdr_section(YAML::Node node, const ru_sdr_unit_config& confi expert_node["low_phy_dl_throttling"] = config.expert_cfg.lphy_dl_throttling; expert_node["tx_mode"] = config.expert_cfg.transmission_mode; expert_node["power_ramping_time_us"] = config.expert_cfg.power_ramping_time_us; + expert_node["pps_time_offset_us"] = config.expert_cfg.pps_time_offset_us; + expert_node["sample_offset"] = config.expert_cfg.sample_offset; + auto gpio_tx_cells = expert_node["gpio_tx_cells"]; + while (config.expert_cfg.gpio_tx_cells.size() > gpio_tx_cells.size()) { + gpio_tx_cells.push_back(YAML::Node()); + } + for (unsigned i = 0; i != config.expert_cfg.gpio_tx_cells.size(); ++i) { + auto gpio_tx_sectors = gpio_tx_cells[i]["sectors"]; + auto config_gpio_tx_sectors = + config.expert_cfg.gpio_tx_cells[i].sectors; + + while (config_gpio_tx_sectors.size() > gpio_tx_sectors.size()) { + gpio_tx_cells.push_back(YAML::Node()); + } + + for (unsigned j = 0; j != config_gpio_tx_sectors.size(); ++j) { + if (config_gpio_tx_sectors[j].gpio_index.has_value()) { + gpio_tx_sectors[j]["gpio_index"] = + config_gpio_tx_sectors[j].gpio_index.value(); + gpio_tx_sectors[j]["sense"] = config_gpio_tx_sectors[j].sense; + gpio_tx_sectors[j]["source"] = config_gpio_tx_sectors[j].source; + gpio_tx_sectors[j]["prelude"] = config_gpio_tx_sectors[j].prelude; + } + } + } expert_node["dl_buffer_size_policy"] = config.expert_cfg.dl_buffer_size_policy; } } diff --git a/include/srsran/gateways/baseband/baseband_gateway_transmitter_metadata.h b/include/srsran/gateways/baseband/baseband_gateway_transmitter_metadata.h index 4e67e77fe4..a32d2d4a93 100644 --- a/include/srsran/gateways/baseband/baseband_gateway_transmitter_metadata.h +++ b/include/srsran/gateways/baseband/baseband_gateway_transmitter_metadata.h @@ -48,6 +48,16 @@ struct baseband_gateway_transmitter_metadata { /// If present, it is the sample index in which there is no signal until the end of the buffer. Otherwise, the /// baseband buffer contains transmit signal until the last sample. std::optional tx_end; + /// \brief Downlink period start according to the TDD config in samples. + /// + /// If present, sample previous it was not part of the downlink period and it + /// is part of the downlink period. + std::optional dl_config_start; + /// \brief Downlink period end according to the TDD config in samples. + /// + /// If present, sample previous it was part of the downlink period and it is + /// not part of the downlink period. + std::optional dl_config_end; }; } // namespace srsran diff --git a/include/srsran/phy/lower/lower_phy_configuration.h b/include/srsran/phy/lower/lower_phy_configuration.h index 3353330e71..44c6cbda9c 100644 --- a/include/srsran/phy/lower/lower_phy_configuration.h +++ b/include/srsran/phy/lower/lower_phy_configuration.h @@ -35,6 +35,7 @@ #include "srsran/ran/cyclic_prefix.h" #include "srsran/ran/n_ta_offset.h" #include "srsran/ran/subcarrier_spacing.h" +#include "srsran/ran/tdd/tdd_ul_dl_config.h" #include "srsran/srslog/srslog.h" #include "srsran/support/executors/task_executor.h" @@ -52,6 +53,8 @@ struct lower_phy_sector_description { unsigned nof_tx_ports; /// Number of receive ports. unsigned nof_rx_ports; + /// TDD Configuration + std::optional tdd_config; }; /// \brief Lower physical layer baseband gateway buffer size policy. diff --git a/include/srsran/phy/lower/processors/downlink/downlink_processor_factories.h b/include/srsran/phy/lower/processors/downlink/downlink_processor_factories.h index 072e8a5ae8..1bb4e896da 100644 --- a/include/srsran/phy/lower/processors/downlink/downlink_processor_factories.h +++ b/include/srsran/phy/lower/processors/downlink/downlink_processor_factories.h @@ -26,6 +26,7 @@ #include "srsran/phy/lower/processors/downlink/downlink_processor.h" #include "srsran/phy/lower/processors/downlink/pdxch/pdxch_processor_factories.h" #include "srsran/phy/lower/sampling_rate.h" +#include "srsran/ran/tdd/tdd_ul_dl_config.h" #include namespace srsran { @@ -40,6 +41,8 @@ struct downlink_processor_configuration { cyclic_prefix cp; /// Baseband sampling rate. sampling_rate rate; + /// TDD Config + std::optional tdd_config; /// Bandwidth in PRB. unsigned bandwidth_prb; /// Center frequency in Hz. @@ -66,4 +69,4 @@ std::shared_ptr create_downlink_processor_factory_sw(std::shared_ptr pdxch_proc_factory, std::shared_ptr amplitude_control_factory); -} // namespace srsran \ No newline at end of file +} // namespace srsran diff --git a/include/srsran/radio/radio_configuration.h b/include/srsran/radio/radio_configuration.h index 21066f3a11..3d0e9c6655 100644 --- a/include/srsran/radio/radio_configuration.h +++ b/include/srsran/radio/radio_configuration.h @@ -24,6 +24,7 @@ #include "srsran/adt/static_vector.h" #include "srsran/radio/radio_constants.h" +#include "srsran/ran/tdd/tdd_ul_dl_config.h" #include "srsran/srslog/logger.h" #include "srsran/support/error_handling.h" @@ -73,6 +74,15 @@ struct channel { std::string args; }; +/// Source of a GPIO signal +enum class gpio_source { + /// Take the value from whether the transmitter is idle or not together with + /// TDD config + idle = 0, + /// Only use TDD config mode to set the GPIO pin + config +}; + /// Describes a stream configuration common for transmit and receive chains. struct stream { /// Provides the configuration for each channel. The number of elements indicates the number of channels for the @@ -81,6 +91,21 @@ struct stream { /// \brief Indicates any device specific parameters to create the stream. /// \remark Not all driver and/or devices support this feature. std::string args; + /// \brief GPIO pin to indicate TX status on (optional) + std::optional gpio_tx_index; + /// \brief Sense of the GPIO pin for indicating TX status + /// + /// True outputs a high when transmitting, false outputs a low when + /// transmitting. + /// + /// Ignored if gpio_tx_index not set + bool gpio_tx_sense; + /// \brief Source of the GPIO signal, either idle or config + gpio_source gpio_tx_source; + /// \brief Amount of time to put GPIO in TX mode early in microseconds. + /// + /// Ignored if gpio_tx_index not set + float gpio_tx_prelude; }; /// \brief Over the wire format. Indicates the data format baseband samples are transported between the baseband unit @@ -102,9 +127,13 @@ enum class transmission_mode { /// The radio continually transmits and the transmit chain is active even when there are no transmission requests. continuous = 0, /// The transmitter stops when there is no data to transmit. - discontinuous, - /// The radio transmits and receives from the same antenna port. It can only be used with TDD cell configurations. - same_port + discontinuous_idle, + /// The transmitter stops at the end of the TX period of TDD config. + discontinuous_config, + /// The radio transmits and receives from the same antenna port. Based on data to transmit. It can only be used with TDD cell configurations. + same_port_idle, + /// The radio transmits and receives from the same antenna port. Based on TDD config. It can only be used with TDD cell configurations. + same_port_config }; /// Describes the necessary parameters to initialize a radio. @@ -119,6 +148,9 @@ struct radio { static_vector rx_streams; /// Sampling rate in Hz common for all transmit and receive chains. double sampling_rate_hz; + /// Number of samples to offset the tx/rx time by, used to compensate for + /// radio offsets. Positive means start tx later/report rx as being later. + int sample_offset; /// Indicates the baseband signal transport format between the device and the host. over_the_wire_format otw_format; /// \brief Transmission mode. @@ -130,6 +162,10 @@ struct radio { /// transmit chain in order to achieve optimal performance. /// \remark Not all drivers and/or devices support this feature. float power_ramping_us; + /// \brief Time the PPS goes high. Usually 0.0, but on some radios later. + /// + /// E.g. on an N310 this can be 101.3 us. + float pps_time_offset_us = 0.0f; /// \brief Indicates any device specific parameters to create the session. /// \remark Not all driver and/or devices support this feature. std::string args; @@ -179,15 +215,34 @@ inline transmission_mode to_transmission_mode(const std::string& str) if (str == "continuous") { return transmission_mode::continuous; } - if (str == "discontinuous") { - return transmission_mode::discontinuous; + if (str == "discontinuous-idle") { + return transmission_mode::discontinuous_idle; + } + if (str == "discontinuous-config") { + return transmission_mode::discontinuous_config; } - if (str == "same-port") { - return transmission_mode::same_port; + if (str == "same-port-idle") { + return transmission_mode::same_port_idle; + } + if (str == "same-port-config") { + return transmission_mode::same_port_config; } report_error("Invalid transmission mode '{}'.", str); } + +/// Convers a string into a gpio source. +inline gpio_source to_gpio_source(const std::string& str) +{ + if (str == "idle") { + return gpio_source::idle; + } + if (str == "config") { + return gpio_source::config; + } + report_error("Invalid gpio source '{}'.", str); +} + /// Interface for validating a given radio configuration. class validator { diff --git a/include/srsran/ran/tdd/tdd_ul_dl_config.h b/include/srsran/ran/tdd/tdd_ul_dl_config.h index 1920469adc..a632d7b0d2 100644 --- a/include/srsran/ran/tdd/tdd_ul_dl_config.h +++ b/include/srsran/ran/tdd/tdd_ul_dl_config.h @@ -112,4 +112,19 @@ std::optional find_next_tdd_ul_slot(const tdd_ul_dl_config_common& cfg /// start_slot_index being >= TDD period in slots. std::optional find_next_tdd_full_ul_slot(const tdd_ul_dl_config_common& cfg, unsigned start_slot_index = 0); +/// \brief Determines if this is the first DL symbol +/// \return True if this is the first dl symbol (e.g. after UL). Else false. +bool is_first_tdd_dl_symbol(const tdd_ul_dl_config_common& cfg, + unsigned slot_index, + unsigned symbol_index, + cyclic_prefix cp); + +/// \brief Determines if this is the last DL symbol (e.g. before a gap to UL) +/// \return True if this is the last dl symbol (e.g. before swap to ul). +/// Else false. +bool is_last_tdd_dl_symbol(const tdd_ul_dl_config_common& cfg, + unsigned slot_index, + unsigned symbol_index, + cyclic_prefix cp); + } // namespace srsran diff --git a/lib/ofh/timing/realtime_timing_worker.cpp b/lib/ofh/timing/realtime_timing_worker.cpp index 40b365bf6e..dcb0c0e4b0 100644 --- a/lib/ofh/timing/realtime_timing_worker.cpp +++ b/lib/ofh/timing/realtime_timing_worker.cpp @@ -76,6 +76,7 @@ realtime_timing_worker::realtime_timing_worker(srslog::basic_logger& logger logger(logger_), executor(executor_), scs(cfg.scs), + cp(cfg.cp), nof_symbols_per_slot(get_nsymb_per_slot(cfg.cp)), nof_symbols_per_sec(nof_symbols_per_slot * get_nof_slots_per_subframe(scs) * NOF_SUBFRAMES_PER_FRAME * 100), symbol_duration(1e9 / nof_symbols_per_sec), diff --git a/lib/ofh/timing/realtime_timing_worker.h b/lib/ofh/timing/realtime_timing_worker.h index a91d46916b..387a4c499b 100644 --- a/lib/ofh/timing/realtime_timing_worker.h +++ b/lib/ofh/timing/realtime_timing_worker.h @@ -58,6 +58,7 @@ class realtime_timing_worker : public controller, public ota_symbol_boundary_not std::vector ota_notifiers; task_executor& executor; const subcarrier_spacing scs; + const cyclic_prefix cp; const unsigned nof_symbols_per_slot; const unsigned nof_symbols_per_sec; const std::chrono::duration symbol_duration; diff --git a/lib/phy/lower/lower_phy_factory.cpp b/lib/phy/lower/lower_phy_factory.cpp index b4178a4baa..8165b0d2a8 100644 --- a/lib/phy/lower/lower_phy_factory.cpp +++ b/lib/phy/lower/lower_phy_factory.cpp @@ -131,8 +131,9 @@ class lower_phy_factory_sw : public lower_phy_factory // Get transmit time offset between the UL and the DL. int tx_time_offset = get_tx_time_offset(config.time_alignment_calibration, config.ta_offset, config.srate); - // Maximum time delay between reception and transmission in samples (1ms plus the time offset). - unsigned rx_to_tx_max_delay = config.srate.to_kHz() + tx_time_offset; + // Maximum time delay between reception and transmission in samples (1ms + // plus, no time offset as the processor adds this after). + unsigned rx_to_tx_max_delay = config.srate.to_kHz(); // Prepare downlink processor configuration. downlink_processor_configuration dl_proc_config; @@ -140,6 +141,7 @@ class lower_phy_factory_sw : public lower_phy_factory dl_proc_config.scs = config.scs; dl_proc_config.cp = config.cp; dl_proc_config.rate = config.srate; + dl_proc_config.tdd_config = sector.tdd_config; dl_proc_config.bandwidth_prb = sector.bandwidth_rb; dl_proc_config.center_frequency_Hz = sector.dl_freq_hz; dl_proc_config.nof_tx_ports = sector.nof_tx_ports; @@ -177,7 +179,7 @@ class lower_phy_factory_sw : public lower_phy_factory proc_bb_adaptor_config.nof_tx_ports = config.sectors.back().nof_tx_ports; proc_bb_adaptor_config.nof_rx_ports = config.sectors.back().nof_rx_ports; proc_bb_adaptor_config.tx_time_offset = tx_time_offset; - proc_bb_adaptor_config.rx_to_tx_max_delay = config.srate.to_kHz() + proc_bb_adaptor_config.tx_time_offset; + proc_bb_adaptor_config.rx_to_tx_max_delay = config.srate.to_kHz(); proc_bb_adaptor_config.rx_buffer_size = rx_buffer_size; proc_bb_adaptor_config.nof_rx_buffers = std::max(4U, rx_to_tx_max_delay / rx_buffer_size); proc_bb_adaptor_config.tx_buffer_size = tx_buffer_size; diff --git a/lib/phy/lower/processors/downlink/CMakeLists.txt b/lib/phy/lower/processors/downlink/CMakeLists.txt index 061e4cf57f..244bb21f6f 100644 --- a/lib/phy/lower/processors/downlink/CMakeLists.txt +++ b/lib/phy/lower/processors/downlink/CMakeLists.txt @@ -25,4 +25,4 @@ add_library(srsran_lower_phy_downlink_processor STATIC downlink_processor_factories.cpp downlink_processor_impl.cpp) -target_link_libraries(srsran_lower_phy_downlink_processor srsvec) \ No newline at end of file +target_link_libraries(srsran_lower_phy_downlink_processor srsvec srsran_ran) diff --git a/lib/phy/lower/processors/downlink/downlink_processor_baseband_impl.cpp b/lib/phy/lower/processors/downlink/downlink_processor_baseband_impl.cpp index 2fbfaad8d6..c963cccf0b 100644 --- a/lib/phy/lower/processors/downlink/downlink_processor_baseband_impl.cpp +++ b/lib/phy/lower/processors/downlink/downlink_processor_baseband_impl.cpp @@ -39,6 +39,7 @@ downlink_processor_baseband_impl::downlink_processor_baseband_impl( nof_slot_tti_in_advance(config.nof_slot_tti_in_advance), sector_id(config.sector_id), scs(config.scs), + tdd_config(config.tdd_config), nof_rx_ports(config.nof_tx_ports), nof_samples_per_subframe(config.rate.to_kHz()), nof_slots_per_subframe(get_nof_slots_per_subframe(config.scs)), @@ -156,6 +157,16 @@ baseband_gateway_transmitter_metadata downlink_processor_baseband_impl::process( unsigned i_slot = i_sf * nof_slots_per_subframe + i_symbol_sf / nof_symbols_per_slot; unsigned i_symbol = i_symbol_sf % nof_symbols_per_slot; + if (tdd_config.has_value()) { + if (is_first_tdd_dl_symbol(tdd_config.value(), i_slot, i_symbol, cp)) { + md.dl_config_start = proc_timestamp - i_sample_symbol - timestamp; + } + if (is_last_tdd_dl_symbol(tdd_config.value(), i_slot, i_symbol, cp)) { + hold_stop_time = proc_timestamp + symbol_sizes[i_symbol] + - i_sample_symbol - timestamp; + } + } + // Create slot point. slot_point slot(to_numerology_value(scs), i_slot); @@ -213,6 +224,12 @@ baseband_gateway_transmitter_metadata downlink_processor_baseband_impl::process( // Increment output writing index. writing_index += nof_advanced_samples; + + if (hold_stop_time.has_value() + && timestamp + writing_index >= hold_stop_time.value()) { + md.dl_config_end = hold_stop_time.value(); + hold_stop_time.reset(); + } } // Fill the unprocessed regions of the buffer with zeros. diff --git a/lib/phy/lower/processors/downlink/downlink_processor_baseband_impl.h b/lib/phy/lower/processors/downlink/downlink_processor_baseband_impl.h index e14e8d7101..4598cabbe4 100644 --- a/lib/phy/lower/processors/downlink/downlink_processor_baseband_impl.h +++ b/lib/phy/lower/processors/downlink/downlink_processor_baseband_impl.h @@ -31,6 +31,7 @@ #include "srsran/phy/lower/processors/downlink/pdxch/pdxch_processor_baseband.h" #include "srsran/phy/lower/sampling_rate.h" #include "srsran/ran/cyclic_prefix.h" +#include "srsran/ran/tdd/tdd_ul_dl_config.h" #include "srsran/support/stats.h" namespace srsran { @@ -45,6 +46,8 @@ struct downlink_processor_baseband_configuration { cyclic_prefix cp; /// Baseband sampling rate. sampling_rate rate; + /// TDD Config + std::optional tdd_config; /// Number of transmit ports. unsigned nof_tx_ports; /// Number of slots notified in advance in the TTI boundary event. @@ -216,6 +219,10 @@ class downlink_processor_baseband_impl : public downlink_processor_baseband unsigned sector_id; /// Subcarrier spacing. subcarrier_spacing scs; + /// Cyclic prefix configuration. + cyclic_prefix cp; + /// TDD Config. + std::optional tdd_config; /// Number of receive ports. unsigned nof_rx_ports; /// Number of samples per subframe; @@ -232,6 +239,9 @@ class downlink_processor_baseband_impl : public downlink_processor_baseband detail::baseband_symbol_buffer temp_buffer; /// Last notified slot boundary. std::optional last_notified_slot; + + /// Hold stop time for the symbol in the temp_buffer. + std::optional hold_stop_time; }; } // namespace srsran diff --git a/lib/phy/lower/processors/downlink/downlink_processor_factories.cpp b/lib/phy/lower/processors/downlink/downlink_processor_factories.cpp index d7c120cbb5..96adb32d92 100644 --- a/lib/phy/lower/processors/downlink/downlink_processor_factories.cpp +++ b/lib/phy/lower/processors/downlink/downlink_processor_factories.cpp @@ -54,6 +54,7 @@ class lower_phy_downlink_processor_factory_sw : public lower_phy_downlink_proces baseband_config.scs = config.scs; baseband_config.cp = config.cp; baseband_config.rate = config.rate; + baseband_config.tdd_config = config.tdd_config; baseband_config.nof_tx_ports = config.nof_tx_ports; baseband_config.nof_slot_tti_in_advance = config.nof_slot_tti_in_advance; @@ -74,4 +75,4 @@ srsran::create_downlink_processor_factory_sw(std::shared_ptr(std::move(pdxch_proc_factory), std::move(amplitude_control_factory)); -} \ No newline at end of file +} diff --git a/lib/radio/uhd/radio_uhd_impl.cpp b/lib/radio/uhd/radio_uhd_impl.cpp index 7e05cd3816..fd770fa954 100644 --- a/lib/radio/uhd/radio_uhd_impl.cpp +++ b/lib/radio/uhd/radio_uhd_impl.cpp @@ -310,7 +310,8 @@ radio_session_uhd_impl::radio_session_uhd_impl(const radio_configuration::radio& // Reset timestamps. if ((total_rx_channel_count > 1 || total_tx_channel_count > 1) && radio_config.clock.sync != radio_configuration::clock_sources::source::GPSDO) { - device.set_time_unknown_pps(uhd::time_spec_t()); + device.set_time_unknown_pps( + uhd::time_spec_t(radio_config.pps_time_offset_us/1e6)); } // Lists of stream descriptions. @@ -335,9 +336,22 @@ radio_session_uhd_impl::radio_session_uhd_impl(const radio_configuration::radio& stream_description.id = stream_idx; stream_description.otw_format = otw_format; stream_description.srate_hz = actual_tx_rate_Hz; + stream_description.sample_offset = radio_config.sample_offset; stream_description.args = stream.args; - stream_description.discontiuous_tx = (radio_config.tx_mode != radio_configuration::transmission_mode::continuous); - stream_description.power_ramping_us = radio_config.power_ramping_us; + stream_description.discontinuous_tx = (radio_config.tx_mode != radio_configuration::transmission_mode::continuous); + stream_description.discontinuous_config = + (radio_config.tx_mode == + radio_configuration::transmission_mode::discontinuous_config + || radio_config.tx_mode == + radio_configuration::transmission_mode::same_port_config); + fmt::print(stderr, "{}\n", stream_description.discontinuous_config); + stream_description.power_ramping_us = radio_config.power_ramping_us; + stream_description.gpio_tx_index = stream.gpio_tx_index; + stream_description.gpio_tx_sense = stream.gpio_tx_sense; + stream_description.gpio_tx_use_config = + stream.gpio_tx_source == srsran::radio_configuration:: + gpio_source::config; + stream_description.gpio_tx_prelude = stream.gpio_tx_prelude; // Setup ports. for (unsigned channel_idx = 0; channel_idx != stream.channels.size(); ++channel_idx) { @@ -387,6 +401,7 @@ radio_session_uhd_impl::radio_session_uhd_impl(const radio_configuration::radio& stream_description.id = stream_idx; stream_description.otw_format = otw_format; stream_description.srate_Hz = actual_rx_rate_Hz; + stream_description.sample_offset = radio_config.sample_offset; stream_description.args = stream.args; // Setup ports. @@ -420,7 +435,10 @@ radio_session_uhd_impl::radio_session_uhd_impl(const radio_configuration::radio& } // Set the same port for TX and RX. - if (radio_config.tx_mode == radio_configuration::transmission_mode::same_port) { + if (radio_config.tx_mode == + radio_configuration::transmission_mode::same_port_idle + || radio_config.tx_mode == + radio_configuration::transmission_mode::same_port_config) { // Get the selected TX antenna. std::string selected_tx_antenna; if (!device.get_selected_tx_antenna(selected_tx_antenna, port_idx)) { diff --git a/lib/radio/uhd/radio_uhd_rx_stream.cpp b/lib/radio/uhd/radio_uhd_rx_stream.cpp index a9369df0bd..c3cd34da4b 100644 --- a/lib/radio/uhd/radio_uhd_rx_stream.cpp +++ b/lib/radio/uhd/radio_uhd_rx_stream.cpp @@ -60,7 +60,10 @@ bool radio_uhd_rx_stream::receive_block(unsigned& nof_rxd_ radio_uhd_rx_stream::radio_uhd_rx_stream(uhd::usrp::multi_usrp::sptr& usrp, const stream_description& description, radio_notification_handler& notifier_) : - id(description.id), srate_Hz(description.srate_Hz), notifier(notifier_) + id(description.id), + srate_Hz(description.srate_Hz), + sample_offset(description.sample_offset), + notifier(notifier_) { srsran_assert(std::isnormal(srate_Hz) && (srate_Hz > 0.0), "Invalid sampling rate {}.", srate_Hz); @@ -104,8 +107,10 @@ bool radio_uhd_rx_stream::start(const uhd::time_spec_t& time_spec) if (!safe_execution([this, &time_spec]() { uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); - stream_cmd.time_spec = time_spec; - stream_cmd.stream_now = (time_spec.get_real_secs() == uhd::time_spec_t()); + stream_cmd.time_spec = time_spec + + uhd::time_spec_t::from_ticks(sample_offset, srate_Hz); + stream_cmd.stream_now = + (stream_cmd.time_spec.get_real_secs() == uhd::time_spec_t()); stream->issue_stream_cmd(stream_cmd); })) { @@ -136,7 +141,7 @@ baseband_gateway_receiver::metadata radio_uhd_rx_stream::receive(baseband_gatewa // Save timespec for first block only if the last timestamp is unknown. if (rxd_samples_total == 0) { - ret.ts = md.time_spec.to_ticks(srate_Hz); + ret.ts = md.time_spec.to_ticks(srate_Hz) - sample_offset; } // Increase the total amount of received samples. @@ -195,7 +200,8 @@ bool radio_uhd_rx_stream::stop() // Try to stop the stream. if (!safe_execution([this]() { uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); - stream_cmd.time_spec = uhd::time_spec_t(); + stream_cmd.time_spec = uhd::time_spec_t() + + uhd::time_spec_t::from_ticks(sample_offset, srate_Hz); stream_cmd.stream_now = true; stream->issue_stream_cmd(stream_cmd); diff --git a/lib/radio/uhd/radio_uhd_rx_stream.h b/lib/radio/uhd/radio_uhd_rx_stream.h index 6651f85db0..9b3ce49c5f 100644 --- a/lib/radio/uhd/radio_uhd_rx_stream.h +++ b/lib/radio/uhd/radio_uhd_rx_stream.h @@ -49,6 +49,9 @@ class radio_uhd_rx_stream : public uhd_exception_handler, public baseband_gatewa unsigned id; /// Sampling rate in hertz. double srate_Hz; + /// Number of samples to offset the tx/rx time by, used to compensate for + /// radio offsets. Positive means start tx later/report rx as being later. + int sample_offset; /// Radio notification interface. radio_notification_handler& notifier; /// Owns the UHD Tx stream. @@ -78,6 +81,9 @@ class radio_uhd_rx_stream : public uhd_exception_handler, public baseband_gatewa unsigned id; /// Sampling rate in hertz. double srate_Hz; + /// Number of samples to offset the tx/rx time by, used to compensate for + /// radio offsets. Positive means start tx later/report rx as being later. + int sample_offset; /// Over-the-wire format. radio_configuration::over_the_wire_format otw_format; /// Stream arguments. diff --git a/lib/radio/uhd/radio_uhd_tx_stream.cpp b/lib/radio/uhd/radio_uhd_tx_stream.cpp index a04500290f..7ebe942796 100644 --- a/lib/radio/uhd/radio_uhd_tx_stream.cpp +++ b/lib/radio/uhd/radio_uhd_tx_stream.cpp @@ -60,12 +60,12 @@ void radio_uhd_tx_stream::recv_async_msg() break; case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: event_description.type = radio_notification_handler::event_type::LATE; - state_fsm.async_event_late_underflow(async_metadata.time_spec); + state_fsm.async_event_late_underflow(async_metadata.time_spec - sample_offset); break; case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: event_description.type = radio_notification_handler::event_type::UNDERFLOW; - state_fsm.async_event_late_underflow(async_metadata.time_spec); + state_fsm.async_event_late_underflow(async_metadata.time_spec - sample_offset); break; case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: @@ -124,7 +124,7 @@ bool radio_uhd_tx_stream::transmit_block(unsigned& n }); } -radio_uhd_tx_stream::radio_uhd_tx_stream(uhd::usrp::multi_usrp::sptr& usrp, +radio_uhd_tx_stream::radio_uhd_tx_stream(uhd::usrp::multi_usrp::sptr& usrp_, const stream_description& description, task_executor& async_executor_, radio_notification_handler& notifier_) : @@ -132,8 +132,13 @@ radio_uhd_tx_stream::radio_uhd_tx_stream(uhd::usrp::multi_usrp::sptr& usrp, async_executor(async_executor_), notifier(notifier_), srate_hz(description.srate_hz), + sample_offset(description.sample_offset), nof_channels(description.ports.size()), - discontinuous_tx(description.discontiuous_tx), + discontinuous_tx(description.discontinuous_tx), + discontinuous_config(description.discontinuous_config), + gpio_tx_index(description.gpio_tx_index), + gpio_tx_sense(description.gpio_tx_sense), + gpio_tx_use_config(description.gpio_tx_use_config), last_tx_timespec(0.0), power_ramping_buffer(nof_channels, 0) { @@ -157,7 +162,15 @@ radio_uhd_tx_stream::radio_uhd_tx_stream(uhd::usrp::multi_usrp::sptr& usrp, stream_args.args = description.args; stream_args.channels = description.ports; - if (!safe_execution([this, usrp, &stream_args]() { + if (!safe_execution([this, usrp_, &stream_args]() { + usrp = usrp_; + if (gpio_tx_index.has_value()) { + uint32_t pin_mask = 1 << gpio_tx_index.value(); + usrp->set_gpio_attr("FP0", "CTRL", 0, pin_mask); + usrp->set_gpio_attr("FP0", "OUT", gpio_tx_sense ? 0 : pin_mask, + pin_mask); // set RX + usrp->set_gpio_attr("FP0", "DDR", pin_mask, pin_mask); + } stream = usrp->get_tx_stream(stream_args); max_packet_size = stream->get_max_num_samps(); })) { @@ -165,6 +178,8 @@ radio_uhd_tx_stream::radio_uhd_tx_stream(uhd::usrp::multi_usrp::sptr& usrp, return; } + gpio_tx_prelude_samples = description.gpio_tx_prelude * description.srate_hz / 1e6; + // Use zero padding to absorb power ramping on each new burst. if (discontinuous_tx) { power_ramping_nof_samples = description.srate_hz * static_cast(description.power_ramping_us) / 1e6; @@ -190,6 +205,67 @@ radio_uhd_tx_stream::radio_uhd_tx_stream(uhd::usrp::multi_usrp::sptr& usrp, run_recv_async_msg(); } +template +static void sanitize_start_end_last( + bool last_on, + std::optional& start, + std::optional& end) { + if (start.has_value() && end.has_value() && start.value() == end.value()) { + start.reset(); + end.reset(); + } + if (last_on && start.has_value() && end.has_value() + && start.value() < end.value()) { + start.reset(); + } + if (!last_on && start.has_value() && end.has_value() + && start.value() > end.value()) { + end.reset(); + } +} + +// Assumes that start/end have been sanitized with sanitize_start_end_last. +template +static void update_last( + bool& last_on, + const std::optional& start, + const std::optional& end) { + if (start.has_value()) { + if (!end.has_value()) { + last_on = true; + } else { + last_on = start.value() > end.value(); + } + } else if (end.has_value()) { + last_on = false; + } +} + +template +static bool is_1( + const T& time, + const bool last_on, + const std::optional& start, + const std::optional& end) { + if (last_on) { + if (!end.has_value()) { + return true; + } else { + return time < end.value(); + } + } else { + if (!start.has_value()) { + return false; + } else if (!end.has_value()) { + return time >= start.value(); + } else { + return time >= start.value() && time < end.value(); + } + } + + // Impossible +} + void radio_uhd_tx_stream::transmit(const baseband_gateway_buffer_reader& data, const baseband_gateway_transmitter_metadata& tx_md) { @@ -198,123 +274,374 @@ void radio_uhd_tx_stream::transmit(const baseband_gateway_buffer_reader& uhd::tx_metadata_t uhd_metadata; - bool tx_start_padding = tx_md.tx_start.has_value(); - bool tx_end_padding = tx_md.tx_end.has_value(); + std::optional idle_start_time_spec; + std::optional idle_end_time_spec; + bool idle_extend_end = false; - uhd::time_spec_t time_spec = time_spec.from_ticks(tx_md.ts, srate_hz); - bool transmit; - if (discontinuous_tx) { - if (tx_start_padding) { - // Set the timespec to the start of the actual transmission if there is head padding in the buffer. - time_spec = - time_spec.from_ticks(tx_md.ts + static_cast(tx_md.tx_start.value()), srate_hz); + unsigned idle_start_index = 0; + unsigned idle_end_index = data.get_nof_samples(); + + std::optional start_time_spec; + std::optional end_time_spec; + + unsigned start_index; + unsigned end_index; + + std::optional dl_config_start_ts; + std::optional dl_config_end_ts; + bool dl_config_extend_end = false; + + unsigned dl_config_start_index = 0; + unsigned dl_config_end_index = data.get_nof_samples(); + + unsigned end_extension_count = 0; + uhd::time_spec_t end_extension_ts = + uhd::time_spec_t::from_ticks(0, srate_hz); + + // Subtracting sample offset because radio seems to have a shifted view + // between GPIO and samples, doesn't work well without this and I think + // it's because the GPIO is turning off the PA early. + if (sample_offset < 0) { + end_extension_count = -sample_offset; + end_extension_ts = uhd::time_spec_t::from_ticks( + end_extension_count, srate_hz); + } + + if (tx_md.tx_start.has_value()) { + // Start at specified time + idle_start_time_spec = uhd::time_spec_t::from_ticks(tx_md.ts + + static_cast(tx_md.tx_start.value()), + srate_hz); + idle_start_index = tx_md.tx_start.value(); + } else { + // If not empty and not currently running start at beginning + if (!tx_md.is_empty && !last_sample_state_tx) { + idle_start_time_spec = uhd::time_spec_t::from_ticks(tx_md.ts, srate_hz); } - // Update state. - transmit = state_fsm.on_transmit(uhd_metadata, time_spec, tx_md.is_empty, tx_end_padding); + } + + if (tx_md.tx_end.has_value()) { + // End at specified time + idle_end_time_spec = uhd::time_spec_t::from_ticks(tx_md.ts + + static_cast(tx_md.tx_end.value()), + srate_hz); + idle_end_index = tx_md.tx_end.value(); + idle_extend_end = true; } else { - transmit = state_fsm.on_transmit(uhd_metadata, time_spec, false, false); + // If empty and was running, stop at beginning + if (tx_md.is_empty && last_sample_state_tx) { + idle_end_time_spec = uhd::time_spec_t::from_ticks(tx_md.ts, srate_hz); + idle_end_index = 0; + } } - // Return if no transmission is required. - if (!transmit) { - return; + if (tx_md.dl_config_start.has_value() && tx_md.dl_config_end.has_value()) { + srsran_assert(tx_md.dl_config_start.value() < tx_md.dl_config_end.value(), + "This code doesn't support an end before start on the TDD config in " + "one block!"); } - // Notify start of burst. - if (uhd_metadata.start_of_burst) { - radio_notification_handler::event_description event_description; - event_description.stream_id = stream_id; - event_description.channel_id = 0; - event_description.source = radio_notification_handler::event_source::TRANSMIT; - event_description.type = radio_notification_handler::event_type::START_OF_BURST; - event_description.timestamp.emplace(time_spec.to_ticks(srate_hz)); - notifier.on_radio_rt_event(event_description); + sanitize_start_end_last(last_sample_state_tx, idle_start_time_spec, + idle_end_time_spec); + + std::optional dl_config_start = tx_md.dl_config_start; + std::optional dl_config_end = tx_md.dl_config_end; + + sanitize_start_end_last(last_tdd_config_state_tx, + dl_config_start, dl_config_end); + + // If there's a dl_config_start, push the start of the transmission back to + // that point + if (dl_config_start.has_value()) { + dl_config_start_index = dl_config_start.value(); + dl_config_start_ts = uhd::time_spec_t::from_ticks( + tx_md.ts + dl_config_start_index, srate_hz); + if (is_1(dl_config_start_ts.value(), last_sample_state_tx, + idle_start_time_spec, idle_end_time_spec)) { + // If transmission ends before TDD config start, cancel it + if (idle_end_time_spec.has_value() && + idle_end_time_spec.value() <= dl_config_start_ts.value()) { + idle_start_time_spec.reset(); + idle_end_time_spec.reset(); + idle_end_index = data.get_nof_samples(); + } else { + idle_start_time_spec = dl_config_start_ts.value(); + idle_start_index = dl_config_start_index; + } + } + // If we're not in a TDD config transmit, cancel the transmission, but mark + // as running (for next TDD config start) + } else if (!last_tdd_config_state_tx) { + idle_start_time_spec.reset(); + idle_end_time_spec.reset(); + last_sample_state_tx = true; + } - // Transmit zeros before the actual transmission to absorb power ramping effects. - if (discontinuous_tx) { - // Compute the time in number of samples between the end of the last transmission and the start of the current - // one. - unsigned transmission_gap = (time_spec - last_tx_timespec).to_ticks(srate_hz); - - // Make sure that the power ramping padding starts at least 10 microseconds after the last transmission end. - unsigned minimum_gap_before_power_ramping = srate_hz / 100000.0; - unsigned nof_padding_samples = 0; - if (transmission_gap > minimum_gap_before_power_ramping) { - nof_padding_samples = std::min(power_ramping_nof_samples, transmission_gap - minimum_gap_before_power_ramping); + // If there's a dl_config_end, pull the end of the transmission back to that + // point + if (dl_config_end.has_value()) { + dl_config_end_index = dl_config_end.value(); + dl_config_end_ts = uhd::time_spec_t::from_ticks( + tx_md.ts + dl_config_end_index, srate_hz); + dl_config_extend_end = true; + if (is_1(dl_config_end_ts.value(), last_sample_state_tx, + idle_start_time_spec, idle_end_time_spec)) { + // If transmission starts after TDD config end, cancel it + if (idle_start_time_spec.has_value() + && dl_config_end_ts <= idle_start_time_spec.value()) { + idle_start_time_spec.reset(); + idle_end_time_spec.reset(); + idle_end_index = data.get_nof_samples(); + } else { + idle_end_time_spec = dl_config_end_ts.value(); + idle_end_index = dl_config_end_index; + idle_extend_end = true; } + } + } // No else because if we're already running the start/end passes through - if (nof_padding_samples > 0) { - unsigned txd_padding_sps_total = 0; + update_last(last_sample_state_tx, idle_start_time_spec, idle_end_time_spec); + update_last(last_tdd_config_state_tx, dl_config_start, dl_config_end); - uhd::tx_metadata_t power_ramping_metadata; - power_ramping_metadata.has_time_spec = true; - power_ramping_metadata.start_of_burst = true; - power_ramping_metadata.end_of_burst = false; - power_ramping_metadata.time_spec = uhd_metadata.time_spec - time_spec.from_ticks(nof_padding_samples, srate_hz); + if (discontinuous_config) { + start_time_spec = dl_config_start_ts; + start_index = dl_config_start_index; + end_time_spec = dl_config_end_ts; + end_index = dl_config_end_index; - // Modify the actual trasnmission metadata, since we have already started the burst with padding. - uhd_metadata.start_of_burst = false; - uhd_metadata.has_time_spec = false; - do { - unsigned txd_samples = 0; + if (dl_config_extend_end) { + if (end_time_spec.has_value()) { + end_time_spec.value() += end_extension_ts; + } + end_index += end_extension_count; + } + } else { + start_time_spec = idle_start_time_spec; + start_index = idle_start_index; + end_time_spec = idle_end_time_spec; + end_index = idle_end_index; + + if (idle_extend_end) { + if (end_time_spec.has_value()) { + end_time_spec.value() += end_extension_ts; + } + end_index += end_extension_count; + } + } - baseband_gateway_buffer_reader_view tx_padding = - baseband_gateway_buffer_reader_view(power_ramping_buffer.get_reader(), 0, nof_padding_samples); + bool has_tx_start = start_time_spec.has_value(); + bool has_tx_end = end_time_spec.has_value(); - if (!transmit_block(txd_samples, tx_padding, txd_padding_sps_total, power_ramping_metadata)) { - printf("Error: failed transmitting power ramping padding. %s.\n", get_error_message().c_str()); - return; - } + uhd::time_spec_t time_spec = + time_spec.from_ticks(tx_md.ts, srate_hz); + bool transmit; + bool start_of_tx = false; + bool end_of_tx = false; + bool force_start_of_burst = false; + bool force_end_of_burst = false; + bool start_of_burst; + bool end_of_burst; + + if (has_tx_start && discontinuous_tx) { + // Time actual transmission will start (regardless of when samples start) + time_spec = start_time_spec.value(); + } - power_ramping_metadata.time_spec += txd_samples * srate_hz; - txd_padding_sps_total += txd_samples; + // Update state. + transmit = state_fsm.on_transmit( + discontinuous_tx, time_spec, start_of_tx, end_of_tx, + force_start_of_burst, force_end_of_burst, + tx_md.is_empty, has_tx_end); + start_of_burst = force_start_of_burst; + end_of_burst = force_end_of_burst; + + if(discontinuous_tx) { + uhd_metadata.start_of_burst |= start_of_tx; + uhd_metadata.end_of_burst |= end_of_tx; + } - } while (txd_padding_sps_total < nof_padding_samples); + // For restarting a late/underflowed transmission + uhd_metadata.start_of_burst |= start_of_burst; + uhd_metadata.end_of_burst |= end_of_burst; + + if (start_of_burst) { + // Is getting forced on for whatever exceptional reason. + last_sample_state_tx = true; + } + if (end_of_burst) { + // Is getting forced off for whatever exceptional reason. + last_sample_state_tx = false; + } + + // Determine actual transmission range. + unsigned data_start = discontinuous_tx ? start_index : 0; + unsigned data_nof_samples = discontinuous_tx ? + end_index - start_index + : data.get_nof_samples(); + + if (gpio_tx_index.has_value()) { + uint32_t pin_mask = 1 << gpio_tx_index.value(); + bool clear_time = false; + + std::optional gpio_start_time; + std::optional gpio_end_time; + + // Determine start time (if there is one) + if (force_start_of_burst) { + gpio_start_time = time_spec; + } else { + if (gpio_tx_use_config) { + if (dl_config_start_ts.has_value()) { + gpio_start_time = dl_config_start_ts.value(); + } + } else { + if (idle_start_time_spec.has_value()) { + gpio_start_time = idle_start_time_spec.value(); + } } } - } - // Notify end of burst. - if (uhd_metadata.end_of_burst) { - radio_notification_handler::event_description event_description; - event_description.stream_id = stream_id; - event_description.channel_id = 0; - event_description.source = radio_notification_handler::event_source::TRANSMIT; - event_description.type = radio_notification_handler::event_type::END_OF_BURST; - event_description.timestamp.emplace(time_spec.to_ticks(srate_hz)); - notifier.on_radio_rt_event(event_description); + if (gpio_start_time.has_value()) { + gpio_start_time.value() -= uhd::time_spec_t::from_ticks( + gpio_tx_prelude_samples, srate_hz); + } + + + // Determine end time (if there is one) + if (force_end_of_burst) { + gpio_end_time = time_spec + uhd::time_spec_t::from_ticks( + data_nof_samples, srate_hz); + } else { + if (gpio_tx_use_config) { + if (dl_config_end_ts.has_value()) { + gpio_end_time = dl_config_end_ts.value(); + } + } else { + if (idle_end_time_spec.has_value()) { + gpio_end_time = idle_end_time_spec.value(); + } + } + } + + // Actually set the GPIO + if (gpio_start_time.has_value()) { + usrp->set_command_time(gpio_start_time.value()); + clear_time = true; + usrp->set_gpio_attr("FP0", "OUT", + gpio_tx_sense ? pin_mask : 0, pin_mask); + } + + if (gpio_end_time.has_value()) { + usrp->set_command_time(gpio_end_time.value()); + clear_time = true; + usrp->set_gpio_attr("FP0", "OUT", + gpio_tx_sense ? 0 : pin_mask, pin_mask); + } + if (clear_time) { + usrp->clear_command_time(); + } } - // Determine actual transmission range. - unsigned data_start = discontinuous_tx && tx_start_padding ? tx_md.tx_start.value() : 0; - unsigned data_nof_samples = - discontinuous_tx && tx_end_padding ? tx_md.tx_end.value() - data_start : data.get_nof_samples() - data_start; - - // Don't pass the transmission data to UHD if the transmit buffer is empty. - baseband_gateway_buffer_reader_view tx_data = - discontinuous_tx && tx_md.is_empty ? baseband_gateway_buffer_reader_view(data, 0, 0) - : baseband_gateway_buffer_reader_view(data, data_start, data_nof_samples); - - unsigned nsamples = tx_data.get_nof_samples(); - - // Transmit stream in multiple blocks. - unsigned txd_samples_total = 0; - do { - unsigned txd_samples = 0; - if (!transmit_block(txd_samples, tx_data, txd_samples_total, uhd_metadata)) { - printf("Error: failed transmitting packet. %s.\n", get_error_message().c_str()); - return; + // Skip if no transmission is required. + if (transmit) { + if(uhd_metadata.start_of_burst) { + uhd_metadata.has_time_spec = true; + uhd_metadata.time_spec = time_spec + + uhd::time_spec_t::from_ticks(sample_offset, srate_hz); + } + + // Notify start of burst. + if (uhd_metadata.start_of_burst) { + radio_notification_handler::event_description event_description; + event_description.stream_id = stream_id; + event_description.channel_id = 0; + event_description.source = radio_notification_handler::event_source::TRANSMIT; + event_description.type = radio_notification_handler::event_type::START_OF_BURST; + event_description.timestamp.emplace(time_spec.to_ticks(srate_hz)); + notifier.on_radio_rt_event(event_description); + + // Transmit zeros before the actual transmission to absorb power ramping effects. + if (discontinuous_tx) { + // Compute the time in number of samples between the end of the last transmission and the start of the current + // one. + unsigned transmission_gap = (time_spec - last_tx_timespec).to_ticks(srate_hz); + + // Make sure that the power ramping padding starts at least 10 microseconds after the last transmission end. + unsigned minimum_gap_before_power_ramping = srate_hz / 100000.0; + unsigned nof_padding_samples = 0; + if (transmission_gap > minimum_gap_before_power_ramping) { + nof_padding_samples = std::min(power_ramping_nof_samples, transmission_gap - minimum_gap_before_power_ramping); + } + + if (nof_padding_samples > 0) { + unsigned txd_padding_sps_total = 0; + + uhd::tx_metadata_t power_ramping_metadata; + power_ramping_metadata.has_time_spec = true; + power_ramping_metadata.start_of_burst = true; + power_ramping_metadata.end_of_burst = false; + power_ramping_metadata.time_spec = uhd_metadata.time_spec - time_spec.from_ticks(nof_padding_samples, srate_hz); + + // Modify the actual trasnmission metadata, since we have already started the burst with padding. + uhd_metadata.start_of_burst = false; + uhd_metadata.has_time_spec = false; + do { + unsigned txd_samples = 0; + + baseband_gateway_buffer_reader_view tx_padding = + baseband_gateway_buffer_reader_view(power_ramping_buffer.get_reader(), 0, nof_padding_samples); + + if (!transmit_block(txd_samples, tx_padding, txd_padding_sps_total, power_ramping_metadata)) { + printf("Error: failed transmitting power ramping padding. %s.\n", get_error_message().c_str()); + return; + } + + power_ramping_metadata.time_spec += txd_samples * srate_hz; + txd_padding_sps_total += txd_samples; + + } while (txd_padding_sps_total < nof_padding_samples); + } + } } - // Increment timespec. - uhd_metadata.time_spec += txd_samples * srate_hz; + // Notify end of burst. + if (uhd_metadata.end_of_burst) { + radio_notification_handler::event_description event_description; + event_description.stream_id = stream_id; + event_description.channel_id = 0; + event_description.source = radio_notification_handler::event_source::TRANSMIT; + event_description.type = radio_notification_handler::event_type::END_OF_BURST; + event_description.timestamp.emplace(time_spec.to_ticks(srate_hz)); + notifier.on_radio_rt_event(event_description); + } - // Increment the total amount of received samples. - txd_samples_total += txd_samples; + // Don't pass the transmission data to UHD if the transmit buffer is empty. + baseband_gateway_buffer_reader_view tx_data = + discontinuous_tx && tx_md.is_empty ? baseband_gateway_buffer_reader_view(data, 0, 0) + : baseband_gateway_buffer_reader_view(data, data_start, data_nof_samples); - } while (txd_samples_total < nsamples); + unsigned nsamples = tx_data.get_nof_samples(); + + // Transmit stream in multiple blocks. + unsigned txd_samples_total = 0; + do { + unsigned txd_samples = 0; + if (!transmit_block(txd_samples, tx_data, txd_samples_total, uhd_metadata)) { + printf("Error: failed transmitting packet. %s.\n", get_error_message().c_str()); + return; + } - last_tx_timespec = time_spec + uhd::time_spec_t::from_ticks(txd_samples_total, srate_hz); + // Increment timespec. + uhd_metadata.time_spec += + uhd::time_spec_t::from_ticks(txd_samples, srate_hz); + + // Increment the total amount of received samples. + txd_samples_total += txd_samples; + + } while (txd_samples_total < nsamples); + + last_tx_timespec = time_spec + uhd::time_spec_t::from_ticks(txd_samples_total, srate_hz); + } } void radio_uhd_tx_stream::stop() diff --git a/lib/radio/uhd/radio_uhd_tx_stream.h b/lib/radio/uhd/radio_uhd_tx_stream.h index ee1e17d604..e427b3d228 100644 --- a/lib/radio/uhd/radio_uhd_tx_stream.h +++ b/lib/radio/uhd/radio_uhd_tx_stream.h @@ -30,6 +30,7 @@ #include "srsran/gateways/baseband/buffer/baseband_gateway_buffer_reader.h" #include "srsran/radio/radio_configuration.h" #include "srsran/radio/radio_notification_handler.h" +#include "srsran/ran/tdd/tdd_ul_dl_config.h" #include "srsran/support/executors/task_executor.h" #include @@ -50,6 +51,8 @@ class radio_uhd_tx_stream : public baseband_gateway_transmitter, public uhd_exce task_executor& async_executor; /// Radio notification interface. radio_notification_handler& notifier; + /// USRP pointer, used for setting GPIO + uhd::usrp::multi_usrp::sptr usrp; /// Owns the UHD Tx stream. uhd::tx_streamer::sptr stream; /// Maximum number of samples in a single packet. @@ -58,14 +61,33 @@ class radio_uhd_tx_stream : public baseband_gateway_transmitter, public uhd_exce std::mutex stream_transmit_mutex; /// Sampling rate in Hz. double srate_hz; + /// Number of samples to offset the tx/rx time by, used to compensate for + /// radio offsets. Positive means start tx later/report rx as being later. + int sample_offset; /// Indicates the number of channels. unsigned nof_channels; /// Indicates the current internal state. radio_uhd_tx_stream_fsm state_fsm; + /// What the last tdd state according to tdd_config is + bool last_tdd_config_state_tx = false; + /// What the last transmite state is according to start/end padding + bool last_sample_state_tx = false; /// Discontinous transmission mode flag. bool discontinuous_tx; + /// Use only config for determining TX/RX + bool discontinuous_config; + /// GPIO transmission mode flag, i.e. set GPIO bits when transmitting/not + bool gpio_tx; /// Number of samples to advance the burst start to protect agains power ramping effects. unsigned power_ramping_nof_samples; + /// Which bit of the GPIO should be toggled for TX + std::optional gpio_tx_index; + /// True if gpio should be high for TX, false if gpio should be low for TX + bool gpio_tx_sense; + /// True if gpio should be set based off config, false otherwise + bool gpio_tx_use_config; + /// Amount of time to put GPIO in TX mode early in samples. + unsigned gpio_tx_prelude_samples; /// Stores the time of the last transmitted sample. uhd::time_spec_t last_tx_timespec; /// Power ramping transmit buffer. It is filled with zeros, used to absorb power ramping when starting a transmission. @@ -97,14 +119,27 @@ class radio_uhd_tx_stream : public baseband_gateway_transmitter, public uhd_exce radio_configuration::over_the_wire_format otw_format; /// Sampling rate in Hz. double srate_hz; + /// Number of samples to offset the tx/rx time by, used to compensate for + /// radio offsets. Positive means start tx later/report rx as being later. + int sample_offset; /// Stream arguments. std::string args; /// Indicates the port indexes for the stream. std::vector ports; /// Enables discontinuous transmission mode. - bool discontiuous_tx; + bool discontinuous_tx; + /// Use only config for determining TX/RX + bool discontinuous_config; /// Time by which to advance the burst start, using zero padding to protect against power ramping. float power_ramping_us; + /// If set, which bit of the GPIO should be toggled for TX + std::optional gpio_tx_index; + /// True if gpio should be high for TX, false if gpio should be low for TX + bool gpio_tx_sense; + /// True if gpio should be set based off config, false otherwise + bool gpio_tx_use_config; + /// Amount of time to put GPIO in TX mode early in microseconds. + float gpio_tx_prelude; }; /// \brief Constructs an UHD transmit stream. @@ -112,7 +147,7 @@ class radio_uhd_tx_stream : public baseband_gateway_transmitter, public uhd_exce /// \param[in] description Provides the stream configuration parameters. /// \param[in] async_executor_ Provides the asynchronous task executor. /// \param[in] notifier_ Provides the radio event notification handler. - radio_uhd_tx_stream(uhd::usrp::multi_usrp::sptr& usrp, + radio_uhd_tx_stream(uhd::usrp::multi_usrp::sptr& usrp_, const stream_description& description, task_executor& async_executor_, radio_notification_handler& notifier_); diff --git a/lib/radio/uhd/radio_uhd_tx_stream_fsm.h b/lib/radio/uhd/radio_uhd_tx_stream_fsm.h index 64d3dd0b86..268150087e 100644 --- a/lib/radio/uhd/radio_uhd_tx_stream_fsm.h +++ b/lib/radio/uhd/radio_uhd_tx_stream_fsm.h @@ -52,6 +52,8 @@ class radio_uhd_tx_stream_fsm START_BURST, /// Indicates the stream is transmitting a burst. IN_BURST, + /// Like START_BURST, but when already running, i.e. tx zeroes + IDLE_BURST, /// Indicates an end-of-burst must be transmitted and abort any transmission. END_OF_BURST, /// Indicates wait for end-of-burst acknowledgement. @@ -86,7 +88,7 @@ class radio_uhd_tx_stream_fsm void async_event_late_underflow(const uhd::time_spec_t& time_spec) { std::unique_lock lock(mutex); - if (state == states::IN_BURST) { + if (state == states::IN_BURST || state == states::IDLE_BURST) { state = states::END_OF_BURST; wait_eob_timeout = time_spec; wait_eob_timeout += WAIT_EOB_ACK_TIMEOUT_S; @@ -104,12 +106,20 @@ class radio_uhd_tx_stream_fsm } /// \brief Handles a new transmission. - /// \param[out] metadata Destination of the required metadata. - /// \param[in] time_spec Transmission time of the first sample. + /// \param[in] discontinuous_tx Whether or not in discontinuous tx mode. + /// \param[in] time_spec Transmission time of the first sample. + /// \param[out] start_of_tx Whether TX is starting, used for SoB if in + /// discontinuous TX. + /// \param[out] end_of_tx Whether TX is ending, used for EoB if in + /// discontinuous TX. + /// \param[out] end_of_burst Whether to set end_of_burst. /// \param[in] is_empty Empty buffer flag. /// \param[in] tail_padding Tail padding flag, indicating the last transmission in the burst. /// \return True if the block shall be transmitted. False if the block shall be ignored. - bool on_transmit(uhd::tx_metadata_t& metadata, uhd::time_spec_t& time_spec, bool is_empty, bool tail_padding) + bool on_transmit(bool discontinuous_tx, const uhd::time_spec_t& time_spec, + bool& start_of_tx, bool& end_of_tx, + bool& start_of_burst, bool& end_of_burst, + bool is_empty, bool tail_padding) { std::unique_lock lock(mutex); switch (state) { @@ -123,32 +133,44 @@ class radio_uhd_tx_stream_fsm case states::START_BURST: // Set start of burst flag and time spec. if (!is_empty) { - metadata.has_time_spec = true; - metadata.start_of_burst = true; - metadata.end_of_burst = tail_padding; - metadata.time_spec = time_spec; + start_of_burst = true; + start_of_tx = true; + end_of_tx = tail_padding; // Transition to in-burst. - if (!tail_padding) { + if (!(end_of_tx && discontinuous_tx)) { state = states::IN_BURST; } return true; } return false; + case states::IDLE_BURST: + if (!is_empty) { + start_of_tx = true; + end_of_tx = tail_padding; + + // Transition to in-burst. + if (!(end_of_tx && discontinuous_tx)) { + state = states::IN_BURST; + } + + return true; + } + return !discontinuous_tx; case states::IN_BURST: if (is_empty || tail_padding) { // Signal end of burst to UHD. - metadata.end_of_burst = true; + end_of_tx = true; - // Transition to start burst without waiting for the EOB ACK. - state = states::START_BURST; + state = states::IDLE_BURST; } break; case states::END_OF_BURST: // Flag end-of-burst. - metadata.end_of_burst = true; - state = states::WAIT_END_OF_BURST; + end_of_burst = true; + end_of_tx = true; + state = states::WAIT_END_OF_BURST; if (wait_eob_timeout == uhd::time_spec_t()) { wait_eob_timeout = time_spec; wait_eob_timeout += WAIT_EOB_ACK_TIMEOUT_S; diff --git a/lib/ran/tdd_ul_dl_config.cpp b/lib/ran/tdd_ul_dl_config.cpp index a96126e3de..a74bf42ded 100644 --- a/lib/ran/tdd_ul_dl_config.cpp +++ b/lib/ran/tdd_ul_dl_config.cpp @@ -158,3 +158,50 @@ std::optional srsran::find_next_tdd_full_ul_slot(const tdd_ul_dl_confi } return ret; } + +bool srsran::is_first_tdd_dl_symbol(const tdd_ul_dl_config_common& cfg, + unsigned slot_index, + unsigned symbol_index, + cyclic_prefix cp) +{ + // All periods are integer number of slots, all slot configuration periods + // start with DL, therefore first symbol MUST be first symbol of slot + if(symbol_index != 0) { + return false; + } + + // By TS 38.213 11.1, first symbol every 20/P is a first symbol of frame + if(slot_index == 0) { + return true; + } else { + if(!has_active_tdd_dl_symbols(cfg, slot_index)) { + return false; + } else { + // Previous slot wasn't a full DL slot + return nof_active_symbols(cfg, slot_index-1, cp, true) + < nof_slots_per_tdd_period(cfg); + } + } + + // Impossible +} + +bool srsran::is_last_tdd_dl_symbol(const tdd_ul_dl_config_common& cfg, + unsigned slot_index, + unsigned symbol_index, + cyclic_prefix cp) +{ + if(is_tdd_full_dl_slot(cfg, slot_index)) { + if(symbol_index == get_nsymb_per_slot(cp) - 1) { + return !has_active_tdd_dl_symbols(cfg, slot_index+1); + } else { + return false; + } + } else { + return symbol_index+1 == + get_active_tdd_dl_symbols(cfg, slot_index, cp).stop(); + } + + // Impossible +} + diff --git a/tests/unittests/radio/zmq/radio_zmq_validator_test.cpp b/tests/unittests/radio/zmq/radio_zmq_validator_test.cpp index 99d429f6d3..2b53a6bcd9 100644 --- a/tests/unittests/radio/zmq/radio_zmq_validator_test.cpp +++ b/tests/unittests/radio/zmq/radio_zmq_validator_test.cpp @@ -243,7 +243,7 @@ const std::vector radio_zmq_validator_test_data = { "Only default OTW format is currently supported.\n"}, {[] { radio_configuration::radio config = radio_base_config; - config.tx_mode = radio_configuration::transmission_mode::discontinuous; + config.tx_mode = radio_configuration::transmission_mode::discontinuous_idle; return config; }, "Discontinuous transmission modes are not supported by the ZMQ radio.\n"},