2
0
mirror of https://github.com/m-labs/artiq.git synced 2024-12-18 16:06:30 +08:00

Merge pull request #1533 from m-labs/phaser

Phaser
This commit is contained in:
Robert Jördens 2020-10-19 09:30:12 +02:00 committed by GitHub
commit a9dd0a268c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1849 additions and 144 deletions

View File

@ -0,0 +1,276 @@
class DAC34H84:
"""DAC34H84 settings and register map.
For possible values, documentation, and explanation, see the DAC datasheet
at https://www.ti.com/lit/pdf/slas751
"""
qmc_corr_ena = 0 # msb ab
qmc_offset_ena = 0 # msb ab
invsinc_ena = 0 # msb ab
interpolation = 1 # 2x
fifo_ena = 1
alarm_out_ena = 1
alarm_out_pol = 1
clkdiv_sync_ena = 1
iotest_ena = 0
cnt64_ena = 0
oddeven_parity = 0 # even
single_parity_ena = 1
dual_parity_ena = 0
rev_interface = 0
dac_complement = 0b0000 # msb A
alarm_fifo = 0b111 # msb 2-away
dacclkgone_ena = 1
dataclkgone_ena = 1
collisiongone_ena = 1
sif4_ena = 1
mixer_ena = 0
mixer_gain = 1
nco_ena = 0
revbus = 0
twos = 1
coarse_dac = 9 # 18.75 mA, 0-15
sif_txenable = 0
mask_alarm_from_zerochk = 0
mask_alarm_fifo_collision = 0
mask_alarm_fifo_1away = 0
mask_alarm_fifo_2away = 0
mask_alarm_dacclk_gone = 0
mask_alarm_dataclk_gone = 0
mask_alarm_output_gone = 0
mask_alarm_from_iotest = 0
mask_alarm_from_pll = 0
mask_alarm_parity = 0b0000 # msb a
qmc_offseta = 0 # 12b
fifo_offset = 2 # 0-7
qmc_offsetb = 0 # 12b
qmc_offsetc = 0 # 12b
qmc_offsetd = 0 # 12b
qmc_gaina = 0 # 11b
cmix_fs8 = 0
cmix_fs4 = 0
cmix_fs2 = 0
cmix_nfs4 = 0
qmc_gainb = 0 # 11b
qmc_gainc = 0 # 11b
output_delayab = 0b00
output_delaycd = 0b00
qmc_gaind = 0 # 11b
qmc_phaseab = 0 # 12b
qmc_phasecd = 0 # 12b
phase_offsetab = 0 # 16b
phase_offsetcd = 0 # 16b
phase_addab_lsb = 0 # 16b
phase_addab_msb = 0 # 16b
phase_addcd_lsb = 0 # 16b
phase_addcd_msb = 0 # 16b
pll_reset = 0
pll_ndivsync_ena = 1
pll_ena = 1
pll_cp = 0b01 # single charge pump
pll_p = 0b100 # p=4
pll_m2 = 1 # x2
pll_m = 8 # m = 8
pll_n = 0b0001 # n = 2
pll_vcotune = 0b01
pll_vco = 0x3f # 4 GHz
bias_sleep = 0
tsense_sleep = 0
pll_sleep = 0
clkrecv_sleep = 0
dac_sleep = 0b0000 # msb a
extref_ena = 0
fuse_sleep = 1
atest = 0b00000 # atest mode
syncsel_qmcoffsetab = 0b1001 # sif_sync and register write
syncsel_qmcoffsetcd = 0b1001 # sif_sync and register write
syncsel_qmccorrab = 0b1001 # sif_sync and register write
syncsel_qmccorrcd = 0b1001 # sif_sync and register write
syncsel_mixerab = 0b1001 # sif_sync and register write
syncsel_mixercd = 0b1001 # sif_sync and register write
syncsel_nco = 0b1000 # sif_sync
syncsel_fifo_input = 0b10 # external lvds istr
sif_sync = 1
syncsel_fifoin = 0b0010 # istr
syncsel_fifoout = 0b0100 # ostr
clkdiv_sync_sel = 0 # ostr
path_a_sel = 0
path_b_sel = 1
path_c_sel = 2
path_d_sel = 3
# swap dac pairs (CDAB) for layout
# swap I-Q dacs for spectral inversion
dac_a_sel = 3
dac_b_sel = 2
dac_c_sel = 1
dac_d_sel = 0
dac_sleep_en = 0b1111 # msb a
clkrecv_sleep_en = 1
pll_sleep_en = 1
lvds_data_sleep_en = 1
lvds_control_sleep_en = 1
temp_sense_sleep_en = 1
bias_sleep_en = 1
data_dly = 2
clk_dly = 0
ostrtodig_sel = 0
ramp_ena = 0
sifdac_ena = 0
grp_delaya = 0x00
grp_delayb = 0x00
grp_delayc = 0x00
grp_delayd = 0x00
sifdac = 0
def __init__(self, updates=None):
if updates is None:
return
for key, value in updates.items():
if not hasattr(self, key):
raise KeyError("invalid setting", key)
setattr(self, key, value)
def get_mmap(self):
mmap = []
mmap.append(
(0x00 << 16) |
(self.qmc_offset_ena << 14) | (self.qmc_corr_ena << 12) |
(self.interpolation << 8) | (self.fifo_ena << 7) |
(self.alarm_out_ena << 4) | (self.alarm_out_pol << 3) |
(self.clkdiv_sync_ena << 2) | (self.invsinc_ena << 0))
mmap.append(
(0x01 << 16) |
(self.iotest_ena << 15) | (self.cnt64_ena << 12) |
(self.oddeven_parity << 11) | (self.single_parity_ena << 10) |
(self.dual_parity_ena << 9) | (self.rev_interface << 8) |
(self.dac_complement << 4) | (self.alarm_fifo << 1))
mmap.append(
(0x02 << 16) |
(self.dacclkgone_ena << 14) | (self.dataclkgone_ena << 13) |
(self.collisiongone_ena << 12) | (self.sif4_ena << 7) |
(self.mixer_ena << 6) | (self.mixer_gain << 5) |
(self.nco_ena << 4) | (self.revbus << 3) | (self.twos << 1))
mmap.append((0x03 << 16) | (self.coarse_dac << 12) | (self.sif_txenable << 0))
mmap.append(
(0x07 << 16) |
(self.mask_alarm_from_zerochk << 15) | (1 << 14) |
(self.mask_alarm_fifo_collision << 13) |
(self.mask_alarm_fifo_1away << 12) |
(self.mask_alarm_fifo_2away << 11) |
(self.mask_alarm_dacclk_gone << 10) |
(self.mask_alarm_dataclk_gone << 9) |
(self.mask_alarm_output_gone << 8) |
(self.mask_alarm_from_iotest << 7) | (1 << 6) |
(self.mask_alarm_from_pll << 5) | (self.mask_alarm_parity << 1))
mmap.append(
(0x08 << 16) | (self.qmc_offseta << 0))
mmap.append(
(0x09 << 16) | (self.fifo_offset << 13) | (self.qmc_offsetb << 0))
mmap.append((0x0a << 16) | (self.qmc_offsetc << 0))
mmap.append((0x0b << 16) | (self.qmc_offsetd << 0))
mmap.append((0x0c << 16) | (self.qmc_gaina << 0))
mmap.append(
(0x0d << 16) |
(self.cmix_fs8 << 15) | (self.cmix_fs4 << 14) |
(self.cmix_fs2 << 12) | (self.cmix_nfs4 << 11) |
(self.qmc_gainb << 0))
mmap.append((0x0e << 16) | (self.qmc_gainc << 0))
mmap.append(
(0x0f << 16) |
(self.output_delayab << 14) | (self.output_delaycd << 12) |
(self.qmc_gaind << 0))
mmap.append((0x10 << 16) | (self.qmc_phaseab << 0))
mmap.append((0x11 << 16) | (self.qmc_phasecd << 0))
mmap.append((0x12 << 16) | (self.phase_offsetab << 0))
mmap.append((0x13 << 16) | (self.phase_offsetcd << 0))
mmap.append((0x14 << 16) | (self.phase_addab_lsb << 0))
mmap.append((0x15 << 16) | (self.phase_addab_msb << 0))
mmap.append((0x16 << 16) | (self.phase_addcd_lsb << 0))
mmap.append((0x17 << 16) | (self.phase_addcd_msb << 0))
mmap.append(
(0x18 << 16) |
(0b001 << 13) | (self.pll_reset << 12) |
(self.pll_ndivsync_ena << 11) | (self.pll_ena << 10) |
(self.pll_cp << 6) | (self.pll_p << 3))
mmap.append(
(0x19 << 16) |
(self.pll_m2 << 15) | (self.pll_m << 8) | (self.pll_n << 4) |
(self.pll_vcotune << 2))
mmap.append(
(0x1a << 16) |
(self.pll_vco << 10) | (self.bias_sleep << 7) |
(self.tsense_sleep << 6) |
(self.pll_sleep << 5) | (self.clkrecv_sleep << 4) |
(self.dac_sleep << 0))
mmap.append(
(0x1b << 16) |
(self.extref_ena << 15) | (self.fuse_sleep << 11) |
(self.atest << 0))
mmap.append(
(0x1e << 16) |
(self.syncsel_qmcoffsetab << 12) |
(self.syncsel_qmcoffsetcd << 8) |
(self.syncsel_qmccorrab << 4) |
(self.syncsel_qmccorrcd << 0))
mmap.append(
(0x1f << 16) |
(self.syncsel_mixerab << 12) | (self.syncsel_mixercd << 8) |
(self.syncsel_nco << 4) | (self.syncsel_fifo_input << 2) |
(self.sif_sync << 1))
mmap.append(
(0x20 << 16) |
(self.syncsel_fifoin << 12) | (self.syncsel_fifoout << 8) |
(self.clkdiv_sync_sel << 0))
mmap.append(
(0x22 << 16) |
(self.path_a_sel << 14) | (self.path_b_sel << 12) |
(self.path_c_sel << 10) | (self.path_d_sel << 8) |
(self.dac_a_sel << 6) | (self.dac_b_sel << 4) |
(self.dac_c_sel << 2) | (self.dac_d_sel << 0))
mmap.append(
(0x23 << 16) |
(self.dac_sleep_en << 12) | (self.clkrecv_sleep_en << 11) |
(self.pll_sleep_en << 10) | (self.lvds_data_sleep_en << 9) |
(self.lvds_control_sleep_en << 8) |
(self.temp_sense_sleep_en << 7) | (1 << 6) |
(self.bias_sleep_en << 5) | (0x1f << 0))
mmap.append(
(0x24 << 16) | (self.data_dly << 13) | (self.clk_dly << 10))
mmap.append(
(0x2d << 16) |
(self.ostrtodig_sel << 14) | (self.ramp_ena << 13) |
(0x002 << 1) | (self.sifdac_ena << 0))
mmap.append(
(0x2e << 16) | (self.grp_delaya << 8) | (self.grp_delayb << 0))
mmap.append(
(0x2f << 16) | (self.grp_delayc << 8) | (self.grp_delayd << 0))
mmap.append((0x30 << 16) | self.sifdac)
return mmap

