diff --git a/litex/soc/cores/spi/spi_mmap.py b/litex/soc/cores/spi/spi_mmap.py index acb9300ed..f9ac6b160 100644 --- a/litex/soc/cores/spi/spi_mmap.py +++ b/litex/soc/cores/spi/spi_mmap.py @@ -35,6 +35,7 @@ SPI_SLOT_MODE_3 = 0b11 SPI_SLOT_LENGTH_32B = 0b00 SPI_SLOT_LENGTH_16B = 0b01 SPI_SLOT_LENGTH_8B = 0b10 +SPI_SLOT_LENGTH_24B = 0b11 SPI_SLOT_BITORDER_MSB_FIRST = 0b0 SPI_SLOT_BITORDER_LSB_FIRST = 0b1 @@ -206,7 +207,10 @@ class SPIMaster(LiteXModule): self.sync += [ If(miso_shift, miso_data.eq(Cat(miso, miso_data)) - ) + ), + If(self.start, + miso_data.eq(0) + ), ] self.comb += self.miso.eq(miso_data) @@ -239,10 +243,18 @@ class SPICtrl(LiteXModule): default_slot_bitorder = SPI_SLOT_BITORDER_MSB_FIRST, default_slot_loopback = 0b1, default_slot_divider = 2, + default_enable = 0b1, + default_slot_wait = 0, ): self.nslots = nslots self.slot_controls = [] - self.slot_status = [] + + version = "SPI0" + self._version = CSRStatus(size=32, description="""SPI Module Version.""", + reset=int.from_bytes(str.encode(version), 'little')) + + self.slot_count = CSRStatus(size=32, description="""SPI Module Slot Count.""", + reset=nslots) # Create TX/RX Control/Status registers. self.tx_control = CSRStorage(fields=[ @@ -302,6 +314,13 @@ class SPICtrl(LiteXModule): self.ev.rx.trigger.eq(self.rx_status.fields.level > self.rx_control.fields.threshold), ] + self.engine = CSRStorage(fields=[ + CSRField("enable", size=1, offset=0, values=[ + ("``0b0``", "SPI Engine Disabled."), + ("``0b1``", "SPI Engine Enabled."), + ], reset=default_enable), + ]) + # Create Slots Control/Status registers. for slot in range(nslots): control = CSRStorage(name=f"slot_control{slot}", fields=[ @@ -319,7 +338,7 @@ class SPICtrl(LiteXModule): ("``0b00``", "32-bit Max."), ("``0b01``", "16-bit Max."), ("``0b10``", " 8-bit Max."), - ("``0b11``", "Reserved."), + ("``0b11``", "24-bit Max."), ], reset=default_slot_length), CSRField("bitorder", size=1, offset=5, values=[ ("``0b0``", "MSB-First."), @@ -335,13 +354,15 @@ class SPICtrl(LiteXModule): ("``0x0002``", "SPI-Clk = Sys-Clk/2."), ("``0x0004``", "SPI-Clk = Sys-Clk/4."), ("``0xxxxx``", "SPI-Clk = Sys-Clk/xxxxx."), - ], reset=default_slot_divider) + ], reset=default_slot_divider), + CSRField("wait", size=16, offset=32, values=[ + ("``0x0000``", "No wait time."), + ("``0x0001``", "wait = 1 / Sys-Clk."), + ("``0xxxxx``", "wait = xxxx / Sys-Clk."), + ], reset=default_slot_wait), ]) - status = CSRStatus(name=f"slot_status{slot}") # CHECKME: Useful? setattr(self, f"slot_control{slot}", control) - setattr(self, f"slot_status{slot}", status) self.slot_controls.append(control) - self.slot_status.append(status) def get_ctrl(self, name, slot=None, cs=None): assert not ((slot is None) and (cs is None)) @@ -487,7 +508,7 @@ class SPIRXMMAP(LiteXModule): # SPI Engine --------------------------------------------------------------------------------------- class SPIEngine(LiteXModule): - def __init__(self, pads, ctrl, data_width, sys_clk_freq, default_enable=0b1): + def __init__(self, pads, ctrl, data_width, sys_clk_freq): self.sink = sink = stream.Endpoint(spi_layout( data_width = data_width, be_width = data_width//8, @@ -499,13 +520,6 @@ class SPIEngine(LiteXModule): cs_width = len(pads.cs_n) )) - self.control = CSRStorage(fields=[ - CSRField("enable", size=1, offset=0, values=[ - ("``0b0``", "SPI Engine Disabled."), - ("``0b1``", "SPI Engine Enabled."), - ], reset=default_enable), - ]) - # # # # SPI Master. @@ -532,6 +546,7 @@ class SPIEngine(LiteXModule): }) self.comb += Case(ctrl.get_ctrl("length", cs=sink.cs), { SPI_SLOT_LENGTH_32B : spi_length_max.eq(32), # 32-bit access max. + SPI_SLOT_LENGTH_24B : spi_length_max.eq(24), # 24-bit access max. SPI_SLOT_LENGTH_16B : spi_length_max.eq(16), # 16-bit access max. SPI_SLOT_LENGTH_8B : spi_length_max.eq( 8), # 8-bit access max. }) @@ -543,8 +558,15 @@ class SPIEngine(LiteXModule): ) ] + # Wait between transfers. + ctrl_wait = ctrl.get_ctrl("wait", cs=sink.cs) + wait_ticks = Signal.like(ctrl_wait) + wait_count = Signal.like(ctrl_wait) + self.comb += wait_ticks.eq(ctrl_wait) + cs_wait = Signal() + # SPI CS. (Use Manual CS to allow back-to-back Xfers). - self.comb += If(self.control.fields.enable & sink.valid, + self.comb += If(ctrl.engine.fields.enable & sink.valid & ~cs_wait, spi.cs.eq(sink.cs) ) @@ -555,7 +577,7 @@ class SPIEngine(LiteXModule): # Control-Path. self.fsm = fsm = FSM(reset_state="START") fsm.act("START", - If(self.control.fields.enable & sink.valid, + If(ctrl.engine.fields.enable & sink.valid, spi.start.eq(1), NextState("XFER") ) @@ -571,6 +593,20 @@ class SPIEngine(LiteXModule): source.be.eq(sink.be), If(source.ready, sink.ready.eq(1), + If(wait_ticks, + cs_wait.eq(1), + NextValue(wait_count, wait_ticks-1), + NextState("WAIT") + ).Else( + NextState("START") + ) + ) + ) + fsm.act("WAIT", + If(wait_count, + cs_wait.eq(1), + NextValue(wait_count, wait_count-1) + ).Else( NextState("START") ) ) @@ -580,9 +616,10 @@ class SPIEngine(LiteXModule): # MSB First. If(spi_bitorder == SPI_SLOT_BITORDER_MSB_FIRST, # TX copy/bitshift. - Case(spi_length, { + Case(spi.length, { 8 : spi.mosi[24:32].eq(sink.data[0: 8]), 16 : spi.mosi[16:32].eq(sink.data[0:16]), + 24 : spi.mosi[ 8:32].eq(sink.data[0:24]), 32 : spi.mosi[ 0:32].eq(sink.data[0:32]), }), # RX copy. @@ -593,9 +630,10 @@ class SPIEngine(LiteXModule): # TX copy. spi.mosi.eq(sink.data[::-1]), # RX copy/bitshift. - Case(spi_length, { + Case(spi.length, { 8 : source.data[0: 8].eq(spi.miso[::-1][24:32]), 16 : source.data[0:16].eq(spi.miso[::-1][16:32]), + 24 : source.data[0:24].eq(spi.miso[::-1][ 8:32]), 32 : source.data[0:32].eq(spi.miso[::-1][ 0:32]), }) ) diff --git a/litex/soc/interconnect/wishbone.py b/litex/soc/interconnect/wishbone.py index 943263f93..4ef9e55ef 100644 --- a/litex/soc/interconnect/wishbone.py +++ b/litex/soc/interconnect/wishbone.py @@ -90,6 +90,8 @@ class Interface(Record): yield self.bte.eq(bte) yield self.we.eq(1) yield from self._do_transaction() + if (yield self.err): + raise ValueError("bus error") def read(self, adr, cti=None, bte=None): yield self.adr.eq(adr) @@ -99,6 +101,8 @@ class Interface(Record): if bte is not None: yield self.bte.eq(bte) yield from self._do_transaction() + if (yield self.err): + raise ValueError("bus error") return (yield self.dat_r) def get_ios(self, bus_name="wb"): diff --git a/test/test_spi_mmap.py b/test/test_spi_mmap.py index 8915eef23..f4d36eeb4 100644 --- a/test/test_spi_mmap.py +++ b/test/test_spi_mmap.py @@ -6,32 +6,77 @@ # SPDX-License-Identifier: BSD-2-Clause import unittest -import random +import inspect -from migen import * +from migen import Record -from litex.gen.sim import * +from litex.gen.sim import run_simulation + +from litex.soc.cores.spi.spi_mmap import ( + SPIMaster, + SPIMMAP, + SPI_SLOT_BITORDER_LSB_FIRST, + SPI_SLOT_BITORDER_MSB_FIRST, + SPI_SLOT_LENGTH_16B, + SPI_SLOT_LENGTH_24B, + SPI_SLOT_LENGTH_32B, + SPI_SLOT_LENGTH_8B, + SPI_SLOT_MODE_0, + SPI_SLOT_MODE_3, +) + +verbose = None + + +def unittest_verbosity(): + """Return the verbosity setting of the currently running unittest + program, or 0 if none is running. + + """ + frame = inspect.currentframe() + while frame: + self = frame.f_locals.get("self") + if isinstance(self, unittest.TestProgram): + return self.verbosity + frame = frame.f_back + return 0 + + +def vprint(*args): + global verbose + if verbose is None: + verbose = unittest_verbosity() + if verbose > 1: + print(*args) + + +def vvprint(*args): + global verbose + if verbose is None: + verbose = unittest_verbosity() + if verbose > 2: + print(*args) -from litex.soc.cores.spi.spi_mmap import SPIMaster class TestSPIMMAP(unittest.TestCase): def test_spi_master(self): pads = Record([("clk", 1), ("cs_n", 4), ("mosi", 1), ("miso", 1)]) - dut = SPIMaster(pads=pads, data_width=32, sys_clk_freq=int(100e6)) + dut = SPIMaster(pads=pads, data_width=32, sys_clk_freq=int(100e6)) + def generator(dut): data = [ 0x12345678, - 0xdeadbeef, + 0xDEADBEEF, ] - #data = [ + # data = [ # 0x80000001, # 0x80000001, - #] + # ] # Config: Mode0, Loopback, Sys-Clk/4 yield dut.loopback.eq(1) yield dut.clk_divider.eq(4) - yield dut.mode.eq(0) + yield dut.mode.eq(SPI_SLOT_MODE_0) yield yield dut.mosi.eq(data[0]) yield dut.cs.eq(0b0001) @@ -49,7 +94,7 @@ class TestSPIMMAP(unittest.TestCase): # Config: Mode3, Loopback, Sys-Clk/4. yield dut.loopback.eq(1) yield dut.clk_divider.eq(4) - yield dut.mode.eq(3) + yield dut.mode.eq(SPI_SLOT_MODE_3) yield yield dut.mosi.eq(data[0]) yield dut.cs.eq(0b0001) @@ -67,7 +112,7 @@ class TestSPIMMAP(unittest.TestCase): # Config: Mode0, Loopback, Sys-Clk/8. yield dut.loopback.eq(1) yield dut.clk_divider.eq(8) - yield dut.mode.eq(0) + yield dut.mode.eq(SPI_SLOT_MODE_0) yield yield dut.mosi.eq(data[1]) yield dut.cs.eq(0b0001) @@ -85,7 +130,7 @@ class TestSPIMMAP(unittest.TestCase): # Config: Mode3, Loopback, Sys-Clk/8. yield dut.loopback.eq(1) yield dut.clk_divider.eq(8) - yield dut.mode.eq(3) + yield dut.mode.eq(SPI_SLOT_MODE_3) yield yield dut.mosi.eq(data[1]) yield dut.cs.eq(0b0001) @@ -101,3 +146,190 @@ class TestSPIMMAP(unittest.TestCase): print(f"mosi_data : {(yield dut.miso):08x}") run_simulation(dut, generator(dut), vcd_name="sim.vcd") + + def mmap_test(self, length, bitorder, data, vcd_name=None, sel_override=None, wait=0): + pads = Record([("clk", 1), ("cs_n", 4), ("mosi", 1), ("miso", 1)]) + dut = SPIMMAP( + pads=pads, + data_width=32, + sys_clk_freq=int(100e6), # only used for clock settle time! + tx_fifo_depth=32, + rx_fifo_depth=32, + ) + + def generator(dut): + # Minimal setup - spi_mmap ctrl defaults are everything enabled and: + # SPI_SLOT_MODE_3, SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_MSB_FIRST, loopback, divider=2, wait=0 + version = yield dut.ctrl._version.status + vprint(f"version: {version}") + vprint(f"slot_count: {(yield dut.ctrl.slot_count.status)}") + # yield dut.ctrl.slot_control0.fields.enable.eq(1) + # yield dut.ctrl.slot_control0.fields.mode.eq(SPI_SLOT_MODE_3) + yield dut.ctrl.slot_control0.fields.length.eq(length) + yield dut.ctrl.slot_control0.fields.bitorder.eq(bitorder) + yield dut.ctrl.slot_control1.fields.length.eq(length) + yield dut.ctrl.slot_control1.fields.bitorder.eq(bitorder) + # yield dut.ctrl.slot_control0.fields.loopback.eq(1) + # yield dut.ctrl.slot_control0.fields.divider.eq(2) + # yield dut.ctrl.slot_control0.fields.enable.eq(1) + yield dut.ctrl.slot_control0.fields.wait.eq(wait) + if length == SPI_SLOT_LENGTH_32B: + spi_length = 32 + sel = 0b1111 + width = 8 + if length == SPI_SLOT_LENGTH_24B: + spi_length = 24 + sel = 0b1111 + width = 6 + if length == SPI_SLOT_LENGTH_16B: + spi_length = 16 + sel = 0b0011 + width = 4 + if length == SPI_SLOT_LENGTH_8B: + spi_length = 8 + sel = 0b0001 + width = 2 + if sel_override: + sel = sel_override + + vprint(f"spi_length {spi_length} width {width} sel {sel:b} len(data) {len(data)}") + + dut_tx_status = dut.ctrl.tx_status.fields + dut_rx_status = dut.ctrl.rx_status.fields + self.assertEqual((yield dut_tx_status.empty), 1) + self.assertEqual((yield dut_tx_status.full), 0) + self.assertEqual((yield dut_tx_status.ongoing), 0) + self.assertEqual((yield dut_tx_status.level), 0) + self.assertEqual((yield dut_rx_status.empty), 1) + self.assertEqual((yield dut_rx_status.full), 0) + self.assertEqual((yield dut_rx_status.ongoing), 0) + self.assertEqual((yield dut_rx_status.level), 0) + for slot, d in data: + vprint(f"write({slot}):{d:0{width}x}") + yield from dut.tx_mmap.bus.write(slot, d, sel) + yield + self.assertEqual((yield dut_tx_status.empty), 0) + self.assertEqual((yield dut_tx_status.full), 0) + self.assertEqual((yield dut_tx_status.ongoing), 1) + self.assertGreater((yield dut_tx_status.level), 0) + self.assertEqual((yield dut_rx_status.empty), 1) + self.assertEqual((yield dut_rx_status.full), 0) + self.assertEqual((yield dut_rx_status.ongoing), 1) + self.assertEqual((yield dut_rx_status.level), 0) + + tx_empty = -1 + rx_empty = -1 + miso = -1 + mosi = -1 + while (yield dut_rx_status.ongoing) == 0b1 or (yield dut_rx_status.level) != len(data): + if rx_empty != (rx_empty := (yield dut_rx_status.empty)): + vprint(f"rx_empty:{rx_empty}") + if tx_empty != (tx_empty := (yield dut_tx_status.empty)): + vprint(f"tx_empty:{tx_empty}") + if mosi != (mosi := (yield dut.tx_rx_engine.spi.mosi)): + vvprint(f"mosi => {mosi:0{width}x}") + if miso != (miso := (yield dut.tx_rx_engine.spi.miso)): + vvprint(f"miso <= {miso:0{width}x}") + yield + + yield + for slot, d in data: + read = yield from dut.rx_mmap.bus.read(slot) + self.assertEqual(read, d, f"read({slot}) {read:0{width}x} expect: {d:0{width}x}") + + run_simulation(dut, generator(dut), vcd_name=vcd_name) + + # 32 bit write to 32bit slot + def test_spi_mmap_32_lsb(self): + data = [(0, 0x12345678), (0, 0x9ABCDEF0)] + self.mmap_test(SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_32_lsb.vcd") + + def test_spi_mmap_32_msb(self): + data = [(0, 0x12345678), (0, 0x9ABCDEF0)] + self.mmap_test(SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_32_msb.vcd") + + def test_spi_mmap_32_slot0_1_lsb(self): + data = [ + (0, 0x12345678), (0, 0x9ABCDEF0), (0, 0x87654321), (0, 0x0FEDCBA9), + (1, 0x0FEDCBA9), (1, 0x87654321), (1, 0x9ABCDEF0), (1, 0x12345678) + ] + self.mmap_test(SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_32_slot_0_1_lsb.vcd") + + def test_spi_mmap_32_slot0_1_msb(self): + data = [ + (0, 0x12345678), (0, 0x9ABCDEF0), (0, 0x87654321), (0, 0x0FEDCBA9), + (1, 0x0FEDCBA9), (1, 0x87654321), (1, 0x9ABCDEF0), (1, 0x12345678) + ] + self.mmap_test(SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_32_slot_0_1_msb.vcd") + + def test_spi_mmap_24_lsb(self): + data = [(0, 0x123456), (0, 0x789ABC), (0, 0xDEF012)] + self.mmap_test(SPI_SLOT_LENGTH_24B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_24_lsb.vcd") + + def test_spi_mmap_24_msb(self): + data = [(0, 0x123456), (0, 0x789ABC), (0, 0xDEF012)] + self.mmap_test(SPI_SLOT_LENGTH_24B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_24_msb.vcd") + + def test_spi_mmap_24_slot0_1_lsb(self): + data = [ + (0, 0x123456), (0, 0x9ABCDE), (0, 0x876543), (0, 0x0FEDCB), + (1, 0x0FEDCB), (1, 0x876543), (1, 0x9ABCDE), (1, 0x123456) + ] + self.mmap_test(SPI_SLOT_LENGTH_24B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_24_slot_0_1_lsb.vcd") + + def test_spi_mmap_24_slot0_1_msb(self): + data = [ + (0, 0x123456), (0, 0x9ABCDE), (0, 0x876543), (0, 0x0FEDCB), + (1, 0x0FEDCB), (1, 0x876543), (1, 0x9ABCDE), (1, 0x123456) + ] + self.mmap_test(SPI_SLOT_LENGTH_24B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_24_slot_0_1_msb.vcd") + + # 16 bit write to 16bit slot + def test_spi_mmap_16_lsb(self): + data = [(0, 0x1234), (0, 0x5678), (0, 0x9ABC), (0, 0xDEF0)] + self.mmap_test(SPI_SLOT_LENGTH_16B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_16_lsb.vcd") + + def test_spi_mmap_16_msb(self): + data = [(0, 0x1234), (0, 0x5678), (0, 0x9ABC), (0, 0xDEF0)] + self.mmap_test(SPI_SLOT_LENGTH_16B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_16_msb.vcd") + + # 32 bit write to 16bit slot + def test_spi_mmap_16_lsb_wb32(self): + data = [(0, 0x1234), (0, 0x5678), (0, 0x9ABC), (0, 0xDEF0)] + self.mmap_test( + SPI_SLOT_LENGTH_16B, + SPI_SLOT_BITORDER_LSB_FIRST, + data, + "mmap_16_lsb_wb32.vcd", + sel_override=0b1111, + ) + + def test_spi_mmap_16_msb_wb32(self): + data = [(0, 0x1234), (0, 0x5678), (0, 0x9ABC), (0, 0xDEF0)] + self.mmap_test( + SPI_SLOT_LENGTH_16B, + SPI_SLOT_BITORDER_MSB_FIRST, + data, + "mmap_16_msb_wb32.vcd", + sel_override=0b1111, + ) + + # 8 bit write to 8bit slot + def test_spi_mmap_8_lsb(self): + data = [(0, 0x12), (0, 0x34), (0, 0x56), (0, 0x78), (0, 0x9A), (0, 0xBC), (0, 0xDE), (0, 0xF0)] + self.mmap_test(SPI_SLOT_LENGTH_8B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_8_lsb.vcd") + + def test_spi_mmap_8_msb(self): + data = [(0, 0x12), (0, 0x34), (0, 0x56), (0, 0x78), (0, 0x9A), (0, 0xBC), (0, 0xDE), (0, 0xF0)] + self.mmap_test(SPI_SLOT_LENGTH_8B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_8_msb.vcd") + + def test_spi_mmap_8_msb_wait1(self): + data = [(0, 0x12), (0, 0x34), (0, 0x56), (0, 0x78), (0, 0x9A), (0, 0xBC), (0, 0xDE), (0, 0xF0)] + self.mmap_test(SPI_SLOT_LENGTH_8B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_8_msb_wait1.vcd", wait=1) + + def test_spi_mmap_8_msb_wait8(self): + data = [(0, 0x12), (0, 0x34), (0, 0x56), (0, 0x78), (0, 0x9A), (0, 0xBC), (0, 0xDE), (0, 0xF0)] + self.mmap_test(SPI_SLOT_LENGTH_8B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_8_msb_wait8.vcd", wait=8) + +if __name__ == "__main__": + unittest.main()