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 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 spi2 as spi
from artiq.coredevice import urukul 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 # Work around ARTIQ-Python import machinery
urukul_sta_pll_lock = urukul.urukul_sta_pll_lock urukul_sta_pll_lock = urukul.urukul_sta_pll_lock
@ -15,10 +13,18 @@ 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",
"RAM_DEST_FTW", "RAM_DEST_POW", "RAM_DEST_ASF", "RAM_DEST_POWASF", "PHASE_MODE_ABSOLUTE",
"RAM_MODE_DIRECTSWITCH", "RAM_MODE_RAMPUP", "RAM_MODE_BIDIR_RAMP", "PHASE_MODE_TRACKING",
"RAM_MODE_CONT_BIDIR_RAMP", "RAM_MODE_CONT_RAMPUP", "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 _PHASE_MODE_DEFAULT = -1
@ -34,12 +40,12 @@ _AD9910_REG_IO_UPDATE = 0x04
_AD9910_REG_FTW = 0x07 _AD9910_REG_FTW = 0x07
_AD9910_REG_POW = 0x08 _AD9910_REG_POW = 0x08
_AD9910_REG_ASF = 0x09 _AD9910_REG_ASF = 0x09
_AD9910_REG_SYNC = 0x0a _AD9910_REG_SYNC = 0x0A
_AD9910_REG_RAMP_LIMIT = 0x0b _AD9910_REG_RAMP_LIMIT = 0x0B
_AD9910_REG_RAMP_STEP = 0x0c _AD9910_REG_RAMP_STEP = 0x0C
_AD9910_REG_RAMP_RATE = 0x0d _AD9910_REG_RAMP_RATE = 0x0D
_AD9910_REG_PROFILE0 = 0x0e _AD9910_REG_PROFILE0 = 0x0E
_AD9910_REG_PROFILE1 = 0x0f _AD9910_REG_PROFILE1 = 0x0F
_AD9910_REG_PROFILE2 = 0x10 _AD9910_REG_PROFILE2 = 0x10
_AD9910_REG_PROFILE3 = 0x11 _AD9910_REG_PROFILE3 = 0x11
_AD9910_REG_PROFILE4 = 0x12 _AD9910_REG_PROFILE4 = 0x12
@ -92,10 +98,10 @@ class SyncDataEeprom:
word = self.eeprom_device.read_i32(self.eeprom_offset) >> 16 word = self.eeprom_device.read_i32(self.eeprom_offset) >> 16
sync_delay_seed = word >> 8 sync_delay_seed = word >> 8
if sync_delay_seed >= 0: if sync_delay_seed >= 0:
io_update_delay = word & 0xff io_update_delay = word & 0xFF
else: else:
io_update_delay = 0 io_update_delay = 0
if io_update_delay == 0xff: # unprogrammed EEPROM if io_update_delay == 0xFF: # unprogrammed EEPROM
io_update_delay = 0 io_update_delay = 0
# With Numpy, type(int32(-1) >> 1) == int64 # With Numpy, type(int32(-1) >> 1) == int64
self.sync_delay_seed = int32(sync_delay_seed) self.sync_delay_seed = int32(sync_delay_seed)
@ -138,13 +144,33 @@ class AD9910:
to the same string value. to the same string value.
""" """
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, def __init__(
pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1, self,
io_update_delay=0, pll_en=1): dmgr,
self.kernel_invariants = {"cpld", "core", "bus", "chip_select", chip_select,
"pll_en", "pll_n", "pll_vco", "pll_cp", cpld_device,
"ftw_per_hz", "sysclk_per_mu", "sysclk", sw_device=None,
"sync_data"} 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.cpld = dmgr.get(cpld_device)
self.core = self.cpld.core self.core = self.cpld.core
self.bus = self.cpld.bus self.bus = self.cpld.bus
@ -163,8 +189,14 @@ class AD9910:
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 = [
(600, 880), (700, 950), (820, 1150)][pll_vco] (370, 510),
(420, 590),
(500, 700),
(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:
@ -174,15 +206,18 @@ class AD9910:
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, if not self.cpld.io_update:
str): 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: if sync_delay_seed != io_update_delay:
raise ValueError("When using EEPROM, sync_delay_seed must be " raise ValueError(
"equal to io_update_delay") "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, self.sync_data = SyncDataUser(self.core, sync_delay_seed, io_update_delay)
io_update_delay)
self.phase_mode = PHASE_MODE_CONTINUOUS self.phase_mode = PHASE_MODE_CONTINUOUS
@ -236,9 +271,10 @@ class AD9910:
:param addr: Register address :param addr: Register address
:param data: Data to be written :param data: Data to be written
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 24, self.bus.set_config_mu(
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPI_CONFIG | spi.SPI_END, 24, urukul.SPIT_DDS_WR, self.chip_select
self.bus.write((addr << 24) | ((data & 0xffff) << 8)) )
self.bus.write((addr << 24) | ((data & 0xFFFF) << 8))
@kernel @kernel
def write32(self, addr: TInt32, data: TInt32): def write32(self, addr: TInt32, data: TInt32):
@ -247,11 +283,13 @@ class AD9910:
:param addr: Register address :param addr: Register address
:param data: Data to be written :param data: Data to be written
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, self.bus.set_config_mu(
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(addr << 24) self.bus.write(addr << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPI_CONFIG | spi.SPI_END, 32, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(data) self.bus.write(data)
@kernel @kernel
@ -260,12 +298,16 @@ class AD9910:
:param addr: Register address :param addr: Register address
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, self.bus.set_config_mu(
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.write((addr | 0x80) << 24)
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 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) self.bus.write(0)
return self.bus.read() return self.bus.read()
@ -275,12 +317,16 @@ class AD9910:
:param addr: Register address :param addr: Register address
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, self.bus.set_config_mu(
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.write((addr | 0x80) << 24)
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 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) self.bus.write(0)
return self.bus.read() return self.bus.read()
@ -292,16 +338,19 @@ class AD9910:
:return: 64-bit integer register value :return: 64-bit integer register value
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG, 8, urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
urukul.SPIT_DDS_WR, self.chip_select) )
self.bus.write((addr | 0x80) << 24) self.bus.write((addr | 0x80) << 24)
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_INPUT, 32, urukul.SPI_CONFIG | spi.SPI_INPUT, 32, urukul.SPIT_DDS_RD, self.chip_select
urukul.SPIT_DDS_RD, self.chip_select) )
self.bus.write(0) self.bus.write(0)
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 32, urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
urukul.SPIT_DDS_RD, self.chip_select) 32,
urukul.SPIT_DDS_RD,
self.chip_select,
)
self.bus.write(0) self.bus.write(0)
hi = self.bus.read() hi = self.bus.read()
lo = self.bus.read() lo = self.bus.read()
@ -315,14 +364,17 @@ class AD9910:
:param data_high: High (MSB) 32 data bits :param data_high: High (MSB) 32 data bits
:param data_low: Low (LSB) 32 data bits :param data_low: Low (LSB) 32 data bits
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, self.bus.set_config_mu(
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(addr << 24) self.bus.write(addr << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG, 32, self.bus.set_config_mu(
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPI_CONFIG, 32, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(data_high) self.bus.write(data_high)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPI_CONFIG | spi.SPI_END, 32, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(data_low) self.bus.write(data_low)
@kernel @kernel
@ -336,15 +388,18 @@ class AD9910:
:param data: 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(
self.chip_select) urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(_AD9910_REG_RAM << 24) self.bus.write(_AD9910_REG_RAM << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG, 32, self.bus.set_config_mu(
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPI_CONFIG, 32, urukul.SPIT_DDS_WR, self.chip_select
)
for i in range(len(data) - 1): for i in range(len(data) - 1):
self.bus.write(data[i]) self.bus.write(data[i])
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(
urukul.SPIT_DDS_WR, self.chip_select) urukul.SPI_CONFIG | spi.SPI_END, 32, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write(data[len(data) - 1]) self.bus.write(data[len(data) - 1])
@kernel @kernel
@ -358,27 +413,36 @@ class AD9910:
:param data: 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(
self.chip_select) urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.chip_select
)
self.bus.write((_AD9910_REG_RAM | 0x80) << 24) self.bus.write((_AD9910_REG_RAM | 0x80) << 24)
n = len(data) - 1 n = len(data) - 1
if n > 0: if n > 0:
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_INPUT, 32, self.bus.set_config_mu(
urukul.SPIT_DDS_RD, self.chip_select) urukul.SPI_CONFIG | spi.SPI_INPUT,
32,
urukul.SPIT_DDS_RD,
self.chip_select,
)
preload = min(n, 8) preload = min(n, 8)
for i in range(n): for i in range(n):
self.bus.write(0) self.bus.write(0)
if i >= preload: if i >= preload:
data[i - preload] = self.bus.read() data[i - preload] = self.bus.read()
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 32, urukul.SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END,
urukul.SPIT_DDS_RD, self.chip_select) 32,
urukul.SPIT_DDS_RD,
self.chip_select,
)
self.bus.write(0) self.bus.write(0)
for i in range(preload + 1): for i in range(preload + 1):
data[(n - preload) + i] = self.bus.read() data[(n - preload) + i] = self.bus.read()
@kernel @kernel
def set_cfr1(self, def set_cfr1(
self,
power_down: TInt32 = 0b0000, power_down: TInt32 = 0b0000,
phase_autoclear: TInt32 = 0, phase_autoclear: TInt32 = 0,
drg_load_lrr: TInt32 = 0, drg_load_lrr: TInt32 = 0,
@ -389,7 +453,8 @@ class AD9910:
ram_enable: TInt32 = 0, ram_enable: TInt32 = 0,
manual_osk_external: TInt32 = 0, manual_osk_external: TInt32 = 0,
osk_enable: TInt32 = 0, osk_enable: TInt32 = 0,
select_auto_osk: TInt32 = 0): select_auto_osk: TInt32 = 0,
):
"""Set CFR1. See the AD9910 datasheet for parameter meanings and sizes. """Set CFR1. See the AD9910 datasheet for parameter meanings and sizes.
This method does not pulse ``IO_UPDATE.`` This method does not pulse ``IO_UPDATE.``
@ -408,33 +473,43 @@ class AD9910:
:param osk_enable: Enable OSK mode. :param osk_enable: Enable OSK mode.
:param select_auto_osk: Select manual or automatic OSK mode. :param select_auto_osk: Select manual or automatic OSK mode.
""" """
self.write32(_AD9910_REG_CFR1, self.write32(
(ram_enable << 31) | _AD9910_REG_CFR1,
(ram_destination << 29) | (ram_enable << 31)
(manual_osk_external << 23) | | (ram_destination << 29)
(internal_profile << 17) | | (manual_osk_external << 23)
(drg_load_lrr << 15) | | (internal_profile << 17)
(drg_autoclear << 14) | | (drg_load_lrr << 15)
(phase_autoclear << 13) | | (drg_autoclear << 14)
(phase_clear << 11) | | (phase_autoclear << 13)
(osk_enable << 9) | | (phase_clear << 11)
(select_auto_osk << 8) | | (osk_enable << 9)
(power_down << 4) | | (select_auto_osk << 8)
2) # SDIO input only, MSB first | (power_down << 4)
| 2,
) # SDIO input only, MSB first
@kernel @kernel
def set_cfr2(self, def set_cfr2(
self,
asf_profile_enable: TInt32 = 1, asf_profile_enable: TInt32 = 1,
drg_destination: TInt32 = 0,
drg_enable: TInt32 = 0, drg_enable: TInt32 = 0,
drg_nodwell_high: TInt32 = 0,
drg_nodwell_low: TInt32 = 0,
effective_ftw: TInt32 = 1, effective_ftw: TInt32 = 1,
sync_validation_disable: TInt32 = 0, sync_validation_disable: TInt32 = 0,
matched_latency_enable: TInt32 = 0): matched_latency_enable: TInt32 = 0,
):
"""Set CFR2. See the AD9910 datasheet for parameter meanings and sizes. """Set CFR2. See the AD9910 datasheet for parameter meanings and sizes.
This method does not pulse ``IO_UPDATE``. This method does not pulse ``IO_UPDATE``.
:param asf_profile_enable: Enable amplitude scale from single tone profiles. :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_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 effective_ftw: Read effective FTW.
:param sync_validation_disable: Disable the SYNC_SMP_ERR pin indicating :param sync_validation_disable: Disable the SYNC_SMP_ERR pin indicating
(active high) detection of a synchronization pulse sampling error. (active high) detection of a synchronization pulse sampling error.
@ -444,12 +519,17 @@ class AD9910:
* matched_latency_enable = 0: in the order listed * matched_latency_enable = 0: in the order listed
* matched_latency_enable = 1: simultaneously. * matched_latency_enable = 1: simultaneously.
""" """
self.write32(_AD9910_REG_CFR2, self.write32(
(asf_profile_enable << 24) | _AD9910_REG_CFR2,
(drg_enable << 19) | (asf_profile_enable << 24)
(effective_ftw << 16) | | (drg_destination << 20)
(matched_latency_enable << 7) | | (drg_enable << 19)
(sync_validation_disable << 5)) | (drg_nodwell_high << 18)
| (drg_nodwell_low << 17)
| (effective_ftw << 16)
| (matched_latency_enable << 7)
| (sync_validation_disable << 5),
)
@kernel @kernel
def init(self, blind: TBool = False): def init(self, blind: TBool = False):
@ -476,7 +556,7 @@ class AD9910:
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
@ -485,9 +565,13 @@ class AD9910:
# sync timing validation disable (enabled later) # sync timing validation disable (enabled later)
self.set_cfr2(sync_validation_disable=1) self.set_cfr2(sync_validation_disable=1)
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1 * us)
cfr3 = (0x0807c000 | (self.pll_vco << 24) | cfr3 = (
(self.pll_cp << 19) | (self.pll_en << 8) | 0x0807C000
(self.pll_n << 1)) | (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.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:
@ -520,11 +604,16 @@ class AD9910:
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1 * us)
@kernel @kernel
def set_mu(self, ftw: TInt32 = 0, pow_: TInt32 = 0, asf: TInt32 = 0x3fff, def set_mu(
self,
ftw: TInt32 = 0,
pow_: TInt32 = 0,
asf: TInt32 = 0x3FFF,
phase_mode: TInt32 = _PHASE_MODE_DEFAULT, phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1), ref_time_mu: TInt64 = int64(-1),
profile: TInt32 = DEFAULT_PROFILE, profile: TInt32 = DEFAULT_PROFILE,
ram_destination: TInt32 = -1) -> TInt32: ram_destination: TInt32 = -1,
) -> TInt32:
"""Set DDS data in machine units. """Set DDS 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
@ -574,8 +663,9 @@ class AD9910:
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
if ram_destination == -1: if ram_destination == -1:
self.write64(_AD9910_REG_PROFILE0 + profile, self.write64(
(asf << 16) | (pow_ & 0xffff), ftw) _AD9910_REG_PROFILE0 + profile, (asf << 16) | (pow_ & 0xFFFF), ftw
)
else: else:
if not ram_destination == RAM_DEST_FTW: if not ram_destination == RAM_DEST_FTW:
self.set_ftw(ftw) self.set_ftw(ftw)
@ -593,7 +683,8 @@ class AD9910:
return pow_ return pow_
@kernel @kernel
def get_mu(self, profile: TInt32 = DEFAULT_PROFILE def get_mu(
self, profile: TInt32 = DEFAULT_PROFILE
) -> TTuple([TInt32, TInt32, TInt32]): ) -> TTuple([TInt32, TInt32, TInt32]):
"""Get the frequency tuning word, phase offset word, """Get the frequency tuning word, phase offset word,
and amplitude scale factor. and amplitude scale factor.
@ -608,15 +699,21 @@ class AD9910:
data = int64(self.read64(_AD9910_REG_PROFILE0 + profile)) data = int64(self.read64(_AD9910_REG_PROFILE0 + profile))
# Extract and return fields # Extract and return fields
ftw = int32(data) ftw = int32(data)
pow_ = int32((data >> 32) & 0xffff) pow_ = int32((data >> 32) & 0xFFFF)
asf = int32((data >> 48) & 0x3fff) asf = int32((data >> 48) & 0x3FFF)
return ftw, pow_, asf return ftw, pow_, asf
@kernel @kernel
def set_profile_ram(self, start: TInt32, end: TInt32, step: TInt32 = 1, def set_profile_ram(
self,
start: TInt32,
end: TInt32,
step: TInt32 = 1,
profile: TInt32 = _DEFAULT_PROFILE_RAM, profile: TInt32 = _DEFAULT_PROFILE_RAM,
nodwell_high: TInt32 = 0, zero_crossing: TInt32 = 0, nodwell_high: TInt32 = 0,
mode: TInt32 = 1): zero_crossing: TInt32 = 0,
mode: TInt32 = 1,
):
"""Set the RAM profile settings. See also AD9910 datasheet. """Set the RAM profile settings. See also AD9910 datasheet.
:param start: Profile start address in RAM (10-bit). :param start: Profile start address in RAM (10-bit).
@ -635,8 +732,13 @@ class AD9910:
:const:`RAM_MODE_RAMPUP`) :const:`RAM_MODE_RAMPUP`)
""" """
hi = (step << 8) | (end >> 2) hi = (step << 8) | (end >> 2)
lo = ((end << 30) | (start << 14) | (nodwell_high << 5) | lo = (
(zero_crossing << 3) | mode) (end << 30)
| (start << 14)
| (nodwell_high << 5)
| (zero_crossing << 3)
| mode
)
self.write64(_AD9910_REG_PROFILE0 + profile, hi, lo) self.write64(_AD9910_REG_PROFILE0 + profile, hi, lo)
@kernel @kernel
@ -711,7 +813,7 @@ class AD9910:
def turns_to_pow(self, turns: TFloat) -> 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_: TInt32) -> TFloat: def pow_to_turns(self, pow_: TInt32) -> TFloat:
@ -723,8 +825,8 @@ class AD9910:
def amplitude_to_asf(self, amplitude: TFloat) -> 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))
if code < 0 or code > 0x3fff: if code < 0 or code > 0x3FFF:
raise ValueError("Invalid AD9910 fractional amplitude!") raise ValueError("Invalid AD9910 fractional amplitude!")
return code return code
@ -732,7 +834,7 @@ class AD9910:
def asf_to_amplitude(self, asf: TInt32) -> TFloat: 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: TList(TFloat), ram: TList(TInt32)): 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 ram[i] = self.amplitude_to_asf(amplitude[i]) << 18
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def turns_amplitude_to_ram(self, turns: TList(TFloat), def turns_amplitude_to_ram(
amplitude: TList(TFloat), ram: TList(TInt32)): 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`.
@ -786,8 +889,9 @@ class AD9910:
Suitable for :meth:`write_ram`. Suitable for :meth:`write_ram`.
""" """
for i in range(len(ram)): for i in range(len(ram)):
ram[i] = ((self.turns_to_pow(turns[i]) << 16) | ram[i] = (self.turns_to_pow(turns[i]) << 16) | self.amplitude_to_asf(
self.amplitude_to_asf(amplitude[i]) << 2) amplitude[i]
) << 2
@kernel @kernel
def set_frequency(self, frequency: TFloat): def set_frequency(self, frequency: TFloat):
@ -844,10 +948,16 @@ class AD9910:
return self.pow_to_turns(self.get_pow()) return self.pow_to_turns(self.get_pow())
@kernel @kernel
def set(self, frequency: TFloat = 0.0, phase: TFloat = 0.0, def set(
amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT, self,
ref_time_mu: TInt64 = int64(-1), profile: TInt32 = DEFAULT_PROFILE, frequency: TFloat = 0.0,
ram_destination: TInt32 = -1) -> TFloat: 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. """Set DDS data in SI units.
See also :meth:`AD9910.set_mu`. See also :meth:`AD9910.set_mu`.
@ -861,13 +971,21 @@ class AD9910:
:param ram_destination: RAM destination. :param ram_destination: RAM destination.
:return: Resulting phase offset in turns :return: Resulting phase offset in turns
""" """
return self.pow_to_turns(self.set_mu( return self.pow_to_turns(
self.frequency_to_ftw(frequency), self.turns_to_pow(phase), self.set_mu(
self.amplitude_to_asf(amplitude), phase_mode, ref_time_mu, self.frequency_to_ftw(frequency),
profile, ram_destination)) self.turns_to_pow(phase),
self.amplitude_to_asf(amplitude),
phase_mode,
ref_time_mu,
profile,
ram_destination,
)
)
@kernel @kernel
def get(self, profile: TInt32 = DEFAULT_PROFILE def get(
self, profile: TInt32 = DEFAULT_PROFILE
) -> TTuple([TFloat, TFloat, TFloat]): ) -> TTuple([TFloat, TFloat, TFloat]):
"""Get the frequency, phase, and amplitude. """Get the frequency, phase, and amplitude.
@ -880,14 +998,19 @@ class AD9910:
# Get values # Get values
ftw, pow_, asf = self.get_mu(profile) ftw, pow_, asf = self.get_mu(profile)
# Convert and return # Convert and return
return (self.ftw_to_frequency(ftw), self.pow_to_turns(pow_), return (
self.asf_to_amplitude(asf)) self.ftw_to_frequency(ftw),
self.pow_to_turns(pow_),
self.asf_to_amplitude(asf),
)
@kernel @kernel
def set_att_mu(self, att: TInt32): 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. 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>`. :meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.set_att_mu>`.
:param att: Attenuation setting, 8-bit digital. :param att: Attenuation setting, 8-bit digital.
@ -898,7 +1021,9 @@ class AD9910:
def set_att(self, att: TFloat): 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. 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>`. :meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.set_att>`.
:param att: Attenuation in dB. :param att: Attenuation in dB.
@ -934,10 +1059,52 @@ 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, def cfg_osk(self, state: TBool):
in_delay: TInt32, """Set CPLD CFG OSK state. The OSK bit is controlled by the
window: TInt32, logical or of the CPLD configuration shift register OSK bit.
en_sync_gen: TInt32 = 0):
: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 """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
@ -950,14 +1117,16 @@ class AD9910:
(``SYNC_OUT``, cf. ``sync_sel == 1``). Should be left off for the normal (``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. use case, where the ``SYNC`` clock is supplied by the core device.
""" """
self.write32(_AD9910_REG_SYNC, self.write32(
(window << 28) | # SYNC S/H validation delay _AD9910_REG_SYNC,
(1 << 27) | # SYNC receiver enable (window << 28) # SYNC S/H validation delay
(en_sync_gen << 26) | # SYNC generator enable | (1 << 27) # SYNC receiver enable
(0 << 25) | # SYNC generator SYS rising edge | (en_sync_gen << 26) # SYNC generator enable
(0 << 18) | # SYNC preset | (0 << 25) # SYNC generator SYS rising edge
(0 << 11) | # SYNC output delay | (0 << 18) # SYNC preset
(in_delay << 3)) # SYNC receiver delay | (0 << 11) # SYNC output delay
| (in_delay << 3),
) # SYNC receiver delay
@kernel @kernel
def clear_smp_err(self): def clear_smp_err(self):
@ -976,8 +1145,7 @@ class AD9910:
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1 * us)
@kernel @kernel
def tune_sync_delay(self, def tune_sync_delay(self, search_seed: TInt32 = 15) -> TTuple([TInt32, TInt32]):
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
@ -1033,8 +1201,9 @@ class AD9910:
raise ValueError("no valid window/delay") raise ValueError("no valid window/delay")
@kernel @kernel
def measure_io_update_alignment(self, delay_start: TInt64, def measure_io_update_alignment(
delay_stop: TInt64) -> TInt32: 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``.
@ -1106,11 +1275,9 @@ class AD9910:
for j in range(repeat): for j in range(repeat):
t1[0] += self.measure_io_update_alignment(i, i + 1) t1[0] += self.measure_io_update_alignment(i, i + 1)
t1[1] += self.measure_io_update_alignment(i + 1, i + 2) t1[1] += self.measure_io_update_alignment(i + 1, i + 2)
if ((t1[0] == 0 and t1[1] == 0) or if (t1[0] == 0 and t1[1] == 0) or (t1[0] == repeat and t1[1] == repeat):
(t1[0] == repeat and t1[1] == repeat)):
# edge is not close to i + 1, can't interpret result # edge is not close to i + 1, can't interpret result
raise ValueError( raise ValueError("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)

View File

@ -1,15 +1,23 @@
from abc import ABC, abstractmethod
from typing import Union
from numpy import int32, int64 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.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 | SPI_CONFIG = (
0 * spi.SPI_INPUT | 1 * spi.SPI_CS_POLARITY | 0 * spi.SPI_OFFLINE
0 * spi.SPI_CLK_POLARITY | 0 * spi.SPI_CLK_PHASE | | 0 * spi.SPI_END
0 * spi.SPI_LSB_FIRST | 0 * spi.SPI_HALF_DUPLEX) | 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 # SPI clock write and read dividers
SPIT_CFG_WR = 2 SPIT_CFG_WR = 2
@ -20,30 +28,24 @@ SPIT_ATT_RD = 16
SPIT_DDS_WR = 2 SPIT_DDS_WR = 2
SPIT_DDS_RD = 16 SPIT_DDS_RD = 16
# CFG configuration register bit offsets # Common CFG configuration register bit offsets
CFG_RF_SW = 0 CFG_RF_SW = 0
CFG_LED = 4 CFG_LED = 4
CFG_PROFILE = 8 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_RF_SW = 0
STA_SMP_ERR = 4 STA_SMP_ERR = 4
STA_PLL_LOCK = 8 STA_PLL_LOCK = 8
STA_IFC_MODE = 12 STA_IFC_MODE = 12
STA_PROTO_REV = 16 STA_PROTO_REV = 16
STA_DROVER = 23
# supported hardware and CPLD code version # Supported hardware and CPLD code version
STA_PROTO_REV_MATCH = 0x08 STA_PROTO_REV_8 = 0x08
STA_PROTO_REV_9 = 0x09
# chip select (decoded) # Chip select (decoded)
CS_CFG = 1 CS_CFG = 1
CS_ATT = 2 CS_ATT = 2
CS_DDS_MULTI = 3 CS_DDS_MULTI = 3
@ -56,62 +58,77 @@ CS_DDS_CH3 = 7
DEFAULT_PROFILE = 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 @portable
def urukul_sta_rf_sw(sta): def urukul_sta_rf_sw(sta):
"""Return the RF switch status from Urukul status register value.""" """Return the RF switch status from Urukul status register value."""
return (sta >> STA_RF_SW) & 0xf return (sta >> STA_RF_SW) & 0xF
@portable @portable
def urukul_sta_smp_err(sta): def urukul_sta_smp_err(sta):
"""Return the SMP_ERR status from Urukul status register value.""" """Return the SMP_ERR status from Urukul status register value."""
return (sta >> STA_SMP_ERR) & 0xf return (sta >> STA_SMP_ERR) & 0xF
@portable @portable
def urukul_sta_pll_lock(sta): def urukul_sta_pll_lock(sta):
"""Return the PLL_LOCK status from Urukul status register value.""" """Return the PLL_LOCK status from Urukul status register value."""
return (sta >> STA_PLL_LOCK) & 0xf return (sta >> STA_PLL_LOCK) & 0xF
@portable @portable
def urukul_sta_ifc_mode(sta): def urukul_sta_ifc_mode(sta):
"""Return the IFC_MODE status from Urukul status register value.""" """Return the IFC_MODE status from Urukul status register value."""
return (sta >> STA_IFC_MODE) & 0xf return (sta >> STA_IFC_MODE) & 0xF
@portable @portable
def urukul_sta_proto_rev(sta): def urukul_sta_proto_rev(sta):
"""Return the PROTO_REV value from Urukul status register value.""" """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: class _RegIOUpdate:
def __init__(self, cpld): def __init__(self, cpld, chip_select):
self.cpld = cpld self.cpld = cpld
self.chip_select = chip_select
@kernel @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 cfg = self.cpld.cfg_reg
self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE)) if self.cpld.proto_rev == 0x08:
delay(t) 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) self.cpld.cfg_write(cfg)
@ -124,6 +141,420 @@ class _DummySync:
pass 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: class CPLD:
"""Urukul CPLD SPI router and configuration interface. """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 front panel SMA with no clock connected), then the ``init()`` method of
the DDS channels can fail with the error message ``PLL lock timeout``. the DDS channels can fail with the error message ``PLL lock timeout``.
""" """
kernel_invariants = {"refclk", "bus", "core", "io_update", "clk_div"} kernel_invariants = {"refclk", "bus", "core", "io_update", "clk_div"}
def __init__(self, dmgr, spi_device, io_update_device=None, def __init__(
dds_reset_device=None, sync_device=None, self,
sync_sel=0, clk_sel=0, clk_div=0, rf_sw=0, dmgr,
refclk=125e6, att=0x00000000, sync_div=None, spi_device,
core_device="core"): 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.core = dmgr.get(core_device)
self.refclk = refclk self.refclk = refclk
@ -179,7 +623,7 @@ class CPLD:
if io_update_device is not None: if io_update_device is not None:
self.io_update = dmgr.get(io_update_device) self.io_update = dmgr.get(io_update_device)
else: else:
self.io_update = _RegIOUpdate(self) self.io_update = io_update_device
if dds_reset_device is not None: if dds_reset_device is not None:
self.dds_reset = dmgr.get(dds_reset_device) self.dds_reset = dmgr.get(dds_reset_device)
if sync_device is not None: if sync_device is not None:
@ -191,77 +635,109 @@ class CPLD:
assert sync_div is None assert sync_div is None
sync_div = 0 sync_div = 0
self.cfg_reg = urukul_cfg(rf_sw=rf_sw, led=0, profile=DEFAULT_PROFILE, # proto_rev = 0x09 # urukul_sta_proto_rev(self.sta_read())
io_update=0, mask_nu=0, clk_sel=clk_sel, 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, sync_sel=sync_sel,
rst=0, io_rst=0, clk_div=clk_div) 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.att_reg = int32(int64(att))
self.sync_div = sync_div self.sync_div = sync_div
@kernel @kernel
def cfg_write(self, cfg: TInt32): def cfg_write(self, cfg):
"""Write to the configuration register. self.version_manager.cfg_write(self, cfg)
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
@kernel @kernel
def sta_read(self) -> TInt32: def sta_read(self):
"""Read the status register. return self.version_manager.sta_read(self)
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()
@kernel @kernel
def init(self, blind: TBool = False): def init(self):
"""Initialize and detect Urukul. self.version_manager.init(self)
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
@kernel @kernel
def io_rst(self): def io_rst(self):
"""Pulse IO_RST""" self.version_manager.io_rst(self)
self.cfg_write(self.cfg_reg | (1 << CFG_IO_RST))
self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST)) @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 @kernel
def cfg_sw(self, channel: TInt32, on: TBool): def cfg_sw(self, channel: TInt32, on: TBool):
@ -285,7 +761,7 @@ class CPLD:
:param state: RF switch state as a 4-bit integer. :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"}) @portable(flags={"fast-math"})
def mu_to_att(self, att_mu: TInt32) -> TFloat: def mu_to_att(self, att_mu: TInt32) -> TFloat:
@ -294,7 +770,7 @@ class CPLD:
:param att_mu: Digital attenuation setting. :param att_mu: Digital attenuation setting.
:return: Attenuation setting in dB. :return: Attenuation setting in dB.
""" """
return (255 - (att_mu & 0xff)) / 8 return (255 - (att_mu & 0xFF)) / 8
@portable(flags={"fast-math"}) @portable(flags={"fast-math"})
def att_to_mu(self, att: TFloat) -> TInt32: def att_to_mu(self, att: TFloat) -> TInt32:
@ -320,7 +796,7 @@ class CPLD:
:param att: 8-bit digital attenuation setting: :param att: 8-bit digital attenuation setting:
255 minimum attenuation, 0 maximum attenuation (31.5 dB) 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) a |= att << (channel * 8)
self.set_all_att_mu(a) self.set_all_att_mu(a)
@ -331,8 +807,7 @@ class CPLD:
:param att_reg: Attenuator setting string (32-bit) :param att_reg: Attenuator setting string (32-bit)
""" """
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)
self.bus.write(att_reg) self.bus.write(att_reg)
self.att_reg = att_reg self.att_reg = att_reg
@ -340,7 +815,6 @@ class CPLD:
def set_att(self, channel: TInt32, att: TFloat): 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.
See also :meth:`set_att_mu`. See also :meth:`set_att_mu`.
:param channel: Attenuator channel (0-3). :param channel: Attenuator channel (0-3).
@ -361,11 +835,9 @@ class CPLD:
:return: 32-bit attenuator settings :return: 32-bit attenuator settings
""" """
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32, self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32, SPIT_ATT_RD, CS_ATT)
SPIT_ATT_RD, CS_ATT)
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
@ -384,7 +856,7 @@ class CPLD:
:return: 8-bit digital attenuation setting: :return: 8-bit digital attenuation setting:
255 minimum attenuation, 0 maximum attenuation (31.5 dB) 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 @kernel
def get_channel_att(self, channel: TInt32) -> TFloat: def get_channel_att(self, channel: TInt32) -> TFloat:
@ -417,13 +889,12 @@ class CPLD:
self.sync.set_mu(ftw) self.sync.set_mu(ftw)
@kernel @kernel
def set_profile(self, profile: TInt32): def set_profile(self, channel: TInt32, profile: TInt32):
"""Set the PROFILE pins. """Set the CFG.PROFILE[0:2] pins for a channel.
The PROFILE pins are common to all four DDS channels.
:param channel: Channel (0-3).
:param profile: PROFILE pins in numeric representation (0-7). :param profile: PROFILE pins in numeric representation (0-7).
""" """
cfg = self.cfg_reg & ~(7 << CFG_PROFILE) cfg = self.cfg_reg & ~(7 << (CFG_PROFILE + channel * 3))
cfg |= (profile & 7) << CFG_PROFILE cfg |= (profile & 7) << (CFG_PROFILE + channel * 3)
self.cfg_write(cfg) self.cfg_write(cfg)