diff options
author | Martin Braun <martin.braun@ettus.com> | 2019-07-03 20:15:35 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-11-26 12:16:25 -0800 |
commit | c256b9df6502536c2e451e690f1ad5962c664d1a (patch) | |
tree | a83ad13e6f5978bbe14bb3ecf8294ba1e3d28db4 /host/lib/usrp/x300/x300_mb_controller.cpp | |
parent | 9a8435ed998fc5c65257f4c55768750b227ab19e (diff) | |
download | uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.tar.gz uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.tar.bz2 uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.zip |
x300/mpmd: Port all RFNoC devices to the new RFNoC framework
Co-Authored-By: Alex Williams <alex.williams@ni.com>
Co-Authored-By: Sugandha Gupta <sugandha.gupta@ettus.com>
Co-Authored-By: Brent Stapleton <brent.stapleton@ettus.com>
Co-Authored-By: Ciro Nishiguchi <ciro.nishiguchi@ni.com>
Diffstat (limited to 'host/lib/usrp/x300/x300_mb_controller.cpp')
-rw-r--r-- | host/lib/usrp/x300/x300_mb_controller.cpp | 731 |
1 files changed, 724 insertions, 7 deletions
diff --git a/host/lib/usrp/x300/x300_mb_controller.cpp b/host/lib/usrp/x300/x300_mb_controller.cpp index 9762f486d..fd70526ab 100644 --- a/host/lib/usrp/x300/x300_mb_controller.cpp +++ b/host/lib/usrp/x300/x300_mb_controller.cpp @@ -5,34 +5,751 @@ // #include "x300_mb_controller.hpp" +#include "x300_fw_common.h" +#include "x300_regs.hpp" +#include <uhd/exception.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <chrono> +#include <thread> +uhd::uart_iface::sptr x300_make_uart_iface(uhd::wb_iface::sptr iface); + +using namespace uhd; using namespace uhd::rfnoc; +using namespace uhd::usrp::x300; +using namespace std::chrono_literals; + +namespace { +constexpr uint32_t DONT_LOOK_FOR_GPSDO = 0x1234abcdul; + +constexpr uint32_t ADC_SELF_TEST_DURATION = 100; // ms + +// When these regs are fixed, there is another fixme below to actually init the +// timekeepers +constexpr uint32_t TK_NUM_TIMEKEEPERS = 12; //Read-only +constexpr uint32_t TK_REG_BASE = 100; +constexpr uint32_t TK_REG_OFFSET = 48; +constexpr uint32_t TK_REG_TICKS_NOW_LO = 0x00; // Read-only +constexpr uint32_t TK_REG_TICKS_NOW_HI = 0x04; // Read-only +constexpr uint32_t TK_REG_TICKS_EVENT_LO = 0x08; // Write-only +constexpr uint32_t TK_REG_TICKS_EVENT_HI = 0x0C; // Write-only +constexpr uint32_t TK_REG_TICKS_CTRL = 0x10; // Write-only +constexpr uint32_t TK_REG_TICKS_PPS_LO = 0x14; // Read-only +constexpr uint32_t TK_REG_TICKS_PPS_HI = 0x18; // Read-only +constexpr uint32_t TK_REG_TICKS_PERIOD_LO = 0x1C; // Read-Write +constexpr uint32_t TK_REG_TICKS_PERIOD_HI = 0x20; // Read-Write + +constexpr char LOG_ID[] = "X300::MB_CTRL"; + +} // namespace + + +/****************************************************************************** + * Structors + *****************************************************************************/ +x300_mb_controller::x300_mb_controller(const size_t hw_rev, + const std::string product_name, + uhd::i2c_iface::sptr zpu_i2c, + uhd::wb_iface::sptr zpu_ctrl, + x300_clock_ctrl::sptr clock_ctrl, + uhd::usrp::mboard_eeprom_t mb_eeprom, + x300_device_args_t args) + : _hw_rev(hw_rev) + , _product_name(product_name) + , _zpu_i2c(zpu_i2c) + , _zpu_ctrl(zpu_ctrl) + , _clock_ctrl(clock_ctrl) + , _mb_eeprom(mb_eeprom) + , _args(args) +{ + _fw_regmap = std::make_shared<fw_regmap_t>(); + _fw_regmap->initialize(*_zpu_ctrl.get(), true); + _fw_regmap->ref_freq_reg.write( + fw_regmap_t::ref_freq_reg_t::REF_FREQ, uint32_t(args.get_system_ref_rate())); + // Initialize clock source to generate a valid radio clock. This may change + // after configuration is done. + // This will configure the LMK and wait for lock + x300_mb_controller::set_clock_source(args.get_clock_source()); + x300_mb_controller::set_time_source(args.get_time_source()); + const size_t num_tks = _zpu_ctrl->peek32(SR_ADDR(SET0_BASE, TK_NUM_TIMEKEEPERS)); + for (size_t i = 0; i < num_tks; i++) { + register_timekeeper(i, std::make_shared<x300_timekeeper>(i, _zpu_ctrl, clock_ctrl->get_master_clock_rate())); + } + + init_gps(); + _radio_refs.reserve(2); +} + +x300_mb_controller::~x300_mb_controller() {} + +/****************************************************************************** + * Timekeeper APIs + *****************************************************************************/ uint64_t x300_mb_controller::x300_timekeeper::get_ticks_now() { - // tbw - return 0; + uint32_t ticks_lo = _zpu_ctrl->peek32(get_tk_addr(TK_REG_TICKS_NOW_LO)); + uint32_t ticks_hi = _zpu_ctrl->peek32(get_tk_addr(TK_REG_TICKS_NOW_HI)); + return uint64_t(ticks_lo) | (uint64_t(ticks_hi) << 32); } uint64_t x300_mb_controller::x300_timekeeper::get_ticks_last_pps() { - // tbw - return 0; + uint32_t ticks_lo = _zpu_ctrl->peek32(get_tk_addr(TK_REG_TICKS_PPS_LO)); + uint32_t ticks_hi = _zpu_ctrl->peek32(get_tk_addr(TK_REG_TICKS_PPS_HI)); + return uint64_t(ticks_lo) | (uint64_t(ticks_hi) << 32); } void x300_mb_controller::x300_timekeeper::set_ticks_now(const uint64_t ticks) { - // tbw + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_EVENT_LO), narrow_cast<uint32_t>(ticks & 0xFFFFFFFF)); + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_EVENT_HI), narrow_cast<uint32_t>(ticks >> 32)); + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_CTRL), narrow_cast<uint32_t>(0x1)); } void x300_mb_controller::x300_timekeeper::set_ticks_next_pps(const uint64_t ticks) { - // tbw + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_EVENT_LO), narrow_cast<uint32_t>(ticks & 0xFFFFFFFF)); + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_EVENT_HI), narrow_cast<uint32_t>(ticks >> 32)); + _zpu_ctrl->poke32( + get_tk_addr(TK_REG_TICKS_CTRL), narrow_cast<uint32_t>(0x2)); } void x300_mb_controller::x300_timekeeper::set_period(const uint64_t period_ns) { - // tbw + _zpu_ctrl->poke32(get_tk_addr(TK_REG_TICKS_PERIOD_LO), + narrow_cast<uint32_t>(period_ns & 0xFFFFFFFF)); + _zpu_ctrl->poke32(get_tk_addr(TK_REG_TICKS_PERIOD_HI), + narrow_cast<uint32_t>(period_ns >> 32)); +} + +uint32_t x300_mb_controller::x300_timekeeper::get_tk_addr(const uint32_t tk_addr) +{ + return SR_ADDR(SET0_BASE, TK_REG_BASE + TK_REG_OFFSET * _tk_idx + tk_addr); +} + +/****************************************************************************** + * Motherboard Control API (see mb_controller.hpp) + *****************************************************************************/ +void x300_mb_controller::init() +{ + if (_radio_refs.empty()) { + UHD_LOG_WARNING(LOG_ID, "No radio registered! Skipping ADC checks."); + return; + } + // Check ADCs + if (_args.get_ext_adc_self_test()) { + extended_adc_test(_args.get_ext_adc_self_test_duration() / _radio_refs.size()); + } else if (_args.get_self_cal_adc_delay()) { + constexpr bool apply_delay = true; + self_cal_adc_xfer_delay(apply_delay); + } else { + for (auto& radio : _radio_refs) { + radio->self_test_adc(ADC_SELF_TEST_DURATION); + } + } +} + +std::string x300_mb_controller::get_mboard_name() const +{ + return _product_name; +} + +void x300_mb_controller::set_time_source(const std::string& source) +{ + if (source == "internal") { + _fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_SELECT, + fw_regmap_t::clk_ctrl_reg_t::SRC_INTERNAL); + } else if (source == "external") { + _fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_SELECT, + fw_regmap_t::clk_ctrl_reg_t::SRC_EXTERNAL); + } else if (source == "gpsdo") { + _fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_SELECT, + fw_regmap_t::clk_ctrl_reg_t::SRC_GPSDO); + } else { + throw uhd::key_error("update_time_source: unknown source: " + source); + } + + /* TODO - Implement intelligent PPS detection + //check for valid pps + if (!is_pps_present(mb)) { + throw uhd::runtime_error((boost::format("The %d PPS was not detected. Please + check the PPS source and try again.") % source).str()); + } + */ +} + +std::string x300_mb_controller::get_time_source() const +{ + return _current_time_src; +} + +std::vector<std::string> x300_mb_controller::get_time_sources() const +{ + return {"internal", "external", "gpsdo"}; +} + +void x300_mb_controller::set_clock_source(const std::string& source) +{ + UHD_LOG_TRACE("X300::MB_CTRL", "Setting clock source to " << source); + // Optimize for the case when the current source is internal and we are trying + // to set it to internal. This is the only case where we are guaranteed that + // the clock has not gone away so we can skip setting the MUX and reseting the LMK. + const bool reconfigure_clks = (_current_refclk_src != "internal") + or (source != "internal"); + if (reconfigure_clks) { + // Update the clock MUX on the motherboard to select the requested source + if (source == "internal") { + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::CLK_SOURCE, + fw_regmap_t::clk_ctrl_reg_t::SRC_INTERNAL); + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::TCXO_EN, 1); + } else if (source == "external") { + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::CLK_SOURCE, + fw_regmap_t::clk_ctrl_reg_t::SRC_EXTERNAL); + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::TCXO_EN, 0); + } else if (source == "gpsdo") { + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::CLK_SOURCE, + fw_regmap_t::clk_ctrl_reg_t::SRC_GPSDO); + _fw_regmap->clock_ctrl_reg.set(fw_regmap_t::clk_ctrl_reg_t::TCXO_EN, 0); + } else { + throw uhd::key_error("set_clock_source: unknown source: " + source); + } + _fw_regmap->clock_ctrl_reg.flush(); + + // Reset the LMK to make sure it re-locks to the new reference + _clock_ctrl->reset_clocks(); + } + + // Wait for the LMK to lock (always, as a sanity check that the clock is useable) + //* Currently the LMK can take as long as 30 seconds to lock to a reference but we + // don't + //* want to wait that long during initialization. + // TODO: Need to verify timeout and settings to make sure lock can be achieved in + // < 1.0 seconds + double timeout = _initialization_done ? 30.0 : 1.0; + + // The programming code in x300_clock_ctrl is not compatible with revs <= 4 and may + // lead to locking issues. So, disable the ref-locked check for older (unsupported) + // boards. + if (_hw_rev > 4) { + if (not wait_for_clk_locked(fw_regmap_t::clk_status_reg_t::LMK_LOCK, timeout)) { + // failed to lock on reference + if (_initialization_done) { + throw uhd::runtime_error( + (boost::format("Reference Clock PLL failed to lock to %s source.") + % source) + .str()); + } else { + // TODO: Re-enable this warning when we figure out a reliable lock time + // UHD_LOGGER_WARNING("X300::MB_CTRL") << "Reference clock failed to lock to " + + // source + " during device initialization. " << + // "Check for the lock before operation or ignore this warning if using + // another clock source." ; + } + } + } + + if (reconfigure_clks) { + // Reset the radio clock PLL in the FPGA + _zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), ZPU_SR_SW_RST_RADIO_CLK_PLL); + _zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), 0); + + // Wait for radio clock PLL to lock + if (not wait_for_clk_locked( + fw_regmap_t::clk_status_reg_t::RADIO_CLK_LOCK, 0.01)) { + throw uhd::runtime_error( + (boost::format("Reference Clock PLL in FPGA failed to lock to %s source.") + % source) + .str()); + } + + // Reset the IDELAYCTRL used to calibrate the data interface delays + _zpu_ctrl->poke32( + SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), ZPU_SR_SW_RST_ADC_IDELAYCTRL); + _zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_SW_RST), 0); + + // Wait for the ADC IDELAYCTRL to be ready + if (not wait_for_clk_locked( + fw_regmap_t::clk_status_reg_t::IDELAYCTRL_LOCK, 0.01)) { + throw uhd::runtime_error( + (boost::format( + "ADC Calibration Clock in FPGA failed to lock to %s source.") + % source) + .str()); + } + + // Reset ADCs and DACs + reset_codecs(); + } + + // Update cache value + _current_refclk_src = source; +} + +std::string x300_mb_controller::get_clock_source() const +{ + return _current_refclk_src; +} + +std::vector<std::string> x300_mb_controller::get_clock_sources() const +{ + return {"internal", "external", "gpsdo"}; +} + +void x300_mb_controller::set_sync_source( + const std::string& clock_source, const std::string& time_source) +{ + device_addr_t sync_args; + sync_args["clock_source"] = clock_source; + sync_args["time_source"] = time_source; + set_sync_source(sync_args); +} + +void x300_mb_controller::set_sync_source(const device_addr_t& sync_source) { + if (sync_source.has_key("clock_source")) { + set_clock_source(sync_source["clock_source"]); + } + if (sync_source.has_key("time_source")) { + set_time_source(sync_source["time_source"]); + } +} + +device_addr_t x300_mb_controller::get_sync_source() const +{ + const std::string clock_source = get_clock_source(); + const std::string time_source = get_time_source(); + device_addr_t sync_source; + sync_source["clock_source"] = clock_source; + sync_source["time_source"] = time_source; + return sync_source; +} + +std::vector<device_addr_t> x300_mb_controller::get_sync_sources() +{ + const std::vector<std::pair<std::string, std::string>> clock_time_src_pairs = { + // Clock source, Time source + {"internal", "internal"}, + {"external", "internal"}, + {"external", "external"}, + {"gpsdo", "gpsdo"}, + {"gpsdo", "internal"} + }; + + // Now convert to vector of device_addr_t + std::vector<device_addr_t> sync_sources; + for (const auto& ct_pair : clock_time_src_pairs) { + device_addr_t sync_source; + sync_source["clock_source"] = ct_pair.first; + sync_source["time_source"] = ct_pair.second; + sync_sources.push_back(sync_source); + } + return sync_sources; +} + +void x300_mb_controller::set_clock_source_out(const bool enb) +{ + _clock_ctrl->set_ref_out(enb); } +void x300_mb_controller::set_time_source_out(const bool enb) +{ + _fw_regmap->clock_ctrl_reg.write( + fw_regmap_t::clk_ctrl_reg_t::PPS_OUT_EN, enb ? 1 : 0); +} + +sensor_value_t x300_mb_controller::get_sensor(const std::string& name) +{ + if (name == "ref_locked") { + return sensor_value_t("Ref", get_ref_locked(), "locked", "unlocked"); + } + // There are only GPS sensors and ref_locked, so we can take a shortcut here + // and directly ask the GPS for its sensor value: + if (_sensors.count(name)) { + return _gps->get_sensor(name); + } + throw uhd::key_error(std::string("Invalid sensor name: ") + name); +} + +std::vector<std::string> x300_mb_controller::get_sensor_names() +{ + return std::vector<std::string>(_sensors.cbegin(), _sensors.cend()); +} + +uhd::usrp::mboard_eeprom_t x300_mb_controller::get_eeprom() +{ + return _mb_eeprom; +} + +bool x300_mb_controller::synchronize(std::vector<mb_controller::sptr>& mb_controllers, + const uhd::time_spec_t& time_spec, + const bool quiet) +{ + if (!mb_controller::synchronize(mb_controllers, time_spec, quiet)) { + return false; + } + + std::vector<std::shared_ptr<x300_mb_controller>> mb_controller_copy; + mb_controller_copy.reserve(mb_controllers.size()); + for (auto mb_controller : mb_controllers) { + if (std::dynamic_pointer_cast<x300_mb_controller>(mb_controller)) { + mb_controller_copy.push_back( + std::dynamic_pointer_cast<x300_mb_controller>(mb_controller)); + } + } + // Now, mb_controller_copy contains only references of mb_controllers that + // are actually x300_mb_controllers + mb_controllers.clear(); + for (auto mb_controller : mb_controller_copy) { + mb_controllers.push_back(mb_controller); + } + + // Now we have the housekeeping out of the way, we can actually start + // synchronizing. The X300 needs to sync its DACs. First, we get a reference + // to all the radios (and thus to the DACs). + std::vector<uhd::usrp::x300::x300_radio_mbc_iface*> radios; + radios.reserve(2 * mb_controller_copy.size()); + for (auto& mbc : mb_controller_copy) { + for (auto radio_ref : mbc->_radio_refs) { + radios.push_back(radio_ref); + } + } + + UHD_LOG_TRACE(LOG_ID, "Running DAC sync on " << radios.size() << " radios."); + + // **PRECONDITION** + // This function assumes that all the VITA times for "radios" are + // synchronized to a common reference, which we did earlier. + + // Get a rough estimate of the cumulative command latency + auto t_start = std::chrono::steady_clock::now(); + for (auto radio : radios) { + radio->get_adc_rx_word(); // Discard value. We are just timing the call + } + auto t_elapsed = std::chrono::duration_cast<std::chrono::microseconds>( + std::chrono::steady_clock::now() - t_start); + // Add 100% of headroom + uncertainty to the command time + uint64_t t_sync_us = (t_elapsed.count() * 2) + 16000 /* Scheduler latency */; + + const double radio_clk_rate = _clock_ctrl->get_master_clock_rate(); + std::string err_str; + // Try to sync 3 times before giving up + constexpr size_t MAX_ATTEMPTS = 3; + for (size_t attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + try { + // Reinitialize and resync all DACs + for (auto radio : radios) { + radio->sync_dac(); + } + + // Make sure FRAMEP/N is 0 + for (auto radio : radios) { + radio->set_dac_sync(false); + } + + // Pick radios[0] as the time reference. + uhd::time_spec_t sync_time = + mb_controller_copy.front()->get_timekeeper(0)->get_time_now() + + uhd::time_spec_t(((double)t_sync_us) / 1e6); + + // Send the sync command + for (auto radio : radios) { + // Arm FRAMEP/N sync pulse by asserting a rising edge + radio->set_dac_sync(true, sync_time); + } + + // Reset FRAMEP/N to 0 after 2 clock cycles, and reset command time + for (auto radio : radios) { + radio->set_dac_sync(false, sync_time + (2.0 / radio_clk_rate)); + } + + // Wait and check status + std::this_thread::sleep_for(std::chrono::microseconds(t_sync_us)); + for (auto radio : radios) { + radio->dac_verify_sync(); + } + + UHD_LOG_TRACE(LOG_ID, "DAC sync passed on attempt " << attempt); + return true; + } catch (const uhd::runtime_error& e) { + err_str = e.what(); + RFNOC_LOG_DEBUG("Retrying DAC synchronization: " << err_str); + } + } + throw uhd::runtime_error(err_str); +} + +/****************************************************************************** + * Private Methods + *****************************************************************************/ +std::string x300_mb_controller::get_unique_id() +{ + return std::string("X300::MB_CTRL") + ""; // FIXME +} + +void x300_mb_controller::init_gps() +{ + // otherwise if not disabled, look for the internal GPSDO + if (_zpu_ctrl->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_GPSDO_STATUS)) + != DONT_LOOK_FOR_GPSDO) { + UHD_LOG_TRACE("X300::MB_CTRL", "Detecting internal GPSDO...."); + try { + // gps_ctrl will print its own log statements if a GPSDO was found + _gps = gps_ctrl::make(x300_make_uart_iface(_zpu_ctrl)); + } catch (std::exception& e) { + UHD_LOGGER_WARNING("X300::MB_CTRL") + << "An error occurred making GPSDO control: " << e.what() + << " Continuing without GPS."; + } + if (_gps and _gps->gps_detected()) { + auto sensors = _gps->get_sensors(); + _sensors.insert(sensors.cbegin(), sensors.cend()); + } else { + UHD_LOG_TRACE("X300::MB_CTRL", + "No GPS found, setting register to save time on next run."); + _zpu_ctrl->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_GPSDO_STATUS), + DONT_LOOK_FOR_GPSDO); + } + } else { + UHD_LOG_TRACE("X300::MB_CTRL", + "Not detecting internal GPSDO, previous run already failed to find it."); + } +} + +void x300_mb_controller::reset_codecs() +{ + for (auto& callback : _reset_cbs) { + UHD_LOG_TRACE("X300::MB_CTRL", "Calling DAC/ADC reset callback"); + callback(); + } +} + +bool x300_mb_controller::wait_for_clk_locked(uint32_t which, double timeout) +{ + const auto timeout_time = std::chrono::steady_clock::now() + + std::chrono::milliseconds(int64_t(timeout * 1000)); + do { + if (_fw_regmap->clock_status_reg.read(which) == 1) { + return true; + } + std::this_thread::sleep_for(5ms); + } while (std::chrono::steady_clock::now() < timeout_time); + + // Check one last time + return (_fw_regmap->clock_status_reg.read(which) == 1); +} + +bool x300_mb_controller::is_pps_present() +{ + // The ZPU_RB_CLK_STATUS_PPS_DETECT bit toggles with each rising edge of the PPS. + // We monitor it for up to 1.5 seconds looking for it to toggle. + uint32_t pps_detect = + _fw_regmap->clock_status_reg.read(fw_regmap_t::clk_status_reg_t::PPS_DETECT); + const auto timeout_time = std::chrono::steady_clock::now() + 1500ms; + while (std::chrono::steady_clock::now() < timeout_time) { + std::this_thread::sleep_for(100ms); + if (pps_detect + != _fw_regmap->clock_status_reg.read( + fw_regmap_t::clk_status_reg_t::PPS_DETECT)) + return true; + } + return false; +} + +bool x300_mb_controller::get_ref_locked() +{ + _fw_regmap->clock_status_reg.refresh(); + return (_fw_regmap->clock_status_reg.get(fw_regmap_t::clk_status_reg_t::LMK_LOCK) + == 1) + && (_fw_regmap->clock_status_reg.get( + fw_regmap_t::clk_status_reg_t::RADIO_CLK_LOCK) + == 1) + && (_fw_regmap->clock_status_reg.get( + fw_regmap_t::clk_status_reg_t::IDELAYCTRL_LOCK) + == 1); +} + +void x300_mb_controller::self_cal_adc_xfer_delay(bool apply_delay) +{ + UHD_LOG_INFO("X300", "Running ADC transfer delay self-cal: "); + + // Effective resolution of the self-cal. + constexpr size_t NUM_DELAY_STEPS = 100; + + double master_clk_period = (1.0e9 / _clock_ctrl->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; + + double cached_clk_delay = _clock_ctrl->get_clock_delay(X300_CLOCK_WHICH_ADC0); + double fpga_clk_delay = _clock_ctrl->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 = _clock_ctrl->set_clock_delay( + X300_CLOCK_WHICH_ADC0, delay_incr * i + delay_start); + wait_for_clk_locked(fw_regmap_t::clk_status_reg_t::LMK_LOCK, 0.1); + + uint32_t err_code = 0; + for (auto& radio : _radio_refs) { + // 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. + radio->set_adc_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 + radio->set_adc_checker_enabled(false); + radio->set_adc_checker_enabled(true); + // 50ms @ 200MHz = 10 million samples + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + if (radio->get_adc_checker_locked(true /* I */)) { + err_code += radio->get_adc_checker_error_code(true /* I */); + } 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. + radio->set_adc_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 + radio->set_adc_checker_enabled(false); + radio->set_adc_checker_enabled(true); + // 50ms @ 200MHz = 10 million samples + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + if (radio->get_adc_checker_locked(false /* Q */)) { + err_code += radio->get_adc_checker_error_code(false /* Q */); + } else { + err_code += 100; // Increment error code by 100 to indicate no lock + } + } + UHD_LOG_TRACE( + LOG_ID, boost::format("XferDelay=%fns, Error=%d") % delay % err_code); + results.push_back(std::pair<double, bool>(delay, err_code == 0)); + } + + // Calculate the valid window + // When done win_start_idx will have the first delay value index that caused + // no errors, and win_stop_idx will have the last valid delay value index + 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; + const 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) { + // Apply delay + win_center = _clock_ctrl->set_clock_delay( + X300_CLOCK_WHICH_ADC0, win_center); // Sets ADC0 and ADC1 + wait_for_clk_locked(fw_regmap_t::clk_status_reg_t::LMK_LOCK, 0.1); + // Validate + for (auto radio_ref : _radio_refs) { + radio_ref->self_test_adc(2000); + } + } else { + // Restore delay + _clock_ctrl->set_clock_delay( + X300_CLOCK_WHICH_ADC0, cached_clk_delay); // Sets ADC0 and ADC1 + } + + // Teardown + for (auto& radio : _radio_refs) { + radio->set_adc_test_word("normal", "normal"); + radio->set_adc_checker_enabled(false); + } + UHD_LOGGER_INFO(LOG_ID) + << (boost::format("ADC transfer delay self-cal done (FPGA->ADC=%.3fns%s, " + "Window=%.3fns)") + % (win_center - fpga_clk_delay) % (cycle_slip ? " +cyc" : "") + % win_length); +} + +void x300_mb_controller::extended_adc_test(double duration_s) +{ + static const size_t SECS_PER_ITER = 5; + RFNOC_LOG_INFO( + boost::format("Running Extended ADC Self-Test (Duration=%.0fs, %ds/iteration)...") + % 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++) { + // Run self-test + RFNOC_LOG_INFO( + boost::format("Extended ADC Self-Test Iteration %06d... ") % (iter + 1)); + try { + for (auto& radio : _radio_refs) { + radio->self_test_adc(SECS_PER_ITER * 1000); + } + RFNOC_LOG_INFO(boost::format("Extended ADC Self-Test Iteration %06d passed ") + % (iter + 1)); + } catch (std::exception& e) { + num_failures++; + RFNOC_LOG_ERROR(e.what()); + } + } + if (num_failures == 0) { + RFNOC_LOG_INFO("Extended ADC Self-Test PASSED"); + } else { + const std::string err_msg = + (boost::format("Extended ADC Self-Test FAILED!!! (%d/%d failures)") + % num_failures % num_iters) + .str(); + RFNOC_LOG_ERROR(err_msg); + throw uhd::runtime_error(err_msg); + } +} |