diff --git a/artiq/coredevice/pdq.py b/artiq/coredevice/pdq.py new file mode 100644 index 000000000..b5ea70648 --- /dev/null +++ b/artiq/coredevice/pdq.py @@ -0,0 +1,201 @@ +from artiq.language.core import kernel, portable, delay_mu +from artiq.coredevice import spi + + +_PDQ_SPI_CONFIG = ( + 0*spi.SPI_OFFLINE | 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 + ) + + +@portable +def _PDQ_CMD(board, is_mem, adr, we): + """Pack PDQ command fields into command byte. + + :param board: Board address, 0 to 15, with ``15 = 0xf`` denoting broadcast + to all boards connected. + :param is_mem: If ``1``, ``adr`` denote the address of the memory to access + (0 to 2). Otherwise ``adr`` denotes the register to access. + :param adr: Address of the register or memory to access. + (``_PDQ_ADR_CONFIG``, ``_PDQ_ADR_FRAME``, ``_PDQ_ADR_CRC``). + :param we: If ``1`` then write, otherwise read. + """ + return (adr << 0) | (is_mem << 2) | (board << 3) | (we << 7) + + +_PDQ_ADR_CONFIG = 0 +_PDQ_ADR_CRC = 1 +_PDQ_ADR_FRAME = 2 + + +class PDQ: + """PDQ smart arbitrary waveform generator stack. + + Provides access to a stack of PDQ boards connected via SPI using PDQ + gateware version 3 or later. + + The SPI bus is wired with ``CS_N`` from the core device connected to + ``F2 IN`` on the master PDQ, ``CLK`` connected to ``F3 IN``, ``MOSI`` + connected to ``F4 IN`` and ``MISO`` (optionally) connected to ``F5 OUT``. + ``F1 TTL Input Trigger`` remains as waveform trigger input. + Due to hardware constraints, there can only be one board connected to the + core device's MISO line and therefore there can only be SPI readback + from one board at any time. + + :param spi_device: Name of the SPI bus this device is on. + :param chip_select: Value to drive on the chip select lines of the SPI bus + during transactions. + """ + + kernel_invariants = {"core", "chip_select", "bus"} + + def __init__(self, dmgr, spi_device, chip_select=1): + self.core = dmgr.get("core") + self.bus = dmgr.get(spi_device) + self.chip_select = chip_select + + @kernel + def setup_bus(self, write_div=24, read_div=64): + """Configure the SPI bus and the SPI transaction parameters + for this device. This method has to be called before any other method + if the bus has been used to access a different device in the meantime. + + This method advances the timeline by the duration of two + RTIO-to-Wishbone bus transactions. + + :param write_div: Write clock divider. + :param read_div: Read clock divider. + """ + # write: 4*8ns >= 20ns = 2*clk (clock de-glitching 50MHz) + # read: 15*8*ns >= ~100ns = 5*clk (clk de-glitching latency + miso + # latency) + self.bus.set_config_mu(_PDQ_SPI_CONFIG, write_div, read_div) + self.bus.set_xfer(self.chip_select, 16, 0) + + @kernel + def write_reg(self, adr, data, board): + """Set a PDQ register. + + :param adr: Address of the register (``_PDQ_ADR_CONFIG``, + ``_PDQ_ADR_FRAME``, ``_PDQ_ADR_CRC``). + :param data: Register data (8 bit). + :param board: Board to access, ``0xf`` to write to all boards. + """ + self.bus.write((_PDQ_CMD(board, 0, adr, 1) << 24) | (data << 16)) + delay_mu(self.bus.ref_period_mu) # get to 20ns min cs high + + @kernel + def read_reg(self, adr, board): + """Get a PDQ register. + + :param adr: Address of the register (``_PDQ_ADR_CONFIG``, + ``_PDQ_ADR_FRAME``, ``_PDQ_ADR_CRC``). + :param board: Board to access, ``0xf`` to write to all boards. + + :return: Register data (8 bit). + """ + self.bus.set_xfer(self.chip_select, 16, 8) + self.bus.write(_PDQ_CMD(board, 0, adr, 0) << 24) + delay_mu(self.bus.ref_period_mu) # get to 20ns min cs high + self.bus.read_async() + self.bus.set_xfer(self.chip_select, 16, 0) + return int(self.bus.input_async() & 0xff) # FIXME: m-labs/artiq#713 + + @kernel + def write_config(self, reset=0, clk2x=0, enable=1, + trigger=0, aux_miso=0, aux_dac=0b111, board=0xf): + """Set configuration register. + + :param reset: Reset board (auto-clear). + :param clk2x: Enable clock double (100 MHz). + :param enable: Enable the reading and execution of waveform data from + memory. + :param trigger: Software trigger, logical OR with ``F1 TTL Input + Trigger``. + :param aux_miso: Use ``F5 OUT`` for ``MISO``. If ``0``, use the + masked logical OR of the DAC channels. + :param aux_dac: DAC channel mask to for AUX (``F5 OUT``) output. + :param board: Boards to address, ``0xf`` to write to all boards. + """ + config = ((reset << 0) | (clk2x << 1) | (enable << 2) | + (trigger << 3) | (aux_miso << 4) | (aux_dac << 5)) + self.write_reg(_PDQ_ADR_CONFIG, config, board) + + @kernel + def read_config(self, board=0xf): + """Read configuration register.""" + return self.read_reg(_PDQ_ADR_CONFIG, board) + + @kernel + def write_crc(self, crc, board=0xf): + """Write checksum register.""" + self.write_reg(_PDQ_ADR_CRC, crc, board) + + @kernel + def read_crc(self, board=0xf): + """Read checksum register.""" + return self.read_reg(_PDQ_ADR_CRC, board) + + @kernel + def write_frame(self, frame, board=0xf): + """Write frame selection register.""" + self.write_reg(_PDQ_ADR_FRAME, frame, board) + + @kernel + def read_frame(self, board=0xf): + """Read frame selection register.""" + return self.read_reg(_PDQ_ADR_FRAME, board) + + @kernel + def write_mem(self, mem, adr, data, board=0xf): # FIXME: m-labs/artiq#714 + """Write to DAC channel waveform data memory. + + :param mem: DAC channel memory to access (0 to 2). + :param adr: Start address. + :param data: Memory data. List of 16 bit integers. + :param board: Board to access (0-15) with ``0xf = 15`` being broadcast + to all boards. + """ + self.bus.set_xfer(self.chip_select, 24, 0) + self.bus.write((_PDQ_CMD(board, 1, mem, 1) << 24) | + ((adr & 0x00ff) << 16) | (adr & 0xff00)) + delay_mu(-self.bus.write_period_mu-3*self.bus.ref_period_mu) + self.bus.set_xfer(self.chip_select, 16, 0) + for i in data: + self.bus.write(i << 16) + delay_mu(-self.bus.write_period_mu) + delay_mu(self.bus.write_period_mu + self.bus.ref_period_mu) + # get to 20ns min cs high + + @kernel + def read_mem(self, mem, adr, data, board=0xf, buffer=8): + """Read from DAC channel waveform data memory. + + :param mem: DAC channel memory to access (0 to 2). + :param adr: Start address. + :param data: Memory data. List of 16 bit integers. + :param board: Board to access (0-15) with ``0xf = 15`` being broadcast + to all boards. + """ + n = len(data) + if not n: + return + self.bus.set_xfer(self.chip_select, 24, 8) + self.bus.write((_PDQ_CMD(board, 1, mem, 0) << 24) | + ((adr & 0x00ff) << 16) | (adr & 0xff00)) + delay_mu(-self.bus.write_period_mu-3*self.bus.ref_period_mu) + self.bus.set_xfer(self.chip_select, 0, 16) + for i in range(n): + self.bus.write(0) + delay_mu(-self.bus.write_period_mu) + if i > 0: + delay_mu(-3*self.bus.ref_period_mu) + self.bus.read_async() + if i > buffer: + data[i - 1 - buffer] = self.bus.input_async() & 0xffff + delay_mu(self.bus.write_period_mu) + self.bus.set_xfer(self.chip_select, 16, 0) + self.bus.read_async() + for i in range(max(0, n - buffer - 1), n): + data[i] = self.bus.input_async() & 0xffff diff --git a/artiq/coredevice/spi.py b/artiq/coredevice/spi.py index 65943533f..6c4b3e7ea 100644 --- a/artiq/coredevice/spi.py +++ b/artiq/coredevice/spi.py @@ -1,6 +1,6 @@ import numpy -from artiq.language.core import (kernel, portable, now_mu, delay_mu) +from artiq.language.core import kernel, portable, now_mu, delay_mu from artiq.language.units import MHz from artiq.coredevice.rtio import rtio_output, rtio_input_data @@ -56,9 +56,14 @@ class SPIMaster: :param channel: RTIO channel number of the SPI bus to control. """ + + kernel_invariants = {"core", "ref_period_mu", "channel"} + def __init__(self, dmgr, channel, core_device="core"): self.core = dmgr.get(core_device) - self.ref_period_mu = self.core.seconds_to_mu(self.core.coarse_ref_period) + self.ref_period_mu = self.core.seconds_to_mu( + self.core.coarse_ref_period) + assert self.ref_period_mu == self.core.ref_multiplier self.channel = channel self.write_period_mu = numpy.int64(0) self.read_period_mu = numpy.int64(0) diff --git a/artiq/gateware/targets/kc705_sma_spi.py b/artiq/gateware/targets/kc705_sma_spi.py new file mode 100644 index 000000000..cea74c574 --- /dev/null +++ b/artiq/gateware/targets/kc705_sma_spi.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 + +import argparse + +from migen import * + +from migen.build.generic_platform import * +from migen.genlib.resetsync import AsyncResetSynchronizer +from migen.genlib.cdc import MultiReg +from misoc.targets.kc705 import soc_kc705_args, soc_kc705_argdict +from misoc.integration.builder import builder_args, builder_argdict +from misoc.interconnect.csr import * + +from artiq.gateware.amp import build_artiq_soc +from artiq.gateware import rtio +from artiq.gateware.rtio.phy import ttl_simple, spi + + +from .kc705_dds import _NIST_Ions + + +class _RTIOCRG(Module, AutoCSR): + def __init__(self, platform, rtio_internal_clk): + self._clock_sel = CSRStorage() + self._pll_reset = CSRStorage(reset=1) + self._pll_locked = CSRStatus() + self.clock_domains.cd_rtio = ClockDomain() + self.clock_domains.cd_rtiox4 = ClockDomain(reset_less=True) + + # 10 MHz when using 125MHz input + self.clock_domains.cd_ext_clkout = ClockDomain(reset_less=True) + + rtio_external_clk = Signal() + + pll_locked = Signal() + rtio_clk = Signal() + rtiox4_clk = Signal() + ext_clkout_clk = Signal() + self.specials += [ + Instance("PLLE2_ADV", + p_STARTUP_WAIT="FALSE", o_LOCKED=pll_locked, + + p_REF_JITTER1=0.01, + p_CLKIN1_PERIOD=8.0, p_CLKIN2_PERIOD=8.0, + i_CLKIN1=rtio_internal_clk, i_CLKIN2=rtio_external_clk, + # Warning: CLKINSEL=0 means CLKIN2 is selected + i_CLKINSEL=~self._clock_sel.storage, + + # VCO @ 1GHz when using 125MHz input + p_CLKFBOUT_MULT=8, p_DIVCLK_DIVIDE=1, + i_CLKFBIN=self.cd_rtio.clk, + i_RST=self._pll_reset.storage, + + o_CLKFBOUT=rtio_clk, + + p_CLKOUT0_DIVIDE=2, p_CLKOUT0_PHASE=0.0, + o_CLKOUT0=rtiox4_clk, + + p_CLKOUT1_DIVIDE=50, p_CLKOUT1_PHASE=0.0, + o_CLKOUT1=ext_clkout_clk), + Instance("BUFG", i_I=rtio_clk, o_O=self.cd_rtio.clk), + Instance("BUFG", i_I=rtiox4_clk, o_O=self.cd_rtiox4.clk), + Instance("BUFG", i_I=ext_clkout_clk, o_O=self.cd_ext_clkout.clk), + + AsyncResetSynchronizer(self.cd_rtio, ~pll_locked), + MultiReg(pll_locked, self._pll_locked.status) + ] + + +_sma_spi = [ + ("sma_spi", 0, + Subsignal("clk", Pins("Y23")), # user_sma_gpio_p + Subsignal("cs_n", Pins("Y24")), # user_sma_gpio_n + Subsignal("mosi", Pins("L25")), # user_sma_clk_p + Subsignal("miso", Pins("K25")), # user_sma_clk_n + IOStandard("LVCMOS25")), +] + + +class SMA_SPI(_NIST_Ions): + """ + SPI on 4 SMA for PDQ2 test/demo. + """ + def __init__(self, cpu_type="or1k", **kwargs): + _NIST_Ions.__init__(self, cpu_type, **kwargs) + + platform = self.platform + self.platform.add_extension(_sma_spi) + + rtio_channels = [] + + phy = ttl_simple.Output(platform.request("user_led", 2)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) + + ams101_dac = self.platform.request("ams101_dac", 0) + phy = ttl_simple.Output(ams101_dac.ldac) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) + + phy = spi.SPIMaster(ams101_dac) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy( + phy, ofifo_depth=4, ififo_depth=4)) + + phy = spi.SPIMaster(self.platform.request("sma_spi")) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy( + phy, ofifo_depth=128, ififo_depth=128)) + + self.config["HAS_RTIO_LOG"] = None + self.config["RTIO_LOG_CHANNEL"] = len(rtio_channels) + rtio_channels.append(rtio.LogChannel()) + + self.add_rtio(rtio_channels) + + def add_rtio(self, rtio_channels): + self.submodules.rtio_crg = _RTIOCRG(self.platform, self.crg.cd_sys.clk) + self.csr_devices.append("rtio_crg") + self.submodules.rtio_core = rtio.Core(rtio_channels) + self.csr_devices.append("rtio_core") + self.submodules.rtio = rtio.KernelInitiator() + self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")( + rtio.DMA(self.get_native_sdram_if())) + self.register_kernel_cpu_csrdevice("rtio") + self.register_kernel_cpu_csrdevice("rtio_dma") + self.submodules.cri_con = rtio.CRIInterconnectShared( + [self.rtio.cri, self.rtio_dma.cri], + [self.rtio_core.cri]) + self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) + self.csr_devices.append("rtio_moninj") + + self.rtio_crg.cd_rtio.clk.attr.add("keep") + self.platform.add_period_constraint(self.rtio_crg.cd_rtio.clk, 8.) + self.platform.add_false_path_constraints( + self.crg.cd_sys.clk, + self.rtio_crg.cd_rtio.clk) + + self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio_core.cri, + self.get_native_sdram_if()) + self.csr_devices.append("rtio_analyzer") + + +def main(): + parser = argparse.ArgumentParser( + description="ARTIQ device binary builder / " + "KC705 SMA SPI demo/test for PDQ2") + builder_args(parser) + soc_kc705_args(parser) + args = parser.parse_args() + + soc = SMA_SPI(**soc_kc705_argdict(args)) + build_artiq_soc(soc, builder_argdict(args)) + + +if __name__ == "__main__": + main() diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index 6def86ae8..bbd0e39c6 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -68,3 +68,9 @@ These drivers are for the core device and the peripherals closely integrated int .. automodule:: artiq.coredevice.sawg :members: + +:mod:`artiq.coredevice.pdq` module +----------------------------------- + +.. automodule:: artiq.coredevice.pdq + :members: