diff options
Diffstat (limited to 'host/lib/usrp/dboard')
| -rw-r--r-- | host/lib/usrp/dboard/eiscat/eiscat_radio_ctrl_impl.cpp | 270 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/eiscat/eiscat_radio_ctrl_impl.hpp | 68 | 
2 files changed, 306 insertions, 32 deletions
diff --git a/host/lib/usrp/dboard/eiscat/eiscat_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/eiscat/eiscat_radio_ctrl_impl.cpp index f78876791..648492486 100644 --- a/host/lib/usrp/dboard/eiscat/eiscat_radio_ctrl_impl.cpp +++ b/host/lib/usrp/dboard/eiscat/eiscat_radio_ctrl_impl.cpp @@ -30,44 +30,74 @@ using namespace uhd;  using namespace uhd::usrp;  using namespace uhd::rfnoc; -static const double EISCAT_TICK_RATE = 208e6; // Hz -static const double EISCAT_RADIO_RATE = 104e6; // Hz -static const double EISCAT_CENTER_FREQ = 104e6; // Hz -static const double EISCAT_DEFAULT_GAIN = 0.0; // Hz -static const double EISCAT_DEFAULT_BANDWIDTH = 52e6; // Hz -static const char* EISCAT_ANTENNA_NAME = "Rx"; -static const size_t EISCAT_NUM_CHANS = 5; -static const size_t EISCAT_NUM_FRONTENDS = 16; +namespace { +    const size_t SR_ANTENNA_GAIN_BASE     = 204; +    const size_t SR_ANTENNA_SELECT_BASE   = 192; // Note: On other dboards, 192 is DB_GPIO address space + +    const double EISCAT_TICK_RATE         = 208e6; // Hz +    const double EISCAT_RADIO_RATE        = 104e6; // Hz +    const double EISCAT_CENTER_FREQ       = 104e6; // Hz +    const double EISCAT_DEFAULT_NULL_GAIN = 0.0; // dB. This is not the digital antenna gain, this a fake stub value. +    const double EISCAT_DEFAULT_BANDWIDTH = 52e6; // Hz +    const char*  EISCAT_DEFAULT_ANTENNA   = "BF"; +    const size_t EISCAT_NUM_ANTENNAS      = 16; +    const size_t EISCAT_NUM_BEAMS         = 10; +    const size_t EISCAT_NUM_PORTS         = 5; +    const size_t EISCAT_GAIN_RANGE        = 18; // Bits, *signed*. +    const int32_t EISCAT_MAX_GAIN         = (1<<(EISCAT_GAIN_RANGE-1))-1; +    const int32_t EISCAT_MIN_GAIN         = -(1<<(EISCAT_GAIN_RANGE-1)); +    const double EISCAT_DEFAULT_NORM_GAIN = 1.0; // Normalized. This is the actual digital gain value. +    const size_t EISCAT_BITS_PER_TAP      = 18; +    const eiscat_radio_ctrl_impl::fir_tap_t EISCAT_MAX_TAP_VALUE  = (1<<(EISCAT_BITS_PER_TAP-1))-1; +    const eiscat_radio_ctrl_impl::fir_tap_t EISCAT_MIN_TAP_VALUE  = -(1<<(EISCAT_BITS_PER_TAP-1)); +    const size_t EISCAT_NUM_FIR_TAPS      = 10; +    const size_t EISCAT_NUM_FIR_SETS      = 1024; // BRAM must be at least EISCAT_NUM_FIR_TAPS * EISCAT_NUM_FIR_SETS +    const size_t EISCAT_FIR_INDEX_IMPULSE = 1002; +    const size_t EISCAT_FIR_INDEX_ZEROS   = 1003; // FIXME +}; +  UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(eiscat_radio_ctrl)  {      UHD_LOG_TRACE("EISCAT", "eiscat_radio_ctrl_impl::ctor() "); -    const size_t num_chans = get_output_ports().size(); -    UHD_ASSERT_THROW(num_chans == EISCAT_NUM_CHANS); -    UHD_LOG_TRACE("EISCAT", "Number of channels: " << num_chans); - +    _num_ports = get_output_ports().size(); +    UHD_LOG_TRACE("EISCAT", "Number of channels: " << _num_ports);      UHD_LOG_TRACE("EISCAT", "Setting tick rate to " << EISCAT_TICK_RATE/1e6 << " MHz"); -    /**** Configure the radio itself ***************************************/ +    /**** Configure the radio_ctrl itself ***********************************/      radio_ctrl_impl::set_rate(EISCAT_TICK_RATE); -    for (size_t chan = 0; chan < num_chans; chan++) { +    for (size_t chan = 0; chan < _num_ports; chan++) {          radio_ctrl_impl::set_rx_frequency(EISCAT_CENTER_FREQ, chan); -        radio_ctrl_impl::set_rx_gain(EISCAT_DEFAULT_GAIN, chan); -        radio_ctrl_impl::set_rx_antenna(EISCAT_ANTENNA_NAME, chan); +        radio_ctrl_impl::set_rx_gain(EISCAT_DEFAULT_NULL_GAIN, chan); +        radio_ctrl_impl::set_rx_antenna(EISCAT_DEFAULT_ANTENNA, chan);          radio_ctrl_impl::set_rx_bandwidth(EISCAT_DEFAULT_BANDWIDTH, chan);      } +    /**** Configure the digital gain controls *******************************/ +    for (size_t i = 0; i < EISCAT_NUM_ANTENNAS; i++) { +        _tree->access<double>(get_arg_path("gain", i) / "value") +            .set_coercer([](double gain){ return std::max(-1.0, std::min(1.0, gain)); }) +            .add_coerced_subscriber([this, i](double gain){ this->set_antenna_gain(i, gain); }) +            .set(EISCAT_DEFAULT_NORM_GAIN) +        ; +    } +      /**** Set up legacy compatible properties ******************************/      // For use with multi_usrp APIs etc.      // For legacy prop tree init:      fs_path fe_path = fs_path("dboards") / "A" / "rx_frontends"; +    auto antenna_options = std::vector<std::string>{"BF"}; +    for (size_t i = 0; i < EISCAT_NUM_ANTENNAS; i++) { +        antenna_options.push_back(str(boost::format("Rx%d") % i)); +    } +      // The EISCAT dboards have 16 frontends total, but they map to 5 channels      // each through a matrix of FIR filters and summations. UHD will get much      // less confused if we create 5 fake frontends, because that's also the      // number of channels. Since we have no control over the frontends,      // nothing is lost here. -    for (size_t fe_idx = 0; fe_idx < EISCAT_NUM_CHANS; fe_idx++) { +    for (size_t fe_idx = 0; fe_idx < _num_ports; fe_idx++) {          _tree->create<std::string>(fe_path / fe_idx / "name")              .set(str(boost::format("EISCAT Beam Contributions %d") % fe_idx))          ; @@ -75,13 +105,11 @@ UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(eiscat_radio_ctrl)              .set("I")          ;          _tree->create<std::string>(fe_path / fe_idx / "antenna" / "value") -            .set(EISCAT_ANTENNA_NAME) -            //.add_coerced_subscriber(boost::bind(&eiscat_radio_ctrl_impl::set_rx_antenna, this, _1, 0)) -            ////.set_publisher(boost::bind(&radio_ctrl_impl::get_rx_antenna, this, 0)) -            //.set_publisher([](){ return EISCAT_ANTENNA_NAME; }) +            .add_coerced_subscriber(boost::bind(&eiscat_radio_ctrl_impl::set_rx_antenna, this, _1, 0)) +            .set_publisher(boost::bind(&radio_ctrl_impl::get_rx_antenna, this, 0))          ;          _tree->create<std::vector<std::string>>(fe_path / fe_idx / "antenna" / "options") -            .set(std::vector<std::string>(1, "Rx")) +            .set(antenna_options)          ;          _tree->create<double>(fe_path / fe_idx / "freq" / "value")              .set(EISCAT_CENTER_FREQ) @@ -92,12 +120,12 @@ UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(eiscat_radio_ctrl)              .set(meta_range_t(EISCAT_CENTER_FREQ, EISCAT_CENTER_FREQ))          ;          _tree->create<double>(fe_path / fe_idx / "gains" / "null" / "value") -            .set(EISCAT_DEFAULT_GAIN) +            .set(EISCAT_DEFAULT_NULL_GAIN)              //.set_coercer(boost::bind(&eiscat_radio_ctrl_impl::set_rx_gain, this, _1, 0))              //.set_publisher(boost::bind(&radio_ctrl_impl::get_rx_gain, this, 0))          ;          _tree->create<meta_range_t>(fe_path / fe_idx / "gains" / "null" / "range") -            .set(meta_range_t(EISCAT_DEFAULT_GAIN, EISCAT_DEFAULT_GAIN)) +            .set(meta_range_t(EISCAT_DEFAULT_NULL_GAIN, EISCAT_DEFAULT_NULL_GAIN))          ;          _tree->create<double>(fe_path / fe_idx / "bandwidth" / "value")              .set(EISCAT_DEFAULT_BANDWIDTH) @@ -115,7 +143,7 @@ UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(eiscat_radio_ctrl)      // We can actually stream data to an EISCAT board, so it needs some tx      // frontends too:      fe_path = fs_path("dboards") / "A" / "tx_frontends"; -    for (size_t fe_idx = 0; fe_idx < EISCAT_NUM_CHANS; fe_idx++) { +    for (size_t fe_idx = 0; fe_idx < _num_ports; fe_idx++) {          _tree->create<std::string>(fe_path / fe_idx / "name")              .set(str(boost::format("EISCAT Uplink %d") % fe_idx))          ; @@ -141,14 +169,78 @@ eiscat_radio_ctrl_impl::~eiscat_radio_ctrl_impl()      UHD_LOG_TRACE("EISCAT", "eiscat_radio_ctrl_impl::dtor() ");  } + +/**************************************************************************** + * Public API calls + ***************************************************************************/  void eiscat_radio_ctrl_impl::set_tx_antenna(const std::string &, const size_t)  {      throw uhd::runtime_error("Cannot set Tx antenna on EISCAT daughterboard");  } -void eiscat_radio_ctrl_impl::set_rx_antenna(const std::string & /* ant */, const size_t /* chan */) +void eiscat_radio_ctrl_impl::set_rx_antenna(const std::string &ant, const size_t port)  { -    UHD_LOG_WARNING("EISCAT", "Ignoring attempt to set Rx antenna"); +    UHD_ASSERT_THROW(port < EISCAT_NUM_PORTS); +    if (ant == "BF") { +        UHD_LOG_TRACE("EISCAT", "Setting antenna to 'BF' (which is a no-op)"); +        return; +    } +    if (ant.size() < 3 or ant.size() > 4 or (ant.substr(0, 2) != "Rx" and ant.substr(0, 2) != "BF")) { +        throw uhd::value_error(str( +            boost::format("EISCAT: Invalid antenna selection: %s") +            % ant +        )); +    } + +    bool use_fir_matrix = (ant.substr(0, 2) == "BF"); + +    const size_t antenna_idx = [&ant](){ +        try { +            return boost::lexical_cast<size_t>(ant.substr(2)); +        } catch (const boost::bad_lexical_cast&) { +            throw uhd::value_error(str( +                boost::format("EISCAT: Invalid antenna selection: %s") +                % ant +            )); +        } +    }(); + +    if (use_fir_matrix) { +        UHD_LOG_TRACE("EISCAT", str( +            boost::format("Setting port %d to only receive on antenna %d via FIR matrix") +            % port % antenna_idx +        )); +        // TODO: When we have a way to select neighbour contributions, we will need +        // to calculate the beam_index as a function of the port *and* if we're the +        // left or right USRP +        const size_t beam_index = port; +        uhd::time_spec_t send_now(0.0); + +        for (size_t i = 0; i < EISCAT_NUM_ANTENNAS; i++) { +            if (i == antenna_idx) { +                select_filter( +                    beam_index, +                    i, +                    EISCAT_FIR_INDEX_IMPULSE, +                    send_now +                ); +            } else { +                select_filter( +                    beam_index, +                    i, +                    EISCAT_FIR_INDEX_ZEROS, +                    send_now +                ); +            } +        } +    } else { +        set_arg<int>("choose_beams", 6); +        UHD_LOG_TRACE("EISCAT", str( +            boost::format("Setting port %d to only receive on antenna %d directly") +            % port % antenna_idx +        )); +        sr_write(SR_ANTENNA_SELECT_BASE + port, antenna_idx); +    }  }  double eiscat_radio_ctrl_impl::get_tx_frequency(const size_t /* chan */) @@ -201,13 +293,17 @@ double eiscat_radio_ctrl_impl::set_rate(double rate)      return get_rate();  } -size_t eiscat_radio_ctrl_impl::get_chan_from_dboard_fe(const std::string &fe, const uhd::direction_t dir) -{ +size_t eiscat_radio_ctrl_impl::get_chan_from_dboard_fe( +        const std::string &fe, +        const uhd::direction_t /* dir */ +) {      return boost::lexical_cast<size_t>(fe);  } -std::string eiscat_radio_ctrl_impl::get_dboard_fe_from_chan(const size_t chan, const uhd::direction_t dir) -{ +std::string eiscat_radio_ctrl_impl::get_dboard_fe_from_chan( +        const size_t chan, +        const uhd::direction_t /* dir */ +) {      return std::to_string(chan);  } @@ -232,4 +328,116 @@ bool eiscat_radio_ctrl_impl::check_radio_config()      return true;  } +/**************************************************************************** + * Internal methods + ***************************************************************************/ +void eiscat_radio_ctrl_impl::write_fir_taps( +    const size_t fir_idx, +    const std::vector<eiscat_radio_ctrl_impl::fir_tap_t> &taps +) { +    if (taps.size() > EISCAT_NUM_FIR_TAPS) { +        throw uhd::value_error(str( +            boost::format("Too many FIR taps for EISCAT filters (%d)") +            % taps.size() +        )); +    } +    for (const auto &tap: taps) { +        if (tap > EISCAT_MAX_TAP_VALUE or tap < EISCAT_MIN_TAP_VALUE) { +            throw uhd::value_error(str( +                boost::format("Filter tap for filter_idx %d exceeds dynamic range (%d bits are allowed)") +                % fir_idx % EISCAT_BITS_PER_TAP +            )); +        } +    } + +    UHD_LOG_TRACE("EISCAT", str( +        boost::format("Writing %d filter taps for filter index %d") +        % taps.size() % fir_idx +    )); +    for (size_t i = 0; i < EISCAT_NUM_FIR_TAPS; i++) { +        // Payload: +        // - bottom 14 bits address, fir_idx * 16 + tap_index +        // - top 18 bits are value +        uint32_t reg_value = (fir_idx * 16) + i;; +        if (taps.size() > i) { +            reg_value |= (taps[i] & 0x3FFFF) << 14; +        } +        sr_write("SR_FIR_BRAM_WRITE_TAPS", reg_value); +    } +} + +void eiscat_radio_ctrl_impl::select_filter( +        const size_t beam_index, +        const size_t antenna_index, +        const size_t fir_index, +        const uhd::time_spec_t &time_spec +) { +    if (antenna_index >= EISCAT_NUM_ANTENNAS) { +        throw uhd::value_error(str( +            boost::format("Antenna index %d out of range. There are %d antennas in EISCAT.") +            % antenna_index % EISCAT_NUM_ANTENNAS +        )); +    } +    if (beam_index >= EISCAT_NUM_BEAMS) { +        throw uhd::value_error(str( +            boost::format("Beam index %d out of range. There are %d beam channels in EISCAT.") +            % beam_index % EISCAT_NUM_BEAMS +        )); +    } + +    UHD_LOGGER_TRACE("EISCAT") +        << "Selecting filter " << fir_index +        << " for beam " << beam_index +        << " and antenna " << antenna_index +    ; +    bool send_now = (time_spec == uhd::time_spec_t(0.0)); +    uint32_t reg_value = 0 +        | (fir_index * 16) +        | (antenna_index & 0xF) << 14 +        | (beam_index & 0xF) << 18 +        | send_now << 22 +    ; + +    if (not send_now) { +        uint64_t cmd_time_ticks = time_spec.to_ticks(EISCAT_TICK_RATE); +        UHD_LOG_TRACE("EISCAT", str( +            boost::format("Filter selection will be applied at time %f (0x%016X == %u)") +            % time_spec.get_full_secs() +            % cmd_time_ticks +        )); +        sr_write("SR_FIR_COMMANDS_CTRL_TIME_LO", cmd_time_ticks & 0xFFFFFFFF); +        sr_write("SR_FIR_COMMANDS_CTRL_TIME_HI", (cmd_time_ticks >> 32) & 0xFFFFFFFF); +    } +    sr_write("SR_FIR_COMMANDS_RELOAD", reg_value); +} + +void eiscat_radio_ctrl_impl::set_antenna_gain( +    const size_t antenna_idx, +    const double normalized_gain +) { +    if (normalized_gain < -1.0 or normalized_gain > 1.0) { +        throw uhd::value_error(str( +            boost::format("Invalid gain value for antenna %d: %f") +            % antenna_idx % normalized_gain +        )); +    } + +    const auto fixpoint_gain = std::max<int32_t>( +        EISCAT_MIN_GAIN, +        std::min( +            EISCAT_MAX_GAIN, +            int32_t(normalized_gain * EISCAT_MAX_GAIN) +        ) +    ); + +    UHD_LOG_TRACE("EISCAT", str( +        boost::format("Setting digital gain value for antenna %d to %f (%d)") +        % antenna_idx % normalized_gain % fixpoint_gain +    )); +    sr_write(SR_ANTENNA_GAIN_BASE + antenna_idx, fixpoint_gain); +} + +/**************************************************************************** + * Registry + ***************************************************************************/  UHD_RFNOC_BLOCK_REGISTER(eiscat_radio_ctrl, "EISCATRadio"); diff --git a/host/lib/usrp/dboard/eiscat/eiscat_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/eiscat/eiscat_radio_ctrl_impl.hpp index 5bfbbc035..cb45208ce 100644 --- a/host/lib/usrp/dboard/eiscat/eiscat_radio_ctrl_impl.hpp +++ b/host/lib/usrp/dboard/eiscat/eiscat_radio_ctrl_impl.hpp @@ -31,7 +31,8 @@ namespace uhd {  class eiscat_radio_ctrl_impl : public radio_ctrl_impl  {  public: -    typedef boost::shared_ptr<eiscat_radio_ctrl_impl> sptr; +    using sptr = boost::shared_ptr<eiscat_radio_ctrl_impl>; +    using fir_tap_t = int32_t;      /************************************************************************       * Structors @@ -46,6 +47,21 @@ public:      double set_rate(double rate);      void set_tx_antenna(const std::string &ant, const size_t chan); + +    /*! Configures FPGA switching for antenna selection +     * +     * +     * Valid antenna values: +     * - BF: This is the default. Will apply the beamforming matrix in whatever +     *   state it currently is. +     * - Rx0...Rx15: Will mux the antenna signal 0...15 straight to this +     *   channel. Note that this will disable the FIR matrix entirely. +     * - BF0...BF15: Will configure the FIR filter matrix such that only the +     *   contributions from antenna 0...15 are passed to this channel. +     * +     * +     * \throws uhd::value_error if the antenna value was not valid +     */      void set_rx_antenna(const std::string &ant, const size_t chan);      double set_tx_frequency(const double freq, const size_t chan); @@ -66,6 +82,56 @@ protected:  private: +    /*! Write filter taps for a specific FIR filter. +     * +     * Note: If the number of taps is smaller than the number of available +     * filter taps, it is padded with zero. +     * +     * \param fir_idx The index of the FIR filter we are reprogramming +     * \param taps A list of FIR filter taps for this filter. +     * +     * \throws uhd::value_error if the number of taps is longer than the number +     *                          of taps that the filter can handle, or if any +     *                          tap has more bits than allowed. +     */ +    void write_fir_taps( +        const size_t fir_idx, +        const std::vector<fir_tap_t> &taps +    ); + + +    /*! Choose a filter to be applied between an output beam and antenna input +     * +     * \param beam_index Beam index +     * \param antenna_index Antenna index +     * \param fir_index The index of the FIR filter taps that get applied +     * \param time_spec If non-zero, the taps get applied at this time +     */ +    void select_filter( +        const size_t beam_index, +        const size_t antenna_index, +        const size_t fir_index, +        const uhd::time_spec_t &time_spec +    ); + +    /*! Sets the digital gain on a specific antenna +     * +     * \param antenna_idx Antenna for which this gain setting applies +     * \param normalized_gain A value in [0, 1] which gets converted to a +     *                        digital gain value +     */ +    void set_antenna_gain( +        const size_t antenna_idx, +        const double normalized_gain +    ); + +    /*! The number of channels this block outputs +     * +     * This is *not* the number of antennas, but the number of streams a single +     * block outputs to the crossbar. +     */ +    size_t _num_ports; +  }; /* class radio_ctrl_impl */  }} /* namespace uhd::rfnoc */  | 
