// // Copyright 2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // #include "x300_mb_controller.hpp" #include "x300_fw_common.h" #include "x300_regs.hpp" #include #include #include #include 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->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(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() { 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() { 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) { _zpu_ctrl->poke32( get_tk_addr(TK_REG_TICKS_EVENT_LO), narrow_cast(ticks & 0xFFFFFFFF)); _zpu_ctrl->poke32( get_tk_addr(TK_REG_TICKS_EVENT_HI), narrow_cast(ticks >> 32)); _zpu_ctrl->poke32( get_tk_addr(TK_REG_TICKS_CTRL), narrow_cast(0x1)); } void x300_mb_controller::x300_timekeeper::set_ticks_next_pps(const uint64_t ticks) { _zpu_ctrl->poke32( get_tk_addr(TK_REG_TICKS_EVENT_LO), narrow_cast(ticks & 0xFFFFFFFF)); _zpu_ctrl->poke32( get_tk_addr(TK_REG_TICKS_EVENT_HI), narrow_cast(ticks >> 32)); _zpu_ctrl->poke32( get_tk_addr(TK_REG_TICKS_CTRL), narrow_cast(0x2)); } void x300_mb_controller::x300_timekeeper::set_period(const uint64_t period_ns) { _zpu_ctrl->poke32(get_tk_addr(TK_REG_TICKS_PERIOD_LO), narrow_cast(period_ns & 0xFFFFFFFF)); _zpu_ctrl->poke32(get_tk_addr(TK_REG_TICKS_PERIOD_HI), narrow_cast(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 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 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 x300_mb_controller::get_sync_sources() { const std::vector> 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 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 x300_mb_controller::get_sensor_names() { return std::vector(_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_controllers, const uhd::time_spec_t& time_spec, const bool quiet) { if (!mb_controller::synchronize(mb_controllers, time_spec, quiet)) { return false; } std::vector> mb_controller_copy; mb_controller_copy.reserve(mb_controllers.size()); for (auto mb_controller : mb_controllers) { if (std::dynamic_pointer_cast(mb_controller)) { mb_controller_copy.push_back( std::dynamic_pointer_cast(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 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::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> 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(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& 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(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); } }