945
artiq/coredevice/phaser.py Normal file
View File

@ -0,0 +1,945 @@
from artiq.language.core import kernel, delay_mu, delay
from artiq.coredevice.rtio import rtio_output, rtio_input_data
from artiq.language.units import us, ns, ms, MHz, dB
from artiq.language.types import TInt32
from artiq.coredevice.dac34h84 import DAC34H84
from artiq.coredevice.trf372017 import TRF372017
PHASER_BOARD_ID = 19
PHASER_ADDR_BOARD_ID = 0x00
PHASER_ADDR_HW_REV = 0x01
PHASER_ADDR_GW_REV = 0x02
PHASER_ADDR_CFG = 0x03
PHASER_ADDR_STA = 0x04
PHASER_ADDR_CRC_ERR = 0x05
PHASER_ADDR_LED = 0x06
PHASER_ADDR_FAN = 0x07
PHASER_ADDR_DUC_STB = 0x08
PHASER_ADDR_ADC_CFG = 0x09
PHASER_ADDR_SPI_CFG = 0x0a
PHASER_ADDR_SPI_DIVLEN = 0x0b
PHASER_ADDR_SPI_SEL = 0x0c
PHASER_ADDR_SPI_DATW = 0x0d
PHASER_ADDR_SPI_DATR = 0x0e
PHASER_ADDR_SYNC_DLY = 0x0f
PHASER_ADDR_DUC0_CFG = 0x10
# PHASER_ADDR_DUC0_RESERVED0 = 0x11
PHASER_ADDR_DUC0_F = 0x12
PHASER_ADDR_DUC0_P = 0x16
PHASER_ADDR_DAC0_DATA = 0x18
PHASER_ADDR_DAC0_TEST = 0x1c
PHASER_ADDR_DUC1_CFG = 0x20
# PHASER_ADDR_DUC1_RESERVED0 = 0x21
PHASER_ADDR_DUC1_F = 0x22
PHASER_ADDR_DUC1_P = 0x26
PHASER_ADDR_DAC1_DATA = 0x28
PHASER_ADDR_DAC1_TEST = 0x2c
PHASER_SEL_DAC = 1 << 0
PHASER_SEL_TRF0 = 1 << 1
PHASER_SEL_TRF1 = 1 << 2
PHASER_SEL_ATT0 = 1 << 3
PHASER_SEL_ATT1 = 1 << 4
PHASER_STA_DAC_ALARM = 1 << 0
PHASER_STA_TRF0_LD = 1 << 1
PHASER_STA_TRF1_LD = 1 << 2
PHASER_STA_TERM0 = 1 << 3
PHASER_STA_TERM1 = 1 << 4
PHASER_STA_SPI_IDLE = 1 << 5
PHASER_DAC_SEL_DUC = 0
PHASER_DAC_SEL_TEST = 1
PHASER_HW_REV_VARIANT = 1 << 4
class Phaser:
"""Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver.
Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion,
quadrature modulation compensation and interpolation features.
The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25
MS/s and 14 bit per quadrature. Each data stream supports 5 independent
numerically controlled IQ oscillators (NCOs, DDSs with 32 bit frequency, 16
bit phase, 15 bit amplitude, and phase accumulator clear functionality)
added together. See :class:`PhaserChannel` and :class:`PhaserOscillator`.
Together with a data clock, framing marker, a checksum and metadata for
register access the streams are sent in groups of 8 samples over 1.5 Gb/s
FastLink via a single EEM connector from coredevice to Phaser.
On Phaser in the FPGA the data streams are buffered and interpolated
from 25 MS/s to 500 MS/s 16 bit followed by a 500 MS/s digital upconverter
with adjustable frequency and phase. The interpolation passband is 20 MHz
wide, passband ripple is less than 1e-3 amplitude, stopband attenuation
is better than 75 dB at offsets > 15 MHz and better than 90 dB at offsets
> 30 MHz.
The four 16 bit 500 MS/s DAC data streams are sent via a 32 bit parallel
LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC (Texas
Instruments DAC34H84). On the DAC 2x interpolation, sinx/x compensation,
quadrature modulator compensation, fine and coarse mixing as well as group
delay capabilities are available.
The latency/group delay from the RTIO events setting
:class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all they
way to the DAC outputs is deterministic. This enables deterministic
absolute phase with respect to other RTIO input and output events.
The four analog DAC outputs are passed through anti-aliasing filters.
In the baseband variant, the even/in-phase DAC channels feed 31.5 dB range
attenuators and are available on the front panel. The odd outputs are
available at MMCX connectors on board.
In the upconverter variant, each IQ output pair feeds one quadrature
upconverter (Texas Instruments TRF372017) with integrated PLL/VCO. This
digitally configured analog quadrature upconverter supports offset tuning
for carrier and sideband suppression. The output from the upconverter
passes through the 31.5 dB range step attenuator and is available at the
front panel.
The DAC, the analog quadrature upconverters and the attenuators are
configured through a shared SPI bus that is accessed and controlled via
FPGA registers.
.. note:: Various register settings of the DAC and the quadrature
upconverters are available to be modified through the `dac`, `trf0`,
`trf1` dictionaries. These can be set through the device database
(`device_db.py`). The settings are frozen during instantiation of the
class and applied during `init()`. See the :class:`DAC34H84` and
:class:`TRF372017` source for details.
.. note:: To establish deterministic latency between RTIO time base and DAC
output, the DAC FIFO read pointer value (`fifo_offset`) must be
fixed. If `tune_fifo_offset=True` (the default) a value with maximum
margin is determined automatically by `dac_tune_fifo_offset` each time
`init()` is called. This value should be used for the `fifo_offset` key
of the `dac` settings of Phaser in `device_db.py` and automatic
tuning should be disabled by `tune_fifo_offset=False`.
:param channel: Base RTIO channel number
:param core_device: Core device name (default: "core")
:param miso_delay: Fastlink MISO signal delay to account for cable
and buffer round trip. Tuning this might be automated later.
:param tune_fifo_offset: Tune the DAC FIFO read pointer offset
(default=True)
:param clk_sel: Select the external SMA clock input (1 or 0)
:param sync_dly: SYNC delay with respect to ISTR.
:param dac: DAC34H84 DAC settings as a dictionary.
:param trf0: Channel 0 TRF372017 quadrature upconverter settings as a
dictionary.
:param trf1: Channel 1 TRF372017 quadrature upconverter settings as a
dictionary.
Attributes:
* :attr:`channel`: List of two :class:`PhaserChannel`
To access oscillators, digital upconverters, PLL/VCO analog
quadrature upconverters and attenuators.
"""
kernel_invariants = {"core", "channel_base", "t_frame", "miso_delay",
"dac_mmap"}
def __init__(self, dmgr, channel_base, miso_delay=1, tune_fifo_offset=True,
clk_sel=0, sync_dly=0, dac=None, trf0=None, trf1=None,
core_device="core"):
self.channel_base = channel_base
self.core = dmgr.get(core_device)
# TODO: auto-align miso-delay in phy
self.miso_delay = miso_delay
# frame duration in mu (10 words, 8 clock cycles each 4 ns)
# self.core.seconds_to_mu(10*8*4*ns) # unfortunately this returns 319
assert self.core.ref_period == 1*ns
self.t_frame = 10*8*4
self.clk_sel = clk_sel
self.tune_fifo_offset = tune_fifo_offset
self.sync_dly = sync_dly
self.dac_mmap = DAC34H84(dac).get_mmap()
self.channel = [PhaserChannel(self, ch, trf)
for ch, trf in enumerate([trf0, trf1])]
@kernel
def init(self, debug=False):
"""Initialize the board.
Verifies board and chip presence, resets components, performs
communication and configuration tests and establishes initial
conditions.
"""
board_id = self.read8(PHASER_ADDR_BOARD_ID)
if board_id != PHASER_BOARD_ID:
raise ValueError("invalid board id")
delay(.1*ms) # slack
hw_rev = self.read8(PHASER_ADDR_HW_REV)
delay(.1*ms) # slack
is_baseband = hw_rev & PHASER_HW_REV_VARIANT
gw_rev = self.read8(PHASER_ADDR_GW_REV)
delay(.1*ms) # slack
# allow a few errors during startup and alignment since boot
if self.get_crc_err() > 20:
raise ValueError("large number of frame CRC errors")
delay(.1*ms) # slack
# reset
self.set_cfg(dac_resetb=0, dac_sleep=1, dac_txena=0,
trf0_ps=1, trf1_ps=1,
att0_rstn=0, att1_rstn=0)
self.set_leds(0x00)
self.set_fan_mu(0)
# bring dac out of reset, keep tx off
self.set_cfg(clk_sel=self.clk_sel, dac_txena=0,
trf0_ps=1, trf1_ps=1,
att0_rstn=0, att1_rstn=0)
delay(.1*ms) # slack
# crossing dac_clk (reference) edges with sync_dly
# changes the optimal fifo_offset by 4
self.set_sync_dly(self.sync_dly)
# 4 wire SPI, sif4_enable
self.dac_write(0x02, 0x0080)
if self.dac_read(0x7f) != 0x5409:
raise ValueError("DAC version readback invalid")
delay(.1*ms)
if self.dac_read(0x00) != 0x049c:
raise ValueError("DAC config0 reset readback invalid")
delay(.1*ms)
t = self.get_dac_temperature()
delay(.1*ms)
if t < 10 or t > 90:
raise ValueError("DAC temperature out of bounds")
for data in self.dac_mmap:
self.dac_write(data >> 16, data)
delay(40*us)
# pll_ndivsync_ena disable
config18 = self.dac_read(0x18)
delay(.1*ms)
self.dac_write(0x18, config18 & ~0x0800)
patterns = [
[0xf05a, 0x05af, 0x5af0, 0xaf05], # test channel/iq/byte/nibble
[0x7a7a, 0xb6b6, 0xeaea, 0x4545], # datasheet pattern a
[0x1a1a, 0x1616, 0xaaaa, 0xc6c6], # datasheet pattern b
]
# A data delay of 2*50 ps heuristically and reproducibly matches
# FPGA+board+DAC skews. There is plenty of margin (>= 250 ps
# either side) and no need to tune at runtime.
# Parity provides another level of safety.
for i in range(len(patterns)):
delay(.5*ms)
errors = self.dac_iotest(patterns[i])
if errors:
raise ValueError("DAC iotest failure")
delay(2*ms) # let it settle
lvolt = self.dac_read(0x18) & 7
delay(.1*ms)
if lvolt < 2 or lvolt > 5:
raise ValueError("DAC PLL lock failed, check clocking")
if self.tune_fifo_offset:
fifo_offset = self.dac_tune_fifo_offset()
if debug:
print(fifo_offset)
self.core.break_realtime()
# self.dac_write(0x20, 0x0000) # stop fifo sync
# alarm = self.get_sta() & 1
# delay(.1*ms)
self.clear_dac_alarms()
delay(2*ms) # let it run a bit
alarms = self.get_dac_alarms()
delay(.1*ms) # slack
if alarms & ~0x0040: # ignore PLL alarms (see DS)
if debug:
print(alarms)
self.core.break_realtime()
# ignore alarms
else:
raise ValueError("DAC alarm")
# power up trfs, release att reset
self.set_cfg(clk_sel=self.clk_sel, dac_txena=0)
for ch in range(2):
channel = self.channel[ch]
# test attenuator write and readback
channel.set_att_mu(0x5a)
if channel.get_att_mu() != 0x5a:
raise ValueError("attenuator test failed")
delay(.1*ms)
channel.set_att_mu(0x00) # minimum attenuation
# test oscillators and DUC
for i in range(len(channel.oscillator)):
oscillator = channel.oscillator[i]
asf = 0
if i == 0:
asf = 0x7fff
# 6pi/4 phase
oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=1)
delay(1*us)
# 3pi/4
channel.set_duc_phase_mu(0x6000)
channel.set_duc_cfg(select=0, clr=1)
self.duc_stb()
delay(.1*ms) # settle link, pipeline and impulse response
data = channel.get_dac_data()
delay(.1*ms)
sqrt2 = 0x5a81 # 0x7fff/sqrt(2)
data_i = data & 0xffff
data_q = (data >> 16) & 0xffff
# allow ripple
if (data_i < sqrt2 - 30 or data_i > sqrt2 or
abs(data_i - data_q) > 2):
raise ValueError("DUC+oscillator phase/amplitude test failed")
if is_baseband:
continue
if channel.trf_read(0) & 0x7f != 0x68:
raise ValueError("TRF identification failed")
delay(.1*ms)
delay(.2*ms)
for data in channel.trf_mmap:
channel.trf_write(data)
delay(2*ms) # lock
if not (self.get_sta() & (PHASER_STA_TRF0_LD << ch)):
raise ValueError("TRF lock failure")
delay(.1*ms)
if channel.trf_read(0) & 0x1000:
raise ValueError("TRF R_SAT_ERR")
delay(.1*ms)
# enable dac tx
self.set_cfg(clk_sel=self.clk_sel)
@kernel
def write8(self, addr, data):
"""Write data to FPGA register.
:param addr: Address to write to (7 bit)
:param data: Data to write (8 bit)
"""
rtio_output((self.channel_base << 8) | (addr & 0x7f) | 0x80, data)
delay_mu(int64(self.t_frame))
@kernel
def read8(self, addr) -> TInt32:
"""Read from FPGA register.
:param addr: Address to read from (7 bit)
:return: Data read (8 bit)
"""
rtio_output((self.channel_base << 8) | (addr & 0x7f), 0)
response = rtio_input_data(self.channel_base)
return response >> self.miso_delay
@kernel
def write32(self, addr, data: TInt32):
"""Write 32 bit to a sequence of FPGA registers."""
for offset in range(4):
byte = data >> 24
self.write8(addr + offset, byte)
data <<= 8
@kernel
def read32(self, addr) -> TInt32:
"""Read 32 bit from a sequence of FPGA registers."""
data = 0
for offset in range(4):
data <<= 8
data |= self.read8(addr + offset)
delay(20*us) # slack
return data
@kernel
def set_leds(self, leds):
"""Set the front panel LEDs.
:param leds: LED settings (6 bit)
"""
self.write8(PHASER_ADDR_LED, leds)
@kernel
def set_fan_mu(self, pwm):
"""Set the fan duty cycle.
:param pwm: Duty cycle in machine units (8 bit)
"""
self.write8(PHASER_ADDR_FAN, pwm)
@kernel
def set_fan(self, duty):
"""Set the fan duty cycle.
:param duty: Duty cycle (0. to 1.)
"""
pwm = int32(round(duty*255.))
if pwm < 0 or pwm > 255:
raise ValueError("duty cycle out of bounds")
self.set_fan_mu(pwm)
@kernel
def set_cfg(self, clk_sel=0, dac_resetb=1, dac_sleep=0, dac_txena=1,
trf0_ps=0, trf1_ps=0, att0_rstn=1, att1_rstn=1):
"""Set the configuration register.
Each flag is a single bit (0 or 1).
:param clk_sel: Select the external SMA clock input
:param dac_resetb: Active low DAC reset pin
:param dac_sleep: DAC sleep pin
:param dac_txena: Enable DAC transmission pin
:param trf0_ps: Quadrature upconverter 0 power save
:param trf1_ps: Quadrature upconverter 1 power save
:param att0_rstn: Active low attenuator 0 reset
:param att1_rstn: Active low attenuator 1 reset
"""
self.write8(PHASER_ADDR_CFG,
((clk_sel & 1) << 0) | ((dac_resetb & 1) << 1) |
((dac_sleep & 1) << 2) | ((dac_txena & 1) << 3) |
((trf0_ps & 1) << 4) | ((trf1_ps & 1) << 5) |
((att0_rstn & 1) << 6) | ((att1_rstn & 1) << 7))
@kernel
def get_sta(self):
"""Get the status register value.
Bit flags are:
* :const:`PHASER_STA_DAC_ALARM`: DAC alarm pin
* :const:`PHASER_STA_TRF0_LD`: Quadrature upconverter 0 lock detect
* :const:`PHASER_STA_TRF1_LD`: Quadrature upconverter 1 lock detect
* :const:`PHASER_STA_TERM0`: ADC channel 0 termination indicator
* :const:`PHASER_STA_TERM1`: ADC channel 1 termination indicator
* :const:`PHASER_STA_SPI_IDLE`: SPI machine is idle and data registers
can be read/written
:return: Status register
"""
return self.read8(PHASER_ADDR_STA)
@kernel
def get_crc_err(self):
"""Get the frame CRC error counter.
:return: The number of frames with CRC mismatches sind the reset of the
device. Overflows at 256.
"""
return self.read8(PHASER_ADDR_CRC_ERR)
@kernel
def set_sync_dly(self, dly):
"""Set SYNC delay.
:param dly: DAC SYNC delay setting (0 to 7)
"""
if dly < 0 or dly > 7:
raise ValueError("SYNC delay out of bounds")
self.write8(PHASER_ADDR_SYNC_DLY, dly)
@kernel
def duc_stb(self):
"""Strobe the DUC configuration register update.
Transfer staging to active registers.
This affects both DUC channels.
"""
self.write8(PHASER_ADDR_DUC_STB, 0)
@kernel
def spi_cfg(self, select, div, end, clk_phase=0, clk_polarity=0,
half_duplex=0, lsb_first=0, offline=0, length=8):
"""Set the SPI machine configuration
:param select: Chip selects to assert (DAC, TRF0, TRF1, ATT0, ATT1)
:param div: SPI clock divider relative to 250 MHz fabric clock
:param end: Whether to end the SPI transaction and deassert chip select
:param clk_phase: SPI clock phase (sample on first or second edge)
:param clk_polarity: SPI clock polarity (idle low or high)
:param half_duplex: Read MISO data from MOSI wire
:param lsb_first: Transfer the least significant bit first
:param offline: Put the SPI interfaces offline and don't drive voltages
:param length: SPI transfer length (1 to 8 bits)
"""
if div < 2 or div > 257:
raise ValueError("divider out of bounds")
if length < 1 or length > 8:
raise ValueError("length out of bounds")
self.write8(PHASER_ADDR_SPI_SEL, select)
self.write8(PHASER_ADDR_SPI_DIVLEN, (div - 2 >> 3) | (length - 1 << 5))
self.write8(PHASER_ADDR_SPI_CFG,
((offline & 1) << 0) | ((end & 1) << 1) |
((clk_phase & 1) << 2) | ((clk_polarity & 1) << 3) |
((half_duplex & 1) << 4) | ((lsb_first & 1) << 5))
@kernel
def spi_write(self, data):
"""Write 8 bits into the SPI data register and start/continue the
transaction."""
self.write8(PHASER_ADDR_SPI_DATW, data)
@kernel
def spi_read(self):
"""Read from the SPI input data register."""
return self.read8(PHASER_ADDR_SPI_DATR)
@kernel
def dac_write(self, addr, data):
"""Write 16 bit to a DAC register.
:param addr: Register address
:param data: Register data to write
"""
div = 34 # 100 ns min period
t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns)
self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0)
self.spi_write(addr)
delay_mu(t_xfer)
self.spi_write(data >> 8)
delay_mu(t_xfer)
self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=1)
self.spi_write(data)
delay_mu(t_xfer)
@kernel
def dac_read(self, addr, div=34) -> TInt32:
"""Read from a DAC register.
:param addr: Register address to read from
:param div: SPI clock divider. Needs to be at least 250 (1 µs SPI
clock) to read the temperature register.
"""
t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns)
self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0)
self.spi_write(addr | 0x80)
delay_mu(t_xfer)
self.spi_write(0)
delay_mu(t_xfer)
data = self.spi_read() << 8
delay(20*us) # slack
self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=1)
self.spi_write(0)
delay_mu(t_xfer)
data |= self.spi_read()
return data
@kernel
def get_dac_temperature(self) -> TInt32:
"""Read the DAC die temperature.
:return: DAC temperature in degree Celsius
"""
return self.dac_read(0x06, div=257) >> 8
@kernel
def get_dac_alarms(self):
"""Read the DAC alarm flags.
:return: DAC alarm flags (see datasheet for bit meaning)
"""
return self.dac_read(0x05)
@kernel
def clear_dac_alarms(self):
"""Clear DAC alarm flags."""
self.dac_write(0x05, 0x0000)
@kernel
def dac_iotest(self, pattern) -> TInt32:
"""Performs a DAC IO test according to the datasheet.
:param patterm: List of four int32 containing the pattern
:return: Bit error mask (16 bits)
"""
if len(pattern) != 4:
raise ValueError("pattern length out of bounds")
for addr in range(len(pattern)):
self.dac_write(0x25 + addr, pattern[addr])
# repeat the pattern twice
self.dac_write(0x29 + addr, pattern[addr])
delay(.1*ms)
for ch in range(2):
channel = self.channel[ch]
channel.set_duc_cfg(select=1) # test
# dac test data is i msb, q lsb
data = pattern[2*ch] | (pattern[2*ch + 1] << 16)
channel.set_dac_test(data)
if channel.get_dac_data() != data:
raise ValueError("DAC test data readback failed")
delay(.1*ms)
cfg = self.dac_read(0x01)
delay(.1*ms)
self.dac_write(0x01, cfg | 0x8000) # iotest_ena
self.dac_write(0x04, 0x0000) # clear iotest_result
delay(.2*ms) # let it rip
# no need to go through the alarm register,
# just read the error mask
# self.clear_dac_alarms()
alarms = self.get_dac_alarms()
delay(.1*ms) # slack
if alarms & 0x0080: # alarm_from_iotest
errors = self.dac_read(0x04)
delay(.1*ms) # slack
else:
errors = 0
self.dac_write(0x01, cfg) # clear config
self.dac_write(0x04, 0x0000) # clear iotest_result
return errors
@kernel
def dac_tune_fifo_offset(self):
"""Scan through `fifo_offset` and configure midpoint setting.
:return: Optimal `fifo_offset` setting with maximum margin to write
pointer.
"""
# expect two or three error free offsets:
#
# read offset 01234567
# write pointer w
# distance 32101234
# error free x xx
config9 = self.dac_read(0x09)
delay(.1*ms)
good = 0
for o in range(8):
# set new fifo_offset
self.dac_write(0x09, (config9 & 0x1fff) | (o << 13))
self.clear_dac_alarms()
delay(.1*ms) # run
alarms = self.get_dac_alarms()
delay(.1*ms) # slack
if (alarms >> 11) & 0x7 == 0: # any fifo alarm
good |= 1 << o
# if there are good offsets accross the wrap around
# offset for computations
if good & 0x81 == 0x81:
good = ((good << 4) & 0xf0) | (good >> 4)
offset = 4
else:
offset = 0
# calculate mean
sum = 0
count = 0
for o in range(8):
if good & (1 << o):
sum += o
count += 1
best = ((sum // count) + offset) % 8
self.dac_write(0x09, (config9 & 0x1fff) | (best << 13))
return best
class PhaserChannel:
"""Phaser channel IQ pair.
A Phaser channel contains:
* multiple oscillators (in the coredevice phy),
* an interpolation chain and digital upconverter (DUC) on Phaser,
* several channel-specific settings in the DAC:
* quadrature modulation compensation QMC
* numerically controlled oscillator NCO or coarse mixer CMIX,
* the analog quadrature upconverter (in the Phaser-Upconverter hardware
variant), and
* a digitally controlled step attenuator.
Attributes:
* :attr:`oscillator`: List of five :class:`PhaserOscillator`.
.. note:: The amplitude sum of the oscillators must be less than one to
avoid clipping or overflow. If any of the DDS or DUC frequencies are
non-zero, it is not sufficient to ensure that the sum in each
quadrature is within range.
.. note:: The interpolation filter on Phaser has an intrinsic sinc-like
overshoot in its step response. That overshoot is an direct consequence
of its near-brick-wall frequency response. For large and wide-band
changes in oscillator parameters, the overshoot can lead to clipping
or overflow after the interpolation. Either band-limit any changes
in the oscillator parameters or back off the amplitude sufficiently.
"""
kernel_invariants = {"index", "phaser", "trf_mmap"}
def __init__(self, phaser, index, trf):
self.phaser = phaser
self.index = index
self.trf_mmap = TRF372017(trf).get_mmap()
self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)]
@kernel
def get_dac_data(self) -> TInt32:
"""Get a sample of the current DAC data.
The data is split accross multiple registers and thus the data
is only valid if constant.
:return: DAC data as 32 bit IQ. I/DACA/DACC in the 16 LSB,
Q/DACB/DACD in the 16 MSB
"""
return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.index << 4))
@kernel
def set_dac_test(self, data: TInt32):
"""Set the DAC test data.
:param data: 32 bit IQ test data, I/DACA/DACC in the 16 LSB,
Q/DACB/DACD in the 16 MSB
"""
self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.index << 4), data)
@kernel
def set_duc_cfg(self, clr=0, clr_once=0, select=0):
"""Set the digital upconverter (DUC) and interpolator configuration.
:param clr: Keep the phase accumulator cleared (persistent)
:param clr_once: Clear the phase accumulator for one cycle
:param select: Select the data to send to the DAC (0: DUC data, 1: test
data, other values: reserved)
"""
self.phaser.write8(PHASER_ADDR_DUC0_CFG + (self.index << 4),
((clr & 1) << 0) | ((clr_once & 1) << 1) |
((select & 3) << 2))
@kernel
def set_duc_frequency_mu(self, ftw):
"""Set the DUC frequency.
:param ftw: DUC frequency tuning word (32 bit)
"""
self.phaser.write32(PHASER_ADDR_DUC0_F + (self.index << 4), ftw)
@kernel
def set_duc_frequency(self, frequency):
"""Set the DUC frequency in SI units.
:param frequency: DUC frequency in Hz (passband from -200 MHz to
200 MHz, wrapping around at +- 250 MHz)
"""
ftw = int32(round(frequency*((1 << 30)/(125*MHz))))
self.set_duc_frequency_mu(ftw)
@kernel
def set_duc_phase_mu(self, pow):
"""Set the DUC phase offset.
:param pow: DUC phase offset word (16 bit)
"""
addr = PHASER_ADDR_DUC0_P + (self.index << 4)
self.phaser.write8(addr, pow >> 8)
self.phaser.write8(addr + 1, pow)
@kernel
def set_duc_phase(self, phase):
"""Set the DUC phase in SI units.
:param phase: DUC phase in turns
"""
pow = int32(round(phase*(1 << 16)))
self.set_duc_phase_mu(pow)
@kernel
def set_nco_frequency_mu(self, ftw):
"""Set the NCO frequency.
:param ftw: NCO frequency tuning word (32 bit)
"""
self.phaser.dac_write(0x15 + (self.index << 1), ftw >> 16)
self.phaser.dac_write(0x14 + (self.index << 1), ftw)
@kernel
def set_nco_frequency(self, frequency):
"""Set the NCO frequency in SI units.
:param frequency: NCO frequency in Hz (passband from -400 MHz
to 400 MHz, wrapping around at +- 500 MHz)
"""
ftw = int32(round(frequency*((1 << 30)/(250*MHz))))
self.set_nco_frequency_mu(ftw)
@kernel
def set_nco_phase_mu(self, pow):
"""Set the NCO phase offset.
:param pow: NCO phase offset word (16 bit)
"""
self.phaser.dac_write(0x12 + self.index, pow)
@kernel
def set_nco_phase(self, phase):
"""Set the NCO phase in SI units.
:param phase: NCO phase in turns
"""
pow = int32(round(phase*(1 << 16)))
self.set_duc_phase_mu(pow)
@kernel
def set_att_mu(self, data):
"""Set channel attenuation.
:param data: Attenuator data in machine units (8 bit)
"""
div = 34 # 30 ns min period
t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns)
self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div,
end=1)
self.phaser.spi_write(data)
delay_mu(t_xfer)
@kernel
def set_att(self, att):
"""Set channel attenuation in SI units.
:param att: Attenuation in dB
"""
# 2 lsb are inactive, resulting in 8 LSB per dB
data = 0xff - int32(round(att*8))
if data < 0 or data > 0xff:
raise ValueError("attenuation out of bounds")
self.set_att_mu(data)
@kernel
def get_att_mu(self) -> TInt32:
"""Read current attenuation.
The current attenuation value is read without side effects.
:return: Current attenuation in machine units
"""
div = 34
t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns)
self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div,
end=0)
self.phaser.spi_write(0)
delay_mu(t_xfer)
data = self.phaser.spi_read()
delay(20*us) # slack
self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div,
end=1)
self.phaser.spi_write(data)
delay_mu(t_xfer)
return data
@kernel
def trf_write(self, data, readback=False):
"""Write 32 bits to quadrature upconverter register.
:param data: Register data (32 bit) containing encoded address
:param readback: Whether to return the read back MISO data
"""
div = 34 # 50 ns min period
t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns)
read = 0
end = 0
clk_phase = 0
if readback:
clk_phase = 1
for i in range(4):
if i == 0 or i == 3:
if i == 3:
end = 1
self.phaser.spi_cfg(select=PHASER_SEL_TRF0 << self.index,
div=div, lsb_first=1, clk_phase=clk_phase,
end=end)
self.phaser.spi_write(data)
data >>= 8
delay_mu(t_xfer)
if readback:
read >>= 8
read |= self.phaser.spi_read() << 24
delay(20*us) # slack
return read
@kernel
def trf_read(self, addr, cnt_mux_sel=0) -> TInt32:
"""Quadrature upconverter register read.
:param addr: Register address to read (0 to 7)
:param cnt_mux_sel: Report VCO counter min or max frequency
:return: Register data (32 bit)
"""
self.trf_write(0x80000008 | (addr << 28) | (cnt_mux_sel << 27))
# single clk pulse with ~LE to start readback
self.phaser.spi_cfg(select=0, div=34, end=1, length=1)
self.phaser.spi_write(0)
delay((1 + 1)*34*4*ns)
return self.trf_write(0x00000008 | (cnt_mux_sel << 27),
readback=True)
class PhaserOscillator:
"""Phaser IQ channel oscillator (NCO/DDS).
.. note:: Latencies between oscillators within a channel and between
oscillator paramters (amplitude and phase/frequency) are deterministic
(with respect to the 25 MS/s sample clock) but not matched.
"""
kernel_invariants = {"channel", "base_addr"}
def __init__(self, channel, index):
self.channel = channel
self.base_addr = ((self.channel.phaser.channel_base + 1 +
2*self.channel.index) << 8) | index
@kernel
def set_frequency_mu(self, ftw):
"""Set Phaser MultiDDS frequency tuning word.
:param ftw: Frequency tuning word (32 bit)
"""
rtio_output(self.base_addr, ftw)
@kernel
def set_frequency(self, frequency):
"""Set Phaser MultiDDS frequency.
:param frequency: Frequency in Hz (passband from -10 MHz to 10 MHz,
wrapping around at +- 12.5 MHz)
"""
ftw = int32(round(frequency*((1 << 30)/(6.25*MHz))))
self.set_frequency_mu(ftw)
@kernel
def set_amplitude_phase_mu(self, asf=0x7fff, pow=0, clr=0):
"""Set Phaser MultiDDS amplitude, phase offset and accumulator clear.
:param asf: Amplitude (15 bit)
:param pow: Phase offset word (16 bit)
:param clr: Clear the phase accumulator (persistent)
"""
data = (asf & 0x7fff) | ((clr & 1) << 15) | (pow << 16)
rtio_output(self.base_addr | (1 << 8), data)
@kernel
def set_amplitude_phase(self, amplitude, phase=0., clr=0):
"""Set Phaser MultiDDS amplitude and phase.
:param amplitude: Amplitude in units of full scale
:param phase: Phase in turns
:param clr: Clear the phase accumulator (persistent)
"""
asf = int32(round(amplitude*0x7fff))
if asf < 0 or asf > 0x7fff:
raise ValueError("amplitude out of bounds")
pow = int32(round(phase*(1 << 16)))
self.set_amplitude_phase_mu(asf, pow, clr)

