From 41fe7cae0b5341531f609943d5f70afd6256cfc8 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Thu, 29 Aug 2019 09:46:20 +0200 Subject: [PATCH] core/spi: add minimal SPISlave --- litex/soc/cores/spi.py | 90 ++++++++++++++++++++++++++++++++++++++++++ test/test_spi.py | 40 ++++++++++++++++++- 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/litex/soc/cores/spi.py b/litex/soc/cores/spi.py index 5758397ac..b34fbf030 100644 --- a/litex/soc/cores/spi.py +++ b/litex/soc/cores/spi.py @@ -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])) + ) diff --git a/test/test_spi.py b/test/test_spi.py index 5d101a6c3..1cfffd57e 100644 --- a/test/test_spi.py +++ b/test/test_spi.py @@ -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)])