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:
parent
9c99d116bb
commit
c52bdf4fe9
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user