aboutsummaryrefslogtreecommitdiffstats
path: root/mpm
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2017-11-15 14:52:12 -0800
committerMartin Braun <martin.braun@ettus.com>2017-12-22 15:05:07 -0800
commitf14b49ff5b9c55972966c74b014850ecc89e75e2 (patch)
tree6d98fc58ae9e4184c4936765d45be4d2ae7dce6b /mpm
parent63f4049a101d66c01f7e89098b03f3f780647cbd (diff)
downloaduhd-f14b49ff5b9c55972966c74b014850ecc89e75e2.tar.gz
uhd-f14b49ff5b9c55972966c74b014850ecc89e75e2.tar.bz2
uhd-f14b49ff5b9c55972966c74b014850ecc89e75e2.zip
n3xx: add support for 122.88 and 153.6 MHz sample clock rates
- re-wrote portions of the LMK driver for flexible rates and configuration - tweaked TDC driver for compatibility and ease of debugging - updated comments and log statements throughout for uniformity
Diffstat (limited to 'mpm')
-rw-r--r--mpm/python/usrp_mpm/chips/lmk04828.py45
-rw-r--r--mpm/python/usrp_mpm/cores/tdc_sync.py40
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/lmk_mg.py123
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/magnesium.py70
-rw-r--r--mpm/python/usrp_mpm/periph_manager/n310.py20
5 files changed, 207 insertions, 91 deletions
diff --git a/mpm/python/usrp_mpm/chips/lmk04828.py b/mpm/python/usrp_mpm/chips/lmk04828.py
index 121d855ca..3d979e65e 100644
--- a/mpm/python/usrp_mpm/chips/lmk04828.py
+++ b/mpm/python/usrp_mpm/chips/lmk04828.py
@@ -18,6 +18,7 @@
LMK04828 parent driver class
"""
+import math
from builtins import object
from ..mpmlog import get_logger
@@ -48,7 +49,7 @@ class LMK04828(object):
Read back the chip ID
"""
chip_id = self.peek8(0x03)
- self.log.trace("Read chip ID: {}".format(chip_id))
+ self.log.trace("Chip ID Readback: {}".format(chip_id))
return chip_id
def verify_chip_id(self):
@@ -57,7 +58,7 @@ class LMK04828(object):
"""
chip_id = self.get_chip_id()
if chip_id != self.LMK_CHIP_ID:
- self.log.error("Wrong chip id 0x{:X}".format(chip_id))
+ self.log.error("Wrong Chip ID 0x{:X}".format(chip_id))
return False
return True
@@ -73,7 +74,7 @@ class LMK04828(object):
"""
pll_lock_status = self.regs_iface.peek8(addr)
if (pll_lock_status & 0x7) != 0x02:
- self.log.warning("LMK {} reporting unlocked... Status: 0x{:x}".format(pll_id, pll_lock_status))
+ self.log.warning("{} reporting unlocked... Status: 0x{:x}".format(pll_id, pll_lock_status))
return False
return True
lock_status = \
@@ -81,3 +82,41 @@ class LMK04828(object):
check_pll_lock("PLL2", 0x183)
return lock_status
+
+## Register bitfield definitions ##
+
+ def divide_to_cnth_cntl_reg(self, divide_val):
+ """
+ From the divider value, returns the CNTL and CNTH register value.
+ Split divider value in half. If odd, round up for the CNTL and down
+ for the CNTH based on the datasheet recommendation.
+ """
+ cntl = int(math.ceil( divide_val/2.0))
+ cnth = int(math.floor(divide_val/2.0))
+ reg_val = ((cnth & 0xF) << 4) | (cntl & 0xF)
+ self.log.trace("From divider value 0d{}, writing CNTH/L as 0x{:02X}."
+ .format(divide_val, reg_val))
+ return reg_val
+
+ def divide_to_reg(self, divide_val, in_drive = 0x1, out_drive = 0x1):
+ """
+ From the divider value, returns the register value combined with the other
+ register fields.
+ """
+ reg_val = (divide_val & 0x1F) | ((in_drive & 0x1) << 5) | ((out_drive & 0x1) << 6)
+ self.log.trace("From divider value 0d{}, writing divider register as 0x{:02X}."
+ .format(divide_val, reg_val))
+ return reg_val
+
+ def pll2_pre_to_reg(self, prescaler, osc_field = 0x01, xtal_en = 0x0, ref_2x_en = 0x0):
+ """
+ From the prescaler value, returns the register value combined with the other
+ register fields.
+ """
+ # valid prescaler values are 2-8, where 8 is represented as 0x00.
+ assert prescaler in range(2,8+1)
+ reg_val = ((prescaler & 0x07) << 5) | ((osc_field & 0x7) << 2) | ((xtal_en & 0x1) << 1) | ((ref_2x_en & 0x1) << 0)
+ self.log.trace("From prescaler value 0d{}, writing register as 0x{:02X}."
+ .format(prescaler, reg_val))
+ return reg_val
+
diff --git a/mpm/python/usrp_mpm/cores/tdc_sync.py b/mpm/python/usrp_mpm/cores/tdc_sync.py
index 9d5195339..5b1e2dac2 100644
--- a/mpm/python/usrp_mpm/cores/tdc_sync.py
+++ b/mpm/python/usrp_mpm/cores/tdc_sync.py
@@ -40,12 +40,12 @@ def rsp_table(ref_clk_freq, radio_clk_freq):
20e6: (20, 10),
25e6: (25, 13),
},
- 122.8e6: {
+ 122.88e6: {
10e6: (250, 125),
20e6: (500, 250),
25e6: (625, 313),
},
- 156.3e6: {
+ 153.6e6: {
10e6: (250, 125),
20e6: (500, 250),
25e6: (625, 313),
@@ -65,10 +65,10 @@ def rtc_table(radio_clk_freq):
Returns a tuple (period, high_time).
"""
return {
- 125e6: (125, 63),
- 122.8e6: (3072, 1536),
- 156.3e6: (3840, 1920),
- 104e6: (104, 52),
+ 125e6: (125, 63),
+ 122.88e6: (3072, 1536),
+ 153.6e6: (3840, 1920),
+ 104e6: (104, 52),
}[radio_clk_freq]
@@ -94,7 +94,6 @@ class ClockSynchronizer(object):
def __init__(
self,
regs_iface,
- dboard_clk_control,
lmk,
phase_dac,
offset,
@@ -102,7 +101,6 @@ class ClockSynchronizer(object):
ref_clk_freq,
fine_delay_step,
init_pdac_word,
- lmk_vco_freq,
target_values,
dac_spi_addr_val,
log
@@ -113,14 +111,14 @@ class ClockSynchronizer(object):
self.poke32 = lambda addr, data: self._iface.poke32(addr + offset, data)
self.lmk = lmk
self.phase_dac = phase_dac
- self.dboard_clk_control = dboard_clk_control
self.radio_clk_freq = radio_clk_freq
self.ref_clk_freq = ref_clk_freq
self.fine_delay_step = fine_delay_step
self.current_phase_dac_word = init_pdac_word
- self.lmk_vco_freq = lmk_vco_freq
+ self.lmk_vco_freq = self.lmk.get_vco_freq()
self.target_values = target_values
self.dac_spi_addr_val = dac_spi_addr_val
+ self.meas_clk_freq = 170.542641116e6
def run_sync(self, measurement_only=False):
"""
@@ -197,9 +195,8 @@ class ClockSynchronizer(object):
raise RuntimeError("Failed to capture PPS.")
self.log.trace("PPS Captured!")
- meas_clk_freq = 170.542641116e6
measure_offset = lambda: self.read_tdc_meas(
- 1.0/meas_clk_freq, 1.0/self.ref_clk_freq, 1.0/self.radio_clk_freq
+ 1.0/self.meas_clk_freq, 1.0/self.ref_clk_freq, 1.0/self.radio_clk_freq
)
# Retrieve the first measurement, but throw it away since it won't align with
# all the re-run measurements.
@@ -207,7 +204,7 @@ class ClockSynchronizer(object):
measure_offset()
# Now, read off 512 measurements and take the mean of them.
- num_meas = 512
+ num_meas = 256
self.log.trace("Reading {} TDC measurements from device...".format(num_meas))
current_value = mean([measure_offset() for _ in range(num_meas)])
self.log.trace("TDC measurements collected.")
@@ -238,6 +235,20 @@ class ClockSynchronizer(object):
self.fine_delay_step
)
+ # Check the calculated distance_to_target value. It should be less than
+ # +/- 1 radio_clk_freq period. The boundary values are set using the same
+ # logic as the high and low bound checks above on the current_value.
+ if abs(distance_to_target) > 1.0/self.radio_clk_freq:
+ self.log.error("Clock synchronizer measured a "
+ "distance to target of {:.3f} ns. " \
+ "Range is [{:.3f},{:.3f}] ns".format(
+ distance_to_target*1e9,
+ -1.0/self.radio_clk_freq*1e9,
+ 1.0/self.radio_clk_freq*1e9))
+ raise RuntimeError("TDC measured distance to target is out of range! "
+ "Current value: {:.3f} ns.".format(
+ distance_to_target*1e9))
+
if not measurement_only:
self.log.trace("Applying calculated shifts...")
# Coarse shift with the LMK.
@@ -265,7 +276,8 @@ class ClockSynchronizer(object):
Return the offset (in seconds) the whatever what measured and whatever
the reference is.
"""
- timeout = time.time() + 1.0
+ # Current worst-case time is around 3.5s.
+ timeout = time.time() + 4.0 # TODO knock this back down after optimizations
while True:
rtc_offset_msb = self.peek32(self.RTC_OFFSET_1)
if rtc_offset_msb & 0x100 == 0x100:
diff --git a/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py b/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py
index e6a1821af..dc3fe203b 100644
--- a/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py
+++ b/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py
@@ -19,27 +19,60 @@ LMK04828 driver for use with Magnesium
"""
import time
+import math
from builtins import zip
from builtins import hex
from ..mpmlog import get_logger
from ..chips import LMK04828
class LMK04828Mg(LMK04828):
- def __init__(self, regs_iface, spi_lock, ref_clock_freq, log=None):
+ def __init__(self, regs_iface, spi_lock, ref_clock_freq, master_clock_freq, log=None):
LMK04828.__init__(self, regs_iface, log)
- self.log.trace("Using reference clock frequency {} MHz".format(ref_clock_freq/1e6))
+ self.log.trace("Using reference clock frequency: {} MHz".format(ref_clock_freq/1e6))
+ self.log.trace("Using master clock frequency: {} MHz".format(master_clock_freq/1e6))
self.spi_lock = spi_lock
assert hasattr(self.spi_lock, 'lock')
assert hasattr(self.spi_lock, 'unlock')
self.ref_clock_freq = ref_clock_freq
+ self.master_clock_freq = master_clock_freq
+ # VCXO on Mg runs at 96 MHz
+ self.vcxo_freq = 96e6
+ # Feedback clock divider is constant for Mg regardless of the master_clock_freq.
+ self.clkfb_divider = 24
+ # PLL2 R value is also constant across all sample clock combinations.
+ self.pll2_r_divider = 4
+ # PLL1 PFD is 1 MHz. Divide the ref_clock_freq by 1 MHz to set the R divider.
+ self.clkin_r_divider = int(math.floor(self.ref_clock_freq/1e6))
+ self.clkout_divider = {122.88e6: 25, 125e6: 24, 153.6e6: 20}[self.master_clock_freq]
+ self.pll1_n_divider = {122.88e6: 128, 125e6: 125, 153.6e6: 128}[self.master_clock_freq]
+ self.sysref_divider = {122.88e6: 500, 125e6: 480, 153.6e6: 400}[self.master_clock_freq]
+ self.pll2_prescaler = {122.88e6: 2, 125e6: 5, 153.6e6: 2}[self.master_clock_freq]
+ self.pll2_n_divider = {122.88e6: 64, 125e6: 25, 153.6e6: 64}[self.master_clock_freq]
+ self.pll2_vco_freq = (self.vcxo_freq/self.pll2_r_divider)*self.pll2_prescaler*self.pll2_n_divider
+ self.log.trace("Variable Configuration Report: "
+ "clkin1_r = 0d{}, clkout_div = 0d{}, pll1_n = 0d{}"
+ .format(self.clkin_r_divider, self.clkout_divider, self.pll1_n_divider))
+ self.log.trace("Variable Configuration Report: "
+ "sysref_divider = 0d{}, pll2_pre = 0d{}, pll2_n = 0d{}"
+ .format(self.sysref_divider, self.pll2_prescaler, self.pll2_n_divider))
+ self.log.trace("Variable Configuration Report: "
+ "pll2_vco_freq = 0d{}"
+ .format(self.pll2_vco_freq))
+ # Run .init() and .config() right off the bat. Save clock shifty-ness for later.
self.init()
self.config()
+ def get_vco_freq(self):
+ """
+ Return the calculated VCO frequency in the LMK PLL2.
+ """
+ return self.pll2_vco_freq
+
def init(self):
"""
Basic init. Turns it on. Let's read SPI.
"""
- self.log.info("Reset LMK & Verify")
+ self.log.info("Reset and Verify Chip ID")
self.pokes8((
(0x000, 0x90), # Assert reset
(0x000, 0x10), # De-assert reset
@@ -47,66 +80,67 @@ class LMK04828Mg(LMK04828):
(0x148, 0x33), # Clock Select as SDO
))
if not self.verify_chip_id():
- raise Exception("Unable to locate LMK04828")
-
+ raise Exception("Unable to locate LMK04828!")
def config(self):
"""
Write lots of config foo.
"""
- self.log.trace("LMK Initialization")
- clkin0_r_divider = {10e6: 0x0A, 20e6: 0x14}[self.ref_clock_freq]
+ clkout_div_val = self.divide_to_reg(self.clkout_divider)
+ clkout_cnt_val = self.divide_to_cnth_cntl_reg(self.clkout_divider)
+
+ self.log.info("Register Initialization Commencing...")
self.pokes8((
- (0x100, 0x78), # CLKout Config
- (0x101, 0xCC), # CLKout Config
+ (0x100, clkout_div_val), # CLKout Config
+ (0x101, clkout_cnt_val), # CLKout Config
(0x102, 0xCC), # CLKout Config
(0x103, 0x00), # CLKout Config
(0x104, 0x20), # CLKout Config
(0x105, 0x00), # CLKout Config
(0x106, 0x72), # CLKout Config MYK: (0xAB where A = SYSREF, B = CLK)
(0x107, 0x15), # CLKout Config 0x15 = LVDS, 0x55 = LVPECL
- (0x108, 0x7E), # CLKout Config
- (0x109, 0xFF), # CLKout Config
+ (0x108, clkout_div_val), # CLKout Config
+ (0x109, clkout_cnt_val), # CLKout Config
(0x10A, 0xFF), # CLKout Config
(0x10B, 0x00), # CLKout Config
(0x10C, 0x00), # CLKout Config
(0x10D, 0x00), # CLKout Config
(0x10E, 0x70), # CLKout Config
(0x10F, 0x55), # CLKout Config
- (0x110, 0x78), # CLKout Config
- (0x111, 0xCC), # CLKout Config
+ (0x110, clkout_div_val), # CLKout Config
+ (0x111, clkout_cnt_val), # CLKout Config
(0x112, 0xCC), # CLKout Config
(0x113, 0x00), # CLKout Config
(0x114, 0x00), # CLKout Config
(0x115, 0x00), # CLKout Config
(0x116, 0xF9), # CLKout Config
(0x117, 0x00), # CLKout Config
- (0x118, 0x78), # CLKout Config
- (0x119, 0xCC), # CLKout Config
+ (0x118, self.divide_to_reg(self.clkfb_divider)), # CLKout Config
+ (0x119, self.divide_to_cnth_cntl_reg(self.clkfb_divider)), # CLKout Config
(0x11A, 0xCC), # CLKout Config
(0x11B, 0x00), # CLKout Config
(0x11C, 0x20), # CLKout Config
(0x11D, 0x00), # CLKout Config
(0x11E, 0xF1), # CLKout Config
(0x11F, 0x00), # CLKout Config
- (0x120, 0x78), # CLKout Config
- (0x121, 0xCC), # CLKout Config
+ (0x120, clkout_div_val), # CLKout Config
+ (0x121, clkout_cnt_val), # CLKout Config
(0x122, 0xCC), # CLKout Config
(0x123, 0x00), # CLKout Config
(0x124, 0x20), # CLKout Config 0x20 = SYSREF output, 0x00 = DEVCLK
(0x125, 0x00), # CLKout Config
(0x126, 0x72), # CLKout Config FPGA: (0xAB where A = SYSREF, B = CLK)
(0x127, 0x55), # CLKout Config 0x1 = LVDS, 0x5 = LVPECL
- (0x128, 0x78), # CLKout Config
- (0x129, 0xCC), # CLKout Config
+ (0x128, clkout_div_val), # CLKout Config
+ (0x129, clkout_cnt_val), # CLKout Config
(0x12A, 0xCC), # CLKout Config
(0x12B, 0x00), # CLKout Config
(0x12C, 0x00), # CLKout Config
(0x12D, 0x00), # CLKout Config
(0x12E, 0x72), # CLKout Config
(0x12F, 0xD0), # CLKout Config
- (0x130, 0x78), # CLKout Config
- (0x131, 0xCC), # CLKout Config
+ (0x130, clkout_div_val), # CLKout Config
+ (0x131, clkout_cnt_val), # CLKout Config
(0x132, 0xCC), # CLKout Config
(0x133, 0x00), # CLKout Config
(0x134, 0x20), # CLKout Config
@@ -115,8 +149,8 @@ class LMK04828Mg(LMK04828):
(0x137, 0x05), # CLKout Config
(0x138, 0x30), # VCO_MUX to VCO 1; OSCout off
(0x139, 0x00), # SYSREF Source = MUX; SYSREF MUX = Normal SYNC
- (0x13A, 0x01), # SYSREF Divide [12:8]
- (0x13B, 0xE0), # SYSREF Divide [7:0]
+ (0x13A, (self.sysref_divider & 0x1F00) >> 8), # SYSREF Divide [12:8]
+ (0x13B, (self.sysref_divider & 0x00FF) >> 0), # SYSREF Divide [7:0]
(0x13C, 0x00), # SYSREF DDLY [12:8]
(0x13D, 0x08), # SYSREF DDLY [7:0] ... 8 is default, <8 is reserved
(0x13E, 0x00), # SYSREF Pulse Count = 1 pulse/request
@@ -141,13 +175,13 @@ class LMK04828Mg(LMK04828):
(0x151, 0x02), # Holdover Settings (defaults)
(0x152, 0x00), # Holdover Settings (defaults)
(0x153, 0x00), # CLKin0_R divider [13:8], default = 0
- (0x154, clkin0_r_divider), # CLKin0_R divider [7:0], default = d120
- (0x155, 0x00), # CLKin1_R divider [13:8], default = 0
- (0x156, clkin0_r_divider), # CLKin1_R divider [7:0], default = d120
+ (0x154, 0x78), # CLKin0_R divider [7:0], default = d120
+ (0x155, (self.clkin_r_divider & 0x3F00) >> 8), # CLKin1_R divider [13:8], default = 0
+ (0x156, (self.clkin_r_divider & 0x00FF) >> 0), # CLKin1_R divider [7:0], default = d120
(0x157, 0x00), # CLKin2_R divider [13:8], default = 0
(0x158, 0x01), # CLKin2_R divider [7:0], default = d120
- (0x159, 0x00), # PLL1 N divider [13:8], default = 0
- (0x15A, 0x7D), # PLL1 N divider [7:0], default = d120
+ (0x159, (self.pll1_n_divider & 0x3F00) >> 8), # PLL1 N divider [13:8], default = 0
+ (0x15A, (self.pll1_n_divider & 0x00FF) >> 0), # PLL1 N divider [7:0], default = d120
(0x15B, 0xCF), # PLL1 PFD
(0x15C, 0x27), # PLL1 DLD Count [13:8]
(0x15D, 0x10), # PLL1 DLD Count [7:0]
@@ -155,7 +189,7 @@ class LMK04828Mg(LMK04828):
(0x15F, 0x0B), # Status LD1 pin = PLL1 LD, push-pull output
(0x160, 0x00), # PLL2 R divider [11:8];
(0x161, 0x04), # PLL2 R divider [7:0]
- (0x162, 0xA4), # PLL2 prescaler; OSCin freq
+ (0x162, self.pll2_pre_to_reg(self.pll2_prescaler)), # PLL2 prescaler; OSCin freq 0xA4
(0x163, 0x00), # PLL2 Cal = PLL2 normal val
(0x164, 0x00), # PLL2 Cal = PLL2 normal val
(0x165, 0x19), # PLL2 Cal = PLL2 normal val
@@ -163,9 +197,9 @@ class LMK04828Mg(LMK04828):
(0x172, 0x02), # Write this val after x165
(0x17C, 0x15), # VCo1 Cal; write before x168
(0x17D, 0x33), # VCo1 Cal; write before x168
- (0x166, 0x00), # PLL2 N[17:16]
- (0x167, 0x00), # PLL2 N[15:8]
- (0x168, 0x19), # PLL2 N[7:0]
+ (0x166, (self.pll2_n_divider & 0x030000) >> 16), # PLL2 N[17:16]
+ (0x167, (self.pll2_n_divider & 0x00FF00) >> 8), # PLL2 N[15:8]
+ (0x168, (self.pll2_n_divider & 0x0000FF) >> 0), # PLL2 N[7:0]
(0x169, 0x51), # PLL2 PFD
(0x16A, 0x27), # PLL2 DLD Count [13:8] = default d32
(0x16B, 0x10), # PLL2 DLD Count [7:0] = default d0
@@ -187,16 +221,14 @@ class LMK04828Mg(LMK04828):
(0x183, 0x1), # Clear Lock Detect Sticky
(0x183, 0x0), # Clear Lock Detect Sticky
))
- # Wait a bit before checking for lock
- # time.sleep(0.050)
if self.check_plls_locked():
locked = True
- self.log.info("LMK PLLs Locked!")
+ self.log.trace("PLLs are Locked!")
break
if not locked:
- raise RuntimeError("At least one LMK PLL did not lock! Check the logs for details.")
+ raise RuntimeError("At least one PLL did not lock! Check the logs for details.")
- self.log.trace("Setting SYNC and SYSREF config...")
+ self.log.trace("Synchronizing output dividers...")
self.pokes8((
(0x143, 0xF1), # toggle SYNC polarity to trigger SYNC event
(0x143, 0xD1), # toggle SYNC polarity to trigger SYNC event
@@ -204,17 +236,15 @@ class LMK04828Mg(LMK04828):
(0x144, 0xFF), # Disable SYNC on all outputs including sysref
(0x143, 0x52), # Pulser selected; SYNC enabled; 1 shot enabled
))
- self.log.info("LMK init'd and locked!")
+ self.log.info("Clocks Initialized and PLLs Locked!")
def lmk_shift(self, num_shifts=0):
"""
- Apply time shift
+ Apply time shift using the dynamic digital delays inside the LMK.
"""
- # TODO: these numbers need to be based off the radio clock freq.
- self.log.trace("LMK04828 Clock Phase Shifting Commencing...")
- ddly_value = 0xCD if num_shifts >= 0 else 0xCB
- ddly_value_sysref_reg0 = 0x01
- ddly_value_sysref_reg1 = 0xE1 if num_shifts >= 0 else 0xDF # 0xE0 is normal
+ self.log.trace("Clock Shifting Commencing using Dynamic Digital Delay...")
+ ddly_value = self.divide_to_cnth_cntl_reg(self.clkout_divider+1) if num_shifts >= 0 else self.divide_to_cnth_cntl_reg(self.clkout_divider-1)
+ ddly_value_sysref = self.sysref_divider+1 if num_shifts >= 0 else self.sysref_divider-1
self.pokes8((
(0x141, 0xB1), # Dynamic digital delay enable on outputs 0, 8, 10
(0x143, 0x53), # SYSREF_CLR; SYNC Enabled; SYNC from pulser @ regwrite
@@ -225,13 +255,12 @@ class LMK04828Mg(LMK04828):
(0x122, ddly_value), # Hidden register. Write the same as previous based on inc/dec.
(0x129, ddly_value), # Set DDLY values for DCLKout10 +/-1 on low cnt
(0x12A, ddly_value), # Hidden register. Write the same as previous based on inc/dec.
- (0x13C, ddly_value_sysref_reg0), # SYSREF DDLY value
- (0x13D, ddly_value_sysref_reg1), # SYSREF DDLY value
+ (0x13C, (ddly_value_sysref & 0x1F00) >> 8), # SYSREF DDLY value [12:8]
+ (0x13D, (ddly_value_sysref & 0x00FF) >> 0), # SYSREF DDLY value [7:0]
(0x144, 0x4E), # Enable SYNC on outputs 0, 8, 10
))
for x in range(abs(num_shifts)):
self.poke8(0x142, 0x1)
# Put everything back the way it was before shifting.
self.poke8(0x144, 0xFF) # Disable SYNC on all outputs including SYSREF
- # self.poke8(0x143, 0xD2) # Reset SYSREF engine to proper SYNC settings
self.poke8(0x143, 0x52) # Pulser selected; SYNC enabled; 1 shot enabled
diff --git a/mpm/python/usrp_mpm/dboard_manager/magnesium.py b/mpm/python/usrp_mpm/dboard_manager/magnesium.py
index 5b6b81598..6c100e992 100644
--- a/mpm/python/usrp_mpm/dboard_manager/magnesium.py
+++ b/mpm/python/usrp_mpm/dboard_manager/magnesium.py
@@ -161,7 +161,7 @@ class DboardClockControl(object):
"""
Uninitialize and reset the MMCM
"""
- self.log.trace("Disabling all Radio Clocks, then resetting MMCM...")
+ self.log.trace("Disabling all Radio Clocks, then resetting FPGA MMCM...")
self.enable_outputs(False)
self.poke32(self.RADIO_CLK_MMCM, 0x1)
@@ -171,16 +171,16 @@ class DboardClockControl(object):
If MMCM is not locked after unreset, an exception is thrown.
"""
- self.log.trace("Un-resetting MMCM...")
+ self.log.trace("Enabling FPGA Radio Clock MMCM...")
self.poke32(self.RADIO_CLK_MMCM, 0x2)
if not poll_with_timeout(
lambda: bool(self.peek32(self.RADIO_CLK_MMCM) & 0x10),
500,
10,
):
- self.log.error("MMCM not locked!")
- raise RuntimeError("MMCM not locked!")
- self.log.trace("MMCM locked. Enabling output MMCM clocks...")
+ self.log.error("FPGA Radio Clock MMCM not locked!")
+ raise RuntimeError("FPGA Radio Clock MMCM not locked!")
+ self.log.trace("FPGA Radio Clock MMCM locked. Enabling clocks to design...")
self.enable_outputs(True)
def check_refclk(self):
@@ -387,7 +387,7 @@ class Magnesium(DboardManagerBase):
self.eeprom_fs, self.eeprom_path = self._init_user_eeprom(
self.user_eeprom[self.rev]
)
- self.radio_regs = UIO(
+ self.dboard_ctrl_regs = UIO(
label="dboard-regs-{}".format(self.slot_idx),
read_only=False
)
@@ -397,7 +397,7 @@ class Magnesium(DboardManagerBase):
for key in self._spi_nodes
}
self.cpld = MgCPLD(self._spi_ifaces['cpld'], self.log)
- self.dboard_clk_control = DboardClockControl(self.radio_regs, self.log)
+ self.dboard_clk_control = DboardClockControl(self.dboard_ctrl_regs, self.log)
def _power_on(self):
" Turn on power to daughterboard "
@@ -472,7 +472,7 @@ class Magnesium(DboardManagerBase):
"""
Execute necessary init dance to bring up dboard
"""
- def _init_lmk(lmk_spi, ref_clk_freq,
+ def _init_lmk(lmk_spi, ref_clk_freq, master_clk_freq,
pdac_spi, init_phase_dac_word):
"""
Sets the phase DAC to initial value, and then brings up the LMK
@@ -483,7 +483,7 @@ class Magnesium(DboardManagerBase):
init_phase_dac_word
))
pdac_spi.poke16(0x0, init_phase_dac_word)
- return LMK04828Mg(lmk_spi, self.spi_lock, ref_clk_freq, self.log)
+ return LMK04828Mg(lmk_spi, self.spi_lock, ref_clk_freq, master_clk_freq, self.log)
def _sync_db_clock(synchronizer):
" Synchronizes the DB clock to the common reference "
synchronizer.run_sync(measurement_only=False)
@@ -508,35 +508,48 @@ class Magnesium(DboardManagerBase):
error_msg = "Cannot run init(), peripherals are not initialized!"
self.log.error(error_msg)
raise RuntimeError(error_msg)
+ if 'ref_clk_freq' in args:
+ self.ref_clock_freq = float(args['ref_clk_freq'])
+ assert self.ref_clock_freq in (10e6, 20e6, 25e6)
+ if 'master_clock_rate' in args:
+ self.master_clock_rate = float(args['master_clock_rate'])
+ assert self.master_clock_rate in (122.88e6, 125e6, 153.6e6)
+ self.log.trace("Creating jesdcore object")
+ self.jesdcore = nijesdcore.NIMgJESDCore(self.dboard_ctrl_regs, self.slot_idx)
+
+ self.log.info("Reset Daughterboard Clocking and JESD204B interfaces...")
self.dboard_clk_control.reset_mmcm()
+ self.jesdcore.reset()
+
self.lmk = _init_lmk(
self._spi_ifaces['lmk'],
self.ref_clock_freq,
+ self.master_clock_rate,
self._spi_ifaces['phase_dac'],
self.INIT_PHASE_DAC_WORD,
)
self.dboard_clk_control.enable_mmcm()
self.log.info("Sample Clocks and Phase DAC Configured Successfully!")
# Synchronize DB Clocks
+ # Future Work: target_value needs to be tweaked to support heterogenous rate sync.
+ self.target_value = {122.88e6: 128e-9, 125e6: 128e-9, 153.6e6: 122e-9}[self.master_clock_rate]
self.clock_synchronizer = ClockSynchronizer(
- self.radio_regs,
- self.dboard_clk_control,
+ self.dboard_ctrl_regs,
self.lmk,
self._spi_ifaces['phase_dac'],
- 0, # TODO this might not actually be zero
+ 0, # register offset value. future work.
self.master_clock_rate,
self.ref_clock_freq,
- 860E-15, # TODO don't hardcode. This should live in the EEPROM
+ 860E-15, # fine phase shift. TODO don't hardcode. This should live in the EEPROM
self.INIT_PHASE_DAC_WORD,
- 3e9, # lmk_vco_freq
- [128e-9,], # target_values
+ [self.target_value,], # target_values
0x0, # spi_addr TODO: make this a constant and replace in _sync_db_clock as well
self.log
)
_sync_db_clock(self.clock_synchronizer)
# Clocks and PPS are now fully active!
- self.init_jesd(self.radio_regs, args)
+ self.init_jesd(args)
self.mykonos.start_radio()
return True
@@ -571,15 +584,12 @@ class Magnesium(DboardManagerBase):
self.log.debug("args[tracking_cals]=0x{:02X}".format(self._tracking_cals_mask))
self.mykonos.setup_cal(self._init_cals_mask, self._tracking_cals_mask, self._init_cals_timeout)
- def init_jesd(self, uio, args):
+ def init_jesd(self, args):
"""
Bring up the JESD link between Mykonos and the N310.
+ All clocks must be set up and stable before starting this routine.
"""
- # CPLD Register Definition
- self.log.trace("Creating jesdcore object")
- self.jesdcore = nijesdcore.NIMgJESDCore(uio, self.slot_idx)
self.jesdcore.check_core()
- self.jesdcore.unreset_qpll()
self.jesdcore.init()
self.log.trace("Pulsing Mykonos Hard Reset...")
@@ -599,7 +609,7 @@ class Magnesium(DboardManagerBase):
self.mykonos.begin_initialization()
# Multi-chip Sync requires two SYSREF pulses at least 17us apart.
self.jesdcore.send_sysref_pulse()
- time.sleep(0.001)
+ time.sleep(0.001) # 17us... ish.
self.jesdcore.send_sysref_pulse()
self.mykonos.finish_initialization()
# TODO:can we call this after JESD?
@@ -618,7 +628,7 @@ class Magnesium(DboardManagerBase):
self.log.trace("Starting Mykonos framer...")
self.mykonos.start_jesd_tx()
self.log.trace("Enable FPGA SYSREF Receiver.")
- self.jesdcore.enable_lmfc()
+ self.jesdcore.enable_lmfc(True)
self.jesdcore.send_sysref_pulse()
self.log.trace("Starting FPGA deframer...")
self.jesdcore.init_deframer()
@@ -645,11 +655,11 @@ class Magnesium(DboardManagerBase):
def dump_jesd_core(self):
" Debug method to dump all JESD core regs "
- radio_regs = UIO(label="dboard-regs-{}".format(self.slot_idx))
+ dboard_ctrl_regs = UIO(label="dboard-regs-{}".format(self.slot_idx))
for i in range(0x2000, 0x2110, 0x10):
print(("0x%04X " % i), end=' ')
for j in range(0, 0x10, 0x4):
- print(("%08X" % radio_regs.peek32(i + j)), end=' ')
+ print(("%08X" % dboard_ctrl_regs.peek32(i + j)), end=' ')
print("")
def get_user_eeprom_data(self):
@@ -716,6 +726,16 @@ class Magnesium(DboardManagerBase):
" Return master clock rate (== sampling rate) "
return self.master_clock_rate
+ def update_ref_clock_freq(self, freq):
+ """
+ Call this function if the frequency of the reference clock changes (the
+ 10, 20, 25 MHz one).
+ """
+ self.log.info("Changing reference clock frequency to {} MHz".format(freq/1e6))
+ assert self.ref_clock_freq in (10e6, 20e6, 25e6)
+ self.ref_clock_freq = freq
+ # JEPSON FIXME call init() ? Maybe not yet!
+
##########################################################################
# Sensors
diff --git a/mpm/python/usrp_mpm/periph_manager/n310.py b/mpm/python/usrp_mpm/periph_manager/n310.py
index 08f6b8c22..e95e3446d 100644
--- a/mpm/python/usrp_mpm/periph_manager/n310.py
+++ b/mpm/python/usrp_mpm/periph_manager/n310.py
@@ -636,13 +636,16 @@ class n310(PeriphManagerBase):
def set_clock_source(self, *args):
"""
- Enable given external reference clock.
+ Switch reference clock.
Throws if clock_source is not a valid value.
"""
clock_source = args[0]
assert clock_source in self.get_clock_sources()
self.log.trace("Setting clock source to `{}'".format(clock_source))
+ if clock_source == self.get_clock_source():
+ self.log.trace("Nothing to do -- clock source already set.")
+ return
if clock_source == 'internal':
self._gpios.set("CLK-MAINSEL-EX_B")
self._gpios.set("CLK-MAINSEL-25MHz")
@@ -679,7 +682,20 @@ class n310(PeriphManagerBase):
assert freq in (10e6, 20e6, 25e6)
self.log.debug("We've been told the external reference clock " \
"frequency is {} MHz.".format(freq/1e6))
+ if self._ext_clk_freq == freq:
+ self.log.trace("New external reference clock frequency assignment matches "\
+ "previous assignment. Ignoring update command.")
+ return
self._ext_clock_freq = freq
+ if self.get_clock_source() == 'external':
+ for slot, dboard in enumerate(self.dboards):
+ if hasattr(dboard, 'update_ref_clock_freq'):
+ self.log.trace(
+ "Updating reference clock on dboard `{}' to {} MHz...".format(
+ slot, freq/1e6
+ )
+ )
+ dboard.update_ref_clock_freq(freq)
def get_ref_clock_freq(self):
" Returns the currently active reference clock frequency"
@@ -736,7 +752,7 @@ class n310(PeriphManagerBase):
"Enabling" if enable else "Disabling"
))
self._gpios.set("PWREN-CLK-MAINREF", int(bool(enable)))
-
+
def enable_1G_ref_clock(self):
"""
Enables 125 MHz refclock for 1G interface.