soc/cores: add new spi master, remove obsolete one
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Wed, 19 Apr 2017 08:16:10 +0000 (10:16 +0200)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Wed, 19 Apr 2017 08:22:35 +0000 (10:22 +0200)
litex/soc/cores/spi.py [new file with mode: 0644]
litex/soc/cores/spi/__init__.py [deleted file]
litex/soc/cores/spi/core.py [deleted file]

diff --git a/litex/soc/cores/spi.py b/litex/soc/cores/spi.py
new file mode 100644 (file)
index 0000000..d5f9278
--- /dev/null
@@ -0,0 +1,373 @@
+from itertools import product
+
+from litex.gen import *
+from litex.soc.interconnect.csr import *
+
+
+class SPIClockGen(Module):
+    def __init__(self, width):
+        self.load = Signal(width)
+        self.bias = Signal()  # bias this clock phase to longer times
+        self.edge = Signal()
+        self.clk = Signal(reset=1)
+
+        cnt = Signal.like(self.load)
+        bias = Signal()
+        zero = Signal()
+        self.comb += [
+            zero.eq(cnt == 0),
+            self.edge.eq(zero & ~bias),
+        ]
+        self.sync += [
+            If(zero,
+                bias.eq(0),
+            ).Else(
+                cnt.eq(cnt - 1),
+            ),
+            If(self.edge,
+                cnt.eq(self.load[1:]),
+                bias.eq(self.load[0] & (self.clk ^ self.bias)),
+                self.clk.eq(~self.clk),
+            )
+        ]
+
+
+class SPIRegister(Module):
+    def __init__(self, width):
+        self.data = Signal(width)
+        self.o = Signal()
+        self.i = Signal()
+        self.lsb = Signal()
+        self.shift = Signal()
+        self.sample = Signal()
+
+        self.comb += [
+            self.o.eq(Mux(self.lsb, self.data[0], self.data[-1])),
+        ]
+        self.sync += [
+            If(self.shift,
+                If(self.lsb,
+                    self.data[:-1].eq(self.data[1:]),
+                ).Else(
+                    self.data[1:].eq(self.data[:-1]),
+                )
+            ),
+            If(self.sample,
+                If(self.lsb,
+                    self.data[-1].eq(self.i),
+                ).Else(
+                    self.data[0].eq(self.i),
+                )
+            )
+        ]
+
+
+class SPIBitCounter(Module):
+    def __init__(self, width):
+        self.n_read = Signal(width)
+        self.n_write = Signal(width)
+        self.read = Signal()
+        self.write = Signal()
+        self.done = Signal()
+
+        self.comb += [
+            self.write.eq(self.n_write != 0),
+            self.read.eq(self.n_read != 0),
+            self.done.eq(~(self.write | self.read)),
+        ]
+        self.sync += [
+            If(self.write,
+                self.n_write.eq(self.n_write - 1),
+            ).Elif(self.read,
+                self.n_read.eq(self.n_read - 1),
+            )
+        ]
+
+
+class SPIMachine(Module):
+    def __init__(self, data_width, clock_width, bits_width):
+        ce = CEInserter()
+        self.submodules.cg = ce(SPIClockGen(clock_width))
+        self.submodules.reg = ce(SPIRegister(data_width))
+        self.submodules.bits = ce(SPIBitCounter(bits_width))
+        self.div_write = Signal.like(self.cg.load)
+        self.div_read = Signal.like(self.cg.load)
+        self.clk_phase = Signal()
+        self.start = Signal()
+        self.cs = Signal()
+        self.oe = Signal()
+        self.done = Signal()
+
+        # # #
+
+        fsm = CEInserter()(FSM("IDLE"))
+        self.submodules += fsm
+
+        fsm.act("IDLE",
+            If(self.start,
+                If(self.clk_phase,
+                    NextState("WAIT"),
+                ).Else(
+                    NextState("SETUP"),
+                )
+            )
+        )
+        fsm.act("SETUP",
+            self.reg.sample.eq(1),
+            NextState("HOLD"),
+        )
+        fsm.act("HOLD",
+            If(self.bits.done & ~self.start,
+                If(self.clk_phase,
+                    NextState("IDLE"),
+                ).Else(
+                    NextState("WAIT"),
+                )
+            ).Else(
+                self.reg.shift.eq(~self.start),
+                NextState("SETUP"),
+            )
+        )
+        fsm.act("WAIT",
+            If(self.bits.done,
+                NextState("IDLE"),
+            ).Else(
+                NextState("SETUP"),
+            )
+        )
+
+        write0 = Signal()
+        self.sync += [
+            If(self.cg.edge & self.reg.shift,
+                write0.eq(self.bits.write),
+            )
+        ]
+        self.comb += [
+            self.cg.ce.eq(self.start | self.cs | ~self.cg.edge),
+            If(self.bits.write | ~self.bits.read,
+                self.cg.load.eq(self.div_write),
+            ).Else(
+                self.cg.load.eq(self.div_read),
+            ),
+            self.cg.bias.eq(self.clk_phase),
+            fsm.ce.eq(self.cg.edge),
+            self.cs.eq(~fsm.ongoing("IDLE")),
+            self.reg.ce.eq(self.cg.edge),
+            self.bits.ce.eq(self.cg.edge & self.reg.sample),
+            self.done.eq(self.cg.edge & self.bits.done & fsm.ongoing("HOLD")),
+            self.oe.eq(write0 | self.bits.write),
+        ]
+
+
+class SPIMasterCore(Module):
+    """SPI Master Core.
+
+    Notes:
+        * M = 32 is the data width (maximum write bits, maximum read bits)
+        * Every transfer consists of a write_length 0-M bit write followed
+          by a read_length 0-M bit read.
+        * cs_n is asserted at the beginning and deasserted at the end of the
+          transfer if there is no other transfer pending.
+        * cs_n handling is agnostic to whether it is one-hot or decoded
+          somewhere downstream. If it is decoded, "cs_n all deasserted"
+          should be handled accordingly (no slave selected).
+          If it is one-hot, asserting multiple slaves should only be attempted
+          if miso is either not connected between slaves, or open collector,
+          or correctly multiplexed externally.
+        * If config.cs_polarity == 0 (cs active low, the default),
+          "cs_n all deasserted" means "all cs_n bits high".
+        * cs is not mandatory in pads. Framing and chip selection can also
+          be handled independently through other means.
+        * If there is a miso wire in pads, the input and output can be done
+          with two signals (a.k.a. 4-wire SPI), else mosi must be used for
+          both output and input (a.k.a. 3-wire SPI) and config.half_duplex
+          must to be set when reading data is desired.
+        * For 4-wire SPI only the sum of read_length and write_length matters.
+          The behavior is the same no matter how the total transfer length is
+          divided between the two. For 3-wire SPI, the direction of mosi/miso
+          is switched from output to input after write_len cycles, at the
+          "shift_out" clk edge corresponding to bit write_length + 1 of the
+          transfer.
+        * The first bit output on mosi is always the MSB/LSB (depending on
+          config.lsb_first) of the miso_data signal, independent of
+          xfer.write_len. The last bit input from miso always ends up in
+          the LSB/MSB (respectively) of the misoc_data signal, independent of
+          read_len.
+        * Data output on mosi in 4-wire SPI during the read cycles is what
+          is found in the data register at the time.
+          Data in the data register outside the least/most (depending
+          on config.lsb_first) significant read_length bits is what is
+          seen on miso during the write cycles.
+        * The SPI data register is double-buffered: Once a transfer has
+          started, new write data can be written, queuing a new transfer.
+          Transfers submitted this way are chained and executed without
+          deasserting cs. Once a transfer completes, the previous transfer's
+          read data is available in the data register.
+        * Changes to config signal take effect immediately. Changes
+          to xfer_* signals are synchronized to the start of a transfer.
+
+    Transaction Sequence:
+        * If desired, set the config signal to set up the core.
+        * If designed, set the xfer signal to set up lengths and cs_n.
+        * Set the miso_data signal (not required for zero-length writes),
+        * Set start signal to 1
+        * Wait for active and pending signals to be 0.
+        * If desired, use the misoc_data signal corresponding to the last
+          completed transfer.
+
+    Core IOs:
+
+    config signal:
+        1 offline: all pins high-z (reset=1)
+        1 active: cs/transfer active (read-only)
+        1 pending: transfer pending in intermediate buffer (read-only)
+        1 cs_polarity: active level of chip select (reset=0)
+        1 clk_polarity: idle level of clk (reset=0)
+        1 clk_phase: first edge after cs assertion to sample data on (reset=0)
+            (clk_polarity, clk_phase) == (CPOL, CPHA) in Freescale language.
+            (0, 0): idle low, output on falling, input on rising
+            (0, 1): idle low, output on rising, input on falling
+            (1, 0): idle high, output on rising, input on falling
+            (1, 1): idle high, output on falling, input on rising
+            There is never a clk edge during a cs edge.
+        1 lsb_first: LSB is the first bit on the wire (reset=0)
+        1 half_duplex: 3-wire SPI, in/out on mosi (reset=0)
+        8 undefined
+        8 div_write: counter load value to divide this module's clock
+            to generate the SPI write clk (reset=0)
+            f_clk/f_spi_write == div_write + 2
+        8 div_read: ditto for the read clock
+
+    xfer_config signal:
+        16 cs: active high bit mask of chip selects to assert (reset=0)
+        6 write_len: 0-M bits (reset=0)
+        2 undefined
+        6 read_len: 0-M bits (reset=0)
+        2 undefined
+
+    xfer_mosi/miso_data signal:
+        M write/read data (reset=0)
+    """
+    def __init__(self, pads):
+        self.config = Record([
+            ("offline", 1),
+            ("padding0", 2),
+            ("cs_polarity", 1),
+            ("clk_polarity", 1),
+            ("clk_phase", 1),
+            ("lsb_first", 1),
+            ("half_duplex", 1),
+            ("padding1", 8),
+            ("div_write", 8),
+            ("div_read", 8),
+        ])
+        self.config.offline.reset = 1
+
+        self.xfer = Record([
+            ("cs", 16),
+            ("write_length", 6),
+            ("padding0", 2),
+            ("read_length", 6),
+            ("padding1", 2),
+        ])
+
+        self.start = Signal()
+        self.active = Signal()
+        self.pending = Signal()
+        self.mosi_data = Signal(32)
+        self.miso_data = Signal(32)
+
+        # # #
+
+        self.submodules.machine = machine = SPIMachine(
+            data_width=32,
+            clock_width=len(self.config.div_read),
+            bits_width=len(self.xfer.read_length))
+
+        pending = Signal()
+        cs = Signal.like(self.xfer.cs)
+        data_read = Signal.like(machine.reg.data)
+        data_write = Signal.like(machine.reg.data)
+
+        self.comb += [
+            self.miso_data.eq(data_read),
+            machine.start.eq(pending & (~machine.cs | machine.done)),
+            machine.clk_phase.eq(self.config.clk_phase),
+            machine.reg.lsb.eq(self.config.lsb_first),
+            machine.div_write.eq(self.config.div_write),
+            machine.div_read.eq(self.config.div_read),
+        ]
+        self.sync += [
+            If(machine.done,
+                data_read.eq(machine.reg.data),
+            ),
+            If(machine.start,
+                cs.eq(self.xfer.cs),
+                machine.bits.n_write.eq(self.xfer.write_length),
+                machine.bits.n_read.eq(self.xfer.read_length),
+                machine.reg.data.eq(data_write),
+                pending.eq(0),
+            ),
+            If(self.start,
+                data_write.eq(self.mosi_data),
+                pending.eq(1)
+            ),
+            self.active.eq(machine.cs),
+            self.pending.eq(pending),
+        ]
+
+        # I/O
+        if hasattr(pads, "cs_n"):
+            cs_n_t = TSTriple(len(pads.cs_n))
+            self.specials += cs_n_t.get_tristate(pads.cs_n)
+            self.comb += [
+                cs_n_t.oe.eq(~self.config.offline),
+                cs_n_t.o.eq((cs & Replicate(machine.cs, len(cs))) ^
+                            Replicate(~self.config.cs_polarity, len(cs))),
+            ]
+
+        clk_t = TSTriple()
+        self.specials += clk_t.get_tristate(pads.clk)
+        self.comb += [
+            clk_t.oe.eq(~self.config.offline),
+            clk_t.o.eq((machine.cg.clk & machine.cs) ^ self.config.clk_polarity),
+        ]
+
+        mosi_t = TSTriple()
+        self.specials += mosi_t.get_tristate(pads.mosi)
+        self.comb += [
+            mosi_t.oe.eq(~self.config.offline & machine.cs &
+                         (machine.oe | ~self.config.half_duplex)),
+            mosi_t.o.eq(machine.reg.o),
+            machine.reg.i.eq(Mux(self.config.half_duplex, mosi_t.i,
+                             getattr(pads, "miso", mosi_t.i))),
+        ]
+
+
+class SPIMaster(Module, AutoCSR):
+    """SPI Master."""
+    def __init__(self, pads, interface="csr"):
+        self.submodules.core = core = SPIMasterCore(pads)
+
+        # # #
+
+        if interface == "csr":
+            self.config = CSRStorage(32)
+            self.xfer = CSRStorage(32)
+            self.start = CSR()
+            self.active = CSRStatus()
+            self.pending = CSRStatus()
+            self.mosi_data = CSRStorage(32)
+            self.miso_data = CSRStatus(32)
+
+            self.comb += [
+                core.config.raw_bits().eq(self.config.storage),
+                core.xfer.raw_bits().eq(self.xfer.storage),
+                core.start.eq(self.start.re & self.start.r),
+                self.active.status.eq(core.active),
+                self.pending.status.eq(core.pending),
+                core.mosi_data.eq(self.mosi_data.storage),
+                self.miso_data.status.eq(core.miso_data)
+            ]
+        else:
+            raise NotImplementedError
diff --git a/litex/soc/cores/spi/__init__.py b/litex/soc/cores/spi/__init__.py
deleted file mode 100644 (file)
index 05e31b2..0000000
+++ /dev/null
@@ -1 +0,0 @@
-from litex.soc.cores.spi.core import SPIMaster
diff --git a/litex/soc/cores/spi/core.py b/litex/soc/cores/spi/core.py
deleted file mode 100644 (file)
index 3bc515f..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-from litex.gen import *
-from litex.soc.interconnect.csr import *
-
-
-class SPIMaster(Module, AutoCSR):
-    def __init__(self, pads, width=24, div=2, cpha=1):
-        self.pads = pads
-
-        self._ctrl = CSR()
-        self._length = CSRStorage(8)
-        self._status = CSRStatus()
-        if hasattr(pads, "mosi"):
-            self._mosi = CSRStorage(width)
-        if hasattr(pads, "miso"):
-            self._miso = CSRStatus(width)
-
-        self.irq = Signal()
-
-        ###
-
-        # ctrl
-        start = Signal()
-        length = self._length.storage
-        enable_cs = Signal()
-        enable_shift = Signal()
-        done = Signal()
-
-        self.comb += [
-            start.eq(self._ctrl.re & self._ctrl.r[0]),
-            self._status.status.eq(done)
-        ]
-
-        # clk
-        i = Signal(max=div)
-        set_clk = Signal()
-        clr_clk = Signal()
-        self.sync += [
-            If(set_clk,
-                pads.clk.eq(enable_cs)
-            ),
-            If(clr_clk,
-                pads.clk.eq(0),
-                i.eq(0)
-            ).Else(
-                i.eq(i + 1),
-            )
-        ]
-
-        self.comb += [
-            set_clk.eq(i == (div//2-1)),
-            clr_clk.eq(i == (div-1))
-        ]
-
-        # fsm
-        cnt = Signal(8)
-        clr_cnt = Signal()
-        inc_cnt = Signal()
-        self.sync += \
-            If(clr_cnt,
-                cnt.eq(0)
-            ).Elif(inc_cnt,
-                cnt.eq(cnt+1)
-            )
-
-        fsm = FSM(reset_state="IDLE")
-        self.submodules += fsm
-        fsm.act("IDLE",
-            If(start,
-                NextState("WAIT_CLK")
-            ),
-            done.eq(1),
-            clr_cnt.eq(1)
-        )
-        fsm.act("WAIT_CLK",
-            If(clr_clk,
-                NextState("SHIFT")
-            ),
-        )
-        fsm.act("SHIFT",
-            If(cnt == length,
-                NextState("END")
-            ).Else(
-                inc_cnt.eq(clr_clk),
-            ),
-            enable_cs.eq(1),
-            enable_shift.eq(1),
-        )
-        fsm.act("END",
-            If(set_clk,
-                NextState("IDLE")
-            ),
-            enable_shift.eq(1),
-            self.irq.eq(1)
-        )
-
-        # miso
-        if hasattr(pads, "miso"):
-            miso = Signal()
-            sr_miso = Signal(width)
-
-            # (cpha = 1: capture on clk falling edge)
-            if cpha:
-                self.sync += \
-                    If(enable_shift,
-                        If(clr_clk,
-                            miso.eq(pads.miso),
-                        ).Elif(set_clk,
-                            sr_miso.eq(Cat(miso, sr_miso[:-1]))
-                        )
-                    )
-            # (cpha = 0: capture on clk rising edge)
-            else:
-                self.sync += \
-                    If(enable_shift,
-                        If(set_clk,
-                            miso.eq(pads.miso),
-                        ).Elif(clr_clk,
-                            sr_miso.eq(Cat(miso, sr_miso[:-1]))
-                        )
-                    )
-            self.comb += self._miso.status.eq(sr_miso)
-
-        # mosi
-        if hasattr(pads, "mosi"):
-            sr_mosi = Signal(width)
-
-            # (cpha = 1: propagated on clk rising edge)
-            if cpha:
-                self.sync += \
-                    If(start,
-                        sr_mosi.eq(self._mosi.storage)
-                    ).Elif(clr_clk & enable_shift,
-                        sr_mosi.eq(Cat(Signal(), sr_mosi[:-1]))
-                    ).Elif(set_clk,
-                        pads.mosi.eq(sr_mosi[-1])
-                    )
-
-            # (cpha = 0: propagated on clk falling edge)
-            else:
-                self.sync += [
-                    If(start,
-                        sr_mosi.eq(self._mosi.storage)
-                    ).Elif(set_clk & enable_shift,
-                        sr_mosi.eq(Cat(Signal(), sr_mosi[:-1]))
-                    ).Elif(clr_clk,
-                        pads.mosi.eq(sr_mosi[-1])
-                    )
-                ]
-
-        # cs_n
-        self.comb += pads.cs_n.eq(~enable_cs)