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:
Robert Jördens 2017-05-12 11:46:45 +02:00
commit 170d2886fd
4 changed files with 371 additions and 2 deletions

201
artiq/coredevice/pdq.py Normal file
View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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: