forked from M-Labs/artiq
1
0
Fork 0

added typing and reformatted driver for ad9910, ad9912, and urukul

Signed-off-by: Leon Riesebos <leon.riesebos@duke.edu>
This commit is contained in:
Leon Riesebos 2021-03-14 23:39:12 -04:00 committed by Sébastien Bourdeauducq
parent 5ba22c11c3
commit c22f731a61
3 changed files with 199 additions and 173 deletions

View File

@ -3,15 +3,15 @@ from numpy import int32, int64
from artiq.language.core import ( from artiq.language.core import (
kernel, delay, portable, delay_mu, now_mu, at_mu) kernel, delay, portable, delay_mu, now_mu, at_mu)
from artiq.language.units import us, ms from artiq.language.units import us, ms
from artiq.language.types import * from artiq.language.types import TBool, TInt32, TInt64, TFloat, TList, TTuple
from artiq.coredevice import spi2 as spi from artiq.coredevice import spi2 as spi
from artiq.coredevice import urukul from artiq.coredevice import urukul
# Work around ARTIQ-Python import machinery # Work around ARTIQ-Python import machinery
urukul_sta_pll_lock = urukul.urukul_sta_pll_lock urukul_sta_pll_lock = urukul.urukul_sta_pll_lock
urukul_sta_smp_err = urukul.urukul_sta_smp_err urukul_sta_smp_err = urukul.urukul_sta_smp_err
__all__ = [ __all__ = [
"AD9910", "AD9910",
"PHASE_MODE_CONTINUOUS", "PHASE_MODE_ABSOLUTE", "PHASE_MODE_TRACKING", "PHASE_MODE_CONTINUOUS", "PHASE_MODE_ABSOLUTE", "PHASE_MODE_TRACKING",
@ -20,7 +20,6 @@ __all__ = [
"RAM_MODE_CONT_BIDIR_RAMP", "RAM_MODE_CONT_RAMPUP", "RAM_MODE_CONT_BIDIR_RAMP", "RAM_MODE_CONT_RAMPUP",
] ]
_PHASE_MODE_DEFAULT = -1 _PHASE_MODE_DEFAULT = -1
PHASE_MODE_CONTINUOUS = 0 PHASE_MODE_CONTINUOUS = 0
PHASE_MODE_ABSOLUTE = 1 PHASE_MODE_ABSOLUTE = 1
@ -124,15 +123,15 @@ class AD9910:
To stabilize the SYNC_IN delay tuning, run :meth:`tune_sync_delay` once To stabilize the SYNC_IN delay tuning, run :meth:`tune_sync_delay` once
and set this to the delay tap number returned (default: -1 to signal no and set this to the delay tap number returned (default: -1 to signal no
synchronization and no tuning during :meth:`init`). synchronization and no tuning during :meth:`init`).
Can be a string of the form "eeprom_device:byte_offset" to read the value Can be a string of the form "eeprom_device:byte_offset" to read the
from a I2C EEPROM; in which case, `io_update_delay` must be set to the value from a I2C EEPROM; in which case, `io_update_delay` must be set
same string value. to the same string value.
:param io_update_delay: IO_UPDATE pulse alignment delay. :param io_update_delay: IO_UPDATE pulse alignment delay.
To align IO_UPDATE to SYNC_CLK, run :meth:`tune_io_update_delay` and To align IO_UPDATE to SYNC_CLK, run :meth:`tune_io_update_delay` and
set this to the delay tap number returned. set this to the delay tap number returned.
Can be a string of the form "eeprom_device:byte_offset" to read the value Can be a string of the form "eeprom_device:byte_offset" to read the
from a I2C EEPROM; in which case, `sync_delay_seed` must be set to the value from a I2C EEPROM; in which case, `sync_delay_seed` must be set
same string value. to the same string value.
""" """
kernel_invariants = {"chip_select", "cpld", "core", "bus", kernel_invariants = {"chip_select", "cpld", "core", "bus",
"ftw_per_hz", "sysclk_per_mu"} "ftw_per_hz", "sysclk_per_mu"}
@ -148,38 +147,41 @@ class AD9910:
if sw_device: if sw_device:
self.sw = dmgr.get(sw_device) self.sw = dmgr.get(sw_device)
self.kernel_invariants.add("sw") self.kernel_invariants.add("sw")
clk = self.cpld.refclk/[4, 1, 2, 4][self.cpld.clk_div] clk = self.cpld.refclk / [4, 1, 2, 4][self.cpld.clk_div]
self.pll_en = pll_en self.pll_en = pll_en
self.pll_n = pll_n self.pll_n = pll_n
self.pll_vco = pll_vco self.pll_vco = pll_vco
self.pll_cp = pll_cp self.pll_cp = pll_cp
if pll_en: if pll_en:
sysclk = clk*pll_n sysclk = clk * pll_n
assert clk <= 60e6 assert clk <= 60e6
assert 12 <= pll_n <= 127 assert 12 <= pll_n <= 127
assert 0 <= pll_vco <= 5 assert 0 <= pll_vco <= 5
vco_min, vco_max = [(370, 510), (420, 590), (500, 700), vco_min, vco_max = [(370, 510), (420, 590), (500, 700),
(600, 880), (700, 950), (820, 1150)][pll_vco] (600, 880), (700, 950), (820, 1150)][pll_vco]
assert vco_min <= sysclk/1e6 <= vco_max assert vco_min <= sysclk / 1e6 <= vco_max
assert 0 <= pll_cp <= 7 assert 0 <= pll_cp <= 7
else: else:
sysclk = clk sysclk = clk
assert sysclk <= 1e9 assert sysclk <= 1e9
self.ftw_per_hz = (1 << 32)/sysclk self.ftw_per_hz = (1 << 32) / sysclk
self.sysclk_per_mu = int(round(sysclk*self.core.ref_period)) self.sysclk_per_mu = int(round(sysclk * self.core.ref_period))
self.sysclk = sysclk self.sysclk = sysclk
if isinstance(sync_delay_seed, str) or isinstance(io_update_delay, str): if isinstance(sync_delay_seed, str) or isinstance(io_update_delay,
str):
if sync_delay_seed != io_update_delay: if sync_delay_seed != io_update_delay:
raise ValueError("When using EEPROM, sync_delay_seed must be equal to io_update_delay") raise ValueError("When using EEPROM, sync_delay_seed must be "
"equal to io_update_delay")
self.sync_data = SyncDataEeprom(dmgr, self.core, sync_delay_seed) self.sync_data = SyncDataEeprom(dmgr, self.core, sync_delay_seed)
else: else:
self.sync_data = SyncDataUser(self.core, sync_delay_seed, io_update_delay) self.sync_data = SyncDataUser(self.core, sync_delay_seed,
io_update_delay)
self.phase_mode = PHASE_MODE_CONTINUOUS self.phase_mode = PHASE_MODE_CONTINUOUS
@kernel @kernel
def set_phase_mode(self, phase_mode): def set_phase_mode(self, phase_mode: TInt32):
r"""Set the default phase mode. r"""Set the default phase mode.
for future calls to :meth:`set` and for future calls to :meth:`set` and
@ -224,7 +226,7 @@ class AD9910:
self.phase_mode = phase_mode self.phase_mode = phase_mode
@kernel @kernel
def write16(self, addr, data): def write16(self, addr: TInt32, data: TInt32):
"""Write to 16 bit register. """Write to 16 bit register.
:param addr: Register address :param addr: Register address
@ -235,7 +237,7 @@ class AD9910:
self.bus.write((addr << 24) | (data << 8)) self.bus.write((addr << 24) | (data << 8))
@kernel @kernel
def write32(self, addr, data): def write32(self, addr: TInt32, data: TInt32):
"""Write to 32 bit register. """Write to 32 bit register.
:param addr: Register address :param addr: Register address
@ -249,7 +251,7 @@ class AD9910:
self.bus.write(data) self.bus.write(data)
@kernel @kernel
def read16(self, addr): def read16(self, addr: TInt32) -> TInt32:
"""Read from 16 bit register. """Read from 16 bit register.
:param addr: Register address :param addr: Register address
@ -264,7 +266,7 @@ class AD9910:
return self.bus.read() return self.bus.read()
@kernel @kernel
def read32(self, addr): def read32(self, addr: TInt32) -> TInt32:
"""Read from 32 bit register. """Read from 32 bit register.
:param addr: Register address :param addr: Register address
@ -279,7 +281,7 @@ class AD9910:
return self.bus.read() return self.bus.read()
@kernel @kernel
def read64(self, addr): def read64(self, addr: TInt32) -> TInt64:
"""Read from 64 bit register. """Read from 64 bit register.
:param addr: Register address :param addr: Register address
@ -302,7 +304,7 @@ class AD9910:
return (int64(hi) << 32) | lo return (int64(hi) << 32) | lo
@kernel @kernel
def write64(self, addr, data_high, data_low): def write64(self, addr: TInt32, data_high: TInt32, data_low: TInt32):
"""Write to 64 bit register. """Write to 64 bit register.
:param addr: Register address :param addr: Register address
@ -320,14 +322,14 @@ class AD9910:
self.bus.write(data_low) self.bus.write(data_low)
@kernel @kernel
def write_ram(self, data): def write_ram(self, data: TList(int32)):
"""Write data to RAM. """Write data to RAM.
The profile to write to and the step, start, and end address The profile to write to and the step, start, and end address
need to be configured before and separately using need to be configured before and separately using
:meth:`set_profile_ram` and the parent CPLD `set_profile`. :meth:`set_profile_ram` and the parent CPLD `set_profile`.
:param data List(int32): Data to be written to RAM. :param data: Data to be written to RAM.
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR,
self.chip_select) self.chip_select)
@ -341,14 +343,14 @@ class AD9910:
self.bus.write(data[len(data) - 1]) self.bus.write(data[len(data) - 1])
@kernel @kernel
def read_ram(self, data): def read_ram(self, data: TList(int32)):
"""Read data from RAM. """Read data from RAM.
The profile to read from and the step, start, and end address The profile to read from and the step, start, and end address
need to be configured before and separately using need to be configured before and separately using
:meth:`set_profile_ram` and the parent CPLD `set_profile`. :meth:`set_profile_ram` and the parent CPLD `set_profile`.
:param data List(int32): List to be filled with data read from RAM. :param data: List to be filled with data read from RAM.
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR,
self.chip_select) self.chip_select)
@ -370,10 +372,12 @@ class AD9910:
data[(n - preload) + i] = self.bus.read() data[(n - preload) + i] = self.bus.read()
@kernel @kernel
def set_cfr1(self, power_down=0b0000, phase_autoclear=0, def set_cfr1(self, power_down: TInt32 = 0b0000,
drg_load_lrr=0, drg_autoclear=0, phase_autoclear: TInt32 = 0,
internal_profile=0, ram_destination=0, ram_enable=0, drg_load_lrr: TInt32 = 0, drg_autoclear: TInt32 = 0,
manual_osk_external=0, osk_enable=0, select_auto_osk=0): internal_profile: TInt32 = 0, ram_destination: TInt32 = 0,
ram_enable: TInt32 = 0, manual_osk_external: TInt32 = 0,
osk_enable: TInt32 = 0, select_auto_osk: TInt32 = 0):
"""Set CFR1. See the AD9910 datasheet for parameter meanings. """Set CFR1. See the AD9910 datasheet for parameter meanings.
This method does not pulse IO_UPDATE. This method does not pulse IO_UPDATE.
@ -405,7 +409,7 @@ class AD9910:
2) # SDIO input only, MSB first 2) # SDIO input only, MSB first
@kernel @kernel
def init(self, blind=False): def init(self, blind: TBool = False):
"""Initialize and configure the DDS. """Initialize and configure the DDS.
Sets up SPI mode, confirms chip presence, powers down unused blocks, Sets up SPI mode, confirms chip presence, powers down unused blocks,
@ -418,70 +422,69 @@ class AD9910:
if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div: if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div:
raise ValueError("parent cpld does not drive SYNC") raise ValueError("parent cpld does not drive SYNC")
if self.sync_data.sync_delay_seed >= 0: if self.sync_data.sync_delay_seed >= 0:
if self.sysclk_per_mu != self.sysclk*self.core.ref_period: if self.sysclk_per_mu != self.sysclk * self.core.ref_period:
raise ValueError("incorrect clock ratio for synchronization") raise ValueError("incorrect clock ratio for synchronization")
delay(50*ms) # slack delay(50 * ms) # slack
# Set SPI mode # Set SPI mode
self.set_cfr1() self.set_cfr1()
self.cpld.io_update.pulse(1*us) self.cpld.io_update.pulse(1 * us)
delay(1*ms) delay(1 * ms)
if not blind: if not blind:
# Use the AUX DAC setting to identify and confirm presence # Use the AUX DAC setting to identify and confirm presence
aux_dac = self.read32(_AD9910_REG_AUX_DAC) aux_dac = self.read32(_AD9910_REG_AUX_DAC)
if aux_dac & 0xff != 0x7f: if aux_dac & 0xff != 0x7f:
raise ValueError("Urukul AD9910 AUX_DAC mismatch") raise ValueError("Urukul AD9910 AUX_DAC mismatch")
delay(50*us) # slack delay(50 * us) # slack
# Configure PLL settings and bring up PLL # Configure PLL settings and bring up PLL
# enable amplitude scale from profiles # enable amplitude scale from profiles
# read effective FTW # read effective FTW
# sync timing validation disable (enabled later) # sync timing validation disable (enabled later)
self.write32(_AD9910_REG_CFR2, 0x01010020) self.write32(_AD9910_REG_CFR2, 0x01010020)
self.cpld.io_update.pulse(1*us) self.cpld.io_update.pulse(1 * us)
cfr3 = (0x0807c000 | (self.pll_vco << 24) | cfr3 = (0x0807c000 | (self.pll_vco << 24) |
(self.pll_cp << 19) | (self.pll_en << 8) | (self.pll_cp << 19) | (self.pll_en << 8) |
(self.pll_n << 1)) (self.pll_n << 1))
self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset
self.cpld.io_update.pulse(1*us) self.cpld.io_update.pulse(1 * us)
if self.pll_en: if self.pll_en:
self.write32(_AD9910_REG_CFR3, cfr3) self.write32(_AD9910_REG_CFR3, cfr3)
self.cpld.io_update.pulse(1*us) self.cpld.io_update.pulse(1 * us)
if blind: if blind:
delay(100*ms) delay(100 * ms)
else: else:
# Wait for PLL lock, up to 100 ms # Wait for PLL lock, up to 100 ms
for i in range(100): for i in range(100):
sta = self.cpld.sta_read() sta = self.cpld.sta_read()
lock = urukul_sta_pll_lock(sta) lock = urukul_sta_pll_lock(sta)
delay(1*ms) delay(1 * ms)
if lock & (1 << self.chip_select - 4): if lock & (1 << self.chip_select - 4):
break break
if i >= 100 - 1: if i >= 100 - 1:
raise ValueError("PLL lock timeout") raise ValueError("PLL lock timeout")
delay(10*us) # slack delay(10 * us) # slack
if self.sync_data.sync_delay_seed >= 0: if self.sync_data.sync_delay_seed >= 0:
self.tune_sync_delay(self.sync_data.sync_delay_seed) self.tune_sync_delay(self.sync_data.sync_delay_seed)
delay(1*ms) delay(1 * ms)
@kernel @kernel
def power_down(self, bits=0b1111): def power_down(self, bits: TInt32 = 0b1111):
"""Power down DDS. """Power down DDS.
:param bits: Power down bits, see datasheet :param bits: Power down bits, see datasheet
""" """
self.set_cfr1(power_down=bits) self.set_cfr1(power_down=bits)
self.cpld.io_update.pulse(1*us) self.cpld.io_update.pulse(1 * us)
# KLUDGE: ref_time_mu default argument is explicitly marked int64() to
# avoid silent truncation of explicitly passed timestamps. (Compiler bug?)
@kernel @kernel
def set_mu(self, ftw, pow_=0, asf=0x3fff, phase_mode=_PHASE_MODE_DEFAULT, def set_mu(self, ftw: TInt32, pow_: TInt32 = 0, asf: TInt32 = 0x3fff,
ref_time_mu=int64(-1), profile=0): phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 0):
"""Set profile 0 data in machine units. """Set profile 0 data in machine units.
This uses machine units (FTW, POW, ASF). The frequency tuning word This uses machine units (FTW, POW, ASF). The frequency tuning word
width is 32, the phase offset word width is 16, and the amplitude width is 32, the phase offset word width is 16, and the amplitude
scale factor width is 12. scale factor width is 14.
After the SPI transfer, the shared IO update pin is pulsed to After the SPI transfer, the shared IO update pin is pulsed to
activate the data. activate the data.
@ -518,7 +521,7 @@ class AD9910:
# Also no need to use IO_UPDATE time as this # Also no need to use IO_UPDATE time as this
# is equivalent to an output pipeline latency. # is equivalent to an output pipeline latency.
dt = int32(now_mu()) - int32(ref_time_mu) dt = int32(now_mu()) - int32(ref_time_mu)
pow_ += dt*ftw*self.sysclk_per_mu >> 16 pow_ += dt * ftw * self.sysclk_per_mu >> 16
self.write64(_AD9910_REG_PROFILE0 + profile, self.write64(_AD9910_REG_PROFILE0 + profile,
(asf << 16) | (pow_ & 0xffff), ftw) (asf << 16) | (pow_ & 0xffff), ftw)
delay_mu(int64(self.sync_data.io_update_delay)) delay_mu(int64(self.sync_data.io_update_delay))
@ -530,8 +533,9 @@ class AD9910:
return pow_ return pow_
@kernel @kernel
def set_profile_ram(self, start, end, step=1, profile=0, nodwell_high=0, def set_profile_ram(self, start: TInt32, end: TInt32, step: TInt32 = 1,
zero_crossing=0, mode=1): profile: TInt32 = 0, nodwell_high: TInt32 = 0,
zero_crossing: TInt32 = 0, mode: TInt32 = 1):
"""Set the RAM profile settings. """Set the RAM profile settings.
:param start: Profile start address in RAM. :param start: Profile start address in RAM.
@ -555,57 +559,60 @@ class AD9910:
self.write64(_AD9910_REG_PROFILE0 + profile, hi, lo) self.write64(_AD9910_REG_PROFILE0 + profile, hi, lo)
@kernel @kernel
def set_ftw(self, ftw): def set_ftw(self, ftw: TInt32):
"""Set the value stored to the AD9910's frequency tuning word (FTW) register. """Set the value stored to the AD9910's frequency tuning word (FTW)
register.
:param ftw: Frequency tuning word to be stored, range: 0 to 0xffffffff. :param ftw: Frequency tuning word to be stored, range: 0 to 0xffffffff.
""" """
self.write32(_AD9910_REG_FTW, ftw) self.write32(_AD9910_REG_FTW, ftw)
@kernel @kernel
def set_asf(self, asf): def set_asf(self, asf: TInt32):
"""Set the value stored to the AD9910's amplitude scale factor (ASF) register. """Set the value stored to the AD9910's amplitude scale factor (ASF)
register.
:param asf: Amplitude scale factor to be stored, range: 0 to 0x3fff. :param asf: Amplitude scale factor to be stored, range: 0 to 0x3fff.
""" """
self.write32(_AD9910_REG_ASF, asf << 2) self.write32(_AD9910_REG_ASF, asf << 2)
@kernel @kernel
def set_pow(self, pow_): def set_pow(self, pow_: TInt32):
"""Set the value stored to the AD9910's phase offset word (POW) register. """Set the value stored to the AD9910's phase offset word (POW)
register.
:param pow_: Phase offset word to be stored, range: 0 to 0xffff. :param pow_: Phase offset word to be stored, range: 0 to 0xffff.
""" """
self.write16(_AD9910_REG_POW, pow_) self.write16(_AD9910_REG_POW, pow_)
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def frequency_to_ftw(self, frequency) -> TInt32: def frequency_to_ftw(self, frequency: TFloat) -> TInt32:
"""Return the 32-bit frequency tuning word corresponding to the given """Return the 32-bit frequency tuning word corresponding to the given
frequency. frequency.
""" """
return int32(round(self.ftw_per_hz*frequency)) return int32(round(self.ftw_per_hz * frequency))
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def ftw_to_frequency(self, ftw): def ftw_to_frequency(self, ftw: TInt32) -> TFloat:
"""Return the frequency corresponding to the given frequency tuning """Return the frequency corresponding to the given frequency tuning
word. word.
""" """
return ftw / self.ftw_per_hz return ftw / self.ftw_per_hz
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def turns_to_pow(self, turns) -> TInt32: def turns_to_pow(self, turns: TFloat) -> TInt32:
"""Return the 16-bit phase offset word corresponding to the given phase """Return the 16-bit phase offset word corresponding to the given phase
in turns.""" in turns."""
return int32(round(turns*0x10000)) & int32(0xffff) return int32(round(turns * 0x10000)) & int32(0xffff)
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def pow_to_turns(self, pow_): def pow_to_turns(self, pow_: TInt32) -> TFloat:
"""Return the phase in turns corresponding to a given phase offset """Return the phase in turns corresponding to a given phase offset
word.""" word."""
return pow_/0x10000 return pow_ / 0x10000
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def amplitude_to_asf(self, amplitude) -> TInt32: def amplitude_to_asf(self, amplitude: TFloat) -> TInt32:
"""Return 14-bit amplitude scale factor corresponding to given """Return 14-bit amplitude scale factor corresponding to given
fractional amplitude.""" fractional amplitude."""
code = int32(round(amplitude * 0x3fff)) code = int32(round(amplitude * 0x3fff))
@ -614,13 +621,13 @@ class AD9910:
return code return code
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def asf_to_amplitude(self, asf): def asf_to_amplitude(self, asf: TInt32) -> TFloat:
"""Return amplitude as a fraction of full scale corresponding to given """Return amplitude as a fraction of full scale corresponding to given
amplitude scale factor.""" amplitude scale factor."""
return asf / float(0x3fff) return asf / float(0x3fff)
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def frequency_to_ram(self, frequency, ram): def frequency_to_ram(self, frequency: TList(TFloat), ram: TList(TInt32)):
"""Convert frequency values to RAM profile data. """Convert frequency values to RAM profile data.
To be used with :const:`RAM_DEST_FTW`. To be used with :const:`RAM_DEST_FTW`.
@ -633,7 +640,7 @@ class AD9910:
ram[i] = self.frequency_to_ftw(frequency[i]) ram[i] = self.frequency_to_ftw(frequency[i])
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def turns_to_ram(self, turns, ram): def turns_to_ram(self, turns: TList(TFloat), ram: TList(TInt32)):
"""Convert phase values to RAM profile data. """Convert phase values to RAM profile data.
To be used with :const:`RAM_DEST_POW`. To be used with :const:`RAM_DEST_POW`.
@ -646,7 +653,7 @@ class AD9910:
ram[i] = self.turns_to_pow(turns[i]) << 16 ram[i] = self.turns_to_pow(turns[i]) << 16
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def amplitude_to_ram(self, amplitude, ram): def amplitude_to_ram(self, amplitude: TList(TFloat), ram: TList(TInt32)):
"""Convert amplitude values to RAM profile data. """Convert amplitude values to RAM profile data.
To be used with :const:`RAM_DEST_ASF`. To be used with :const:`RAM_DEST_ASF`.
@ -659,7 +666,8 @@ class AD9910:
ram[i] = self.amplitude_to_asf(amplitude[i]) << 18 ram[i] = self.amplitude_to_asf(amplitude[i]) << 18
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def turns_amplitude_to_ram(self, turns, amplitude, ram): def turns_amplitude_to_ram(self, turns: TList(TFloat),
amplitude: TList(TFloat), ram: TList(TInt32)):
"""Convert phase and amplitude values to RAM profile data. """Convert phase and amplitude values to RAM profile data.
To be used with :const:`RAM_DEST_POWASF`. To be used with :const:`RAM_DEST_POWASF`.
@ -674,32 +682,36 @@ class AD9910:
self.amplitude_to_asf(amplitude[i]) << 2) self.amplitude_to_asf(amplitude[i]) << 2)
@kernel @kernel
def set_frequency(self, frequency): def set_frequency(self, frequency: TFloat):
"""Set the value stored to the AD9910's frequency tuning word (FTW) register. """Set the value stored to the AD9910's frequency tuning word (FTW)
register.
:param frequency: frequency to be stored, in Hz. :param frequency: frequency to be stored, in Hz.
""" """
return self.set_ftw(self.frequency_to_ftw(frequency)) self.set_ftw(self.frequency_to_ftw(frequency))
@kernel @kernel
def set_amplitude(self, amplitude): def set_amplitude(self, amplitude: TFloat):
"""Set the value stored to the AD9910's amplitude scale factor (ASF) register. """Set the value stored to the AD9910's amplitude scale factor (ASF)
register.
:param amplitude: amplitude to be stored, in units of full scale. :param amplitude: amplitude to be stored, in units of full scale.
""" """
return self.set_asf(self.amplitude_to_asf(amplitude)) self.set_asf(self.amplitude_to_asf(amplitude))
@kernel @kernel
def set_phase(self, turns): def set_phase(self, turns: TFloat):
"""Set the value stored to the AD9910's phase offset word (POW) register. """Set the value stored to the AD9910's phase offset word (POW)
register.
:param turns: phase offset to be stored, in turns. :param turns: phase offset to be stored, in turns.
""" """
return self.set_pow(self.turns_to_pow(turns)) self.set_pow(self.turns_to_pow(turns))
@kernel @kernel
def set(self, frequency, phase=0.0, amplitude=1.0, def set(self, frequency: TFloat, phase: TFloat = 0.0,
phase_mode=_PHASE_MODE_DEFAULT, ref_time_mu=int64(-1), profile=0): amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 0):
"""Set profile 0 data in SI units. """Set profile 0 data in SI units.
.. seealso:: :meth:`set_mu` .. seealso:: :meth:`set_mu`
@ -718,7 +730,7 @@ class AD9910:
profile)) profile))
@kernel @kernel
def set_att_mu(self, att): def set_att_mu(self, att: TInt32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -730,7 +742,7 @@ class AD9910:
self.cpld.set_att_mu(self.chip_select - 4, att) self.cpld.set_att_mu(self.chip_select - 4, att)
@kernel @kernel
def set_att(self, att): def set_att(self, att: TFloat):
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -742,7 +754,7 @@ class AD9910:
self.cpld.set_att(self.chip_select - 4, att) self.cpld.set_att(self.chip_select - 4, att)
@kernel @kernel
def cfg_sw(self, state): def cfg_sw(self, state: TInt32):
"""Set CPLD CFG RF switch state. The RF switch is controlled by the """Set CPLD CFG RF switch state. The RF switch is controlled by the
logical or of the CPLD configuration shift register logical or of the CPLD configuration shift register
RF switch bit and the SW TTL line (if used). RF switch bit and the SW TTL line (if used).
@ -752,7 +764,7 @@ class AD9910:
self.cpld.cfg_sw(self.chip_select - 4, state) self.cpld.cfg_sw(self.chip_select - 4, state)
@kernel @kernel
def set_sync(self, in_delay, window): def set_sync(self, in_delay: TInt32, window: TInt32):
"""Set the relevant parameters in the multi device synchronization """Set the relevant parameters in the multi device synchronization
register. See the AD9910 datasheet for details. The SYNC clock register. See the AD9910 datasheet for details. The SYNC clock
generator preset value is set to zero, and the SYNC_OUT generator is generator preset value is set to zero, and the SYNC_OUT generator is
@ -782,12 +794,13 @@ class AD9910:
Also modifies CFR2. Also modifies CFR2.
""" """
self.write32(_AD9910_REG_CFR2, 0x01010020) # clear SMP_ERR self.write32(_AD9910_REG_CFR2, 0x01010020) # clear SMP_ERR
self.cpld.io_update.pulse(1*us) self.cpld.io_update.pulse(1 * us)
self.write32(_AD9910_REG_CFR2, 0x01010000) # enable SMP_ERR self.write32(_AD9910_REG_CFR2, 0x01010000) # enable SMP_ERR
self.cpld.io_update.pulse(1*us) self.cpld.io_update.pulse(1 * us)
@kernel @kernel
def tune_sync_delay(self, search_seed=15): def tune_sync_delay(self,
search_seed: TInt32 = 15) -> TTuple([TInt32, TInt32]):
"""Find a stable SYNC_IN delay. """Find a stable SYNC_IN delay.
This method first locates a valid SYNC_IN delay at zero validation This method first locates a valid SYNC_IN delay at zero validation
@ -812,7 +825,7 @@ class AD9910:
margin = 1 # 1*75ps setup and hold margin = 1 # 1*75ps setup and hold
for window in range(16): for window in range(16):
next_seed = -1 next_seed = -1
for in_delay in range(search_span - 2*window): for in_delay in range(search_span - 2 * window):
# alternate search direction around search_seed # alternate search direction around search_seed
if in_delay & 1: if in_delay & 1:
in_delay = -in_delay in_delay = -in_delay
@ -822,9 +835,9 @@ class AD9910:
self.set_sync(in_delay, window) self.set_sync(in_delay, window)
self.clear_smp_err() self.clear_smp_err()
# integrate SMP_ERR statistics for a few hundred cycles # integrate SMP_ERR statistics for a few hundred cycles
delay(100*us) delay(100 * us)
err = urukul_sta_smp_err(self.cpld.sta_read()) err = urukul_sta_smp_err(self.cpld.sta_read())
delay(100*us) # slack delay(100 * us) # slack
if not (err >> (self.chip_select - 4)) & 1: if not (err >> (self.chip_select - 4)) & 1:
next_seed = in_delay next_seed = in_delay
break break
@ -836,14 +849,15 @@ class AD9910:
window = max(min_window, window - 1 - margin) window = max(min_window, window - 1 - margin)
self.set_sync(search_seed, window) self.set_sync(search_seed, window)
self.clear_smp_err() self.clear_smp_err()
delay(100*us) # slack delay(100 * us) # slack
return search_seed, window return search_seed, window
else: else:
break break
raise ValueError("no valid window/delay") raise ValueError("no valid window/delay")
@kernel @kernel
def measure_io_update_alignment(self, delay_start, delay_stop): def measure_io_update_alignment(self, delay_start: TInt64,
delay_stop: TInt64) -> TInt32:
"""Use the digital ramp generator to locate the alignment between """Use the digital ramp generator to locate the alignment between
IO_UPDATE and SYNC_CLK. IO_UPDATE and SYNC_CLK.
@ -878,14 +892,14 @@ class AD9910:
at_mu(t + 0x1000 + delay_stop) at_mu(t + 0x1000 + delay_stop)
self.cpld.io_update.pulse_mu(16 - delay_stop) # realign self.cpld.io_update.pulse_mu(16 - delay_stop) # realign
ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW
delay(100*us) # slack delay(100 * us) # slack
# disable DRG # disable DRG
self.write32(_AD9910_REG_CFR2, 0x01010000) self.write32(_AD9910_REG_CFR2, 0x01010000)
self.cpld.io_update.pulse_mu(8) self.cpld.io_update.pulse_mu(8)
return ftw & 1 return ftw & 1
@kernel @kernel
def tune_io_update_delay(self): def tune_io_update_delay(self) -> TInt32:
"""Find a stable IO_UPDATE delay alignment. """Find a stable IO_UPDATE delay alignment.
Scan through increasing IO_UPDATE delays until a delay is found that Scan through increasing IO_UPDATE delays until a delay is found that
@ -922,5 +936,5 @@ class AD9910:
"no clear IO_UPDATE-SYNC_CLK alignment edge found") "no clear IO_UPDATE-SYNC_CLK alignment edge found")
else: else:
# the good delay is period//2 after the edge # the good delay is period//2 after the edge
return (i + 1 + period//2) & (period - 1) return (i + 1 + period // 2) & (period - 1)
raise ValueError("no IO_UPDATE-SYNC_CLK alignment edge found") raise ValueError("no IO_UPDATE-SYNC_CLK alignment edge found")

View File

@ -1,6 +1,6 @@
from numpy import int32, int64 from numpy import int32, int64
from artiq.language.types import TInt32, TInt64 from artiq.language.types import TInt32, TInt64, TFloat, TTuple
from artiq.language.core import kernel, delay, portable from artiq.language.core import kernel, delay, portable
from artiq.language.units import ms, us, ns from artiq.language.units import ms, us, ns
from artiq.coredevice.ad9912_reg import * from artiq.coredevice.ad9912_reg import *
@ -39,12 +39,12 @@ class AD9912:
self.sw = dmgr.get(sw_device) self.sw = dmgr.get(sw_device)
self.kernel_invariants.add("sw") self.kernel_invariants.add("sw")
self.pll_n = pll_n self.pll_n = pll_n
sysclk = self.cpld.refclk/[1, 1, 2, 4][self.cpld.clk_div]*pll_n sysclk = self.cpld.refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
assert sysclk <= 1e9 assert sysclk <= 1e9
self.ftw_per_hz = 1/sysclk*(int64(1) << 48) self.ftw_per_hz = 1 / sysclk * (int64(1) << 48)
@kernel @kernel
def write(self, addr, data, length): def write(self, addr: TInt32, data: TInt32, length: TInt32):
"""Variable length write to a register. """Variable length write to a register.
Up to 4 bytes. Up to 4 bytes.
@ -55,14 +55,14 @@ class AD9912:
assert length > 0 assert length > 0
assert length <= 4 assert length <= 4
self.bus.set_config_mu(urukul.SPI_CONFIG, 16, self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((addr | ((length - 1) << 13)) << 16) self.bus.write((addr | ((length - 1) << 13)) << 16)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, length*8, self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, length * 8,
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write(data << (32 - length*8)) self.bus.write(data << (32 - length * 8))
@kernel @kernel
def read(self, addr, length): def read(self, addr: TInt32, length: TInt32) -> TInt32:
"""Variable length read from a register. """Variable length read from a register.
Up to 4 bytes. Up to 4 bytes.
@ -73,15 +73,15 @@ class AD9912:
assert length > 0 assert length > 0
assert length <= 4 assert length <= 4
self.bus.set_config_mu(urukul.SPI_CONFIG, 16, self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((addr | ((length - 1) << 13) | 0x8000) << 16) self.bus.write((addr | ((length - 1) << 13) | 0x8000) << 16)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END
| spi.SPI_INPUT, length*8, | spi.SPI_INPUT, length * 8,
urukul.SPIT_DDS_RD, self.chip_select) urukul.SPIT_DDS_RD, self.chip_select)
self.bus.write(0) self.bus.write(0)
data = self.bus.read() data = self.bus.read()
if length < 4: if length < 4:
data &= (1 << (length*8)) - 1 data &= (1 << (length * 8)) - 1
return data return data
@kernel @kernel
@ -94,24 +94,24 @@ class AD9912:
""" """
# SPI mode # SPI mode
self.write(AD9912_SER_CONF, 0x99, length=1) self.write(AD9912_SER_CONF, 0x99, length=1)
self.cpld.io_update.pulse(2*us) self.cpld.io_update.pulse(2 * us)
# Verify chip ID and presence # Verify chip ID and presence
prodid = self.read(AD9912_PRODIDH, length=2) prodid = self.read(AD9912_PRODIDH, length=2)
if (prodid != 0x1982) and (prodid != 0x1902): if (prodid != 0x1982) and (prodid != 0x1902):
raise ValueError("Urukul AD9912 product id mismatch") raise ValueError("Urukul AD9912 product id mismatch")
delay(50*us) delay(50 * us)
# HSTL power down, CMOS power down # HSTL power down, CMOS power down
self.write(AD9912_PWRCNTRL1, 0x80, length=1) self.write(AD9912_PWRCNTRL1, 0x80, length=1)
self.cpld.io_update.pulse(2*us) self.cpld.io_update.pulse(2 * us)
self.write(AD9912_N_DIV, self.pll_n//2 - 2, length=1) self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1)
self.cpld.io_update.pulse(2*us) self.cpld.io_update.pulse(2 * us)
# I_cp = 375 µA, VCO high range # I_cp = 375 µA, VCO high range
self.write(AD9912_PLLCFG, 0b00000101, length=1) self.write(AD9912_PLLCFG, 0b00000101, length=1)
self.cpld.io_update.pulse(2*us) self.cpld.io_update.pulse(2 * us)
delay(1*ms) delay(1 * ms)
@kernel @kernel
def set_att_mu(self, att): def set_att_mu(self, att: TInt32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -123,7 +123,7 @@ class AD9912:
self.cpld.set_att_mu(self.chip_select - 4, att) self.cpld.set_att_mu(self.chip_select - 4, att)
@kernel @kernel
def set_att(self, att): def set_att(self, att: TFloat):
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -135,62 +135,73 @@ class AD9912:
self.cpld.set_att(self.chip_select - 4, att) self.cpld.set_att(self.chip_select - 4, att)
@kernel @kernel
def set_mu(self, ftw, pow): def set_mu(self, ftw: TInt64, pow_: TInt32):
"""Set profile 0 data in machine units. """Set profile 0 data in machine units.
After the SPI transfer, the shared IO update pin is pulsed to After the SPI transfer, the shared IO update pin is pulsed to
activate the data. activate the data.
:param ftw: Frequency tuning word: 48 bit unsigned. :param ftw: Frequency tuning word: 48 bit unsigned.
:param pow: Phase tuning word: 16 bit unsigned. :param pow_: Phase tuning word: 16 bit unsigned.
""" """
# streaming transfer of FTW and POW # streaming transfer of FTW and POW
self.bus.set_config_mu(urukul.SPI_CONFIG, 16, self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((AD9912_POW1 << 16) | (3 << 29)) self.bus.write((AD9912_POW1 << 16) | (3 << 29))
self.bus.set_config_mu(urukul.SPI_CONFIG, 32, self.bus.set_config_mu(urukul.SPI_CONFIG, 32,
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((pow << 16) | (int32(ftw >> 32) & 0xffff)) self.bus.write((pow_ << 16) | (int32(ftw >> 32) & 0xffff))
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write(int32(ftw)) self.bus.write(int32(ftw))
self.cpld.io_update.pulse(10*ns) self.cpld.io_update.pulse(10 * ns)
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def frequency_to_ftw(self, frequency) -> TInt64: def frequency_to_ftw(self, frequency: TFloat) -> TInt64:
"""Returns the 48-bit frequency tuning word corresponding to the given """Returns the 48-bit frequency tuning word corresponding to the given
frequency. frequency.
""" """
return int64(round(self.ftw_per_hz*frequency)) & ((int64(1) << 48) - 1) return int64(round(self.ftw_per_hz * frequency)) & (
(int64(1) << 48) - 1)
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def ftw_to_frequency(self, ftw): def ftw_to_frequency(self, ftw: TInt64) -> TFloat:
"""Returns the frequency corresponding to the given """Returns the frequency corresponding to the given
frequency tuning word. frequency tuning word.
""" """
return ftw/self.ftw_per_hz return ftw / self.ftw_per_hz
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def turns_to_pow(self, phase) -> TInt32: def turns_to_pow(self, phase: TFloat) -> TInt32:
"""Returns the 16-bit phase offset word corresponding to the given """Returns the 16-bit phase offset word corresponding to the given
phase. phase.
""" """
return int32(round((1 << 14)*phase)) & 0xffff return int32(round((1 << 14) * phase)) & 0xffff
@portable(flags={"fast-math"})
def pow_to_turns(self, pow_: TInt32) -> TFloat:
"""Return the phase in turns corresponding to a given phase offset
word.
:param pow_: Phase offset word.
:return: Phase in turns.
"""
return pow_ / (1 << 14)
@kernel @kernel
def set(self, frequency, phase=0.0): def set(self, frequency: TFloat, phase: TFloat = 0.0):
"""Set profile 0 data in SI units. """Set profile 0 data in SI units.
.. seealso:: :meth:`set_mu` .. seealso:: :meth:`set_mu`
:param ftw: Frequency in Hz :param frequency: Frequency in Hz
:param pow: Phase tuning word in turns :param phase: Phase tuning word in turns
""" """
self.set_mu(self.frequency_to_ftw(frequency), self.set_mu(self.frequency_to_ftw(frequency),
self.turns_to_pow(phase)) self.turns_to_pow(phase))
@kernel @kernel
def cfg_sw(self, state): def cfg_sw(self, state: TInt32):
"""Set CPLD CFG RF switch state. The RF switch is controlled by the """Set CPLD CFG RF switch state. The RF switch is controlled by the
logical or of the CPLD configuration shift register logical or of the CPLD configuration shift register
RF switch bit and the SW TTL line (if used). RF switch bit and the SW TTL line (if used).

View File

@ -1,15 +1,15 @@
from numpy import int32, int64
from artiq.language.core import kernel, delay, portable, at_mu, now_mu from artiq.language.core import kernel, delay, portable, at_mu, now_mu
from artiq.language.units import us, ms from artiq.language.units import us, ms
from artiq.language.types import TInt32, TFloat, TBool
from numpy import int32, int64
from artiq.coredevice import spi2 as spi from artiq.coredevice import spi2 as spi
SPI_CONFIG = (0 * spi.SPI_OFFLINE | 0 * spi.SPI_END |
SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | 0 * spi.SPI_INPUT | 1 * spi.SPI_CS_POLARITY |
0*spi.SPI_INPUT | 1*spi.SPI_CS_POLARITY | 0 * spi.SPI_CLK_POLARITY | 0 * spi.SPI_CLK_PHASE |
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | 0 * spi.SPI_LSB_FIRST | 0 * spi.SPI_HALF_DUPLEX)
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
# SPI clock write and read dividers # SPI clock write and read dividers
SPIT_CFG_WR = 2 SPIT_CFG_WR = 2
@ -105,7 +105,7 @@ class _RegIOUpdate:
self.cpld = cpld self.cpld = cpld
@kernel @kernel
def pulse(self, t): def pulse(self, t: TFloat):
cfg = self.cpld.cfg_reg cfg = self.cpld.cfg_reg
self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE)) self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE))
delay(t) delay(t)
@ -117,7 +117,7 @@ class _DummySync:
self.cpld = cpld self.cpld = cpld
@kernel @kernel
def set_mu(self, ftw): def set_mu(self, ftw: TInt32):
pass pass
@ -196,12 +196,12 @@ class CPLD:
self.sync_div = sync_div self.sync_div = sync_div
@kernel @kernel
def cfg_write(self, cfg): def cfg_write(self, cfg: TInt32):
"""Write to the configuration register. """Write to the configuration register.
See :func:`urukul_cfg` for possible flags. See :func:`urukul_cfg` for possible flags.
:param data: 24 bit data to be written. Will be stored at :param cfg: 24 bit data to be written. Will be stored at
:attr:`cfg_reg`. :attr:`cfg_reg`.
""" """
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24,
@ -210,7 +210,7 @@ class CPLD:
self.cfg_reg = cfg self.cfg_reg = cfg
@kernel @kernel
def sta_read(self): def sta_read(self) -> TInt32:
"""Read the status register. """Read the status register.
Use any of the following functions to extract values: Use any of the following functions to extract values:
@ -229,7 +229,7 @@ class CPLD:
return self.bus.read() return self.bus.read()
@kernel @kernel
def init(self, blind=False): def init(self, blind: TBool = False):
"""Initialize and detect Urukul. """Initialize and detect Urukul.
Resets the DDS I/O interface and verifies correct CPLD gateware Resets the DDS I/O interface and verifies correct CPLD gateware
@ -247,12 +247,12 @@ class CPLD:
proto_rev = urukul_sta_proto_rev(self.sta_read()) proto_rev = urukul_sta_proto_rev(self.sta_read())
if proto_rev != STA_PROTO_REV_MATCH: if proto_rev != STA_PROTO_REV_MATCH:
raise ValueError("Urukul proto_rev mismatch") raise ValueError("Urukul proto_rev mismatch")
delay(100*us) # reset, slack delay(100 * us) # reset, slack
self.cfg_write(cfg) self.cfg_write(cfg)
if self.sync_div: if self.sync_div:
at_mu(now_mu() & ~0xf) # align to RTIO/2 at_mu(now_mu() & ~0xf) # align to RTIO/2
self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16 self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16
delay(1*ms) # DDS wake up delay(1 * ms) # DDS wake up
@kernel @kernel
def io_rst(self): def io_rst(self):
@ -261,7 +261,7 @@ class CPLD:
self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST)) self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST))
@kernel @kernel
def cfg_sw(self, channel, on): def cfg_sw(self, channel: TInt32, on: TBool):
"""Configure the RF switches through the configuration register. """Configure the RF switches through the configuration register.
These values are logically OR-ed with the LVDS lines on EEM1. These values are logically OR-ed with the LVDS lines on EEM1.
@ -277,7 +277,7 @@ class CPLD:
self.cfg_write(c) self.cfg_write(c)
@kernel @kernel
def cfg_switches(self, state): def cfg_switches(self, state: TInt32):
"""Configure all four RF switches through the configuration register. """Configure all four RF switches through the configuration register.
:param state: RF switch state as a 4 bit integer. :param state: RF switch state as a 4 bit integer.
@ -285,11 +285,12 @@ class CPLD:
self.cfg_write((self.cfg_reg & ~0xf) | state) self.cfg_write((self.cfg_reg & ~0xf) | state)
@kernel @kernel
def set_att_mu(self, channel, att): def set_att_mu(self, channel: TInt32, att: TInt32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
This method will also write the attenuator settings of the three other channels. Use This method will also write the attenuator settings of the three
:meth:`get_att_mu` to retrieve the hardware state set in previous experiments. other channels. Use :meth:`get_att_mu` to retrieve the hardware
state set in previous experiments.
:param channel: Attenuator channel (0-3). :param channel: Attenuator channel (0-3).
:param att: 8-bit digital attenuation setting: :param att: 8-bit digital attenuation setting:
@ -300,7 +301,7 @@ class CPLD:
self.set_all_att_mu(a) self.set_all_att_mu(a)
@kernel @kernel
def set_all_att_mu(self, att_reg): def set_all_att_mu(self, att_reg: TInt32):
"""Set all four digital step attenuators (in machine units). """Set all four digital step attenuators (in machine units).
.. seealso:: :meth:`set_att_mu` .. seealso:: :meth:`set_att_mu`
@ -313,7 +314,7 @@ class CPLD:
self.att_reg = att_reg self.att_reg = att_reg
@kernel @kernel
def set_att(self, channel, att): def set_att(self, channel: TInt32, att: TFloat):
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -325,16 +326,16 @@ class CPLD:
attenuation. Minimum attenuation is 0*dB, maximum attenuation is attenuation. Minimum attenuation is 0*dB, maximum attenuation is
31.5*dB. 31.5*dB.
""" """
code = 255 - int32(round(att*8)) self.set_att_mu(channel, self.att_to_mu(att))
if code < 0 or code > 255:
raise ValueError("Invalid urukul.CPLD attenuation!")
self.set_att_mu(channel, code)
@kernel @kernel
def get_att_mu(self): def get_att_mu(self) -> TInt32:
"""Return the digital step attenuator settings in machine units. """Return the digital step attenuator settings in machine units.
The result is stored and will be used in future calls of :meth:`set_att_mu`. The result is stored and will be used in future calls of
:meth:`set_att_mu` and :meth:`set_att`.
.. seealso:: :meth:`get_channel_att_mu`
:return: 32 bit attenuator settings :return: 32 bit attenuator settings
""" """
@ -343,13 +344,13 @@ class CPLD:
self.bus.write(0) # shift in zeros, shift out current value self.bus.write(0) # shift in zeros, shift out current value
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32,
SPIT_ATT_WR, CS_ATT) SPIT_ATT_WR, CS_ATT)
delay(10*us) delay(10 * us)
self.att_reg = self.bus.read() self.att_reg = self.bus.read()
self.bus.write(self.att_reg) # shift in current value again and latch self.bus.write(self.att_reg) # shift in current value again and latch
return self.att_reg return self.att_reg
@kernel @kernel
def set_sync_div(self, div): def set_sync_div(self, div: TInt32):
"""Set the SYNC_IN AD9910 pulse generator frequency """Set the SYNC_IN AD9910 pulse generator frequency
and align it to the current RTIO timestamp. and align it to the current RTIO timestamp.
@ -361,12 +362,12 @@ class CPLD:
Minimum division ratio is 2. Maximum division ratio is 16. Minimum division ratio is 2. Maximum division ratio is 16.
""" """
ftw_max = 1 << 4 ftw_max = 1 << 4
ftw = ftw_max//div ftw = ftw_max // div
assert ftw*div == ftw_max assert ftw * div == ftw_max
self.sync.set_mu(ftw) self.sync.set_mu(ftw)
@kernel @kernel
def set_profile(self, profile): def set_profile(self, profile: TInt32):
"""Set the PROFILE pins. """Set the PROFILE pins.
The PROFILE pins are common to all four DDS channels. The PROFILE pins are common to all four DDS channels.