diff --git a/artiq/coredevice/fastino.py b/artiq/coredevice/fastino.py index baa01529f..32306388a 100644 --- a/artiq/coredevice/fastino.py +++ b/artiq/coredevice/fastino.py @@ -5,8 +5,9 @@ TODO: Example, describe update/hold """ from artiq.language.core import kernel, portable, delay -from artiq.coredevice.rtio import rtio_output, rtio_input_data +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, TFloat class Fastino: @@ -14,13 +15,19 @@ class Fastino: :param channel: RTIO channel number :param core_device: Core device name (default: "core") + :param log2_width: Width of DAC channel group (power of two, + see the RTIO PHY for details). If 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. Value must match the corresponding value + in the RTIO PHY. """ + kernel_invariants = {"core", "channel", "width"} - kernel_invariants = {"core", "channel"} - - def __init__(self, dmgr, channel, core_device="core"): + 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 @kernel def init(self): @@ -65,6 +72,21 @@ class Fastino: """ self.write(dac, data) + @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) + @portable def voltage_to_mu(self, voltage): """Convert SI Volts to DAC machine units. @@ -74,6 +96,20 @@ class Fastino: """ return int(round((0x8000/10.)*voltage)) + 0x8000 + @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] = v + @kernel def set_dac(self, dac, voltage): """Set DAC data to given voltage. @@ -83,6 +119,17 @@ class Fastino: """ self.write(dac, self.voltage_to_mu(voltage)) + @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) + @kernel def update(self, update): """Schedule channels for update. diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 0c8bb2372..fba2c1b5a 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -622,6 +622,7 @@ class Fastino(_EEM): cls.add_extension(target, eem, iostandard=iostandard) phy = fastino.Fastino(target.platform.request("fastino{}_ser_p".format(eem)), - target.platform.request("fastino{}_ser_n".format(eem))) + target.platform.request("fastino{}_ser_n".format(eem)), + log2_width=0) target.submodules += phy target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) diff --git a/artiq/gateware/rtio/phy/fastino.py b/artiq/gateware/rtio/phy/fastino.py index 4c95568f3..14d9d9cf1 100644 --- a/artiq/gateware/rtio/phy/fastino.py +++ b/artiq/gateware/rtio/phy/fastino.py @@ -133,26 +133,36 @@ class SerDes(Module): class Fastino(Module): - def __init__(self, pins, pins_n): + def __init__(self, pins, pins_n, log2_width=0): + width = 1 << log2_width self.rtlink = rtlink.Interface( - rtlink.OInterface(data_width=32, address_width=8, + rtlink.OInterface(data_width=max(16*width, 32), + address_width=8, enable_replace=False), rtlink.IInterface(data_width=32)) self.submodules.serializer = SerDes(pins, pins_n) # Support staging DAC data (in `dacs`) by writing to the - # 32 DAC RTIO addresses, if a channel is not "held" by its + # DAC RTIO addresses, if a channel is not "held" by its # bit in `hold` the next frame will contain the update. # For the DACs held, the update is triggered by setting the # corresponding bit in `update`. Update is self-clearing. # This enables atomic DAC updates synchronized to a frame edge. # - # This RTIO layout enables narrow RTIO words (32 bit - # compared to 512), efficient few-channel updates, - # least amount of DAC state tracking in kernels, + # 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 compared to 512) 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 ((n*(32+32+64) vs # 32+32*16+64)) + # + # 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. hold = Signal.like(self.serializer.enable) @@ -174,12 +184,12 @@ class Fastino(Module): # reserved 0x24: self.serializer.cfg[12:].eq(self.rtlink.o.data), } - for i in range(len(self.serializer.dacs)): + for i in range(0, len(self.serializer.dacs), width): cases[i] = [ - self.serializer.dacs[i].eq(self.rtlink.o.data), - If(~hold[i], - self.serializer.enable[i].eq(1), - ) + Cat(self.serializer.dacs[i:i + width]).eq(self.rtlink.o.data), + [If(~hold[i + j], + self.serializer.enable[i + j].eq(1), + ) for j in range(width)] ] self.sync.rio_phy += [