forked from M-Labs/artiq
Merge branch 'pdq'
* pdq: pdq: documentation pdq2 -> pdq pdq2: use 16 bit data, buffered read_mem() spi: style pdq2: mem_read pdq2: align subsequent writes to end sma_spi: undo cri_con pdq2: memory write, kernel_invariants sma_spi: cri/cd changes sma_spi: LVCMOS25 coredevice.spi: kernel invariants and style sma_spi: free up user_sma pins sma_spi: add demo target with SPI on four SMA pdq2: memory write pdq2: crc/frame register accessors doc: pdq2 spi backend pdq2: config writes
This commit is contained in:
commit
170d2886fd
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue