Source code for artiq.coredevice.fastino

"""RTIO driver for the Fastino 32channel, 16 bit, 2.5 MS/s per channel,
streaming DAC.
"""
from numpy import int32

from artiq.language.core import kernel, portable, delay, delay_mu
from artiq.coredevice.rtio import (rtio_output, rtio_output_wide,
                                   rtio_input_data)
from artiq.language.units import us
from artiq.language.types import TInt32, TList


[docs]class Fastino: """Fastino 32-channel, 16-bit, 2.5 MS/s per channel streaming DAC The RTIO PHY supports staging DAC data before transmitting them by writing to the DAC RTIO addresses, if a channel is not "held" by setting its bit using :meth:`set_hold`, the next frame will contain the update. For the DACs held, the update is triggered explicitly by setting the corresponding bit using :meth:`set_update`. Update is self-clearing. This enables atomic DAC updates synchronized to a frame edge. The `log2_width=0` RTIO layout uses one DAC channel per RTIO address and a dense RTIO address space. The RTIO words are narrow. (32 bit) and few-channel updates are efficient. There is the least amount of DAC state tracking in kernels, at the cost of more DMA and RTIO data. The setting here and in the RTIO PHY (gateware) must match. Other `log2_width` (up to `log2_width=5`) settings pack multiple (in powers of two) DAC channels into one group and into one RTIO write. The RTIO data width increases accordingly. The `log2_width` LSBs of the RTIO address for a DAC channel write must be zero and the address space is sparse. For `log2_width=5` the RTIO data is 512 bit wide. If `log2_width` is zero, the :meth:`set_dac`/:meth:`set_dac_mu` interface must be used. If non-zero, the :meth:`set_group`/:meth:`set_group_mu` interface must be used. :param channel: RTIO channel number :param core_device: Core device name (default: "core") :param log2_width: Width of DAC channel group (logarithm base 2). Value must match the corresponding value in the RTIO PHY (gateware). """ kernel_invariants = {"core", "channel", "width"} def __init__(self, dmgr, channel, core_device="core", log2_width=0): self.channel = channel << 8 self.core = dmgr.get(core_device) self.width = 1 << log2_width
[docs] @kernel def init(self): """Initialize the device. This clears reset, unsets DAC_CLR, enables AFE_PWR, clears error counters, then enables error counting """ self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=1) delay(1*us) self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=0) delay(1*us)
[docs] @kernel def write(self, addr, data): """Write data to a Fastino register. :param addr: Address to write to. :param data: Data to write. """ rtio_output(self.channel | addr, data)
[docs] @kernel def read(self, addr): """Read from Fastino register. TODO: untested :param addr: Address to read from. :return: The data read. """ rtio_output(self.channel | addr | 0x80) return rtio_input_data(self.channel >> 8)
[docs] @kernel def set_dac_mu(self, dac, data): """Write DAC data in machine units. :param dac: DAC channel to write to (0-31). :param data: DAC word to write, 16 bit unsigned integer, in machine units. """ self.write(dac, data)
[docs] @kernel def set_group_mu(self, dac: TInt32, data: TList(TInt32)): """Write a group of DAC channels in machine units. :param dac: First channel in DAC channel group (0-31). The `log2_width` LSBs must be zero. :param data: List of DAC data pairs (2x16 bit unsigned) to write, in machine units. Data exceeding group size is ignored. If the list length is less than group size, the remaining DAC channels within the group are cleared to 0 (machine units). """ if dac & (self.width - 1): raise ValueError("Group index LSBs must be zero") rtio_output_wide(self.channel | dac, data)
[docs] @portable def voltage_to_mu(self, voltage): """Convert SI Volts to DAC machine units. :param voltage: Voltage in SI Volts. :return: DAC data word in machine units, 16 bit integer. """ data = int32(round((0x8000/10.)*voltage)) + int32(0x8000) if data < 0 or data > 0xffff: raise ValueError("DAC voltage out of bounds") return data
[docs] @portable def voltage_group_to_mu(self, voltage, data): """Convert SI Volts to packed DAC channel group machine units. :param voltage: List of SI Volt voltages. :param data: List of DAC channel data pairs to write to. Half the length of `voltage`. """ for i in range(len(voltage)): v = self.voltage_to_mu(voltage[i]) if i & 1: v = data[i // 2] | (v << 16) data[i // 2] = int32(v)
[docs] @kernel def set_dac(self, dac, voltage): """Set DAC data to given voltage. :param dac: DAC channel (0-31). :param voltage: Desired output voltage. """ self.write(dac, self.voltage_to_mu(voltage))
[docs] @kernel def set_group(self, dac, voltage): """Set DAC group data to given voltage. :param dac: DAC channel (0-31). :param voltage: Desired output voltage. """ data = [int32(0)] * (len(voltage) // 2) self.voltage_group_to_mu(voltage, data) self.set_group_mu(dac, data)
[docs] @kernel def update(self, update): """Schedule channels for update. :param update: Bit mask of channels to update (32 bit). """ self.write(0x20, update)
[docs] @kernel def set_hold(self, hold): """Set channels to manual update. :param hold: Bit mask of channels to hold (32 bit). """ self.write(0x21, hold)
[docs] @kernel def set_cfg(self, reset=0, afe_power_down=0, dac_clr=0, clr_err=0): """Set configuration bits. :param reset: Reset SPI PLL and SPI clock domain. :param afe_power_down: Disable AFE power. :param dac_clr: Assert all 32 DAC_CLR signals setting all DACs to mid-scale (0 V). :param clr_err: Clear error counters and PLL reset indicator. This clears the sticky red error LED. Must be cleared to enable error counting. """ self.write(0x22, (reset << 0) | (afe_power_down << 1) | (dac_clr << 2) | (clr_err << 3))
[docs] @kernel def set_leds(self, leds): """Set the green user-defined LEDs :param leds: LED status, 8 bit integer each bit corresponding to one green LED. """ self.write(0x23, leds)
[docs] @kernel def set_continuous(self, channel_mask): """Enable continuous DAC updates on channels regardless of new data being submitted. """ self.write(0x25, channel_mask)
[docs] @kernel def stage_cic_mu(self, rate_mantissa, rate_exponent, gain_exponent): """Stage machine unit CIC interpolator configuration. """ if rate_mantissa < 0 or rate_mantissa >= 1 << 6: raise ValueError("rate_mantissa out of bounds") if rate_exponent < 0 or rate_exponent >= 1 << 4: raise ValueError("rate_exponent out of bounds") if gain_exponent < 0 or gain_exponent >= 1 << 6: raise ValueError("gain_exponent out of bounds") config = rate_mantissa | (rate_exponent << 6) | (gain_exponent << 10) self.write(0x26, config)
[docs] @kernel def stage_cic(self, rate) -> TInt32: """Compute and stage interpolator configuration. This method approximates the desired interpolation rate using a 10 bit floating point representation (6 bit mantissa, 4 bit exponent) and then determines an optimal interpolation gain compensation exponent to avoid clipping. Gains for rates that are powers of two are accurately compensated. Other rates lead to overall less than unity gain (but more than 0.5 gain). The overall gain including gain compensation is `actual_rate**order/2**ceil(log2(actual_rate**order))` where `order = 3`. Returns the actual interpolation rate. """ if rate <= 0 or rate > 1 << 16: raise ValueError("rate out of bounds") rate_mantissa = rate rate_exponent = 0 while rate_mantissa > 1 << 6: rate_exponent += 1 rate_mantissa >>= 1 order = 3 gain = 1 for i in range(order): gain *= rate_mantissa gain_exponent = 0 while gain > 1 << gain_exponent: gain_exponent += 1 gain_exponent += order*rate_exponent assert gain_exponent <= order*16 self.stage_cic_mu(rate_mantissa - 1, rate_exponent, gain_exponent) return rate_mantissa << rate_exponent
[docs] @kernel def apply_cic(self, channel_mask): """Apply the staged interpolator configuration on the specified channels. Each Fastino channel includes a fourth order (cubic) CIC interpolator with variable rate change and variable output gain compensation (see :meth:`stage_cic`). Channels using non-unity interpolation rate should have continous DAC updates enabled (see :meth:`set_continuous`) unless their output is supposed to be constant. This method resets and settles the affected interpolators. There will be no output updates for the next `order = 3` input samples. Affected channels will only accept one input sample per input sample period. This method synchronizes the input sample period to the current frame on the affected channels. If application of new interpolator settings results in a change of the overall gain, there will be a corresponding output step. """ self.write(0x27, channel_mask)