soc/cores/spi: Integrate SPIWishboneBridge from https://github.com/xobs/spibone.
"The ability to bridge Wishbone is an incredibly powerful one. However, the various bridges can be rather heavy in terms of resource usage. This presents a simple bridge that operates over SPI."
This commit is contained in:
parent
f5c9425e14
commit
9f52ed1207
|
@ -0,0 +1,357 @@
|
|||
#
|
||||
# This file is part of LiteX.
|
||||
#
|
||||
# Copyright (c) 2019 Sean Cross <sean@xobs.io>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from migen import *
|
||||
from migen.fhdl.specials import Tristate, TSTriple
|
||||
from migen.genlib.cdc import MultiReg
|
||||
|
||||
from litex.soc.integration.doc import ModuleDoc, AutoDoc
|
||||
from litex.soc.interconnect import wishbone, stream
|
||||
|
||||
class Spi4WireDocumentation(ModuleDoc):
|
||||
"""4-Wire SPI Protocol
|
||||
|
||||
The 4-wire SPI protocol does not require any pins to change direction, and
|
||||
is therefore suitable for designs with level-shifters or without GPIOs that
|
||||
can change direction.
|
||||
|
||||
While waiting for the response, the ``MISO`` line remains high. As soon as
|
||||
a response is available, the device pulls the `MISO` line low and clocks
|
||||
out either a ``0x00`` or `0x01` byte indicating whether it's a READ or a WRITE
|
||||
that is being answered. Note that if the operation is fast enough, the
|
||||
device will not pull the `MISO` line high and will immediately respond
|
||||
with ``0x00`` or ``0x01``.
|
||||
|
||||
You can abort the operation by driving ``CS`` high. However, if a WRITE or
|
||||
READ has already been initiated then it will not be aborted.
|
||||
|
||||
.. wavedrom::
|
||||
:caption: 4-Wire SPI Operation
|
||||
|
||||
{ "signal": [
|
||||
["Read",
|
||||
{ "name": 'MOSI', "wave": 'x23...x|xxxxxx', "data": '0x01 [ADDRESS]'},
|
||||
{ "name": 'MISO', "wave": 'x.....x|25...x', "data": '0x01 [DATA]' },
|
||||
{ "name": 'CS', "wave": 'x0.....|.....x', "data": '1 2 3'},
|
||||
{ "name": 'data bits', "wave": 'xx2222x|x2222x', "data": '31:24 23:16 15:8 7:0 31:24 23:16 15:8 7:0'}
|
||||
],
|
||||
{},
|
||||
["Write",
|
||||
{ "name": 'MOSI', "wave": 'x23...3...x|xx', "data": '0x00 [ADDRESS] [DATA]'},
|
||||
{ "name": 'MISO', "wave": 'x.........1|2x', "data": '0x00' },
|
||||
{ "name": 'CS', "wave": 'x0.........|.x', "data": '1 2 3'},
|
||||
{ "name": 'data bits', "wave": 'xx22222222x|xx', "data": '31:24 23:16 15:8 7:0 31:24 23:16 15:8 7:0'}
|
||||
]
|
||||
]}
|
||||
"""
|
||||
|
||||
class Spi3WireDocumentation(ModuleDoc):
|
||||
"""3-Wire SPI Protocol
|
||||
|
||||
The 3-wire SPI protocol repurposes the ``MOSI`` line for both data input and
|
||||
data output. The direction of the line changes immediately after the
|
||||
address (for read) or the data (for write) and the device starts writing
|
||||
``0xFF``.
|
||||
|
||||
As soon as data is available (read) or the data has been written (write),
|
||||
the device drives the ``MOSI`` line low in order to clock out ``0x00``
|
||||
or ``0x01``. This will always happen on a byte boundary.
|
||||
|
||||
You can abort the operation by driving ``CS`` high. However, if a WRITE or
|
||||
READ has already been initiated then it will not be aborted.
|
||||
|
||||
.. wavedrom::
|
||||
:caption: 3-Wire SPI Operation
|
||||
|
||||
{ "signal": [
|
||||
["Read",
|
||||
{ "name": 'MOSI', "wave": 'x23...5|55...x', "data": '0x01 [ADDRESS] 0xFF 0x01 [DATA]'},
|
||||
{ "name": 'CS', "wave": 'x0.....|.....x', "data": '1 2 3'},
|
||||
{ "name": 'data bits', "wave": 'xx2222x|x2222x', "data": '31:24 23:16 15:8 7:0 31:24 23:16 15:8 7:0'}
|
||||
],
|
||||
{},
|
||||
["Write",
|
||||
{ "name": 'MOSI', "wave": 'x23...3...5|50', "data": '0x00 [ADDRESS] [DATA] 0xFF 0x00'},
|
||||
{ "name": 'CS', "wave": 'x0.........|.x', "data": '1 2 3'},
|
||||
{ "name": 'data bits', "wave": 'xx22222222x|xx', "data": '31:24 23:16 15:8 7:0 31:24 23:16 15:8 7:0'}
|
||||
]
|
||||
]}
|
||||
"""
|
||||
|
||||
class Spi2WireDocumentation(ModuleDoc):
|
||||
"""2-Wire SPI Protocol
|
||||
|
||||
The 2-wire SPI protocol removes the ``CS`` line in favor of a sync byte.
|
||||
Note that the 2-wire protocol has no way of interrupting communication,
|
||||
so if the bus locks up the device must be reset. The direction of the
|
||||
data line changes immediately after the address (for read) or the data
|
||||
(for write) and the device starts writing ``0xFF``.
|
||||
|
||||
As soon as data is available (read) or the data has been written (write),
|
||||
the device drives the ``MOSI`` line low in order to clock out ``0x00``
|
||||
or ``0x01``. This will always happen on a byte boundary.
|
||||
|
||||
All transactions begin with a sync byte of ``0xAB``.
|
||||
|
||||
.. wavedrom::
|
||||
:caption: 2-Wire SPI Operation
|
||||
|
||||
{ "signal": [
|
||||
["Write",
|
||||
{ "name": 'MOSI', "wave": '223...5|55...', "data": '0xAB 0x01 [ADDRESS] 0xFF 0x01 [DATA]'},
|
||||
{ "name": 'data bits', "wave": 'xx2222x|x2222', "data": '31:24 23:16 15:8 7:0 31:24 23:16 15:8 7:0'}
|
||||
],
|
||||
{},
|
||||
["Read",
|
||||
{ "name": 'MOSI', "wave": '223...3...5|5', "data": '0xAB 0x00 [ADDRESS] [DATA] 0xFF 0x00'},
|
||||
{ "name": 'data bits', "wave": 'xx22222222x|x', "data": '31:24 23:16 15:8 7:0 31:24 23:16 15:8 7:0'}
|
||||
]
|
||||
]}
|
||||
"""
|
||||
|
||||
class SpiWishboneBridge(Module, ModuleDoc, AutoDoc):
|
||||
"""Wishbone Bridge over SPI
|
||||
|
||||
This module allows for accessing a Wishbone bridge over a {}-wire protocol.
|
||||
All operations occur on byte boundaries, and are big-endian.
|
||||
|
||||
The device can take a variable amount of time to respond, so the host should
|
||||
continue polling after the operation begins. If the Wishbone bus is
|
||||
particularly busy, such as during periods of heavy processing when the
|
||||
CPU's icache is empty, responses can take many thousands of cycles.
|
||||
|
||||
The bridge core is designed to run at 1/4 the system clock.
|
||||
"""
|
||||
def __init__(self, pads, wires=4, with_tristate=True):
|
||||
self.wishbone = wishbone.Interface()
|
||||
|
||||
# # #
|
||||
self.__doc__ = self.__doc__.format(wires)
|
||||
if wires == 4:
|
||||
self.mod_doc = Spi4WireDocumentation()
|
||||
elif wires == 3:
|
||||
self.mod_doc = Spi3WireDocumentation()
|
||||
elif wires == 2:
|
||||
self.mod_doc = Spi2WireDocumentation()
|
||||
|
||||
clk = Signal()
|
||||
cs_n = Signal()
|
||||
mosi = Signal()
|
||||
miso = Signal()
|
||||
miso_en = Signal()
|
||||
|
||||
counter = Signal(8)
|
||||
write_offset = Signal(5)
|
||||
command = Signal(8)
|
||||
address = Signal(32)
|
||||
value = Signal(32)
|
||||
wr = Signal()
|
||||
sync_byte = Signal(8)
|
||||
|
||||
self.specials += [
|
||||
MultiReg(pads.clk, clk),
|
||||
]
|
||||
if wires == 2:
|
||||
io = TSTriple()
|
||||
self.specials += io.get_tristate(pads.mosi)
|
||||
self.specials += MultiReg(io.i, mosi)
|
||||
self.comb += io.o.eq(miso)
|
||||
self.comb += io.oe.eq(miso_en)
|
||||
elif wires == 3:
|
||||
self.specials += MultiReg(pads.cs_n, cs_n),
|
||||
io = TSTriple()
|
||||
self.specials += io.get_tristate(pads.mosi)
|
||||
self.specials += MultiReg(io.i, mosi)
|
||||
self.comb += io.o.eq(miso)
|
||||
self.comb += io.oe.eq(miso_en)
|
||||
elif wires == 4:
|
||||
self.specials += MultiReg(pads.cs_n, cs_n),
|
||||
self.specials += MultiReg(pads.mosi, mosi)
|
||||
if with_tristate:
|
||||
self.specials += Tristate(pads.miso, miso, ~cs_n)
|
||||
else:
|
||||
self.comb += pads.miso.eq(miso)
|
||||
else:
|
||||
raise ValueError("`wires` must be 2, 3, or 4")
|
||||
|
||||
clk_last = Signal()
|
||||
clk_rising = Signal()
|
||||
clk_falling = Signal()
|
||||
self.sync += clk_last.eq(clk)
|
||||
self.comb += clk_rising.eq(clk & ~clk_last)
|
||||
self.comb += clk_falling.eq(~clk & clk_last)
|
||||
|
||||
fsm = FSM(reset_state="IDLE")
|
||||
fsm = ResetInserter()(fsm)
|
||||
self.submodules += fsm
|
||||
self.comb += fsm.reset.eq(cs_n)
|
||||
|
||||
# Connect the Wishbone bus up to our values
|
||||
self.comb += [
|
||||
self.wishbone.adr.eq(address[2:]),
|
||||
self.wishbone.dat_w.eq(value),
|
||||
self.wishbone.sel.eq(2**len(self.wishbone.sel) - 1)
|
||||
]
|
||||
|
||||
# Constantly have the counter increase, except when it's reset
|
||||
# in the IDLE state
|
||||
self.sync += If(cs_n, counter.eq(0)).Elif(clk_rising, counter.eq(counter + 1))
|
||||
|
||||
if wires == 2:
|
||||
fsm.act("IDLE",
|
||||
miso_en.eq(0),
|
||||
NextValue(miso, 1),
|
||||
If(clk_rising,
|
||||
NextValue(sync_byte, Cat(mosi, sync_byte))
|
||||
),
|
||||
If(sync_byte[0:7] == 0b101011,
|
||||
NextState("GET_TYPE_BYTE"),
|
||||
NextValue(counter, 0),
|
||||
NextValue(command, mosi),
|
||||
)
|
||||
)
|
||||
elif wires == 3 or wires == 4:
|
||||
fsm.act("IDLE",
|
||||
miso_en.eq(0),
|
||||
NextValue(miso, 1),
|
||||
If(clk_rising,
|
||||
NextState("GET_TYPE_BYTE"),
|
||||
NextValue(command, mosi),
|
||||
),
|
||||
)
|
||||
else:
|
||||
raise ValueError("invalid `wires` count: {}".format(wires))
|
||||
|
||||
# Determine if it's a read or a write
|
||||
fsm.act("GET_TYPE_BYTE",
|
||||
miso_en.eq(0),
|
||||
NextValue(miso, 1),
|
||||
If(counter == 8,
|
||||
# Write value
|
||||
If(command == 0,
|
||||
NextValue(wr, 1),
|
||||
NextState("READ_ADDRESS"),
|
||||
|
||||
# Read value
|
||||
).Elif(command == 1,
|
||||
NextValue(wr, 0),
|
||||
NextState("READ_ADDRESS"),
|
||||
).Else(
|
||||
NextState("END"),
|
||||
),
|
||||
),
|
||||
If(clk_rising,
|
||||
NextValue(command, Cat(mosi, command)),
|
||||
),
|
||||
)
|
||||
|
||||
fsm.act("READ_ADDRESS",
|
||||
miso_en.eq(0),
|
||||
If(counter == 32 + 8,
|
||||
If(wr,
|
||||
NextState("READ_VALUE"),
|
||||
).Else(
|
||||
NextState("READ_WISHBONE"),
|
||||
)
|
||||
),
|
||||
If(clk_rising,
|
||||
NextValue(address, Cat(mosi, address)),
|
||||
),
|
||||
)
|
||||
|
||||
fsm.act("READ_VALUE",
|
||||
miso_en.eq(0),
|
||||
If(counter == 32 + 32 + 8,
|
||||
NextState("WRITE_WISHBONE"),
|
||||
),
|
||||
If(clk_rising,
|
||||
NextValue(value, Cat(mosi, value)),
|
||||
),
|
||||
)
|
||||
|
||||
fsm.act("WRITE_WISHBONE",
|
||||
self.wishbone.stb.eq(1),
|
||||
self.wishbone.we.eq(1),
|
||||
self.wishbone.cyc.eq(1),
|
||||
miso_en.eq(1),
|
||||
If(self.wishbone.ack | self.wishbone.err,
|
||||
NextState("WAIT_BYTE_BOUNDARY"),
|
||||
),
|
||||
)
|
||||
|
||||
fsm.act("READ_WISHBONE",
|
||||
self.wishbone.stb.eq(1),
|
||||
self.wishbone.we.eq(0),
|
||||
self.wishbone.cyc.eq(1),
|
||||
miso_en.eq(1),
|
||||
If(self.wishbone.ack | self.wishbone.err,
|
||||
NextState("WAIT_BYTE_BOUNDARY"),
|
||||
NextValue(value, self.wishbone.dat_r),
|
||||
),
|
||||
)
|
||||
|
||||
fsm.act("WAIT_BYTE_BOUNDARY",
|
||||
miso_en.eq(1),
|
||||
If(clk_falling,
|
||||
If(counter[0:3] == 0,
|
||||
NextValue(miso, 0),
|
||||
# For writes, fill in the 0 byte response
|
||||
If(wr,
|
||||
NextState("WRITE_WR_RESPONSE"),
|
||||
).Else(
|
||||
NextState("WRITE_RESPONSE"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Write the "01" byte that indicates a response
|
||||
fsm.act("WRITE_RESPONSE",
|
||||
miso_en.eq(1),
|
||||
If(clk_falling,
|
||||
If(counter[0:3] == 0b111,
|
||||
NextValue(miso, 1),
|
||||
).Elif(counter[0:3] == 0,
|
||||
NextValue(write_offset, 31),
|
||||
NextState("WRITE_VALUE")
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Write the actual value
|
||||
fsm.act("WRITE_VALUE",
|
||||
miso_en.eq(1),
|
||||
NextValue(miso, value >> write_offset),
|
||||
If(clk_falling,
|
||||
NextValue(write_offset, write_offset - 1),
|
||||
If(write_offset == 0,
|
||||
NextValue(miso, 0),
|
||||
NextState("END"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
fsm.act("WRITE_WR_RESPONSE",
|
||||
miso_en.eq(1),
|
||||
If(clk_falling,
|
||||
If(counter[0:3] == 0,
|
||||
NextState("END"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
if wires == 3 or wires == 4:
|
||||
fsm.act("END",
|
||||
miso_en.eq(1),
|
||||
)
|
||||
elif wires == 2:
|
||||
fsm.act("END",
|
||||
miso_en.eq(0),
|
||||
NextValue(sync_byte, 0),
|
||||
NextState("IDLE")
|
||||
)
|
||||
else:
|
||||
raise ValueError("invalid `wires` count: {}".format(wires))
|
Loading…
Reference in New Issue