2
0
mirror of https://github.com/m-labs/artiq.git synced 2025-02-03 06:10:18 +08:00

Add driver support for individual IO_UPDATE, PROFILE, OSK, DRG, and ATT for new Urukul proto_rev 0x09.

This commit is contained in:
newell 2024-12-22 14:06:33 -08:00
parent 9c99d116bb
commit c52bdf4fe9
2 changed files with 943 additions and 305 deletions

View File

@ -1,13 +1,11 @@
from numpy import int32, int64
from artiq.language.core import (
kernel, delay, portable, delay_mu, now_mu, at_mu)
from artiq.language.units import us, ms
from artiq.language.types import TBool, TInt32, TInt64, TFloat, TList, TTuple
from artiq.coredevice import spi2 as spi
from artiq.coredevice import urukul
from artiq.coredevice.urukul import DEFAULT_PROFILE
from artiq.coredevice.urukul import DEFAULT_PROFILE, _RegIOUpdate
from artiq.language.core import at_mu, delay, delay_mu, kernel, now_mu, portable
from artiq.language.types import TBool, TFloat, TInt32, TInt64, TList, TTuple
from artiq.language.units import ms, us
# Work around ARTIQ-Python import machinery
urukul_sta_pll_lock = urukul.urukul_sta_pll_lock
@ -15,10 +13,18 @@ urukul_sta_smp_err = urukul.urukul_sta_smp_err
__all__ = [
"AD9910",
"PHASE_MODE_CONTINUOUS", "PHASE_MODE_ABSOLUTE", "PHASE_MODE_TRACKING",
"RAM_DEST_FTW", "RAM_DEST_POW", "RAM_DEST_ASF", "RAM_DEST_POWASF",
"RAM_MODE_DIRECTSWITCH", "RAM_MODE_RAMPUP", "RAM_MODE_BIDIR_RAMP",
"RAM_MODE_CONT_BIDIR_RAMP", "RAM_MODE_CONT_RAMPUP",
"PHASE_MODE_CONTINUOUS",
"PHASE_MODE_ABSOLUTE",
"PHASE_MODE_TRACKING",
"RAM_DEST_FTW",
"RAM_DEST_POW",
"RAM_DEST_ASF",
"RAM_DEST_POWASF",
"RAM_MODE_DIRECTSWITCH",
"RAM_MODE_RAMPUP",
"RAM_MODE_BIDIR_RAMP",
"RAM_MODE_CONT_BIDIR_RAMP",
"RAM_MODE_CONT_RAMPUP",
]
_PHASE_MODE_DEFAULT = -1
@ -34,12 +40,12 @@ _AD9910_REG_IO_UPDATE = 0x04
_AD9910_REG_FTW = 0x07
_AD9910_REG_POW = 0x08
_AD9910_REG_ASF = 0x09
_AD9910_REG_SYNC = 0x0a
_AD9910_REG_RAMP_LIMIT = 0x0b
_AD9910_REG_RAMP_STEP = 0x0c
_AD9910_REG_RAMP_RATE = 0x0d
_AD9910_REG_PROFILE0 = 0x0e
_AD9910_REG_PROFILE1 = 0x0f
_AD9910_REG_SYNC = 0x0A
_AD9910_REG_RAMP_LIMIT = 0x0B
_AD9910_REG_RAMP_STEP = 0x0C
_AD9910_REG_RAMP_RATE = 0x0D
_AD9910_REG_PROFILE0 = 0x0E
_AD9910_REG_PROFILE1 = 0x0F
_AD9910_REG_PROFILE2 = 0x10
_AD9910_REG_PROFILE3 = 0x11
_AD9910_REG_PROFILE4 = 0x12
@ -92,10 +98,10 @@ class SyncDataEeprom:
word = self.eeprom_device.read_i32(self.eeprom_offset) >> 16
sync_delay_seed = word >> 8
if sync_delay_seed >= 0:
io_update_delay = word & 0xff
io_update_delay = word & 0xFF
else:
io_update_delay = 0
if io_update_delay == 0xff: # unprogrammed EEPROM
if io_update_delay == 0xFF: # unprogrammed EEPROM
io_update_delay = 0
# With Numpy, type(int32(-1) >> 1) == int64
self.sync_delay_seed = int32(sync_delay_seed)
@ -138,13 +144,33 @@ class AD9910:
to the same string value.
"""
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1,
io_update_delay=0, pll_en=1):
self.kernel_invariants = {"cpld", "core", "bus", "chip_select",
"pll_en", "pll_n", "pll_vco", "pll_cp",
"ftw_per_hz", "sysclk_per_mu", "sysclk",
"sync_data"}
def __init__(
self,
dmgr,
chip_select,
cpld_device,
sw_device=None,
pll_n=40,
pll_cp=7,
pll_vco=5,
sync_delay_seed=-1,
io_update_delay=0,
pll_en=1,
):
self.kernel_invariants = {
"cpld",
"core",
"bus",
"chip_select",
"pll_en",
"pll_n",
"pll_vco",
"pll_cp",
"ftw_per_hz",
"sysclk_per_mu",
"sysclk",
"sync_data",
}
self.cpld = dmgr.get(cpld_device)
self.core = self.cpld.core
self.bus = self.cpld.bus
@ -163,8 +189,14 @@ class AD9910:
assert clk <= 60e6
assert 12 <= pll_n <= 127
assert 0 <= pll_vco <= 5
vco_min, vco_max = [(370, 510), (420, 590), (500, 700),
(600, 880), (700, 950), (820, 1150)][pll_vco]
vco_min, vco_max = [
(370, 510),
(420, 590),
(500, 700),
(600, 880),
(700, 950),
(820, 1150),
][pll_vco]
assert vco_min <= sysclk / 1e6 <= vco_max
assert 0 <= pll_cp <= 7
else:
@ -174,15 +206,18 @@ class AD9910:
self.sysclk_per_mu = int(round(sysclk * self.core.ref_period))
self.sysclk = sysclk
if isinstance(sync_delay_seed, str) or isinstance(io_update_delay,
str):
if not self.cpld.io_update:
self.cpld.io_update = _RegIOUpdate(self.cpld, self.chip_select)
if isinstance(sync_delay_seed, str) or isinstance(io_update_delay, str):
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)
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
@ -236,9 +271,10 @@ class AD9910:
:param addr: Register address
:param data: Data to be written
"""
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 24,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((addr << 24) | ((data & 0xffff) << 8))
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END, 24, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write((addr << 24) | ((data & 0xFFFF) << 8))
@kernel
def write32(self, addr: TInt32, data: TInt32):
@ -247,11 +283,13 @@ class AD9910:
:param addr: Register address
:param data: Data to be written
"""
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(addr << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END, 32, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(data)
@kernel
@ -260,12 +298,16 @@ class AD9910:
:param addr: Register address
"""
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write((addr | 0x80) << 24)
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
16, urukul.SPIT_DDS_RD, self.chip_select)
16,
urukul.SPIT_DDS_RD,
self.chip_select,
)
self.bus.write(0)
return self.bus.read()
@ -275,12 +317,16 @@ class AD9910:
:param addr: Register address
"""
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write((addr | 0x80) << 24)
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
32, urukul.SPIT_DDS_RD, self.chip_select)
32,
urukul.SPIT_DDS_RD,
self.chip_select,
)
self.bus.write(0)
return self.bus.read()
@ -292,16 +338,19 @@ class AD9910:
:return: 64-bit integer register value
"""
self.bus.set_config_mu(
urukul.SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select)
urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write((addr | 0x80) << 24)
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_INPUT, 32,
urukul.SPIT_DDS_RD, self.chip_select)
urukul.SPI_CONFIG | spi.SPI_INPUT, 32, urukul.SPIT_DDS_RD, self.chip_select
)
self.bus.write(0)
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 32,
urukul.SPIT_DDS_RD, self.chip_select)
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
32,
urukul.SPIT_DDS_RD,
self.chip_select,
)
self.bus.write(0)
hi = self.bus.read()
lo = self.bus.read()
@ -312,17 +361,20 @@ class AD9910:
"""Write to 64-bit register.
:param addr: Register address
:param data_high: High (MSB) 32 data bits
:param data_high: High (MSB) 32 data bits
:param data_low: Low (LSB) 32 data bits
"""
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(addr << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG, 32,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG, 32, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(data_high)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END, 32, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(data_low)
@kernel
@ -336,15 +388,18 @@ class AD9910:
:param data: Data to be written to RAM.
"""
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR,
self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(_AD9910_REG_RAM << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG, 32,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG, 32, urukul.SPIT_DDS_WR, self.chip_select
)
for i in range(len(data) - 1):
self.bus.write(data[i])
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END, 32, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(data[len(data) - 1])
@kernel
@ -353,43 +408,53 @@ class AD9910:
The profile to read from and the step, start, and end address
need to be configured before and separately using
:meth:`set_profile_ram` and the parent CPLD
:meth:`set_profile_ram` and the parent CPLD
:meth:`~artiq.coredevice.urukul.CPLD.set_profile`.
: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.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write((_AD9910_REG_RAM | 0x80) << 24)
n = len(data) - 1
if n > 0:
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_INPUT, 32,
urukul.SPIT_DDS_RD, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_INPUT,
32,
urukul.SPIT_DDS_RD,
self.chip_select,
)
preload = min(n, 8)
for i in range(n):
self.bus.write(0)
if i >= preload:
data[i - preload] = self.bus.read()
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 32,
urukul.SPIT_DDS_RD, self.chip_select)
urukul.SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END,
32,
urukul.SPIT_DDS_RD,
self.chip_select,
)
self.bus.write(0)
for i in range(preload + 1):
data[(n - preload) + i] = self.bus.read()
@kernel
def set_cfr1(self,
power_down: TInt32 = 0b0000,
phase_autoclear: TInt32 = 0,
drg_load_lrr: TInt32 = 0,
drg_autoclear: TInt32 = 0,
phase_clear: TInt32 = 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):
def set_cfr1(
self,
power_down: TInt32 = 0b0000,
phase_autoclear: TInt32 = 0,
drg_load_lrr: TInt32 = 0,
drg_autoclear: TInt32 = 0,
phase_clear: TInt32 = 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 and sizes.
This method does not pulse ``IO_UPDATE.``
@ -408,33 +473,43 @@ class AD9910:
:param osk_enable: Enable OSK mode.
:param select_auto_osk: Select manual or automatic OSK mode.
"""
self.write32(_AD9910_REG_CFR1,
(ram_enable << 31) |
(ram_destination << 29) |
(manual_osk_external << 23) |
(internal_profile << 17) |
(drg_load_lrr << 15) |
(drg_autoclear << 14) |
(phase_autoclear << 13) |
(phase_clear << 11) |
(osk_enable << 9) |
(select_auto_osk << 8) |
(power_down << 4) |
2) # SDIO input only, MSB first
self.write32(
_AD9910_REG_CFR1,
(ram_enable << 31)
| (ram_destination << 29)
| (manual_osk_external << 23)
| (internal_profile << 17)
| (drg_load_lrr << 15)
| (drg_autoclear << 14)
| (phase_autoclear << 13)
| (phase_clear << 11)
| (osk_enable << 9)
| (select_auto_osk << 8)
| (power_down << 4)
| 2,
) # SDIO input only, MSB first
@kernel
def set_cfr2(self,
asf_profile_enable: TInt32 = 1,
drg_enable: TInt32 = 0,
effective_ftw: TInt32 = 1,
sync_validation_disable: TInt32 = 0,
matched_latency_enable: TInt32 = 0):
def set_cfr2(
self,
asf_profile_enable: TInt32 = 1,
drg_destination: TInt32 = 0,
drg_enable: TInt32 = 0,
drg_nodwell_high: TInt32 = 0,
drg_nodwell_low: TInt32 = 0,
effective_ftw: TInt32 = 1,
sync_validation_disable: TInt32 = 0,
matched_latency_enable: TInt32 = 0,
):
"""Set CFR2. See the AD9910 datasheet for parameter meanings and sizes.
This method does not pulse ``IO_UPDATE``.
:param asf_profile_enable: Enable amplitude scale from single tone profiles.
:param drg_destination: Digital ramp destination.
:param drg_enable: Digital ramp enable.
:param drg_nodwell_high: Digital ramp no-dwell high.
:param drg_nodwell_low: Digital ramp no-dwell low.
:param effective_ftw: Read effective FTW.
:param sync_validation_disable: Disable the SYNC_SMP_ERR pin indicating
(active high) detection of a synchronization pulse sampling error.
@ -444,19 +519,24 @@ class AD9910:
* matched_latency_enable = 0: in the order listed
* matched_latency_enable = 1: simultaneously.
"""
self.write32(_AD9910_REG_CFR2,
(asf_profile_enable << 24) |
(drg_enable << 19) |
(effective_ftw << 16) |
(matched_latency_enable << 7) |
(sync_validation_disable << 5))
self.write32(
_AD9910_REG_CFR2,
(asf_profile_enable << 24)
| (drg_destination << 20)
| (drg_enable << 19)
| (drg_nodwell_high << 18)
| (drg_nodwell_low << 17)
| (effective_ftw << 16)
| (matched_latency_enable << 7)
| (sync_validation_disable << 5),
)
@kernel
def init(self, blind: TBool = False):
"""Initialize and configure the DDS.
Sets up SPI mode, confirms chip presence, powers down unused blocks,
configures the PLL, waits for PLL lock. Uses the ``IO_UPDATE``
configures the PLL, waits for PLL lock. Uses the ``IO_UPDATE``
signal multiple times.
:param blind: Do not read back DDS identity and do not wait for lock.
@ -476,7 +556,7 @@ class AD9910:
if not blind:
# Use the AUX DAC setting to identify and confirm presence
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")
delay(50 * us) # slack
# Configure PLL settings and bring up PLL
@ -485,9 +565,13 @@ class AD9910:
# sync timing validation disable (enabled later)
self.set_cfr2(sync_validation_disable=1)
self.cpld.io_update.pulse(1 * us)
cfr3 = (0x0807c000 | (self.pll_vco << 24) |
(self.pll_cp << 19) | (self.pll_en << 8) |
(self.pll_n << 1))
cfr3 = (
0x0807C000
| (self.pll_vco << 24)
| (self.pll_cp << 19)
| (self.pll_en << 8)
| (self.pll_n << 1)
)
self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset
self.cpld.io_update.pulse(1 * us)
if self.pll_en:
@ -520,11 +604,16 @@ class AD9910:
self.cpld.io_update.pulse(1 * us)
@kernel
def set_mu(self, ftw: TInt32 = 0, pow_: TInt32 = 0, asf: TInt32 = 0x3fff,
phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1),
profile: TInt32 = DEFAULT_PROFILE,
ram_destination: TInt32 = -1) -> TInt32:
def set_mu(
self,
ftw: TInt32 = 0,
pow_: TInt32 = 0,
asf: TInt32 = 0x3FFF,
phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1),
profile: TInt32 = DEFAULT_PROFILE,
ram_destination: TInt32 = -1,
) -> TInt32:
"""Set DDS data in machine units.
This uses machine units (FTW, POW, ASF). The frequency tuning word
@ -574,8 +663,9 @@ class AD9910:
dt = int32(now_mu()) - int32(ref_time_mu)
pow_ += dt * ftw * self.sysclk_per_mu >> 16
if ram_destination == -1:
self.write64(_AD9910_REG_PROFILE0 + profile,
(asf << 16) | (pow_ & 0xffff), ftw)
self.write64(
_AD9910_REG_PROFILE0 + profile, (asf << 16) | (pow_ & 0xFFFF), ftw
)
else:
if not ram_destination == RAM_DEST_FTW:
self.set_ftw(ftw)
@ -593,8 +683,9 @@ class AD9910:
return pow_
@kernel
def get_mu(self, profile: TInt32 = DEFAULT_PROFILE
) -> TTuple([TInt32, TInt32, TInt32]):
def get_mu(
self, profile: TInt32 = DEFAULT_PROFILE
) -> TTuple([TInt32, TInt32, TInt32]):
"""Get the frequency tuning word, phase offset word,
and amplitude scale factor.
@ -608,15 +699,21 @@ class AD9910:
data = int64(self.read64(_AD9910_REG_PROFILE0 + profile))
# Extract and return fields
ftw = int32(data)
pow_ = int32((data >> 32) & 0xffff)
asf = int32((data >> 48) & 0x3fff)
pow_ = int32((data >> 32) & 0xFFFF)
asf = int32((data >> 48) & 0x3FFF)
return ftw, pow_, asf
@kernel
def set_profile_ram(self, start: TInt32, end: TInt32, step: TInt32 = 1,
profile: TInt32 = _DEFAULT_PROFILE_RAM,
nodwell_high: TInt32 = 0, zero_crossing: TInt32 = 0,
mode: TInt32 = 1):
def set_profile_ram(
self,
start: TInt32,
end: TInt32,
step: TInt32 = 1,
profile: TInt32 = _DEFAULT_PROFILE_RAM,
nodwell_high: TInt32 = 0,
zero_crossing: TInt32 = 0,
mode: TInt32 = 1,
):
"""Set the RAM profile settings. See also AD9910 datasheet.
:param start: Profile start address in RAM (10-bit).
@ -635,8 +732,13 @@ class AD9910:
:const:`RAM_MODE_RAMPUP`)
"""
hi = (step << 8) | (end >> 2)
lo = ((end << 30) | (start << 14) | (nodwell_high << 5) |
(zero_crossing << 3) | mode)
lo = (
(end << 30)
| (start << 14)
| (nodwell_high << 5)
| (zero_crossing << 3)
| mode
)
self.write64(_AD9910_REG_PROFILE0 + profile, hi, lo)
@kernel
@ -711,7 +813,7 @@ class AD9910:
def turns_to_pow(self, turns: TFloat) -> TInt32:
"""Return the 16-bit phase offset word corresponding to the given phase
in turns."""
return int32(round(turns * 0x10000)) & int32(0xffff)
return int32(round(turns * 0x10000)) & int32(0xFFFF)
@portable(flags={"fast-math"})
def pow_to_turns(self, pow_: TInt32) -> TFloat:
@ -723,8 +825,8 @@ class AD9910:
def amplitude_to_asf(self, amplitude: TFloat) -> TInt32:
"""Return 14-bit amplitude scale factor corresponding to given
fractional amplitude."""
code = int32(round(amplitude * 0x3fff))
if code < 0 or code > 0x3fff:
code = int32(round(amplitude * 0x3FFF))
if code < 0 or code > 0x3FFF:
raise ValueError("Invalid AD9910 fractional amplitude!")
return code
@ -732,7 +834,7 @@ class AD9910:
def asf_to_amplitude(self, asf: TInt32) -> TFloat:
"""Return amplitude as a fraction of full scale corresponding to given
amplitude scale factor."""
return asf / float(0x3fff)
return asf / float(0x3FFF)
@portable(flags={"fast-math"})
def frequency_to_ram(self, frequency: TList(TFloat), ram: TList(TInt32)):
@ -774,8 +876,9 @@ class AD9910:
ram[i] = self.amplitude_to_asf(amplitude[i]) << 18
@portable(flags={"fast-math"})
def turns_amplitude_to_ram(self, turns: TList(TFloat),
amplitude: TList(TFloat), ram: TList(TInt32)):
def turns_amplitude_to_ram(
self, turns: TList(TFloat), amplitude: TList(TFloat), ram: TList(TInt32)
):
"""Convert phase and amplitude values to RAM profile data.
To be used with :const:`RAM_DEST_POWASF`.
@ -786,8 +889,9 @@ class AD9910:
Suitable for :meth:`write_ram`.
"""
for i in range(len(ram)):
ram[i] = ((self.turns_to_pow(turns[i]) << 16) |
self.amplitude_to_asf(amplitude[i]) << 2)
ram[i] = (self.turns_to_pow(turns[i]) << 16) | self.amplitude_to_asf(
amplitude[i]
) << 2
@kernel
def set_frequency(self, frequency: TFloat):
@ -844,10 +948,16 @@ class AD9910:
return self.pow_to_turns(self.get_pow())
@kernel
def set(self, frequency: TFloat = 0.0, phase: TFloat = 0.0,
amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1), profile: TInt32 = DEFAULT_PROFILE,
ram_destination: TInt32 = -1) -> TFloat:
def set(
self,
frequency: TFloat = 0.0,
phase: TFloat = 0.0,
amplitude: TFloat = 1.0,
phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1),
profile: TInt32 = DEFAULT_PROFILE,
ram_destination: TInt32 = -1,
) -> TFloat:
"""Set DDS data in SI units.
See also :meth:`AD9910.set_mu`.
@ -861,14 +971,22 @@ class AD9910:
:param ram_destination: RAM destination.
:return: Resulting phase offset in turns
"""
return self.pow_to_turns(self.set_mu(
self.frequency_to_ftw(frequency), self.turns_to_pow(phase),
self.amplitude_to_asf(amplitude), phase_mode, ref_time_mu,
profile, ram_destination))
return self.pow_to_turns(
self.set_mu(
self.frequency_to_ftw(frequency),
self.turns_to_pow(phase),
self.amplitude_to_asf(amplitude),
phase_mode,
ref_time_mu,
profile,
ram_destination,
)
)
@kernel
def get(self, profile: TInt32 = DEFAULT_PROFILE
) -> TTuple([TFloat, TFloat, TFloat]):
def get(
self, profile: TInt32 = DEFAULT_PROFILE
) -> TTuple([TFloat, TFloat, TFloat]):
"""Get the frequency, phase, and amplitude.
See also :meth:`AD9910.get_mu`.
@ -880,14 +998,19 @@ class AD9910:
# Get values
ftw, pow_, asf = self.get_mu(profile)
# Convert and return
return (self.ftw_to_frequency(ftw), self.pow_to_turns(pow_),
self.asf_to_amplitude(asf))
return (
self.ftw_to_frequency(ftw),
self.pow_to_turns(pow_),
self.asf_to_amplitude(asf),
)
@kernel
def set_att_mu(self, att: TInt32):
"""Set digital step attenuator in machine units.
This method will write the attenuator settings of all four channels. See also
This method will write the attenuator settings of the channel
(Urukul proto_rev 0x08, all four channels will be updated at same time).
See also
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.set_att_mu>`.
:param att: Attenuation setting, 8-bit digital.
@ -898,7 +1021,9 @@ class AD9910:
def set_att(self, att: TFloat):
"""Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels. See also
This method will write the attenuator settings of the channel
(Urukul proto_rev 0x08, all four channels will be updated at same time).
See also
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.set_att>`.
:param att: Attenuation in dB.
@ -916,7 +1041,7 @@ class AD9910:
@kernel
def get_att(self) -> TFloat:
"""Get digital step attenuator value in SI units. See also
"""Get digital step attenuator value in SI units. See also
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.get_channel_att>`.
:return: Attenuation in dB.
@ -934,10 +1059,52 @@ class AD9910:
self.cpld.cfg_sw(self.chip_select - 4, state)
@kernel
def set_sync(self,
in_delay: TInt32,
window: TInt32,
en_sync_gen: TInt32 = 0):
def cfg_osk(self, state: TBool):
"""Set CPLD CFG OSK state. The OSK bit is controlled by the
logical or of the CPLD configuration shift register OSK bit.
:param state: CPLD CFG OSK bit
"""
self.cpld.cfg_osk(self.chip_select - 4, state)
@kernel
def cfg_drctl(self, state: TBool):
"""Set CPLD CFG DRCTL state. The DRCTL bit is controlled by the
logical or of the CPLD configuration shift register DRCTL bit.
:param state: CPLD CFG DRCTL bit
"""
self.cpld.cfg_drctl(self.chip_select - 4, state)
@kernel
def cfg_drhold(self, state: TBool):
"""Set CPLD CFG DRHOLD state. The DRHOLD bit is controlled by the
logical or of the CPLD configuration shift register DRHOLD bit.
:param state: CPLD CFG DRHOLD bit
"""
self.cpld.cfg_drhold(self.chip_select - 4, state)
@kernel
def cfg_mask_nu(self, state: TBool):
"""Set CPLD CFG MASK_NU state. The MASK_NU bit is controlled by the
logical or of the CPLD configuration shift register MASK_NU bit.
:param state: CPLD CFG MASK_NU bit
"""
self.cpld.cfg_mask_nu(self.chip_select - 4, state)
@kernel
def cfg_att_en(self, state: TBool):
"""Set CPLD CFG ATT_EN state. The ATT_EN bit is controlled by the
logical or of the CPLD configuration shift register ATT_EN bit.
:param state: CPLD CFG ATT_EN bit
"""
self.cpld.cfg_att_en(self.chip_select - 4, state)
@kernel
def set_sync(self, in_delay: TInt32, window: TInt32, en_sync_gen: TInt32 = 0):
"""Set the relevant parameters in the multi device synchronization
register. See the AD9910 datasheet for details. The ``SYNC`` clock
generator preset value is set to zero, and the ``SYNC_OUT`` generator is
@ -950,14 +1117,16 @@ class AD9910:
(``SYNC_OUT``, cf. ``sync_sel == 1``). Should be left off for the normal
use case, where the ``SYNC`` clock is supplied by the core device.
"""
self.write32(_AD9910_REG_SYNC,
(window << 28) | # SYNC S/H validation delay
(1 << 27) | # SYNC receiver enable
(en_sync_gen << 26) | # SYNC generator enable
(0 << 25) | # SYNC generator SYS rising edge
(0 << 18) | # SYNC preset
(0 << 11) | # SYNC output delay
(in_delay << 3)) # SYNC receiver delay
self.write32(
_AD9910_REG_SYNC,
(window << 28) # SYNC S/H validation delay
| (1 << 27) # SYNC receiver enable
| (en_sync_gen << 26) # SYNC generator enable
| (0 << 25) # SYNC generator SYS rising edge
| (0 << 18) # SYNC preset
| (0 << 11) # SYNC output delay
| (in_delay << 3),
) # SYNC receiver delay
@kernel
def clear_smp_err(self):
@ -976,8 +1145,7 @@ class AD9910:
self.cpld.io_update.pulse(1 * us)
@kernel
def tune_sync_delay(self,
search_seed: TInt32 = 15) -> TTuple([TInt32, TInt32]):
def tune_sync_delay(self, search_seed: TInt32 = 15) -> TTuple([TInt32, TInt32]):
"""Find a stable ``SYNC_IN`` delay.
This method first locates a valid ``SYNC_IN`` delay at zero validation
@ -1033,8 +1201,9 @@ class AD9910:
raise ValueError("no valid window/delay")
@kernel
def measure_io_update_alignment(self, delay_start: TInt64,
delay_stop: TInt64) -> TInt32:
def measure_io_update_alignment(
self, delay_start: TInt64, delay_stop: TInt64
) -> TInt32:
"""Use the digital ramp generator to locate the alignment between
``IO_UPDATE`` and ``SYNC_CLK``.
@ -1106,11 +1275,9 @@ class AD9910:
for j in range(repeat):
t1[0] += self.measure_io_update_alignment(i, i + 1)
t1[1] += self.measure_io_update_alignment(i + 1, i + 2)
if ((t1[0] == 0 and t1[1] == 0) or
(t1[0] == repeat and t1[1] == repeat)):
if (t1[0] == 0 and t1[1] == 0) or (t1[0] == repeat and t1[1] == repeat):
# edge is not close to i + 1, can't interpret result
raise ValueError(
"no clear IO_UPDATE-SYNC_CLK alignment edge found")
raise ValueError("no clear IO_UPDATE-SYNC_CLK alignment edge found")
else:
# the good delay is period//2 after the edge
return (i + 1 + period // 2) & (period - 1)

View File

@ -1,15 +1,23 @@
from abc import ABC, abstractmethod
from typing import Union
from numpy import int32, int64
from artiq.language.core import kernel, delay, portable, at_mu, now_mu
from artiq.language.units import us, ms
from artiq.language.types import TInt32, TFloat, TBool
from artiq.coredevice import spi2 as spi
from artiq.language.core import at_mu, delay, kernel, now_mu, portable
from artiq.language.types import TBool, TFloat, TInt32, TInt64
from artiq.language.units import ms, us
SPI_CONFIG = (0 * spi.SPI_OFFLINE | 0 * spi.SPI_END |
0 * spi.SPI_INPUT | 1 * spi.SPI_CS_POLARITY |
0 * spi.SPI_CLK_POLARITY | 0 * spi.SPI_CLK_PHASE |
0 * spi.SPI_LSB_FIRST | 0 * spi.SPI_HALF_DUPLEX)
SPI_CONFIG = (
0 * spi.SPI_OFFLINE
| 0 * spi.SPI_END
| 0 * spi.SPI_INPUT
| 1 * spi.SPI_CS_POLARITY
| 0 * spi.SPI_CLK_POLARITY
| 0 * spi.SPI_CLK_PHASE
| 0 * spi.SPI_LSB_FIRST
| 0 * spi.SPI_HALF_DUPLEX
)
# SPI clock write and read dividers
SPIT_CFG_WR = 2
@ -20,30 +28,24 @@ SPIT_ATT_RD = 16
SPIT_DDS_WR = 2
SPIT_DDS_RD = 16
# CFG configuration register bit offsets
# Common CFG configuration register bit offsets
CFG_RF_SW = 0
CFG_LED = 4
CFG_PROFILE = 8
CFG_IO_UPDATE = 12
CFG_MASK_NU = 13
CFG_CLK_SEL0 = 17
CFG_CLK_SEL1 = 21
CFG_SYNC_SEL = 18
CFG_RST = 19
CFG_IO_RST = 20
CFG_CLK_DIV = 22
# STA status register bit offsets
# Common STA status register bit offsets
STA_RF_SW = 0
STA_SMP_ERR = 4
STA_PLL_LOCK = 8
STA_IFC_MODE = 12
STA_PROTO_REV = 16
STA_DROVER = 23
# supported hardware and CPLD code version
STA_PROTO_REV_MATCH = 0x08
# Supported hardware and CPLD code version
STA_PROTO_REV_8 = 0x08
STA_PROTO_REV_9 = 0x09
# chip select (decoded)
# Chip select (decoded)
CS_CFG = 1
CS_ATT = 2
CS_DDS_MULTI = 3
@ -56,62 +58,77 @@ CS_DDS_CH3 = 7
DEFAULT_PROFILE = 7
@portable
def urukul_cfg(rf_sw, led, profile, io_update, mask_nu,
clk_sel, sync_sel, rst, io_rst, clk_div):
"""Build Urukul CPLD configuration register"""
return ((rf_sw << CFG_RF_SW) |
(led << CFG_LED) |
(profile << CFG_PROFILE) |
(io_update << CFG_IO_UPDATE) |
(mask_nu << CFG_MASK_NU) |
((clk_sel & 0x01) << CFG_CLK_SEL0) |
((clk_sel & 0x02) << (CFG_CLK_SEL1 - 1)) |
(sync_sel << CFG_SYNC_SEL) |
(rst << CFG_RST) |
(io_rst << CFG_IO_RST) |
(clk_div << CFG_CLK_DIV))
@portable
def urukul_sta_rf_sw(sta):
"""Return the RF switch status from Urukul status register value."""
return (sta >> STA_RF_SW) & 0xf
return (sta >> STA_RF_SW) & 0xF
@portable
def urukul_sta_smp_err(sta):
"""Return the SMP_ERR status from Urukul status register value."""
return (sta >> STA_SMP_ERR) & 0xf
return (sta >> STA_SMP_ERR) & 0xF
@portable
def urukul_sta_pll_lock(sta):
"""Return the PLL_LOCK status from Urukul status register value."""
return (sta >> STA_PLL_LOCK) & 0xf
return (sta >> STA_PLL_LOCK) & 0xF
@portable
def urukul_sta_ifc_mode(sta):
"""Return the IFC_MODE status from Urukul status register value."""
return (sta >> STA_IFC_MODE) & 0xf
return (sta >> STA_IFC_MODE) & 0xF
@portable
def urukul_sta_proto_rev(sta):
"""Return the PROTO_REV value from Urukul status register value."""
return (sta >> STA_PROTO_REV) & 0x7f
return (sta >> STA_PROTO_REV) & 0x7F
@portable
def urukul_sta_drover(sta):
"""Return the DROVER status from Urukul status register value."""
return (sta >> STA_DROVER) & 0xF
class _RegIOUpdate:
def __init__(self, cpld):
def __init__(self, cpld, chip_select):
self.cpld = cpld
self.chip_select = chip_select
@kernel
def pulse(self, t: TFloat):
def pulse_mu(self, duration):
"""Pulse the output high for the specified duration
(in machine units).
The time cursor is advanced by the specified duration."""
cfg = self.cpld.cfg_reg
self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE))
delay(t)
if self.cpld.proto_rev == 0x08:
self.cpld.cfg_write(cfg | (1 << ProtoRev8.CFG_IO_UPDATE))
else:
self.cpld.cfg_write(
cfg | (int64(1) << (ProtoRev9.CFG_IO_UPDATE + (self.chip_select - 4)))
)
delay_mu(duration)
self.cpld.cfg_write(cfg)
@kernel
def pulse(self, duration):
"""Pulse the output high for the specified duration
(in seconds).
The time cursor is advanced by the specified duration."""
cfg = self.cpld.cfg_reg
if self.cpld.proto_rev == 0x08:
self.cpld.cfg_write(cfg | (1 << ProtoRev8.CFG_IO_UPDATE))
else:
self.cpld.cfg_write(
cfg | (int64(1) << (ProtoRev9.CFG_IO_UPDATE + (self.chip_select - 4)))
)
delay(duration + 50 * ms) # Needed extra for slack
self.cpld.cfg_write(cfg)
@ -124,6 +141,420 @@ class _DummySync:
pass
class CPLDVersionManager(ABC):
@abstractmethod
@kernel
def cfg_write(self, cpld, cfg):
pass
@abstractmethod
@kernel
def sta_read(self, cpld):
pass
@abstractmethod
@kernel
def init(self, cpld):
pass
@abstractmethod
@kernel
def io_rst(self, cpld):
pass
def _not_implemented(self, *args, **kwargs):
raise NotImplementedError(
"This function is not implemented for this Urukul version."
)
@kernel
def configure_bit(self, cpld, bit_offset: TInt32, channel: TInt32, on: TBool):
self._not_implemented()
@kernel
def configure_all_bits(self, cpld, bit_offset: TInt32, state: TInt32):
self._not_implemented()
@kernel
def cfg_att_en(self, cpld, channel: TInt32, on: TBool):
self._not_implemented()
@kernel
def cfg_att_en_all(self, cpld, state: TInt32):
self._not_implemented()
@kernel
def cfg_osk(self, cpld, channel: TInt32, on: TBool):
self._not_implemented()
@kernel
def cfg_osk_all(self, cpld, state: TInt32):
self._not_implemented()
@kernel
def cfg_drctl(self, cpld, channel: TInt32, on: TBool):
self._not_implemented()
@kernel
def cfg_drctl_all(self, cpld, state: TInt32):
self._not_implemented()
@kernel
def cfg_drhold(self, cpld, channel: TInt32, on: TBool):
self._not_implemented()
@kernel
def cfg_drhold_all(self, cpld, state: TInt32):
self._not_implemented()
@kernel
def cfg_mask_nu(self, cpld, channel: TInt32, on: TBool):
self._not_implemented()
@kernel
def cfg_mask_nu_all(self, cpld, state: TInt32):
self._not_implemented()
class ProtoRev8(CPLDVersionManager):
# ProtoRev8 CFG configuration register bit offsets
CFG_IO_UPDATE = 12
CFG_MASK_NU = 13
CFG_CLK_SEL0 = 17
CFG_CLK_SEL1 = 21
CFG_SYNC_SEL = 18
CFG_RST = 19
CFG_IO_RST = 20
CFG_CLK_DIV = 22
@staticmethod
@portable
def urukul_cfg(
rf_sw,
led,
profile,
io_update,
mask_nu,
clk_sel,
sync_sel,
rst,
io_rst,
clk_div,
):
"""Build Urukul CPLD configuration register"""
return (
(rf_sw << CFG_RF_SW)
| (led << CFG_LED)
| (profile << CFG_PROFILE)
| (io_update << ProtoRev8.CFG_IO_UPDATE)
| (mask_nu << ProtoRev8.CFG_MASK_NU)
| ((clk_sel & 0x01) << ProtoRev8.CFG_CLK_SEL0)
| ((clk_sel & 0x02) << (ProtoRev8.CFG_CLK_SEL1 - 1))
| (sync_sel << ProtoRev8.CFG_SYNC_SEL)
| (rst << ProtoRev8.CFG_RST)
| (io_rst << ProtoRev8.CFG_IO_RST)
| (clk_div << ProtoRev8.CFG_CLK_DIV)
)
@kernel
def cfg_write(self, cpld, cfg: TInt32):
"""Write to the configuration register.
See :func:`urukul_cfg` for possible flags.
:param cfg: 24-bit data to be written. Will be stored at
:attr:`cfg_reg`.
"""
cpld.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, SPIT_CFG_WR, CS_CFG)
cpld.bus.write(cfg << 8)
cpld.cfg_reg = cfg
@kernel
def sta_read(self, cpld) -> TInt32:
"""Read the status register.
Use any of the following functions to extract values:
* :func:`urukul_sta_rf_sw`
* :func:`urukul_sta_smp_err`
* :func:`urukul_sta_pll_lock`
* :func:`urukul_sta_ifc_mode`
* :func:`urukul_sta_proto_rev`
:return: The status register value.
"""
cpld.bus.set_config_mu(
SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 24, SPIT_CFG_RD, CS_CFG
)
cpld.bus.write(cpld.cfg_reg << 8)
return cpld.bus.read()
@kernel
def init(self, cpld):
"""Initialize and detect Urukul.
Resets the DDS I/O interface and verifies correct CPLD gateware
version.
Does not pulse the DDS ``MASTER_RESET`` as that confuses the AD9910.
"""
cfg = cpld.cfg_reg
# Don't pulse MASTER_RESET (m-labs/artiq#940)
cpld.cfg_reg = cfg | (0 << ProtoRev8.CFG_RST) | (1 << ProtoRev8.CFG_IO_RST)
delay(100 * us) # reset, slack
cpld.cfg_write(cfg)
if cpld.sync_div:
at_mu(now_mu() & ~0xF) # align to RTIO/2
cpld.set_sync_div(cpld.sync_div) # 125 MHz/2 = 1 GHz/16
delay(1 * ms) # DDS wake up
@kernel
def io_rst(self, cpld):
"""Pulse IO_RST"""
cpld.cfg_write(cpld.cfg_reg | (1 << ProtoRev8.CFG_IO_RST))
cpld.cfg_write(cpld.cfg_reg & ~(1 << ProtoRev8.CFG_IO_RST))
class ProtoRev9(CPLDVersionManager):
# ProtoRev9 CFG configuration register bit offsets
CFG_OSK = 20
CFG_DRCTL = 24
CFG_DRHOLD = 28
CFG_IO_UPDATE = 32
CFG_MASK_NU = 36
CFG_CLK_SEL0 = 40
CFG_CLK_SEL1 = 44
CFG_SYNC_SEL = 41
CFG_RST = 42
CFG_IO_RST = 43
CFG_CLK_DIV = 45
CFG_ATT_EN = 47
@staticmethod
@portable
def urukul_cfg(
rf_sw,
led,
profile,
osk,
drctl,
drhold,
io_update,
mask_nu,
clk_sel,
sync_sel,
rst,
io_rst,
clk_div,
att_en,
):
"""Build Urukul CPLD configuration register"""
return (
(rf_sw << CFG_RF_SW)
| (led << CFG_LED)
| (profile << CFG_PROFILE)
| (osk << ProtoRev9.CFG_OSK)
| (drctl << ProtoRev9.CFG_DRCTL)
| (drhold << ProtoRev9.CFG_DRHOLD)
| (io_update << ProtoRev9.CFG_IO_UPDATE)
| (mask_nu << ProtoRev9.CFG_MASK_NU)
| ((clk_sel & 0x01) << ProtoRev9.CFG_CLK_SEL0)
| ((clk_sel & 0x02) << (ProtoRev9.CFG_CLK_SEL1 - 1))
| (sync_sel << ProtoRev9.CFG_SYNC_SEL)
| (rst << ProtoRev9.CFG_RST)
| (io_rst << ProtoRev9.CFG_IO_RST)
| (clk_div << ProtoRev9.CFG_CLK_DIV)
| (att_en << ProtoRev9.CFG_ATT_EN)
)
@kernel
def cfg_write(self, cpld, cfg: TInt64):
"""Write to the configuration register.
See :func:`urukul_cfg` for possible flags.
:param cfg: 52-bit data to be written. Will be stored at
:attr:`cfg_reg`.
"""
cpld.bus.set_config_mu(SPI_CONFIG, 24, SPIT_CFG_WR, CS_CFG)
cpld.bus.write(((cfg >> 28) & 0xFFFFFF) << 8)
cpld.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 28, SPIT_CFG_WR, CS_CFG)
cpld.bus.write((cfg & 0xFFFFFFF) << 4)
cpld.cfg_reg = cfg
@kernel
def sta_read(self, cpld) -> TInt64:
"""Read the status register.
Use any of the following functions to extract values:
* :func:`urukul_sta_rf_sw`
* :func:`urukul_sta_smp_err`
* :func:`urukul_sta_pll_lock`
* :func:`urukul_sta_ifc_mode`
* :func:`urukul_sta_proto_rev`
* :func:`urukul_sta_drover`
:return: The status register value.
"""
cpld.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 24, SPIT_CFG_RD, CS_CFG)
cpld.bus.write(((cpld.cfg_reg >> 24) & 0xFFFFFF) << 8)
cpld.bus.set_config_mu(
SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 28, SPIT_CFG_RD, CS_CFG
)
cpld.bus.write((cpld.cfg_reg & 0xFFFFFFF) << 4)
hi = cpld.bus.read()
lo = cpld.bus.read()
return (int64(hi) << 28) | lo
@kernel
def init(self, cpld):
"""Initialize and detect Urukul.
Resets the DDS I/O interface and verifies correct CPLD gateware
version.
Does not pulse the DDS ``MASTER_RESET`` as that confuses the AD9910.
"""
cfg = cpld.cfg_reg
# Don't pulse MASTER_RESET (m-labs/artiq#940)
cpld.cfg_reg = cfg | (0 << ProtoRev9.CFG_RST) | (1 << ProtoRev9.CFG_IO_RST)
delay(100 * us) # reset, slack
cpld.cfg_write(cfg)
if cpld.sync_div:
at_mu(now_mu() & ~0xF) # align to RTIO/2
cpld.set_sync_div(cpld.sync_div) # 125 MHz/2 = 1 GHz/16
delay(1 * ms) # DDS wake up
@kernel
def io_rst(self, cpld):
"""Pulse IO_RST"""
cpld.cfg_write(cpld.cfg_reg | (1 << ProtoRev9.CFG_IO_RST))
cpld.cfg_write(cpld.cfg_reg & ~(1 << ProtoRev9.CFG_IO_RST))
@kernel
def _configure_bit(self, cpld, bit_offset: TInt32, channel: TInt32, on: TBool):
"""Configure a single bit in the configuration register.
:param bit_offset: Base bit offset for the configuration type
:param channel: Channel index (0-3)
:param on: Switch value
"""
c = cpld.cfg_reg
if on:
c |= int64(1) << (bit_offset + channel)
else:
c &= ~(int64(1) << (bit_offset + channel))
cpld.cfg_write(c)
@kernel
def _configure_all_bits(self, cpld, bit_offset: TInt32, state: TInt32):
"""Configure all four bits of a specific type in the configuration register.
:param bit_offset: Base bit offset for the configuration type
:param state: State as a 4-bit integer
"""
cpld.cfg_write(
(cpld.cfg_reg & ~(int64(0xF) << bit_offset)) | (int64(state) << bit_offset)
)
@kernel
def cfg_att_en(self, cpld, channel: TInt32, on: TBool):
"""Configure the ATT_EN bit through the configuration register.
:param channel: Channel index (0-3)
:param on: Switch value
"""
cpld._configure_bit(ProtoRev9.CFG_ATT_EN, channel, on)
@kernel
def cfg_att_en_all(self, cpld, state: TInt32):
"""Configure all four ATT_EN bits through the configuration register.
:param state: OSK state as a 4-bit integer.
"""
cpld._configure_all_bits(ProtoRev9.CFG_ATT_EN, state)
@kernel
def cfg_osk(self, cpld, channel: TInt32, on: TBool):
"""Configure the OSK bit through the configuration register.
:param channel: Channel index (0-3)
:param on: Switch value
"""
cpld._configure_bit(ProtoRev9.CFG_OSK, channel, on)
@kernel
def cfg_osk_all(self, cpld, state: TInt32):
"""Configure all four OSK bits through the configuration register.
:param state: OSK state as a 4-bit integer.
"""
cpld._configure_all_bits(ProtoRev9.CFG_OSK, state)
@kernel
def cfg_drctl(self, cpld, channel: TInt32, on: TBool):
"""Configure the DRCTL bit through the configuration register.
:param channel: Channel index (0-3)
:param on: Switch value
"""
cpld._configure_bit(ProtoRev9.CFG_DRCTL, channel, on)
@kernel
def cfg_drctl_all(self, cpld, state: TInt32):
"""Configure all four DRCTL bits through the configuration register.
:param state: DRCTL state as a 4-bit integer.
"""
cpld._configure_all_bits(ProtoRev9.CFG_DRCTL, state)
@kernel
def cfg_drhold(self, cpld, channel: TInt32, on: TBool):
"""Configure the DRHOLD bit through the configuration register.
:param channel: Channel index (0-3)
:param on: Switch value
"""
cpld._configure_bit(ProtoRev9.CFG_DRHOLD, channel, on)
@kernel
def cfg_drhold_all(self, cpld, state: TInt32):
"""Configure all four DRHOLD bits through the configuration register.
:param state: DRHOLD state as a 4-bit integer.
"""
cpld._configure_all_bits(ProtoRev9.CFG_DRHOLD, state)
@kernel
def cfg_mask_nu(self, cpld, channel: TInt32, on: TBool):
"""Configure the MASK_NU bits through the configuration register.
:param channel: Channel index (0-3)
:param on: Switch value
"""
cpld._configure_bit(ProtoRev9.CFG_MASK_NU, channel, on)
@kernel
def cfg_mask_nu_all(self, cpld, state: TInt32):
"""Configure all four MASK_NU bits through the configuration register.
:param state: MASK_NU state as a 4-bit integer.
"""
cpld._configure_all_bits(ProtoRev9.CFG_MASK_NU, state)
class CPLDVersionManagerFactory:
@staticmethod
def get_version(proto_rev: int) -> CPLDVersionManager:
if proto_rev == STA_PROTO_REV_8:
return ProtoRev8()
elif proto_rev == STA_PROTO_REV_9:
return ProtoRev9()
else:
raise ValueError(f"Urukul unsupported proto_rev: {proto_rev}")
class CPLD:
"""Urukul CPLD SPI router and configuration interface.
@ -162,13 +593,26 @@ class CPLD:
front panel SMA with no clock connected), then the ``init()`` method of
the DDS channels can fail with the error message ``PLL lock timeout``.
"""
kernel_invariants = {"refclk", "bus", "core", "io_update", "clk_div"}
def __init__(self, dmgr, spi_device, io_update_device=None,
dds_reset_device=None, sync_device=None,
sync_sel=0, clk_sel=0, clk_div=0, rf_sw=0,
refclk=125e6, att=0x00000000, sync_div=None,
core_device="core"):
def __init__(
self,
dmgr,
spi_device,
io_update_device=None,
dds_reset_device=None,
sync_device=None,
sync_sel=0,
clk_sel=0,
clk_div=0,
rf_sw=0,
refclk=125e6,
att=0x00000000,
sync_div=None,
proto_rev=0x08,
core_device="core",
):
self.core = dmgr.get(core_device)
self.refclk = refclk
@ -179,7 +623,7 @@ class CPLD:
if io_update_device is not None:
self.io_update = dmgr.get(io_update_device)
else:
self.io_update = _RegIOUpdate(self)
self.io_update = io_update_device
if dds_reset_device is not None:
self.dds_reset = dmgr.get(dds_reset_device)
if sync_device is not None:
@ -191,77 +635,109 @@ class CPLD:
assert sync_div is None
sync_div = 0
self.cfg_reg = urukul_cfg(rf_sw=rf_sw, led=0, profile=DEFAULT_PROFILE,
io_update=0, mask_nu=0, clk_sel=clk_sel,
sync_sel=sync_sel,
rst=0, io_rst=0, clk_div=clk_div)
# proto_rev = 0x09 # urukul_sta_proto_rev(self.sta_read())
self.proto_rev = proto_rev
self.version_manager = CPLDVersionManagerFactory.get_version(proto_rev)
if self.proto_rev == STA_PROTO_REV_8:
self.cfg_reg = ProtoRev8.urukul_cfg(
rf_sw=rf_sw,
led=0,
profile=DEFAULT_PROFILE,
io_update=0,
mask_nu=0,
clk_sel=clk_sel,
sync_sel=sync_sel,
rst=0,
io_rst=0,
clk_div=clk_div,
)
else:
self.cfg_reg = ProtoRev9.urukul_cfg(
rf_sw=rf_sw,
led=0,
profile=(DEFAULT_PROFILE << 9)
| (DEFAULT_PROFILE << 6)
| (DEFAULT_PROFILE << 3)
| DEFAULT_PROFILE,
osk=0,
drctl=0,
drhold=0,
io_update=0,
mask_nu=0,
clk_sel=clk_sel,
sync_sel=sync_sel,
rst=0,
io_rst=0,
clk_div=clk_div,
att_en=0,
)
self.att_reg = int32(int64(att))
self.sync_div = sync_div
@kernel
def cfg_write(self, cfg: TInt32):
"""Write to the configuration register.
See :func:`urukul_cfg` for possible flags.
:param cfg: 24-bit data to be written. Will be stored at
:attr:`cfg_reg`.
"""
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24,
SPIT_CFG_WR, CS_CFG)
self.bus.write(cfg << 8)
self.cfg_reg = cfg
def cfg_write(self, cfg):
self.version_manager.cfg_write(self, cfg)
@kernel
def sta_read(self) -> TInt32:
"""Read the status register.
Use any of the following functions to extract values:
* :func:`urukul_sta_rf_sw`
* :func:`urukul_sta_smp_err`
* :func:`urukul_sta_pll_lock`
* :func:`urukul_sta_ifc_mode`
* :func:`urukul_sta_proto_rev`
:return: The status register value.
"""
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 24,
SPIT_CFG_RD, CS_CFG)
self.bus.write(self.cfg_reg << 8)
return self.bus.read()
def sta_read(self):
return self.version_manager.sta_read(self)
@kernel
def init(self, blind: TBool = False):
"""Initialize and detect Urukul.
Resets the DDS I/O interface and verifies correct CPLD gateware
version.
Does not pulse the DDS ``MASTER_RESET`` as that confuses the AD9910.
:param blind: Do not attempt to verify presence and compatibility.
"""
cfg = self.cfg_reg
# Don't pulse MASTER_RESET (m-labs/artiq#940)
self.cfg_reg = cfg | (0 << CFG_RST) | (1 << CFG_IO_RST)
if blind:
self.cfg_write(self.cfg_reg)
else:
proto_rev = urukul_sta_proto_rev(self.sta_read())
if proto_rev != STA_PROTO_REV_MATCH:
raise ValueError("Urukul proto_rev mismatch")
delay(100 * us) # reset, slack
self.cfg_write(cfg)
if self.sync_div:
at_mu(now_mu() & ~0xf) # align to RTIO/2
self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16
delay(1 * ms) # DDS wake up
def init(self):
self.version_manager.init(self)
@kernel
def io_rst(self):
"""Pulse IO_RST"""
self.cfg_write(self.cfg_reg | (1 << CFG_IO_RST))
self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST))
self.version_manager.io_rst(self)
@kernel
def configure_bit(self, bit_offset: TInt32, channel: TInt32, on: TBool):
self.version_manager.configure_bit(self, bit_offset, channel, on)
@kernel
def configure_all_bits(self, bit_offset: TInt32, state: TInt32):
self.version_manager.configure_all_bits(self, bit_offset, state)
@kernel
def cfg_att_en(self, channel: TInt32, on: TBool):
self.version_manager.cfg_att_en(self, channel, on)
@kernel
def cfg_att_en_all(self, state: TInt32):
self.version_manager.cfg_att_en_all(self, state)
@kernel
def cfg_osk(self, channel: TInt32, on: TBool):
self.version_manager.cfg_osk(self, channel, on)
@kernel
def cfg_osk_all(self, state: TInt32):
self.version_manager.cfg_osk_all(self, state)
@kernel
def cfg_drctl(self, channel: TInt32, on: TBool):
self.version_manager.cfg_drctl(self, channel, on)
@kernel
def cfg_drctl_all(self, state: TInt32):
self.version_manager.cfg_drctl_all(self, state)
@kernel
def cfg_drhold(self, channel: TInt32, on: TBool):
self.version_manager.cfg_drhold(self, channel, on)
@kernel
def cfg_drhold_all(self, state: TInt32):
self.version_manager.cfg_drhold_all(self, state)
@kernel
def cfg_mask_nu(self, channel: TInt32, on: TBool):
self.version_manager.cfg_mask_nu(self, channel, on)
@kernel
def cfg_mask_nu_all(self, state: TInt32):
self.version_manager.cfg_mask_nu_all(self, state)
@kernel
def cfg_sw(self, channel: TInt32, on: TBool):
@ -285,7 +761,7 @@ class CPLD:
:param state: RF switch state as a 4-bit integer.
"""
self.cfg_write((self.cfg_reg & ~0xf) | state)
self.cfg_write((self.cfg_reg & ~0xF) | state)
@portable(flags={"fast-math"})
def mu_to_att(self, att_mu: TInt32) -> TFloat:
@ -294,7 +770,7 @@ class CPLD:
:param att_mu: Digital attenuation setting.
:return: Attenuation setting in dB.
"""
return (255 - (att_mu & 0xff)) / 8
return (255 - (att_mu & 0xFF)) / 8
@portable(flags={"fast-math"})
def att_to_mu(self, att: TFloat) -> TInt32:
@ -320,19 +796,18 @@ class CPLD:
:param att: 8-bit digital attenuation setting:
255 minimum attenuation, 0 maximum attenuation (31.5 dB)
"""
a = self.att_reg & ~(0xff << (channel * 8))
a = self.att_reg & ~(0xFF << (channel * 8))
a |= att << (channel * 8)
self.set_all_att_mu(a)
@kernel
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).
See also :meth:`set_att_mu`.
:param att_reg: Attenuator setting string (32-bit)
"""
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32,
SPIT_ATT_WR, CS_ATT)
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, SPIT_ATT_WR, CS_ATT)
self.bus.write(att_reg)
self.att_reg = att_reg
@ -340,7 +815,6 @@ class CPLD:
def set_att(self, channel: TInt32, att: TFloat):
"""Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels.
See also :meth:`set_att_mu`.
:param channel: Attenuator channel (0-3).
@ -361,11 +835,9 @@ class CPLD:
:return: 32-bit attenuator settings
"""
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32,
SPIT_ATT_RD, CS_ATT)
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32, SPIT_ATT_RD, CS_ATT)
self.bus.write(0) # shift in zeros, shift out current value
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32,
SPIT_ATT_WR, CS_ATT)
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, SPIT_ATT_WR, CS_ATT)
delay(10 * us)
self.att_reg = self.bus.read()
self.bus.write(self.att_reg) # shift in current value again and latch
@ -384,7 +856,7 @@ class CPLD:
:return: 8-bit digital attenuation setting:
255 minimum attenuation, 0 maximum attenuation (31.5 dB)
"""
return int32((self.get_att_mu() >> (channel * 8)) & 0xff)
return int32((self.get_att_mu() >> (channel * 8)) & 0xFF)
@kernel
def get_channel_att(self, channel: TInt32) -> TFloat:
@ -417,13 +889,12 @@ class CPLD:
self.sync.set_mu(ftw)
@kernel
def set_profile(self, profile: TInt32):
"""Set the PROFILE pins.
The PROFILE pins are common to all four DDS channels.
def set_profile(self, channel: TInt32, profile: TInt32):
"""Set the CFG.PROFILE[0:2] pins for a channel.
:param channel: Channel (0-3).
:param profile: PROFILE pins in numeric representation (0-7).
"""
cfg = self.cfg_reg & ~(7 << CFG_PROFILE)
cfg |= (profile & 7) << CFG_PROFILE
cfg = self.cfg_reg & ~(7 << (CFG_PROFILE + channel * 3))
cfg |= (profile & 7) << (CFG_PROFILE + channel * 3)
self.cfg_write(cfg)