diff --git a/litex/soc/cores/spi/__init__.py b/litex/soc/cores/spi/__init__.py new file mode 100644 index 000000000..3c2350bc1 --- /dev/null +++ b/litex/soc/cores/spi/__init__.py @@ -0,0 +1,3 @@ +# SPI-Master/Slave. +from litex.soc.cores.spi.spi_master import SPIMaster +from litex.soc.cores.spi.spi_slave import SPISlave diff --git a/litex/soc/cores/spi.py b/litex/soc/cores/spi/spi_master.py similarity index 100% rename from litex/soc/cores/spi.py rename to litex/soc/cores/spi/spi_master.py diff --git a/litex/soc/cores/spi/spi_slave.py b/litex/soc/cores/spi/spi_slave.py new file mode 100644 index 000000000..6dfa03323 --- /dev/null +++ b/litex/soc/cores/spi/spi_slave.py @@ -0,0 +1,103 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2019-2020 Florent Kermarrec +# SPDX-License-Identifier: BSD-2-Clause + +import math + +from migen import * +from migen.genlib.cdc import MultiReg + +from litex.soc.interconnect.csr import * + +# 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() # o, Signal a start of SPI Xfer. + self.length = Signal(8) # o, Signal the length of the SPI Xfer (in bits). + self.done = Signal() # o, Signal that SPI Xfer is done/inactive. + self.irq = Signal() # o, Signal the end of a SPI Xfer. + self.mosi = Signal(data_width) # i, Data to send on SPI MOSI. + self.miso = Signal(data_width) # o, Data received on SPI MISO. + self.loopback = Signal() # i, Loopback enable. + + # # # + + 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])) + ) + ]