From 6170c904597bc5210e829fea0caf2ba76c1befb4 Mon Sep 17 00:00:00 2001 From: Richard Tucker Date: Thu, 19 Oct 2023 12:21:47 +1100 Subject: [PATCH 01/12] soc/cores/spi_mmap: adjust CSR mapping to better suit drivers Currently the TX_RX_ENGINE CSR register lives below the slot registers which are dynamic in length (based on how many slots (chip selects) are configured in the gateware). Move the TX_RX_ENGINE CSR to above the SLOT configuration registers so TX_RX_ENGINE never moves. This makes for an easier and cleaner driver. --- litex/soc/cores/spi/spi_mmap.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/litex/soc/cores/spi/spi_mmap.py b/litex/soc/cores/spi/spi_mmap.py index acb9300ed..663078c1f 100644 --- a/litex/soc/cores/spi/spi_mmap.py +++ b/litex/soc/cores/spi/spi_mmap.py @@ -239,6 +239,7 @@ class SPICtrl(LiteXModule): default_slot_bitorder = SPI_SLOT_BITORDER_MSB_FIRST, default_slot_loopback = 0b1, default_slot_divider = 2, + default_enable = 0b1, ): self.nslots = nslots self.slot_controls = [] @@ -302,6 +303,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=[ @@ -487,7 +495,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 +507,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. @@ -544,7 +545,7 @@ class SPIEngine(LiteXModule): ] # 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, spi.cs.eq(sink.cs) ) @@ -555,7 +556,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") ) From 3477aeaca1be51afa9ad9e4e534151b363a26e98 Mon Sep 17 00:00:00 2001 From: Richard Tucker Date: Thu, 19 Oct 2023 12:20:22 +1100 Subject: [PATCH 02/12] soc/cores/spi_mmap: add read only version register --- litex/soc/cores/spi/spi_mmap.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/litex/soc/cores/spi/spi_mmap.py b/litex/soc/cores/spi/spi_mmap.py index 663078c1f..24a054d60 100644 --- a/litex/soc/cores/spi/spi_mmap.py +++ b/litex/soc/cores/spi/spi_mmap.py @@ -245,6 +245,10 @@ class SPICtrl(LiteXModule): 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')) + # Create TX/RX Control/Status registers. self.tx_control = CSRStorage(fields=[ CSRField("enable", size=1, offset=0, values=[ From 2e67f6a1a30b83212f8191826af7af1770e6338c Mon Sep 17 00:00:00 2001 From: Richard Tucker Date: Tue, 24 Oct 2023 09:51:21 +1100 Subject: [PATCH 03/12] soc/cores/spi_mmap: add read only slot count register --- litex/soc/cores/spi/spi_mmap.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/litex/soc/cores/spi/spi_mmap.py b/litex/soc/cores/spi/spi_mmap.py index 24a054d60..4932248a1 100644 --- a/litex/soc/cores/spi/spi_mmap.py +++ b/litex/soc/cores/spi/spi_mmap.py @@ -249,6 +249,9 @@ class SPICtrl(LiteXModule): 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=[ CSRField("enable", size=1, offset=0, values=[ From 4bc47c959fcd8c01a6bfc7cfb762e515d6bf0b22 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Sun, 25 Feb 2024 09:16:17 +1100 Subject: [PATCH 04/12] test/spi_mmap: lint and autoformat with Ruff --- test/test_spi_mmap.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/test_spi_mmap.py b/test/test_spi_mmap.py index 8915eef23..6b08dbb4b 100644 --- a/test/test_spi_mmap.py +++ b/test/test_spi_mmap.py @@ -6,27 +6,28 @@ # SPDX-License-Identifier: BSD-2-Clause import unittest -import random -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 + 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) From a9c007d8d7436e948bf64d242cdfe9873b4e0613 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Sun, 25 Feb 2024 10:32:29 +1100 Subject: [PATCH 05/12] test/spi_mmap: add some SPIMMAP tests Some pass and some fail demonstrating issues observed in driver development. run unittest with -v to see more test details. Ran 9 tests in 4.161s FAILED (failures=4) --- test/test_spi_mmap.py | 182 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 177 insertions(+), 5 deletions(-) diff --git a/test/test_spi_mmap.py b/test/test_spi_mmap.py index 6b08dbb4b..4c26baa31 100644 --- a/test/test_spi_mmap.py +++ b/test/test_spi_mmap.py @@ -6,12 +6,47 @@ # SPDX-License-Identifier: BSD-2-Clause import unittest +import inspect from migen import Record from litex.gen.sim import run_simulation -from litex.soc.cores.spi.spi_mmap import SPIMaster +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_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) class TestSPIMMAP(unittest.TestCase): @@ -32,7 +67,7 @@ class TestSPIMMAP(unittest.TestCase): # 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) @@ -50,7 +85,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) @@ -68,7 +103,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) @@ -86,7 +121,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) @@ -102,3 +137,140 @@ 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): + 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, + 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_control0.fields.loopback.eq(1) + # yield dut.ctrl.slot_control0.fields.divider.eq(2) + # yield dut.ctrl.slot_control0.fields.enable.eq(1) + if length == SPI_SLOT_LENGTH_32B: + spi_length = 32 + sel = 0b1111 + width = 8 + 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 d in data: + vprint(f"write {d:0{width}x}") + yield from dut.tx_mmap.bus.write(0, 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)): + vprint(f"mosi => {mosi:0{width}x}") + if miso != (miso := (yield dut.tx_rx_engine.spi.miso)): + vprint(f"miso <= {miso:0{width}x}") + yield + + yield + for d in data: + read = yield from dut.rx_mmap.bus.read(0) + self.assertEqual(read, d, f"read {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 = [0x12345678, 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 = [0x12345678, 0x9ABCDEF0] + self.mmap_test(SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_32_msb.vcd") + + # 16 bit write to 16bit slot + def test_spi_mmap_16_lsb(self): + data = [0x1234, 0x5678, 0x9ABC, 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 = [0x1234, 0x5678, 0x9ABC, 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 = [0x1234, 0x5678, 0x9ABC, 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 = [0x1234, 0x5678, 0x9ABC, 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 = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 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 = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] + self.mmap_test(SPI_SLOT_LENGTH_8B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_8_msb.vcd") + + +if __name__ == "__main__": + unittest.main() From 422b02cc16f1fa8d3002e0bef43120e69e216a47 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Sun, 25 Feb 2024 11:13:45 +1100 Subject: [PATCH 06/12] cores/spi_mmap: fix data in unused rx_fifo bits clear miso at start. Prevent previous transfer data in unused bits with 8 and 16bit slot lengths and 32bit bus read. Fixes 2 tests. --- litex/soc/cores/spi/spi_mmap.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/litex/soc/cores/spi/spi_mmap.py b/litex/soc/cores/spi/spi_mmap.py index 4932248a1..17b2ca8c0 100644 --- a/litex/soc/cores/spi/spi_mmap.py +++ b/litex/soc/cores/spi/spi_mmap.py @@ -206,7 +206,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) From 5d1fa7b6cadcc384e289ac125f838eb9e8cd96df Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Sun, 25 Feb 2024 11:15:38 +1100 Subject: [PATCH 07/12] cores/spi_mmap: fix data if bus width > length Details: * 32bit bus write to 8 and 16bit MSB first slot resulted in shifted data on mosi. * 32bit read from 8 and 16bit LSB first slot resulted with shifted data in fifo. Fixes 2 tests - all current tests now pass. --- litex/soc/cores/spi/spi_mmap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litex/soc/cores/spi/spi_mmap.py b/litex/soc/cores/spi/spi_mmap.py index 17b2ca8c0..58ea9e1fd 100644 --- a/litex/soc/cores/spi/spi_mmap.py +++ b/litex/soc/cores/spi/spi_mmap.py @@ -591,7 +591,7 @@ 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]), 32 : spi.mosi[ 0:32].eq(sink.data[0:32]), @@ -604,7 +604,7 @@ 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]), 32 : source.data[0:32].eq(spi.miso[::-1][ 0:32]), From f3b287adddcabb2ffdf6523898b56dc257b1aaa7 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Sun, 25 Feb 2024 11:25:49 +1100 Subject: [PATCH 08/12] cores/spi_mmap: add 24-bit slot length --- litex/soc/cores/spi/spi_mmap.py | 6 +++++- test/test_spi_mmap.py | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/litex/soc/cores/spi/spi_mmap.py b/litex/soc/cores/spi/spi_mmap.py index 58ea9e1fd..452c6a6b2 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 @@ -337,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."), @@ -543,6 +544,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. }) @@ -594,6 +596,7 @@ class SPIEngine(LiteXModule): 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. @@ -607,6 +610,7 @@ class SPIEngine(LiteXModule): 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/test/test_spi_mmap.py b/test/test_spi_mmap.py index 4c26baa31..c5465bcc6 100644 --- a/test/test_spi_mmap.py +++ b/test/test_spi_mmap.py @@ -18,6 +18,7 @@ from litex.soc.cores.spi.spi_mmap import ( 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, @@ -165,6 +166,10 @@ class TestSPIMMAP(unittest.TestCase): 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 @@ -232,6 +237,14 @@ class TestSPIMMAP(unittest.TestCase): data = [0x12345678, 0x9ABCDEF0] self.mmap_test(SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_32_msb.vcd") + def test_spi_mmap_24_lsb(self): + data = [0x123456, 0x789ABC, 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 = [0x123456, 0x789ABC, 0xDEF012] + self.mmap_test(SPI_SLOT_LENGTH_24B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_24_msb.vcd") + # 16 bit write to 16bit slot def test_spi_mmap_16_lsb(self): data = [0x1234, 0x5678, 0x9ABC, 0xDEF0] From 416f1b42816a90db8fb247afc9d1de24b0a271d1 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Sun, 25 Feb 2024 13:24:24 +1100 Subject: [PATCH 09/12] cores/spi_mmap: add slot post transfer cs_wait Also remove unused slot_status - maintains CSR alignment now that slot_control is 64 bit (two 32bit registers). --- litex/soc/cores/spi/spi_mmap.py | 35 +++++++++++++++++++++++++++------ test/test_spi_mmap.py | 12 +++++++++-- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/litex/soc/cores/spi/spi_mmap.py b/litex/soc/cores/spi/spi_mmap.py index 452c6a6b2..f9ac6b160 100644 --- a/litex/soc/cores/spi/spi_mmap.py +++ b/litex/soc/cores/spi/spi_mmap.py @@ -244,10 +244,10 @@ class SPICtrl(LiteXModule): 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.""", @@ -354,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)) @@ -556,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(ctrl.engine.fields.enable & sink.valid, + self.comb += If(ctrl.engine.fields.enable & sink.valid & ~cs_wait, spi.cs.eq(sink.cs) ) @@ -584,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") ) ) diff --git a/test/test_spi_mmap.py b/test/test_spi_mmap.py index c5465bcc6..6d1924ac3 100644 --- a/test/test_spi_mmap.py +++ b/test/test_spi_mmap.py @@ -139,7 +139,7 @@ class TestSPIMMAP(unittest.TestCase): run_simulation(dut, generator(dut), vcd_name="sim.vcd") - def mmap_test(self, length, bitorder, data, vcd_name=None, sel_override=None): + 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, @@ -151,7 +151,7 @@ class TestSPIMMAP(unittest.TestCase): 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, + # 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)}") @@ -162,6 +162,7 @@ class TestSPIMMAP(unittest.TestCase): # 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 @@ -284,6 +285,13 @@ class TestSPIMMAP(unittest.TestCase): data = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 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 = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 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 = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 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() From 07cfda119daf5e302e36a7b68cd26e4c76e6ef37 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Fri, 5 Apr 2024 10:04:39 +1100 Subject: [PATCH 10/12] interconnect/wishbone: check err in simulation --- litex/soc/interconnect/wishbone.py | 4 ++++ 1 file changed, 4 insertions(+) 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"): From c2da8de7b06747fa145ea9896f3fb8a1308396f1 Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Fri, 5 Apr 2024 10:06:54 +1100 Subject: [PATCH 11/12] test_spi_mmap: tests for slot 0&1 --- test/test_spi_mmap.py | 66 +++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/test/test_spi_mmap.py b/test/test_spi_mmap.py index 6d1924ac3..dc8c705e1 100644 --- a/test/test_spi_mmap.py +++ b/test/test_spi_mmap.py @@ -159,6 +159,8 @@ class TestSPIMMAP(unittest.TestCase): # 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) @@ -194,9 +196,9 @@ class TestSPIMMAP(unittest.TestCase): 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 d in data: - vprint(f"write {d:0{width}x}") - yield from dut.tx_mmap.bus.write(0, d, sel) + 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) @@ -223,41 +225,69 @@ class TestSPIMMAP(unittest.TestCase): yield yield - for d in data: - read = yield from dut.rx_mmap.bus.read(0) - self.assertEqual(read, d, f"read {read:0{width}x} expect: {d:0{width}x}") + 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 = [0x12345678, 0x9ABCDEF0] + 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 = [0x12345678, 0x9ABCDEF0] + 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 = [0x123456, 0x789ABC, 0xDEF012] + 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 = [0x123456, 0x789ABC, 0xDEF012] + 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 = [0x1234, 0x5678, 0x9ABC, 0xDEF0] + 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 = [0x1234, 0x5678, 0x9ABC, 0xDEF0] + 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 = [0x1234, 0x5678, 0x9ABC, 0xDEF0] + data = [(0, 0x1234), (0, 0x5678), (0, 0x9ABC), (0, 0xDEF0)] self.mmap_test( SPI_SLOT_LENGTH_16B, SPI_SLOT_BITORDER_LSB_FIRST, @@ -267,7 +297,7 @@ class TestSPIMMAP(unittest.TestCase): ) def test_spi_mmap_16_msb_wb32(self): - data = [0x1234, 0x5678, 0x9ABC, 0xDEF0] + data = [(0, 0x1234), (0, 0x5678), (0, 0x9ABC), (0, 0xDEF0)] self.mmap_test( SPI_SLOT_LENGTH_16B, SPI_SLOT_BITORDER_MSB_FIRST, @@ -278,19 +308,19 @@ class TestSPIMMAP(unittest.TestCase): # 8 bit write to 8bit slot def test_spi_mmap_8_lsb(self): - data = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] + 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 = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] + 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 = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] + 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 = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] + 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__": From 5ae098ebc613d7513c7b1cdfdb4f3539d69f1e9d Mon Sep 17 00:00:00 2001 From: Andrew Dennison Date: Fri, 5 Apr 2024 12:29:51 +1100 Subject: [PATCH 12/12] test/spi_mmap: be less verbose don't print miso/mosi changes with -v --- test/test_spi_mmap.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/test_spi_mmap.py b/test/test_spi_mmap.py index dc8c705e1..f4d36eeb4 100644 --- a/test/test_spi_mmap.py +++ b/test/test_spi_mmap.py @@ -50,6 +50,14 @@ def vprint(*args): print(*args) +def vvprint(*args): + global verbose + if verbose is None: + verbose = unittest_verbosity() + if verbose > 2: + print(*args) + + class TestSPIMMAP(unittest.TestCase): def test_spi_master(self): pads = Record([("clk", 1), ("cs_n", 4), ("mosi", 1), ("miso", 1)]) @@ -219,9 +227,9 @@ class TestSPIMMAP(unittest.TestCase): 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)): - vprint(f"mosi => {mosi:0{width}x}") + vvprint(f"mosi => {mosi:0{width}x}") if miso != (miso := (yield dut.tx_rx_engine.spi.miso)): - vprint(f"miso <= {miso:0{width}x}") + vvprint(f"miso <= {miso:0{width}x}") yield yield