coredevice/zotino: port to NAC3

This commit is contained in:
Sebastien Bourdeauducq 2021-11-19 19:13:50 +08:00
parent 64877c0588
commit bd95d9cf3d
2 changed files with 76 additions and 56 deletions

View File

@ -8,17 +8,19 @@ time is an error.
# Designed from the data sheets and somewhat after the linux kernel # Designed from the data sheets and somewhat after the linux kernel
# iio driver. # iio driver.
from numpy import int32 from numpy import int32, int64
from artiq.language.core import (kernel, portable, delay_mu, delay, now_mu, from artiq.language.core import nac3, kernel, portable, KernelInvariant
at_mu)
from artiq.language.units import ns, us from artiq.language.units import ns, us
from artiq.coredevice import spi2 as spi from artiq.coredevice.core import Core
from artiq.coredevice.ttl import TTLOut
from artiq.coredevice.spi2 import *
SPI_AD53XX_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | SPI_AD53XX_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
0*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE | 0*SPI_INPUT | 0*SPI_CS_POLARITY |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) 0*SPI_CLK_POLARITY | 1*SPI_CLK_PHASE |
0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
AD53XX_CMD_DATA = 3 << 22 AD53XX_CMD_DATA = 3 << 22
AD53XX_CMD_OFFSET = 2 << 22 AD53XX_CMD_OFFSET = 2 << 22
@ -52,7 +54,7 @@ AD53XX_READ_AB3 = 0x109 << 7
@portable @portable
def ad53xx_cmd_write_ch(channel, value, op): def ad53xx_cmd_write_ch(channel: int32, value: int32, op: int32) -> int32:
"""Returns the word that must be written to the DAC to set a DAC """Returns the word that must be written to the DAC to set a DAC
channel register to a given value. channel register to a given value.
@ -67,7 +69,7 @@ def ad53xx_cmd_write_ch(channel, value, op):
@portable @portable
def ad53xx_cmd_read_ch(channel, op): def ad53xx_cmd_read_ch(channel: int32, op: int32) -> int32:
"""Returns the word that must be written to the DAC to read a given """Returns the word that must be written to the DAC to read a given
DAC channel register. DAC channel register.
@ -82,7 +84,7 @@ def ad53xx_cmd_read_ch(channel, op):
# maintain function definition for backward compatibility # maintain function definition for backward compatibility
@portable @portable
def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.): def voltage_to_mu(voltage: float, offset_dacs: int32 = 0x2000, vref: float = 5.) -> int32:
"""Returns the 16-bit DAC register value required to produce a given output """Returns the 16-bit DAC register value required to produce a given output
voltage, assuming offset and gain errors have been trimmed out. voltage, assuming offset and gain errors have been trimmed out.
@ -100,22 +102,25 @@ def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.):
:param vref: DAC reference voltage (default: 5.) :param vref: DAC reference voltage (default: 5.)
:return: The 16-bit DAC register value :return: The 16-bit DAC register value
""" """
code = int(round((1 << 16) * (voltage / (4. * vref)) + offset_dacs * 0x4)) code = round(float(1 << 16) * (voltage / (4. * vref))) + offset_dacs * 0x4
if code < 0x0 or code > 0xffff: if code < 0x0 or code > 0xffff:
raise ValueError("Invalid DAC voltage!") # NAC3TODO raise ValueError("Invalid DAC voltage!")
pass
return code return code
@nac3
class _DummyTTL: class _DummyTTL:
@portable @kernel
def on(self): def on(self):
pass pass
@portable @kernel
def off(self): def off(self):
pass pass
@nac3
class AD53xx: class AD53xx:
"""Analog devices AD53[67][0123] family of multi-channel Digital to Analog """Analog devices AD53[67][0123] family of multi-channel Digital to Analog
Converters. Converters.
@ -137,8 +142,15 @@ class AD53xx:
experiments. (default: 8192) experiments. (default: 8192)
:param core_device: Core device name (default: "core") :param core_device: Core device name (default: "core")
""" """
kernel_invariants = {"bus", "ldac", "clr", "chip_select", "div_write", core: KernelInvariant[Core]
"div_read", "vref", "core"} bus: KernelInvariant[SPIMaster]
ldac: KernelInvariant[TTLOut]
clr: KernelInvariant[TTLOut]
chip_select: KernelInvariant[int32]
div_write: KernelInvariant[int32]
div_read: KernelInvariant[int32]
vref: KernelInvariant[float]
offset_dacs: int32
def __init__(self, dmgr, spi_device, ldac_device=None, clr_device=None, def __init__(self, dmgr, spi_device, ldac_device=None, clr_device=None,
chip_select=1, div_write=4, div_read=16, vref=5., chip_select=1, div_write=4, div_read=16, vref=5.,
@ -161,7 +173,7 @@ class AD53xx:
self.core = dmgr.get(core) self.core = dmgr.get(core)
@kernel @kernel
def init(self, blind=False): def init(self, blind: bool = False):
"""Configures the SPI bus, drives LDAC and CLR high, programmes """Configures the SPI bus, drives LDAC and CLR high, programmes
the offset DACs, and enables overtemperature shutdown. the offset DACs, and enables overtemperature shutdown.
@ -177,22 +189,25 @@ class AD53xx:
self.chip_select) self.chip_select)
self.write_offset_dacs_mu(self.offset_dacs) self.write_offset_dacs_mu(self.offset_dacs)
if not blind: if not blind:
ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL) ctrl = self.read_reg(0, AD53XX_READ_CONTROL)
if ctrl == 0xffff: if ctrl == 0xffff:
raise ValueError("DAC not found") # NAC3TODO raise ValueError("DAC not found")
if ctrl & 0b10000: pass
raise ValueError("DAC over temperature") if (ctrl & 0b10000) != 0:
delay(25*us) # NAC3TODO raise ValueError("DAC over temperature")
pass
self.core.delay(125.*us) # NAC3TODO try to restore original 25us after kernel invariants
self.bus.write( # enable power and overtemperature shutdown self.bus.write( # enable power and overtemperature shutdown
(AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_CONTROL | 0b0010) << 8) (AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_CONTROL | 0b0010) << 8)
if not blind: if not blind:
ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL) ctrl = self.read_reg(0, AD53XX_READ_CONTROL)
if (ctrl & 0b10111) != 0b00010: if (ctrl & 0b10111) != 0b00010:
raise ValueError("DAC CONTROL readback mismatch") # NAC3TODO raise ValueError("DAC CONTROL readback mismatch")
delay(15*us) pass
self.core.delay(115.*us) # NAC3TODO try to restore original 15us after kernel invariants
@kernel @kernel
def read_reg(self, channel=0, op=AD53XX_READ_X1A): def read_reg(self, channel: int32 = 0, op: int32 = AD53XX_READ_X1A) -> int32:
"""Read a DAC register. """Read a DAC register.
This method advances the timeline by the duration of two SPI transfers This method advances the timeline by the duration of two SPI transfers
@ -205,17 +220,16 @@ class AD53xx:
:return: The 16 bit register value :return: The 16 bit register value
""" """
self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8) self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8)
self.bus.set_config_mu(SPI_AD53XX_CONFIG | spi.SPI_INPUT, 24, self.bus.set_config_mu(SPI_AD53XX_CONFIG | SPI_INPUT, 24,
self.div_read, self.chip_select) self.div_read, self.chip_select)
delay(270*ns) # t_21 min sync high in readback self.core.delay(270.*ns) # t_21 min sync high in readback
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_NOP) << 8) self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_NOP) << 8)
self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write, self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write,
self.chip_select) self.chip_select)
# FIXME: the int32 should not be needed to resolve unification return self.bus.read() & 0xffff
return self.bus.read() & int32(0xffff)
@kernel @kernel
def write_offset_dacs_mu(self, value): def write_offset_dacs_mu(self, value: int32):
"""Program the OFS0 and OFS1 offset DAC registers. """Program the OFS0 and OFS1 offset DAC registers.
Writes to the offset DACs take effect immediately without requiring Writes to the offset DACs take effect immediately without requiring
@ -230,7 +244,7 @@ class AD53xx:
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_OFS1 | value) << 8) self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_OFS1 | value) << 8)
@kernel @kernel
def write_gain_mu(self, channel, gain=0xffff): def write_gain_mu(self, channel: int32, gain: int32 = 0xffff):
"""Program the gain register for a DAC channel. """Program the gain register for a DAC channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:). The DAC output is not updated until LDAC is pulsed (see :meth load:).
@ -242,7 +256,7 @@ class AD53xx:
ad53xx_cmd_write_ch(channel, gain, AD53XX_CMD_GAIN) << 8) ad53xx_cmd_write_ch(channel, gain, AD53XX_CMD_GAIN) << 8)
@kernel @kernel
def write_offset_mu(self, channel, offset=0x8000): def write_offset_mu(self, channel: int32, offset: int32 = 0x8000):
"""Program the offset register for a DAC channel. """Program the offset register for a DAC channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:). The DAC output is not updated until LDAC is pulsed (see :meth load:).
@ -254,7 +268,7 @@ class AD53xx:
ad53xx_cmd_write_ch(channel, offset, AD53XX_CMD_OFFSET) << 8) ad53xx_cmd_write_ch(channel, offset, AD53XX_CMD_OFFSET) << 8)
@kernel @kernel
def write_offset(self, channel, voltage): def write_offset(self, channel: int32, voltage: float):
"""Program the DAC offset voltage for a channel. """Program the DAC offset voltage for a channel.
An offset of +V can be used to trim out a DAC offset error of -V. An offset of +V can be used to trim out a DAC offset error of -V.
@ -267,7 +281,7 @@ class AD53xx:
self.vref)) self.vref))
@kernel @kernel
def write_dac_mu(self, channel, value): def write_dac_mu(self, channel: int32, value: int32):
"""Program the DAC input register for a channel. """Program the DAC input register for a channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:). The DAC output is not updated until LDAC is pulsed (see :meth load:).
@ -277,7 +291,7 @@ class AD53xx:
ad53xx_cmd_write_ch(channel, value, AD53XX_CMD_DATA) << 8) ad53xx_cmd_write_ch(channel, value, AD53XX_CMD_DATA) << 8)
@kernel @kernel
def write_dac(self, channel, voltage): def write_dac(self, channel: int32, voltage: float):
"""Program the DAC output voltage for a channel. """Program the DAC output voltage for a channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:). The DAC output is not updated until LDAC is pulsed (see :meth load:).
@ -299,11 +313,11 @@ class AD53xx:
This method advances the timeline by two RTIO clock periods. This method advances the timeline by two RTIO clock periods.
""" """
self.ldac.off() self.ldac.off()
delay_mu(2*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low delay_mu(int64(2)*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low
self.ldac.on() self.ldac.on()
@kernel @kernel
def set_dac_mu(self, values, channels=list(range(40))): def set_dac_mu(self, values: list[int32], channels: list[int32] = list(range(40))):
"""Program multiple DAC channels and pulse LDAC to update the DAC """Program multiple DAC channels and pulse LDAC to update the DAC
outputs. outputs.
@ -322,17 +336,19 @@ class AD53xx:
t0 = now_mu() t0 = now_mu()
# t10: max busy period after writing to DAC registers # t10: max busy period after writing to DAC registers
t_10 = self.core.seconds_to_mu(1500*ns) t_10 = self.core.seconds_to_mu(1500.*ns)
# NAC3TODO len(values) https://git.m-labs.hk/M-Labs/nac3/issues/103
len_values = 4
# compensate all delays that will be applied # compensate all delays that will be applied
delay_mu(-t_10-len(values)*self.bus.xfer_duration_mu) delay_mu(-t_10-int64(len_values)*self.bus.xfer_duration_mu)
for i in range(len(values)): for i in range(len_values):
self.write_dac_mu(channels[i], values[i]) self.write_dac_mu(channels[i], values[i])
delay_mu(t_10) delay_mu(t_10)
self.load() self.load()
at_mu(t0) at_mu(t0)
@kernel @kernel
def set_dac(self, voltages, channels=list(range(40))): def set_dac(self, voltages: list[float], channels: list[int32] = list(range(40))):
"""Program multiple DAC channels and pulse LDAC to update the DAC """Program multiple DAC channels and pulse LDAC to update the DAC
outputs. outputs.
@ -351,7 +367,7 @@ class AD53xx:
self.set_dac_mu(values, channels) self.set_dac_mu(values, channels)
@kernel @kernel
def calibrate(self, channel, vzs, vfs): def calibrate(self, channel: int32, vzs: float, vfs: float):
"""Two-point calibration of a DAC channel. """Two-point calibration of a DAC channel.
Programs the offset and gain register to trim out DAC errors. Does not Programs the offset and gain register to trim out DAC errors. Does not
@ -371,15 +387,15 @@ class AD53xx:
gain_err = voltage_to_mu(vfs, self.offset_dacs, self.vref) - ( gain_err = voltage_to_mu(vfs, self.offset_dacs, self.vref) - (
offset_err + 0xffff) offset_err + 0xffff)
assert offset_err <= 0 # NAC3TODO assert offset_err <= 0
assert gain_err >= 0 # NAC3TODO assert gain_err >= 0
self.core.break_realtime() self.core.break_realtime()
self.write_offset_mu(channel, 0x8000-offset_err) self.write_offset_mu(channel, 0x8000-offset_err)
self.write_gain_mu(channel, 0xffff-gain_err) self.write_gain_mu(channel, 0xffff-gain_err)
@portable @portable
def voltage_to_mu(self, voltage): def voltage_to_mu(self, voltage: float) -> int32:
"""Returns the 16-bit DAC register value required to produce a given """Returns the 16-bit DAC register value required to produce a given
output voltage, assuming offset and gain errors have been trimmed out. output voltage, assuming offset and gain errors have been trimmed out.

View File

@ -4,19 +4,23 @@ Output event replacement is not supported and issuing commands at the same
time is an error. time is an error.
""" """
from artiq.language.core import kernel from numpy import int32
from artiq.coredevice import spi2 as spi
from artiq.language.core import nac3, kernel
from artiq.coredevice.spi2 import *
from artiq.coredevice.ad53xx import SPI_AD53XX_CONFIG, AD53xx from artiq.coredevice.ad53xx import SPI_AD53XX_CONFIG, AD53xx
_SPI_SR_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | _SPI_SR_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | 0*SPI_INPUT | 0*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_CS_DAC = 1 _SPI_CS_DAC = 1
_SPI_CS_SR = 2 _SPI_CS_SR = 2
@nac3
class Zotino(AD53xx): class Zotino(AD53xx):
""" Zotino 32-channel, 16-bit 1MSPS DAC. """ Zotino 32-channel, 16-bit 1MSPS DAC.
@ -42,7 +46,7 @@ class Zotino(AD53xx):
div_read=div_read, core=core) div_read=div_read, core=core)
@kernel @kernel
def set_leds(self, leds): def set_leds(self, leds: int32):
"""Sets the states of the 8 user LEDs. """Sets the states of the 8 user LEDs.
:param leds: 8-bit word with LED state :param leds: 8-bit word with LED state