From 2c4e5bfee46b0e49eb6310f682db91fd77709814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 16 Sep 2019 17:28:36 +0000 Subject: [PATCH] fastino: add [WIP] --- artiq/coredevice/fastino.py | 124 +++++++++++++++ artiq/frontend/artiq_ddb_template.py | 12 ++ artiq/gateware/eem.py | 24 ++- artiq/gateware/rtio/phy/fastino.py | 199 ++++++++++++++++++++++++ artiq/gateware/targets/kasli_generic.py | 7 + doc/manual/core_drivers_reference.rst | 6 + 6 files changed, 371 insertions(+), 1 deletion(-) create mode 100644 artiq/coredevice/fastino.py create mode 100644 artiq/gateware/rtio/phy/fastino.py diff --git a/artiq/coredevice/fastino.py b/artiq/coredevice/fastino.py new file mode 100644 index 000000000..30c6bca54 --- /dev/null +++ b/artiq/coredevice/fastino.py @@ -0,0 +1,124 @@ +"""RTIO driver for the Fastino 32channel, 16 bit, 2.5 MS/s per channel, +streaming DAC. + +TODO: Example, describe update/hold +""" + +from artiq.language.core import kernel, portable +from artiq.coredevice.rtio import rtio_output, rtio_input_data +from artiq.language.units import us + + +class Fastino: + """Fastino 32-channel, 16-bit, 2.5 MS/s per channel streaming DAC + + :param channel: RTIO channel number + :param core_device: Core device name (default: "core") + """ + + kernel_invariants = {"core", "channel"} + + def __init__(self, dmgr, channel, core_device="core"): + self.channel = channel << 8 + self.core = dmgr.get(core_device) + + @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(0x8) + delay(1*us) + self.set_cfg(0x0) + delay(1*us) + + @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) + + @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) + + @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) + + @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. + """ + return int(round((0x8000/10.)*voltage)) + 0x8000 + + @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)) + + @kernel + def update(self, update): + """Schedule channels for update. + + :param update: Bit mask of channels to update (32 bit). + """ + self.write(0x20, update) + + @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) + + @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_pwr_disable << 1) | + (dac_clr << 2) | (clr_err << 3)) + + @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) diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 114cd2d79..57bddf32c 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -473,6 +473,18 @@ class PeripheralManager: channel=rtio_offset) return 2 + def process_fastino(self, rtio_offset, peripheral): + self.gen(""" + device_db["{name}"] = {{ + "type": "local", + "module": "artiq.coredevice.fastino", + "class": "Fastino", + "arguments": {{"channel": 0x{channel:06x}}} + }}""", + name=self.get_name("fastino"), + channel=rtio_offset) + return 1 + def process(self, rtio_offset, peripheral): processor = getattr(self, "process_"+str(peripheral["type"])) return processor(rtio_offset, peripheral) diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 84f4526ce..0c8bb2372 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -5,7 +5,7 @@ from migen.genlib.io import DifferentialOutput from artiq.gateware import rtio from artiq.gateware.rtio.phy import spi2, ad53xx_monitor, grabber from artiq.gateware.suservo import servo, pads as servo_pads -from artiq.gateware.rtio.phy import servo as rtservo +from artiq.gateware.rtio.phy import servo as rtservo, fastino def _eem_signal(i): @@ -603,3 +603,25 @@ class Mirny(_EEM): phy = ttl_out_cls(pads.p, pads.n) target.submodules += phy target.rtio_channels.append(rtio.Channel.from_phy(phy)) + + +class Fastino(_EEM): + @staticmethod + def io(eem, iostandard="LVDS_25"): + return [ + ("fastino{}_ser_{}".format(eem, pol), 0, + Subsignal("clk", Pins(_eem_pin(eem, 0, pol))), + Subsignal("mosi", Pins(*(_eem_pin(eem, i, pol) + for i in range(1, 7)))), + Subsignal("miso", Pins(_eem_pin(eem, 7, pol))), + IOStandard(iostandard), + ) for pol in "pn"] + + @classmethod + def add_std(cls, target, eem, iostandard="LVDS_25"): + 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.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 new file mode 100644 index 000000000..4c95568f3 --- /dev/null +++ b/artiq/gateware/rtio/phy/fastino.py @@ -0,0 +1,199 @@ +from migen import * +from migen.genlib.cdc import MultiReg +from migen.genlib.io import DifferentialOutput, DifferentialInput, DDROutput +from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine + +from artiq.gateware.rtio import rtlink + + +class SerDes(Module): + def transpose(self, i, n): + # i is n,m c-contiguous + # o is m,n c-contiguous + m = len(i)//n + assert n*m == len(i) + + def __init__(self, pins, pins_n): + n_bits = 16 # bits per dac data word + n_channels = 32 # channels per fastino + n_div = 7 # bits per lane and word + assert n_div == 7 + n_frame = 14 # word per frame + n_lanes = len(pins.mosi) # number of data lanes + n_checksum = 12 # checksum bits + n_addr = 4 # readback address bits + n_word = n_lanes*n_div + n_body = n_word*n_frame - (n_frame//2 + 1) - n_checksum + + # dac data words + self.dacs = [Signal(n_bits) for i in range(n_channels)] + # dac update enable + self.enable = Signal(n_channels) + # configuration word + self.cfg = Signal(20) + # readback data + self.dat_r = Signal(n_frame//2*(1 << n_addr)) + # data load synchronization event + self.stb = Signal() + + # # # + + # crc-12 telco + self.submodules.crc = LiteEthMACCRCEngine( + data_width=2*n_lanes, width=n_checksum, polynom=0x80f) + + addr = Signal(4) + body_ = Cat(self.cfg, addr, self.enable, self.dacs) + assert len(body_) == n_body + body = Signal(n_body) + self.comb += body.eq(body_) + + words_ = [] + j = 0 + for i in range(n_frame): # iterate over words + if i == 0: # data and checksum + k = n_word - n_checksum + elif i == 1: # marker + words_.append(C(1)) + k = n_word - 1 + elif i < n_frame//2 + 2: # marker + words_.append(C(0)) + k = n_word - 1 + else: # full word + k = n_word + # append corresponding frame body bits + words_.append(body[j:j + k]) + j += k + words_ = Cat(words_) + assert len(words_) == n_frame*n_word - n_checksum + words = Signal(len(words_)) + self.comb += words.eq(words_) + + clk = Signal(n_div, reset=0b1100011) + clk_stb = Signal() + i_frame = Signal(max=n_div*n_frame//2) # DDR + frame_stb = Signal() + sr = [Signal(n_frame*n_div - n_checksum//n_lanes, reset_less=True) + for i in range(n_lanes)] + assert len(Cat(sr)) == len(words) + # DDR bits for each register + ddr_data = Cat([sri[-2] for sri in sr], [sri[-1] for sri in sr]) + self.comb += [ + # assert one cycle ahead + clk_stb.eq(~clk[0] & clk[-1]), + # double period because of DDR + frame_stb.eq(i_frame == n_div*n_frame//2 - 1), + + # LiteETHMACCRCEngine takes data LSB first + self.crc.data[::-1].eq(ddr_data), + self.stb.eq(frame_stb & clk_stb), + ] + miso = Signal() + miso_sr = Signal(n_frame, reset_less=True) + self.sync.rio_phy += [ + # shift 7 bit clock pattern by two bits each DDR cycle + clk.eq(Cat(clk[-2:], clk)), + [sri[2:].eq(sri) for sri in sr], + self.crc.last.eq(self.crc.next), + If(clk[:2] == 0, # TODO: tweak MISO sampling + miso_sr.eq(Cat(miso, miso_sr)), + ), + If(~frame_stb, + i_frame.eq(i_frame + 1), + ), + If(frame_stb & clk_stb, + i_frame.eq(0), + self.crc.last.eq(0), + # transpose, load + Cat(sr).eq(Cat(words[mm::n_lanes] for mm in range(n_lanes))), + Array([self.dat_r[i*n_frame//2:(i + 1)*n_frame//2] + for i in range(1 << len(addr))])[addr].eq(miso_sr), + addr.eq(addr + 1), + ), + If(i_frame == n_div*n_frame//2 - 2, + # inject crc + ddr_data.eq(self.crc.next), + ), + ] + + clk_ddr = Signal() + miso0 = Signal() + self.specials += [ + DDROutput(clk[-1], clk[-2], clk_ddr, ClockSignal("rio_phy")), + DifferentialOutput(clk_ddr, pins.clk, pins_n.clk), + DifferentialInput(pins.miso, pins_n.miso, miso0), + MultiReg(miso0, miso, "rio_phy"), + ] + for sri, ddr, mp, mn in zip( + sr, Signal(n_lanes), pins.mosi, pins_n.mosi): + self.specials += [ + DDROutput(sri[-1], sri[-2], ddr, ClockSignal("rio_phy")), + DifferentialOutput(ddr, mp, mn), + ] + + +class Fastino(Module): + def __init__(self, pins, pins_n): + self.rtlink = rtlink.Interface( + rtlink.OInterface(data_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 + # 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, + # at the cost of more DMA and RTIO data ((n*(32+32+64) vs + # 32+32*16+64)) + + hold = Signal.like(self.serializer.enable) + + # TODO: stb, timestamp + read_regs = Array([ + self.serializer.dat_r[i*7:(i + 1)*7] + for i in range(1 << 4) + ]) + + cases = { + # update + 0x20: self.serializer.enable.eq(self.serializer.enable | self.rtlink.o.data), + # hold + 0x21: hold.eq(self.rtlink.o.data), + # cfg + 0x22: self.serializer.cfg[:4].eq(self.rtlink.o.data), + # leds + 0x23: self.serializer.cfg[4:12].eq(self.rtlink.o.data), + # reserved + 0x24: self.serializer.cfg[12:].eq(self.rtlink.o.data), + } + for i in range(len(self.serializer.dacs)): + cases[i] = [ + self.serializer.dacs[i].eq(self.rtlink.o.data), + If(~hold[i], + self.serializer.enable[i].eq(1), + ) + ] + + self.sync.rio_phy += [ + If(self.serializer.stb, + self.serializer.enable.eq(0), + ), + If(self.rtlink.o.stb & ~self.rtlink.o.address[-1], + Case(self.rtlink.o.address[:-1], cases), + ), + ] + + self.sync.rtio += [ + self.rtlink.i.stb.eq(self.rtlink.o.stb & + self.rtlink.o.address[-1]), + self.rtlink.i.data.eq( + read_regs[self.rtlink.o.address[:-1]]), + ] diff --git a/artiq/gateware/targets/kasli_generic.py b/artiq/gateware/targets/kasli_generic.py index 2f593459a..477fecf16 100755 --- a/artiq/gateware/targets/kasli_generic.py +++ b/artiq/gateware/targets/kasli_generic.py @@ -105,6 +105,12 @@ def peripheral_mirny(module, peripheral): eem.Mirny.add_std(module, peripheral["ports"][0], ttl_serdes_7series.Output_8X) + +def peripheral_fastino(module, peripheral): + if len(peripheral["ports"]) != 1: + raise ValueError("wrong number of ports") + eem.Fastino.add_std(module, peripheral["ports"][0]) + peripheral_processors = { "dio": peripheral_dio, @@ -115,6 +121,7 @@ peripheral_processors = { "zotino": peripheral_zotino, "grabber": peripheral_grabber, "mirny": peripheral_mirny, + "fastino": peripheral_fastino, } diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index 17e4909c9..b0026b6ac 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -158,6 +158,12 @@ DAC/ADC drivers .. automodule:: artiq.coredevice.novogorny :members: +:mod:`artiq.coredevice.fastino` module +++++++++++++++++++++++++++++++++++++++++ + +.. automodule:: artiq.coredevice.fastino + :members: + Miscellaneous -------------