forked from M-Labs/artiq
commit
a9dd0a268c
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
])
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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")),
|
||||||
|
]
|
|
@ -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),
|
||||||
|
]
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||||
---------------
|
---------------
|
||||||
|
|
Loading…
Reference in New Issue