mirror of https://github.com/m-labs/artiq.git
parent
91a4a7b0ee
commit
37a0d6580b
|
@ -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)
|
|
@ -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 = []
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
-------------------------------------
|
||||
|
||||
|
|
Loading…
Reference in New Issue