forked from M-Labs/artiq
phaser: port to NAC3
This commit is contained in:
parent
a407007e0b
commit
ea55c29568
|
@ -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)
|
||||||
|
|
|
@ -40,6 +40,10 @@
|
||||||
{
|
{
|
||||||
"type": "fastino",
|
"type": "fastino",
|
||||||
"ports": [7]
|
"ports": [7]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "phaser",
|
||||||
|
"ports": [8]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue