aboutsummaryrefslogtreecommitdiffstats
path: root/host
diff options
context:
space:
mode:
authorAshish Chaudhari <ashish@ettus.com>2014-11-06 14:21:39 -0800
committerAshish Chaudhari <ashish@ettus.com>2014-11-06 14:21:39 -0800
commit71565ecad53214c0179666d5715a5b81f4fea23f (patch)
tree2b49814d0d42b1f33167cb01d3bc4de4b4c20362 /host
parent60455eb59e1aeb68bc54ab3620ef7995ada49be4 (diff)
downloaduhd-71565ecad53214c0179666d5715a5b81f4fea23f.tar.gz
uhd-71565ecad53214c0179666d5715a5b81f4fea23f.tar.bz2
uhd-71565ecad53214c0179666d5715a5b81f4fea23f.zip
x300: Cleaned up DAC ctrl and clock init logic
- DAC: Squashed configuration into 2 main operations: reset and reset_and_resync - DAC: Put in sleep mode during configuration - DAC: Synchronize only if streaming to more than one DAC - DAC: Use falling edge sync mode - DAC: Fixed power up/down settings - DAC: Frontend sync failure is fatal - Clocks: Refactored clock source change logic - Clocks: Cleaned up init and lock-check sequence
Diffstat (limited to 'host')
-rw-r--r--host/lib/usrp/x300/x300_clock_ctrl.cpp1
-rw-r--r--host/lib/usrp/x300/x300_dac_ctrl.cpp210
-rw-r--r--host/lib/usrp/x300/x300_dac_ctrl.hpp14
-rw-r--r--host/lib/usrp/x300/x300_impl.cpp273
-rw-r--r--host/lib/usrp/x300/x300_impl.hpp12
-rw-r--r--host/lib/usrp/x300/x300_io_impl.cpp9
6 files changed, 318 insertions, 201 deletions
diff --git a/host/lib/usrp/x300/x300_clock_ctrl.cpp b/host/lib/usrp/x300/x300_clock_ctrl.cpp
index 21411e651..247c10ac4 100644
--- a/host/lib/usrp/x300/x300_clock_ctrl.cpp
+++ b/host/lib/usrp/x300/x300_clock_ctrl.cpp
@@ -49,7 +49,6 @@ x300_clock_ctrl_impl(uhd::spi_iface::sptr spiface,
_master_clock_rate(master_clock_rate),
_system_ref_rate(system_ref_rate)
{
- set_master_clock_rate(master_clock_rate);
}
void reset_clocks() {
diff --git a/host/lib/usrp/x300/x300_dac_ctrl.cpp b/host/lib/usrp/x300/x300_dac_ctrl.cpp
index 62fe55c35..d3bcb8644 100644
--- a/host/lib/usrp/x300/x300_dac_ctrl.cpp
+++ b/host/lib/usrp/x300/x300_dac_ctrl.cpp
@@ -25,6 +25,8 @@
#include <boost/foreach.hpp>
#include <boost/thread/thread.hpp> //sleep
+#define X300_DAC_FRONTEND_SYNC_FAILURE_FATAL
+
using namespace uhd;
#define write_ad9146_reg(addr, data) \
@@ -45,19 +47,68 @@ public:
x300_dac_ctrl_impl(uhd::spi_iface::sptr iface, const size_t slaveno, const double refclk):
_iface(iface), _slaveno(slaveno), _refclk(refclk)
{
- init();
- check_pll();
+ //Power up all DAC subsystems
+ write_ad9146_reg(0x01, 0x10); //Up: I DAC, Q DAC, Receiver, Voltage Ref, Clocks
+ write_ad9146_reg(0x02, 0x00); //No extended delays. Up: Voltage Ref, PLL, DAC, FIFO, Filters
+
+ reset();
}
- void init()
+ ~x300_dac_ctrl_impl(void)
{
- write_ad9146_reg(0x00, 0x20); // Take DAC into reset.
- write_ad9146_reg(0x00, 0x80); // Enable SPI reads and come out of reset
- write_ad9146_reg(0x1e, 0x01); // Data path config - set for proper operation
+ UHD_SAFE_CALL
+ (
+ //Power down all DAC subsystems
+ write_ad9146_reg(0x01, 0xEF); //Down: I DAC, Q DAC, Receiver, Voltage Ref, Clocks
+ write_ad9146_reg(0x02, 0x1F); //No extended delays. Down: Voltage Ref, PLL, DAC, FIFO, Filters
+ )
+ }
+
+ void reset()
+ {
+ //ADI recommendations:
+ //- soft reset the chip before configuration
+ //- put the chip in sleep mode during configuration and wake it up when done
+ _soft_reset();
+ _sleep_mode(true);
+ _init();
+ _sleep_mode(false);
+ }
+
+ void reset_and_resync()
+ {
+ //ADI recommendations:
+ //- soft reset the chip before configuration
+ //- put the chip in sleep mode during configuration and wake it up when done
+ //- configure synchronization settings when sleeping
+ _soft_reset();
+ _sleep_mode(true);
+ _init();
+ _backend_sync();
+ _sleep_mode(false);
+ }
+
+ void verify_sync()
+ {
+ _check_pll();
+ _check_dac_sync();
+#ifdef X300_DAC_FRONTEND_SYNC_FAILURE_FATAL
+ _check_frontend_sync(true);
+#else
+ _check_frontend_sync(false);
+#endif
+ }
+
+ //
+ // Setup all non-synchronization related DAC parameters
+ //
+ void _init()
+ {
+ write_ad9146_reg(0x1e, 0x01); //Datasheet: "Set 1 for proper operation"
+ write_ad9146_reg(0x06, 0xFF); //Clear all event flags
// Calculate N0 to be VCO friendly.
// Aim for VCO between 1 and 2GHz, assert otherwise.
- // const int N1 = 4;
const int N1 = 4;
int N0_val, N0;
for (N0_val = 0; N0_val < 3; N0_val++)
@@ -68,78 +119,83 @@ public:
UHD_ASSERT_THROW((_refclk * N0 * N1) >= 1e9);
UHD_ASSERT_THROW((_refclk * N0 * N1) <= 2e9);
- /* Start PLL */
- //write_ad9146_reg(0x0C, 0xD1); // Narrow PLL loop filter, Midrange charge pump.
+ // Start PLL
+ write_ad9146_reg(0x06, 0xC0); //Clear PLL event flags
+ write_ad9146_reg(0x0C, 0xD1); // Narrow PLL loop filter, Midrange charge pump.
write_ad9146_reg(0x0D, 0xD1 | (N0_val << 2)); // N1=4, N2=16, N0 as calculated
- //write_ad9146_reg(0x0D, 0x90 | (N0_val << 2)); // N1=2, N2=8, N0 as calculated
write_ad9146_reg(0x0A, 0xCF); // Auto init VCO band training as per datasheet
write_ad9146_reg(0x0A, 0xA0); // See above.
- /* Skew DCI signal to find stable data eye */
- //write_ad9146_reg(0x16, 0x04); //Disable delay in DCI
- //write_ad9146_reg(0x16, 0x00); //165ps delay in DCI
- //write_ad9146_reg(0x16, 0x01); //375ps delay in DCI
- write_ad9146_reg(0x16, 0x02); //615ps delay in DCI
- //write_ad9146_reg(0x16, 0x03); //720ps delay in DCI
+ _check_pll();
+ // Configure digital interface settings
+ write_ad9146_reg(0x16, 0x02); // Skew DCI signal by 615ps to find stable data eye
write_ad9146_reg(0x03, 0x00); // 2's comp, I first, byte wide interface
-
//fpga wants I,Q in the sample word:
//first transaction goes into low bits
//second transaction goes into high bits
//therefore, we want Q to go first (bit 6 == 1)
write_ad9146_reg(0x03, (1 << 6)); //2s comp, i first, byte mode
- write_ad9146_reg(0x10, 0x48); // Disable SYNC mode.
-
- // FIFO write pointer offset
- // It was found that the read was happening before the write
- // so the FIFO was maintainining a depth of 3 during operation.
- // Setting it to 5 to ensure it maintains the ideal depth of 4.
- // TODO: Investigate RefClk -> DCI clock timing.
- write_ad9146_reg(0x17, 0x05);
-
- write_ad9146_reg(0x18, 0x02); // Request soft FIFO align
- write_ad9146_reg(0x18, 0x00); // (See above)
- write_ad9146_reg(0x1B, 0xE4); // Bypass: Modulator, InvSinc, IQ Bal
-
- /* Configure interpolation filters */
+ // Configure interpolation filters
write_ad9146_reg(0x1C, 0x00); // Configure HB1
write_ad9146_reg(0x1D, 0x00); // Configure HB2
+ write_ad9146_reg(0x1B, 0xE4); // Bypass: Modulator, InvSinc, IQ Bal
- // Clear event flags
- write_ad9146_reg(0x06, 0xFF);
- }
-
-
- ~x300_dac_ctrl_impl(void)
- {
- UHD_SAFE_CALL
- (
- write_ad9146_reg(0x1, 0xf); //total power down
- write_ad9146_reg(0x2, 0xf); //total power down
- )
+ // Disable sync mode by default (may get turned on later)
+ write_ad9146_reg(0x10, 0x40); // Disable SYNC mode.
}
- void arm_dac_sync(void)
+ //
+ // Attempt to synchronize AD9146's
+ //
+ void _backend_sync(void)
{
- //
- // Attempt to synchronize AD9146's
- //
- write_ad9146_reg(0x10, 0x48); // Disable SYNC mode.
+ write_ad9146_reg(0x10, 0x40); // Disable SYNC mode to reset state machines.
write_ad9146_reg(0x06, 0x30); // Clear Sync event flags
- write_ad9146_reg(0x10, 0xCF); // Enable SYNC mode. Sync Averaging set to 128.
- }
- void reset()
- {
- init();
+ //SYNC Settings:
+ //- SYNC = Enabled
+ //- Data Rate Mode: Synchronize at the rate at which data is consumed and not at
+ // the granularity of the FIFO
+ //- Falling edge sync: For the X300, DACCLK is generated using RefClk. Within the
+ // DAC, the RefClk is sampled by DACCLK to sync interpolation
+ // stages across multiple DACs. To ensure that we capture the
+ // RefClk when it is not transitioning, we sample on the falling
+ // edge of DACCLK
+ //- Averaging = MAX
+ write_ad9146_reg(0x10, 0xC7); // Enable SYNC mode. Falling edge sync. Averaging set to 128.
+
+ //Wait for backend SYNC state machine to lock before proceeding. This guarantees that the
+ //inputs and output of the FIFO have synchronized clocks
+ _check_dac_sync();
+
+ //FIFO write pointer offset
+ //One of ADI's requirements to use data-rate synchronization in PLL mode is to meet
+ //setup and hold times for RefClk -> DCI clock which we *do not* currently meet in
+ //the FPGA. The DCI clock reaches a full RefClk cycle later which results in the
+ //FIFO popping before the first push. This results in a steady-state FIFO fullness
+ //of pointer - 1. To reach the optimal FIFO fullness of 4 we set the pointer to 5.
+ //FIXME: At some point we should meet timing on this interface
+ write_ad9146_reg(0x17, 0x05);
+
+ // We are requesting a soft FIFO align just to put the FIFO
+ // in a known state. The FRAME will actually do sync the
+ // FIFO correctly when a stream is created
+ write_ad9146_reg(0x18, 0x02); // Request soft FIFO align
+ write_ad9146_reg(0x18, 0x00); // (See above)
+
+ //Verify the FIFO thermometer
+ _check_frontend_sync(false); //FIFO sanity check
}
- void check_pll()
+ //
+ // Check for PLL lock. Fatal if not locked within timeout
+ //
+ void _check_pll()
{
// Verify PLL is Locked. 1 sec timeout.
- // NOTE: Data sheet inconsistant about which pins give PLL lock status. FIXME!
+ // NOTE: Data sheet inconsistent about which pins give PLL lock status. FIXME!
const time_spec_t exit_time = time_spec_t::get_system_time() + time_spec_t(1.0);
while (true)
{
@@ -149,13 +205,16 @@ public:
break;
if (exit_time < time_spec_t::get_system_time())
throw uhd::runtime_error("x300_dac_ctrl: timeout waiting for DAC PLL to lock");
- if (reg_6 & (1 << 7)) // Sync lost?
+ if (reg_6 & (1 << 7)) // Lock lost?
write_ad9146_reg(0x06, 0xC0); // Clear PLL event flags
boost::this_thread::sleep(boost::posix_time::milliseconds(10));
}
}
- void check_dac_sync()
+ //
+ // Check for DAC sync. Fatal if not synced within timeout
+ //
+ void _check_dac_sync()
{
const time_spec_t exit_time = time_spec_t::get_system_time() + time_spec_t(1.0);
while (true)
@@ -167,19 +226,44 @@ public:
break;
if (exit_time < time_spec_t::get_system_time())
throw uhd::runtime_error("x300_dac_ctrl: timeout waiting for backend synchronization");
- if (reg_12 & (1 << 7)) // Sync acquired and lost?
- arm_dac_sync(); // Re-arm and try again
- else if (reg_6 & (1 << 5))
+ if (reg_6 & (1 << 5))
write_ad9146_reg(0x06, 0x30); // Clear Sync event flags
+#ifdef X300_DAC_RETRY_BACKEND_SYNC
+ if (reg_12 & (1 << 7)) // Sync acquired and lost?
+ write_ad9146_reg(0x10, 0xC7); // Enable SYNC mode. Falling edge sync. Averaging set to 128.
+#endif
}
}
- void check_frontend_sync()
+ //
+ // Check FIFO thermometer.
+ //
+ void _check_frontend_sync(bool failure_is_fatal)
{
// Register 0x19 has a thermometer indicator of the FIFO depth
const size_t reg_19 = read_ad9146_reg(0x19);
- if ((reg_19 & 0xFF) != 0xF)
- UHD_MSG(warning) << "x300_dac_ctrl: unexpected FIFO depth [0x" << std::hex << (reg_19 & 0xFF) << std::dec << "]" << std::endl;
+ if ((reg_19 & 0xFF) != 0xF) {
+ std::string msg((boost::format("x300_dac_ctrl: front-end sync failed. unexpected FIFO depth [0x%x]\n") % (reg_19 & 0xFF)).str());
+ if (failure_is_fatal) {
+ throw uhd::runtime_error(msg);
+ } else {
+ UHD_MSG(warning) << msg;
+ }
+ }
+ }
+
+ void _sleep_mode(bool sleep)
+ {
+ boost::uint8_t sleep_val = sleep ? (1<<7) : 0x00;
+ //Set sleep word and default fullscale value
+ write_ad9146_reg(0x41, sleep_val | 0x01); //I DAC
+ write_ad9146_reg(0x45, sleep_val | 0x01); //Q DAC
+ }
+
+ void _soft_reset()
+ {
+ write_ad9146_reg(0x00, 0x20); // Take DAC into reset.
+ write_ad9146_reg(0x00, 0x80); // Enable SPI reads and come out of reset
}
private:
diff --git a/host/lib/usrp/x300/x300_dac_ctrl.hpp b/host/lib/usrp/x300/x300_dac_ctrl.hpp
index 5fd7e13d8..c2e509b54 100644
--- a/host/lib/usrp/x300/x300_dac_ctrl.hpp
+++ b/host/lib/usrp/x300/x300_dac_ctrl.hpp
@@ -37,20 +37,14 @@ public:
*/
static sptr make(uhd::spi_iface::sptr iface, const size_t slaveno, const double clock_rate);
- // ! Arm the sync feature in DAC
- virtual void arm_dac_sync(void) = 0;
-
- // ! Check for successful backend sync
- virtual void check_dac_sync(void) = 0;
-
// ! Reset the DAC
virtual void reset(void) = 0;
- // ! Check for PLL lock
- virtual void check_pll(void) = 0;
+ // ! Reset the DAC and resync
+ virtual void reset_and_resync(void) = 0;
- // ! Check for successful frontend sync
- virtual void check_frontend_sync(void) = 0;
+ // ! Check for successful backend and frontend sync
+ virtual void verify_sync(void) = 0;
};
#endif /* INCLUDED_X300_DAC_CTRL_HPP */
diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp
index b4286ee79..5520cc82e 100644
--- a/host/lib/usrp/x300/x300_impl.cpp
+++ b/host/lib/usrp/x300/x300_impl.cpp
@@ -372,6 +372,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
{
const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_i);
mboard_members_t &mb = _mb[mb_i];
+ mb.initialization_done = false;
mb.addr = dev_addr.has_key("resource") ? dev_addr["resource"] : dev_addr["addr"];
mb.xport_path = dev_addr.has_key("resource") ? "nirio" : "eth";
@@ -621,32 +622,18 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
mb.hw_rev = X300_REV("D");
}
- //Initialize clock control with internal references and GPSDO power on.
- mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_INTERNAL;
- mb.clock_control_regs_pps_select = ZPU_SR_CLOCK_CTRL_PPS_SRC_INTERNAL;
- mb.clock_control_regs_pps_out_enb = 0;
- mb.clock_control_regs_tcxo_enb = 1;
- mb.clock_control_regs_gpsdo_pwr = 1;
- this->update_clock_control(mb);
-
- //Create clock control
+ //Create clock control. NOTE: This does not configure the LMK yet.
+ initialize_clock_control(mb);
mb.clock = x300_clock_ctrl::make(mb.zpu_spi,
1 /*slaveno*/,
mb.hw_rev,
dev_addr.cast<double>("master_clock_rate", X300_DEFAULT_TICK_RATE),
dev_addr.cast<double>("system_ref_rate", X300_DEFAULT_SYSREF_RATE));
- //wait for reference clock to lock
- if(mb.hw_rev > 4)
- {
- try {
- //FIXME: Need to verify timeout value to make sure lock can be achieved in < 1.0 seconds
- wait_for_ref_locked(mb.zpu_ctrl, 1.0);
- } catch (uhd::runtime_error &e) {
- //Silently fail for now, but fix after we have the correct timeout value
- //UHD_MSG(warning) << "Clock failed to lock to internal source during initialization." << std::endl;
- }
- }
+ //Initialize clock source to use internal reference and generate
+ //a valid radio clock. This may change after configuration is done.
+ //This will configure the LMK and wait for lock
+ update_clock_source(mb, "internal");
////////////////////////////////////////////////////////////////////
// create clock properties
@@ -750,7 +737,6 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
_tree->create<std::string>(mb_path / "clock_source" / "value")
.set("internal")
.subscribe(boost::bind(&x300_impl::update_clock_source, this, boost::ref(mb), _1))
- .subscribe(boost::bind(&x300_impl::reset_clocks, this, boost::ref(mb)))
.subscribe(boost::bind(&x300_impl::reset_radios, this, boost::ref(mb)));
static const std::vector<std::string> clock_source_options = boost::assign::list_of("internal")("external")("gpsdo");
@@ -769,6 +755,22 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
_tree->create<bool>(mb_path / "clock_source" / "output")
.subscribe(boost::bind(&x300_clock_ctrl::set_ref_out, mb.clock, _1));
+ ////////////////////////////////////////////////////////////////////
+ // initialize clock and time sources
+ ////////////////////////////////////////////////////////////////////
+ if (mb.gps and mb.gps->gps_detected())
+ {
+ UHD_MSG(status) << "Initializing clock and time using GPSDO... " << std::flush;
+ _tree->access<std::string>(mb_path / "clock_source" / "value").set("gpsdo");
+ _tree->access<std::string>(mb_path / "time_source" / "value").set("gpsdo");
+ const time_t tp = time_t(mb.gps->get_sensor("gps_time").to_int() + 1);
+ _tree->access<time_spec_t>(mb_path / "time" / "pps").set(time_spec_t(tp));
+ } else {
+ UHD_MSG(status) << "Initializing clock and time using internal sources... " << std::flush;
+ _tree->access<std::string>(mb_path / "clock_source" / "value").set("internal");
+ _tree->access<std::string>(mb_path / "time_source" / "value").set("internal");
+ }
+ UHD_MSG(status) << "done" << std::endl;
////////////////////////////////////////////////////////////////////
// create frontend mapping
@@ -811,25 +813,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
_tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_fe_spec);
_tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_fe_spec);
- UHD_MSG(status) << "Initializing clock and PPS references..." << std::endl;
- //Set to the GPSDO if installed
- if (mb.gps and mb.gps->gps_detected())
- {
- _tree->access<std::string>(mb_path / "clock_source" / "value").set("gpsdo");
- try {
- wait_for_ref_locked(mb.zpu_ctrl, 1.0);
- } catch (uhd::exception::runtime_error &e) {
- UHD_MSG(warning) << "Clock reference failed to lock to GPSDO during device initialization. " <<
- "Check for the lock before operation or ignore this warning if using another clock source." << std::endl;
- }
- _tree->access<std::string>(mb_path / "time_source" / "value").set("gpsdo");
- UHD_MSG(status) << "References initialized to GPSDO sources" << std::endl;
- UHD_MSG(status) << "Initializing time to the GPSDO time" << std::endl;
- const time_t tp = time_t(mb.gps->get_sensor("gps_time").to_int()+1);
- _tree->access<time_spec_t>(mb_path / "time" / "pps").set(time_spec_t(tp));
- } else {
- UHD_MSG(status) << "References initialized to internal sources" << std::endl;
- }
+ mb.initialization_done = true;
}
x300_impl::~x300_impl(void)
@@ -917,14 +901,6 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name)
perif.adc->set_test_word("normal", "normal");
////////////////////////////////////////////////////////////////
- // Sync DAC's for MIMO
- ////////////////////////////////////////////////////////////////
- UHD_MSG(status) << "Sync DAC's." << std::endl;
- perif.dac->arm_dac_sync(); // Put DAC into data Sync mode
- perif.ctrl->poke32(TOREG(SR_DACSYNC), 0x1); // Arm FRAMEP/N sync pulse
-
-
- ////////////////////////////////////////////////////////////////
// create codec control objects
////////////////////////////////////////////////////////////////
_tree->create<int>(mb_path / "rx_codecs" / slot_name / "gains"); //phony property so this dir exists
@@ -960,8 +936,6 @@ void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name)
.subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, perif.tx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
-
-
////////////////////////////////////////////////////////////////////
// create rx dsp control objects
////////////////////////////////////////////////////////////////////
@@ -1200,10 +1174,10 @@ x300_impl::both_xports_t x300_impl::make_transport(
<< std::endl;
}
- size_t system_max_send_frame_size = (size_t) _max_frame_sizes.send_frame_size;
- size_t system_max_recv_frame_size = (size_t) _max_frame_sizes.recv_frame_size;
+ size_t system_max_send_frame_size = (size_t) _max_frame_sizes.send_frame_size;
+ size_t system_max_recv_frame_size = (size_t) _max_frame_sizes.recv_frame_size;
- // Make sure frame sizes do not exceed the max available value supported by UHD
+ // Make sure frame sizes do not exceed the max available value supported by UHD
default_buff_args.send_frame_size =
(prefix == X300_RADIO_DEST_PREFIX_TX)
? std::min(system_max_send_frame_size, X300_10GE_DATA_FRAME_MAX_SIZE)
@@ -1346,11 +1320,9 @@ void x300_impl::register_loopback_self_test(wb_iface::sptr iface)
UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl;
}
-void x300_impl::set_time_source_out(mboard_members_t &mb, const bool enb)
-{
- mb.clock_control_regs_pps_out_enb = enb? 1 : 0;
- this->update_clock_control(mb);
-}
+/***********************************************************************
+ * clock and time control logic
+ **********************************************************************/
void x300_impl::update_clock_control(mboard_members_t &mb)
{
@@ -1363,79 +1335,71 @@ void x300_impl::update_clock_control(mboard_members_t &mb)
mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_CLOCK_CTRL), reg);
}
-void x300_impl::update_clock_source(mboard_members_t &mb, const std::string &source)
+void x300_impl::initialize_clock_control(mboard_members_t &mb)
{
- mb.clock_control_regs_clock_source = 0;
- mb.clock_control_regs_tcxo_enb = 0;
- if (source == "internal") {
- mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_INTERNAL;
- mb.clock_control_regs_tcxo_enb = 1;
- } else if (source == "external") {
- mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_EXTERNAL;
- } else if (source == "gpsdo") {
- mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_GPSDO;
- } else {
- throw uhd::key_error("update_clock_source: unknown source: " + source);
- }
-
+ //Initialize clock control register soft copies
+ mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_INTERNAL;
+ mb.clock_control_regs_pps_select = ZPU_SR_CLOCK_CTRL_PPS_SRC_INTERNAL;
+ mb.clock_control_regs_pps_out_enb = 0;
+ mb.clock_control_regs_tcxo_enb = 1;
+ mb.clock_control_regs_gpsdo_pwr = 1; //GPSDO power always ON
this->update_clock_control(mb);
-
- /* FIXME: implement when we know the correct timeouts
- * //wait for lock
- * double timeout = 1.0;
- * try {
- * if (mb.hw_rev > 4)
- * wait_for_ref_locked(mb.zpu_ctrl, timeout);
- * } catch (uhd::runtime_error &e) {
- * //failed to lock on reference
- * throw uhd::runtime_error((boost::format("Clock failed to lock to %s source.") % source).str());
- * }
- */
}
-void x300_impl::reset_clocks(mboard_members_t &mb)
+void x300_impl::set_time_source_out(mboard_members_t &mb, const bool enb)
{
- mb.clock->reset_clocks();
-
- if (mb.hw_rev > 4)
- {
- try {
- wait_for_ref_locked(mb.zpu_ctrl, 30.0);
- } catch (uhd::runtime_error &e) {
- //failed to lock on reference
- throw uhd::runtime_error((boost::format("PLL failed to lock to reference clock.")).str());
- }
- }
+ mb.clock_control_regs_pps_out_enb = enb? 1 : 0;
+ this->update_clock_control(mb);
}
-void x300_impl::reset_radios(mboard_members_t &mb)
+void x300_impl::update_clock_source(mboard_members_t &mb, const std::string &source)
{
- // reset ADCs and DACs
- BOOST_FOREACH (radio_perifs_t& perif, mb.radio_perifs)
- {
- perif.adc->reset();
- perif.dac->reset();
- }
+ //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.
+ if (not (mb.current_refclk_src == "internal" and source == "internal")) {
+ //Update the clock MUX on the motherboard to select the requested source
+ mb.clock_control_regs_clock_source = 0;
+ mb.clock_control_regs_tcxo_enb = 0;
+ if (source == "internal") {
+ mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_INTERNAL;
+ mb.clock_control_regs_tcxo_enb = 1;
+ } else if (source == "external") {
+ mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_EXTERNAL;
+ } else if (source == "gpsdo") {
+ mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_GPSDO;
+ } else {
+ throw uhd::key_error("update_clock_source: unknown source: " + source);
+ }
+ this->update_clock_control(mb);
- // check PLL locks
- BOOST_FOREACH (radio_perifs_t& perif, mb.radio_perifs)
- {
- perif.dac->check_pll();
+ //Reset the LMK to make sure it re-locks to the new reference
+ mb.clock->reset_clocks();
}
- // Sync DACs
- BOOST_FOREACH (radio_perifs_t& perif, mb.radio_perifs)
- {
- perif.dac->arm_dac_sync();
- }
- BOOST_FOREACH (radio_perifs_t& perif, mb.radio_perifs)
- {
- perif.dac->check_dac_sync();
- // Arm FRAMEP/N sync pulse
- // TODO: Investigate timing of the sync frame pulse.
- perif.ctrl->poke32(TOREG(SR_DACSYNC), 0x1);
- perif.dac->check_frontend_sync();
+ //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 = mb.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 (mb.hw_rev > 4) {
+ if (not wait_for_ref_locked(mb.zpu_ctrl, timeout)) {
+ //failed to lock on reference
+ if (mb.initialization_done) {
+ throw uhd::runtime_error((boost::format("Reference Clock failed to lock to %s source.") % source).str());
+ } else {
+ //TODO: Re-enable this warning when we figure out a reliable lock time
+ //UHD_MSG(warning) << "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." << std::endl;
+ }
+ }
}
+
+ //Update cache value
+ mb.current_refclk_src = source;
}
void x300_impl::update_time_source(mboard_members_t &mb, const std::string &source)
@@ -1460,18 +1424,18 @@ void x300_impl::update_time_source(mboard_members_t &mb, const std::string &sour
}
}
-void x300_impl::wait_for_ref_locked(wb_iface::sptr ctrl, double timeout)
+bool x300_impl::wait_for_ref_locked(wb_iface::sptr ctrl, double timeout)
{
boost::system_time timeout_time = boost::get_system_time() + boost::posix_time::milliseconds(timeout * 1000.0);
do
{
if (get_ref_locked(ctrl).to_bool())
- return;
+ return true;
boost::this_thread::sleep(boost::posix_time::milliseconds(1));
} while (boost::get_system_time() < timeout_time);
//failed to lock on reference
- throw uhd::runtime_error("The reference clock failed to lock.");
+ return false;
}
sensor_value_t x300_impl::get_ref_locked(wb_iface::sptr ctrl)
@@ -1496,6 +1460,67 @@ bool x300_impl::is_pps_present(wb_iface::sptr ctrl)
return false;
}
+/***********************************************************************
+ * reset and synchronization logic
+ **********************************************************************/
+
+void x300_impl::reset_radios(mboard_members_t &mb)
+{
+ // Reset ADCs and DACs
+ BOOST_FOREACH (radio_perifs_t& perif, mb.radio_perifs)
+ {
+ perif.adc->reset();
+ perif.dac->reset();
+ }
+}
+
+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
+ **********************************************************************/
+
void x300_impl::set_db_eeprom(i2c_iface::sptr i2c, const size_t addr, const uhd::usrp::dboard_eeprom_t &db_eeprom)
{
db_eeprom.store(*i2c, addr);
@@ -1507,6 +1532,10 @@ void x300_impl::set_mb_eeprom(i2c_iface::sptr i2c, const mboard_eeprom_t &mb_eep
mb_eeprom.commit(*eeprom16, "X300");
}
+/***********************************************************************
+ * front-panel GPIO
+ **********************************************************************/
+
boost::uint32_t x300_impl::get_fp_gpio(gpio_core_200::sptr gpio, const std::string &)
{
return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX));
diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp
index 924cb61a4..70c5dccb4 100644
--- a/host/lib/usrp/x300/x300_impl.hpp
+++ b/host/lib/usrp/x300/x300_impl.hpp
@@ -194,6 +194,7 @@ private:
uhd::dict<size_t, boost::weak_ptr<uhd::rx_streamer> > rx_streamers;
uhd::dict<size_t, boost::weak_ptr<uhd::tx_streamer> > tx_streamers;
+ bool initialization_done;
uhd::task::sptr claimer_task;
std::string addr;
std::string xport_path;
@@ -233,6 +234,7 @@ private:
std::string loaded_fpga_image;
size_t hw_rev;
+ std::string current_refclk_src;
};
std::vector<mboard_members_t> _mb;
@@ -341,14 +343,14 @@ private:
void update_tx_samp_rate(mboard_members_t&, const size_t, const double);
void update_clock_control(mboard_members_t&);
+ void initialize_clock_control(mboard_members_t &mb);
void set_time_source_out(mboard_members_t&, const bool);
void update_clock_source(mboard_members_t&, const std::string &);
void update_time_source(mboard_members_t&, const std::string &);
- void reset_clocks(mboard_members_t&);
void reset_radios(mboard_members_t&);
uhd::sensor_value_t get_ref_locked(uhd::wb_iface::sptr);
- void wait_for_ref_locked(uhd::wb_iface::sptr, double timeout = 0.0);
+ bool wait_for_ref_locked(uhd::wb_iface::sptr, double timeout = 0.0);
bool is_pps_present(uhd::wb_iface::sptr);
void set_db_eeprom(uhd::i2c_iface::sptr i2c, const size_t, const uhd::usrp::dboard_eeprom_t &);
@@ -360,6 +362,12 @@ private:
void update_atr_leds(gpio_core_200_32wo::sptr, const std::string &ant);
boost::uint32_t get_fp_gpio(gpio_core_200::sptr, const std::string &);
void set_fp_gpio(gpio_core_200::sptr, const std::string &, const boost::uint32_t);
+
+ //**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.
+ static void synchronize_dacs(const std::vector<radio_perifs_t*>& mboards);
};
#endif /* INCLUDED_X300_IMPL_HPP */
diff --git a/host/lib/usrp/x300/x300_io_impl.cpp b/host/lib/usrp/x300/x300_io_impl.cpp
index c5b8f49e3..04042049d 100644
--- a/host/lib/usrp/x300/x300_io_impl.cpp
+++ b/host/lib/usrp/x300/x300_io_impl.cpp
@@ -540,6 +540,7 @@ tx_streamer::sptr x300_impl::get_tx_stream(const uhd::stream_args_t &args_)
//shared async queue for all channels in streamer
boost::shared_ptr<async_md_type> async_md(new async_md_type(1000/*messages deep*/));
+ std::vector<radio_perifs_t*> radios_list;
boost::shared_ptr<sph::send_packet_streamer> my_streamer;
for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++)
{
@@ -557,9 +558,10 @@ tx_streamer::sptr x300_impl::get_tx_stream(const uhd::stream_args_t &args_)
}
// Find the DSP that corresponds to this mainboard and subdev
mboard_members_t &mb = _mb[mb_index];
- const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "tx_chan_dsp_mapping")
+ const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "tx_chan_dsp_mapping")
.get().at(mb_chan);
radio_perifs_t &perif = mb.radio_perifs[radio_index];
+ radios_list.push_back(&perif);
//setup the dsp transport hints (TODO)
device_addr_t device_addr = mb.send_args;
@@ -571,8 +573,8 @@ tx_streamer::sptr x300_impl::get_tx_stream(const uhd::stream_args_t &args_)
both_xports_t xport = this->make_transport(mb_index, dest, X300_RADIO_DEST_PREFIX_TX, device_addr, data_sid);
UHD_LOG << boost::format("data_sid = 0x%08x\n") % data_sid << std::endl;
- // To calculate the max number of samples per packet, we assume the maximum header length
- // to avoid fragmentation should the entire header be used.
+ // To calculate the max number of samples per packet, we assume the maximum header length
+ // to avoid fragmentation should the entire header be used.
const size_t bpp = xport.send->get_send_frame_size() - X300_TX_MAX_HDR_LEN;
const size_t bpi = convert::get_bytes_per_item(args.otw_format);
const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi));
@@ -640,5 +642,6 @@ tx_streamer::sptr x300_impl::get_tx_stream(const uhd::stream_args_t &args_)
_tree->access<double>(mb_path / "tx_dsps" / boost::lexical_cast<std::string>(radio_index) / "rate" / "value").update();
}
+ synchronize_dacs(radios_list);
return my_streamer;
}