phaser: port to NAC3

This commit is contained in:
Sebastien Bourdeauducq 2022-02-28 17:00:24 +08:00
parent a407007e0b
commit ea55c29568
3 changed files with 210 additions and 166 deletions

View File

@ -1,13 +1,28 @@
from __future__ import annotations
from numpy import int32, int64 from numpy import int32, int64
from artiq.language.core import kernel, delay_mu, delay from artiq.language.core import *
from artiq.coredevice.core import Core
from artiq.coredevice.rtio import rtio_output, rtio_input_data, rtio_input_timestamp from artiq.coredevice.rtio import rtio_output, rtio_input_data, rtio_input_timestamp
from artiq.language.units import us, ns, ms, MHz from artiq.language.units import us, ns, ms, MHz
from artiq.language.types import TInt32
from artiq.coredevice.dac34h84 import DAC34H84 from artiq.coredevice.dac34h84 import DAC34H84
from artiq.coredevice.trf372017 import TRF372017 from artiq.coredevice.trf372017 import TRF372017
# NAC3TODO work around https://git.m-labs.hk/M-Labs/nac3/issues/189
@nac3
class ValueError(Exception):
pass
# NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/201
@portable
def abs(x: int32) -> int32:
if x > 0:
return x
else:
return -x
PHASER_BOARD_ID = 19 PHASER_BOARD_ID = 19
PHASER_ADDR_BOARD_ID = 0x00 PHASER_ADDR_BOARD_ID = 0x00
PHASER_ADDR_HW_REV = 0x01 PHASER_ADDR_HW_REV = 0x01
@ -59,6 +74,7 @@ PHASER_DAC_SEL_TEST = 1
PHASER_HW_REV_VARIANT = 1 << 4 PHASER_HW_REV_VARIANT = 1 << 4
@nac3
class Phaser: class Phaser:
"""Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver. """Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver.
@ -133,7 +149,7 @@ class Phaser:
and buffer round trip. Tuning this might be automated later. and buffer round trip. Tuning this might be automated later.
:param tune_fifo_offset: Tune the DAC FIFO read pointer offset :param tune_fifo_offset: Tune the DAC FIFO read pointer offset
(default=True) (default=True)
:param clk_sel: Select the external SMA clock input (1 or 0) :param clk_sel: Select the external SMA clock input.
:param sync_dly: SYNC delay with respect to ISTR. :param sync_dly: SYNC delay with respect to ISTR.
:param dac: DAC34H84 DAC settings as a dictionary. :param dac: DAC34H84 DAC settings as a dictionary.
:param trf0: Channel 0 TRF372017 quadrature upconverter settings as a :param trf0: Channel 0 TRF372017 quadrature upconverter settings as a
@ -147,11 +163,20 @@ class Phaser:
To access oscillators, digital upconverters, PLL/VCO analog To access oscillators, digital upconverters, PLL/VCO analog
quadrature upconverters and attenuators. quadrature upconverters and attenuators.
""" """
kernel_invariants = {"core", "channel_base", "t_frame", "miso_delay",
"dac_mmap"} core: KernelInvariant[Core]
channel_base: KernelInvariant[int32]
t_frame: KernelInvariant[int32]
miso_delay: KernelInvariant[int32]
frame_tstamp: Kernel[int64]
clk_sel: Kernel[bool]
tune_fifo_offset: Kernel[bool]
sync_dly: Kernel[int32]
dac_mmap: KernelInvariant[list[int32]]
channel: Kernel[list[PhaserChannel]]
def __init__(self, dmgr, channel_base, miso_delay=1, tune_fifo_offset=True, 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, clk_sel=False, sync_dly=0, dac=None, trf0=None, trf1=None,
core_device="core"): core_device="core"):
self.channel_base = channel_base self.channel_base = channel_base
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -172,7 +197,7 @@ class Phaser:
for ch, trf in enumerate([trf0, trf1])] for ch, trf in enumerate([trf0, trf1])]
@kernel @kernel
def init(self, debug=False): def init(self, debug: bool = False):
"""Initialize the board. """Initialize the board.
Verifies board and chip presence, resets components, performs Verifies board and chip presence, resets components, performs
@ -182,40 +207,40 @@ class Phaser:
board_id = self.read8(PHASER_ADDR_BOARD_ID) board_id = self.read8(PHASER_ADDR_BOARD_ID)
if board_id != PHASER_BOARD_ID: if board_id != PHASER_BOARD_ID:
raise ValueError("invalid board id") raise ValueError("invalid board id")
delay(.1*ms) # slack self.core.delay(.1*ms) # slack
hw_rev = self.read8(PHASER_ADDR_HW_REV) hw_rev = self.read8(PHASER_ADDR_HW_REV)
delay(.1*ms) # slack self.core.delay(.1*ms) # slack
is_baseband = hw_rev & PHASER_HW_REV_VARIANT is_baseband = hw_rev & PHASER_HW_REV_VARIANT != 0
gw_rev = self.read8(PHASER_ADDR_GW_REV) gw_rev = self.read8(PHASER_ADDR_GW_REV)
if debug: if debug:
print("gw_rev:", gw_rev) # NAC3TODO print("gw_rev:", gw_rev)
self.core.break_realtime() self.core.break_realtime()
delay(.1*ms) # slack self.core.delay(.1*ms) # slack
# allow a few errors during startup and alignment since boot # allow a few errors during startup and alignment since boot
if self.get_crc_err() > 20: if self.get_crc_err() > 20:
raise ValueError("large number of frame CRC errors") raise ValueError("large number of frame CRC errors")
delay(.1*ms) # slack self.core.delay(.1*ms) # slack
# determine the origin for frame-aligned timestamps # determine the origin for frame-aligned timestamps
self.measure_frame_timestamp() self.measure_frame_timestamp()
if self.frame_tstamp < 0: if self.frame_tstamp < int64(0):
raise ValueError("frame timestamp measurement timed out") raise ValueError("frame timestamp measurement timed out")
delay(.1*ms) self.core.delay(.1*ms)
# reset # reset
self.set_cfg(dac_resetb=0, dac_sleep=1, dac_txena=0, self.set_cfg(dac_resetb=False, dac_sleep=True, dac_txena=False,
trf0_ps=1, trf1_ps=1, trf0_ps=True, trf1_ps=True,
att0_rstn=0, att1_rstn=0) att0_rstn=False, att1_rstn=False)
self.set_leds(0x00) self.set_leds(0x00)
self.set_fan_mu(0) self.set_fan_mu(0)
# bring dac out of reset, keep tx off # bring dac out of reset, keep tx off
self.set_cfg(clk_sel=self.clk_sel, dac_txena=0, self.set_cfg(clk_sel=self.clk_sel, dac_txena=False,
trf0_ps=1, trf1_ps=1, trf0_ps=True, trf1_ps=True,
att0_rstn=0, att1_rstn=0) att0_rstn=False, att1_rstn=False)
delay(.1*ms) # slack self.core.delay(.1*ms) # slack
# crossing dac_clk (reference) edges with sync_dly # crossing dac_clk (reference) edges with sync_dly
# changes the optimal fifo_offset by 4 # changes the optimal fifo_offset by 4
@ -225,25 +250,25 @@ class Phaser:
self.dac_write(0x02, 0x0080) self.dac_write(0x02, 0x0080)
if self.dac_read(0x7f) != 0x5409: if self.dac_read(0x7f) != 0x5409:
raise ValueError("DAC version readback invalid") raise ValueError("DAC version readback invalid")
delay(.1*ms) self.core.delay(.1*ms)
if self.dac_read(0x00) != 0x049c: if self.dac_read(0x00) != 0x049c:
raise ValueError("DAC config0 reset readback invalid") raise ValueError("DAC config0 reset readback invalid")
delay(.1*ms) self.core.delay(.1*ms)
t = self.get_dac_temperature() t = self.get_dac_temperature()
delay(.1*ms) self.core.delay(.1*ms)
if t < 10 or t > 90: if t < 10 or t > 90:
raise ValueError("DAC temperature out of bounds") raise ValueError("DAC temperature out of bounds")
for data in self.dac_mmap: for data in self.dac_mmap:
self.dac_write(data >> 16, data) self.dac_write(data >> 16, data)
delay(40*us) self.core.delay(40.*us)
self.dac_sync() self.dac_sync()
delay(40*us) self.core.delay(40.*us)
# pll_ndivsync_ena disable # pll_ndivsync_ena disable
config18 = self.dac_read(0x18) config18 = self.dac_read(0x18)
delay(.1*ms) self.core.delay(.1*ms)
self.dac_write(0x18, config18 & ~0x0800) self.dac_write(0x18, config18 & ~0x0800)
patterns = [ patterns = [
@ -256,33 +281,33 @@ class Phaser:
# either side) and no need to tune at runtime. # either side) and no need to tune at runtime.
# Parity provides another level of safety. # Parity provides another level of safety.
for i in range(len(patterns)): for i in range(len(patterns)):
delay(.5*ms) self.core.delay(.5*ms)
errors = self.dac_iotest(patterns[i]) errors = self.dac_iotest(patterns[i])
if errors: if errors != 0:
raise ValueError("DAC iotest failure") raise ValueError("DAC iotest failure")
delay(2*ms) # let it settle self.core.delay(2.*ms) # let it settle
lvolt = self.dac_read(0x18) & 7 lvolt = self.dac_read(0x18) & 7
delay(.1*ms) self.core.delay(.1*ms)
if lvolt < 2 or lvolt > 5: if lvolt < 2 or lvolt > 5:
raise ValueError("DAC PLL lock failed, check clocking") raise ValueError("DAC PLL lock failed, check clocking")
if self.tune_fifo_offset: if self.tune_fifo_offset:
fifo_offset = self.dac_tune_fifo_offset() fifo_offset = self.dac_tune_fifo_offset()
if debug: if debug:
print("fifo_offset:", fifo_offset) # NAC3TODO print("fifo_offset:", fifo_offset)
self.core.break_realtime() self.core.break_realtime()
# self.dac_write(0x20, 0x0000) # stop fifo sync # self.dac_write(0x20, 0x0000) # stop fifo sync
# alarm = self.get_sta() & 1 # alarm = self.get_sta() & 1
# delay(.1*ms) # self.core.delay(.1*ms)
self.clear_dac_alarms() self.clear_dac_alarms()
delay(2*ms) # let it run a bit self.core.delay(2.*ms) # let it run a bit
alarms = self.get_dac_alarms() alarms = self.get_dac_alarms()
delay(.1*ms) # slack self.core.delay(.1*ms) # slack
if alarms & ~0x0040: # ignore PLL alarms (see DS) if alarms & ~0x0040 != 0: # ignore PLL alarms (see DS)
if debug: if debug:
print("alarms:", alarms) # NAC3TODO print("alarms:", alarms)
self.core.break_realtime() self.core.break_realtime()
# ignore alarms # ignore alarms
else: else:
@ -290,16 +315,16 @@ class Phaser:
# avoid malformed output for: mixer_ena=1, nco_ena=0 after power up # avoid malformed output for: mixer_ena=1, nco_ena=0 after power up
self.dac_write(self.dac_mmap[2] >> 16, self.dac_mmap[2] | (1 << 4)) self.dac_write(self.dac_mmap[2] >> 16, self.dac_mmap[2] | (1 << 4))
delay(40*us) self.core.delay(40.*us)
self.dac_sync() self.dac_sync()
delay(100*us) self.core.delay(100.*us)
self.dac_write(self.dac_mmap[2] >> 16, self.dac_mmap[2]) self.dac_write(self.dac_mmap[2] >> 16, self.dac_mmap[2])
delay(40*us) self.core.delay(40.*us)
self.dac_sync() self.dac_sync()
delay(100*us) self.core.delay(100.*us)
# power up trfs, release att reset # power up trfs, release att reset
self.set_cfg(clk_sel=self.clk_sel, dac_txena=0) self.set_cfg(clk_sel=self.clk_sel, dac_txena=False)
for ch in range(2): for ch in range(2):
channel = self.channel[ch] channel = self.channel[ch]
@ -307,7 +332,7 @@ class Phaser:
channel.set_att_mu(0x5a) channel.set_att_mu(0x5a)
if channel.get_att_mu() != 0x5a: if channel.get_att_mu() != 0x5a:
raise ValueError("attenuator test failed") raise ValueError("attenuator test failed")
delay(.1*ms) self.core.delay(.1*ms)
channel.set_att_mu(0x00) # minimum attenuation channel.set_att_mu(0x00) # minimum attenuation
# test oscillators and DUC # test oscillators and DUC
@ -317,18 +342,18 @@ class Phaser:
if i == 0: if i == 0:
asf = 0x7fff asf = 0x7fff
# 6pi/4 phase # 6pi/4 phase
oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=1) oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=True)
delay(1*us) self.core.delay(1.*us)
# 3pi/4 # 3pi/4
channel.set_duc_phase_mu(0x6000) channel.set_duc_phase_mu(0x6000)
channel.set_duc_cfg(select=0, clr=1) channel.set_duc_cfg(select=0, clr=True)
self.duc_stb() self.duc_stb()
delay(.1*ms) # settle link, pipeline and impulse response self.core.delay(.1*ms) # settle link, pipeline and impulse response
data = channel.get_dac_data() data = channel.get_dac_data()
delay(1*us) self.core.delay(1.*us)
channel.oscillator[0].set_amplitude_phase_mu(asf=0, pow=0xc000, channel.oscillator[0].set_amplitude_phase_mu(asf=0, pow=0xc000,
clr=1) clr=True)
delay(.1*ms) self.core.delay(.1*ms)
sqrt2 = 0x5a81 # 0x7fff/sqrt(2) sqrt2 = 0x5a81 # 0x7fff/sqrt(2)
data_i = data & 0xffff data_i = data & 0xffff
data_q = (data >> 16) & 0xffff data_q = (data >> 16) & 0xffff
@ -342,27 +367,27 @@ class Phaser:
if channel.trf_read(0) & 0x7f != 0x68: if channel.trf_read(0) & 0x7f != 0x68:
raise ValueError("TRF identification failed") raise ValueError("TRF identification failed")
delay(.1*ms) self.core.delay(.1*ms)
delay(.2*ms) self.core.delay(.2*ms)
for data in channel.trf_mmap: for data in channel.trf_mmap:
channel.trf_write(data) channel.trf_write(data)
channel.cal_trf_vco() channel.cal_trf_vco()
delay(2*ms) # lock self.core.delay(2.*ms) # lock
if not (self.get_sta() & (PHASER_STA_TRF0_LD << ch)): if not (self.get_sta() & (PHASER_STA_TRF0_LD << ch)):
raise ValueError("TRF lock failure") raise ValueError("TRF lock failure")
delay(.1*ms) self.core.delay(.1*ms)
if channel.trf_read(0) & 0x1000: if channel.trf_read(0) & 0x1000 != 0:
raise ValueError("TRF R_SAT_ERR") raise ValueError("TRF R_SAT_ERR")
delay(.1*ms) self.core.delay(.1*ms)
channel.en_trf_out() channel.en_trf_out()
# enable dac tx # enable dac tx
self.set_cfg(clk_sel=self.clk_sel) self.set_cfg(clk_sel=self.clk_sel)
@kernel @kernel
def write8(self, addr, data): def write8(self, addr: int32, data: int32):
"""Write data to FPGA register. """Write data to FPGA register.
:param addr: Address to write to (7 bit) :param addr: Address to write to (7 bit)
@ -372,7 +397,7 @@ class Phaser:
delay_mu(int64(self.t_frame)) delay_mu(int64(self.t_frame))
@kernel @kernel
def read8(self, addr) -> TInt32: def read8(self, addr: int32) -> int32:
"""Read from FPGA register. """Read from FPGA register.
:param addr: Address to read from (7 bit) :param addr: Address to read from (7 bit)
@ -383,7 +408,7 @@ class Phaser:
return response >> self.miso_delay return response >> self.miso_delay
@kernel @kernel
def write32(self, addr, data: TInt32): def write32(self, addr: int32, data: int32):
"""Write 32 bit to a sequence of FPGA registers.""" """Write 32 bit to a sequence of FPGA registers."""
for offset in range(4): for offset in range(4):
byte = data >> 24 byte = data >> 24
@ -391,17 +416,17 @@ class Phaser:
data <<= 8 data <<= 8
@kernel @kernel
def read32(self, addr) -> TInt32: def read32(self, addr: int32) -> int32:
"""Read 32 bit from a sequence of FPGA registers.""" """Read 32 bit from a sequence of FPGA registers."""
data = 0 data = 0
for offset in range(4): for offset in range(4):
data <<= 8 data <<= 8
data |= self.read8(addr + offset) data |= self.read8(addr + offset)
delay(20*us) # slack self.core.delay(20.*us) # slack
return data return data
@kernel @kernel
def set_leds(self, leds): def set_leds(self, leds: int32):
"""Set the front panel LEDs. """Set the front panel LEDs.
:param leds: LED settings (6 bit) :param leds: LED settings (6 bit)
@ -409,7 +434,7 @@ class Phaser:
self.write8(PHASER_ADDR_LED, leds) self.write8(PHASER_ADDR_LED, leds)
@kernel @kernel
def set_fan_mu(self, pwm): def set_fan_mu(self, pwm: int32):
"""Set the fan duty cycle. """Set the fan duty cycle.
:param pwm: Duty cycle in machine units (8 bit) :param pwm: Duty cycle in machine units (8 bit)
@ -417,19 +442,19 @@ class Phaser:
self.write8(PHASER_ADDR_FAN, pwm) self.write8(PHASER_ADDR_FAN, pwm)
@kernel @kernel
def set_fan(self, duty): def set_fan(self, duty: float):
"""Set the fan duty cycle. """Set the fan duty cycle.
:param duty: Duty cycle (0. to 1.) :param duty: Duty cycle (0. to 1.)
""" """
pwm = int32(round(duty*255.)) pwm = round(duty*255.)
if pwm < 0 or pwm > 255: if pwm < 0 or pwm > 255:
raise ValueError("duty cycle out of bounds") raise ValueError("duty cycle out of bounds")
self.set_fan_mu(pwm) self.set_fan_mu(pwm)
@kernel @kernel
def set_cfg(self, clk_sel=0, dac_resetb=1, dac_sleep=0, dac_txena=1, def set_cfg(self, clk_sel: bool = False, dac_resetb: bool = True, dac_sleep: bool = False, dac_txena: bool = True,
trf0_ps=0, trf1_ps=0, att0_rstn=1, att1_rstn=1): trf0_ps: bool = False, trf1_ps: bool = False, att0_rstn: bool = True, att1_rstn: bool = True):
"""Set the configuration register. """Set the configuration register.
Each flag is a single bit (0 or 1). Each flag is a single bit (0 or 1).
@ -444,13 +469,13 @@ class Phaser:
:param att1_rstn: Active low attenuator 1 reset :param att1_rstn: Active low attenuator 1 reset
""" """
self.write8(PHASER_ADDR_CFG, self.write8(PHASER_ADDR_CFG,
((clk_sel & 1) << 0) | ((dac_resetb & 1) << 1) | (int32(clk_sel) << 0) | (int32(dac_resetb) << 1) |
((dac_sleep & 1) << 2) | ((dac_txena & 1) << 3) | (int32(dac_sleep) << 2) | (int32(dac_txena) << 3) |
((trf0_ps & 1) << 4) | ((trf1_ps & 1) << 5) | (int32(trf0_ps) << 4) | (int32(trf1_ps) << 5) |
((att0_rstn & 1) << 6) | ((att1_rstn & 1) << 7)) (int32(att0_rstn) << 6) | (int32(att1_rstn) << 7))
@kernel @kernel
def get_sta(self): def get_sta(self) -> int32:
"""Get the status register value. """Get the status register value.
Bit flags are: Bit flags are:
@ -468,7 +493,7 @@ class Phaser:
return self.read8(PHASER_ADDR_STA) return self.read8(PHASER_ADDR_STA)
@kernel @kernel
def get_crc_err(self): def get_crc_err(self) -> int32:
"""Get the frame CRC error counter. """Get the frame CRC error counter.
:return: The number of frames with CRC mismatches sind the reset of the :return: The number of frames with CRC mismatches sind the reset of the
@ -484,21 +509,21 @@ class Phaser:
See `get_next_frame_mu()`. See `get_next_frame_mu()`.
""" """
rtio_output(self.channel_base << 8, 0) # read any register rtio_output(self.channel_base << 8, 0) # read any register
self.frame_tstamp = rtio_input_timestamp(now_mu() + 4 * self.t_frame, self.channel_base) self.frame_tstamp = rtio_input_timestamp(now_mu() + int64(4) * int64(self.t_frame), self.channel_base)
delay(100 * us) self.core.delay(100. * us)
@kernel @kernel
def get_next_frame_mu(self): def get_next_frame_mu(self) -> int64:
"""Return the timestamp of the frame strictly after `now_mu()`. """Return the timestamp of the frame strictly after `now_mu()`.
Register updates (DUC, DAC, TRF, etc.) scheduled at this timestamp and multiples Register updates (DUC, DAC, TRF, etc.) scheduled at this timestamp and multiples
of `self.t_frame` later will have deterministic latency to output. of `self.t_frame` later will have deterministic latency to output.
""" """
n = int64((now_mu() - self.frame_tstamp) / self.t_frame) n = int64((now_mu() - self.frame_tstamp) / int64(self.t_frame))
return self.frame_tstamp + (n + 1) * self.t_frame return self.frame_tstamp + (n + int64(1)) * int64(self.t_frame)
@kernel @kernel
def set_sync_dly(self, dly): def set_sync_dly(self, dly: int32):
"""Set SYNC delay. """Set SYNC delay.
:param dly: DAC SYNC delay setting (0 to 7) :param dly: DAC SYNC delay setting (0 to 7)
@ -517,8 +542,8 @@ class Phaser:
self.write8(PHASER_ADDR_DUC_STB, 0) self.write8(PHASER_ADDR_DUC_STB, 0)
@kernel @kernel
def spi_cfg(self, select, div, end, clk_phase=0, clk_polarity=0, def spi_cfg(self, select: int32, div: int32, end: bool, clk_phase: bool = False, clk_polarity: bool = False,
half_duplex=0, lsb_first=0, offline=0, length=8): half_duplex: bool = False, lsb_first: bool = False, offline: bool = False, length: int32 = 8):
"""Set the SPI machine configuration """Set the SPI machine configuration
:param select: Chip selects to assert (DAC, TRF0, TRF1, ATT0, ATT1) :param select: Chip selects to assert (DAC, TRF0, TRF1, ATT0, ATT1)
@ -538,63 +563,63 @@ class Phaser:
self.write8(PHASER_ADDR_SPI_SEL, select) self.write8(PHASER_ADDR_SPI_SEL, select)
self.write8(PHASER_ADDR_SPI_DIVLEN, (div - 2 >> 3) | (length - 1 << 5)) self.write8(PHASER_ADDR_SPI_DIVLEN, (div - 2 >> 3) | (length - 1 << 5))
self.write8(PHASER_ADDR_SPI_CFG, self.write8(PHASER_ADDR_SPI_CFG,
((offline & 1) << 0) | ((end & 1) << 1) | (int32(offline) << 0) | (int32(end) << 1) |
((clk_phase & 1) << 2) | ((clk_polarity & 1) << 3) | (int32(clk_phase) << 2) | (int32(clk_polarity) << 3) |
((half_duplex & 1) << 4) | ((lsb_first & 1) << 5)) (int32(half_duplex) << 4) | (int32(lsb_first) << 5))
@kernel @kernel
def spi_write(self, data): def spi_write(self, data: int32):
"""Write 8 bits into the SPI data register and start/continue the """Write 8 bits into the SPI data register and start/continue the
transaction.""" transaction."""
self.write8(PHASER_ADDR_SPI_DATW, data) self.write8(PHASER_ADDR_SPI_DATW, data)
@kernel @kernel
def spi_read(self): def spi_read(self) -> int32:
"""Read from the SPI input data register.""" """Read from the SPI input data register."""
return self.read8(PHASER_ADDR_SPI_DATR) return self.read8(PHASER_ADDR_SPI_DATR)
@kernel @kernel
def dac_write(self, addr, data): def dac_write(self, addr: int32, data: int32):
"""Write 16 bit to a DAC register. """Write 16 bit to a DAC register.
:param addr: Register address :param addr: Register address
:param data: Register data to write :param data: Register data to write
""" """
div = 34 # 100 ns min period div = 34 # 100 ns min period
t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns)
self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=False)
self.spi_write(addr) self.spi_write(addr)
delay_mu(t_xfer) delay_mu(t_xfer)
self.spi_write(data >> 8) self.spi_write(data >> 8)
delay_mu(t_xfer) delay_mu(t_xfer)
self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=1) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=True)
self.spi_write(data) self.spi_write(data)
delay_mu(t_xfer) delay_mu(t_xfer)
@kernel @kernel
def dac_read(self, addr, div=34) -> TInt32: def dac_read(self, addr: int32, div: int32 = 34) -> int32:
"""Read from a DAC register. """Read from a DAC register.
:param addr: Register address to read from :param addr: Register address to read from
:param div: SPI clock divider. Needs to be at least 250 (1 µs SPI :param div: SPI clock divider. Needs to be at least 250 (1 µs SPI
clock) to read the temperature register. clock) to read the temperature register.
""" """
t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns)
self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=False)
self.spi_write(addr | 0x80) self.spi_write(addr | 0x80)
delay_mu(t_xfer) delay_mu(t_xfer)
self.spi_write(0) self.spi_write(0)
delay_mu(t_xfer) delay_mu(t_xfer)
data = self.spi_read() << 8 data = self.spi_read() << 8
delay(20*us) # slack self.core.delay(20.*us) # slack
self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=1) self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=True)
self.spi_write(0) self.spi_write(0)
delay_mu(t_xfer) delay_mu(t_xfer)
data |= self.spi_read() data |= self.spi_read()
return data return data
@kernel @kernel
def get_dac_temperature(self) -> TInt32: def get_dac_temperature(self) -> int32:
"""Read the DAC die temperature. """Read the DAC die temperature.
:return: DAC temperature in degree Celsius :return: DAC temperature in degree Celsius
@ -617,12 +642,12 @@ class Phaser:
.. note:: Synchronising the NCO clears the phase-accumulator .. note:: Synchronising the NCO clears the phase-accumulator
""" """
config1f = self.dac_read(0x1f) config1f = self.dac_read(0x1f)
delay(.1*ms) self.core.delay(.1*ms)
self.dac_write(0x1f, config1f & ~int32(1 << 1)) self.dac_write(0x1f, config1f & ~int32(1 << 1))
self.dac_write(0x1f, config1f | (1 << 1)) self.dac_write(0x1f, config1f | (1 << 1))
@kernel @kernel
def set_dac_cmix(self, fs_8_step): def set_dac_cmix(self, fs_8_step: int32):
"""Set the DAC coarse mixer frequency for both channels """Set the DAC coarse mixer frequency for both channels
Use of the coarse mixer requires the DAC mixer to be enabled. The mixer Use of the coarse mixer requires the DAC mixer to be enabled. The mixer
@ -640,11 +665,11 @@ class Phaser:
vals = [0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0001, 0b1110] vals = [0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0001, 0b1110]
cmix = vals[fs_8_step%8] cmix = vals[fs_8_step%8]
config0d = self.dac_read(0x0d) config0d = self.dac_read(0x0d)
delay(.1*ms) self.core.delay(.1*ms)
self.dac_write(0x0d, (config0d & ~(0b1111 << 12)) | (cmix << 12)) self.dac_write(0x0d, (config0d & ~(0b1111 << 12)) | (cmix << 12))
@kernel @kernel
def get_dac_alarms(self): def get_dac_alarms(self) -> int32:
"""Read the DAC alarm flags. """Read the DAC alarm flags.
:return: DAC alarm flags (see datasheet for bit meaning) :return: DAC alarm flags (see datasheet for bit meaning)
@ -657,7 +682,7 @@ class Phaser:
self.dac_write(0x05, 0x0000) self.dac_write(0x05, 0x0000)
@kernel @kernel
def dac_iotest(self, pattern) -> TInt32: def dac_iotest(self, pattern: list[int32]) -> int32:
"""Performs a DAC IO test according to the datasheet. """Performs a DAC IO test according to the datasheet.
:param pattern: List of four int32 containing the pattern :param pattern: List of four int32 containing the pattern
@ -669,7 +694,7 @@ class Phaser:
self.dac_write(0x25 + addr, pattern[addr]) self.dac_write(0x25 + addr, pattern[addr])
# repeat the pattern twice # repeat the pattern twice
self.dac_write(0x29 + addr, pattern[addr]) self.dac_write(0x29 + addr, pattern[addr])
delay(.1*ms) self.core.delay(.1*ms)
for ch in range(2): for ch in range(2):
channel = self.channel[ch] channel = self.channel[ch]
channel.set_duc_cfg(select=1) # test channel.set_duc_cfg(select=1) # test
@ -678,20 +703,20 @@ class Phaser:
channel.set_dac_test(data) channel.set_dac_test(data)
if channel.get_dac_data() != data: if channel.get_dac_data() != data:
raise ValueError("DAC test data readback failed") raise ValueError("DAC test data readback failed")
delay(.1*ms) self.core.delay(.1*ms)
cfg = self.dac_read(0x01) cfg = self.dac_read(0x01)
delay(.1*ms) self.core.delay(.1*ms)
self.dac_write(0x01, cfg | 0x8000) # iotest_ena self.dac_write(0x01, cfg | 0x8000) # iotest_ena
self.dac_write(0x04, 0x0000) # clear iotest_result self.dac_write(0x04, 0x0000) # clear iotest_result
delay(.2*ms) # let it rip self.core.delay(.2*ms) # let it rip
# no need to go through the alarm register, # no need to go through the alarm register,
# just read the error mask # just read the error mask
# self.clear_dac_alarms() # self.clear_dac_alarms()
alarms = self.get_dac_alarms() alarms = self.get_dac_alarms()
delay(.1*ms) # slack self.core.delay(.1*ms) # slack
if alarms & 0x0080: # alarm_from_iotest if alarms & 0x0080 != 0: # alarm_from_iotest
errors = self.dac_read(0x04) errors = self.dac_read(0x04)
delay(.1*ms) # slack self.core.delay(.1*ms) # slack
else: else:
errors = 0 errors = 0
self.dac_write(0x01, cfg) # clear config self.dac_write(0x01, cfg) # clear config
@ -699,7 +724,7 @@ class Phaser:
return errors return errors
@kernel @kernel
def dac_tune_fifo_offset(self): def dac_tune_fifo_offset(self) -> int32:
"""Scan through `fifo_offset` and configure midpoint setting. """Scan through `fifo_offset` and configure midpoint setting.
:return: Optimal `fifo_offset` setting with maximum margin to write :return: Optimal `fifo_offset` setting with maximum margin to write
@ -712,15 +737,15 @@ class Phaser:
# distance 32101234 # distance 32101234
# error free x xx # error free x xx
config9 = self.dac_read(0x09) config9 = self.dac_read(0x09)
delay(.1*ms) self.core.delay(.1*ms)
good = 0 good = 0
for o in range(8): for o in range(8):
# set new fifo_offset # set new fifo_offset
self.dac_write(0x09, (config9 & 0x1fff) | (o << 13)) self.dac_write(0x09, (config9 & 0x1fff) | (o << 13))
self.clear_dac_alarms() self.clear_dac_alarms()
delay(.1*ms) # run self.core.delay(.1*ms) # run
alarms = self.get_dac_alarms() alarms = self.get_dac_alarms()
delay(.1*ms) # slack self.core.delay(.1*ms) # slack
if (alarms >> 11) & 0x7 == 0: # any fifo alarm if (alarms >> 11) & 0x7 == 0: # any fifo alarm
good |= 1 << o good |= 1 << o
# if there are good offsets accross the wrap around # if there are good offsets accross the wrap around
@ -734,7 +759,7 @@ class Phaser:
sum = 0 sum = 0
count = 0 count = 0
for o in range(8): for o in range(8):
if good & (1 << o): if good & (1 << o) != 0:
sum += o sum += o
count += 1 count += 1
best = ((sum // count) + offset) % 8 best = ((sum // count) + offset) % 8
@ -742,6 +767,7 @@ class Phaser:
return best return best
@nac3
class PhaserChannel: class PhaserChannel:
"""Phaser channel IQ pair. """Phaser channel IQ pair.
@ -772,9 +798,15 @@ class PhaserChannel:
or overflow after the interpolation. Either band-limit any changes or overflow after the interpolation. Either band-limit any changes
in the oscillator parameters or back off the amplitude sufficiently. in the oscillator parameters or back off the amplitude sufficiently.
""" """
kernel_invariants = {"index", "phaser", "trf_mmap"}
core: KernelInvariant[Core]
phaser: KernelInvariant[Phaser]
index: KernelInvariant[int32]
trf_mmap: KernelInvariant[list[int32]]
oscillator: Kernel[list[PhaserOscillator]]
def __init__(self, phaser, index, trf): def __init__(self, phaser, index, trf):
self.core = phaser.core
self.phaser = phaser self.phaser = phaser
self.index = index self.index = index
self.trf_mmap = TRF372017(trf).get_mmap() self.trf_mmap = TRF372017(trf).get_mmap()
@ -782,7 +814,7 @@ class PhaserChannel:
self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)] self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)]
@kernel @kernel
def get_dac_data(self) -> TInt32: def get_dac_data(self) -> int32:
"""Get a sample of the current DAC data. """Get a sample of the current DAC data.
The data is split accross multiple registers and thus the data The data is split accross multiple registers and thus the data
@ -794,7 +826,7 @@ class PhaserChannel:
return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.index << 4)) return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.index << 4))
@kernel @kernel
def set_dac_test(self, data: TInt32): def set_dac_test(self, data: int32):
"""Set the DAC test data. """Set the DAC test data.
:param data: 32 bit IQ test data, I/DACA/DACC in the 16 LSB, :param data: 32 bit IQ test data, I/DACA/DACC in the 16 LSB,
@ -803,7 +835,7 @@ class PhaserChannel:
self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.index << 4), data) self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.index << 4), data)
@kernel @kernel
def set_duc_cfg(self, clr=0, clr_once=0, select=0): def set_duc_cfg(self, clr: bool = False, clr_once: bool = False, select: int32 = 0):
"""Set the digital upconverter (DUC) and interpolator configuration. """Set the digital upconverter (DUC) and interpolator configuration.
:param clr: Keep the phase accumulator cleared (persistent) :param clr: Keep the phase accumulator cleared (persistent)
@ -812,11 +844,11 @@ class PhaserChannel:
data, other values: reserved) data, other values: reserved)
""" """
self.phaser.write8(PHASER_ADDR_DUC0_CFG + (self.index << 4), self.phaser.write8(PHASER_ADDR_DUC0_CFG + (self.index << 4),
((clr & 1) << 0) | ((clr_once & 1) << 1) | (int32(clr) << 0) | (int32(clr_once) << 1) |
((select & 3) << 2)) ((select & 3) << 2))
@kernel @kernel
def set_duc_frequency_mu(self, ftw): def set_duc_frequency_mu(self, ftw: int32):
"""Set the DUC frequency. """Set the DUC frequency.
:param ftw: DUC frequency tuning word (32 bit) :param ftw: DUC frequency tuning word (32 bit)
@ -824,17 +856,17 @@ class PhaserChannel:
self.phaser.write32(PHASER_ADDR_DUC0_F + (self.index << 4), ftw) self.phaser.write32(PHASER_ADDR_DUC0_F + (self.index << 4), ftw)
@kernel @kernel
def set_duc_frequency(self, frequency): def set_duc_frequency(self, frequency: float):
"""Set the DUC frequency in SI units. """Set the DUC frequency in SI units.
:param frequency: DUC frequency in Hz (passband from -200 MHz to :param frequency: DUC frequency in Hz (passband from -200 MHz to
200 MHz, wrapping around at +- 250 MHz) 200 MHz, wrapping around at +- 250 MHz)
""" """
ftw = int32(round(frequency*((1 << 30)/(125*MHz)))) ftw = round(frequency*(float(1 << 30)/(125.*MHz)))
self.set_duc_frequency_mu(ftw) self.set_duc_frequency_mu(ftw)
@kernel @kernel
def set_duc_phase_mu(self, pow): def set_duc_phase_mu(self, pow: int32):
"""Set the DUC phase offset. """Set the DUC phase offset.
:param pow: DUC phase offset word (16 bit) :param pow: DUC phase offset word (16 bit)
@ -844,16 +876,16 @@ class PhaserChannel:
self.phaser.write8(addr + 1, pow) self.phaser.write8(addr + 1, pow)
@kernel @kernel
def set_duc_phase(self, phase): def set_duc_phase(self, phase: float):
"""Set the DUC phase in SI units. """Set the DUC phase in SI units.
:param phase: DUC phase in turns :param phase: DUC phase in turns
""" """
pow = int32(round(phase*(1 << 16))) pow = round(phase*float(1 << 16))
self.set_duc_phase_mu(pow) self.set_duc_phase_mu(pow)
@kernel @kernel
def set_nco_frequency_mu(self, ftw): def set_nco_frequency_mu(self, ftw: int32):
"""Set the NCO frequency. """Set the NCO frequency.
This method stages the new NCO frequency, but does not apply it. This method stages the new NCO frequency, but does not apply it.
@ -868,7 +900,7 @@ class PhaserChannel:
self.phaser.dac_write(0x14 + (self.index << 1), ftw) self.phaser.dac_write(0x14 + (self.index << 1), ftw)
@kernel @kernel
def set_nco_frequency(self, frequency): def set_nco_frequency(self, frequency: float):
"""Set the NCO frequency in SI units. """Set the NCO frequency in SI units.
This method stages the new NCO frequency, but does not apply it. This method stages the new NCO frequency, but does not apply it.
@ -880,11 +912,11 @@ class PhaserChannel:
:param frequency: NCO frequency in Hz (passband from -400 MHz :param frequency: NCO frequency in Hz (passband from -400 MHz
to 400 MHz, wrapping around at +- 500 MHz) to 400 MHz, wrapping around at +- 500 MHz)
""" """
ftw = int32(round(frequency*((1 << 30)/(250*MHz)))) ftw = round(frequency*(float(1 << 30)/(250.*MHz)))
self.set_nco_frequency_mu(ftw) self.set_nco_frequency_mu(ftw)
@kernel @kernel
def set_nco_phase_mu(self, pow): def set_nco_phase_mu(self, pow: int32):
"""Set the NCO phase offset. """Set the NCO phase offset.
By default, the new NCO phase applies on completion of the SPI By default, the new NCO phase applies on completion of the SPI
@ -902,7 +934,7 @@ class PhaserChannel:
self.phaser.dac_write(0x12 + self.index, pow) self.phaser.dac_write(0x12 + self.index, pow)
@kernel @kernel
def set_nco_phase(self, phase): def set_nco_phase(self, phase: float):
"""Set the NCO phase in SI units. """Set the NCO phase in SI units.
By default, the new NCO phase applies on completion of the SPI By default, the new NCO phase applies on completion of the SPI
@ -917,36 +949,36 @@ class PhaserChannel:
:param phase: NCO phase in turns :param phase: NCO phase in turns
""" """
pow = int32(round(phase*(1 << 16))) pow = round(phase*float(1 << 16))
self.set_nco_phase_mu(pow) self.set_nco_phase_mu(pow)
@kernel @kernel
def set_att_mu(self, data): def set_att_mu(self, data: int32):
"""Set channel attenuation. """Set channel attenuation.
:param data: Attenuator data in machine units (8 bit) :param data: Attenuator data in machine units (8 bit)
""" """
div = 34 # 30 ns min period div = 34 # 30 ns min period
t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns)
self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div, self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div,
end=1) end=True)
self.phaser.spi_write(data) self.phaser.spi_write(data)
delay_mu(t_xfer) delay_mu(t_xfer)
@kernel @kernel
def set_att(self, att): def set_att(self, att: float):
"""Set channel attenuation in SI units. """Set channel attenuation in SI units.
:param att: Attenuation in dB :param att: Attenuation in dB
""" """
# 2 lsb are inactive, resulting in 8 LSB per dB # 2 lsb are inactive, resulting in 8 LSB per dB
data = 0xff - int32(round(att*8)) data = 0xff - round(att*8.)
if data < 0 or data > 0xff: if data < 0 or data > 0xff:
raise ValueError("attenuation out of bounds") raise ValueError("attenuation out of bounds")
self.set_att_mu(data) self.set_att_mu(data)
@kernel @kernel
def get_att_mu(self) -> TInt32: def get_att_mu(self) -> int32:
"""Read current attenuation. """Read current attenuation.
The current attenuation value is read without side effects. The current attenuation value is read without side effects.
@ -954,39 +986,39 @@ class PhaserChannel:
:return: Current attenuation in machine units :return: Current attenuation in machine units
""" """
div = 34 div = 34
t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns)
self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div, self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div,
end=0) end=False)
self.phaser.spi_write(0) self.phaser.spi_write(0)
delay_mu(t_xfer) delay_mu(t_xfer)
data = self.phaser.spi_read() data = self.phaser.spi_read()
delay(20*us) # slack self.core.delay(20.*us) # slack
self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div, self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div,
end=1) end=True)
self.phaser.spi_write(data) self.phaser.spi_write(data)
delay_mu(t_xfer) delay_mu(t_xfer)
return data return data
@kernel @kernel
def trf_write(self, data, readback=False): def trf_write(self, data: int32, readback: bool = False) -> int32:
"""Write 32 bits to quadrature upconverter register. """Write 32 bits to quadrature upconverter register.
:param data: Register data (32 bit) containing encoded address :param data: Register data (32 bit) containing encoded address
:param readback: Whether to return the read back MISO data :param readback: Whether to return the read back MISO data
""" """
div = 34 # 50 ns min period div = 34 # 50 ns min period
t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns)
read = 0 read = 0
end = 0 end = False
clk_phase = 0 clk_phase = False
if readback: if readback:
clk_phase = 1 clk_phase = True
for i in range(4): for i in range(4):
if i == 0 or i == 3: if i == 0 or i == 3:
if i == 3: if i == 3:
end = 1 end = True
self.phaser.spi_cfg(select=PHASER_SEL_TRF0 << self.index, self.phaser.spi_cfg(select=PHASER_SEL_TRF0 << self.index,
div=div, lsb_first=1, clk_phase=clk_phase, div=div, lsb_first=True, clk_phase=clk_phase,
end=end) end=end)
self.phaser.spi_write(data) self.phaser.spi_write(data)
data >>= 8 data >>= 8
@ -994,22 +1026,22 @@ class PhaserChannel:
if readback: if readback:
read >>= 8 read >>= 8
read |= self.phaser.spi_read() << 24 read |= self.phaser.spi_read() << 24
delay(20*us) # slack self.core.delay(20.*us) # slack
return read return read
@kernel @kernel
def trf_read(self, addr, cnt_mux_sel=0) -> TInt32: def trf_read(self, addr: int32, cnt_mux_sel: int32 = 0) -> int32:
"""Quadrature upconverter register read. """Quadrature upconverter register read.
:param addr: Register address to read (0 to 7) :param addr: Register address to read (0 to 7)
:param cnt_mux_sel: Report VCO counter min or max frequency :param cnt_mux_sel: Report VCO counter min or max frequency
:return: Register data (32 bit) :return: Register data (32 bit)
""" """
self.trf_write(0x80000008 | (addr << 28) | (cnt_mux_sel << 27)) self.trf_write(int32(int64(0x80000008)) | (addr << 28) | (cnt_mux_sel << 27))
# single clk pulse with ~LE to start readback # single clk pulse with ~LE to start readback
self.phaser.spi_cfg(select=0, div=34, end=1, length=1) self.phaser.spi_cfg(select=0, div=34, end=True, length=1)
self.phaser.spi_write(0) self.phaser.spi_write(0)
delay((1 + 1)*34*4*ns) self.core.delay((1. + 1.)*34.*4.*ns)
return self.trf_write(0x00000008 | (cnt_mux_sel << 27), return self.trf_write(0x00000008 | (cnt_mux_sel << 27),
readback=True) readback=True)
@ -1022,24 +1054,25 @@ class PhaserChannel:
self.trf_write(self.trf_mmap[1] | (1 << 31)) self.trf_write(self.trf_mmap[1] | (1 << 31))
@kernel @kernel
def en_trf_out(self, rf=1, lo=0): def en_trf_out(self, rf: bool = True, lo: bool = False):
"""Enable the rf/lo outputs of the upconverter (hardware variant). """Enable the rf/lo outputs of the upconverter (hardware variant).
:param rf: 1 to enable RF output, 0 to disable :param rf: 1 to enable RF output, 0 to disable
:param lo: 1 to enable LO output, 0 to disable :param lo: 1 to enable LO output, 0 to disable
""" """
data = self.trf_read(0xc) data = self.trf_read(0xc)
delay(0.1 * ms) self.core.delay(0.1 * ms)
# set RF and LO output bits # set RF and LO output bits
data = data | (1 << 12) | (1 << 13) | (1 << 14) data = data | (1 << 12) | (1 << 13) | (1 << 14)
# clear to enable output # clear to enable output
if rf == 1: if rf:
data = data ^ (1 << 14) data = data ^ (1 << 14)
if lo == 1: if lo:
data = data ^ ((1 << 12) | (1 << 13)) data = data ^ ((1 << 12) | (1 << 13))
self.trf_write(data) self.trf_write(data)
@nac3
class PhaserOscillator: class PhaserOscillator:
"""Phaser IQ channel oscillator (NCO/DDS). """Phaser IQ channel oscillator (NCO/DDS).
@ -1047,15 +1080,19 @@ class PhaserOscillator:
oscillator parameters (amplitude and phase/frequency) are deterministic oscillator parameters (amplitude and phase/frequency) are deterministic
(with respect to the 25 MS/s sample clock) but not matched. (with respect to the 25 MS/s sample clock) but not matched.
""" """
kernel_invariants = {"channel", "base_addr"}
core: KernelInvariant[Core]
channel: KernelInvariant[PhaserChannel]
base_addr: KernelInvariant[int32]
def __init__(self, channel, index): def __init__(self, channel, index):
self.core = channel.core
self.channel = channel self.channel = channel
self.base_addr = ((self.channel.phaser.channel_base + 1 + self.base_addr = ((self.channel.phaser.channel_base + 1 +
2*self.channel.index) << 8) | index 2*self.channel.index) << 8) | index
@kernel @kernel
def set_frequency_mu(self, ftw): def set_frequency_mu(self, ftw: int32):
"""Set Phaser MultiDDS frequency tuning word. """Set Phaser MultiDDS frequency tuning word.
:param ftw: Frequency tuning word (32 bit) :param ftw: Frequency tuning word (32 bit)
@ -1063,36 +1100,36 @@ class PhaserOscillator:
rtio_output(self.base_addr, ftw) rtio_output(self.base_addr, ftw)
@kernel @kernel
def set_frequency(self, frequency): def set_frequency(self, frequency: float):
"""Set Phaser MultiDDS frequency. """Set Phaser MultiDDS frequency.
:param frequency: Frequency in Hz (passband from -10 MHz to 10 MHz, :param frequency: Frequency in Hz (passband from -10 MHz to 10 MHz,
wrapping around at +- 12.5 MHz) wrapping around at +- 12.5 MHz)
""" """
ftw = int32(round(frequency*((1 << 30)/(6.25*MHz)))) ftw = round(frequency*(float(1 << 30)/(6.25*MHz)))
self.set_frequency_mu(ftw) self.set_frequency_mu(ftw)
@kernel @kernel
def set_amplitude_phase_mu(self, asf=0x7fff, pow=0, clr=0): def set_amplitude_phase_mu(self, asf: int32 = 0x7fff, pow: int32 = 0, clr: bool = False):
"""Set Phaser MultiDDS amplitude, phase offset and accumulator clear. """Set Phaser MultiDDS amplitude, phase offset and accumulator clear.
:param asf: Amplitude (15 bit) :param asf: Amplitude (15 bit)
:param pow: Phase offset word (16 bit) :param pow: Phase offset word (16 bit)
:param clr: Clear the phase accumulator (persistent) :param clr: Clear the phase accumulator (persistent)
""" """
data = (asf & 0x7fff) | ((clr & 1) << 15) | (pow << 16) data = (asf & 0x7fff) | (int32(clr) << 15) | (pow << 16)
rtio_output(self.base_addr + (1 << 8), data) rtio_output(self.base_addr + (1 << 8), data)
@kernel @kernel
def set_amplitude_phase(self, amplitude, phase=0., clr=0): def set_amplitude_phase(self, amplitude: float, phase: float = 0., clr: bool = False):
"""Set Phaser MultiDDS amplitude and phase. """Set Phaser MultiDDS amplitude and phase.
:param amplitude: Amplitude in units of full scale :param amplitude: Amplitude in units of full scale
:param phase: Phase in turns :param phase: Phase in turns
:param clr: Clear the phase accumulator (persistent) :param clr: Clear the phase accumulator (persistent)
""" """
asf = int32(round(amplitude*0x7fff)) asf = round(amplitude*float(0x7fff))
if asf < 0 or asf > 0x7fff: if asf < 0 or asf > 0x7fff:
raise ValueError("amplitude out of bounds") raise ValueError("amplitude out of bounds")
pow = int32(round(phase*(1 << 16))) pow = round(phase*float(1 << 16))
self.set_amplitude_phase_mu(asf, pow, clr) self.set_amplitude_phase_mu(asf, pow, clr)

