aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAshish Chaudhari <ashish@ettus.com>2015-07-23 12:11:13 -0700
committerAshish Chaudhari <ashish@ettus.com>2015-07-23 12:11:13 -0700
commite0919bab09f9ecaabb760da6a64bfce87951e34b (patch)
treedbbc5d6d758ed4c4fee17836b5946f68b1e8a00d
parent19a53da854c87d3ddfac92ba799e50302e77d0be (diff)
downloaduhd-e0919bab09f9ecaabb760da6a64bfce87951e34b.tar.gz
uhd-e0919bab09f9ecaabb760da6a64bfce87951e34b.tar.bz2
uhd-e0919bab09f9ecaabb760da6a64bfce87951e34b.zip
x300: Moved system-level ADC and DAC operations
- The following function implementations were moved from x300_impl.cpp to x300_adc_dac_utils.cpp - synchronize_dacs - self_test_adcs - extended_adc_test - self_cal_adc_capture_delay - self_cal_adc_xfer_delay - This reduces the size of the x300_impl object file
-rw-r--r--host/lib/usrp/x300/CMakeLists.txt1
-rw-r--r--host/lib/usrp/x300/x300_adc_dac_utils.cpp412
-rw-r--r--host/lib/usrp/x300/x300_impl.cpp386
3 files changed, 413 insertions, 386 deletions
diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt
index 15af44721..9a8601452 100644
--- a/host/lib/usrp/x300/CMakeLists.txt
+++ b/host/lib/usrp/x300/CMakeLists.txt
@@ -35,6 +35,7 @@ IF(ENABLE_X300)
${CMAKE_CURRENT_SOURCE_DIR}/x300_dboard_iface.cpp
${CMAKE_CURRENT_SOURCE_DIR}/x300_clock_ctrl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/x300_image_loader.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/x300_adc_dac_utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/cdecode.c
)
ENDIF(ENABLE_X300)
diff --git a/host/lib/usrp/x300/x300_adc_dac_utils.cpp b/host/lib/usrp/x300/x300_adc_dac_utils.cpp
new file mode 100644
index 000000000..2dadea26e
--- /dev/null
+++ b/host/lib/usrp/x300/x300_adc_dac_utils.cpp
@@ -0,0 +1,412 @@
+//
+// Copyright 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
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "x300_impl.hpp"
+#include <boost/date_time/posix_time/posix_time_io.hpp>
+
+/***********************************************************************
+ * DAC: Reset and synchronization operations
+ **********************************************************************/
+
+void x300_impl::synchronize_dacs(const std::vector<radio_perifs_t*>& radios)
+{
+ if (radios.size() < 2) return; //Nothing to synchronize
+
+ //**PRECONDITION**
+ //This function assumes that all the VITA times in "radios" are synchronized
+ //to a common reference. Currently, this function is called in get_tx_stream
+ //which also has the same precondition.
+
+ //Reinitialize and resync all DACs
+ for (size_t i = 0; i < radios.size(); i++) {
+ radios[i]->dac->reset_and_resync();
+ }
+
+ //Get a rough estimate of the cumulative command latency
+ boost::posix_time::ptime t_start = boost::posix_time::microsec_clock::local_time();
+ for (size_t i = 0; i < radios.size(); i++) {
+ radios[i]->ctrl->peek64(RB64_TIME_NOW); //Discard value. We are just timing the call
+ }
+ boost::posix_time::time_duration t_elapsed =
+ boost::posix_time::microsec_clock::local_time() - t_start;
+
+ //Add 100% of headroom + uncertaintly to the command time
+ boost::uint64_t t_sync_us = (t_elapsed.total_microseconds() * 2) + 13000 /*Scheduler latency*/;
+
+ //Pick radios[0] as the time reference.
+ uhd::time_spec_t sync_time =
+ radios[0]->time64->get_time_now() + uhd::time_spec_t(((double)t_sync_us)/1e6);
+
+ //Send the sync command
+ for (size_t i = 0; i < radios.size(); i++) {
+ radios[i]->ctrl->set_time(sync_time);
+ radios[i]->ctrl->poke32(TOREG(SR_DACSYNC), 0x1); //Arm FRAMEP/N sync pulse
+ radios[i]->ctrl->set_time(uhd::time_spec_t(0.0)); //Clear command time
+ }
+
+ //Wait and check status
+ boost::this_thread::sleep(boost::posix_time::microseconds(t_sync_us));
+ for (size_t i = 0; i < radios.size(); i++) {
+ radios[i]->dac->verify_sync();
+ }
+}
+
+/***********************************************************************
+ * ADC: Self-test operations
+ **********************************************************************/
+
+static void check_adc(uhd::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());
+ }
+}
+
+/***********************************************************************
+ * ADC: Self-calibration operations
+ **********************************************************************/
+
+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_clk_locked(mb.zpu_ctrl, ZPU_RB_CLK_STATUS_LMK_LOCK, 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_clk_locked(mb.zpu_ctrl, ZPU_RB_CLK_STATUS_LMK_LOCK, 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;
+}
diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp
index ef6e41a83..82ed5bfe2 100644
--- a/host/lib/usrp/x300/x300_impl.cpp
+++ b/host/lib/usrp/x300/x300_impl.cpp
@@ -37,7 +37,6 @@
#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"
@@ -1549,53 +1548,6 @@ bool x300_impl::is_pps_present(wb_iface::sptr ctrl)
}
/***********************************************************************
- * reset and synchronization logic
- **********************************************************************/
-
-void x300_impl::synchronize_dacs(const std::vector<radio_perifs_t*>& radios)
-{
- if (radios.size() < 2) return; //Nothing to synchronize
-
- //**PRECONDITION**
- //This function assumes that all the VITA times in "radios" are synchronized
- //to a common reference. Currently, this function is called in get_tx_stream
- //which also has the same precondition.
-
- //Reinitialize and resync all DACs
- for (size_t i = 0; i < radios.size(); i++) {
- radios[i]->dac->reset_and_resync();
- }
-
- //Get a rough estimate of the cumulative command latency
- boost::posix_time::ptime t_start = boost::posix_time::microsec_clock::local_time();
- for (size_t i = 0; i < radios.size(); i++) {
- radios[i]->ctrl->peek64(RB64_TIME_NOW); //Discard value. We are just timing the call
- }
- boost::posix_time::time_duration t_elapsed =
- boost::posix_time::microsec_clock::local_time() - t_start;
-
- //Add 100% of headroom + uncertaintly to the command time
- boost::uint64_t t_sync_us = (t_elapsed.total_microseconds() * 2) + 13000 /*Scheduler latency*/;
-
- //Pick radios[0] as the time reference.
- uhd::time_spec_t sync_time =
- radios[0]->time64->get_time_now() + uhd::time_spec_t(((double)t_sync_us)/1e6);
-
- //Send the sync command
- for (size_t i = 0; i < radios.size(); i++) {
- radios[i]->ctrl->set_time(sync_time);
- radios[i]->ctrl->poke32(TOREG(SR_DACSYNC), 0x1); //Arm FRAMEP/N sync pulse
- radios[i]->ctrl->set_time(uhd::time_spec_t(0.0)); //Clear command time
- }
-
- //Wait and check status
- boost::this_thread::sleep(boost::posix_time::microseconds(t_sync_us));
- for (size_t i = 0; i < radios.size(); i++) {
- radios[i]->dac->verify_sync();
- }
-}
-
-/***********************************************************************
* eeprom
**********************************************************************/
@@ -1907,341 +1859,3 @@ 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_clk_locked(mb.zpu_ctrl, ZPU_RB_CLK_STATUS_LMK_LOCK, 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_clk_locked(mb.zpu_ctrl, ZPU_RB_CLK_STATUS_LMK_LOCK, 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());
- }
-}