aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/x300/x300_mb_controller.cpp
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2019-07-03 20:15:35 -0700
committerMartin Braun <martin.braun@ettus.com>2019-11-26 12:16:25 -0800
commitc256b9df6502536c2e451e690f1ad5962c664d1a (patch)
treea83ad13e6f5978bbe14bb3ecf8294ba1e3d28db4 /host/lib/usrp/x300/x300_mb_controller.cpp
parent9a8435ed998fc5c65257f4c55768750b227ab19e (diff)
downloaduhd-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.cpp731
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);
+ }
+}