View File

@ -40,6 +40,10 @@
{ {
"type": "fastino", "type": "fastino",
"ports": [7] "ports": [7]
},
{
"type": "phaser",
"ports": [8]
} }
] ]
} }

View File

@ -10,6 +10,7 @@ from artiq.coredevice.sampler import Sampler
from artiq.coredevice.edge_counter import EdgeCounter from artiq.coredevice.edge_counter import EdgeCounter
from artiq.coredevice.grabber import Grabber from artiq.coredevice.grabber import Grabber
from artiq.coredevice.fastino import Fastino from artiq.coredevice.fastino import Fastino
from artiq.coredevice.phaser import Phaser
@nac3 @nac3
@ -25,6 +26,7 @@ class NAC3Devices(EnvExperiment):
ttl0_counter: KernelInvariant[EdgeCounter] ttl0_counter: KernelInvariant[EdgeCounter]
grabber0: KernelInvariant[Grabber] grabber0: KernelInvariant[Grabber]
fastino0: KernelInvariant[Fastino] fastino0: KernelInvariant[Fastino]
# NAC3TODO segfault phaser0: KernelInvariant[Fastino]
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
@ -38,6 +40,7 @@ class NAC3Devices(EnvExperiment):
self.setattr_device("ttl0_counter") self.setattr_device("ttl0_counter")
self.setattr_device("grabber0") self.setattr_device("grabber0")
self.setattr_device("fastino0") self.setattr_device("fastino0")
# NAC3TODO segfault self.setattr_device("phaser0")
@kernel @kernel
def run(self): def run(self):