cores/spi: move CSR control/status to add_control method, add loopback capability and simple xfer loopback test

Moving control/status registers to add_control method allow using SPIMaster directly with exposed signals.
Add loopback capability (mostly for simulation, but can be useful on hardware too).
This commit is contained in:
Florent Kermarrec 2019-07-13 12:54:24 +02:00
parent ee8fec10ff
commit 769d15d433
2 changed files with 64 additions and 31 deletions

View File

@ -21,45 +21,39 @@ class SPIMaster(Module, AutoCSR):
configurable data_width and frequency. configurable data_width and frequency.
""" """
pads_layout = [("clk", 1), ("cs_n", 1), ("mosi", 1), ("miso", 1)] pads_layout = [("clk", 1), ("cs_n", 1), ("mosi", 1), ("miso", 1)]
def __init__(self, pads, data_width, sys_clk_freq, spi_clk_freq): def __init__(self, pads, data_width, sys_clk_freq, spi_clk_freq, with_control=True):
if pads is None: if pads is None:
pads = Record(self.pads_layout) pads = Record(self.pads_layout)
self.pads = pads if not hasattr(pads, "cs_n"):
pads.cs_n = Signal()
self.pads = pads
self.data_width = data_width
self._control = CSRStorage(16) self.start = Signal()
self._status = CSRStatus(1) self.length = Signal(8)
self._mosi = CSRStorage(data_width) self.done = Signal()
self._miso = CSRStatus(data_width) self.irq = Signal()
if hasattr(pads, "cs_n"): self.mosi = Signal(data_width)
self._cs = CSRStorage(len(pads.cs_n), reset=1) self.miso = Signal(data_width)
self.cs = Signal(len(pads.cs_n), reset=1)
self.loopback = Signal()
self.irq = Signal() if with_control:
self.add_control()
# # # # # #
bits = Signal(8) bits = Signal(8)
cs = Signal() xfer = Signal()
shift = Signal() shift = Signal()
# Control/Status ---------------------------------------------------------------------------
start = Signal()
length = Signal(8)
done = Signal()
# XFER start: initialize SPI XFER on SPI_CONTROL_START write and latch length
self.comb += start.eq(self._control.re & self._control.storage[SPI_CONTROL_START])
self.sync += If(self._control.re, length.eq(self._control.storage[SPI_CONTROL_LENGTH:]))
# XFER done
self.comb += self._status.status[SPI_STATUS_DONE].eq(done)
# Clock generation ------------------------------------------------------------------------- # Clock generation -------------------------------------------------------------------------
clk_divide = math.ceil(sys_clk_freq/spi_clk_freq) clk_divide = math.ceil(sys_clk_freq/spi_clk_freq)
clk_divider = Signal(max=clk_divide) clk_divider = Signal(max=clk_divide)
clk_rise = Signal() clk_rise = Signal()
clk_fall = Signal() clk_fall = Signal()
self.sync += [ self.sync += [
If(clk_rise, pads.clk.eq(cs)), If(clk_rise, pads.clk.eq(xfer)),
If(clk_fall, pads.clk.eq(0)), If(clk_fall, pads.clk.eq(0)),
If(clk_fall, If(clk_fall,
clk_divider.eq(0) clk_divider.eq(0)
@ -73,8 +67,8 @@ class SPIMaster(Module, AutoCSR):
# Control FSM ------------------------------------------------------------------------------ # Control FSM ------------------------------------------------------------------------------
self.submodules.fsm = fsm = FSM(reset_state="IDLE") self.submodules.fsm = fsm = FSM(reset_state="IDLE")
fsm.act("IDLE", fsm.act("IDLE",
done.eq(1), self.done.eq(1),
If(start, If(self.start,
NextValue(bits, 0), NextValue(bits, 0),
NextState("WAIT-CLK-FALL") NextState("WAIT-CLK-FALL")
) )
@ -85,12 +79,12 @@ class SPIMaster(Module, AutoCSR):
) )
) )
fsm.act("XFER", fsm.act("XFER",
If(bits == length, If(bits == self.length,
NextState("END") NextState("END")
).Elif(clk_fall, ).Elif(clk_fall,
NextValue(bits, bits + 1) NextValue(bits, bits + 1)
), ),
cs.eq(1), xfer.eq(1),
shift.eq(1) shift.eq(1)
) )
fsm.act("END", fsm.act("END",
@ -104,13 +98,13 @@ class SPIMaster(Module, AutoCSR):
# Chip Select generation ------------------------------------------------------------------- # Chip Select generation -------------------------------------------------------------------
if hasattr(pads, "cs_n"): if hasattr(pads, "cs_n"):
for i in range(len(pads.cs_n)): for i in range(len(pads.cs_n)):
self.comb += pads.cs_n[i].eq(~self._cs.storage[i] | ~cs) self.comb += pads.cs_n[i].eq(~self.cs[i] | ~xfer)
# Master Out Slave In (MOSI) generation (generated on spi_clk falling edge) --------------- # Master Out Slave In (MOSI) generation (generated on spi_clk falling edge) ---------------
mosi_data = Signal(data_width) mosi_data = Signal(data_width)
self.sync += \ self.sync += \
If(start, If(self.start,
mosi_data.eq(self._mosi.storage) mosi_data.eq(self.mosi)
).Elif(clk_rise & shift, ).Elif(clk_rise & shift,
mosi_data.eq(Cat(Signal(), mosi_data[:-1])) mosi_data.eq(Cat(Signal(), mosi_data[:-1]))
).Elif(clk_fall, ).Elif(clk_fall,
@ -119,7 +113,7 @@ class SPIMaster(Module, AutoCSR):
# Master In Slave Out (MISO) capture (captured on spi_clk rising edge) -------------------- # Master In Slave Out (MISO) capture (captured on spi_clk rising edge) --------------------
miso = Signal() miso = Signal()
miso_data = self._miso.status miso_data = self.miso
self.sync += \ self.sync += \
If(shift, If(shift,
If(clk_rise, If(clk_rise,
@ -128,3 +122,25 @@ class SPIMaster(Module, AutoCSR):
miso_data.eq(Cat(miso, miso_data[:-1])) miso_data.eq(Cat(miso, miso_data[:-1]))
) )
) )
# Loopback ---------------------------------------------------------------------------------
self.comb += If(self.loopback, pads.miso.eq(pads.mosi))
def add_control(self):
self._control = CSRStorage(16)
self._status = CSRStatus()
self._mosi = CSRStorage(self.data_width)
self._miso = CSRStatus(self.data_width)
self._cs = CSRStorage(len(self.cs), reset=1)
self._loopback = CSRStorage()
self.comb += [
self.start.eq(self._control.re & self._control.storage[SPI_CONTROL_START]),
self.length.eq(self._control.storage[SPI_CONTROL_LENGTH:]),
self.mosi.eq(self._mosi.storage),
self.cs.eq(self._cs.storage),
self.loopback.eq(self._loopback.storage),
self._status.status[SPI_STATUS_DONE].eq(self.done),
self._miso.status.eq(self.miso),
]

View File

@ -7,7 +7,24 @@ from migen import *
from litex.soc.cores.spi import SPIMaster from litex.soc.cores.spi import SPIMaster
class TestSPI(unittest.TestCase): class TestSPI(unittest.TestCase):
def test_spi_master_syntax(self): def test_spi_master_syntax(self):
spi_master = SPIMaster(pads=None, data_width=32, sys_clk_freq=100e6, spi_clk_freq=5e6) spi_master = SPIMaster(pads=None, data_width=32, sys_clk_freq=100e6, spi_clk_freq=5e6)
self.assertEqual(hasattr(spi_master, "pads"), 1) self.assertEqual(hasattr(spi_master, "pads"), 1)
def test_spi_xfer_loopback(self):
def generator(dut):
yield dut.loopback.eq(1)
yield dut.mosi.eq(0xdeadbeef)
yield dut.length.eq(32)
yield dut.start.eq(1)
yield
yield dut.start.eq(0)
yield
while (yield dut.done) == 0:
yield
self.assertEqual((yield dut.miso), 0xdeadbeef)
dut = SPIMaster(pads=None, data_width=32, sys_clk_freq=100e6, spi_clk_freq=5e6, with_control=False)
run_simulation(dut, generator(dut))