From 99340124c8f3dade2c2e59cac17a7dd3bad0f6bc Mon Sep 17 00:00:00 2001 From: Alex Orange Date: Mon, 7 Oct 2024 17:04:01 -0600 Subject: [PATCH] Implement GPIO signalling for when transmitting/not. This feature is separate from the tx_mode which controls whether the tx stream is started and stopped. Configuration options: * Pin to output on (gpio_index) * Whether to output high or low on transmit (sense) * How many microseconds to set the tx output early (to turn on external PAs, switches, etc) (prelude) * Whether to source from idle or config (source) * Offset between PPS and real time (pps_time_offset_us) * Offset in samples between sample time and everything else (sample_offset) TDD Config start/stop piped down to radio layer. In lib/phy/lower/lower_phy_factory.cpp removed the adding of tx_time_offset to the max delay as this was already handled by the processor. Processor checks the max delay against it's version of timestamp, processes symbols based on such timestamp, but then sends the symbols out tx_time_offset late. Therefore the max delay is being between two timeframes offset by tx_time_offset, so the tx_time_offset term is already accounted for. Add sample offset to align RF and GPIO (and PA which uses GPIO). SDR does not line up it's time for everything else (e.g. GPIO) with the sample time (i.e. RF). Therefore the transmit time needs to be brought up by a certain number of samples. Similarly the end needs to be extended so the SDR doesn't shut off the PA on itself (it is truly confused about time). Stitch together start/end from both sources. Fixups to transmit FSM to keep underflow restart working. Add configuration for whether to use idle or config for TX and GPIO. Add PPS time offset parameter to compensate for 101.3 us offset in N310. --- apps/examples/radio/radio_util_sample.cpp | 2 +- .../split_8/helpers/ru_sdr_config.h | 36 +- .../helpers/ru_sdr_config_cli11_schema.cpp | 91 ++- .../helpers/ru_sdr_config_translator.cpp | 40 +- .../helpers/ru_sdr_config_validator.cpp | 4 +- .../helpers/ru_sdr_config_yaml_writer.cpp | 25 + .../baseband_gateway_transmitter_metadata.h | 10 + .../phy/lower/lower_phy_configuration.h | 3 + .../downlink/downlink_processor_factories.h | 5 +- include/srsran/radio/radio_configuration.h | 69 ++- include/srsran/ran/tdd/tdd_ul_dl_config.h | 15 + lib/ofh/timing/realtime_timing_worker.cpp | 1 + lib/ofh/timing/realtime_timing_worker.h | 1 + lib/phy/lower/lower_phy_factory.cpp | 8 +- .../lower/processors/downlink/CMakeLists.txt | 2 +- .../downlink_processor_baseband_impl.cpp | 17 + .../downlink_processor_baseband_impl.h | 10 + .../downlink/downlink_processor_factories.cpp | 3 +- lib/radio/uhd/radio_uhd_impl.cpp | 26 +- lib/radio/uhd/radio_uhd_rx_stream.cpp | 16 +- lib/radio/uhd/radio_uhd_rx_stream.h | 6 + lib/radio/uhd/radio_uhd_tx_stream.cpp | 519 ++++++++++++++---- lib/radio/uhd/radio_uhd_tx_stream.h | 39 +- lib/radio/uhd/radio_uhd_tx_stream_fsm.h | 50 +- lib/ran/tdd_ul_dl_config.cpp | 47 ++ .../radio/zmq/radio_zmq_validator_test.cpp | 2 +- 26 files changed, 895 insertions(+), 152 deletions(-) diff --git a/apps/examples/radio/radio_util_sample.cpp b/apps/examples/radio/radio_util_sample.cpp index 621336469a1..56b12b7608e 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 b97ed4626b3..870f2357ca7 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 aab667ff2c4..66c7eb2a3c7 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 4b06a377e31..b833f09d2ac 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 4a82f4ef687..fa04492a7df 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 3f727ff8158..3f03720f59e 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 4e67e77fe45..a32d2d4a93d 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 3353330e718..44c6cbda9c4 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 072e8a5ae8f..1bb4e896da5 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 21066f3a11a..3d0e9c66555 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 1920469adc9..a632d7b0d28 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 40b365bf6ee..dcb0c0e4b09 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 a91d46916b5..387a4c499b7 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 b4178a4baa4..8165b0d2a83 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 061e4cf57f2..244bb21f6f6 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 2fbfaad8d61..c963cccf0b3 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 e14e8d7101a..4598cabbe44 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 d7c120cbb53..96adb32d926 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 7e05cd38169..fd770fa9546 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 a9369df0bdd..c3cd34da4ba 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 6651f85db07..9b3ce49c5fb 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 a04500290f1..7ebe9427966 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 ee1e17d6045..e427b3d2286 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 64d3dd0b864..268150087e8 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 a96126e3de3..a74bf42ded5 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 99d429f6d39..2b53a6bcd9e 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"},