diff options
Diffstat (limited to 'host')
| -rw-r--r-- | host/include/uhd/utils/soft_register.hpp | 312 | ||||
| -rw-r--r-- | host/lib/ic_reg_maps/gen_lmk04816_regs.py | 2 | ||||
| -rw-r--r-- | host/lib/usrp/x300/x300_adc_ctrl.cpp | 4 | ||||
| -rw-r--r-- | host/lib/usrp/x300/x300_clock_ctrl.cpp | 243 | ||||
| -rw-r--r-- | host/lib/usrp/x300/x300_clock_ctrl.hpp | 18 | ||||
| -rw-r--r-- | host/lib/usrp/x300/x300_fw_common.h | 2 | ||||
| -rw-r--r-- | host/lib/usrp/x300/x300_impl.cpp | 343 | ||||
| -rw-r--r-- | host/lib/usrp/x300/x300_impl.hpp | 48 | ||||
| -rw-r--r-- | host/lib/usrp/x300/x300_regs.hpp | 1 | 
9 files changed, 914 insertions, 59 deletions
| diff --git a/host/include/uhd/utils/soft_register.hpp b/host/include/uhd/utils/soft_register.hpp new file mode 100644 index 000000000..d3537a618 --- /dev/null +++ b/host/include/uhd/utils/soft_register.hpp @@ -0,0 +1,312 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_UTILS_SOFT_REGISTER_HPP +#define INCLUDED_UHD_UTILS_SOFT_REGISTER_HPP + +#include <boost/cstdint.hpp> +#include <boost/noncopyable.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/exception.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/locks.hpp> + +#define UHD_DEFINE_SOFT_REG_FIELD(name, width, shift) \ +    static const uhd::soft_reg_field_t name = (((shift & 0xFF) << 8) | (width & 0xFF)) + +namespace uhd { + +/* A register field is defined as a tuple of the mask and the shift. + * It can be used to make read-modify-write operations more convenient + * For efficiency reasons, it is recommended to always use a constant + * of this type because it will get optimized out by the compiler and + * will result in zero memory overhead + */ +typedef boost::uint32_t soft_reg_field_t; + +namespace soft_reg_field { +    inline size_t width(const soft_reg_field_t field) { +        return (field & 0xFF); +    } + +    inline size_t shift(const soft_reg_field_t field) { +        return ((field >> 8) & 0xFF); +    } + +    template<typename data_t> +    inline size_t mask(const soft_reg_field_t field) { +        return ((static_cast<data_t>(1)<<width(field))-1)<<shift(field); +    } +} + +/*! + * Soft register object that holds offset, soft-copy and the control iface. + * Methods give convenient field-level access to soft-copy and the ability + * to do read-modify-write operations. + */ +template<typename reg_data_t, bool readable, bool writeable> +class UHD_API soft_register_t : public boost::noncopyable { +public: +    typedef boost::shared_ptr< soft_register_t<reg_data_t, readable, writeable> > sptr; + +    /*! +     * Generic constructor for all soft_register types +     */ +    soft_register_t(wb_iface::wb_addr_type wr_addr, wb_iface::wb_addr_type rd_addr): +        _iface(NULL), _wr_addr(wr_addr), _rd_addr(rd_addr), _soft_copy(0) +    {} + +    /*! +     * Constructor for read-only, write-only registers and read-write registers +     * with rd_addr == wr_addr +     */ +    soft_register_t(wb_iface::wb_addr_type addr): +        _iface(NULL), _wr_addr(addr), _rd_addr(addr), _soft_copy(0) +    {} + +    /*! +     * Initialize the register when the underlying bus is usable. +     * Can be optionally synced with hardware. +     * NOTE: Memory management of the iface is up to the caller +     */ +    inline void initialize(wb_iface& iface, bool sync = false) +    { +        _iface = &iface; + +        //Synchronize with hardware. For RW register, flush THEN refresh. +        if (sync && writeable) flush(); +        if (sync && readable) refresh(); +    } + +    /*! +     * Update specified field in the soft-copy with the arg value. +     * Performs a read-modify-write operation so all other field are preserved. +     * NOTE: This does not write the value to hardware. +     */ +    inline void set(const soft_reg_field_t field, const reg_data_t value) +    { +        _soft_copy = (_soft_copy & ~soft_reg_field::mask<reg_data_t>(field)) | +                     ((value << soft_reg_field::shift(field)) & soft_reg_field::mask<reg_data_t>(field)); +    } + +    /*! +     * Get the value of the specified field from the soft-copy. +     * NOTE: This does not read anything from hardware. +     */ +    inline reg_data_t get(const soft_reg_field_t field) +    { +        return (_soft_copy & soft_reg_field::mask<reg_data_t>(field)) >> soft_reg_field::shift(field); +    } + +    /*! +     * Write the contents of the soft-copy to hardware. +     */ +    inline void flush() +    { +        if (writeable && _iface) { +            if (sizeof(reg_data_t) <= 2) { +                _iface->poke16(_wr_addr, static_cast<boost::uint16_t>(_soft_copy)); +            } else if (sizeof(reg_data_t) <= 4) { +                _iface->poke32(_wr_addr, static_cast<boost::uint32_t>(_soft_copy)); +            } else if (sizeof(reg_data_t) <= 8) { +                _iface->poke64(_wr_addr, static_cast<boost::uint64_t>(_soft_copy)); +            } else { +                throw uhd::not_implemented_error("soft_register only supports up to 64 bits."); +            } +        } else { +            throw uhd::not_implemented_error("soft_register is not writable."); +        } +    } + +    /*! +     * Read the contents of the register from hardware and update the soft copy. +     */ +    inline void refresh() +    { +        if (readable && _iface) { +            if (sizeof(reg_data_t) <= 2) { +                _soft_copy = static_cast<reg_data_t>(_iface->peek16(_rd_addr)); +            } else if (sizeof(reg_data_t) <= 4) { +                _soft_copy = static_cast<reg_data_t>(_iface->peek32(_rd_addr)); +            } else if (sizeof(reg_data_t) <= 8) { +                _soft_copy = static_cast<reg_data_t>(_iface->peek64(_rd_addr)); +            } else { +                throw uhd::not_implemented_error("soft_register only supports up to 64 bits."); +            } +        } else { +            throw uhd::not_implemented_error("soft_register is not readable."); +        } +    } + +    /*! +     * Shortcut for a set and a flush. +     */ +    inline void write(const soft_reg_field_t field, const reg_data_t value) +    { +        set(field, value); +        flush(); +    } + +    /*! +     * Shortcut for refresh and get +     */ +    inline reg_data_t read(const soft_reg_field_t field) +    { +        refresh(); +        return get(field); +    } + +private: +    wb_iface*                       _iface; +    const wb_iface::wb_addr_type    _wr_addr; +    const wb_iface::wb_addr_type    _rd_addr; +    reg_data_t                      _soft_copy; +}; + +/*! + * A synchronized soft register object. + * All operations in the synchronized register are serialized. + */ +template<typename reg_data_t, bool readable, bool writeable> +class UHD_API soft_register_sync_t : public soft_register_t<reg_data_t, readable, writeable> { +public: +    typedef boost::shared_ptr< soft_register_sync_t<reg_data_t, readable, writeable> > sptr; + +    soft_register_sync_t(wb_iface::wb_addr_type wr_addr, wb_iface::wb_addr_type rd_addr): +        soft_register_t<reg_data_t, readable, writeable>(wr_addr, rd_addr), _mutex() +    {} + +    soft_register_sync_t(wb_iface::wb_addr_type addr): +        soft_register_t<reg_data_t, readable, writeable>(addr), _mutex() +    {} + +    inline void initialize(wb_iface& iface, bool sync = false) +    { +        boost::lock_guard<boost::mutex> lock(_mutex); +        soft_register_t<reg_data_t, readable, writeable>::initialize(iface, sync); +    } + +    inline void set(const soft_reg_field_t field, const reg_data_t value) +    { +        boost::lock_guard<boost::mutex> lock(_mutex); +        soft_register_t<reg_data_t, readable, writeable>::set(field, value); +    } + +    inline reg_data_t get(const soft_reg_field_t field) +    { +        boost::lock_guard<boost::mutex> lock(_mutex); +        return soft_register_t<reg_data_t, readable, writeable>::get(field); +    } + +    inline void flush() +    { +        boost::lock_guard<boost::mutex> lock(_mutex); +        soft_register_t<reg_data_t, readable, writeable>::flush(); +    } + +    inline void refresh() +    { +        boost::lock_guard<boost::mutex> lock(_mutex); +        soft_register_t<reg_data_t, readable, writeable>::refresh(); +    } + +    inline void write(const soft_reg_field_t field, const reg_data_t value) +    { +        boost::lock_guard<boost::mutex> lock(_mutex); +        soft_register_t<reg_data_t, readable, writeable>::write(field, value); +    } + +    inline reg_data_t read(const soft_reg_field_t field) +    { +        boost::lock_guard<boost::mutex> lock(_mutex); +        return soft_register_t<reg_data_t, readable, writeable>::read(field); +    } + +private: +    boost::mutex _mutex; +}; + +/* + * Register Shortcut Formats: + * - soft_reg<bits>_<mode>_t: Soft register object with an unsynchronized soft-copy. + *                            Thread unsafe but lightweight. Mostly const propagated. + * - soft_reg<bits>_<mode>_sync_t: Soft register object with a synchronized soft-copy. + *                                 Thread safe but with memory/speed overhead. + * where: + * - <bits> = {16, 32 or 64} + * - <mode> = {wo(write-only), rw(read-write) or ro(read-only)} + * + */ + +//16-bit shortcuts +typedef soft_register_t<boost::uint16_t, false, true>       soft_reg16_wo_t; +typedef soft_register_t<boost::uint16_t, true, false>       soft_reg16_ro_t; +typedef soft_register_t<boost::uint16_t, true, true>        soft_reg16_rw_t; +typedef soft_register_sync_t<boost::uint16_t, false, true>  soft_reg16_wo_sync_t; +typedef soft_register_sync_t<boost::uint16_t, true, false>  soft_reg16_ro_sync_t; +typedef soft_register_sync_t<boost::uint16_t, true, true>   soft_reg16_rw_sync_t; +//32-bit shortcuts +typedef soft_register_t<boost::uint32_t, false, true>       soft_reg32_wo_t; +typedef soft_register_t<boost::uint32_t, true, false>       soft_reg32_ro_t; +typedef soft_register_t<boost::uint32_t, true, true>        soft_reg32_rw_t; +typedef soft_register_sync_t<boost::uint32_t, false, true>  soft_reg32_wo_sync_t; +typedef soft_register_sync_t<boost::uint32_t, true, false>  soft_reg32_ro_sync_t; +typedef soft_register_sync_t<boost::uint32_t, true, true>   soft_reg32_rw_sync_t; +//64-bit shortcuts +typedef soft_register_t<boost::uint64_t, false, true>       soft_reg64_wo_t; +typedef soft_register_t<boost::uint64_t, true, false>       soft_reg64_ro_t; +typedef soft_register_t<boost::uint64_t, true, true>        soft_reg64_rw_t; +typedef soft_register_sync_t<boost::uint64_t, false, true>  soft_reg64_wo_sync_t; +typedef soft_register_sync_t<boost::uint64_t, true, false>  soft_reg64_ro_sync_t; +typedef soft_register_sync_t<boost::uint64_t, true, true>   soft_reg64_rw_sync_t; + + +/* + * Usage example + * +  //===Define bit width, RW mode, and synchronization using base class=== +  class example_reg_t : public soft_reg32_wo_sync_t (or soft_reg32_wo_t) { +  public: +    //===Define all the fields=== +    UHD_DEFINE_SOFT_REG_FIELD(FIELD0,  1,  0);  //[0] +    UHD_DEFINE_SOFT_REG_FIELD(FIELD1, 15,  1);  //[15:1] +    UHD_DEFINE_SOFT_REG_FIELD(FIELD2, 16, 16);  //[31:16] + +    example_reg_t():    //ctor with no args +      soft_reg32_wo_t(SR_CORE_EXAMPLE_REG_OFFSET)) //===Bind to offset=== +    { +      //===Set Initial values=== +      set(FIELD0, 0); +      set(FIELD1, 1); +      set(FIELD2, 0xFFFF); +    } +  }; //===Full register definition encapsulated in one class=== + +  void main() { +    example_reg_t reg_obj; +    reg_obj.initialize(iface); +    reg_obj.write(example_reg_t::FIELD2, 0x1234); + +    example_reg_t::sptr reg_sptr = boost::make_shared<example_reg_t>(); +    reg_obj->initialize(iface); +    reg_obj->write(example_reg_t::FIELD2, 0x1234); +  } +*/ + +} //namespace uhd + +#endif /* INCLUDED_UHD_UTILS_SOFT_REGISTER_HPP */ diff --git a/host/lib/ic_reg_maps/gen_lmk04816_regs.py b/host/lib/ic_reg_maps/gen_lmk04816_regs.py index e89a82671..d1f0633a4 100644 --- a/host/lib/ic_reg_maps/gen_lmk04816_regs.py +++ b/host/lib/ic_reg_maps/gen_lmk04816_regs.py @@ -26,7 +26,7 @@ address0		 0[0:4]     0  CLKout0_1_DIV	         0[5:15]    25            CLKout0_1_HS             0[16]      0            RESET                    0[17]      0          no_reset, reset -CLKout0_1_DDLY	         0[18:27]   0          five +CLKout0_1_DDLY	         0[18:27]   0            CLKout0_ADLY_SEL         0[28]      0          d_pd, d_ev_x, d_odd_y, d_both  CLKout1_ADLY_SEL	 0[29]      0          d_pd, d_ev_x, d_odd_y, d_both  Required_0		 0[30]      0          diff --git a/host/lib/usrp/x300/x300_adc_ctrl.cpp b/host/lib/usrp/x300/x300_adc_ctrl.cpp index b0e4e4b95..edb4ce885 100644 --- a/host/lib/usrp/x300/x300_adc_ctrl.cpp +++ b/host/lib/usrp/x300/x300_adc_ctrl.cpp @@ -55,8 +55,8 @@ public:          _ads62p48_regs.lvds_cmos = ads62p48_regs_t::LVDS_CMOS_DDR_LVDS;          _ads62p48_regs.channel_control = ads62p48_regs_t::CHANNEL_CONTROL_INDEPENDENT;          _ads62p48_regs.data_format = ads62p48_regs_t::DATA_FORMAT_2S_COMPLIMENT; -        _ads62p48_regs.clk_out_pos_edge = ads62p48_regs_t::CLK_OUT_POS_EDGE_MINUS7_26; -        _ads62p48_regs.clk_out_neg_edge = ads62p48_regs_t::CLK_OUT_NEG_EDGE_MINUS7_26; +        _ads62p48_regs.clk_out_pos_edge = ads62p48_regs_t::CLK_OUT_POS_EDGE_NORMAL; +        _ads62p48_regs.clk_out_neg_edge = ads62p48_regs_t::CLK_OUT_NEG_EDGE_NORMAL;          this->send_ads62p48_reg(0); diff --git a/host/lib/usrp/x300/x300_clock_ctrl.cpp b/host/lib/usrp/x300/x300_clock_ctrl.cpp index e182f649b..350e9e9bc 100644 --- a/host/lib/usrp/x300/x300_clock_ctrl.cpp +++ b/host/lib/usrp/x300/x300_clock_ctrl.cpp @@ -29,6 +29,31 @@ static const double X300_REF_CLK_OUT_RATE  = 10e6;  static const boost::uint16_t X300_MAX_CLKOUT_DIV = 1045;  static const double X300_DEFAULT_DBOARD_CLK_RATE = 50e6; +struct x300_clk_delays { +    x300_clk_delays() : +        fpga_dly_ns(0.0),adc_dly_ns(0.0),dac_dly_ns(0.0),db_rx_dly_ns(0.0),db_tx_dly_ns(0.0) +    {} +    x300_clk_delays(double fpga, double adc, double dac, double db_rx, double db_tx) : +        fpga_dly_ns(fpga),adc_dly_ns(adc),dac_dly_ns(dac),db_rx_dly_ns(db_rx),db_tx_dly_ns(db_tx) +    {} + +    double fpga_dly_ns; +    double adc_dly_ns; +    double dac_dly_ns; +    double db_rx_dly_ns; +    double db_tx_dly_ns; +}; + +// Delay the FPGA_CLK by 900ps to ensure a safe ADC_SSCLK -> RADIO_CLK crossing. +// If the FPGA_CLK is delayed, we also need to delay the reference clocks going to the DAC +// because the data interface clock is generated from FPGA_CLK. +// NOTE: This delay value was verified at room temperature only. +static const x300_clk_delays X300_REV0_6_CLK_DELAYS = x300_clk_delays( +    /*fpga=*/0.900, /*adc=*/0.000, /*dac=*/0.900, /*db_rx=*/0.000, /*db_tx=*/0.000); + +static const x300_clk_delays X300_REV7_CLK_DELAYS = x300_clk_delays( +    /*fpga=*/0.000, /*adc=*/4.400, /*dac=*/0.000, /*db_rx=*/0.000, /*db_tx=*/0.000); +  using namespace uhd;  x300_clock_ctrl::~x300_clock_ctrl(void){ @@ -213,6 +238,187 @@ public:          _spiface->write_spi(_slaveno, spi_config_t::EDGE_RISE, data,32);      } +    double set_clock_delay(const x300_clock_which_t which, const double delay_ns, const bool resync = true) { +        //All dividers have are delayed by 5 taps by default. The delay +        //set by this function is relative to the 5 tap delay +        static const boost::uint16_t DDLY_MIN_TAPS  = 5; +        static const boost::uint16_t DDLY_MAX_TAPS  = 522;  //Extended mode + +        //The resolution and range of the analog delay is fixed +        static const double ADLY_RES_NS = 0.025; +        static const double ADLY_MIN_NS = 0.500; +        static const double ADLY_MAX_NS = 0.975; + +        //Each digital tap delays the clock by one VCO period +        double vco_period_ns = 1.0e9/_vco_freq; +        double half_vco_period_ns = vco_period_ns/2.0; + +        //Implement as much of the requested delay using digital taps. Whatever is leftover +        //will be made up using the analog delay element and the half-cycle digital tap. +        //A caveat here is that the analog delay starts at ADLY_MIN_NS, so we need to back off +        //by that much when coming up with the digital taps so that the difference can be made +        //up using the analog delay. +        boost::uint16_t ddly_taps = 0; +        if (delay_ns < ADLY_MIN_NS) { +            ddly_taps = static_cast<boost::uint16_t>(std::floor((delay_ns)/vco_period_ns)); +        } else { +            ddly_taps = static_cast<boost::uint16_t>(std::floor((delay_ns-ADLY_MIN_NS)/vco_period_ns)); +        } +        double leftover_delay = delay_ns - (vco_period_ns * ddly_taps); + +        //Compute settings +        boost::uint16_t ddly_value    = ddly_taps + DDLY_MIN_TAPS; +        bool            adly_en       = false; +        boost::uint8_t  adly_value    = 0; +        boost::uint8_t  half_shift_en = 0; + +        if (ddly_value > DDLY_MAX_TAPS) { +            throw uhd::value_error("set_clock_delay: Requested delay is out of range."); +        } + +        double coerced_delay = (vco_period_ns * ddly_taps); +        if (leftover_delay > ADLY_MAX_NS) { +            //The VCO is running too slowly for us to compensate the digital delay difference using +            //analog delay. Do the best we can. +            adly_en = true; +            adly_value = static_cast<boost::uint8_t>(round((ADLY_MAX_NS-ADLY_MIN_NS)/ADLY_RES_NS)); +            coerced_delay += ADLY_MAX_NS; +        } else if (leftover_delay >= ADLY_MIN_NS && leftover_delay <= ADLY_MAX_NS) { +            //The leftover delay can be compensated by the analog delay up to the analog delay resolution +            adly_en = true; +            adly_value = static_cast<boost::uint8_t>(round((leftover_delay-ADLY_MIN_NS)/ADLY_RES_NS)); +            coerced_delay += ADLY_MIN_NS+(ADLY_RES_NS*adly_value); +        } else if (leftover_delay >= (ADLY_MIN_NS - half_vco_period_ns) && leftover_delay < ADLY_MIN_NS) { +            //The leftover delay if less than the minimum supported analog delay but if we move the digital +            //delay back by half a VCO cycle then it will be in the range of the analog delay. So do that! +            adly_en = true; +            adly_value = static_cast<boost::uint8_t>(round((leftover_delay+half_vco_period_ns-ADLY_MIN_NS)/ADLY_RES_NS)); +            half_shift_en = 1; +            coerced_delay += ADLY_MIN_NS+(ADLY_RES_NS*adly_value)-half_vco_period_ns; +        } else { +            //Even after moving the digital delay back by half a cycle, we cannot make up the difference +            //so give up on compensating for the difference from the digital delay tap. +            //If control reaches here then the value of leftover_delay is possible very small and will still +            //be close to what the client requested. +        } + +        UHD_LOGV(often) +            << boost::format("x300_clock_ctrl::set_clock_delay: Which=%d, Requested=%f, Digital Taps=%d, Half Shift=%d, Analog Delay=%d (%s), Coerced Delay=%fns" +            ) % which % delay_ns % ddly_value % (half_shift_en?"ON":"OFF") % ((int)adly_value) % (adly_en?"ON":"OFF") % coerced_delay << std::endl; + +        //Apply settings +        switch (which) +        { +        case X300_CLOCK_WHICH_FPGA: +            _lmk04816_regs.CLKout0_1_DDLY = ddly_value; +            _lmk04816_regs.CLKout0_1_HS = half_shift_en; +            if (adly_en) { +                _lmk04816_regs.CLKout0_ADLY_SEL = lmk04816_regs_t::CLKOUT0_ADLY_SEL_D_BOTH; +                _lmk04816_regs.CLKout1_ADLY_SEL = lmk04816_regs_t::CLKOUT1_ADLY_SEL_D_BOTH; +                _lmk04816_regs.CLKout0_1_ADLY = adly_value; +            } else { +                _lmk04816_regs.CLKout0_ADLY_SEL = lmk04816_regs_t::CLKOUT0_ADLY_SEL_D_PD; +                _lmk04816_regs.CLKout1_ADLY_SEL = lmk04816_regs_t::CLKOUT1_ADLY_SEL_D_PD; +            } +            write_regs(0); +            write_regs(6); +            _delays.fpga_dly_ns = coerced_delay; +            break; +        case X300_CLOCK_WHICH_DB0_RX: +        case X300_CLOCK_WHICH_DB1_RX: +            _lmk04816_regs.CLKout2_3_DDLY = ddly_value; +            _lmk04816_regs.CLKout2_3_HS = half_shift_en; +            if (adly_en) { +                _lmk04816_regs.CLKout2_ADLY_SEL = lmk04816_regs_t::CLKOUT2_ADLY_SEL_D_BOTH; +                _lmk04816_regs.CLKout3_ADLY_SEL = lmk04816_regs_t::CLKOUT3_ADLY_SEL_D_BOTH; +                _lmk04816_regs.CLKout2_3_ADLY = adly_value; +            } else { +                _lmk04816_regs.CLKout2_ADLY_SEL = lmk04816_regs_t::CLKOUT2_ADLY_SEL_D_PD; +                _lmk04816_regs.CLKout3_ADLY_SEL = lmk04816_regs_t::CLKOUT3_ADLY_SEL_D_PD; +            } +            write_regs(1); +            write_regs(6); +            _delays.db_rx_dly_ns = coerced_delay; +            break; +        case X300_CLOCK_WHICH_DB0_TX: +        case X300_CLOCK_WHICH_DB1_TX: +            _lmk04816_regs.CLKout4_5_DDLY = ddly_value; +            _lmk04816_regs.CLKout4_5_HS = half_shift_en; +            if (adly_en) { +                _lmk04816_regs.CLKout4_ADLY_SEL = lmk04816_regs_t::CLKOUT4_ADLY_SEL_D_BOTH; +                _lmk04816_regs.CLKout5_ADLY_SEL = lmk04816_regs_t::CLKOUT5_ADLY_SEL_D_BOTH; +                _lmk04816_regs.CLKout4_5_ADLY = adly_value; +            } else { +                _lmk04816_regs.CLKout4_ADLY_SEL = lmk04816_regs_t::CLKOUT4_ADLY_SEL_D_PD; +                _lmk04816_regs.CLKout5_ADLY_SEL = lmk04816_regs_t::CLKOUT5_ADLY_SEL_D_PD; +            } +            write_regs(2); +            write_regs(7); +            _delays.db_tx_dly_ns = coerced_delay; +            break; +        case X300_CLOCK_WHICH_DAC0: +        case X300_CLOCK_WHICH_DAC1: +            _lmk04816_regs.CLKout6_7_DDLY = ddly_value; +            _lmk04816_regs.CLKout6_7_HS = half_shift_en; +            if (adly_en) { +                _lmk04816_regs.CLKout6_ADLY_SEL = lmk04816_regs_t::CLKOUT6_ADLY_SEL_D_BOTH; +                _lmk04816_regs.CLKout7_ADLY_SEL = lmk04816_regs_t::CLKOUT7_ADLY_SEL_D_BOTH; +                _lmk04816_regs.CLKout6_7_ADLY = adly_value; +            } else { +                _lmk04816_regs.CLKout6_ADLY_SEL = lmk04816_regs_t::CLKOUT6_ADLY_SEL_D_PD; +                _lmk04816_regs.CLKout7_ADLY_SEL = lmk04816_regs_t::CLKOUT7_ADLY_SEL_D_PD; +            } +            write_regs(3); +            write_regs(7); +            _delays.dac_dly_ns = coerced_delay; +            break; +        case X300_CLOCK_WHICH_ADC0: +        case X300_CLOCK_WHICH_ADC1: +            _lmk04816_regs.CLKout8_9_DDLY = ddly_value; +            _lmk04816_regs.CLKout8_9_HS = half_shift_en; +            if (adly_en) { +                _lmk04816_regs.CLKout8_ADLY_SEL = lmk04816_regs_t::CLKOUT8_ADLY_SEL_D_BOTH; +                _lmk04816_regs.CLKout9_ADLY_SEL = lmk04816_regs_t::CLKOUT9_ADLY_SEL_D_BOTH; +                _lmk04816_regs.CLKout8_9_ADLY = adly_value; +            } else { +                _lmk04816_regs.CLKout8_ADLY_SEL = lmk04816_regs_t::CLKOUT8_ADLY_SEL_D_PD; +                _lmk04816_regs.CLKout9_ADLY_SEL = lmk04816_regs_t::CLKOUT9_ADLY_SEL_D_PD; +            } +            write_regs(4); +            write_regs(8); +            _delays.adc_dly_ns = coerced_delay; +            break; +        default: +            throw uhd::value_error("set_clock_delay: Requested source is invalid."); +        } + +        //Delays are applied only on a sync event +        if (resync) sync_clocks(); + +        return coerced_delay; +    } + +    double get_clock_delay(const x300_clock_which_t which) { +        switch (which) +        { +        case X300_CLOCK_WHICH_FPGA: +            return _delays.fpga_dly_ns; +        case X300_CLOCK_WHICH_DB0_RX: +        case X300_CLOCK_WHICH_DB1_RX: +            return _delays.db_rx_dly_ns; +        case X300_CLOCK_WHICH_DB0_TX: +        case X300_CLOCK_WHICH_DB1_TX: +            return _delays.db_tx_dly_ns; +        case X300_CLOCK_WHICH_DAC0: +        case X300_CLOCK_WHICH_DAC1: +            return _delays.dac_dly_ns; +        case X300_CLOCK_WHICH_ADC0: +        case X300_CLOCK_WHICH_ADC1: +            return _delays.adc_dly_ns; +        default: +            throw uhd::value_error("get_clock_delay: Requested source is invalid."); +        } +    }  private: @@ -409,9 +615,6 @@ private:          _lmk04816_regs.CLKout0_1_PD = lmk04816_regs_t::CLKOUT0_1_PD_POWER_UP;          this->write_regs(0);          _lmk04816_regs.CLKout0_1_DIV = master_clock_div; -        _lmk04816_regs.CLKout0_ADLY_SEL = lmk04816_regs_t::CLKOUT0_ADLY_SEL_D_EV_X; -        _lmk04816_regs.CLKout6_ADLY_SEL = lmk04816_regs_t::CLKOUT6_ADLY_SEL_D_BOTH; -        _lmk04816_regs.CLKout7_ADLY_SEL = lmk04816_regs_t::CLKOUT7_ADLY_SEL_D_BOTH;          this->write_regs(0);          // Register 1 @@ -435,11 +638,6 @@ private:          _lmk04816_regs.CLKout1_TYPE = lmk04816_regs_t::CLKOUT1_TYPE_P_DOWN; //CPRI feedback clock, use LVDS          _lmk04816_regs.CLKout2_TYPE = lmk04816_regs_t::CLKOUT2_TYPE_LVPECL_700MVPP; //DB_0_RX          _lmk04816_regs.CLKout3_TYPE = lmk04816_regs_t::CLKOUT3_TYPE_LVPECL_700MVPP; //DB_1_RX -        // Delay the FPGA_CLK by 900ps to ensure a safe ADC_SSCLK -> RADIO_CLK crossing. -        // If the FPGA_CLK is delayed, we also need to delay the reference clocks going to the DAC -        // because the data interface clock is generated from FPGA_CLK. -        // NOTE: This delay value was verified at room temperature only. -        _lmk04816_regs.CLKout0_1_ADLY = 0x10;          // Register 7          _lmk04816_regs.CLKout4_TYPE = lmk04816_regs_t::CLKOUT4_TYPE_LVPECL_700MVPP; //DB_1_TX @@ -447,7 +645,6 @@ private:          _lmk04816_regs.CLKout6_TYPE = lmk04816_regs_t::CLKOUT6_TYPE_LVPECL_700MVPP; //DB0_DAC          _lmk04816_regs.CLKout7_TYPE = lmk04816_regs_t::CLKOUT7_TYPE_LVPECL_700MVPP; //DB1_DAC          _lmk04816_regs.CLKout8_TYPE = lmk04816_regs_t::CLKOUT8_TYPE_LVPECL_700MVPP; //DB0_ADC -        _lmk04816_regs.CLKout6_7_ADLY = _lmk04816_regs.CLKout0_1_ADLY;          // Register 8          _lmk04816_regs.CLKout9_TYPE = lmk04816_regs_t::CLKOUT9_TYPE_LVPECL_700MVPP; //DB1_ADC @@ -506,6 +703,19 @@ private:          // PLL2_P_30 set in individual cases above          // PLL2_N_30 set in individual cases above +        if (_hw_rev >= 7) { +            _delays = X300_REV7_CLK_DELAYS; +        } else { +            _delays = X300_REV0_6_CLK_DELAYS; +        } + +        //Apply delay values +        set_clock_delay(X300_CLOCK_WHICH_FPGA,   _delays.fpga_dly_ns,  false); +        set_clock_delay(X300_CLOCK_WHICH_DB0_RX, _delays.db_rx_dly_ns, false);  //Sets both Ch0 and Ch1 +        set_clock_delay(X300_CLOCK_WHICH_DB0_TX, _delays.db_tx_dly_ns, false);  //Sets both Ch0 and Ch1 +        set_clock_delay(X300_CLOCK_WHICH_ADC0,   _delays.adc_dly_ns,   false);  //Sets both Ch0 and Ch1 +        set_clock_delay(X300_CLOCK_WHICH_DAC0,   _delays.dac_dly_ns,   false);  //Sets both Ch0 and Ch1 +          /* Write the configuration values into the LMK */          for (size_t i = 1; i <= 16; ++i) {              this->write_regs(i); @@ -517,13 +727,14 @@ private:          this->sync_clocks();      } -    const spi_iface::sptr _spiface; -    const size_t _slaveno; -    const size_t _hw_rev; -    const double _master_clock_rate; -    const double _system_ref_rate; -    lmk04816_regs_t _lmk04816_regs; -    double _vco_freq; +    const spi_iface::sptr   _spiface; +    const size_t            _slaveno; +    const size_t            _hw_rev; +    const double            _master_clock_rate; +    const double            _system_ref_rate; +    lmk04816_regs_t         _lmk04816_regs; +    double                  _vco_freq; +    x300_clk_delays         _delays;  };  x300_clock_ctrl::sptr x300_clock_ctrl::make(uhd::spi_iface::sptr spiface, diff --git a/host/lib/usrp/x300/x300_clock_ctrl.hpp b/host/lib/usrp/x300/x300_clock_ctrl.hpp index 9c08aa356..160a14e6d 100644 --- a/host/lib/usrp/x300/x300_clock_ctrl.hpp +++ b/host/lib/usrp/x300/x300_clock_ctrl.hpp @@ -33,7 +33,7 @@ enum x300_clock_which_t      X300_CLOCK_WHICH_DB0_TX,      X300_CLOCK_WHICH_DB1_RX,      X300_CLOCK_WHICH_DB1_TX, -    X300_CLOCK_WHICH_TEST, +    X300_CLOCK_WHICH_FPGA,  };  class x300_clock_ctrl : boost::noncopyable @@ -94,6 +94,22 @@ public:       */      virtual void set_ref_out(const bool) = 0; +    /*! Set the clock delay for the given clock divider. +     * \param which which clock +     * \param rate the delay in nanoseconds +     * \param resync resync clocks to apply delays +     * \return the actual delay value set +     * \throw exception when which invalid or delay_ns out of range +     */ +    virtual double set_clock_delay(const x300_clock_which_t which, const double delay_ns, const bool resync = true) = 0; + +    /*! Get the clock delay for the given clock divider. +     * \param which which clock +     * \return the actual delay value set +     * \throw exception when which invalid +     */ +    virtual double get_clock_delay(const x300_clock_which_t which) = 0; +      /*! Reset the clocks.       *  Should be called if the reference clock changes       *  to reduce the time required to achieve a lock. diff --git a/host/lib/usrp/x300/x300_fw_common.h b/host/lib/usrp/x300/x300_fw_common.h index a526cabe5..00eafe091 100644 --- a/host/lib/usrp/x300/x300_fw_common.h +++ b/host/lib/usrp/x300/x300_fw_common.h @@ -31,7 +31,7 @@ extern "C" {  #define X300_FW_COMPAT_MAJOR 4  #define X300_FW_COMPAT_MINOR 0 -#define X300_FPGA_COMPAT_MAJOR 10 +#define X300_FPGA_COMPAT_MAJOR 11  //shared memory sections - in between the stack and the program space  #define X300_FW_SHMEM_BASE 0x6000 diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp index b2b9e5c6a..a2b2c9f9e 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -16,7 +16,6 @@  //  #include "x300_impl.hpp" -#include "x300_regs.hpp"  #include "x300_lvbitx.hpp"  #include "x310_lvbitx.hpp"  #include <boost/algorithm/string.hpp> @@ -688,9 +687,16 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)      ////////////////////////////////////////////////////////////////////      // setup radios      //////////////////////////////////////////////////////////////////// -    UHD_MSG(status) << "Initialize Radio control..." << std::endl; -    this->setup_radio(mb_i, "A"); -    this->setup_radio(mb_i, "B"); +    this->setup_radio(mb_i, "A", dev_addr); +    this->setup_radio(mb_i, "B", dev_addr); + +    //////////////////////////////////////////////////////////////////// +    // ADC test and cal +    //////////////////////////////////////////////////////////////////// +    if (dev_addr.has_key("self_cal_adc_delay")) { +        self_cal_adc_xfer_delay(mb, true /* Apply ADC delay */); +    } +    self_test_adcs(mb);      ////////////////////////////////////////////////////////////////////      // front panel gpio @@ -820,8 +826,15 @@ x300_impl::~x300_impl(void)      {          BOOST_FOREACH(mboard_members_t &mb, _mb)          { -            mb.radio_perifs[0].ctrl->poke32(TOREG(SR_MISC_OUTS), (1 << 2)); //disable/reset ADC/DAC -            mb.radio_perifs[1].ctrl->poke32(TOREG(SR_MISC_OUTS), (1 << 2)); //disable/reset ADC/DAC +            //disable/reset ADC/DAC +            mb.radio_perifs[0].misc_outs->set(radio_misc_outs_reg::ADC_RESET, 1); +            mb.radio_perifs[0].misc_outs->set(radio_misc_outs_reg::DAC_RESET_N, 0); +            mb.radio_perifs[0].misc_outs->set(radio_misc_outs_reg::DAC_ENABLED, 0); +            mb.radio_perifs[0].misc_outs->flush(); +            mb.radio_perifs[1].misc_outs->set(radio_misc_outs_reg::ADC_RESET, 1); +            mb.radio_perifs[1].misc_outs->set(radio_misc_outs_reg::DAC_RESET_N, 0); +            mb.radio_perifs[1].misc_outs->set(radio_misc_outs_reg::DAC_ENABLED, 0); +            mb.radio_perifs[1].misc_outs->flush();              //kill the claimer task and unclaim the device              mb.claimer_task.reset(); @@ -841,15 +854,7 @@ x300_impl::~x300_impl(void)      }  } -static void check_adc(wb_iface::sptr iface, const boost::uint32_t val) -{ -    boost::uint32_t adc_rb = iface->peek32(RB32_RX); -    adc_rb ^= 0xfffc0000; //adapt for I inversion in FPGA -    //UHD_MSG(status) << "adc_rb " << std::hex << adc_rb << "  val " << std::hex << val << std::endl; -    UHD_ASSERT_THROW(adc_rb == val); -} - -void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name) +void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, const uhd::device_addr_t &dev_addr)  {      const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_i);      UHD_ASSERT_THROW(mb_i < _mb.size()); @@ -857,6 +862,8 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name)      const size_t radio_index = mb.get_radio_index(slot_name);      radio_perifs_t &perif = mb.radio_perifs[radio_index]; +    UHD_MSG(status) << boost::format("Initialize Radio%d control...") % radio_index << std::endl; +      ////////////////////////////////////////////////////////////////////      // radio control      //////////////////////////////////////////////////////////////////// @@ -864,8 +871,20 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name)      boost::uint32_t ctrl_sid;      both_xports_t xport = this->make_transport(mb_i, dest, X300_RADIO_DEST_PREFIX_CTRL, device_addr_t(), ctrl_sid);      perif.ctrl = radio_ctrl_core_3000::make(mb.if_pkt_is_big_endian, xport.recv, xport.send, ctrl_sid, slot_name); -    perif.ctrl->poke32(TOREG(SR_MISC_OUTS), (1 << 2)); //reset adc + dac -    perif.ctrl->poke32(TOREG(SR_MISC_OUTS),  (1 << 1) | (1 << 0)); //out of reset + dac enable + +    perif.misc_outs = boost::make_shared<radio_misc_outs_reg>(); +    perif.misc_ins = boost::make_shared<radio_misc_ins_reg>(); +    perif.misc_outs->initialize(*perif.ctrl, true); +    perif.misc_ins->initialize(*perif.ctrl); + +    //reset adc + dac +    perif.misc_outs->set(radio_misc_outs_reg::ADC_RESET, 1); +    perif.misc_outs->set(radio_misc_outs_reg::DAC_RESET_N, 0); +    perif.misc_outs->flush(); +    perif.misc_outs->set(radio_misc_outs_reg::ADC_RESET, 0); +    perif.misc_outs->set(radio_misc_outs_reg::DAC_RESET_N, 1); +    perif.misc_outs->set(radio_misc_outs_reg::DAC_ENABLED, 1); +    perif.misc_outs->flush();      this->register_loopback_self_test(perif.ctrl); @@ -874,31 +893,16 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name)      perif.dac = x300_dac_ctrl::make(perif.spi, DB_DAC_SEN, mb.clock->get_master_clock_rate());      perif.leds = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_LEDS)); +    //Capture delays are calibrated every time. The status is only printed is the user +    //asks to run the xfer self cal using "self_cal_adc_delay" +    self_cal_adc_capture_delay(mb, radio_index, dev_addr.has_key("self_cal_adc_delay")); +      _tree->access<time_spec_t>(mb_path / "time" / "cmd")          .subscribe(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1));      _tree->access<double>(mb_path / "tick_rate")          .subscribe(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1));      //////////////////////////////////////////////////////////////// -    // ADC self test -    //////////////////////////////////////////////////////////////// -    perif.adc->set_test_word("ones", "ones"); check_adc(perif.ctrl, 0xfffcfffc); -    perif.adc->set_test_word("zeros", "zeros"); check_adc(perif.ctrl, 0x00000000); -    perif.adc->set_test_word("ones", "zeros"); check_adc(perif.ctrl, 0xfffc0000); -    perif.adc->set_test_word("zeros", "ones"); check_adc(perif.ctrl, 0x0000fffc); -    for (size_t k = 0; k < 14; k++) -    { -        perif.adc->set_test_word("zeros", "custom", 1 << k); -        check_adc(perif.ctrl, 1 << (k+2)); -    } -    for (size_t k = 0; k < 14; k++) -    { -        perif.adc->set_test_word("custom", "zeros", 1 << k); -        check_adc(perif.ctrl, 1 << (k+18)); -    } -    perif.adc->set_test_word("normal", "normal"); - -    ////////////////////////////////////////////////////////////////      // create codec control objects      ////////////////////////////////////////////////////////////////      _tree->create<int>(mb_path / "rx_codecs" / slot_name / "gains"); //phony property so this dir exists @@ -1789,3 +1793,270 @@ x300_impl::x300_mboard_t x300_impl::get_mb_type_from_eeprom(const uhd::usrp::mbo      return mb_type;  } +void x300_impl::self_cal_adc_capture_delay(mboard_members_t& mb, const size_t radio_i, bool print_status) +{ +    radio_perifs_t& perif = mb.radio_perifs[radio_i]; +    if (print_status) UHD_MSG(status) << "Running ADC capture delay self-cal..." << std::flush; + +    static const boost::uint32_t NUM_DELAY_STEPS = 32;   //The IDELAYE2 element has 32 steps +    int win_start = -1, win_stop = -1; + +    for (boost::uint32_t dly_tap = 0; dly_tap < NUM_DELAY_STEPS; dly_tap++) { +        //Apply delay +        perif.misc_outs->write(radio_misc_outs_reg::ADC_DATA_DLY_VAL, dly_tap); +        perif.misc_outs->write(radio_misc_outs_reg::ADC_DATA_DLY_STB, 1); +        perif.misc_outs->write(radio_misc_outs_reg::ADC_DATA_DLY_STB, 0); + +        boost::uint32_t err_code = 0; + +        // -- Test I Channel -- +        //Put ADC in ramp test mode. Tie the other channel to all ones. +        perif.adc->set_test_word("ramp", "ones"); +        //Turn on the pattern checker in the FPGA. It will lock when it sees a zero +        //and count deviations from the expected value +        perif.misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 0); +        perif.misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 1); +        //10ms @ 200MHz = 2 million samples +        boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +        if (perif.misc_ins->read(radio_misc_ins_reg::ADC_CHECKER0_I_LOCKED)) { +            err_code += perif.misc_ins->get(radio_misc_ins_reg::ADC_CHECKER0_I_ERROR); +        } else { +            err_code += 100;    //Increment error code by 100 to indicate no lock +        } + +        // -- Test Q Channel -- +        //Put ADC in ramp test mode. Tie the other channel to all ones. +        perif.adc->set_test_word("ones", "ramp"); +        //Turn on the pattern checker in the FPGA. It will lock when it sees a zero +        //and count deviations from the expected value +        perif.misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 0); +        perif.misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 1); +        //10ms @ 200MHz = 2 million samples +        boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +        if (perif.misc_ins->read(radio_misc_ins_reg::ADC_CHECKER0_Q_LOCKED)) { +            err_code += perif.misc_ins->get(radio_misc_ins_reg::ADC_CHECKER0_Q_ERROR); +        } else { +            err_code += 100;    //Increment error code by 100 to indicate no lock +        } + +        if (err_code == 0) { +            if (win_start == -1) {      //This is the first window +                win_start = dly_tap; +                win_stop = dly_tap; +            } else {                    //We are extending the window +                win_stop = dly_tap; +            } +        } else { +            if (win_start != -1) {      //A valid window turned invalid +                if (win_stop - win_start >= 4) break; +            } +        } +        //UHD_MSG(status) << (boost::format("CapTap=%d, Error=%d\n") % dly_tap % err_code); +    } +    perif.adc->set_test_word("normal", "normal"); +    perif.misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 0); + +    if (win_start == -1) { +        throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration failed. Convergence error."); +    } + +    if (win_stop-win_start < 4) { +        throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration failed. Valid window too narrow."); +    } + +    boost::uint32_t ideal_tap = (win_stop + win_start) / 2; +    perif.misc_outs->write(radio_misc_outs_reg::ADC_DATA_DLY_VAL, ideal_tap); +    perif.misc_outs->write(radio_misc_outs_reg::ADC_DATA_DLY_STB, 1); +    perif.misc_outs->write(radio_misc_outs_reg::ADC_DATA_DLY_STB, 0); + +    if (print_status) { +        double tap_delay = (1.0e12 / mb.clock->get_master_clock_rate()) / (2*32); //in ps +        UHD_MSG(status) << boost::format(" done (Tap=%d, Window=%d, TapDelay=%.3fps)\n") % ideal_tap % (win_stop-win_start) % tap_delay; +    } +} + +double x300_impl::self_cal_adc_xfer_delay(mboard_members_t& mb, bool apply_delay) +{ +    UHD_MSG(status) << "Running ADC transfer delay self-cal: " << std::flush; + +    //Effective resolution of the self-cal. +    static const size_t NUM_DELAY_STEPS = 100; + +    double master_clk_period = (1.0e9 / mb.clock->get_master_clock_rate()); //in ns +    double delay_start = 0.0; +    double delay_range = 2 * master_clk_period; +    double delay_incr = delay_range / NUM_DELAY_STEPS; + +    UHD_MSG(status) << "Measuring..." << std::flush; +    double cached_clk_delay = mb.clock->get_clock_delay(X300_CLOCK_WHICH_ADC0); +    double fpga_clk_delay = mb.clock->get_clock_delay(X300_CLOCK_WHICH_FPGA); + +    //Iterate through several values of delays and measure ADC data integrity +    std::vector< std::pair<double,bool> > results; +    for (size_t i = 0; i < NUM_DELAY_STEPS; i++) { +        //Delay the ADC clock (will set both Ch0 and Ch1 delays) +        double delay = mb.clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, delay_incr*i + delay_start); +        wait_for_ref_locked(mb.zpu_ctrl, 0.1); + +        boost::uint32_t err_code = 0; +        for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) { +            //Test each channel (I and Q) individually so as to not accidentally trigger +            //on the data from the other channel if there is a swap + +            // -- Test I Channel -- +            //Put ADC in ramp test mode. Tie the other channel to all ones. +            mb.radio_perifs[r].adc->set_test_word("ramp", "ones"); +            //Turn on the pattern checker in the FPGA. It will lock when it sees a zero +            //and count deviations from the expected value +            mb.radio_perifs[r].misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 0); +            mb.radio_perifs[r].misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 1); +            //50ms @ 200MHz = 10 million samples +            boost::this_thread::sleep(boost::posix_time::milliseconds(50)); +            if (mb.radio_perifs[r].misc_ins->read(radio_misc_ins_reg::ADC_CHECKER1_I_LOCKED)) { +                err_code += mb.radio_perifs[r].misc_ins->get(radio_misc_ins_reg::ADC_CHECKER1_I_ERROR); +            } else { +                err_code += 100;    //Increment error code by 100 to indicate no lock +            } + +            // -- Test Q Channel -- +            //Put ADC in ramp test mode. Tie the other channel to all ones. +            mb.radio_perifs[r].adc->set_test_word("ones", "ramp"); +            //Turn on the pattern checker in the FPGA. It will lock when it sees a zero +            //and count deviations from the expected value +            mb.radio_perifs[r].misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 0); +            mb.radio_perifs[r].misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 1); +            //50ms @ 200MHz = 10 million samples +            boost::this_thread::sleep(boost::posix_time::milliseconds(50)); +            if (mb.radio_perifs[r].misc_ins->read(radio_misc_ins_reg::ADC_CHECKER1_Q_LOCKED)) { +                err_code += mb.radio_perifs[r].misc_ins->get(radio_misc_ins_reg::ADC_CHECKER1_Q_ERROR); +            } else { +                err_code += 100;    //Increment error code by 100 to indicate no lock +            } +        } +        //UHD_MSG(status) << (boost::format("XferDelay=%fns, Error=%d\n") % delay % err_code); +        results.push_back(std::pair<double,bool>(delay, err_code==0)); +    } + +    //Calculate the valid window +    int win_start_idx = -1, win_stop_idx = -1, cur_start_idx = -1, cur_stop_idx = -1; +    for (size_t i = 0; i < results.size(); i++) { +        std::pair<double,bool>& item = results[i]; +        if (item.second) {  //If data is stable +            if (cur_start_idx == -1) {  //This is the first window +                cur_start_idx = i; +                cur_stop_idx = i; +            } else {                    //We are extending the window +                cur_stop_idx = i; +            } +        } else { +            if (cur_start_idx == -1) {  //We haven't yet seen valid data +                //Do nothing +            } else if (win_start_idx == -1) {   //We passed the first valid window +                win_start_idx = cur_start_idx; +                win_stop_idx = cur_stop_idx; +            } else {                    //Update cached window if current window is larger +                double cur_win_len = results[cur_stop_idx].first - results[cur_start_idx].first; +                double cached_win_len = results[win_stop_idx].first - results[win_start_idx].first; +                if (cur_win_len > cached_win_len) { +                    win_start_idx = cur_start_idx; +                    win_stop_idx = cur_stop_idx; +                } +            } +            //Reset current window +            cur_start_idx = -1; +            cur_stop_idx = -1; +        } +    } +    if (win_start_idx == -1) { +        throw uhd::runtime_error("self_cal_adc_xfer_delay: Self calibration failed. Convergence error."); +    } + +    double win_center = (results[win_stop_idx].first + results[win_start_idx].first) / 2.0; +    double win_length = results[win_stop_idx].first - results[win_start_idx].first; +    if (win_length < master_clk_period/4) { +        throw uhd::runtime_error("self_cal_adc_xfer_delay: Self calibration failed. Valid window too narrow."); +    } + +    //Cycle slip the relative delay by a clock cycle to prevent sample misalignment +    //fpga_clk_delay > 0 and 0 < win_center < 2*(1/MCR) so one cycle slip is all we need +    bool cycle_slip = (win_center-fpga_clk_delay >= master_clk_period); +    if (cycle_slip) { +        win_center -= master_clk_period; +    } + +    if (apply_delay) { +        UHD_MSG(status) << "Validating..." << std::flush; +        //Apply delay +        win_center = mb.clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, win_center);  //Sets ADC0 and ADC1 +        wait_for_ref_locked(mb.zpu_ctrl, 0.1); +        //Validate +        self_test_adcs(mb, 2000); +    } else { +        //Restore delay +        mb.clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, cached_clk_delay);  //Sets ADC0 and ADC1 +    } + +    //Teardown +    for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) { +        mb.radio_perifs[r].adc->set_test_word("normal", "normal"); +        mb.radio_perifs[r].misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 0); +    } +    UHD_MSG(status) << (boost::format(" done (FPGA->ADC=%.3fns%s, Window=%.3fns)\n") % +        (win_center-fpga_clk_delay) % (cycle_slip?" +cyc":"") % win_length); + +    return win_center; +} + +static void check_adc(wb_iface::sptr iface, const boost::uint32_t val, const boost::uint32_t i) +{ +    boost::uint32_t adc_rb = iface->peek32(RB32_RX); +    adc_rb ^= 0xfffc0000; //adapt for I inversion in FPGA +    if (val != adc_rb) { +        throw uhd::runtime_error( +            (boost::format("ADC self-test failed for Radio%d. (Exp=0x%x, Got=0x%x)")%i%val%adc_rb).str()); +    } +} + +void x300_impl::self_test_adcs(mboard_members_t& mb, boost::uint32_t ramp_time_ms) { +    for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) { +        radio_perifs_t &perif = mb.radio_perifs[r]; + +        //First test basic patterns +        perif.adc->set_test_word("ones", "ones"); check_adc(perif.ctrl, 0xfffcfffc,r); +        perif.adc->set_test_word("zeros", "zeros"); check_adc(perif.ctrl, 0x00000000,r); +        perif.adc->set_test_word("ones", "zeros"); check_adc(perif.ctrl, 0xfffc0000,r); +        perif.adc->set_test_word("zeros", "ones"); check_adc(perif.ctrl, 0x0000fffc,r); +        for (size_t k = 0; k < 14; k++) +        { +            perif.adc->set_test_word("zeros", "custom", 1 << k); +            check_adc(perif.ctrl, 1 << (k+2),r); +        } +        for (size_t k = 0; k < 14; k++) +        { +            perif.adc->set_test_word("custom", "zeros", 1 << k); +            check_adc(perif.ctrl, 1 << (k+18),r); +        } + +        //Turn on ramp pattern test +        perif.adc->set_test_word("ramp", "ramp"); +        perif.misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 0); +        perif.misc_outs->write(radio_misc_outs_reg::ADC_CHECKER_ENABLED, 1); +    } +    boost::this_thread::sleep(boost::posix_time::milliseconds(ramp_time_ms)); +    for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) { +        radio_perifs_t &perif = mb.radio_perifs[r]; + +        if (!perif.misc_ins->read(radio_misc_ins_reg::ADC_CHECKER1_I_LOCKED) || +            perif.misc_ins->read(radio_misc_ins_reg::ADC_CHECKER1_I_ERROR) || +            !perif.misc_ins->read(radio_misc_ins_reg::ADC_CHECKER1_Q_LOCKED) || +            perif.misc_ins->read(radio_misc_ins_reg::ADC_CHECKER1_Q_ERROR)) +        { +            throw uhd::runtime_error( +                (boost::format("ADC self-test failed for Radio%d. (Ramp checker failure)")%r).str()); +        } + +        //Return to normal mode +        perif.adc->set_test_word("normal", "normal"); +    } +} + diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp index c27133745..dca6360b8 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -49,6 +49,8 @@  #include <uhd/transport/nirio/niusrprio_session.h>  #include <uhd/transport/vrt_if_packet.hpp>  #include "recv_packet_demuxer_3000.hpp" +#include <uhd/utils/soft_register.hpp> +#include "x300_regs.hpp"  static const std::string X300_FW_FILE_NAME  = "usrp_x300_fw.bin"; @@ -169,9 +171,43 @@ public:  private:      boost::shared_ptr<async_md_type> _async_md; +    class radio_misc_outs_reg : public uhd::soft_reg32_wo_t { +    public: +        UHD_DEFINE_SOFT_REG_FIELD(DAC_ENABLED,          /*width*/ 1, /*shift*/ 0);  //[0] +        UHD_DEFINE_SOFT_REG_FIELD(DAC_RESET_N,          /*width*/ 1, /*shift*/ 1);  //[1] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_RESET,            /*width*/ 1, /*shift*/ 2);  //[2] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_STB,     /*width*/ 1, /*shift*/ 3);  //[3] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_VAL,     /*width*/ 5, /*shift*/ 4);  //[8:4] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_ENABLED,  /*width*/ 1, /*shift*/ 9);  //[9] + +        radio_misc_outs_reg(): uhd::soft_reg32_wo_t(TOREG(SR_MISC_OUTS)) { +            //Initial values +            set(DAC_ENABLED, 0); +            set(DAC_RESET_N, 0); +            set(ADC_RESET, 0); +            set(ADC_DATA_DLY_STB, 0); +            set(ADC_DATA_DLY_VAL, 16); +            set(ADC_CHECKER_ENABLED, 0); +        } +    }; +    class radio_misc_ins_reg : public uhd::soft_reg32_ro_t { +    public: +        UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 0);  //[0] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 1);  //[1] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 2);  //[2] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 3);  //[3] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_ERROR,  /*width*/ 1, /*shift*/ 4);  //[4] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_ERROR,  /*width*/ 1, /*shift*/ 5);  //[5] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_ERROR,  /*width*/ 1, /*shift*/ 6);  //[6] +        UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_ERROR,  /*width*/ 1, /*shift*/ 7);  //[7] + +        radio_misc_ins_reg(): uhd::soft_reg32_ro_t(RB32_MISC_INS) { } +    }; +      //perifs in the radio core      struct radio_perifs_t      { +        //Interfaces          radio_ctrl_core_3000::sptr ctrl;          spi_core_3000::sptr spi;          x300_adc_ctrl::sptr adc; @@ -184,6 +220,9 @@ private:          gpio_core_200_32wo::sptr leds;          rx_frontend_core_200::sptr rx_fe;          tx_frontend_core_200::sptr tx_fe; +        //Registers +        radio_misc_outs_reg::sptr misc_outs; +        radio_misc_ins_reg::sptr misc_ins;      };      //overflow recovery impl @@ -211,7 +250,8 @@ private:          i2c_core_100_wb32::sptr zpu_i2c;          //perifs in each radio -        radio_perifs_t radio_perifs[2]; //!< This is hardcoded s.t. radio_perifs[0] points to slot A and [1] to B +        static const size_t NUM_RADIOS = 2; +        radio_perifs_t radio_perifs[NUM_RADIOS]; //!< This is hardcoded s.t. radio_perifs[0] points to slot A and [1] to B          uhd::usrp::dboard_eeprom_t db_eeproms[8];          //! Return the index of a radio component, given a slot name. This means DSPs, radio_perifs          size_t get_radio_index(const std::string &slot_name) { @@ -259,7 +299,7 @@ private:        * \param mb_i Motherboard index        * \param slot_name Slot name (A or B).        */ -    void setup_radio(const size_t, const std::string &slot_name); +    void setup_radio(const size_t, const std::string &slot_name, const uhd::device_addr_t &dev_addr);      size_t _sid_framer;      struct sid_config_t @@ -364,6 +404,10 @@ private:      boost::uint32_t get_fp_gpio(gpio_core_200::sptr);      void set_fp_gpio(gpio_core_200::sptr, const gpio_attr_t, const boost::uint32_t); +    void self_cal_adc_capture_delay(mboard_members_t& mb, const size_t radio_i, bool print_status = false); +    double self_cal_adc_xfer_delay(mboard_members_t& mb, bool apply_delay = false); +    void self_test_adcs(mboard_members_t& mb, boost::uint32_t ramp_time_ms = 100); +      //**PRECONDITION**      //This function assumes that all the VITA times in "radios" are synchronized      //to a common reference. Currently, this function is called in get_tx_stream diff --git a/host/lib/usrp/x300/x300_regs.hpp b/host/lib/usrp/x300/x300_regs.hpp index 4c5729efe..e984eb908 100644 --- a/host/lib/usrp/x300/x300_regs.hpp +++ b/host/lib/usrp/x300/x300_regs.hpp @@ -49,6 +49,7 @@ localparam RB64_TIME_PPS = 16;  localparam RB32_TEST     = 24;  localparam RB32_RX       = 28;  localparam RB32_FP_GPIO  = 32; +localparam RB32_MISC_INS = 36;  localparam BL_ADDRESS    = 0;  localparam BL_DATA       = 1; | 
