diff options
Diffstat (limited to 'host/lib/usrp/x300')
-rw-r--r-- | host/lib/usrp/x300/x300_adc_ctrl.cpp | 4 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_clock_ctrl.cpp | 238 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_clock_ctrl.hpp | 18 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_dac_ctrl.cpp | 16 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_fw_common.h | 6 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_impl.cpp | 449 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_impl.hpp | 54 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_io_impl.cpp | 52 | ||||
-rw-r--r-- | host/lib/usrp/x300/x300_regs.hpp | 141 |
9 files changed, 768 insertions, 210 deletions
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 6450686dd..0812bcc8e 100644 --- a/host/lib/usrp/x300/x300_clock_ctrl.cpp +++ b/host/lib/usrp/x300/x300_clock_ctrl.cpp @@ -21,6 +21,7 @@ #include <uhd/utils/math.hpp> #include <boost/cstdint.hpp> #include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> #include <stdexcept> #include <cmath> #include <cstdlib> @@ -29,6 +30,30 @@ 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; +}; + +// Tune the FPGA->ADC clock delay 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. +static const x300_clk_delays X300_REV0_6_CLK_DELAYS = x300_clk_delays( + /*fpga=*/0.000, /*adc=*/1.600, /*dac=*/0.000, /*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>(boost::math::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>(boost::math::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>(boost::math::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,7 +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; this->write_regs(0); // Register 1 @@ -433,9 +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 - // Analog delay of 900ps to synchronize the radio clock with the source synchronous ADC clocks. - // This delay may need to vary due to temperature. Tested and 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 @@ -501,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); @@ -512,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_dac_ctrl.cpp b/host/lib/usrp/x300/x300_dac_ctrl.cpp index d3bcb8644..bb41146b6 100644 --- a/host/lib/usrp/x300/x300_dac_ctrl.cpp +++ b/host/lib/usrp/x300/x300_dac_ctrl.cpp @@ -129,12 +129,16 @@ public: _check_pll(); // Configure digital interface settings - write_ad9146_reg(0x16, 0x02); // Skew DCI signal by 615ps to find stable data eye - write_ad9146_reg(0x03, 0x00); // 2's comp, I first, byte wide interface - //fpga wants I,Q in the sample word: - //first transaction goes into low bits - //second transaction goes into high bits - //therefore, we want Q to go first (bit 6 == 1) + // Bypass DCI delay. We center the clock edge in the data + // valid window in the FPGA by phase shifting the DCI going + // to the DAC. + write_ad9146_reg(0x16, 0x04); + // 2's comp, I first, byte wide interface + write_ad9146_reg(0x03, 0x00); + // FPGA wants I,Q in the sample word: + // - First transaction goes into low bits + // - Second transaction goes into high bits + // therefore, we want Q to go first (bit 6 == 1) write_ad9146_reg(0x03, (1 << 6)); //2s comp, i first, byte mode // Configure interpolation filters diff --git a/host/lib/usrp/x300/x300_fw_common.h b/host/lib/usrp/x300/x300_fw_common.h index 76531f921..175d37849 100644 --- a/host/lib/usrp/x300/x300_fw_common.h +++ b/host/lib/usrp/x300/x300_fw_common.h @@ -29,10 +29,10 @@ extern "C" { #endif -#define X300_MAX_HW_REV 6 -#define X300_FW_COMPAT_MAJOR 3 +#define X300_MAX_HW_REV 7 +#define X300_FW_COMPAT_MAJOR 4 #define X300_FW_COMPAT_MINOR 0 -#define X300_FPGA_COMPAT_MAJOR 9 +#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 aff150acb..91e6ded26 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2013-2014 Ettus Research LLC +// Copyright 2013-2015 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 @@ -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> @@ -41,7 +40,7 @@ #define NIUSRPRIO_DEFAULT_RPC_PORT "5444" -#define X300_REV(x) (x - "A" + 1) +#define X300_REV(x) ((x) - "A" + 1) using namespace uhd; using namespace uhd::usrp; @@ -508,9 +507,10 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) x300_load_fw(mb.zpu_ctrl, x300_fw_image); } - //check compat -- good place to do after conditional loading - this->check_fw_compat(mb_path, mb.zpu_ctrl); + //check compat numbers + //check fpga compat before fw compat because the fw is a subset of the fpga image this->check_fpga_compat(mb_path, mb.zpu_ctrl); + this->check_fw_compat(mb_path, mb.zpu_ctrl); //store which FPGA image is loaded mb.loaded_fpga_image = get_fpga_option(mb.zpu_ctrl); @@ -696,23 +696,29 @@ 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 //////////////////////////////////////////////////////////////////// mb.fp_gpio = gpio_core_200::make(mb.radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO); - const std::vector<std::string> GPIO_ATTRS = boost::assign::list_of("CTRL")("DDR")("OUT")("ATR_0X")("ATR_RX")("ATR_TX")("ATR_XX"); - BOOST_FOREACH(const std::string &attr, GPIO_ATTRS) + BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map) { - _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / attr) + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / attr.second) .set(0) - .subscribe(boost::bind(&x300_impl::set_fp_gpio, this, mb.fp_gpio, attr, _1)); + .subscribe(boost::bind(&x300_impl::set_fp_gpio, this, mb.fp_gpio, attr.first, _1)); } _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") - .publish(boost::bind(&x300_impl::get_fp_gpio, this, mb.fp_gpio, "READBACK")); + .publish(boost::bind(&x300_impl::get_fp_gpio, this, mb.fp_gpio)); //////////////////////////////////////////////////////////////////// // register the time keepers - only one can be the highlander @@ -829,8 +835,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(); @@ -850,15 +863,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()); @@ -866,6 +871,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 //////////////////////////////////////////////////////////////////// @@ -873,8 +880,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); @@ -883,31 +902,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 @@ -1442,8 +1446,8 @@ bool x300_impl::wait_for_ref_locked(wb_iface::sptr ctrl, double timeout) boost::this_thread::sleep(boost::posix_time::milliseconds(1)); } while (boost::get_system_time() < timeout_time); - //failed to lock on reference - return false; + //Check one last time + return get_ref_locked(ctrl).to_bool(); } sensor_value_t x300_impl::get_ref_locked(wb_iface::sptr ctrl) @@ -1544,20 +1548,24 @@ void x300_impl::set_mb_eeprom(i2c_iface::sptr i2c, const mboard_eeprom_t &mb_eep * front-panel GPIO **********************************************************************/ -boost::uint32_t x300_impl::get_fp_gpio(gpio_core_200::sptr gpio, const std::string &) +boost::uint32_t x300_impl::get_fp_gpio(gpio_core_200::sptr gpio) { return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); } -void x300_impl::set_fp_gpio(gpio_core_200::sptr gpio, const std::string &attr, const boost::uint32_t value) +void x300_impl::set_fp_gpio(gpio_core_200::sptr gpio, const gpio_attr_t attr, const boost::uint32_t value) { - if (attr == "CTRL") return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); - if (attr == "DDR") return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); - if (attr == "OUT") return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); - if (attr == "ATR_0X") return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); - if (attr == "ATR_RX") return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); - if (attr == "ATR_TX") return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); - if (attr == "ATR_XX") return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); + switch (attr) + { + case GPIO_CTRL: return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); + case GPIO_DDR: return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); + case GPIO_OUT: return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); + case GPIO_ATR_0X: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); + case GPIO_ATR_RX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); + case GPIO_ATR_TX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); + case GPIO_ATR_XX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); + default: UHD_THROW_INVALID_CODE_PATH(); + } } /*********************************************************************** @@ -1730,14 +1738,20 @@ x300_impl::x300_mboard_t x300_impl::get_mb_type_from_pcie(const std::string& res case X300_USRP_PCIE_SSID: mb_type = USRP_X300_MB; break; case X310_USRP_PCIE_SSID: - case X310_2940R_PCIE_SSID: - case X310_2942R_PCIE_SSID: - case X310_2943R_PCIE_SSID: - case X310_2944R_PCIE_SSID: - case X310_2950R_PCIE_SSID: - case X310_2952R_PCIE_SSID: - case X310_2953R_PCIE_SSID: - case X310_2954R_PCIE_SSID: + case X310_2940R_40MHz_PCIE_SSID: + case X310_2940R_120MHz_PCIE_SSID: + case X310_2942R_40MHz_PCIE_SSID: + case X310_2942R_120MHz_PCIE_SSID: + case X310_2943R_40MHz_PCIE_SSID: + case X310_2943R_120MHz_PCIE_SSID: + case X310_2944R_40MHz_PCIE_SSID: + case X310_2950R_40MHz_PCIE_SSID: + case X310_2950R_120MHz_PCIE_SSID: + case X310_2952R_40MHz_PCIE_SSID: + case X310_2952R_120MHz_PCIE_SSID: + case X310_2953R_40MHz_PCIE_SSID: + case X310_2953R_120MHz_PCIE_SSID: + case X310_2954R_40MHz_PCIE_SSID: mb_type = USRP_X310_MB; break; default: mb_type = UNKNOWN; break; @@ -1765,14 +1779,20 @@ x300_impl::x300_mboard_t x300_impl::get_mb_type_from_eeprom(const uhd::usrp::mbo case X300_USRP_PCIE_SSID: mb_type = USRP_X300_MB; break; case X310_USRP_PCIE_SSID: - case X310_2940R_PCIE_SSID: - case X310_2942R_PCIE_SSID: - case X310_2943R_PCIE_SSID: - case X310_2944R_PCIE_SSID: - case X310_2950R_PCIE_SSID: - case X310_2952R_PCIE_SSID: - case X310_2953R_PCIE_SSID: - case X310_2954R_PCIE_SSID: + case X310_2940R_40MHz_PCIE_SSID: + case X310_2940R_120MHz_PCIE_SSID: + case X310_2942R_40MHz_PCIE_SSID: + case X310_2942R_120MHz_PCIE_SSID: + case X310_2943R_40MHz_PCIE_SSID: + case X310_2943R_120MHz_PCIE_SSID: + case X310_2944R_40MHz_PCIE_SSID: + case X310_2950R_40MHz_PCIE_SSID: + case X310_2950R_120MHz_PCIE_SSID: + case X310_2952R_40MHz_PCIE_SSID: + case X310_2952R_120MHz_PCIE_SSID: + case X310_2953R_40MHz_PCIE_SSID: + case X310_2953R_120MHz_PCIE_SSID: + case X310_2954R_40MHz_PCIE_SSID: mb_type = USRP_X310_MB; break; default: UHD_MSG(warning) << "X300 unknown product code in EEPROM: " << product_num << std::endl; @@ -1782,3 +1802,288 @@ 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 + static const boost::uint32_t NUM_RETRIES = 2; //Retry self-cal if it fails in warmup situations + static const boost::int32_t MIN_WINDOW_LEN = 4; + + boost::int32_t win_start = -1, win_stop = -1; + boost::uint32_t iter = 0; + while (iter++ < NUM_RETRIES) { + 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 >= MIN_WINDOW_LEN) { + break; //Valid window found + } else { + win_start = -1; //Reset window + } + } + } + //UHD_MSG(status) << (boost::format("CapTap=%d, Error=%d\n") % dly_tap % err_code); + } + + //Retry the self-cal if it fails + if ((win_start == -1 || (win_stop - win_start) < MIN_WINDOW_LEN) && iter < NUM_RETRIES /*not last iteration*/) { + win_start = -1; + win_stop = -1; + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + } else { + break; + } + } + 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 < MIN_WINDOW_LEN) { + 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, Iter=%d)\n") % ideal_tap % (win_stop-win_start) % tap_delay % iter; + } +} + +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 890ef7bcb..dca6360b8 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -1,5 +1,5 @@ // -// Copyright 2013-2014 Ettus Research LLC +// Copyright 2013-2015 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 @@ -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 @@ -361,8 +401,12 @@ private: void check_fpga_compat(const uhd::fs_path &mb_path, uhd::wb_iface::sptr iface); void update_atr_leds(gpio_core_200_32wo::sptr, const std::string &ant); - boost::uint32_t get_fp_gpio(gpio_core_200::sptr, const std::string &); - void set_fp_gpio(gpio_core_200::sptr, const std::string &, const boost::uint32_t); + 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 diff --git a/host/lib/usrp/x300/x300_io_impl.cpp b/host/lib/usrp/x300/x300_io_impl.cpp index 334ae8168..e3515af0c 100644 --- a/host/lib/usrp/x300/x300_io_impl.cpp +++ b/host/lib/usrp/x300/x300_io_impl.cpp @@ -23,6 +23,7 @@ #include <uhd/transport/nirio_zero_copy.hpp> #include "async_packet_handler.hpp" #include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/chdr.hpp> #include <boost/bind.hpp> #include <uhd/utils/tasks.hpp> #include <uhd/utils/log.hpp> @@ -124,41 +125,6 @@ void x300_impl::update_subdev_spec(const std::string &tx_rx, const size_t mb_i, /*********************************************************************** - * VITA stuff - **********************************************************************/ -static void x300_if_hdr_unpack_be( - const boost::uint32_t *packet_buff, - vrt::if_packet_info_t &if_packet_info -){ - if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; - return vrt::if_hdr_unpack_be(packet_buff, if_packet_info); -} - -static void x300_if_hdr_pack_be( - boost::uint32_t *packet_buff, - vrt::if_packet_info_t &if_packet_info -){ - if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; - return vrt::if_hdr_pack_be(packet_buff, if_packet_info); -} - -static void x300_if_hdr_unpack_le( - const boost::uint32_t *packet_buff, - vrt::if_packet_info_t &if_packet_info -){ - if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; - return vrt::if_hdr_unpack_le(packet_buff, if_packet_info); -} - -static void x300_if_hdr_pack_le( - boost::uint32_t *packet_buff, - vrt::if_packet_info_t &if_packet_info -){ - if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; - return vrt::if_hdr_pack_le(packet_buff, if_packet_info); -} - -/*********************************************************************** * RX flow control handler **********************************************************************/ static size_t get_rx_flow_control_window(size_t frame_size, size_t sw_buff_size, const device_addr_t& rx_args) @@ -209,9 +175,9 @@ static void handle_rx_flowctrl(const boost::uint32_t sid, zero_copy_if::sptr xpo //load header if (big_endian) - x300_if_hdr_pack_be(pkt, packet_info); + vrt::chdr::if_hdr_pack_be(pkt, packet_info); else - x300_if_hdr_pack_le(pkt, packet_info); + vrt::chdr::if_hdr_pack_le(pkt, packet_info); //load payload pkt[packet_info.num_header_words32+0] = uhd::htonx<boost::uint32_t>(0); @@ -276,12 +242,12 @@ static void handle_tx_async_msgs(boost::shared_ptr<x300_tx_fc_guts_t> guts, zero { if (big_endian) { - x300_if_hdr_unpack_be(packet_buff, if_packet_info); + vrt::chdr::if_hdr_unpack_be(packet_buff, if_packet_info); endian_conv = uhd::ntohx; } else { - x300_if_hdr_unpack_le(packet_buff, if_packet_info); + vrt::chdr::if_hdr_unpack_le(packet_buff, if_packet_info); endian_conv = uhd::wtohx; } } @@ -430,10 +396,10 @@ rx_streamer::sptr x300_impl::get_rx_stream(const uhd::stream_args_t &args_) //init some streamer stuff std::string conv_endianness; if (mb.if_pkt_is_big_endian) { - my_streamer->set_vrt_unpacker(&x300_if_hdr_unpack_be); + my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_be); conv_endianness = "be"; } else { - my_streamer->set_vrt_unpacker(&x300_if_hdr_unpack_le); + my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_le); conv_endianness = "le"; } @@ -594,10 +560,10 @@ tx_streamer::sptr x300_impl::get_tx_stream(const uhd::stream_args_t &args_) std::string conv_endianness; if (mb.if_pkt_is_big_endian) { - my_streamer->set_vrt_packer(&x300_if_hdr_pack_be); + my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_be); conv_endianness = "be"; } else { - my_streamer->set_vrt_packer(&x300_if_hdr_pack_le); + my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_le); conv_endianness = "le"; } diff --git a/host/lib/usrp/x300/x300_regs.hpp b/host/lib/usrp/x300/x300_regs.hpp index f920b5ae2..e984eb908 100644 --- a/host/lib/usrp/x300/x300_regs.hpp +++ b/host/lib/usrp/x300/x300_regs.hpp @@ -25,40 +25,41 @@ #define localparam static const int -localparam SR_DACSYNC = 5; -localparam SR_LOOPBACK = 6; -localparam SR_TEST = 7; -localparam SR_SPI = 8; -localparam SR_GPIO = 16; -localparam SR_MISC_OUTS = 24; -localparam SR_READBACK = 32; -localparam SR_TX_CTRL = 64; -localparam SR_RX_CTRL = 96; -localparam SR_TIME = 128; -localparam SR_RX_DSP = 144; -localparam SR_TX_DSP = 184; -localparam SR_LEDS = 195; -localparam SR_FP_GPIO = 200; -localparam SR_RX_FRONT = 208; -localparam SR_TX_FRONT = 216; - -localparam RB32_GPIO = 0; -localparam RB32_SPI = 4; -localparam RB64_TIME_NOW = 8; -localparam RB64_TIME_PPS = 16; -localparam RB32_TEST = 24; -localparam RB32_RX = 28; -localparam RB32_FP_GPIO = 32; - -localparam BL_ADDRESS = 0; -localparam BL_DATA = 1; +localparam SR_DACSYNC = 5; +localparam SR_LOOPBACK = 6; +localparam SR_TEST = 7; +localparam SR_SPI = 8; +localparam SR_GPIO = 16; +localparam SR_MISC_OUTS = 24; +localparam SR_READBACK = 32; +localparam SR_TX_CTRL = 64; +localparam SR_RX_CTRL = 96; +localparam SR_TIME = 128; +localparam SR_RX_DSP = 144; +localparam SR_TX_DSP = 184; +localparam SR_LEDS = 195; +localparam SR_FP_GPIO = 200; +localparam SR_RX_FRONT = 208; +localparam SR_TX_FRONT = 216; + +localparam RB32_GPIO = 0; +localparam RB32_SPI = 4; +localparam RB64_TIME_NOW = 8; +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; //wishbone settings map - relevant to host code -#define SET0_BASE 0xa000 -#define SETXB_BASE 0xb000 -#define BOOT_LDR_BASE 0xFA00 -#define I2C0_BASE 0xfe00 -#define I2C1_BASE 0xff00 +#define SET0_BASE 0xa000 +#define SETXB_BASE 0xb000 +#define BOOT_LDR_BASE 0xfa00 +#define I2C0_BASE 0xfe00 +#define I2C1_BASE 0xff00 #define SR_ADDR(base, offset) ((base) + (offset)*4) localparam ZPU_SR_LEDS = 00; @@ -70,56 +71,62 @@ localparam ZPU_SR_ETHINT0 = 40; localparam ZPU_SR_ETHINT1 = 56; //clock controls -#define ZPU_SR_CLOCK_CTRL_CLK_SRC_EXTERNAL 0x00 -#define ZPU_SR_CLOCK_CTRL_CLK_SRC_INTERNAL 0x02 -#define ZPU_SR_CLOCK_CTRL_CLK_SRC_GPSDO 0x03 -#define ZPU_SR_CLOCK_CTRL_PPS_SRC_EXTERNAL 0x00 -#define ZPU_SR_CLOCK_CTRL_PPS_SRC_INTERNAL 0x02 -#define ZPU_SR_CLOCK_CTRL_PPS_SRC_GPSDO 0x03 - -localparam ZPU_RB_SPI = 2; +#define ZPU_SR_CLOCK_CTRL_CLK_SRC_EXTERNAL 0x00 +#define ZPU_SR_CLOCK_CTRL_CLK_SRC_INTERNAL 0x02 +#define ZPU_SR_CLOCK_CTRL_CLK_SRC_GPSDO 0x03 +#define ZPU_SR_CLOCK_CTRL_PPS_SRC_EXTERNAL 0x00 +#define ZPU_SR_CLOCK_CTRL_PPS_SRC_INTERNAL 0x02 +#define ZPU_SR_CLOCK_CTRL_PPS_SRC_GPSDO 0x03 + +localparam ZPU_RB_SPI = 2; localparam ZPU_RB_CLK_STATUS = 3; localparam ZPU_RB_COMPAT_NUM = 6; localparam ZPU_RB_ETH_TYPE0 = 4; localparam ZPU_RB_ETH_TYPE1 = 5; //clock status -#define ZPU_RB_CLK_STATUS_LMK_STATUS (0x3 << 0) -#define ZPU_RB_CLK_STATUS_LMK_LOCK (0x1 << 2) -#define ZPU_RB_CLK_STATUS_LMK_HOLDOVER (0x1 << 3) -#define ZPU_RB_CLK_STATUS_PPS_DETECT (0x1 << 4) +#define ZPU_RB_CLK_STATUS_LMK_STATUS (0x3 << 0) +#define ZPU_RB_CLK_STATUS_LMK_LOCK (0x1 << 2) +#define ZPU_RB_CLK_STATUS_LMK_HOLDOVER (0x1 << 3) +#define ZPU_RB_CLK_STATUS_PPS_DETECT (0x1 << 4) //spi slaves on radio -#define DB_DAC_SEN (1 << 7) -#define DB_ADC_SEN (1 << 6) +#define DB_DAC_SEN (1 << 7) +#define DB_ADC_SEN (1 << 6) #define DB_RX_LSADC_SEN (1 << 5) #define DB_RX_LSDAC_SEN (1 << 4) #define DB_TX_LSADC_SEN (1 << 3) #define DB_TX_LSDAC_SEN (1 << 2) -#define DB_RX_SEN (1 << 1) -#define DB_TX_SEN (1 << 0) +#define DB_RX_SEN (1 << 1) +#define DB_TX_SEN (1 << 0) //------------------------------------------------------------------- // PCIe Registers //------------------------------------------------------------------- -static const uint32_t X300_PCIE_VID = 0x1093; -static const uint32_t X300_PCIE_PID = 0xC4C4; -static const uint32_t X300_USRP_PCIE_SSID = 0x7736; -static const uint32_t X310_USRP_PCIE_SSID = 0x76CA; -static const uint32_t X310_2940R_PCIE_SSID = 0x772B; -static const uint32_t X310_2942R_PCIE_SSID = 0x772C; -static const uint32_t X310_2943R_PCIE_SSID = 0x772D; -static const uint32_t X310_2944R_PCIE_SSID = 0x772E; -static const uint32_t X310_2950R_PCIE_SSID = 0x772F; -static const uint32_t X310_2952R_PCIE_SSID = 0x7730; -static const uint32_t X310_2953R_PCIE_SSID = 0x7731; -static const uint32_t X310_2954R_PCIE_SSID = 0x7732; +static const uint32_t X300_PCIE_VID = 0x1093; +static const uint32_t X300_PCIE_PID = 0xC4C4; +static const uint32_t X300_USRP_PCIE_SSID = 0x7736; +static const uint32_t X310_USRP_PCIE_SSID = 0x76CA; +static const uint32_t X310_2940R_40MHz_PCIE_SSID = 0x772B; +static const uint32_t X310_2940R_120MHz_PCIE_SSID = 0x77FB; +static const uint32_t X310_2942R_40MHz_PCIE_SSID = 0x772C; +static const uint32_t X310_2942R_120MHz_PCIE_SSID = 0x77FC; +static const uint32_t X310_2943R_40MHz_PCIE_SSID = 0x772D; +static const uint32_t X310_2943R_120MHz_PCIE_SSID = 0x77FD; +static const uint32_t X310_2944R_40MHz_PCIE_SSID = 0x772E; +static const uint32_t X310_2950R_40MHz_PCIE_SSID = 0x772F; +static const uint32_t X310_2950R_120MHz_PCIE_SSID = 0x77FE; +static const uint32_t X310_2952R_40MHz_PCIE_SSID = 0x7730; +static const uint32_t X310_2952R_120MHz_PCIE_SSID = 0x77FF; +static const uint32_t X310_2953R_40MHz_PCIE_SSID = 0x7731; +static const uint32_t X310_2953R_120MHz_PCIE_SSID = 0x7800; +static const uint32_t X310_2954R_40MHz_PCIE_SSID = 0x7732; static const uint32_t FPGA_X3xx_SIG_VALUE = 0x58333030; static const uint32_t PCIE_FPGA_ADDR_BASE = 0xC0000; -#define PCIE_FPGA_REG(X) (PCIE_FPGA_ADDR_BASE + X) +#define PCIE_FPGA_REG(X) (PCIE_FPGA_ADDR_BASE + (X)) static const uint32_t FPGA_PCIE_SIG_REG = PCIE_FPGA_REG(0x0000); static const uint32_t FPGA_CNTR_LO_REG = PCIE_FPGA_REG(0x0004); @@ -140,8 +147,8 @@ static const uint32_t DMA_FRAME_SIZE_REG = 0x4; static const uint32_t DMA_SAMPLE_COUNT_REG = 0x8; static const uint32_t DMA_PKT_COUNT_REG = 0xC; -#define PCIE_TX_DMA_REG(REG, CHAN) (PCIE_TX_DMA_REG_BASE + (CHAN*DMA_REG_GRP_SIZE) + REG) -#define PCIE_RX_DMA_REG(REG, CHAN) (PCIE_RX_DMA_REG_BASE + (CHAN*DMA_REG_GRP_SIZE) + REG) +#define PCIE_TX_DMA_REG(REG, CHAN) (PCIE_TX_DMA_REG_BASE + ((CHAN)*DMA_REG_GRP_SIZE) + (REG)) +#define PCIE_RX_DMA_REG(REG, CHAN) (PCIE_RX_DMA_REG_BASE + ((CHAN)*DMA_REG_GRP_SIZE) + (REG)) static const uint32_t DMA_CTRL_DISABLED = 0x00000000; static const uint32_t DMA_CTRL_ENABLED = 0x00000002; @@ -154,15 +161,15 @@ static const uint32_t DMA_STATUS_ERROR = 0x00000001; static const uint32_t DMA_STATUS_BUSY = 0x00000002; static const uint32_t PCIE_ROUTER_REG_BASE = PCIE_FPGA_REG(0x0500); -#define PCIE_ROUTER_REG(X) (PCIE_ROUTER_REG_BASE + X) +#define PCIE_ROUTER_REG(X) (PCIE_ROUTER_REG_BASE + (X)) static const uint32_t PCIE_ZPU_DATA_BASE = 0x30000; static const uint32_t PCIE_ZPU_READ_BASE = 0x20000; //Trig and Status share the same base static const uint32_t PCIE_ZPU_STATUS_BASE = 0x20000; -#define PCIE_ZPU_DATA_REG(X) (PCIE_FPGA_REG(PCIE_ZPU_DATA_BASE) + X) -#define PCIE_ZPU_READ_REG(X) (PCIE_FPGA_REG(PCIE_ZPU_READ_BASE) + X) -#define PCIE_ZPU_STATUS_REG(X) (PCIE_FPGA_REG(PCIE_ZPU_STATUS_BASE) + X) +#define PCIE_ZPU_DATA_REG(X) (PCIE_FPGA_REG(PCIE_ZPU_DATA_BASE) + (X)) +#define PCIE_ZPU_READ_REG(X) (PCIE_FPGA_REG(PCIE_ZPU_READ_BASE) + (X)) +#define PCIE_ZPU_STATUS_REG(X) (PCIE_FPGA_REG(PCIE_ZPU_STATUS_BASE) + (X)) static const uint32_t PCIE_ZPU_READ_START = 0x0; static const uint32_t PCIE_ZPU_READ_CLOBBER = 0x80000000; |