From 37a0d6580bb545c88ce10713152f699adb6ea556 Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Wed, 21 Feb 2018 13:34:45 +0000 Subject: [PATCH] spi2: add RTIO gateware and coredevice driver https://github.com/m-labs/misoc/commit/1006218997c40d570686196cb8a25d01add8fab7 --- artiq/coredevice/spi2.py | 211 ++++++++++++++++++++++++++ artiq/gateware/rtio/phy/spi2.py | 116 ++++++++++++++ conda/artiq-dev/meta.yaml | 2 +- doc/manual/core_drivers_reference.rst | 6 + 4 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 artiq/coredevice/spi2.py create mode 100644 artiq/gateware/rtio/phy/spi2.py diff --git a/artiq/coredevice/spi2.py b/artiq/coredevice/spi2.py new file mode 100644 index 000000000..8e5604703 --- /dev/null +++ b/artiq/coredevice/spi2.py @@ -0,0 +1,211 @@ +""" +Driver for generic SPI on RTIO. + +This ARTIQ coredevice driver corresponds to the "new" MiSoC SPI core (v2). + +Output event replacement is not supported and issuing commands at the same +time is an error. +""" + +from numpy import int64 +from artiq.language.core import syscall, kernel, portable, now_mu, delay_mu +from artiq.language.types import TInt32, TNone +from artiq.coredevice.rtio import rtio_output, rtio_input_data + + +__all__ = [ + "SPI_DATA_ADDR", "SPI_CONFIG_ADDR", + "SPI_OFFLINE", "SPI_END", "SPI_INPUT", + "SPI_CS_POLARITY", "SPI_CLK_POLARITY", "SPI_CLK_PHASE", + "SPI_LSB_FIRST", "SPI_HALF_DUPLEX", + "SPIMaster", "NRTSPIMaster" +] + +SPI_DATA_ADDR = 0 +SPI_CONFIG_ADDR = 1 + +SPI_OFFLINE = 0x01 +SPI_END = 0x02 +SPI_INPUT = 0x04 +SPI_CS_POLARITY = 0x08 +SPI_CLK_POLARITY = 0x10 +SPI_CLK_PHASE = 0x20 +SPI_LSB_FIRST = 0x40 +SPI_HALF_DUPLEX = 0x80 + + +class SPIMaster: + """Core device Serial Peripheral Interface (SPI) bus master. + + Owns one SPI bus. + + This ARTIQ coredevice driver corresponds to the "new" MiSoC SPI core (v2). + + **Transfer Sequence**: + + * If necessary, set the ``config`` register (:meth:`set_config` and + :meth:`set_config_mu`) to activate and configure the core and to set + various transfer parameters like transfer length, clock divider, + and chip selects. + * :meth:`write` to the ``data`` register. Writing starts the transfer. + * If the transfer included submitting the SPI input data as an RTIO input + event (``SPI_INPUT`` set), then :meth:`read` the ``data``. + * If ``SPI_END`` was not set, repeat the transfer sequence. + + A **transaction** consists of one or more **transfers**. The chip select + pattern is asserted for the entire length of the transaction. All but the + last transfer are submitted with ``SPI_END`` cleared in the configuration + register. + + :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) + assert self.ref_period_mu == self.core.ref_multiplier + self.channel = channel + self.xfer_duration_mu = 2*self.ref_period_mu + + @portable + def frequency_to_div(self, f): + """Convert a SPI clock frequency to the closest SPI clock divider.""" + return int64(round( + 1/(f*self.core.mu_to_seconds(self.ref_period_mu)))) + + @kernel + def set_config(self, flags, length, freq, c): + """Set the configuration register. + + * If ``SPI_CS_POLARITY`` is cleared (``cs`` active low, the default), + "``cs`` all deasserted" means "all ``cs_n`` bits high". + * ``cs_n`` is not mandatory in the pads supplied to the gateware core. + Framing and chip selection can also be handled independently + through other means, e.g. ``TTLOut``. + * If there is a ``miso`` wire in the pads supplied in the gateware, + input and output may be two signals ("4-wire SPI"), + otherwise ``mosi`` must be used for both output and input + ("3-wire SPI") and ``SPI_HALF_DUPLEX`` must to be set + when reading data or when the slave drives the + ``mosi`` signal at any point. + * The first bit output on ``mosi`` is always the MSB/LSB (depending + on ``SPI_LSB_FIRST``) of the ``data`` written, independent of + the ``length`` of the transfer. The last bit input from ``miso`` + always ends up in the LSB/MSB (respectively) of the ``data`` read, + independent of the ``length`` of the transfer. + * ``cs`` is asserted at the beginning and deasserted at the end + of the transaction. + * ``cs`` handling is agnostic to whether it is one-hot or decoded + somewhere downstream. If it is decoded, "``cs`` all deasserted" + should be handled accordingly (no slave selected). + If it is one-hot, asserting multiple slaves should only be attempted + if ``miso`` is either not connected between slaves, or open + collector, or correctly multiplexed externally. + * Changes to the configuration register take effect on the start of the + next transfer with the exception of ``SPI_OFFLINE`` which takes + effect immediately. + * The SPI core can only be written to when it is idle or waiting + for the next transfer data. Writing (:meth:`set_config`, + :meth:`set_config_mu` or :meth:`write`) + when the core is busy will result in an RTIO busy error being logged. + + This method advances the timeline by one coarse RTIO clock cycle. + + **Configuration flags**: + + * :const:`SPI_OFFLINE`: all pins high-z (reset=1) + * :const:`SPI_END`: transfer in progress (reset=1) + * :const:`SPI_INPUT`: submit SPI read data as RTIO input event when + transfer is complete (reset=0) + * :const:`SPI_CS_POLARITY`: active level of ``cs_n`` (reset=0) + * :const:`SPI_CLK_POLARITY`: idle level of ``clk`` (reset=0) + * :const:`SPI_CLK_PHASE`: first edge after ``cs`` assertion to sample + data on (reset=0). In Motorola/Freescale SPI language + (:const:`SPI_CLK_POLARITY`, :const:`SPI_CLK_PHASE`) == (CPOL, CPHA): + + - (0, 0): idle low, output on falling, input on rising + - (0, 1): idle low, output on rising, input on falling + - (1, 0): idle high, output on rising, input on falling + - (1, 1): idle high, output on falling, input on rising + * :const:`SPI_LSB_FIRST`: LSB is the first bit on the wire (reset=0) + * :const:`SPI_HALF_DUPLEX`: 3-wire SPI, in/out on ``mosi`` (reset=0) + + :param flags: A bit map of `SPI_*` flags. + :param length: Number of bits to write during the next transfer. + (reset=1) + :param freq: Desired SPI clock frequency. (reset=f_rtio/2) + :param cs: Bit pattern of chip selects to assert. + Or number of the chip select to assert if ``cs`` is decoded + downstream. (reset=0) + """ + self.set_config_mu( + flags, length, self.frequency_to_div(write_freq), cs) + + @kernel + def set_config_mu(self, flags, length, div, cs): + """Set the ``config`` register (in SPI bus machine units). + + .. seealso:: :meth:`set_config` + + :param flags: A bit map of `SPI_*` flags. + :param length: Number of bits to write during the next transfer. + (reset=1) + :param div: Counter load value to divide the RTIO + clock by to generate the SPI clock. (minimum=2, reset=2) + ``f_rtio_clk/f_spi == div``. If ``div`` is odd, + the setup phase of the SPI clock is one coarse RTIO clock cycle + longer than the hold phase. + :param cs: Bit pattern of chip selects to assert. + Or number of the chip select to assert if ``cs`` is decoded + downstream. (reset=0) + """ + if length > 32 or length < 1: + raise ValueError("Invalid SPI transfer length") + if div > 257 or div < 2: + raise ValueError("Invalid SPI clock divider") + self.xfer_duration_mu = (length + 1)*div*self.ref_period_mu + rtio_output(now_mu(), self.channel, SPI_CONFIG_ADDR, flags | + ((length - 1) << 8) | ((div - 2) << 16) | (cs << 24)) + delay_mu(self.ref_period_mu) + + @kernel + def write(self, data): + """Write SPI data to shift register register and start transfer. + + * The ``data`` register and the shift register are 32 bits wide. + * Data writes take one ``ref_period`` cycle. + * A transaction consisting of a single transfer (``SPI_END``) takes + :attr:`xfer_duration_mu` ``=(n + 1)*div`` cycles RTIO time where + ``n`` is the number of bits and ``div`` is the SPI clock divider. + * Transfers in a multi-transfer transaction take up to one SPI clock + cycle less time depending on multiple parameters. Advanced users may + rewind the timeline appropriately to achieve faster multi-transfer + transactions. + * The SPI core will be busy for the duration of the SPI transfer. + * For bit alignment and bit ordering see :meth:`set_config`. + * The SPI core can only be written to when it is idle or waiting + for the next transfer data. Writing (:meth:`set_config`, + :meth:`set_config_mu` or :meth:`write`) + when the core is busy will result in an RTIO busy error being logged. + + This method advances the timeline by the duration of one + single-transfer SPI transaction (:attr:`xfer_duration_mu`). + + :param data: SPI output data to be written. + """ + rtio_output(now_mu(), self.channel, SPI_DATA_ADDR, data) + delay_mu(self.xfer_duration_mu) + + @kernel + def read(self): + """Read SPI data submitted by the SPI core. + + For bit alignment and bit ordering see :meth:`set_config`. + + This method does not alter the timeline. + + :return: SPI input data. + """ + return rtio_input_data(self.channel) diff --git a/artiq/gateware/rtio/phy/spi2.py b/artiq/gateware/rtio/phy/spi2.py new file mode 100644 index 000000000..cc4c06da6 --- /dev/null +++ b/artiq/gateware/rtio/phy/spi2.py @@ -0,0 +1,116 @@ +from migen import * + +from misoc.cores.spi2 import SPIMachine, SPIInterfaceXC7Diff, SPIInterface +from artiq.gateware.rtio import rtlink + + +class SPIMaster(Module): + """ + RTIO SPI Master version 2. + + Register address and bit map: + + data (address 0): + 32 write/read data + + config (address 1): + 1 offline: all pins high-z (reset=1) + 1 end: end transaction with next transfer (reset=1) + 1 input: submit read data on RTIO input when readable (reset=0) + 1 cs_polarity: active level of chip select (reset=0) + 1 clk_polarity: idle level of clk (reset=0) + 1 clk_phase: first edge after cs assertion to sample data on (reset=0) + (clk_polarity, clk_phase) == (CPOL, CPHA) in Freescale language. + (0, 0): idle low, output on falling, input on rising + (0, 1): idle low, output on rising, input on falling + (1, 0): idle high, output on rising, input on falling + (1, 1): idle high, output on falling, input on rising + There is never a clk edge during a cs edge. + 1 lsb_first: LSB is the first bit on the wire (reset=0) + 1 half_duplex: 3-wire SPI, in/out on mosi (reset=0) + 5 length: 1-32 bits = length + 1 (reset=0) + 3 padding + 8 div: counter load value to divide this module's clock + to generate the SPI write clk (reset=0) + f_clk/f_spi == div + 2 + 8 cs: active high bit pattern of chip selects (reset=0) + """ + def __init__(self, pads, pads_n=None): + to_rio_phy = ClockDomainsRenamer("rio_phy") + if pads_n is None: + interface = SPIInterface(pads) + else: + interface = SPIInterfaceXC7Diff(pads, pads_n) + interface = to_rio_phy(interface) + spi = to_rio_phy(SPIMachine(data_width=32, div_width=8)) + self.submodules += interface, spi + + self.rtlink = rtlink.Interface( + rtlink.OInterface(len(spi.reg.pdo), address_width=1, + enable_replace=False), + rtlink.IInterface(len(spi.reg.pdi), timestamped=False) + ) + + ### + + config = Record([ + ("offline", 1), + ("end", 1), + ("input", 1), + ("cs_polarity", 1), + ("clk_polarity", 1), + ("clk_phase", 1), + ("lsb_first", 1), + ("half_duplex", 1), + ("length", 5), + ("padding", 3), + ("div", 8), + ("cs", 8), + ]) + assert len(config) == len(spi.reg.pdo) == len(spi.reg.pdi) == 32 + + config.offline.reset = 1 + config.end.reset = 1 + read = Signal() + + self.sync.rio += [ + If(self.rtlink.i.stb, + read.eq(0) + ), + If(self.rtlink.o.stb & spi.writable, + If(self.rtlink.o.address, + config.raw_bits().eq(self.rtlink.o.data) + ).Else( + read.eq(config.input) + ) + ), + ] + + self.comb += [ + spi.length.eq(config.length), + spi.end.eq(config.end), + spi.cg.div.eq(config.div), + spi.clk_phase.eq(config.clk_phase), + spi.reg.lsb_first.eq(config.lsb_first), + + interface.half_duplex.eq(config.half_duplex), + interface.cs.eq(config.cs), + interface.cs_polarity.eq(Replicate( + config.cs_polarity, len(interface.cs_polarity))), + interface.clk_polarity.eq(config.clk_polarity), + interface.offline.eq(config.offline), + interface.cs_next.eq(spi.cs_next), + interface.clk_next.eq(spi.clk_next), + interface.ce.eq(spi.ce), + interface.sample.eq(spi.reg.sample), + spi.reg.sdi.eq(interface.sdi), + interface.sdo.eq(spi.reg.sdo), + + spi.load.eq(self.rtlink.o.stb & spi.writable & + ~self.rtlink.o.address), + spi.reg.pdo.eq(self.rtlink.o.data), + self.rtlink.o.busy.eq(~spi.writable), + self.rtlink.i.stb.eq(spi.readable & read), + self.rtlink.i.data.eq(spi.reg.pdi) + ] + self.probes = [] diff --git a/conda/artiq-dev/meta.yaml b/conda/artiq-dev/meta.yaml index dc7687b8a..47e4deae4 100644 --- a/conda/artiq-dev/meta.yaml +++ b/conda/artiq-dev/meta.yaml @@ -15,7 +15,7 @@ requirements: - python >=3.5.3,<3.6 - setuptools 33.1.1 - migen 0.7 py35_4+git9c3a301 - - misoc 0.9 py35_10+git3072d794 + - misoc 0.9 py35_11+git10062189 - jesd204b 0.4 - microscope - binutils-or1k-linux >=2.27 diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index d099c8ff3..003b8322d 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -33,6 +33,12 @@ These drivers are for the core device and the peripherals closely integrated int .. automodule:: artiq.coredevice.spi :members: +:mod:`artiq.coredevice.spi2` module +----------------------------------- + +.. automodule:: artiq.coredevice.spi2 + :members: + :mod:`artiq.coredevice.ad5360` module -------------------------------------