View File

@ -0,0 +1,133 @@
class TRF372017:
"""TRF372017 settings and register map.
For possible values, documentation, and explanation, see the datasheet.
https://www.ti.com/lit/gpn/trf372017
"""
rdiv = 21 # 13b
ref_inv = 0
neg_vco = 1
icp = 0 # 1.94 mA, 5b
icp_double = 0
cal_clk_sel = 12 # /16, 4b
ndiv = 420 # 16b
pll_div_sel = 0 # /1, 2b
prsc_sel = 1 # 8/9
vco_sel = 2 # 2b
vcosel_mode = 0
cal_acc = 0b00 # 2b
en_cal = 1
nfrac = 0 # 25b
pwd_pll = 0
pwd_cp = 0
pwd_vco = 0
pwd_vcomux = 0
pwd_div124 = 0
pwd_presc = 0
pwd_out_buff = 1
pwd_lo_div = 1
pwd_tx_div = 0
pwd_bb_vcm = 0
pwd_dc_off = 0
en_extvco = 0
en_isource = 0
ld_ana_prec = 0 # 2b
cp_tristate = 0 # 2b
speedup = 0
ld_dig_prec = 1
en_dith = 1
mod_ord = 2 # 3rd order, 2b
dith_sel = 0
del_sd_clk = 2 # 2b
en_frac = 0
vcobias_rtrim = 4 # 3b
pllbias_rtrim = 2 # 2b
vco_bias = 8 # 460 µA, 4b
vcobuf_bias = 2 # 2b
vcomux_bias = 3 # 2b
bufout_bias = 0 # 300 µA, 2b
vco_cal_ib = 0 # PTAT
vco_cal_ref = 2 # 1.04 V, 2b
vco_ampl_ctrl = 3 # 2b
vco_vb_ctrl = 0 # 1.2 V, 2b
en_ld_isource = 0
ioff = 0x80 # 8b
qoff = 0x80 # 8b
vref_sel = 4 # 0.85 V, 3b
tx_div_sel = 1 # div2, 2b
lo_div_sel = 3 # div8, 2b
tx_div_bias = 1 # 37.5 µA, 2b
lo_div_bias = 2 # 50 µA, 2b
vco_trim = 0x20 # 6b
vco_test_mode = 0
cal_bypass = 0
mux_ctrl = 1 # lock detect, 3b
isource_sink = 0
isource_trim = 4 # 3b
pd_tc = 0 # 2b
ib_vcm_sel = 0 # ptat
dcoffset_i = 2 # 150 µA, 2b
vco_bias_sel = 1 # spi
def __init__(self, updates=None):
if updates is None:
return
for key, value in updates.items():
if not hasattr(self, key):
raise KeyError("invalid setting", key)
setattr(self, key, value)
def get_mmap(self):
mmap = []
mmap.append(
0x9 |
(self.rdiv << 5) | (self.ref_inv << 19) | (self.neg_vco << 20) |
(self.icp << 21) | (self.icp_double << 26) |
(self.cal_clk_sel << 27))
mmap.append(
0xa |
(self.ndiv << 5) | (self.pll_div_sel << 21) | (self.prsc_sel << 23) |
(self.vco_sel << 26) | (self.vcosel_mode << 28) |
(self.cal_acc << 29) | (self.en_cal << 31))
mmap.append(0xb | (self.nfrac << 5))
mmap.append(
0xc |
(self.pwd_pll << 5) | (self.pwd_cp << 6) | (self.pwd_vco << 7) |
(self.pwd_vcomux << 8) | (self.pwd_div124 << 9) |
(self.pwd_presc << 10) | (self.pwd_out_buff << 12) |
(self.pwd_lo_div << 13) | (self.pwd_tx_div << 14) |
(self.pwd_bb_vcm << 15) | (self.pwd_dc_off << 16) |
(self.en_extvco << 17) | (self.en_isource << 18) |
(self.ld_ana_prec << 19) | (self.cp_tristate << 21) |
(self.speedup << 23) | (self.ld_dig_prec << 24) |
(self.en_dith << 25) | (self.mod_ord << 26) |
(self.dith_sel << 28) | (self.del_sd_clk << 29) |
(self.en_frac << 31))
mmap.append(
0xd |
(self.vcobias_rtrim << 5) | (self.pllbias_rtrim << 8) |
(self.vco_bias << 10) | (self.vcobuf_bias << 14) |
(self.vcomux_bias << 16) | (self.bufout_bias << 18) |
(1 << 21) | (self.vco_cal_ib << 22) | (self.vco_cal_ref << 23) |
(self.vco_ampl_ctrl << 26) | (self.vco_vb_ctrl << 28) |
(self.en_ld_isource << 31))
mmap.append(
0xe |
(self.ioff << 5) | (self.qoff << 13) | (self.vref_sel << 21) |
(self.tx_div_sel << 24) | (self.lo_div_sel << 26) |
(self.tx_div_bias << 28) | (self.lo_div_bias << 30))
mmap.append(
0xf |
(self.vco_trim << 7) | (self.vco_test_mode << 14) |
(self.cal_bypass << 15) | (self.mux_ctrl << 16) |
(self.isource_sink << 19) | (self.isource_trim << 20) |
(self.pd_tc << 23) | (self.ib_vcm_sel << 25) |
(1 << 28) | (self.dcoffset_i << 29) |
(self.vco_bias_sel << 31))
return mmap

View File

@ -485,6 +485,21 @@ class PeripheralManager:
channel=rtio_offset) channel=rtio_offset)
return 1 return 1
def process_phaser(self, rtio_offset, peripheral):
self.gen("""
device_db["{name}"] = {{
"type": "local",
"module": "artiq.coredevice.phaser",
"class": "Phaser",
"arguments": {{
"channel_base": 0x{channel:06x},
"miso_delay": 1,
}}
}}""",
name=self.get_name("phaser"),
channel=rtio_offset)
return 2
def process(self, rtio_offset, peripheral): def process(self, rtio_offset, peripheral):
processor = getattr(self, "process_"+str(peripheral["type"])) processor = getattr(self, "process_"+str(peripheral["type"]))
return processor(rtio_offset, peripheral) return processor(rtio_offset, peripheral)

View File

@ -51,6 +51,7 @@ class SinaraTester(EnvExperiment):
self.samplers = dict() self.samplers = dict()
self.zotinos = dict() self.zotinos = dict()
self.fastinos = dict() self.fastinos = dict()
self.phasers = dict()
self.grabbers = dict() self.grabbers = dict()
ddb = self.get_device_db() ddb = self.get_device_db()
@ -77,6 +78,8 @@ class SinaraTester(EnvExperiment):
self.zotinos[name] = self.get_device(name) self.zotinos[name] = self.get_device(name)
elif (module, cls) == ("artiq.coredevice.fastino", "Fastino"): elif (module, cls) == ("artiq.coredevice.fastino", "Fastino"):
self.fastinos[name] = self.get_device(name) self.fastinos[name] = self.get_device(name)
elif (module, cls) == ("artiq.coredevice.phaser", "Phaser"):
self.phasers[name] = self.get_device(name)
elif (module, cls) == ("artiq.coredevice.grabber", "Grabber"): elif (module, cls) == ("artiq.coredevice.grabber", "Grabber"):
self.grabbers[name] = self.get_device(name) self.grabbers[name] = self.get_device(name)
@ -111,6 +114,7 @@ class SinaraTester(EnvExperiment):
self.samplers = sorted(self.samplers.items(), key=lambda x: x[1].cnv.channel) self.samplers = sorted(self.samplers.items(), key=lambda x: x[1].cnv.channel)
self.zotinos = sorted(self.zotinos.items(), key=lambda x: x[1].bus.channel) self.zotinos = sorted(self.zotinos.items(), key=lambda x: x[1].bus.channel)
self.fastinos = sorted(self.fastinos.items(), key=lambda x: x[1].channel) self.fastinos = sorted(self.fastinos.items(), key=lambda x: x[1].channel)
self.phasers = sorted(self.phasers.items(), key=lambda x: x[1].channel_base)
self.grabbers = sorted(self.grabbers.items(), key=lambda x: x[1].channel_base) self.grabbers = sorted(self.grabbers.items(), key=lambda x: x[1].channel_base)
@kernel @kernel
@ -391,6 +395,57 @@ class SinaraTester(EnvExperiment):
[card_dev for _, (__, card_dev) in enumerate(self.fastinos)] [card_dev for _, (__, card_dev) in enumerate(self.fastinos)]
) )
@kernel
def set_phaser_frequencies(self, phaser, duc, osc):
self.core.break_realtime()
phaser.init()
delay(1*ms)
phaser.channel[0].set_duc_frequency(duc)
phaser.channel[0].set_duc_cfg()
phaser.channel[0].set_att(6*dB)
phaser.channel[1].set_duc_frequency(-duc)
phaser.channel[1].set_duc_cfg()
phaser.channel[1].set_att(6*dB)
phaser.duc_stb()
delay(1*ms)
for i in range(len(osc)):
phaser.channel[0].oscillator[i].set_frequency(osc[i])
phaser.channel[0].oscillator[i].set_amplitude_phase(.2)
phaser.channel[1].oscillator[i].set_frequency(-osc[i])
phaser.channel[1].oscillator[i].set_amplitude_phase(.2)
delay(1*ms)
@kernel
def phaser_led_wave(self, phasers):
while not is_enter_pressed():
self.core.break_realtime()
# do not fill the FIFOs too much to avoid long response times
t = now_mu() - self.core.seconds_to_mu(.2)
while self.core.get_rtio_counter_mu() < t:
pass
for phaser in phasers:
for i in range(6):
phaser.set_leds(1 << i)
delay(100*ms)
phaser.set_leds(0)
delay(100*ms)
def test_phasers(self):
print("*** Testing Phaser DACs and 6 USER LEDs.")
print("Frequencies:")
for card_n, (card_name, card_dev) in enumerate(self.phasers):
duc = (card_n + 1)*10*MHz
osc = [i*1*MHz for i in range(5)]
print(card_name,
" ".join(["{:.0f}+{:.0f}".format(duc/MHz, f/MHz) for f in osc]),
"MHz")
self.set_phaser_frequencies(card_dev, duc, osc)
print("Press ENTER when done.")
# Test switching on/off USR_LEDs at the same time
self.phaser_led_wave(
[card_dev for _, (__, card_dev) in enumerate(self.phasers)]
)
@kernel @kernel
def grabber_capture(self, card_dev, rois): def grabber_capture(self, card_dev, rois):
self.core.break_realtime() self.core.break_realtime()
@ -443,6 +498,8 @@ class SinaraTester(EnvExperiment):
self.test_zotinos() self.test_zotinos()
if self.fastinos: if self.fastinos:
self.test_fastinos() self.test_fastinos()
if self.phasers:
self.test_phasers()
if self.grabbers: if self.grabbers:
self.test_grabbers() self.test_grabbers()

View File

@ -5,7 +5,7 @@ from migen.genlib.io import DifferentialOutput
from artiq.gateware import rtio from artiq.gateware import rtio
from artiq.gateware.rtio.phy import spi2, ad53xx_monitor, grabber from artiq.gateware.rtio.phy import spi2, ad53xx_monitor, grabber
from artiq.gateware.suservo import servo, pads as servo_pads from artiq.gateware.suservo import servo, pads as servo_pads
from artiq.gateware.rtio.phy import servo as rtservo, fastino from artiq.gateware.rtio.phy import servo as rtservo, fastino, phaser
def _eem_signal(i): def _eem_signal(i):
@ -613,7 +613,8 @@ class Fastino(_EEM):
Subsignal("clk", Pins(_eem_pin(eem, 0, pol))), Subsignal("clk", Pins(_eem_pin(eem, 0, pol))),
Subsignal("mosi", Pins(*(_eem_pin(eem, i, pol) Subsignal("mosi", Pins(*(_eem_pin(eem, i, pol)
for i in range(1, 7)))), for i in range(1, 7)))),
Subsignal("miso", Pins(_eem_pin(eem, 7, pol))), Subsignal("miso", Pins(_eem_pin(eem, 7, pol)),
Misc("DIFF_TERM=TRUE")),
IOStandard(iostandard), IOStandard(iostandard),
) for pol in "pn"] ) for pol in "pn"]
@ -626,3 +627,33 @@ class Fastino(_EEM):
log2_width=log2_width) log2_width=log2_width)
target.submodules += phy target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4))
class Phaser(_EEM):
@staticmethod
def io(eem, iostandard="LVDS_25"):
return [
("phaser{}_ser_{}".format(eem, pol), 0,
Subsignal("clk", Pins(_eem_pin(eem, 0, pol))),
Subsignal("mosi", Pins(*(_eem_pin(eem, i, pol)
for i in range(1, 7)))),
Subsignal("miso", Pins(_eem_pin(eem, 7, pol)),
Misc("DIFF_TERM=TRUE")),
IOStandard(iostandard),
) for pol in "pn"]
@classmethod
def add_std(cls, target, eem, iostandard="LVDS_25"):
cls.add_extension(target, eem, iostandard=iostandard)
phy = phaser.Phaser(
target.platform.request("phaser{}_ser_p".format(eem)),
target.platform.request("phaser{}_ser_n".format(eem)))
target.submodules += phy
target.rtio_channels.extend([
rtio.Channel.from_phy(phy, ififo_depth=4),
rtio.Channel.from_phy(phy.ch0.frequency),
rtio.Channel.from_phy(phy.ch0.phase_amplitude),
rtio.Channel.from_phy(phy.ch1.frequency),
rtio.Channel.from_phy(phy.ch1.phase_amplitude),
])

View File

@ -4,132 +4,7 @@ from migen.genlib.io import DifferentialOutput, DifferentialInput, DDROutput
from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine
from artiq.gateware.rtio import rtlink from artiq.gateware.rtio import rtlink
from .fastlink import SerDes, SerInterface
class SerDes(Module):
def transpose(self, i, n):
# i is n,m c-contiguous
# o is m,n c-contiguous
m = len(i)//n
assert n*m == len(i)
def __init__(self, pins, pins_n):
n_bits = 16 # bits per dac data word
n_channels = 32 # channels per fastino
n_div = 7 # bits per lane and word
assert n_div == 7
n_frame = 14 # word per frame
n_lanes = len(pins.mosi) # number of data lanes
n_checksum = 12 # checksum bits
n_addr = 4 # readback address bits
n_word = n_lanes*n_div
n_body = n_word*n_frame - (n_frame//2 + 1) - n_checksum
# dac data words
self.dacs = [Signal(n_bits) for i in range(n_channels)]
# dac update enable
self.enable = Signal(n_channels)
# configuration word
self.cfg = Signal(20)
# readback data
self.dat_r = Signal(n_frame//2*(1 << n_addr))
# data load synchronization event
self.stb = Signal()
# # #
# crc-12 telco
self.submodules.crc = LiteEthMACCRCEngine(
data_width=2*n_lanes, width=n_checksum, polynom=0x80f)
addr = Signal(4)
body_ = Cat(self.cfg, addr, self.enable, self.dacs)
assert len(body_) == n_body
body = Signal(n_body)
self.comb += body.eq(body_)
words_ = []
j = 0
for i in range(n_frame): # iterate over words
if i == 0: # data and checksum
k = n_word - n_checksum
elif i == 1: # marker
words_.append(C(1))
k = n_word - 1
elif i < n_frame//2 + 2: # marker
words_.append(C(0))
k = n_word - 1
else: # full word
k = n_word
# append corresponding frame body bits
words_.append(body[j:j + k])
j += k
words_ = Cat(words_)
assert len(words_) == n_frame*n_word - n_checksum
words = Signal(len(words_))
self.comb += words.eq(words_)
clk = Signal(n_div, reset=0b1100011)
clk_stb = Signal()
i_frame = Signal(max=n_div*n_frame//2) # DDR
frame_stb = Signal()
sr = [Signal(n_frame*n_div - n_checksum//n_lanes, reset_less=True)
for i in range(n_lanes)]
assert len(Cat(sr)) == len(words)
# DDR bits for each register
ddr_data = Cat([sri[-2] for sri in sr], [sri[-1] for sri in sr])
self.comb += [
# assert one cycle ahead
clk_stb.eq(~clk[0] & clk[-1]),
# double period because of DDR
frame_stb.eq(i_frame == n_div*n_frame//2 - 1),
# LiteETHMACCRCEngine takes data LSB first
self.crc.data[::-1].eq(ddr_data),
self.stb.eq(frame_stb & clk_stb),
]
miso = Signal()
miso_sr = Signal(n_frame, reset_less=True)
self.sync.rio_phy += [
# shift 7 bit clock pattern by two bits each DDR cycle
clk.eq(Cat(clk[-2:], clk)),
[sri[2:].eq(sri) for sri in sr],
self.crc.last.eq(self.crc.next),
If(clk[:2] == 0, # TODO: tweak MISO sampling
miso_sr.eq(Cat(miso, miso_sr)),
),
If(~frame_stb,
i_frame.eq(i_frame + 1),
),
If(frame_stb & clk_stb,
i_frame.eq(0),
self.crc.last.eq(0),
# transpose, load
Cat(sr).eq(Cat(words[mm::n_lanes] for mm in range(n_lanes))),
Array([self.dat_r[i*n_frame//2:(i + 1)*n_frame//2]
for i in range(1 << len(addr))])[addr].eq(miso_sr),
addr.eq(addr + 1),
),
If(i_frame == n_div*n_frame//2 - 2,
# inject crc
ddr_data.eq(self.crc.next),
),
]
clk_ddr = Signal()
miso0 = Signal()
self.specials += [
DDROutput(clk[-1], clk[-2], clk_ddr, ClockSignal("rio_phy")),
DifferentialOutput(clk_ddr, pins.clk, pins_n.clk),
DifferentialInput(pins.miso, pins_n.miso, miso0),
MultiReg(miso0, miso, "rio_phy"),
]
for sri, ddr, mp, mn in zip(
sr, Signal(n_lanes), pins.mosi, pins_n.mosi):
self.specials += [
DDROutput(sri[-1], sri[-2], ddr, ClockSignal("rio_phy")),
DifferentialOutput(ddr, mp, mn),
]
class Fastino(Module): class Fastino(Module):
@ -139,9 +14,31 @@ class Fastino(Module):
rtlink.OInterface(data_width=max(16*width, 32), rtlink.OInterface(data_width=max(16*width, 32),
address_width=8, address_width=8,
enable_replace=False), enable_replace=False),
rtlink.IInterface(data_width=32)) rtlink.IInterface(data_width=14))
self.submodules.serializer = SerDes(pins, pins_n) self.submodules.serializer = SerDes(
n_data=8, t_clk=7, d_clk=0b1100011,
n_frame=14, n_crc=12, poly=0x80f)
self.submodules.intf = SerInterface(pins, pins_n)
self.comb += [
Cat(self.intf.data[:-1]).eq(Cat(self.serializer.data[:-1])),
self.serializer.data[-1].eq(self.intf.data[-1]),
]
# dac data words
dacs = [Signal(16) for i in range(32)]
header = Record([
("cfg", 4),
("leds", 8),
("reserved", 8),
("addr", 4),
("enable", len(dacs)),
])
body = Cat(header.raw_bits(), dacs)
assert len(body) == len(self.serializer.payload)
self.comb += self.serializer.payload.eq(body)
# # #
# Support staging DAC data (in `dacs`) by writing to the # Support staging DAC data (in `dacs`) by writing to the
# DAC RTIO addresses, if a channel is not "held" by its # DAC RTIO addresses, if a channel is not "held" by its
@ -164,37 +61,36 @@ class Fastino(Module):
# LSBs of the RTIO address for a DAC channel write must be zero and the # LSBs of the RTIO address for a DAC channel write must be zero and the
# address space is sparse. # address space is sparse.
hold = Signal.like(self.serializer.enable) hold = Signal.like(header.enable)
# TODO: stb, timestamp read_regs = Array([Signal.like(self.serializer.readback)
read_regs = Array([ for _ in range(1 << len(header.addr))])
self.serializer.dat_r[i*7:(i + 1)*7]
for i in range(1 << 4)
])
cases = { cases = {
# update # update
0x20: self.serializer.enable.eq(self.serializer.enable | self.rtlink.o.data), 0x20: header.enable.eq(header.enable | self.rtlink.o.data),
# hold # hold
0x21: hold.eq(self.rtlink.o.data), 0x21: hold.eq(self.rtlink.o.data),
# cfg # cfg
0x22: self.serializer.cfg[:4].eq(self.rtlink.o.data), 0x22: header.cfg.eq(self.rtlink.o.data),
# leds # leds
0x23: self.serializer.cfg[4:12].eq(self.rtlink.o.data), 0x23: header.leds.eq(self.rtlink.o.data),
# reserved # reserved
0x24: self.serializer.cfg[12:].eq(self.rtlink.o.data), 0x24: header.reserved.eq(self.rtlink.o.data),
} }
for i in range(0, len(self.serializer.dacs), width): for i in range(0, len(dacs), width):
cases[i] = [ cases[i] = [
Cat(self.serializer.dacs[i:i + width]).eq(self.rtlink.o.data), Cat(dacs[i:i + width]).eq(self.rtlink.o.data),
[If(~hold[i + j], [If(~hold[i + j],
self.serializer.enable[i + j].eq(1), header.enable[i + j].eq(1),
) for j in range(width)] ) for j in range(width)]
] ]
self.sync.rio_phy += [ self.sync.rio_phy += [
If(self.serializer.stb, If(self.serializer.stb,
self.serializer.enable.eq(0), header.enable.eq(0),
read_regs[header.addr].eq(self.serializer.readback),
header.addr.eq(header.addr + 1),
), ),
If(self.rtlink.o.stb & ~self.rtlink.o.address[-1], If(self.rtlink.o.stb & ~self.rtlink.o.address[-1],
Case(self.rtlink.o.address[:-1], cases), Case(self.rtlink.o.address[:-1], cases),

View File

@ -0,0 +1,133 @@
from migen import *
from migen.genlib.io import (DifferentialOutput, DifferentialInput,
DDROutput, DDRInput)
from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine
from artiq.gateware.rtio import rtlink
class SerDes(Module):
# crc-12 telco: 0x80f
def __init__(self, n_data=8, t_clk=7, d_clk=0b1100011,
n_frame=14, n_crc=12, poly=0x80f):
"""DDR fast link.
* One word clock lane with `t_clk` period.
* Multiple data lanes at DDR speed.
* One return data lane at slower speed.
* n_frame//2 - 1 marker bits are used to provide framing.
* `n_data` lanes
* `t_clk` bits per clk cycle with pattern `d_clk`
* `n_frame` words per frame
* `n_crc` CRC bits per frame for divisor poly `poly`
"""
# pins
self.data = [Signal(2, reset_less=True) for _ in range(n_data)]
n_mosi = n_data - 2 # mosi lanes
n_word = n_mosi*t_clk # bits per word
t_frame = t_clk*n_frame # frame duration
n_marker = n_frame//2 + 1
n_body = n_word*n_frame - n_marker - n_crc
t_miso = 0 # miso sampling latency TODO
assert n_crc % n_mosi == 0
# frame data
self.payload = Signal(n_body)
# readback data
self.readback = Signal(n_frame, reset_less=True)
# data load synchronization event
self.stb = Signal()
# # #
self.submodules.crca = LiteEthMACCRCEngine(
data_width=n_mosi, width=n_crc, polynom=poly)
self.submodules.crcb = LiteEthMACCRCEngine(
data_width=n_mosi, width=n_crc, polynom=poly)
words_ = []
j = 0
# build from LSB to MSB because MSB first
for i in range(n_frame): # iterate over words
if i == 0: # data and checksum
words_.append(C(0, n_crc))
k = n_word - n_crc
elif i == 1: # marker
words_.append(C(1))
k = n_word - 1
elif i < n_frame//2 + 2: # marker
words_.append(C(0))
k = n_word - 1
else: # full word
k = n_word
# append corresponding frame body bits
words_.append(self.payload[j:j + k])
j += k
words_ = Cat(words_)
assert len(words_) == n_frame*n_word
words = Signal(len(words_))
self.comb += words.eq(words_)
clk = Signal(t_clk, reset=d_clk)
i = Signal(max=t_frame//2)
# big shift register for mosi and
sr = [Signal(t_frame, reset_less=True) for i in range(n_mosi)]
assert len(Cat(sr)) == len(words)
crc_insert = Cat(([d[0] for d in self.data[1:-1]] +
[d[1] for d in self.data[1:-1]])[:n_crc])
miso_sr = Signal(t_frame, reset_less=True)
miso_sr_next = Signal.like(miso_sr)
self.comb += [
self.stb.eq(i == t_frame//2 - 1),
# LiteETHMACCRCEngine takes data LSB first
self.crca.data.eq(Cat([sri[-1] for sri in sr[::-1]])),
self.crcb.data.eq(Cat([sri[-2] for sri in sr[::-1]])),
self.crcb.last.eq(self.crca.next),
miso_sr_next.eq(Cat(self.data[-1], miso_sr)),
# unload miso
# TODO: align to marker
self.readback.eq(Cat([miso_sr_next[t_miso + i*t_clk]
for i in range(n_frame)])),
]
self.sync.rio_phy += [
# shift everything by two bits
[di.eq(sri[-2:]) for di, sri in zip(self.data, [clk] + sr)],
clk.eq(Cat(clk[-2:], clk)),
[sri.eq(Cat(C(0, 2), sri)) for sri in sr],
miso_sr.eq(miso_sr_next),
self.crca.last.eq(self.crcb.next),
i.eq(i + 1),
If(self.stb,
i.eq(0),
clk.eq(clk.reset),
self.crca.last.eq(0),
# transpose, load
[sri.eq(Cat(words[i::n_mosi])) for i, sri in enumerate(sr)],
# inject crc for the last cycle
crc_insert.eq(self.crca.next if n_crc // n_mosi <= 1
else self.crca.last),
),
]
class SerInterface(Module):
def __init__(self, pins, pins_n):
self.data = [Signal(2) for _ in range(2 + len(pins.mosi))]
for d, pp, pn in zip(self.data,
[pins.clk] + list(pins.mosi),
[pins_n.clk] + list(pins_n.mosi)):
ddr = Signal()
self.specials += [
# d1 closer to q
DDROutput(d[1], d[0], ddr, ClockSignal("rio_phy")),
DifferentialOutput(ddr, pp, pn),
]
ddr = Signal()
self.specials += [
DifferentialInput(pins.miso, pins_n.miso, ddr),
# q1 closer to d
DDRInput(ddr, self.data[-1][0], self.data[-1][1],
ClockSignal("rio_phy")),
]

View File

@ -0,0 +1,89 @@
from migen import *
from misoc.cores.duc import MultiDDS
from artiq.gateware.rtio import rtlink
from .fastlink import SerDes, SerInterface
class Phy(Module):
def __init__(self, regs):
self.rtlink = rtlink.Interface(
rtlink.OInterface(data_width=32, address_width=4,
enable_replace=True))
self.sync.rtio += [
If(self.rtlink.o.stb,
Array(regs)[self.rtlink.o.address].eq(self.rtlink.o.data)
)
]
class DDSChannel(Module):
def __init__(self, share_lut=None):
to_rio_phy = ClockDomainsRenamer("rio_phy")
self.submodules.dds = to_rio_phy(MultiDDS(
n=5, fwidth=32, xwidth=16, z=19, zl=10, share_lut=share_lut))
self.submodules.frequency = Phy([i.f for i in self.dds.i])
self.submodules.phase_amplitude = Phy(
[Cat(i.a, i.clr, i.p) for i in self.dds.i])
class Phaser(Module):
def __init__(self, pins, pins_n):
self.rtlink = rtlink.Interface(
rtlink.OInterface(data_width=8, address_width=8,
enable_replace=False),
rtlink.IInterface(data_width=10))
# share a CosSinGen LUT between the two channels
self.submodules.ch0 = DDSChannel()
self.submodules.ch1 = DDSChannel(share_lut=self.ch0.dds.cs.lut)
n_channels = 2
n_samples = 8
n_bits = 14
body = Signal(n_samples*n_channels*2*n_bits, reset_less=True)
self.sync.rio_phy += [
If(self.ch0.dds.valid, # & self.ch1.dds.valid,
# recent:ch0:i as low order in body
Cat(body).eq(Cat(self.ch0.dds.o.i[2:], self.ch0.dds.o.q[2:],
self.ch1.dds.o.i[2:], self.ch1.dds.o.q[2:],
body)),
),
]
self.submodules.serializer = SerDes(
n_data=8, t_clk=8, d_clk=0b00001111,
n_frame=10, n_crc=6, poly=0x2f)
self.submodules.intf = SerInterface(pins, pins_n)
self.comb += [
Cat(self.intf.data[:-1]).eq(Cat(self.serializer.data[:-1])),
self.serializer.data[-1].eq(self.intf.data[-1]),
]
header = Record([
("we", 1),
("addr", 7),
("data", 8),
("type", 4)
])
assert len(Cat(header.raw_bits(), body)) == \
len(self.serializer.payload)
self.comb += self.serializer.payload.eq(Cat(header.raw_bits(), body))
re_dly = Signal(3) # stage, send, respond
self.sync.rtio += [
header.type.eq(1), # body type is baseband data
If(self.serializer.stb,
self.ch0.dds.stb.eq(1), # synchronize
self.ch1.dds.stb.eq(1), # synchronize
header.we.eq(0),
re_dly.eq(re_dly[1:]),
),
If(self.rtlink.o.stb,
re_dly[-1].eq(~self.rtlink.o.address[-1]),
header.we.eq(self.rtlink.o.address[-1]),
header.addr.eq(self.rtlink.o.address),
header.data.eq(self.rtlink.o.data),
),
self.rtlink.i.stb.eq(re_dly[0] & self.serializer.stb),
self.rtlink.i.data.eq(self.serializer.readback),
]

View File

@ -113,6 +113,12 @@ def peripheral_fastino(module, peripheral):
peripheral.get("log2_width", 0)) peripheral.get("log2_width", 0))
def peripheral_phaser(module, peripheral):
if len(peripheral["ports"]) != 1:
raise ValueError("wrong number of ports")
eem.Phaser.add_std(module, peripheral["ports"][0])
peripheral_processors = { peripheral_processors = {
"dio": peripheral_dio, "dio": peripheral_dio,
"urukul": peripheral_urukul, "urukul": peripheral_urukul,
@ -123,6 +129,7 @@ peripheral_processors = {
"grabber": peripheral_grabber, "grabber": peripheral_grabber,
"mirny": peripheral_mirny, "mirny": peripheral_mirny,
"fastino": peripheral_fastino, "fastino": peripheral_fastino,
"phaser": peripheral_phaser,
} }

View File

@ -0,0 +1,84 @@
import unittest
from migen import *
from artiq.gateware.rtio.phy.fastlink import *
class TestPhaser(unittest.TestCase):
def setUp(self):
self.dut = SerDes(n_data=8, t_clk=8, d_clk=0b00001111,
n_frame=10, n_crc=6, poly=0x2f)
def test_init(self):
pass
def record_frame(self, frame):
clk = 0
marker = 0
stb = 0
while True:
if stb == 2:
frame.append((yield self.dut.data))
clk = (clk << 2) & 0xff
clk |= (yield self.dut.data[0])
if clk == 0x0f:
if marker == 0x01:
stb += 1
if stb >= 3:
break
# 10/2 + 1 marker bits
marker = (marker << 1) & 0x3f
marker |= (yield self.dut.data[1]) & 1
yield
def test_frame(self):
frame = []
self.dut.comb += self.dut.payload.eq((1 << len(self.dut.payload)) - 1)
run_simulation(self.dut, self.record_frame(frame),
clocks={n: 2 for n in ["sys", "rio", "rio_phy"]})
self.assertEqual(len(frame), 8*10//2)
self.assertEqual([d[0] for d in frame], [0, 0, 3, 3] * 10)
self.assertEqual([d[1] & 1 for d in frame[4*4 - 1:10*4 - 1:4]],
[0, 0, 0, 0, 0, 1])
class TestFastino(unittest.TestCase):
def setUp(self):
self.dut = SerDes(
n_data=8, t_clk=7, d_clk=0b1100011,
n_frame=14, n_crc=12, poly=0x80f)
def test_init(self):
pass
def record_frame(self, frame):
clk = 0
marker = 0
stb = 0
while True:
if stb == 2:
frame.append((yield self.dut.data))
clk = (clk << 2) & 0xff
clk |= (yield self.dut.data[0])
if clk in (0b11100011, 0b11000111):
if marker == 0x01:
stb += 1
if stb >= 3:
break
# 14/2 + 1 marker bits
marker = (marker << 1) & 0xff
if clk & 0b100:
marker |= (yield self.dut.data[1]) >> 1
else:
marker |= (yield self.dut.data[1]) & 1
yield
def test_frame(self):
frame = []
self.dut.comb += self.dut.payload.eq((1 << len(self.dut.payload)) - 1)
run_simulation(self.dut, self.record_frame(frame),
clocks={n: 2 for n in ["sys", "rio", "rio_phy"]})
self.assertEqual(len(frame), 7*14//2)
self.assertEqual([d[0] for d in frame], [3, 0, 1, 3, 2, 0, 3] * 7)
self.assertEqual(frame[-1], [3, 3, 1, 1, 1, 2, 1, 0]) # crc12

View File

@ -0,0 +1,34 @@
import unittest
from artiq.experiment import *
from artiq.test.hardware_testbench import ExperimentCase
from artiq.language.core import kernel, delay
from artiq.language.units import us
class PhaserExperiment(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("phaser0")
@kernel
def run(self):
self.core.reset()
# The Phaser initialization performs a comprehensive test:
# * Fastlink bringup
# * Fastlink error counter
# * Board identification
# * Hardware identification
# * SPI write, readback, timing
# * Temperature readout
# * DAC identification, IOTEST, alarm sweep, PLL configuration, FIFO
# alignmend
# * DUC+Oscillator configuration, data end-to-end verification and
# readback
# * Attenuator write and readback
# * TRF bringup PLL locking
self.phaser0.init()
class PhaserTest(ExperimentCase):
def test(self):
self.execute(PhaserExperiment)

View File

@ -130,6 +130,11 @@ RF generation drivers
.. automodule:: artiq.coredevice.basemod_att .. automodule:: artiq.coredevice.basemod_att
:members: :members:
:mod:`artiq.coredevice.phaser` module
+++++++++++++++++++++++++++++++++++++
.. automodule:: artiq.coredevice.phaser
:members:
DAC/ADC drivers DAC/ADC drivers
--------------- ---------------