aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAshish Chaudhari <ashish@ettus.com>2015-07-07 14:57:25 -0700
committerAshish Chaudhari <ashish@ettus.com>2015-07-07 15:15:17 -0700
commitcf3566e750000fcc125a4d192c975d0100655483 (patch)
treec07ef8e4bbfd546b0bbad2ada9a21c55814f2ed8
parente840a3ca30ae136a3c0886b005645a03656ec64c (diff)
downloaduhd-cf3566e750000fcc125a4d192c975d0100655483.tar.gz
uhd-cf3566e750000fcc125a4d192c975d0100655483.tar.bz2
uhd-cf3566e750000fcc125a4d192c975d0100655483.zip
x300: Added self-cal to tune ADC source-sync data delays
- Self-calibration routine steps through various values of IDELAY taps on the SS data bits to detect metastability in the capture interface and computes an ideal delay tap value - Self calibration is triggered at device creation
-rw-r--r--host/lib/usrp/x300/x300_adc_ctrl.cpp4
-rw-r--r--host/lib/usrp/x300/x300_impl.cpp181
-rw-r--r--host/lib/usrp/x300/x300_impl.hpp25
3 files changed, 160 insertions, 50 deletions
diff --git a/host/lib/usrp/x300/x300_adc_ctrl.cpp b/host/lib/usrp/x300/x300_adc_ctrl.cpp
index b0e4e4b95..edb4ce885 100644
--- a/host/lib/usrp/x300/x300_adc_ctrl.cpp
+++ b/host/lib/usrp/x300/x300_adc_ctrl.cpp
@@ -55,8 +55,8 @@ public:
_ads62p48_regs.lvds_cmos = ads62p48_regs_t::LVDS_CMOS_DDR_LVDS;
_ads62p48_regs.channel_control = ads62p48_regs_t::CHANNEL_CONTROL_INDEPENDENT;
_ads62p48_regs.data_format = ads62p48_regs_t::DATA_FORMAT_2S_COMPLIMENT;
- _ads62p48_regs.clk_out_pos_edge = ads62p48_regs_t::CLK_OUT_POS_EDGE_MINUS7_26;
- _ads62p48_regs.clk_out_neg_edge = ads62p48_regs_t::CLK_OUT_NEG_EDGE_MINUS7_26;
+ _ads62p48_regs.clk_out_pos_edge = ads62p48_regs_t::CLK_OUT_POS_EDGE_NORMAL;
+ _ads62p48_regs.clk_out_neg_edge = ads62p48_regs_t::CLK_OUT_NEG_EDGE_NORMAL;
this->send_ads62p48_reg(0);
diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp
index d8ad737b7..a2b2c9f9e 100644
--- a/host/lib/usrp/x300/x300_impl.cpp
+++ b/host/lib/usrp/x300/x300_impl.cpp
@@ -687,15 +687,14 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
////////////////////////////////////////////////////////////////////
// setup radios
////////////////////////////////////////////////////////////////////
- UHD_MSG(status) << "Initialize Radio control..." << std::endl;
- this->setup_radio(mb_i, "A");
- this->setup_radio(mb_i, "B");
+ this->setup_radio(mb_i, "A", dev_addr);
+ this->setup_radio(mb_i, "B", dev_addr);
////////////////////////////////////////////////////////////////////
// ADC test and cal
////////////////////////////////////////////////////////////////////
if (dev_addr.has_key("self_cal_adc_delay")) {
- self_cal_adc_delay(mb, true /* Apply ADC delay */);
+ self_cal_adc_xfer_delay(mb, true /* Apply ADC delay */);
}
self_test_adcs(mb);
@@ -855,7 +854,7 @@ x300_impl::~x300_impl(void)
}
}
-void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name)
+void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, const uhd::device_addr_t &dev_addr)
{
const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_i);
UHD_ASSERT_THROW(mb_i < _mb.size());
@@ -863,6 +862,8 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name)
const size_t radio_index = mb.get_radio_index(slot_name);
radio_perifs_t &perif = mb.radio_perifs[radio_index];
+ UHD_MSG(status) << boost::format("Initialize Radio%d control...") % radio_index << std::endl;
+
////////////////////////////////////////////////////////////////////
// radio control
////////////////////////////////////////////////////////////////////
@@ -892,6 +893,10 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name)
perif.dac = x300_dac_ctrl::make(perif.spi, DB_DAC_SEN, mb.clock->get_master_clock_rate());
perif.leds = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_LEDS));
+ //Capture delays are calibrated every time. The status is only printed is the user
+ //asks to run the xfer self cal using "self_cal_adc_delay"
+ self_cal_adc_capture_delay(mb, radio_index, dev_addr.has_key("self_cal_adc_delay"));
+
_tree->access<time_spec_t>(mb_path / "time" / "cmd")
.subscribe(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1));
_tree->access<double>(mb_path / "tick_rate")
@@ -1788,9 +1793,91 @@ x300_impl::x300_mboard_t x300_impl::get_mb_type_from_eeprom(const uhd::usrp::mbo
return mb_type;
}
-double x300_impl::self_cal_adc_delay(mboard_members_t& mb, bool apply_delay)
+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
+ int win_start = -1, win_stop = -1;
+
+ 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 >= 4) break;
+ }
+ }
+ //UHD_MSG(status) << (boost::format("CapTap=%d, Error=%d\n") % dly_tap % err_code);
+ }
+ 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 < 4) {
+ 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)\n") % ideal_tap % (win_stop-win_start) % tap_delay;
+ }
+}
+
+double x300_impl::self_cal_adc_xfer_delay(mboard_members_t& mb, bool apply_delay)
{
- UHD_MSG(status) << "Running ADC delay self-calibration: " << std::flush;
+ 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;
@@ -1801,6 +1888,8 @@ double x300_impl::self_cal_adc_delay(mboard_members_t& mb, bool apply_delay)
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;
@@ -1823,8 +1912,8 @@ double x300_impl::self_cal_adc_delay(mboard_members_t& mb, bool apply_delay)
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_CHECKER_I_LOCKED)) {
- err_code += mb.radio_perifs[r].misc_ins->get(radio_misc_ins_reg::ADC_CHECKER_I_ERROR);
+ 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
}
@@ -1838,13 +1927,13 @@ double x300_impl::self_cal_adc_delay(mboard_members_t& mb, bool apply_delay)
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_CHECKER_Q_LOCKED)) {
- err_code += mb.radio_perifs[r].misc_ins->get(radio_misc_ins_reg::ADC_CHECKER_Q_ERROR);
+ 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("Delay=%fns, Error=%d\n") % delay % err_code);
+ //UHD_MSG(status) << (boost::format("XferDelay=%fns, Error=%d\n") % delay % err_code);
results.push_back(std::pair<double,bool>(delay, err_code==0));
}
@@ -1879,37 +1968,32 @@ double x300_impl::self_cal_adc_delay(mboard_members_t& mb, bool apply_delay)
}
}
if (win_start_idx == -1) {
- throw uhd::runtime_error("self_cal_adc_delay: Self calibration failed. Convergence error.");
+ 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_delay: Self calibration failed. Valid window too narrow.");
+ 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) << "Applying..." << std::flush;
+ UHD_MSG(status) << "Validating..." << std::flush;
+ //Apply delay
win_center = mb.clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, win_center); //Sets ADC0 and ADC1
wait_for_ref_locked(mb.zpu_ctrl, 0.1);
-
- UHD_MSG(status) << "Validating..." << std::flush;
- //Validate delay value
- for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) {
- mb.radio_perifs[r].adc->set_test_word("ramp", "ramp");
- 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);
- }
- boost::this_thread::sleep(boost::posix_time::milliseconds(3000));
- for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) {
- if (!mb.radio_perifs[r].misc_ins->read(radio_misc_ins_reg::ADC_CHECKER_I_LOCKED) ||
- mb.radio_perifs[r].misc_ins->read(radio_misc_ins_reg::ADC_CHECKER_I_ERROR) ||
- !mb.radio_perifs[r].misc_ins->read(radio_misc_ins_reg::ADC_CHECKER_Q_LOCKED) ||
- mb.radio_perifs[r].misc_ins->read(radio_misc_ins_reg::ADC_CHECKER_Q_ERROR))
- {
- throw uhd::runtime_error("self_cal_adc_delay: Self calibration verification failed.");
- }
- }
+ //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
@@ -1917,12 +2001,8 @@ double x300_impl::self_cal_adc_delay(mboard_members_t& mb, bool apply_delay)
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);
}
-
- //Report
- UHD_MSG(status) << " done" << std::endl;
- UHD_MSG(status) << (boost::format(
- "ADC delay self-calibration results: Delay=%.3fns, Window=%.3fns, FPGA->ADC=%.3fns\n")
- % win_center % win_length % (win_center - mb.clock->get_clock_delay(X300_CLOCK_WHICH_FPGA)));
+ 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;
}
@@ -1937,10 +2017,11 @@ static void check_adc(wb_iface::sptr iface, const boost::uint32_t val, const boo
}
}
-void x300_impl::self_test_adcs(mboard_members_t& mb) {
+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);
@@ -1955,6 +2036,26 @@ void x300_impl::self_test_adcs(mboard_members_t& mb) {
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));
+ for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) {
+ radio_perifs_t &perif = mb.radio_perifs[r];
+
+ if (!perif.misc_ins->read(radio_misc_ins_reg::ADC_CHECKER1_I_LOCKED) ||
+ perif.misc_ins->read(radio_misc_ins_reg::ADC_CHECKER1_I_ERROR) ||
+ !perif.misc_ins->read(radio_misc_ins_reg::ADC_CHECKER1_Q_LOCKED) ||
+ perif.misc_ins->read(radio_misc_ins_reg::ADC_CHECKER1_Q_ERROR))
+ {
+ throw uhd::runtime_error(
+ (boost::format("ADC self-test failed for Radio%d. (Ramp checker failure)")%r).str());
+ }
+
+ //Return to normal mode
perif.adc->set_test_word("normal", "normal");
}
}
diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp
index 83d11c6b6..dca6360b8 100644
--- a/host/lib/usrp/x300/x300_impl.hpp
+++ b/host/lib/usrp/x300/x300_impl.hpp
@@ -176,22 +176,30 @@ private:
UHD_DEFINE_SOFT_REG_FIELD(DAC_ENABLED, /*width*/ 1, /*shift*/ 0); //[0]
UHD_DEFINE_SOFT_REG_FIELD(DAC_RESET_N, /*width*/ 1, /*shift*/ 1); //[1]
UHD_DEFINE_SOFT_REG_FIELD(ADC_RESET, /*width*/ 1, /*shift*/ 2); //[2]
- UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_ENABLED, /*width*/ 1, /*shift*/ 4); //[4]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_STB, /*width*/ 1, /*shift*/ 3); //[3]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_VAL, /*width*/ 5, /*shift*/ 4); //[8:4]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_ENABLED, /*width*/ 1, /*shift*/ 9); //[9]
radio_misc_outs_reg(): uhd::soft_reg32_wo_t(TOREG(SR_MISC_OUTS)) {
//Initial values
set(DAC_ENABLED, 0);
set(DAC_RESET_N, 0);
set(ADC_RESET, 0);
+ set(ADC_DATA_DLY_STB, 0);
+ set(ADC_DATA_DLY_VAL, 16);
set(ADC_CHECKER_ENABLED, 0);
}
};
class radio_misc_ins_reg : public uhd::soft_reg32_ro_t {
public:
- UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_Q_LOCKED, /*width*/ 1, /*shift*/ 0); //[0]
- UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_I_LOCKED, /*width*/ 1, /*shift*/ 1); //[1]
- UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_Q_ERROR, /*width*/ 1, /*shift*/ 2); //[2]
- UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_I_ERROR, /*width*/ 1, /*shift*/ 3); //[3]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 0); //[0]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 1); //[1]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 2); //[2]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 3); //[3]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_ERROR, /*width*/ 1, /*shift*/ 4); //[4]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_ERROR, /*width*/ 1, /*shift*/ 5); //[5]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_ERROR, /*width*/ 1, /*shift*/ 6); //[6]
+ UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_ERROR, /*width*/ 1, /*shift*/ 7); //[7]
radio_misc_ins_reg(): uhd::soft_reg32_ro_t(RB32_MISC_INS) { }
};
@@ -291,7 +299,7 @@ private:
* \param mb_i Motherboard index
* \param slot_name Slot name (A or B).
*/
- void setup_radio(const size_t, const std::string &slot_name);
+ void setup_radio(const size_t, const std::string &slot_name, const uhd::device_addr_t &dev_addr);
size_t _sid_framer;
struct sid_config_t
@@ -396,8 +404,9 @@ private:
boost::uint32_t get_fp_gpio(gpio_core_200::sptr);
void set_fp_gpio(gpio_core_200::sptr, const gpio_attr_t, const boost::uint32_t);
- double self_cal_adc_delay(mboard_members_t& mb, bool apply_delay = false);
- void self_test_adcs(mboard_members_t& mb);
+ void self_cal_adc_capture_delay(mboard_members_t& mb, const size_t radio_i, bool print_status = false);
+ double self_cal_adc_xfer_delay(mboard_members_t& mb, bool apply_delay = false);
+ void self_test_adcs(mboard_members_t& mb, boost::uint32_t ramp_time_ms = 100);
//**PRECONDITION**
//This function assumes that all the VITA times in "radios" are synchronized