From bd95d9cf3d4b9365df58788b3d3460dc8fff5e62 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 19 Nov 2021 19:13:50 +0800 Subject: [PATCH] coredevice/zotino: port to NAC3 --- artiq/coredevice/ad53xx.py | 112 +++++++++++++++++++++---------------- artiq/coredevice/zotino.py | 20 ++++--- 2 files changed, 76 insertions(+), 56 deletions(-) diff --git a/artiq/coredevice/ad53xx.py b/artiq/coredevice/ad53xx.py index 9a2b8eb2e..4d7656072 100644 --- a/artiq/coredevice/ad53xx.py +++ b/artiq/coredevice/ad53xx.py @@ -8,17 +8,19 @@ time is an error. # Designed from the data sheets and somewhat after the linux kernel # iio driver. -from numpy import int32 +from numpy import int32, int64 -from artiq.language.core import (kernel, portable, delay_mu, delay, now_mu, - at_mu) +from artiq.language.core import nac3, kernel, portable, KernelInvariant 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 | - 0*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) + +SPI_AD53XX_CONFIG = (0*SPI_OFFLINE | 1*SPI_END | + 0*SPI_INPUT | 0*SPI_CS_POLARITY | + 0*SPI_CLK_POLARITY | 1*SPI_CLK_PHASE | + 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX) AD53XX_CMD_DATA = 3 << 22 AD53XX_CMD_OFFSET = 2 << 22 @@ -52,7 +54,7 @@ AD53XX_READ_AB3 = 0x109 << 7 @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 channel register to a given value. @@ -67,7 +69,7 @@ def ad53xx_cmd_write_ch(channel, value, op): @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 DAC channel register. @@ -82,7 +84,7 @@ def ad53xx_cmd_read_ch(channel, op): # maintain function definition for backward compatibility @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 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.) :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: - raise ValueError("Invalid DAC voltage!") + # NAC3TODO raise ValueError("Invalid DAC voltage!") + pass return code +@nac3 class _DummyTTL: - @portable + @kernel def on(self): pass - @portable + @kernel def off(self): pass +@nac3 class AD53xx: """Analog devices AD53[67][0123] family of multi-channel Digital to Analog Converters. @@ -137,8 +142,15 @@ class AD53xx: experiments. (default: 8192) :param core_device: Core device name (default: "core") """ - kernel_invariants = {"bus", "ldac", "clr", "chip_select", "div_write", - "div_read", "vref", "core"} + core: KernelInvariant[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, chip_select=1, div_write=4, div_read=16, vref=5., @@ -161,7 +173,7 @@ class AD53xx: self.core = dmgr.get(core) @kernel - def init(self, blind=False): + def init(self, blind: bool = False): """Configures the SPI bus, drives LDAC and CLR high, programmes the offset DACs, and enables overtemperature shutdown. @@ -177,22 +189,25 @@ class AD53xx: self.chip_select) self.write_offset_dacs_mu(self.offset_dacs) if not blind: - ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL) + ctrl = self.read_reg(0, AD53XX_READ_CONTROL) if ctrl == 0xffff: - raise ValueError("DAC not found") - if ctrl & 0b10000: - raise ValueError("DAC over temperature") - delay(25*us) + # NAC3TODO raise ValueError("DAC not found") + pass + if (ctrl & 0b10000) != 0: + # 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 (AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_CONTROL | 0b0010) << 8) 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: - raise ValueError("DAC CONTROL readback mismatch") - delay(15*us) + # NAC3TODO raise ValueError("DAC CONTROL readback mismatch") + pass + self.core.delay(115.*us) # NAC3TODO try to restore original 15us after kernel invariants @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. This method advances the timeline by the duration of two SPI transfers @@ -205,17 +220,16 @@ class AD53xx: :return: The 16 bit register value """ 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) - 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.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write, self.chip_select) - # FIXME: the int32 should not be needed to resolve unification - return self.bus.read() & int32(0xffff) + return self.bus.read() & 0xffff @kernel - def write_offset_dacs_mu(self, value): + def write_offset_dacs_mu(self, value: int32): """Program the OFS0 and OFS1 offset DAC registers. 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) @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. 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) @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. 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) @kernel - def write_offset(self, channel, voltage): + def write_offset(self, channel: int32, voltage: float): """Program the DAC offset voltage for a channel. An offset of +V can be used to trim out a DAC offset error of -V. @@ -267,7 +281,7 @@ class AD53xx: self.vref)) @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. 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) @kernel - def write_dac(self, channel, voltage): + def write_dac(self, channel: int32, voltage: float): """Program the DAC output voltage for a channel. 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. """ 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() @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 outputs. @@ -322,17 +336,19 @@ class AD53xx: t0 = now_mu() # 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 - delay_mu(-t_10-len(values)*self.bus.xfer_duration_mu) - for i in range(len(values)): + delay_mu(-t_10-int64(len_values)*self.bus.xfer_duration_mu) + for i in range(len_values): self.write_dac_mu(channels[i], values[i]) delay_mu(t_10) self.load() at_mu(t0) @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 outputs. @@ -351,8 +367,8 @@ class AD53xx: self.set_dac_mu(values, channels) @kernel - def calibrate(self, channel, vzs, vfs): - """ Two-point calibration of a DAC channel. + def calibrate(self, channel: int32, vzs: float, vfs: float): + """Two-point calibration of a DAC channel. Programs the offset and gain register to trim out DAC errors. Does not take effect until LDAC is pulsed (see :meth load:). @@ -371,15 +387,15 @@ class AD53xx: gain_err = voltage_to_mu(vfs, self.offset_dacs, self.vref) - ( offset_err + 0xffff) - assert offset_err <= 0 - assert gain_err >= 0 + # NAC3TODO assert offset_err <= 0 + # NAC3TODO assert gain_err >= 0 self.core.break_realtime() self.write_offset_mu(channel, 0x8000-offset_err) self.write_gain_mu(channel, 0xffff-gain_err) @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 output voltage, assuming offset and gain errors have been trimmed out. diff --git a/artiq/coredevice/zotino.py b/artiq/coredevice/zotino.py index 9052f589a..a6c6a3703 100644 --- a/artiq/coredevice/zotino.py +++ b/artiq/coredevice/zotino.py @@ -4,19 +4,23 @@ Output event replacement is not supported and issuing commands at the same time is an error. """ -from artiq.language.core import kernel -from artiq.coredevice import spi2 as spi +from numpy import int32 + +from artiq.language.core import nac3, kernel +from artiq.coredevice.spi2 import * 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 | - 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) + +_SPI_SR_CONFIG = (0*SPI_OFFLINE | 1*SPI_END | + 0*SPI_INPUT | 0*SPI_CS_POLARITY | + 0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE | + 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX) _SPI_CS_DAC = 1 _SPI_CS_SR = 2 +@nac3 class Zotino(AD53xx): """ Zotino 32-channel, 16-bit 1MSPS DAC. @@ -42,8 +46,8 @@ class Zotino(AD53xx): div_read=div_read, core=core) @kernel - def set_leds(self, leds): - """ Sets the states of the 8 user LEDs. + def set_leds(self, leds: int32): + """Sets the states of the 8 user LEDs. :param leds: 8-bit word with LED state """