forked from M-Labs/artiq
1
0
Fork 0

spi2: add RTIO gateware and coredevice driver

1006218997
This commit is contained in:
Robert Jördens 2018-02-21 13:34:45 +00:00
parent 91a4a7b0ee
commit 37a0d6580b
4 changed files with 334 additions and 1 deletions

211
artiq/coredevice/spi2.py Normal file
View File

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

View File

@ -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 = []

View File

@ -15,7 +15,7 @@ requirements:
- python >=3.5.3,<3.6 - python >=3.5.3,<3.6
- setuptools 33.1.1 - setuptools 33.1.1
- migen 0.7 py35_4+git9c3a301 - migen 0.7 py35_4+git9c3a301
- misoc 0.9 py35_10+git3072d794 - misoc 0.9 py35_11+git10062189
- jesd204b 0.4 - jesd204b 0.4
- microscope - microscope
- binutils-or1k-linux >=2.27 - binutils-or1k-linux >=2.27

View File

@ -33,6 +33,12 @@ These drivers are for the core device and the peripherals closely integrated int
.. automodule:: artiq.coredevice.spi .. automodule:: artiq.coredevice.spi
:members: :members:
:mod:`artiq.coredevice.spi2` module
-----------------------------------
.. automodule:: artiq.coredevice.spi2
:members:
:mod:`artiq.coredevice.ad5360` module :mod:`artiq.coredevice.ad5360` module
------------------------------------- -------------------------------------