cores: add simple and minimal hardware SPI Master with CPOL=0, CPHA=0 and build time...
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 5 Jul 2019 13:49:17 +0000 (15:49 +0200)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 5 Jul 2019 13:50:58 +0000 (15:50 +0200)
litex/soc/cores/spi.py [new file with mode: 0644]
test/test_spi.py [new file with mode: 0644]

diff --git a/litex/soc/cores/spi.py b/litex/soc/cores/spi.py
new file mode 100644 (file)
index 0000000..64c2000
--- /dev/null
@@ -0,0 +1,128 @@
+# This file is Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
+# License: BSD
+
+import math
+
+from migen import *
+
+from litex.soc.interconnect.csr import *
+
+# SPI Master ---------------------------------------------------------------------------------------
+
+SPI_CONTROL_START  = 0
+SPI_CONTROL_LENGTH = 8
+
+SPI_STATUS_DONE = 0
+
+class SPIMaster(Module, AutoCSR):
+    """4-wire SPI Master
+
+    Provides a simple and minimal hardware SPI Master with CPOL=0, CPHA=0 and build time
+    configurable data_width and frequency.
+    """
+    pads_layout = [("clk", 1), ("cs_n", 1), ("mosi", 1), ("miso", 1)]
+    def __init__(self, pads, data_width, sys_clk_freq, spi_clk_freq):
+        if pads is None:
+            pads = Record(self.pads_layout)
+        self.pads = pads
+
+        self._control = CSR(16)
+        self._status  = CSRStatus(1)
+        self._mosi    = CSRStorage(data_width)
+        self._miso    = CSRStatus(data_width)
+        self._cs      = CSRStorage(len(pads.cs_n), reset=1)
+
+        self.irq = Signal()
+
+        # # #
+
+        bits  = Signal(8)
+        cs    = 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.r[SPI_CONTROL_START])
+        self.sync += If(self._control.re, length.eq(self._control.r[SPI_CONTROL_LENGTH:]))
+
+        # XFER done
+        self.comb += self._status.status[SPI_STATUS_DONE].eq(done)
+
+        # Clock generation -------------------------------------------------------------------------
+        clk_divide  = math.ceil(sys_clk_freq/spi_clk_freq)
+        clk_divider = Signal(max=clk_divide)
+        clk_rise    = Signal()
+        clk_fall    = Signal()
+        self.sync += [
+            If(clk_rise,   pads.clk.eq(cs)),
+            If(clk_fall, pads.clk.eq(0)),
+            If(clk_fall,
+                clk_divider.eq(0)
+            ).Else(
+                clk_divider.eq(clk_divider + 1)
+            )
+        ]
+        self.comb += clk_rise.eq(clk_divider == (clk_divide//2 - 1))
+        self.comb += clk_fall.eq(clk_divider == (clk_divide - 1))
+
+        # Control FSM ------------------------------------------------------------------------------
+        self.submodules.fsm = fsm = FSM(reset_state="IDLE")
+        fsm.act("IDLE",
+            done.eq(1),
+            If(start,
+                NextValue(bits, 0),
+                NextState("WAIT-CLK-FALL")
+            )
+        )
+        fsm.act("WAIT-CLK-FALL",
+            If(clk_fall,
+                NextState("XFER")
+            )
+        )
+        fsm.act("XFER",
+            If(bits == length,
+                NextState("END")
+            ).Elif(clk_fall,
+                NextValue(bits, bits + 1)
+            ),
+            cs.eq(1),
+            shift.eq(1)
+        )
+        fsm.act("END",
+            If(clk_rise,
+                NextState("IDLE")
+            ),
+            shift.eq(1),
+            self.irq.eq(1)
+        )
+
+        # Chip Select generation -------------------------------------------------------------------
+        for i in range(len(pads.cs_n)):
+            self.comb += pads.cs_n[i].eq(~self._cs.storage[i] | ~cs)
+
+        # Master Out Slave In (MOSI) generation (generated on spi_clk falling edge) ---------------
+        mosi_data = Signal(data_width)
+        self.sync += \
+            If(start,
+                mosi_data.eq(self._mosi.storage)
+            ).Elif(clk_rise & shift,
+                mosi_data.eq(Cat(Signal(), mosi_data[:-1]))
+            ).Elif(clk_fall,
+                pads.mosi.eq(mosi_data[-1])
+            )
+
+        # Master In Slave Out (MISO) capture (captured on spi_clk rising edge) --------------------
+        miso      = Signal()
+        miso_data = self._miso.status
+        self.sync += \
+            If(shift,
+                If(clk_rise,
+                    miso.eq(pads.miso),
+                ).Elif(clk_fall,
+                    miso_data.eq(Cat(miso, miso_data[:-1]))
+                )
+            )
diff --git a/test/test_spi.py b/test/test_spi.py
new file mode 100644 (file)
index 0000000..f2b61fc
--- /dev/null
@@ -0,0 +1,13 @@
+# This file is Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
+# License: BSD
+
+import unittest
+
+from migen import *
+
+from litex.soc.cores.spi import SPIMaster
+
+class TestSPI(unittest.TestCase):
+    def test_spi_master_syntax(self):
+        spi_master = SPIMaster(pads=None, data_width=32, sys_clk_freq=100e6, spi_clk_freq=5e6)
+        self.assertEqual(hasattr(spi_master, "pads"), 1)