forked from M-Labs/artiq
remove old spi RTIO Phy
This commit is contained in:
parent
1329e1a23e
commit
cc70578f1f
|
@ -1,4 +1,4 @@
|
||||||
from artiq.coredevice import exceptions, dds, spi, spi2
|
from artiq.coredevice import exceptions, dds, spi2
|
||||||
from artiq.coredevice.exceptions import (RTIOUnderflow, RTIOOverflow)
|
from artiq.coredevice.exceptions import (RTIOUnderflow, RTIOOverflow)
|
||||||
from artiq.coredevice.dds import (PHASE_MODE_CONTINUOUS, PHASE_MODE_ABSOLUTE,
|
from artiq.coredevice.dds import (PHASE_MODE_CONTINUOUS, PHASE_MODE_ABSOLUTE,
|
||||||
PHASE_MODE_TRACKING)
|
PHASE_MODE_TRACKING)
|
||||||
|
|
|
@ -492,11 +492,6 @@ def create_channel_handlers(vcd_manager, devices, ref_period,
|
||||||
dds_onehot_sel, dds_sysclk)
|
dds_onehot_sel, dds_sysclk)
|
||||||
channel_handlers[dds_bus_channel] = dds_handler
|
channel_handlers[dds_bus_channel] = dds_handler
|
||||||
dds_handler.add_dds_channel(name, dds_channel)
|
dds_handler.add_dds_channel(name, dds_channel)
|
||||||
if (desc["module"] == "artiq.coredevice.spi" and
|
|
||||||
desc["class"] == "SPIMaster"):
|
|
||||||
channel = desc["arguments"]["channel"]
|
|
||||||
channel_handlers[channel] = SPIMasterHandler(
|
|
||||||
vcd_manager, name)
|
|
||||||
if (desc["module"] == "artiq.coredevice.spi2" and
|
if (desc["module"] == "artiq.coredevice.spi2" and
|
||||||
desc["class"] == "SPIMaster"):
|
desc["class"] == "SPIMaster"):
|
||||||
channel = desc["arguments"]["channel"]
|
channel = desc["arguments"]["channel"]
|
||||||
|
|
|
@ -1,286 +0,0 @@
|
||||||
"""
|
|
||||||
Driver for generic SPI on RTIO.
|
|
||||||
|
|
||||||
This ARTIQ coredevice driver corresponds to the legacy MiSoC SPI core (v1).
|
|
||||||
|
|
||||||
Output event replacement is not supported and issuing commands at the same
|
|
||||||
time is an error.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
from artiq.language.core import syscall, kernel, portable, now_mu, delay_mu
|
|
||||||
from artiq.language.types import TInt32, TNone
|
|
||||||
from artiq.language.units import MHz
|
|
||||||
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"SPI_DATA_ADDR", "SPI_XFER_ADDR", "SPI_CONFIG_ADDR",
|
|
||||||
"SPI_OFFLINE", "SPI_ACTIVE", "SPI_PENDING",
|
|
||||||
"SPI_CS_POLARITY", "SPI_CLK_POLARITY", "SPI_CLK_PHASE",
|
|
||||||
"SPI_LSB_FIRST", "SPI_HALF_DUPLEX",
|
|
||||||
"SPIMaster"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
SPI_DATA_ADDR, SPI_XFER_ADDR, SPI_CONFIG_ADDR = range(3)
|
|
||||||
(
|
|
||||||
SPI_OFFLINE,
|
|
||||||
SPI_ACTIVE,
|
|
||||||
SPI_PENDING,
|
|
||||||
SPI_CS_POLARITY,
|
|
||||||
SPI_CLK_POLARITY,
|
|
||||||
SPI_CLK_PHASE,
|
|
||||||
SPI_LSB_FIRST,
|
|
||||||
SPI_HALF_DUPLEX,
|
|
||||||
) = (1 << i for i in range(8))
|
|
||||||
|
|
||||||
SPI_RT2WB_READ = 1 << 2
|
|
||||||
|
|
||||||
|
|
||||||
class SPIMaster:
|
|
||||||
"""Core device Serial Peripheral Interface (SPI) bus master.
|
|
||||||
Owns one SPI bus.
|
|
||||||
|
|
||||||
**Transfer Sequence**:
|
|
||||||
|
|
||||||
* If desired, write the ``config`` register (:meth:`set_config`)
|
|
||||||
to configure and activate the core.
|
|
||||||
* If desired, write the ``xfer`` register (:meth:`set_xfer`)
|
|
||||||
to set ``cs_n``, ``write_length``, and ``read_length``.
|
|
||||||
* :meth:`write` to the ``data`` register (also for transfers with
|
|
||||||
zero bits to be written). Writing starts the transfer.
|
|
||||||
* If desired, :meth:`read_sync` (or :meth:`read_async` followed by a
|
|
||||||
:meth:`input_async` later) the ``data`` register corresponding to
|
|
||||||
the last completed transfer.
|
|
||||||
* If desired, :meth:`set_xfer` for the next transfer.
|
|
||||||
* If desired, :meth:`write` ``data`` queuing the next
|
|
||||||
(possibly chained) transfer.
|
|
||||||
|
|
||||||
**Notes**:
|
|
||||||
|
|
||||||
* In order to chain a transfer onto an in-flight transfer without
|
|
||||||
deasserting ``cs`` in between, the second :meth:`write` needs to
|
|
||||||
happen strictly later than ``2*ref_period_mu`` (two coarse RTIO
|
|
||||||
cycles) but strictly earlier than ``xfer_period_mu + write_period_mu``
|
|
||||||
after the first. Note that :meth:`write` already applies a delay of
|
|
||||||
``xfer_period_mu + write_period_mu``.
|
|
||||||
* A full transfer takes ``write_period_mu + xfer_period_mu``.
|
|
||||||
* Chained transfers can happen every ``xfer_period_mu``.
|
|
||||||
* Read data is available every ``xfer_period_mu`` starting
|
|
||||||
a bit after xfer_period_mu (depending on ``clk_phase``).
|
|
||||||
* As a consequence, in order to chain transfers together, new data must
|
|
||||||
be written before the pending transfer's read data becomes available.
|
|
||||||
|
|
||||||
: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.write_period_mu = numpy.int64(0)
|
|
||||||
self.read_period_mu = numpy.int64(0)
|
|
||||||
self.xfer_period_mu = numpy.int64(0)
|
|
||||||
|
|
||||||
@portable
|
|
||||||
def frequency_to_div(self, f):
|
|
||||||
return int(1/(f*self.core.mu_to_seconds(self.ref_period_mu))) + 1
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def set_config(self, flags=0, write_freq=20*MHz, read_freq=20*MHz):
|
|
||||||
"""Set the configuration register.
|
|
||||||
|
|
||||||
* If ``config.cs_polarity`` == 0 (``cs`` active low, the default),
|
|
||||||
"``cs_n`` 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 ``config.half_duplex`` must to be set
|
|
||||||
when reading data is desired or when the slave drives the
|
|
||||||
``mosi`` signal at any point.
|
|
||||||
* The first bit output on ``mosi`` is always the MSB/LSB (depending
|
|
||||||
on ``config.lsb_first``) of the ``data`` register, independent of
|
|
||||||
``xfer.write_length``. The last bit input from ``miso`` always ends
|
|
||||||
up in the LSB/MSB (respectively) of the ``data`` register,
|
|
||||||
independent of ``xfer.read_length``.
|
|
||||||
* Writes to the ``config`` register take effect immediately.
|
|
||||||
|
|
||||||
**Configuration flags**:
|
|
||||||
|
|
||||||
* :const:`SPI_OFFLINE`: all pins high-z (reset=1)
|
|
||||||
* :const:`SPI_ACTIVE`: transfer in progress (read-only)
|
|
||||||
* :const:`SPI_PENDING`: transfer pending in intermediate buffer
|
|
||||||
(read-only)
|
|
||||||
* :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)
|
|
||||||
|
|
||||||
This method advances the timeline by the duration of the
|
|
||||||
RTIO-to-Wishbone bus transaction (three RTIO clock cycles).
|
|
||||||
|
|
||||||
:param flags: A bit map of `SPI_*` flags.
|
|
||||||
:param write_freq: Desired SPI clock frequency during write bits.
|
|
||||||
:param read_freq: Desired SPI clock frequency during read bits.
|
|
||||||
"""
|
|
||||||
self.set_config_mu(flags, self.frequency_to_div(write_freq),
|
|
||||||
self.frequency_to_div(read_freq))
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def set_config_mu(self, flags=0, write_div=6, read_div=6):
|
|
||||||
"""Set the ``config`` register (in SPI bus machine units).
|
|
||||||
|
|
||||||
.. seealso:: :meth:`set_config`
|
|
||||||
|
|
||||||
:param write_div: Counter load value to divide the RTIO
|
|
||||||
clock by to generate the SPI write clk. (minimum=2, reset=2)
|
|
||||||
``f_rtio_clk/f_spi_write == write_div``. If ``write_div`` is odd,
|
|
||||||
the setup phase of the SPI clock is biased to longer lengths
|
|
||||||
by one RTIO clock cycle.
|
|
||||||
:param read_div: Ditto for the read clock.
|
|
||||||
"""
|
|
||||||
if write_div > 257 or write_div < 2 or read_div > 257 or read_div < 2:
|
|
||||||
raise ValueError('Divider values out of range')
|
|
||||||
rtio_output(now_mu(), self.channel, SPI_CONFIG_ADDR, flags |
|
|
||||||
((write_div - 2) << 16) | ((read_div - 2) << 24))
|
|
||||||
self.write_period_mu = int(write_div*self.ref_period_mu)
|
|
||||||
self.read_period_mu = int(read_div*self.ref_period_mu)
|
|
||||||
delay_mu(3*self.ref_period_mu)
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def set_xfer(self, chip_select=0, write_length=0, read_length=0):
|
|
||||||
"""Set the ``xfer`` register.
|
|
||||||
|
|
||||||
* Every transfer consists of a write of ``write_length`` bits
|
|
||||||
immediately followed by a read of ``read_length`` bits.
|
|
||||||
* ``cs_n`` is asserted at the beginning and deasserted at the end
|
|
||||||
of the transfer if there is no other transfer pending.
|
|
||||||
* ``cs_n`` handling is agnostic to whether it is one-hot or decoded
|
|
||||||
somewhere downstream. If it is decoded, "``cs_n`` 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.
|
|
||||||
* For 4-wire SPI only the sum of ``read_length`` and ``write_length``
|
|
||||||
matters. The behavior is the same (except for clock speeds) no matter
|
|
||||||
how the total transfer length is divided between the two. For
|
|
||||||
3-wire SPI, the direction of ``mosi`` is switched from output to
|
|
||||||
input after ``write_length`` bits.
|
|
||||||
* Data output on ``mosi`` in 4-wire SPI during the read cycles is what
|
|
||||||
is found in the data register at the time.
|
|
||||||
Data in the ``data`` register outside the least/most (depending
|
|
||||||
on ``config.lsb_first``) significant ``read_length`` bits is what is
|
|
||||||
seen on ``miso`` (or ``mosi`` if ``config.half_duplex``)
|
|
||||||
during the write cycles.
|
|
||||||
* Writes to ``xfer`` are synchronized to the start of the next
|
|
||||||
(possibly chained) transfer.
|
|
||||||
|
|
||||||
This method advances the timeline by the duration of the
|
|
||||||
RTIO-to-Wishbone bus transaction (three RTIO clock cycles).
|
|
||||||
|
|
||||||
:param chip_select: Bit mask of chip selects to assert. Or number of
|
|
||||||
the chip select to assert if ``cs`` is decoded downstream.
|
|
||||||
(reset=0)
|
|
||||||
:param write_length: Number of bits to write during the next transfer.
|
|
||||||
(reset=0)
|
|
||||||
:param read_length: Number of bits to read during the next transfer.
|
|
||||||
(reset=0)
|
|
||||||
"""
|
|
||||||
rtio_output(now_mu(), self.channel, SPI_XFER_ADDR,
|
|
||||||
chip_select | (write_length << 16) | (read_length << 24))
|
|
||||||
self.xfer_period_mu = int(write_length*self.write_period_mu +
|
|
||||||
read_length*self.read_period_mu)
|
|
||||||
delay_mu(3*self.ref_period_mu)
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def write(self, data=0):
|
|
||||||
"""Write data to data register.
|
|
||||||
|
|
||||||
* The ``data`` register and the shift register are 32 bits wide.
|
|
||||||
If there are no writes to the register, ``miso`` data reappears on
|
|
||||||
``mosi`` after 32 cycles.
|
|
||||||
* A wishbone data register write is acknowledged when the
|
|
||||||
transfer has been written to the intermediate buffer.
|
|
||||||
It will be started when there are no other transactions being
|
|
||||||
executed, either beginning a new SPI transfer of chained
|
|
||||||
to an in-flight transfer.
|
|
||||||
* Writes take three ``ref_period`` cycles unless another
|
|
||||||
chained transfer is pending and the transfer being
|
|
||||||
executed is not complete.
|
|
||||||
* The SPI ``data`` register is double-buffered: Once a transfer has
|
|
||||||
started, new write data can be written, queuing a new transfer.
|
|
||||||
Transfers submitted this way are chained and executed without
|
|
||||||
deasserting ``cs`` in between. Once a transfer completes,
|
|
||||||
the previous transfer's read data is available in the
|
|
||||||
``data`` register.
|
|
||||||
* For bit alignment and bit ordering see :meth:`set_config`.
|
|
||||||
|
|
||||||
This method advances the timeline by the duration of the SPI transfer.
|
|
||||||
If a transfer is to be chained, the timeline needs to be rewound.
|
|
||||||
"""
|
|
||||||
rtio_output(now_mu(), self.channel, SPI_DATA_ADDR, data)
|
|
||||||
delay_mu(self.xfer_period_mu + self.write_period_mu)
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def read_async(self):
|
|
||||||
"""Trigger an asynchronous read from the ``data`` register.
|
|
||||||
|
|
||||||
For bit alignment and bit ordering see :meth:`set_config`.
|
|
||||||
|
|
||||||
Reads always finish in two cycles.
|
|
||||||
|
|
||||||
Every data register read triggered by a :meth:`read_async`
|
|
||||||
must be matched by a :meth:`input_async` to retrieve the data.
|
|
||||||
|
|
||||||
This method advances the timeline by the duration of the
|
|
||||||
RTIO-to-Wishbone bus transaction (three RTIO clock cycles).
|
|
||||||
"""
|
|
||||||
rtio_output(now_mu(), self.channel, SPI_DATA_ADDR | SPI_RT2WB_READ, 0)
|
|
||||||
delay_mu(3*self.ref_period_mu)
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def input_async(self):
|
|
||||||
"""Retrieves data read asynchronously from the ``data`` register.
|
|
||||||
|
|
||||||
:meth:`input_async` must match a preeeding :meth:`read_async`.
|
|
||||||
"""
|
|
||||||
return rtio_input_data(self.channel)
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def read_sync(self):
|
|
||||||
"""Read the ``data`` register synchronously.
|
|
||||||
|
|
||||||
This is a shortcut for :meth:`read_async` followed by
|
|
||||||
:meth:`input_async`.
|
|
||||||
"""
|
|
||||||
self.read_async()
|
|
||||||
return self.input_async()
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def _get_xfer_sync(self):
|
|
||||||
rtio_output(now_mu(), self.channel, SPI_XFER_ADDR | SPI_RT2WB_READ, 0)
|
|
||||||
return rtio_input_data(self.channel)
|
|
||||||
|
|
||||||
@kernel
|
|
||||||
def _get_config_sync(self):
|
|
||||||
rtio_output(now_mu(), self.channel, SPI_CONFIG_ADDR | SPI_RT2WB_READ,
|
|
||||||
0)
|
|
||||||
return rtio_input_data(self.channel)
|
|
|
@ -1,13 +0,0 @@
|
||||||
from migen import *
|
|
||||||
|
|
||||||
from artiq.gateware.spi import SPIMaster as SPIMasterWB
|
|
||||||
from artiq.gateware.rtio.phy.wishbone import RT2WB
|
|
||||||
|
|
||||||
|
|
||||||
class SPIMaster(Module):
|
|
||||||
def __init__(self, pads, pads_n=None, **kwargs):
|
|
||||||
self.submodules._ll = ClockDomainsRenamer("rio_phy")(
|
|
||||||
SPIMasterWB(pads, pads_n, **kwargs))
|
|
||||||
self.submodules._rt2wb = RT2WB(2, self._ll.bus)
|
|
||||||
self.rtlink = self._rt2wb.rtlink
|
|
||||||
self.probes = []
|
|
|
@ -1,385 +0,0 @@
|
||||||
from itertools import product
|
|
||||||
|
|
||||||
from migen import *
|
|
||||||
from misoc.interconnect import wishbone
|
|
||||||
from misoc.cores.spi import SPIMachine
|
|
||||||
|
|
||||||
|
|
||||||
class SPIMaster(Module):
|
|
||||||
"""SPI Master.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
* M = 32 is the data width (width of the data register,
|
|
||||||
maximum write bits, maximum read bits)
|
|
||||||
* Every transfer consists of a write_length 0-M bit write followed
|
|
||||||
by a read_length 0-M bit read.
|
|
||||||
* cs_n is asserted at the beginning and deasserted at the end of the
|
|
||||||
transfer if there is no other transfer pending.
|
|
||||||
* cs_n handling is agnostic to whether it is one-hot or decoded
|
|
||||||
somewhere downstream. If it is decoded, "cs_n 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.
|
|
||||||
* If config.cs_polarity == 0 (cs active low, the default),
|
|
||||||
"cs_n all deasserted" means "all cs_n bits high".
|
|
||||||
* cs is not mandatory in pads. Framing and chip selection can also
|
|
||||||
be handled independently through other means.
|
|
||||||
* If there is a miso wire in pads, the input and output can be done
|
|
||||||
with two signals (a.k.a. 4-wire SPI), else mosi must be used for
|
|
||||||
both output and input (a.k.a. 3-wire SPI) and config.half_duplex
|
|
||||||
must to be set when reading data is desired.
|
|
||||||
* For 4-wire SPI only the sum of read_length and write_length matters.
|
|
||||||
The behavior is the same no matter how the total transfer length is
|
|
||||||
divided between the two. For 3-wire SPI, the direction of mosi/miso
|
|
||||||
is switched from output to input after write_len cycles, at the
|
|
||||||
"shift_out" clk edge corresponding to bit write_length + 1 of the
|
|
||||||
transfer.
|
|
||||||
* The first bit output on mosi is always the MSB/LSB (depending on
|
|
||||||
config.lsb_first) of the data register, independent of
|
|
||||||
xfer.write_len. The last bit input from miso always ends up in
|
|
||||||
the LSB/MSB (respectively) of the data register, independent of
|
|
||||||
read_len.
|
|
||||||
* Data output on mosi in 4-wire SPI during the read cycles is what
|
|
||||||
is found in the data register at the time.
|
|
||||||
Data in the data register outside the least/most (depending
|
|
||||||
on config.lsb_first) significant read_length bits is what is
|
|
||||||
seen on miso during the write cycles.
|
|
||||||
* The SPI data register is double-buffered: Once a transfer has
|
|
||||||
started, new write data can be written, queuing a new transfer.
|
|
||||||
Transfers submitted this way are chained and executed without
|
|
||||||
deasserting cs. Once a transfer completes, the previous transfer's
|
|
||||||
read data is available in the data register.
|
|
||||||
* Writes to the config register take effect immediately. Writes to xfer
|
|
||||||
and data are synchronized to the start of a transfer.
|
|
||||||
* A wishbone data register write is ack-ed when the transfer has
|
|
||||||
been written to the intermediate buffer. It will be started when
|
|
||||||
there are no other transactions being executed, either starting
|
|
||||||
a new SPI transfer of chained to an in-flight transfer.
|
|
||||||
Writes take two cycles unless the write is to the data register
|
|
||||||
and another chained transfer is pending and the transfer being
|
|
||||||
executed is not complete. Reads always finish in two cycles.
|
|
||||||
|
|
||||||
Transaction Sequence:
|
|
||||||
* If desired, write the config register to set up the core.
|
|
||||||
* If desired, write the xfer register to change lengths and cs_n.
|
|
||||||
* Write the data register (also for zero-length writes),
|
|
||||||
writing triggers the transfer and when the transfer is accepted to
|
|
||||||
the inermediate buffer, the write is ack-ed.
|
|
||||||
* If desired, read the data register corresponding to the last
|
|
||||||
completed transfer.
|
|
||||||
* If desired, change xfer register for the next transfer.
|
|
||||||
* If desired, write data queuing the next (possibly chained) transfer.
|
|
||||||
|
|
||||||
Register address and bit map:
|
|
||||||
|
|
||||||
config (address 2):
|
|
||||||
1 offline: all pins high-z (reset=1)
|
|
||||||
1 active: cs/transfer active (read-only)
|
|
||||||
1 pending: transfer pending in intermediate buffer (read-only)
|
|
||||||
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)
|
|
||||||
8 undefined
|
|
||||||
8 div_write: counter load value to divide this module's clock
|
|
||||||
to generate the SPI write clk (reset=0)
|
|
||||||
f_clk/f_spi_write == div_write + 2
|
|
||||||
8 div_read: ditto for the read clock
|
|
||||||
|
|
||||||
xfer (address 1):
|
|
||||||
16 cs: active high bit mask of chip selects to assert (reset=0)
|
|
||||||
6 write_len: 0-M bits (reset=0)
|
|
||||||
2 undefined
|
|
||||||
6 read_len: 0-M bits (reset=0)
|
|
||||||
2 undefined
|
|
||||||
|
|
||||||
data (address 0):
|
|
||||||
M write/read data (reset=0)
|
|
||||||
"""
|
|
||||||
def __init__(self, pads, pads_n=None, bus=None):
|
|
||||||
if bus is None:
|
|
||||||
bus = wishbone.Interface(data_width=32)
|
|
||||||
self.bus = bus
|
|
||||||
|
|
||||||
###
|
|
||||||
|
|
||||||
# Wishbone
|
|
||||||
config = Record([
|
|
||||||
("offline", 1),
|
|
||||||
("active", 1),
|
|
||||||
("pending", 1),
|
|
||||||
("cs_polarity", 1),
|
|
||||||
("clk_polarity", 1),
|
|
||||||
("clk_phase", 1),
|
|
||||||
("lsb_first", 1),
|
|
||||||
("half_duplex", 1),
|
|
||||||
("padding", 8),
|
|
||||||
("div_write", 8),
|
|
||||||
("div_read", 8),
|
|
||||||
])
|
|
||||||
config.offline.reset = 1
|
|
||||||
assert len(config) <= len(bus.dat_w)
|
|
||||||
|
|
||||||
xfer = Record([
|
|
||||||
("cs", 16),
|
|
||||||
("write_length", 6),
|
|
||||||
("padding0", 2),
|
|
||||||
("read_length", 6),
|
|
||||||
("padding1", 2),
|
|
||||||
])
|
|
||||||
assert len(xfer) <= len(bus.dat_w)
|
|
||||||
|
|
||||||
self.submodules.spi = spi = SPIMachine(
|
|
||||||
data_width=len(bus.dat_w) + 1,
|
|
||||||
clock_width=len(config.div_read),
|
|
||||||
bits_width=len(xfer.read_length))
|
|
||||||
|
|
||||||
pending = Signal()
|
|
||||||
cs = Signal.like(xfer.cs)
|
|
||||||
data_read = Signal.like(spi.reg.data)
|
|
||||||
data_write = Signal.like(spi.reg.data)
|
|
||||||
|
|
||||||
self.comb += [
|
|
||||||
spi.start.eq(pending & (~spi.cs | spi.done)),
|
|
||||||
spi.clk_phase.eq(config.clk_phase),
|
|
||||||
spi.reg.lsb.eq(config.lsb_first),
|
|
||||||
spi.div_write.eq(config.div_write),
|
|
||||||
spi.div_read.eq(config.div_read),
|
|
||||||
]
|
|
||||||
self.sync += [
|
|
||||||
If(spi.done,
|
|
||||||
data_read.eq(
|
|
||||||
Mux(spi.reg.lsb, spi.reg.data[1:], spi.reg.data[:-1])),
|
|
||||||
),
|
|
||||||
If(spi.start,
|
|
||||||
cs.eq(xfer.cs),
|
|
||||||
spi.bits.n_write.eq(xfer.write_length),
|
|
||||||
spi.bits.n_read.eq(xfer.read_length),
|
|
||||||
If(spi.reg.lsb,
|
|
||||||
spi.reg.data[:-1].eq(data_write),
|
|
||||||
).Else(
|
|
||||||
spi.reg.data[1:].eq(data_write),
|
|
||||||
),
|
|
||||||
pending.eq(0),
|
|
||||||
),
|
|
||||||
# wb.ack a transaction if any of the following:
|
|
||||||
# a) reading,
|
|
||||||
# b) writing to non-data register
|
|
||||||
# c) writing to data register and no pending transfer
|
|
||||||
# d) writing to data register and pending and swapping buffers
|
|
||||||
bus.ack.eq(bus.cyc & bus.stb &
|
|
||||||
(~bus.we | (bus.adr != 0) | ~pending | spi.done)),
|
|
||||||
If(bus.cyc & bus.stb,
|
|
||||||
bus.dat_r.eq(
|
|
||||||
Array([data_read, xfer.raw_bits(), config.raw_bits()
|
|
||||||
])[bus.adr]),
|
|
||||||
),
|
|
||||||
If(bus.ack,
|
|
||||||
bus.ack.eq(0),
|
|
||||||
If(bus.we,
|
|
||||||
Array([data_write, xfer.raw_bits(), config.raw_bits()
|
|
||||||
])[bus.adr].eq(bus.dat_w),
|
|
||||||
If(bus.adr == 0, # data register
|
|
||||||
pending.eq(1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
config.active.eq(spi.cs),
|
|
||||||
config.pending.eq(pending),
|
|
||||||
]
|
|
||||||
|
|
||||||
# I/O
|
|
||||||
mosi_oe = Signal()
|
|
||||||
clk = Signal()
|
|
||||||
self.comb += [
|
|
||||||
mosi_oe.eq(
|
|
||||||
~config.offline & spi.cs &
|
|
||||||
(spi.oe | ~config.half_duplex)),
|
|
||||||
]
|
|
||||||
self.sync += [
|
|
||||||
If(spi.cg.ce & spi.cg.edge,
|
|
||||||
clk.eq((~spi.cg.clk & spi.cs_next) ^ config.clk_polarity)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
if pads_n is None:
|
|
||||||
if hasattr(pads, "cs_n"):
|
|
||||||
cs_n_t = TSTriple(len(pads.cs_n))
|
|
||||||
self.specials += cs_n_t.get_tristate(pads.cs_n)
|
|
||||||
self.comb += [
|
|
||||||
cs_n_t.oe.eq(~config.offline),
|
|
||||||
cs_n_t.o.eq((cs & Replicate(spi.cs, len(cs))) ^
|
|
||||||
Replicate(~config.cs_polarity, len(cs))),
|
|
||||||
]
|
|
||||||
|
|
||||||
clk_t = TSTriple()
|
|
||||||
self.specials += clk_t.get_tristate(pads.clk)
|
|
||||||
self.comb += [
|
|
||||||
clk_t.oe.eq(~config.offline),
|
|
||||||
clk_t.o.eq(clk),
|
|
||||||
]
|
|
||||||
|
|
||||||
mosi_t = TSTriple()
|
|
||||||
self.specials += mosi_t.get_tristate(pads.mosi)
|
|
||||||
self.comb += [
|
|
||||||
mosi_t.oe.eq(mosi_oe),
|
|
||||||
mosi_t.o.eq(spi.reg.o),
|
|
||||||
spi.reg.i.eq(Mux(config.half_duplex, mosi_t.i,
|
|
||||||
getattr(pads, "miso", mosi_t.i))),
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
if hasattr(pads, "cs_n"):
|
|
||||||
for i in range(len(pads.cs_n)):
|
|
||||||
self.specials += Instance("OBUFTDS",
|
|
||||||
i_I=(cs[i] & spi.cs) ^ ~config.cs_polarity,
|
|
||||||
i_T=config.offline,
|
|
||||||
o_O=pads.cs_n[i], o_OB=pads_n.cs_n[i])
|
|
||||||
|
|
||||||
self.specials += Instance("OBUFTDS",
|
|
||||||
i_I=clk, i_T=config.offline,
|
|
||||||
o_O=pads.clk, o_OB=pads_n.clk)
|
|
||||||
|
|
||||||
mosi = Signal()
|
|
||||||
self.specials += Instance("IOBUFDS_INTERMDISABLE",
|
|
||||||
p_DIFF_TERM="TRUE",
|
|
||||||
p_IBUF_LOW_PWR="FALSE",
|
|
||||||
p_USE_IBUFDISABLE="TRUE",
|
|
||||||
i_IBUFDISABLE=config.offline | mosi_oe,
|
|
||||||
i_INTERMDISABLE=config.offline | mosi_oe,
|
|
||||||
o_O=mosi, i_I=spi.reg.o, i_T=~mosi_oe,
|
|
||||||
io_IO=pads.mosi, io_IOB=pads_n.mosi)
|
|
||||||
if hasattr(pads, "miso"):
|
|
||||||
miso = Signal()
|
|
||||||
self.specials += Instance("IBUFDS_INTERMDISABLE",
|
|
||||||
p_DIFF_TERM="TRUE",
|
|
||||||
p_IBUF_LOW_PWR="FALSE",
|
|
||||||
p_USE_IBUFDISABLE="TRUE",
|
|
||||||
i_IBUFDISABLE=config.offline,
|
|
||||||
i_INTERMDISABLE=config.offline,
|
|
||||||
o_O=miso, i_I=pads.miso, i_IB=pads_n.miso)
|
|
||||||
else:
|
|
||||||
miso = mosi
|
|
||||||
self.comb += spi.reg.i.eq(Mux(config.half_duplex, mosi, miso))
|
|
||||||
|
|
||||||
|
|
||||||
SPI_DATA_ADDR, SPI_XFER_ADDR, SPI_CONFIG_ADDR = range(3)
|
|
||||||
(
|
|
||||||
SPI_OFFLINE,
|
|
||||||
SPI_ACTIVE,
|
|
||||||
SPI_PENDING,
|
|
||||||
SPI_CS_POLARITY,
|
|
||||||
SPI_CLK_POLARITY,
|
|
||||||
SPI_CLK_PHASE,
|
|
||||||
SPI_LSB_FIRST,
|
|
||||||
SPI_HALF_DUPLEX,
|
|
||||||
) = (1 << i for i in range(8))
|
|
||||||
|
|
||||||
|
|
||||||
def SPI_DIV_WRITE(i):
|
|
||||||
return i << 16
|
|
||||||
|
|
||||||
|
|
||||||
def SPI_DIV_READ(i):
|
|
||||||
return i << 24
|
|
||||||
|
|
||||||
|
|
||||||
def SPI_CS(i):
|
|
||||||
return i << 0
|
|
||||||
|
|
||||||
|
|
||||||
def SPI_WRITE_LENGTH(i):
|
|
||||||
return i << 16
|
|
||||||
|
|
||||||
|
|
||||||
def SPI_READ_LENGTH(i):
|
|
||||||
return i << 24
|
|
||||||
|
|
||||||
|
|
||||||
def _test_xfer(bus, cs, wlen, rlen, wdata):
|
|
||||||
yield from bus.write(SPI_XFER_ADDR, SPI_CS(cs) |
|
|
||||||
SPI_WRITE_LENGTH(wlen) | SPI_READ_LENGTH(rlen))
|
|
||||||
yield from bus.write(SPI_DATA_ADDR, wdata)
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
def _test_read(bus, sync=SPI_ACTIVE | SPI_PENDING):
|
|
||||||
while (yield from bus.read(SPI_CONFIG_ADDR)) & sync:
|
|
||||||
pass
|
|
||||||
return (yield from bus.read(SPI_DATA_ADDR))
|
|
||||||
|
|
||||||
|
|
||||||
def _test_gen(bus):
|
|
||||||
yield from bus.write(SPI_CONFIG_ADDR,
|
|
||||||
0*SPI_CLK_PHASE | 0*SPI_LSB_FIRST |
|
|
||||||
1*SPI_HALF_DUPLEX |
|
|
||||||
SPI_DIV_WRITE(3) | SPI_DIV_READ(5))
|
|
||||||
yield from _test_xfer(bus, 0b01, 4, 0, 0x90000000)
|
|
||||||
print(hex((yield from _test_read(bus))))
|
|
||||||
yield from _test_xfer(bus, 0b10, 0, 4, 0x90000000)
|
|
||||||
print(hex((yield from _test_read(bus))))
|
|
||||||
yield from _test_xfer(bus, 0b11, 4, 4, 0x81000000)
|
|
||||||
print(hex((yield from _test_read(bus))))
|
|
||||||
yield from _test_xfer(bus, 0b01, 8, 32, 0x87654321)
|
|
||||||
yield from _test_xfer(bus, 0b01, 0, 32, 0x12345678)
|
|
||||||
print(hex((yield from _test_read(bus, SPI_PENDING))))
|
|
||||||
print(hex((yield from _test_read(bus, SPI_ACTIVE))))
|
|
||||||
return
|
|
||||||
for cpol, cpha, lsb, clk in product(
|
|
||||||
(0, 1), (0, 1), (0, 1), (0, 1)):
|
|
||||||
yield from bus.write(SPI_CONFIG_ADDR,
|
|
||||||
cpol*SPI_CLK_POLARITY | cpha*SPI_CLK_PHASE |
|
|
||||||
lsb*SPI_LSB_FIRST | SPI_DIV_WRITE(clk) |
|
|
||||||
SPI_DIV_READ(clk))
|
|
||||||
for wlen, rlen, wdata in product((0, 8, 32), (0, 8, 32),
|
|
||||||
(0, 0xffffffff, 0xdeadbeef)):
|
|
||||||
rdata = (yield from _test_xfer(bus, 0b1, wlen, rlen, wdata, True))
|
|
||||||
len = (wlen + rlen) % 32
|
|
||||||
mask = (1 << len) - 1
|
|
||||||
if lsb:
|
|
||||||
shift = (wlen + rlen) % 32
|
|
||||||
else:
|
|
||||||
shift = 0
|
|
||||||
a = (wdata >> wshift) & wmask
|
|
||||||
b = (rdata >> rshift) & rmask
|
|
||||||
if a != b:
|
|
||||||
print("ERROR", end=" ")
|
|
||||||
print(cpol, cpha, lsb, clk, wlen, rlen,
|
|
||||||
hex(wdata), hex(rdata), hex(a), hex(b))
|
|
||||||
|
|
||||||
|
|
||||||
class _TestPads:
|
|
||||||
def __init__(self):
|
|
||||||
self.cs_n = Signal(2)
|
|
||||||
self.clk = Signal()
|
|
||||||
self.mosi = Signal()
|
|
||||||
self.miso = Signal()
|
|
||||||
|
|
||||||
|
|
||||||
class _TestTristate(Module):
|
|
||||||
def __init__(self, t):
|
|
||||||
oe = Signal()
|
|
||||||
self.comb += [
|
|
||||||
t.target.eq(t.o),
|
|
||||||
oe.eq(t.oe),
|
|
||||||
t.i.eq(t.o),
|
|
||||||
]
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from migen.fhdl.specials import Tristate
|
|
||||||
|
|
||||||
pads = _TestPads()
|
|
||||||
dut = SPIMaster(pads)
|
|
||||||
dut.comb += pads.miso.eq(pads.mosi)
|
|
||||||
# from migen.fhdl.verilog import convert
|
|
||||||
# print(convert(dut))
|
|
||||||
|
|
||||||
Tristate.lower = _TestTristate
|
|
||||||
run_simulation(dut, _test_gen(dut.bus), vcd_name="spi_master.vcd")
|
|
|
@ -27,12 +27,6 @@ These drivers are for the core device and the peripherals closely integrated int
|
||||||
.. automodule:: artiq.coredevice.dma
|
.. automodule:: artiq.coredevice.dma
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
:mod:`artiq.coredevice.spi` module
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
.. automodule:: artiq.coredevice.spi
|
|
||||||
:members:
|
|
||||||
|
|
||||||
:mod:`artiq.coredevice.spi2` module
|
:mod:`artiq.coredevice.spi2` module
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ The sequence is exactly equivalent to::
|
||||||
|
|
||||||
ttl.pulse(2*us)
|
ttl.pulse(2*us)
|
||||||
|
|
||||||
The :meth:`artiq.coredevice.ttl.TTLOut.pulse` method advances the timeline cursor (using ``delay()``) while other methods such as :meth:`artiq.coredevice.ttl.TTLOut.on`, :meth:`artiq.coredevice.ttl.TTLOut.off`, :meth:`artiq.coredevice.dds._DDSGeneric.set`, or the ``set_*()`` methods of :class:`artiq.coredevice.spi.SPIMaster` do not. The latter are called *zero-duration* methods.
|
The :meth:`artiq.coredevice.ttl.TTLOut.pulse` method advances the timeline cursor (using ``delay()``) while other methods such as :meth:`artiq.coredevice.ttl.TTLOut.on`, :meth:`artiq.coredevice.ttl.TTLOut.off`, :meth:`artiq.coredevice.dds._DDSGeneric.set`. The latter are called *zero-duration* methods.
|
||||||
|
|
||||||
Underflow exceptions
|
Underflow exceptions
|
||||||
--------------------
|
--------------------
|
||||||
|
|
Loading…
Reference in New Issue