diff options
Diffstat (limited to 'host/lib/usrp/x300/x300_impl.cpp')
-rw-r--r-- | host/lib/usrp/x300/x300_impl.cpp | 631 |
1 files changed, 532 insertions, 99 deletions
diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp index aff150acb..c742d34df 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> @@ -38,10 +37,11 @@ #include <uhd/transport/nirio_zero_copy.hpp> #include <uhd/transport/nirio/niusrprio_session.h> #include <uhd/utils/platform.hpp> +#include <boost/date_time/posix_time/posix_time_io.hpp> #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; @@ -236,7 +236,7 @@ static device_addrs_t x300_find_pcie(const device_addr_t &hint, bool explicit_qu return addrs; } -static device_addrs_t x300_find(const device_addr_t &hint_) +device_addrs_t x300_find(const device_addr_t &hint_) { //handle the multi-device discovery device_addrs_t hints = separate_device_addr(hint_); @@ -400,7 +400,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) default: nirio_status_to_exception(status, "Motherboard detection error. Please ensure that you \ have a valid USRP X3x0, NI USRP-294xR or NI USRP-295xR device and that all the device \ - driver have been loaded."); + drivers have loaded successfully."); } //Load the lvbitx onto the device UHD_MSG(status) << boost::format("Using LVBITX bitfile %s...\n") % lvbitx->get_bitfile_path(); @@ -508,9 +508,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 + //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); this->check_fw_compat(mb_path, mb.zpu_ctrl); - this->check_fpga_compat(mb_path, mb.zpu_ctrl); //store which FPGA image is loaded mb.loaded_fpga_image = get_fpga_option(mb.zpu_ctrl); @@ -558,6 +559,13 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) .set(mb_eeprom) .subscribe(boost::bind(&x300_impl::set_mb_eeprom, this, mb.zpu_i2c, _1)); + bool recover_mb_eeprom = dev_addr.has_key("recover_mb_eeprom"); + if (recover_mb_eeprom) { + UHD_MSG(warning) << "UHD is operating in EEPROM Recovery Mode which disables hardware version " + "checks.\nOperating in this mode may cause hardware damage and unstable " + "radio performance!"<< std::endl; + } + //////////////////////////////////////////////////////////////////// // parse the product number //////////////////////////////////////////////////////////////////// @@ -570,7 +578,10 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) product_name = "X310"; break; default: - break; + if (not recover_mb_eeprom) + throw uhd::runtime_error("Unrecognized product type.\n" + "Either the software does not support this device in which case please update your driver software to the latest version and retry OR\n" + "The product code in the EEPROM is corrupt and may require reprogramming."); } _tree->create<std::string>(mb_path / "name").set(product_name); _tree->create<std::string>(mb_path / "codename").set("Yetti"); @@ -602,36 +613,57 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) } //////////////////////////////////////////////////////////////////// - // create clock control objects + // read hardware revision and compatibility number //////////////////////////////////////////////////////////////////// - UHD_MSG(status) << "Setup RF frontend clocking..." << std::endl; - mb.hw_rev = 0; if(mb_eeprom.has_key("revision") and not mb_eeprom["revision"].empty()) { try { mb.hw_rev = boost::lexical_cast<size_t>(mb_eeprom["revision"]); } catch(...) { - UHD_MSG(warning) << "Revision in EEPROM is invalid! Please reprogram your EEPROM." << std::endl; + if (not recover_mb_eeprom) + throw uhd::runtime_error("Revision in EEPROM is invalid! Please reprogram your EEPROM."); } } else { - UHD_MSG(warning) << "No revision detected MB EEPROM must be reprogrammed!" << std::endl; + if (not recover_mb_eeprom) + throw uhd::runtime_error("No revision detected. MB EEPROM must be reprogrammed!"); } - if(mb.hw_rev == 0) { - UHD_MSG(warning) << "Defaulting to X300 RevD Clock Settings. This will result in non-optimal lock times." << std::endl; - mb.hw_rev = X300_REV("D"); + size_t hw_rev_compat = 0; + if (mb.hw_rev >= 7) { //Revision compat was added with revision 7 + if (mb_eeprom.has_key("revision_compat") and not mb_eeprom["revision_compat"].empty()) { + try { + hw_rev_compat = boost::lexical_cast<size_t>(mb_eeprom["revision_compat"]); + } catch(...) { + if (not recover_mb_eeprom) + throw uhd::runtime_error("Revision compat in EEPROM is invalid! Please reprogram your EEPROM."); + } + } else { + if (not recover_mb_eeprom) + throw uhd::runtime_error("No revision compat detected. MB EEPROM must be reprogrammed!"); + } + } else { + //For older HW just assume that revision_compat = revision + hw_rev_compat = mb.hw_rev; } - if (mb.hw_rev > X300_MAX_HW_REV) { - throw uhd::runtime_error(str( - boost::format("Unsupported board revision number: %d.\n" - "The maximum board revision number supported in this version is %d.\n" - "Please update your UHD version.") - % mb.hw_rev % X300_MAX_HW_REV - )); + if (hw_rev_compat > X300_REVISION_COMPAT) { + if (not recover_mb_eeprom) + throw uhd::runtime_error(str(boost::format( + "Hardware is too new for this software. Please upgrade to a driver that supports hardware revision %d.") + % mb.hw_rev)); + } else if (mb.hw_rev < X300_REVISION_MIN) { //Compare min against the revision (and not compat) to give us more leeway for partial support for a compat + if (not recover_mb_eeprom) + throw uhd::runtime_error(str(boost::format( + "Software is too new for this hardware. Please downgrade to a driver that supports hardware revision %d.") + % mb.hw_rev)); } - //Create clock control. NOTE: This does not configure the LMK yet. + //////////////////////////////////////////////////////////////////// + // create clock control objects + //////////////////////////////////////////////////////////////////// + UHD_MSG(status) << "Setup RF frontend clocking..." << std::endl; + + //Initialize clock control registers. NOTE: This does not configure the LMK yet. initialize_clock_control(mb); mb.clock = x300_clock_ctrl::make(mb.zpu_spi, 1 /*slaveno*/, @@ -696,23 +728,33 @@ 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 */); + } + if (dev_addr.has_key("ext_adc_self_test")) { + extended_adc_test(mb, dev_addr.cast<double>("ext_adc_self_test", 30)); + } else { + 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 +871,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 +899,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 +907,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 +916,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 +938,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 +1482,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 +1584,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(); + } } /*********************************************************************** @@ -1688,25 +1732,33 @@ void x300_impl::check_fw_compat(const fs_path &mb_path, wb_iface::sptr iface) % compat_major % compat_minor)); } -void x300_impl::check_fpga_compat(const fs_path &mb_path, wb_iface::sptr iface) +void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t &members) { - boost::uint32_t compat_num = iface->peek32(SR_ADDR(SET0_BASE, ZPU_RB_COMPAT_NUM)); + boost::uint32_t compat_num = members.zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_COMPAT_NUM)); boost::uint32_t compat_major = (compat_num >> 16); boost::uint32_t compat_minor = (compat_num & 0xffff); if (compat_major != X300_FPGA_COMPAT_MAJOR) { + std::string image_loader_path = (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string(); + std::string image_loader_cmd = str(boost::format("\"%s\" --args=\"type=x300,%s=%s\"") + % image_loader_path + % (members.xport_path == "eth" ? "addr" + : "resource") + % members.addr); + throw uhd::runtime_error(str(boost::format( "Expected FPGA compatibility number %d, but got %d:\n" "The FPGA image on your device is not compatible with this host code build.\n" "Download the appropriate FPGA images for this version of UHD.\n" "%s\n\n" "Then burn a new image to the on-board flash storage of your\n" - "USRP X3xx device using the burner utility. %s\n\n" + "USRP X3xx device using the image loader utility. Use this command:\n\n%s\n\n" "For more information, refer to the UHD manual:\n\n" " http://files.ettus.com/manual/page_usrp_x3x0.html#x3x0_flash" ) % int(X300_FPGA_COMPAT_MAJOR) % compat_major - % print_utility_error("uhd_images_downloader.py") % print_utility_error("usrp_x3xx_fpga_burner"))); + % print_utility_error("uhd_images_downloader.py") + % image_loader_cmd)); } _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % compat_major % compat_minor)); @@ -1727,17 +1779,39 @@ x300_impl::x300_mboard_t x300_impl::get_mb_type_from_pcie(const std::string& res if (nirio_status_not_fatal(status)) { //The PCIe ID -> MB mapping may be different from the EEPROM -> MB mapping switch (pid) { - case X300_USRP_PCIE_SSID: + case X300_USRP_PCIE_SSID_ADC_33: + case X300_USRP_PCIE_SSID_ADC_18: 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_USRP_PCIE_SSID_ADC_33: + case X310_2940R_40MHz_PCIE_SSID_ADC_33: + case X310_2940R_120MHz_PCIE_SSID_ADC_33: + case X310_2942R_40MHz_PCIE_SSID_ADC_33: + case X310_2942R_120MHz_PCIE_SSID_ADC_33: + case X310_2943R_40MHz_PCIE_SSID_ADC_33: + case X310_2943R_120MHz_PCIE_SSID_ADC_33: + case X310_2944R_40MHz_PCIE_SSID_ADC_33: + case X310_2950R_40MHz_PCIE_SSID_ADC_33: + case X310_2950R_120MHz_PCIE_SSID_ADC_33: + case X310_2952R_40MHz_PCIE_SSID_ADC_33: + case X310_2952R_120MHz_PCIE_SSID_ADC_33: + case X310_2953R_40MHz_PCIE_SSID_ADC_33: + case X310_2953R_120MHz_PCIE_SSID_ADC_33: + case X310_2954R_40MHz_PCIE_SSID_ADC_33: + case X310_USRP_PCIE_SSID_ADC_18: + case X310_2940R_40MHz_PCIE_SSID_ADC_18: + case X310_2940R_120MHz_PCIE_SSID_ADC_18: + case X310_2942R_40MHz_PCIE_SSID_ADC_18: + case X310_2942R_120MHz_PCIE_SSID_ADC_18: + case X310_2943R_40MHz_PCIE_SSID_ADC_18: + case X310_2943R_120MHz_PCIE_SSID_ADC_18: + case X310_2944R_40MHz_PCIE_SSID_ADC_18: + case X310_2950R_40MHz_PCIE_SSID_ADC_18: + case X310_2950R_120MHz_PCIE_SSID_ADC_18: + case X310_2952R_40MHz_PCIE_SSID_ADC_18: + case X310_2952R_120MHz_PCIE_SSID_ADC_18: + case X310_2953R_40MHz_PCIE_SSID_ADC_18: + case X310_2953R_120MHz_PCIE_SSID_ADC_18: + case X310_2954R_40MHz_PCIE_SSID_ADC_18: mb_type = USRP_X310_MB; break; default: mb_type = UNKNOWN; break; @@ -1762,17 +1836,39 @@ x300_impl::x300_mboard_t x300_impl::get_mb_type_from_eeprom(const uhd::usrp::mbo switch (product_num) { //The PCIe ID -> MB mapping may be different from the EEPROM -> MB mapping - case X300_USRP_PCIE_SSID: + case X300_USRP_PCIE_SSID_ADC_33: + case X300_USRP_PCIE_SSID_ADC_18: 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_USRP_PCIE_SSID_ADC_33: + case X310_2940R_40MHz_PCIE_SSID_ADC_33: + case X310_2940R_120MHz_PCIE_SSID_ADC_33: + case X310_2942R_40MHz_PCIE_SSID_ADC_33: + case X310_2942R_120MHz_PCIE_SSID_ADC_33: + case X310_2943R_40MHz_PCIE_SSID_ADC_33: + case X310_2943R_120MHz_PCIE_SSID_ADC_33: + case X310_2944R_40MHz_PCIE_SSID_ADC_33: + case X310_2950R_40MHz_PCIE_SSID_ADC_33: + case X310_2950R_120MHz_PCIE_SSID_ADC_33: + case X310_2952R_40MHz_PCIE_SSID_ADC_33: + case X310_2952R_120MHz_PCIE_SSID_ADC_33: + case X310_2953R_40MHz_PCIE_SSID_ADC_33: + case X310_2953R_120MHz_PCIE_SSID_ADC_33: + case X310_2954R_40MHz_PCIE_SSID_ADC_33: + case X310_USRP_PCIE_SSID_ADC_18: + case X310_2940R_40MHz_PCIE_SSID_ADC_18: + case X310_2940R_120MHz_PCIE_SSID_ADC_18: + case X310_2942R_40MHz_PCIE_SSID_ADC_18: + case X310_2942R_120MHz_PCIE_SSID_ADC_18: + case X310_2943R_40MHz_PCIE_SSID_ADC_18: + case X310_2943R_120MHz_PCIE_SSID_ADC_18: + case X310_2944R_40MHz_PCIE_SSID_ADC_18: + case X310_2950R_40MHz_PCIE_SSID_ADC_18: + case X310_2950R_120MHz_PCIE_SSID_ADC_18: + case X310_2952R_40MHz_PCIE_SSID_ADC_18: + case X310_2952R_120MHz_PCIE_SSID_ADC_18: + case X310_2953R_40MHz_PCIE_SSID_ADC_18: + case X310_2953R_120MHz_PCIE_SSID_ADC_18: + case X310_2954R_40MHz_PCIE_SSID_ADC_18: mb_type = USRP_X310_MB; break; default: UHD_MSG(warning) << "X300 unknown product code in EEPROM: " << product_num << std::endl; @@ -1782,3 +1878,340 @@ 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)); + + bool passed = true; + std::string status_str; + for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) { + radio_perifs_t &perif = mb.radio_perifs[r]; + perif.misc_ins->refresh(); + + std::string i_status, q_status; + if (perif.misc_ins->get(radio_misc_ins_reg::ADC_CHECKER1_I_LOCKED)) + if (perif.misc_ins->get(radio_misc_ins_reg::ADC_CHECKER1_I_ERROR)) + i_status = "Bit Errors!"; + else + i_status = "Good"; + else + i_status = "Not Locked!"; + + if (perif.misc_ins->get(radio_misc_ins_reg::ADC_CHECKER1_Q_LOCKED)) + if (perif.misc_ins->get(radio_misc_ins_reg::ADC_CHECKER1_Q_ERROR)) + q_status = "Bit Errors!"; + else + q_status = "Good"; + else + q_status = "Not Locked!"; + + passed = passed && (i_status == "Good") && (q_status == "Good"); + status_str += (boost::format(", ADC%d_I=%s, ADC%d_Q=%s")%r%i_status%r%q_status).str(); + + //Return to normal mode + perif.adc->set_test_word("normal", "normal"); + } + + if (not passed) { + throw uhd::runtime_error( + (boost::format("ADC self-test failed! Ramp checker status: {%s}")%status_str.substr(2)).str()); + } +} + +void x300_impl::extended_adc_test(mboard_members_t& mb, double duration_s) +{ + static const size_t SECS_PER_ITER = 5; + UHD_MSG(status) << boost::format("Running Extended ADC Self-Test (Duration=%.0fs, %ds/iteration)...\n") + % duration_s % SECS_PER_ITER; + + size_t num_iters = static_cast<size_t>(ceil(duration_s/SECS_PER_ITER)); + size_t num_failures = 0; + for (size_t iter = 0; iter < num_iters; iter++) { + //Print date and time + boost::posix_time::time_facet *facet = new boost::posix_time::time_facet("%d-%b-%Y %H:%M:%S"); + std::ostringstream time_strm; + time_strm.imbue(std::locale(std::locale::classic(), facet)); + time_strm << boost::posix_time::second_clock::local_time(); + //Run self-test + UHD_MSG(status) << boost::format("-- [%s] Iteration %06d... ") % time_strm.str() % (iter+1); + try { + self_test_adcs(mb, SECS_PER_ITER*1000); + UHD_MSG(status) << "passed" << std::endl; + } catch(std::exception &e) { + num_failures++; + UHD_MSG(status) << e.what() << std::endl; + } + + } + if (num_failures == 0) { + UHD_MSG(status) << "Extended ADC Self-Test PASSED\n"; + } else { + throw uhd::runtime_error( + (boost::format("Extended ADC Self-Test FAILED!!! (%d/%d failures)\n") % num_failures % num_iters).str()); + } +} |