coredevice/urukul: port to NAC3

This commit is contained in:
Sebastien Bourdeauducq 2021-11-10 19:01:44 +08:00
parent 31955d0c7a
commit 93f24c9f94
1 changed files with 86 additions and 65 deletions

View File

@ -1,15 +1,17 @@
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.core import nac3, KernelInvariant, kernel, portable
from artiq.language.units import us, ms 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.core import Core
from artiq.coredevice.spi2 import *
from artiq.coredevice.ttl import TTLOut
SPI_CONFIG = (0 * spi.SPI_OFFLINE | 0 * spi.SPI_END |
0 * spi.SPI_INPUT | 1 * spi.SPI_CS_POLARITY | SPI_CONFIG = (0 * SPI_OFFLINE | 0 * SPI_END |
0 * spi.SPI_CLK_POLARITY | 0 * spi.SPI_CLK_PHASE | 0 * SPI_INPUT | 1 * SPI_CS_POLARITY |
0 * spi.SPI_LSB_FIRST | 0 * spi.SPI_HALF_DUPLEX) 0 * SPI_CLK_POLARITY | 0 * SPI_CLK_PHASE |
0 * SPI_LSB_FIRST | 0 * SPI_HALF_DUPLEX)
# SPI clock write and read dividers # SPI clock write and read dividers
SPIT_CFG_WR = 2 SPIT_CFG_WR = 2
@ -54,8 +56,8 @@ CS_DDS_CH3 = 7
@portable @portable
def urukul_cfg(rf_sw, led, profile, io_update, mask_nu, def urukul_cfg(rf_sw: int32, led: int32, profile: int32, io_update: int32, mask_nu: int32,
clk_sel, sync_sel, rst, io_rst, clk_div): clk_sel: int32, sync_sel: int32, rst: int32, io_rst: int32, clk_div: int32) -> int32:
"""Build Urukul CPLD configuration register""" """Build Urukul CPLD configuration register"""
return ((rf_sw << CFG_RF_SW) | return ((rf_sw << CFG_RF_SW) |
(led << CFG_LED) | (led << CFG_LED) |
@ -71,56 +73,45 @@ def urukul_cfg(rf_sw, led, profile, io_update, mask_nu,
@portable @portable
def urukul_sta_rf_sw(sta): def urukul_sta_rf_sw(sta: int32) -> int32:
"""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: int32) -> int32:
"""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: int32) -> int32:
"""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: int32) -> int32:
"""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: int32) -> int32:
"""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
@nac3
class _RegIOUpdate:
def __init__(self, cpld):
self.cpld = cpld
@kernel
def pulse(self, t: TFloat):
cfg = self.cpld.cfg_reg
self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE))
delay(t)
self.cpld.cfg_write(cfg)
class _DummySync: class _DummySync:
def __init__(self, cpld): def __init__(self, cpld):
self.cpld = cpld self.cpld = cpld
@kernel @kernel
def set_mu(self, ftw: TInt32): def set_mu(self, ftw: int32):
pass pass
@nac3
class CPLD: class CPLD:
"""Urukul CPLD SPI router and configuration interface. """Urukul CPLD SPI router and configuration interface.
@ -159,7 +150,16 @@ 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"}
core: KernelInvariant[Core]
refclk: KernelInvariant[float]
bus: KernelInvariant[SPIMaster]
io_update: KernelInvariant[TTLOut]
clk_div: KernelInvariant[int32]
sync: KernelInvariant[_DummySync]
cfg_reg: int32
att_reg: int32
sync_div: int32
def __init__(self, dmgr, spi_device, io_update_device=None, def __init__(self, dmgr, spi_device, io_update_device=None,
dds_reset_device=None, sync_device=None, dds_reset_device=None, sync_device=None,
@ -176,13 +176,17 @@ 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 = _RegIOUpdate(self.core, self)
# NAC3TODO
raise NotImplementedError
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:
self.sync = dmgr.get(sync_device) self.sync = dmgr.get(sync_device)
if sync_div is None: if sync_div is None:
sync_div = 2 sync_div = 2
# NAC3TODO
raise NotImplementedError
else: else:
self.sync = _DummySync(self) self.sync = _DummySync(self)
assert sync_div is None assert sync_div is None
@ -196,7 +200,7 @@ class CPLD:
self.sync_div = sync_div self.sync_div = sync_div
@kernel @kernel
def cfg_write(self, cfg: TInt32): def cfg_write(self, cfg: int32):
"""Write to the configuration register. """Write to the configuration register.
See :func:`urukul_cfg` for possible flags. See :func:`urukul_cfg` for possible flags.
@ -204,13 +208,13 @@ class CPLD:
:param cfg: 24 bit data to be written. Will be stored at :param cfg: 24 bit data to be written. Will be stored at
:attr:`cfg_reg`. :attr:`cfg_reg`.
""" """
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24,
SPIT_CFG_WR, CS_CFG) SPIT_CFG_WR, CS_CFG)
self.bus.write(cfg << 8) self.bus.write(cfg << 8)
self.cfg_reg = cfg self.cfg_reg = cfg
@kernel @kernel
def sta_read(self) -> TInt32: def sta_read(self) -> int32:
"""Read the status register. """Read the status register.
Use any of the following functions to extract values: Use any of the following functions to extract values:
@ -223,13 +227,13 @@ class CPLD:
:return: The status register value. :return: The status register value.
""" """
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 24, self.bus.set_config_mu(SPI_CONFIG | SPI_END | SPI_INPUT, 24,
SPIT_CFG_RD, CS_CFG) SPIT_CFG_RD, CS_CFG)
self.bus.write(self.cfg_reg << 8) self.bus.write(self.cfg_reg << 8)
return self.bus.read() return self.bus.read()
@kernel @kernel
def init(self, blind: TBool = False): def init(self, blind: bool = False):
"""Initialize and detect Urukul. """Initialize and detect Urukul.
Resets the DDS I/O interface and verifies correct CPLD gateware Resets the DDS I/O interface and verifies correct CPLD gateware
@ -246,13 +250,14 @@ class CPLD:
else: else:
proto_rev = urukul_sta_proto_rev(self.sta_read()) proto_rev = urukul_sta_proto_rev(self.sta_read())
if proto_rev != STA_PROTO_REV_MATCH: if proto_rev != STA_PROTO_REV_MATCH:
raise ValueError("Urukul proto_rev mismatch") # NAC3TODO raise ValueError("Urukul proto_rev mismatch")
delay(100 * us) # reset, slack pass
self.core.delay(100. * us) # reset, slack
self.cfg_write(cfg) self.cfg_write(cfg)
if self.sync_div: if self.sync_div != 0:
at_mu(now_mu() & ~0xf) # align to RTIO/2 at_mu(now_mu() & ~int64(0xf)) # align to RTIO/2
self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16 self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16
delay(1 * ms) # DDS wake up self.core.delay(1. * ms) # DDS wake up
@kernel @kernel
def io_rst(self): def io_rst(self):
@ -260,8 +265,8 @@ class CPLD:
self.cfg_write(self.cfg_reg | (1 << CFG_IO_RST)) self.cfg_write(self.cfg_reg | (1 << CFG_IO_RST))
self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST)) self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST))
@kernel # NAC3TODO @kernel
def cfg_sw(self, channel: TInt32, on: TBool): def cfg_sw(self, channel: int32, on: bool):
"""Configure the RF switches through the configuration register. """Configure the RF switches through the configuration register.
These values are logically OR-ed with the LVDS lines on EEM1. These values are logically OR-ed with the LVDS lines on EEM1.
@ -277,15 +282,15 @@ class CPLD:
self.cfg_write(c) self.cfg_write(c)
@kernel @kernel
def cfg_switches(self, state: TInt32): def cfg_switches(self, state: int32):
"""Configure all four RF switches through the configuration register. """Configure all four RF switches through the configuration register.
:param state: RF switch state as a 4 bit integer. :param state: RF switch state as a 4 bit integer.
""" """
self.cfg_write((self.cfg_reg & ~0xf) | state) self.cfg_write((self.cfg_reg & ~0xf) | state)
@portable(flags={"fast-math"}) @portable
def mu_to_att(self, att_mu: TInt32) -> TFloat: def mu_to_att(self, att_mu: int32) -> float:
"""Convert a digital attenuation setting to dB. """Convert a digital attenuation setting to dB.
:param att_mu: Digital attenuation setting. :param att_mu: Digital attenuation setting.
@ -293,20 +298,21 @@ class CPLD:
""" """
return (255 - (att_mu & 0xff)) / 8 return (255 - (att_mu & 0xff)) / 8
@portable(flags={"fast-math"}) @portable
def att_to_mu(self, att: TFloat) -> TInt32: def att_to_mu(self, att: float) -> int32:
"""Convert an attenuation setting in dB to machine units. """Convert an attenuation setting in dB to machine units.
:param att: Attenuation setting in dB. :param att: Attenuation setting in dB.
:return: Digital attenuation setting. :return: Digital attenuation setting.
""" """
code = int32(255) - int32(round(att * 8)) code = 255 - round(att * 8.)
if code < 0 or code > 255: if code < 0 or code > 255:
raise ValueError("Invalid urukul.CPLD attenuation!") # NAC3TODO raise ValueError("Invalid urukul.CPLD attenuation!")
pass
return code return code
@kernel # NAC3TODO @kernel
def set_att_mu(self, channel: TInt32, att: TInt32): def set_att_mu(self, channel: int32, att: int32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
This method will also write the attenuator settings of the three This method will also write the attenuator settings of the three
@ -322,20 +328,20 @@ class CPLD:
self.set_all_att_mu(a) self.set_all_att_mu(a)
@kernel @kernel
def set_all_att_mu(self, att_reg: TInt32): def set_all_att_mu(self, att_reg: int32):
"""Set all four digital step attenuators (in machine units). """Set all four digital step attenuators (in machine units).
.. seealso:: :meth:`set_att_mu` .. seealso:: :meth:`set_att_mu`
: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_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
@kernel # NAC3TODO @kernel
def set_att(self, channel: TInt32, att: TFloat): def set_att(self, channel: int32, att: float):
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -350,7 +356,7 @@ class CPLD:
self.set_att_mu(channel, self.att_to_mu(att)) self.set_att_mu(channel, self.att_to_mu(att))
@kernel @kernel
def get_att_mu(self) -> TInt32: def get_att_mu(self) -> int32:
"""Return the digital step attenuator settings in machine units. """Return the digital step attenuator settings in machine units.
The result is stored and will be used in future calls of The result is stored and will be used in future calls of
@ -360,18 +366,18 @@ 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_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_END, 32,
SPIT_ATT_WR, CS_ATT) SPIT_ATT_WR, CS_ATT)
delay(10 * us) self.core.delay(10. * us)
self.att_reg = self.bus.read() self.att_reg = self.bus.read()
self.bus.write(self.att_reg) # shift in current value again and latch self.bus.write(self.att_reg) # shift in current value again and latch
return self.att_reg return self.att_reg
@kernel @kernel
def get_channel_att_mu(self, channel: TInt32) -> TInt32: def get_channel_att_mu(self, channel: int32) -> int32:
"""Get digital step attenuator value for a channel in machine units. """Get digital step attenuator value for a channel in machine units.
The result is stored and will be used in future calls of The result is stored and will be used in future calls of
@ -386,7 +392,7 @@ class CPLD:
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: int32) -> float:
"""Get digital step attenuator value for a channel in SI units. """Get digital step attenuator value for a channel in SI units.
.. seealso:: :meth:`get_channel_att_mu` .. seealso:: :meth:`get_channel_att_mu`
@ -399,7 +405,7 @@ class CPLD:
return self.mu_to_att(self.get_channel_att_mu(channel)) return self.mu_to_att(self.get_channel_att_mu(channel))
@kernel @kernel
def set_sync_div(self, div: TInt32): def set_sync_div(self, div: int32):
"""Set the SYNC_IN AD9910 pulse generator frequency """Set the SYNC_IN AD9910 pulse generator frequency
and align it to the current RTIO timestamp. and align it to the current RTIO timestamp.
@ -412,11 +418,11 @@ class CPLD:
""" """
ftw_max = 1 << 4 ftw_max = 1 << 4
ftw = ftw_max // div ftw = ftw_max // div
assert ftw * div == ftw_max # NAC3TODO assert ftw * div == ftw_max
self.sync.set_mu(ftw) self.sync.set_mu(ftw)
@kernel # NAC3TODO @kernel
def set_profile(self, profile: TInt32): def set_profile(self, profile: int32):
"""Set the PROFILE pins. """Set the PROFILE pins.
The PROFILE pins are common to all four DDS channels. The PROFILE pins are common to all four DDS channels.
@ -426,3 +432,18 @@ class CPLD:
cfg = self.cfg_reg & ~(7 << CFG_PROFILE) cfg = self.cfg_reg & ~(7 << CFG_PROFILE)
cfg |= (profile & 7) << CFG_PROFILE cfg |= (profile & 7) << CFG_PROFILE
self.cfg_write(cfg) self.cfg_write(cfg)
class _RegIOUpdate:
core: KernelInvariant[Core]
cpld: KernelInvariant[CPLD]
def __init__(self, core, cpld):
self.core = core
self.cpld = cpld
@kernel
def pulse(self, t: float):
cfg = self.cpld.cfg_reg
self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE))
delay(t)
self.cpld.cfg_write(cfg)