core/spi: add minimal SPISlave
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Thu, 29 Aug 2019 07:46:20 +0000 (09:46 +0200)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Thu, 29 Aug 2019 07:46:20 +0000 (09:46 +0200)
litex/soc/cores/spi.py
test/test_spi.py

index 5758397acb1a8b9f244fdf4ad1b0f38cd03c8ffd..b34fbf0308804018e7a2434aab3b91576e63289a 100644 (file)
@@ -4,6 +4,7 @@
 import math
 
 from migen import *
+from migen.genlib.cdc import MultiReg
 
 from litex.soc.interconnect.csr import *
 
@@ -145,3 +146,92 @@ class SPIMaster(Module, AutoCSR):
             self._status.status[SPI_STATUS_DONE].eq(self.done),
             self._miso.status.eq(self.miso),
         ]
+
+# SPI Slave ----------------------------------------------------------------------------------------
+
+class SPISlave(Module):
+    """4-wire SPI Slave
+
+    Provides a simple and minimal hardware SPI Slave with CPOL=0, CPHA=0 and build time configurable
+    data_width.
+    """
+    pads_layout = [("clk", 1), ("cs_n", 1), ("mosi", 1), ("miso", 1)]
+    def __init__(self, pads, data_width):
+        if pads is None:
+            pads = Record(self.pads_layout)
+        if not hasattr(pads, "cs_n"):
+            pads.cs_n = Signal()
+        self.pads       = pads
+        self.data_width = data_width
+
+        self.start    = Signal()
+        self.length   = Signal(8)
+        self.done     = Signal()
+        self.irq      = Signal()
+        self.mosi     = Signal(data_width)
+        self.miso     = Signal(data_width)
+        self.cs       = Signal()
+        self.loopback = Signal()
+
+        # # #
+
+        clk  = Signal()
+        cs   = Signal()
+        mosi = Signal()
+        miso = Signal()
+
+        # IOs <--> Internal (input resynchronization) ----------------------------------------------
+        self.specials += [
+            MultiReg(pads.clk, clk),
+            MultiReg(~pads.cs_n, cs),
+            MultiReg(pads.mosi, mosi),
+        ]
+        self.comb += pads.miso.eq(miso)
+
+        # Clock detection --------------------------------------------------------------------------
+        clk_d = Signal()
+        clk_rise = Signal()
+        clk_fall = Signal()
+        self.sync += clk_d.eq(clk)
+        self.comb += clk_rise.eq(clk & ~clk_d)
+        self.comb += clk_fall.eq(~clk & clk_d)
+
+        # Control FSM ------------------------------------------------------------------------------
+        self.submodules.fsm = fsm = FSM(reset_state="IDLE")
+        fsm.act("IDLE",
+            If(cs,
+                self.start.eq(1),
+                NextValue(self.length, 0),
+                NextState("XFER")
+            ).Else(
+                self.done.eq(1)
+            )
+        )
+        fsm.act("XFER",
+            If(~cs,
+                self.irq.eq(1),
+                NextState("IDLE")
+            ),
+            NextValue(self.length, self.length + clk_rise)
+        )
+
+        # Master In Slave Out (MISO) generation (generated on spi_clk falling edge) ----------------
+        miso_data = Signal(data_width)
+        self.sync += \
+            If(self.start,
+                miso_data.eq(self.miso)
+            ).Elif(cs & clk_fall,
+                miso_data.eq(Cat(Signal(), miso_data[:-1]))
+            )
+        self.comb += \
+            If(self.loopback,
+                miso.eq(mosi)
+            ).Else(
+                miso.eq(miso_data[-1]),
+            )
+
+        # Master Out Slave In (MOSI) capture (captured on spi_clk rising edge) ---------------------
+        self.sync += \
+            If(cs & clk_rise,
+                self.mosi.eq(Cat(mosi, self.mosi[:-1]))
+            )
index 5d101a6c30079cb58cd7685e5947e8013330d248..1cfffd57e53606e3a81f51939862a7f9162bda7e 100644 (file)
@@ -5,7 +5,7 @@ import unittest
 
 from migen import *
 
-from litex.soc.cores.spi import SPIMaster
+from litex.soc.cores.spi import SPIMaster, SPISlave
 
 
 class TestSPI(unittest.TestCase):
@@ -13,7 +13,7 @@ class TestSPI(unittest.TestCase):
         spi_master = SPIMaster(pads=None, data_width=32, sys_clk_freq=100e6, spi_clk_freq=5e6)
         self.assertEqual(hasattr(spi_master, "pads"), 1)
 
-    def test_spi_xfer_loopback(self):
+    def test_spi_master_xfer_loopback(self):
         def generator(dut):
             yield dut.loopback.eq(1)
             yield dut.mosi.eq(0xdeadbeef)
@@ -28,3 +28,39 @@ class TestSPI(unittest.TestCase):
 
         dut = SPIMaster(pads=None, data_width=32, sys_clk_freq=100e6, spi_clk_freq=5e6, with_csr=False)
         run_simulation(dut, generator(dut))
+
+    def test_spi_slave_syntax(self):
+        spi_slave = SPISlave(pads=None, data_width=32)
+        self.assertEqual(hasattr(spi_slave, "pads"), 1)
+
+    def test_spi_slave_xfer(self):
+        class DUT(Module):
+            def __init__(self):
+                pads = Record([("clk", 1), ("cs_n", 1), ("mosi", 1), ("miso", 1)])
+                self.submodules.master = SPIMaster(pads, data_width=32,
+                    sys_clk_freq=100e6, spi_clk_freq=5e6,
+                    with_csr=False)
+                self.submodules.slave  = SPISlave(pads, data_width=32)
+
+        def master_generator(dut):
+            yield dut.master.mosi.eq(0xdeadbeef)
+            yield dut.master.length.eq(32)
+            yield dut.master.start.eq(1)
+            yield
+            yield dut.master.start.eq(0)
+            yield
+            while (yield dut.master.done) == 0:
+                yield
+            self.assertEqual((yield dut.master.miso), 0x12345678)
+
+        def slave_generator(dut):
+            yield dut.slave.miso.eq(0x12345678)
+            while (yield dut.slave.start) == 0:
+                yield
+            while (yield dut.slave.done) == 0:
+                yield
+            self.assertEqual((yield dut.slave.mosi), 0xdeadbeef)
+            self.assertEqual((yield dut.slave.length), 32)
+
+        dut = DUT()
+        run_simulation(dut, [master_generator(dut), slave_generator(dut)])