mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-16 21:10:10 +00:00
- Confirmed the Phase DAC to be initialized at mid-scale. - Confirmed the Phase DAC step resolution for fine clock shifting. The clock synchronization algorithm relies on the Phase DAC to fine shift the sampling clocks on each daughterboard. Only a certain number of DAC codes are required for the actual clock adjustment, thus a different range of codes may be chosen by initializing the Phase DAC with a given value. With the selected range, one may measure the Phase DAC's linearity and step resolution, which defines how many steps are required when performing the fine shifting of the clocks. After initializing the 16-bit Phase DAC at 25%, 50% (mid-scale), and 75%; it was found that the clock distribution PLL locks relatively faster when using mid-scale (2^15). By testing the Phase DAC's linearity, it was confirmed that the circuit resolution is 1.11 ps per code.
490 lines
23 KiB
Python
490 lines
23 KiB
Python
#
|
|
# Copyright 2018 Ettus Research, a National Instruments Company
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
"""
|
|
Helper class to initialize a Rhodium daughterboard
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
import time
|
|
from usrp_mpm.sys_utils.uio import UIO
|
|
from usrp_mpm.dboard_manager.lmk_rh import LMK04828Rh
|
|
from usrp_mpm.dboard_manager.rh_periphs import DboardClockControl
|
|
from usrp_mpm.cores import ClockSynchronizer
|
|
from usrp_mpm.cores import nijesdcore
|
|
from usrp_mpm.cores.eyescan import EyeScanTool
|
|
from usrp_mpm.dboard_manager.gain_rh import GainTableRh
|
|
|
|
|
|
class RhodiumInitManager(object):
|
|
"""
|
|
Helper class: Holds all the logic to initialize an N320/N321 (Rhodium)
|
|
daughterboard.
|
|
"""
|
|
# The Phase DAC is set at midscale, having its flatness validate +/- 1023 codes
|
|
# from this initial value.
|
|
INIT_PHASE_DAC_WORD = 32768
|
|
PHASE_DAC_SPI_ADDR = 0x3
|
|
# External PPS pipeline delay from the PPS captured at the FPGA to TDC input,
|
|
# in reference clock ticks
|
|
EXT_PPS_DELAY = 5
|
|
# Variable PPS delay before the RP/SP pulsers begin. Fixed value for the N3xx devices.
|
|
N3XX_INT_PPS_DELAY = 4
|
|
# JESD core default configuration.
|
|
JESD_DEFAULT_ARGS = {"lmfc_divider" : 12,
|
|
"rx_sysref_delay": 5,
|
|
"tx_sysref_delay": 11,
|
|
"tx_driver_swing": 0b1101,
|
|
"tx_precursor" : 0b00100,
|
|
"tx_postcursor" : 0b00100}
|
|
# After testing the roundtrip latency (i.e. FPGA -> TX -> RX -> FPGA),
|
|
# it was found that a different value of RX SYSREF delay is required
|
|
# for sampling_clock_rate = 400 MSPS to achieve latency consistency.
|
|
RX_SYSREF_DLY_DIC = {400e6: 6, 491.52e6: 5, 500e6: 5}
|
|
|
|
|
|
def __init__(self, rh_class, spi_ifaces):
|
|
self.rh_class = rh_class
|
|
self._spi_ifaces = spi_ifaces
|
|
self.adc = rh_class.adc
|
|
self.dac = rh_class.dac
|
|
self.slot_idx = rh_class.slot_idx
|
|
self.log = rh_class.log.getChild('init')
|
|
|
|
|
|
def _init_lmk(self, lmk_spi, ref_clk_freq, sampling_clock_rate,
|
|
pdac_spi, init_phase_dac_word, phase_dac_spi_addr):
|
|
"""
|
|
Sets the phase DAC to initial value, and then brings up the LMK
|
|
according to the selected ref clock frequency.
|
|
Will throw if something fails.
|
|
"""
|
|
self.log.trace("Initializing Phase DAC to d{}.".format(
|
|
init_phase_dac_word
|
|
))
|
|
pdac_spi.poke16(phase_dac_spi_addr, init_phase_dac_word)
|
|
return LMK04828Rh(self.slot_idx, lmk_spi, ref_clk_freq, sampling_clock_rate, self.log)
|
|
|
|
|
|
def _sync_db_clock(self, dboard_ctrl_regs, ref_clk_freq, master_clock_rate, args):
|
|
" Synchronizes the DB clock to the common reference "
|
|
reg_offset = 0x200
|
|
ext_pps_delay = self.EXT_PPS_DELAY
|
|
if args.get('time_source', self.rh_class.default_time_source) == 'sfp0':
|
|
reg_offset = 0x400
|
|
ref_clk_freq = 62.5e6
|
|
ext_pps_delay = 1 # only 1 flop between the WR core output and the TDC input
|
|
synchronizer = ClockSynchronizer(
|
|
dboard_ctrl_regs,
|
|
self.rh_class.lmk,
|
|
self._spi_ifaces['phase_dac'],
|
|
reg_offset,
|
|
master_clock_rate,
|
|
ref_clk_freq,
|
|
1.116E-12, # fine phase shift. TODO don't hardcode. This should live in the EEPROM
|
|
self.INIT_PHASE_DAC_WORD,
|
|
self.PHASE_DAC_SPI_ADDR,
|
|
ext_pps_delay,
|
|
self.N3XX_INT_PPS_DELAY,
|
|
self.slot_idx)
|
|
# The radio clock traces on the motherboard are 69 ps longer for Daughterboard B
|
|
# than Daughterboard A. We want both of these clocks to align at the converters
|
|
# on each board, so adjust the target value for DB B. This is an N3xx series
|
|
# peculiarity and will not apply to other motherboards.
|
|
trace_delay_offset = {0: 0.0e-0,
|
|
1: 69.0e-12}[self.slot_idx]
|
|
offset_error = abs(synchronizer.run(
|
|
num_meas=[512, 128],
|
|
target_offset=trace_delay_offset))
|
|
if offset_error > 100e-12:
|
|
self.log.error("Clock synchronizer measured an offset of {:.1f} ps!".format(
|
|
offset_error*1e12
|
|
))
|
|
self.log.warning("RuntimeError is not being thrown for Rhodium Rev. A")
|
|
# raise RuntimeError("Clock synchronizer measured an offset of {:.1f} ps!".format(
|
|
# offset_error*1e12
|
|
# ))
|
|
else:
|
|
self.log.debug("Residual synchronization error: {:.1f} ps.".format(
|
|
offset_error*1e12
|
|
))
|
|
synchronizer = None
|
|
self.log.debug("Sample Clock Synchronization Complete!")
|
|
|
|
|
|
def set_jesd_rate(self, jesdcore, new_rate, current_jesd_rate, force=False):
|
|
"""
|
|
Make the QPLL and GTX changes required to change the JESD204B core rate.
|
|
"""
|
|
# The core is directly compiled for 500 MHz sample rate, which
|
|
# corresponds to a lane rate of 5.0 Gbps. The same QPLL and GTX settings apply
|
|
# for the 491.52 MHz sample rate.
|
|
#
|
|
# The lower non-LTE rate, 400 MHz, requires changes to the default configuration
|
|
# of the MGT components. This function performs the required changes in the
|
|
# following order (as recommended by UG476).
|
|
#
|
|
# 1) Modify any QPLL settings.
|
|
# 2) Perform the QPLL reset routine by pulsing reset then waiting for lock.
|
|
# 3) Modify any GTX settings.
|
|
# 4) Perform the GTX reset routine by pulsing reset and waiting for reset done.
|
|
|
|
assert new_rate in (4000e6, 4915.2e6, 5000e6)
|
|
|
|
# On first run, we have no idea how the FPGA is configured... so let's force an
|
|
# update to our rate.
|
|
force = force or (current_jesd_rate is None)
|
|
|
|
skip_drp = False
|
|
if not force:
|
|
# Current New Skip?
|
|
skip_drp = {4000.0e6 : {4000.0e6: True , 4915.2e6: False, 5000.0e6: False},
|
|
4915.2e6 : {4000.0e6: False, 4915.2e6: True , 5000.0e6: True },
|
|
5000.0e6 : {4000.0e6: False, 4915.2e6: True , 5000.0e6: True }}[current_jesd_rate][new_rate]
|
|
|
|
if skip_drp:
|
|
self.log.trace("Current lane rate is compatible with the new rate. Skipping "
|
|
"reconfiguration.")
|
|
|
|
# These are the only registers in the QPLL and GTX that change based on the
|
|
# selected line rate. The MGT wizard IP was generated for each of the rates and
|
|
# reference clock frequencies and then diffed to create this table.
|
|
QPLL_CFG = {4000.0e6: 0x6801C1, 4915.2e6: 0x680181, 5000.0e6: 0x0680181}[new_rate]
|
|
MGT_RX_CLK25_DIV = {4000.0e6: 8, 4915.2e6: 10, 5000.0e6: 10}[new_rate]
|
|
MGT_TX_CLK25_DIV = {4000.0e6: 8, 4915.2e6: 10, 5000.0e6: 10}[new_rate]
|
|
|
|
# 1-2) Do the QPLL first
|
|
if not skip_drp:
|
|
self.log.trace("Changing QPLL settings to support {} Gbps".format(new_rate/1e9))
|
|
jesdcore.set_drp_target('qpll', 0)
|
|
# QPLL_CONFIG is spread across two regs: 0x32 (dedicated) and 0x33 (shared)
|
|
reg_x32 = QPLL_CFG & 0xFFFF # [16:0] -> [16:0]
|
|
reg_x33 = jesdcore.drp_access(rd=True, addr=0x33)
|
|
reg_x33 = (reg_x33 & 0xF800) | ((QPLL_CFG >> 16) & 0x7FF) # [26:16] -> [11:0]
|
|
jesdcore.drp_access(rd=False, addr=0x32, wr_data=reg_x32)
|
|
jesdcore.drp_access(rd=False, addr=0x33, wr_data=reg_x33)
|
|
|
|
# Run the QPLL reset sequence and prep the MGTs for modification.
|
|
jesdcore.init()
|
|
|
|
# 3-4) And the 4 MGTs second
|
|
if not skip_drp:
|
|
self.log.trace("Changing MGT settings to support {} Gbps"
|
|
.format(new_rate/1e9))
|
|
for lane in range(4):
|
|
jesdcore.set_drp_target('mgt', lane)
|
|
# MGT_RX_CLK25_DIV is embedded with others in 0x11. The
|
|
# encoding for the DRP register value is one less than the
|
|
# desired value.
|
|
reg_x11 = jesdcore.drp_access(rd=True, addr=0x11)
|
|
reg_x11 = (reg_x11 & 0xF83F) | \
|
|
((MGT_RX_CLK25_DIV-1 & 0x1F) << 6) # [10:6]
|
|
jesdcore.drp_access(rd=False, addr=0x11, wr_data=reg_x11)
|
|
# MGT_TX_CLK25_DIV is embedded with others in 0x6A. The
|
|
# encoding for the DRP register value is one less than the
|
|
# desired value.
|
|
reg_x6a = jesdcore.drp_access(rd=True, addr=0x6A)
|
|
reg_x6a = (reg_x6a & 0xFFE0) | (MGT_TX_CLK25_DIV-1 & 0x1F) # [4:0]
|
|
jesdcore.drp_access(rd=False, addr=0x6A, wr_data=reg_x6a)
|
|
self.log.trace("GTX settings changed to support {} Gbps"
|
|
.format(new_rate/1e9))
|
|
jesdcore.disable_drp_target()
|
|
|
|
self.log.trace("JESD204b Lane Rate set to {} Gbps!"
|
|
.format(new_rate/1e9))
|
|
return new_rate
|
|
|
|
|
|
def init_jesd(self, jesdcore, sampling_clock_rate):
|
|
"""
|
|
Bringup the JESD links between the ADC, DAC, and the FPGA.
|
|
All clocks must be set up and stable before starting this routine.
|
|
"""
|
|
jesdcore.check_core()
|
|
|
|
# JESD Lane Rate only depends on the sampling_clock_rate selection, since all
|
|
# other link parameters (LMFS,N) remain constant.
|
|
L = 4
|
|
M = 2
|
|
F = 1
|
|
S = 1
|
|
N = 16
|
|
new_rate = sampling_clock_rate * M * N * (10.0/8) / L / S
|
|
self.log.trace("Calculated JESD204B lane rate is {} Gbps".format(new_rate/1e9))
|
|
self.rh_class.current_jesd_rate = \
|
|
self.set_jesd_rate(jesdcore, new_rate, self.rh_class.current_jesd_rate)
|
|
|
|
self.log.trace("Setting up JESD204B TX blocks.")
|
|
jesdcore.init_framer() # Initialize FPGA's framer.
|
|
self.adc.init_framer() # Initialize ADC's framer.
|
|
|
|
self.log.trace("Enabling SYSREF capture blocks.")
|
|
self.dac.enable_sysref_capture(True) # Enable DAC's SYSREF capture.
|
|
self.adc.enable_sysref_capture(True) # Enable ADC's SYSREF capture.
|
|
jesdcore.enable_lmfc(True) # Enable FPGA's SYSREF capture.
|
|
|
|
self.log.trace("Setting up JESD204B DAC RX block.")
|
|
self.dac.init_deframer() # Initialize DAC's deframer.
|
|
|
|
self.log.trace("Sending SYSREF to all devices.")
|
|
jesdcore.send_sysref_pulse() # Send SYSREF to all devices.
|
|
|
|
self.log.trace("Setting up JESD204B FPGA RX block.")
|
|
jesdcore.init_deframer() # Initialize FPGA's deframer.
|
|
|
|
self.log.trace("Disabling SYSREF capture blocks.")
|
|
self.dac.enable_sysref_capture(False) # Disable DAC's SYSREF capture.
|
|
self.adc.enable_sysref_capture(False) # Disable ADC's SYSREF capture.
|
|
jesdcore.enable_lmfc(False) # Disable FPGA's SYSREF capture.
|
|
|
|
time.sleep(0.100) # Allow time for CGS/ILA.
|
|
|
|
self.log.trace("Verifying JESD204B link status.")
|
|
error_flag = False
|
|
if not jesdcore.get_framer_status():
|
|
self.log.error("JESD204b FPGA Core Framer is not synced!")
|
|
error_flag = True
|
|
if not self.dac.check_deframer_status():
|
|
self.log.error("DAC JESD204B Deframer is not synced!")
|
|
error_flag = True
|
|
if not self.adc.check_framer_status():
|
|
self.log.error("ADC JESD204B Framer is not synced!")
|
|
error_flag = True
|
|
if not jesdcore.get_deframer_status():
|
|
self.log.error("JESD204B FPGA Core Deframer is not synced!")
|
|
error_flag = True
|
|
if error_flag:
|
|
raise RuntimeError('JESD204B Link Initialization Failed. See MPM logs for details.')
|
|
self.log.info("JESD204B Link Initialization & Training Complete")
|
|
|
|
|
|
def init(self, args):
|
|
"""
|
|
Run the full initialization sequence. This will bring everything up
|
|
from scratch: The LMK, JESD cores, the AD9695, the DAC37J82, and
|
|
anything else that is clocking-related.
|
|
Depending on the settings, this can take a fair amount of time.
|
|
"""
|
|
# Input validation on RX margin tests (@ FPGA and DAC)
|
|
# By accepting the rx_eyescan/tx_prbs argument being str or bool, one may
|
|
# request an eyescan measurement to be performed from either the USRP's
|
|
# shell (i.e. using --default-args) or from the host's MPM shell.
|
|
perform_rx_eyescan = False
|
|
if 'rx_eyescan' in args:
|
|
perform_rx_eyescan = (args['rx_eyescan'] == 'True') or (args['rx_eyescan'] == True)
|
|
if perform_rx_eyescan:
|
|
self.log.trace("Adding RX eye scan PMA enable to JESD args.")
|
|
self.JESD_DEFAULT_ARGS["enable_rx_eyescan"] = True
|
|
perform_tx_prbs = False
|
|
if 'tx_prbs' in args:
|
|
perform_tx_prbs = (args['tx_prbs'] == 'True') or (args['tx_prbs'] == True)
|
|
|
|
# Latency across the JESD204B TX/RX links should remain constant and
|
|
# deterministic across the supported sampling_clock_rate values.
|
|
# After testing the roundtrip latency (i.e. FPGA -> TX -> RX -> FPGA),
|
|
# it was found that a different set of SYSREF delay values are required
|
|
# for sampling_clock_rate = 400 MSPS to achieve latency consistency.
|
|
self.JESD_DEFAULT_ARGS['rx_sysref_delay'] = \
|
|
self.RX_SYSREF_DLY_DIC[self.rh_class.sampling_clock_rate]
|
|
|
|
# Bringup Sequence.
|
|
# 1. Prerequisites (include opening mmaps)
|
|
# 2. Initialize LMK and bringup clocks.
|
|
# 3. Synchronize DB Clocks.
|
|
# 4. Initialize FPGA JESD IP.
|
|
# 5. DAC Configuration.
|
|
# 6. ADC Configuration.
|
|
# 7. JESD204B Initialization.
|
|
# 8. CPLD Gain Tables Initialization.
|
|
|
|
# 1. Prerequisites
|
|
# Open FPGA IP (Clock control and JESD core).
|
|
self.log.trace("Creating dboard clock control object")
|
|
db_clk_control = DboardClockControl(self.rh_class.radio_regs, self.log)
|
|
self.log.trace("Creating jesdcore object")
|
|
jesdcore = nijesdcore.NIJESDCore(self.rh_class.radio_regs, self.rh_class.slot_idx, **self.JESD_DEFAULT_ARGS)
|
|
|
|
self.log.trace("Creating gain table object...")
|
|
self.gain_table_loader = GainTableRh(
|
|
self._spi_ifaces['cpld'],
|
|
self._spi_ifaces['cpld_gain_loader'],
|
|
self.log)
|
|
|
|
# 2. Initialize LMK and bringup clocks.
|
|
# Disable FPGA MMCM's outputs, and assert its reset.
|
|
db_clk_control.reset_mmcm()
|
|
# Always place the JESD204b cores in reset before modifying the clocks,
|
|
# otherwise high power or erroneous conditions could exist in the FPGA!
|
|
jesdcore.reset()
|
|
# Configure and bringup the LMK's clocks.
|
|
self.log.trace("Initializing LMK...")
|
|
self.rh_class.lmk = self._init_lmk(
|
|
self._spi_ifaces['lmk'],
|
|
self.rh_class.ref_clock_freq,
|
|
self.rh_class.sampling_clock_rate,
|
|
self._spi_ifaces['phase_dac'],
|
|
self.INIT_PHASE_DAC_WORD,
|
|
self.PHASE_DAC_SPI_ADDR
|
|
)
|
|
self.log.trace("LMK Initialized!")
|
|
# Deassert FPGA's MMCM reset, poll for lock, and enable outputs.
|
|
db_clk_control.enable_mmcm()
|
|
|
|
# 3. Synchronize DB Clocks.
|
|
# The clock synchronzation driver receives the master_clock_rate, which for
|
|
# Rhodium is half the sampling_clock_rate.
|
|
self._sync_db_clock(
|
|
self.rh_class.radio_regs,
|
|
self.rh_class.ref_clock_freq,
|
|
self.rh_class.sampling_clock_rate / 2,
|
|
args)
|
|
|
|
# 4. DAC Configuration.
|
|
self.dac.config()
|
|
|
|
# 5. ADC Configuration.
|
|
self.adc.config()
|
|
|
|
# 6-7. JESD204B Initialization.
|
|
self.init_jesd(jesdcore, self.rh_class.sampling_clock_rate)
|
|
# [Optional] Perform RX eyescan.
|
|
if perform_rx_eyescan:
|
|
self.log.info("Performing RX eye scan on ADC to FPGA link...")
|
|
self._rx_eyescan(jesdcore, args)
|
|
# [Optional] Perform TX PRBS test.
|
|
if perform_tx_prbs:
|
|
self.log.info("Performing TX PRBS-31 test on FPGA to DAC link...")
|
|
self._tx_prbs_test(jesdcore, args)
|
|
jesdcore = None # We are done using the jesdcore at this point.
|
|
|
|
# 8. CPLD Gain Tables Initialization.
|
|
self.gain_table_loader.init()
|
|
|
|
return True
|
|
|
|
|
|
##########################################################################
|
|
# JESD204B RX margin testing
|
|
##########################################################################
|
|
|
|
def _rx_eyescan(self, jesdcore, args):
|
|
"""
|
|
This function creates an eyescan object to perform this measurement with the
|
|
given configuration and lanes.
|
|
|
|
Parameters:
|
|
prescale -> Controls the prescaling of the sample count to keep both sample
|
|
count and error count in reasonable precision.
|
|
Valid values: from 0 to 31.
|
|
"""
|
|
# The following constants must be defined according to GTs configuration
|
|
# for each project. For further details, refer to the eyescan.py file.
|
|
# For Rhodium, these parameters are based on the JESD core.
|
|
rxout_div = 2
|
|
rx_int_datawidth = 20
|
|
eq_mode = 'LPM'
|
|
# The following variables define the GTs to be scanned and the range of the
|
|
# measurement.
|
|
prescale = 0
|
|
scan_lanes = [0, 1, 2, 3]
|
|
hor_range = {'start':-32 , 'stop':32 , 'step': 2}
|
|
ver_range = {'start':-127, 'stop':127, 'step': 2}
|
|
# Set default configuration values for Rhodium when the user is not intentionally
|
|
# changing the constants/variables default values.
|
|
for key in ('rxout_div', 'rx_int_datawidth', 'eq_mode',
|
|
'prescale', 'scan_lanes', 'hor_range', 'ver_range'):
|
|
if key not in args:
|
|
self.log.trace("Setting Rh default value for {0}... val: {1}"
|
|
.format(key, locals()[key]))
|
|
args[key] = locals()[key]
|
|
#
|
|
# Create an eyescan object.
|
|
assert jesdcore is not None
|
|
eyescan_tool = EyeScanTool(jesdcore, self.slot_idx, **args)
|
|
# Put the ADC in pseudorandom test mode.
|
|
adc_regs = self._spi_ifaces['adc']
|
|
# test_val = adc_regs.peek8(0x0550)
|
|
# adc_regs.poke8(0x0550, 0x05)
|
|
test_val = adc_regs.peek8(0x0573)
|
|
adc_regs.poke8(0x0573, 0x13)
|
|
# Perform eye scan on given lanes and range.
|
|
file_name = eyescan_tool.eyescan_full_scan(args['scan_lanes'],
|
|
args['hor_range'], args['ver_range'])
|
|
# Do some housekeeping...
|
|
# adc_regs.poke8(0x0550, test_val) # Enable normal operation.
|
|
adc_regs.poke8(0x0573, test_val) # Enable normal operation.
|
|
adc_regs.poke8(0x0000, 0x81) # Reset.
|
|
eyescan_tool = None
|
|
return file_name
|
|
|
|
def _tx_prbs_test(self, jesdcore, args):
|
|
"""
|
|
This function allows to test the PRBS-31 pattern at the DAC.
|
|
"""
|
|
def _test_lanes(**tx_settings):
|
|
"""
|
|
This methods enables, monitors, and disables the PRBS-31 test.
|
|
"""
|
|
results = []
|
|
jesdcore.adjust_tx_phy(**tx_phy_settings)
|
|
self.log.info("Testing TX PHY settings: tx_driver_swing=0b{0:04b}"
|
|
" tx_precursor=0b{1:05b}"
|
|
" tx_postcursor=0b{2:05b}"
|
|
.format(tx_phy_settings["tx_driver_swing"],
|
|
tx_phy_settings["tx_precursor"],
|
|
tx_phy_settings["tx_postcursor"]))
|
|
# Enable the GTs TX pattern generator in PRBS-31 mode.
|
|
jesdcore.set_pattern_gen(mode='PRBS-31')
|
|
# Monitor each receive lane at DAC.
|
|
for lane_num in range(0, 4):
|
|
self.dac.test_mode(mode='PRBS-31', lane=lane_num) # Enable PRBS test mode.
|
|
number_of_failures = 0
|
|
for _ in range(0, POLLS_PER_GT):
|
|
time.sleep(WAIT_TIME_PER_POLL)
|
|
alarm_pin_dac = self.rh_class.cpld.get_dac_alarm()
|
|
if alarm_pin_dac:
|
|
number_of_failures += 1
|
|
results.append(number_of_failures)
|
|
if number_of_failures > 0:
|
|
self.log.error("PRBS-31 test for DAC lane {0} failed {1}/{2}!"
|
|
.format(lane_num, number_of_failures, POLLS_PER_GT))
|
|
else:
|
|
self.log.info("PRBS-31 test for DAC lane {0} passed!"
|
|
.format(lane_num))
|
|
self.dac.test_mode(mode='OFF', lane=lane_num) # Disable PRBS test mode.
|
|
# Disable TX pattern generator at FPGA
|
|
jesdcore.set_pattern_gen(mode='OFF')
|
|
return results
|
|
#
|
|
WAIT_TIME_PER_POLL = 0.001 # in seconds.
|
|
POLLS_PER_GT = 100
|
|
# Create the CSV file.
|
|
f = open('tx_prbs_sweep.csv', 'w')
|
|
f.write("Swing,Precursor,Postcursor,Polls,Failures 0,Failures 1,Failures 2,Failures 3\n")
|
|
# Default TX PHY settings.
|
|
tx_phy_settings = {"tx_driver_swing": 0b1111, # See UG476, TXDIFFCTRL
|
|
"tx_precursor" : 0b00000, # See UG476, TXPRECURSOR
|
|
"tx_postcursor" : 0b00000} # See UG476, TXPOSTCURSOR
|
|
# Define sweep ranges.
|
|
DEFAULT_SWING_RANGE = {'start': 0b0000, 'stop': 0b1111 + 0b1, 'step': 1}
|
|
DEFAULT_CURSOR_RANGE = {'start': 0b00000, 'stop': 0b11111 + 0b1, 'step': 2}
|
|
swing_range = args.get("swing_range", DEFAULT_SWING_RANGE)
|
|
precursor_range = args.get("precursor_range", DEFAULT_CURSOR_RANGE)
|
|
postcursor_range = args.get("postcursor_range", DEFAULT_CURSOR_RANGE)
|
|
# Test the TX margin across multiple PHY settings.
|
|
for swing in range(swing_range['start'], swing_range['stop'], swing_range['step']):
|
|
tx_phy_settings["tx_driver_swing"] = swing
|
|
for precursor in range(precursor_range['start'], precursor_range['stop'], precursor_range['step']):
|
|
tx_phy_settings["tx_precursor"] = precursor
|
|
for postcursor in range(postcursor_range['start'], postcursor_range['stop'], postcursor_range['step']):
|
|
tx_phy_settings["tx_postcursor"] = postcursor
|
|
results = _test_lanes(**tx_phy_settings)
|
|
f.write("{},{},{},{},{},{},{},{}\n".format(
|
|
tx_phy_settings["tx_driver_swing"],
|
|
tx_phy_settings["tx_precursor"],
|
|
tx_phy_settings["tx_postcursor"],
|
|
POLLS_PER_GT, results[0], results[1], results[2], results[3]))
|
|
# Housekeeping...
|
|
f.close()
|