periph.sram: add SRAMPeripheral
authorJean-François Nguyen <jf@lambdaconcept.com>
Wed, 25 Mar 2020 12:28:16 +0000 (13:28 +0100)
committerJean-François Nguyen <jf@lambdaconcept.com>
Wed, 25 Mar 2020 12:33:01 +0000 (13:33 +0100)
lambdasoc/periph/sram.py [new file with mode: 0644]
lambdasoc/test/test_periph_sram.py [new file with mode: 0644]

diff --git a/lambdasoc/periph/sram.py b/lambdasoc/periph/sram.py
new file mode 100644 (file)
index 0000000..9f3b7e1
--- /dev/null
@@ -0,0 +1,105 @@
+from nmigen import *
+from nmigen.utils import log2_int
+
+from nmigen_soc import wishbone
+from nmigen_soc.memory import MemoryMap
+
+from . import Peripheral
+
+
+__all__ = ["SRAMPeripheral"]
+
+
+class SRAMPeripheral(Peripheral, Elaboratable):
+    """SRAM storage peripheral.
+
+    Parameters
+    ----------
+    size : int
+        Memory size in bytes.
+    data_width : int
+        Bus data width.
+    granularity : int
+        Bus granularity.
+    writable : bool
+        Memory is writable.
+
+    Attributes
+    ----------
+    bus : :class:`nmigen_soc.wishbone.Interface`
+        Wishbone bus interface.
+    """
+    # TODO raise bus.err if read-only and a bus write is attempted.
+    def __init__(self, *, size, data_width=32, granularity=8, writable=True):
+        super().__init__()
+
+        if not isinstance(size, int) or size <= 0 or size & size-1:
+            raise ValueError("Size must be an integer power of two, not {!r}"
+                             .format(size))
+        if size < data_width // granularity:
+            raise ValueError("Size {} cannot be lesser than the data width/granularity ratio "
+                             "of {} ({} / {})"
+                              .format(size, data_width // granularity, data_width, granularity))
+
+        self._mem  = Memory(depth=(size * granularity) // data_width, width=data_width,
+                            name=self.name)
+
+        self.bus = wishbone.Interface(addr_width=log2_int(self._mem.depth),
+                                      data_width=self._mem.width, granularity=granularity,
+                                      features={"cti", "bte"})
+
+        map = MemoryMap(addr_width=log2_int(size), data_width=granularity)
+        map.add_resource(self._mem, size=size)
+        self.bus.memory_map = map
+
+        self.size        = size
+        self.granularity = granularity
+        self.writable    = writable
+
+    @property
+    def init(self):
+        return self._mem.init
+
+    @init.setter
+    def init(self, init):
+        self._mem.init = init
+
+    def elaborate(self, platform):
+        m = Module()
+
+        incr = Signal.like(self.bus.adr)
+
+        with m.Switch(self.bus.bte):
+            with m.Case(wishbone.BurstTypeExt.LINEAR):
+                m.d.comb += incr.eq(self.bus.adr + 1)
+            with m.Case(wishbone.BurstTypeExt.WRAP_4):
+                m.d.comb += incr[:2].eq(self.bus.adr[:2] + 1)
+                m.d.comb += incr[2:].eq(self.bus.adr[2:])
+            with m.Case(wishbone.BurstTypeExt.WRAP_8):
+                m.d.comb += incr[:3].eq(self.bus.adr[:3] + 1)
+                m.d.comb += incr[3:].eq(self.bus.adr[3:])
+            with m.Case(wishbone.BurstTypeExt.WRAP_16):
+                m.d.comb += incr[:4].eq(self.bus.adr[:4] + 1)
+                m.d.comb += incr[4:].eq(self.bus.adr[4:])
+
+        m.submodules.mem_rp = mem_rp = self._mem.read_port()
+        m.d.comb += self.bus.dat_r.eq(mem_rp.data)
+
+        with m.If(self.bus.ack):
+            m.d.sync += self.bus.ack.eq(0)
+
+        with m.If(self.bus.cyc & self.bus.stb):
+            m.d.sync += self.bus.ack.eq(1)
+            with m.If((self.bus.cti == wishbone.CycleType.INCR_BURST) & self.bus.ack):
+                m.d.comb += mem_rp.addr.eq(incr)
+            with m.Else():
+                m.d.comb += mem_rp.addr.eq(self.bus.adr)
+
+        if self.writable:
+            m.submodules.mem_wp = mem_wp = self._mem.write_port(granularity=self.granularity)
+            m.d.comb += mem_wp.addr.eq(mem_rp.addr)
+            m.d.comb += mem_wp.data.eq(self.bus.dat_w)
+            with m.If(self.bus.cyc & self.bus.stb & self.bus.we):
+                m.d.comb += mem_wp.en.eq(self.bus.sel)
+
+        return m
diff --git a/lambdasoc/test/test_periph_sram.py b/lambdasoc/test/test_periph_sram.py
new file mode 100644 (file)
index 0000000..7e4b95e
--- /dev/null
@@ -0,0 +1,148 @@
+#nmigen: UnusedElaboratable=no
+
+import unittest
+
+from nmigen import *
+from nmigen.utils import log2_int
+from nmigen.back.pysim import *
+
+from nmigen_soc.wishbone import CycleType, BurstTypeExt
+
+from ._wishbone import *
+from ..periph.sram import SRAMPeripheral
+
+
+def simulation_test(dut, process):
+    with Simulator(dut, vcd_file=open("test.vcd", "w")) as sim:
+        sim.add_clock(1e-6)
+        sim.add_sync_process(process)
+        sim.run()
+
+
+def _burst_type(wrap):
+    if wrap == 0:
+        return BurstTypeExt.LINEAR
+    if wrap == 4:
+        return BurstTypeExt.WRAP_4
+    if wrap == 8:
+        return BurstTypeExt.WRAP_8
+    if wrap == 16:
+        return BurstTypeExt.WRAP_16
+    assert False
+
+
+class SRAMPeripheralTestCase(unittest.TestCase):
+    def read_incr(self, dut, *, addr, count, wrap=0): # FIXME clean
+        data = []
+        yield dut.bus.cyc.eq(1)
+        yield dut.bus.stb.eq(1)
+        yield dut.bus.adr.eq(addr)
+        yield dut.bus.bte.eq(_burst_type(wrap))
+        yield dut.bus.cti.eq(CycleType.END_OF_BURST if count == 0 else CycleType.INCR_BURST)
+        yield
+        self.assertFalse((yield dut.bus.ack))
+        for i in range(count):
+            yield
+            self.assertTrue((yield dut.bus.ack))
+            data.append((yield dut.bus.dat_r))
+            if wrap == 0:
+                yield dut.bus.adr.eq((yield dut.bus.adr) + 1)
+            else:
+                yield dut.bus.adr[:log2_int(wrap)].eq((yield dut.bus.adr[:log2_int(wrap)]) + 1)
+            yield dut.bus.cti.eq(CycleType.END_OF_BURST if i == count-1 else CycleType.INCR_BURST)
+        yield dut.bus.cyc.eq(0)
+        yield dut.bus.stb.eq(0)
+        return data
+
+    def test_bus(self):
+        dut = SRAMPeripheral(size=16, data_width=32, granularity=8)
+        self.assertEqual(dut.bus.addr_width,  2)
+        self.assertEqual(dut.bus.data_width, 32)
+        self.assertEqual(dut.bus.granularity, 8)
+
+    def test_invalid_size(self):
+        with self.assertRaisesRegex(ValueError,
+                r"Size must be an integer power of two, not 'foo'"):
+            dut = SRAMPeripheral(size='foo')
+        with self.assertRaisesRegex(ValueError,
+                r"Size must be an integer power of two, not 3"):
+            dut = SRAMPeripheral(size=3)
+
+    def test_invalid_size_ratio(self):
+        with self.assertRaisesRegex(ValueError,
+                r"Size 2 cannot be lesser than the data width/granularity ratio of "
+                r"4 \(32 / 8\)"):
+            dut = SRAMPeripheral(size=2, data_width=32, granularity=8)
+
+    def test_read(self):
+        dut = SRAMPeripheral(size=4, data_width=8, writable=False)
+        dut.init = [0x00, 0x01, 0x02, 0x03]
+        def process():
+            for i in range(4):
+                data = (yield from wb_read(dut.bus, addr=i, sel=1))
+                self.assertEqual(data, dut.init[i])
+                yield
+        simulation_test(dut, process)
+
+    def test_read_incr_linear(self):
+        dut = SRAMPeripheral(size=8, data_width=8, writable=False)
+        dut.init = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]
+        def process():
+            data = (yield from self.read_incr(dut, addr=0x00, count=6))
+            self.assertEqual(data, dut.init[:6])
+            yield
+            data = (yield from self.read_incr(dut, addr=0x06, count=2))
+            self.assertEqual(data, dut.init[6:])
+        simulation_test(dut, process)
+
+    def test_read_incr_wrap_4(self):
+        dut = SRAMPeripheral(size=8, data_width=8, writable=False)
+        dut.init = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]
+        def process():
+            data = (yield from self.read_incr(dut, addr=0x01, count=8, wrap=4))
+            self.assertEqual(data, 2*(dut.init[1:4] + [dut.init[0]]))
+        simulation_test(dut, process)
+
+    def test_read_incr_wrap_8(self):
+        dut = SRAMPeripheral(size=8, data_width=8, writable=False)
+        dut.init = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]
+        def process():
+            data = (yield from self.read_incr(dut, addr=0x06, count=16, wrap=8))
+            self.assertEqual(data, 2*(dut.init[6:] + dut.init[:6]))
+        simulation_test(dut, process)
+
+    def test_read_incr_wrap_16(self):
+        dut = SRAMPeripheral(size=16, data_width=8, writable=False)
+        dut.init = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]
+        def process():
+            data = (yield from self.read_incr(dut, addr=0x06, count=32, wrap=16))
+            self.assertEqual(data, 2*(dut.init[6:] + dut.init[:6]))
+        simulation_test(dut, process)
+
+    def test_write(self):
+        dut = SRAMPeripheral(size=4, data_width=8)
+        def process():
+            data = [0x00, 0x01, 0x02, 0x03]
+            for i in range(len(data)):
+                yield from wb_write(dut.bus, addr=i, data=data[i], sel=1)
+                yield
+            for i in range(len(data)):
+                b = yield from wb_read(dut.bus, addr=i, sel=1)
+                yield
+                self.assertEqual(b, data[i])
+        simulation_test(dut, process)
+
+    def test_write_sel(self):
+        dut = SRAMPeripheral(size=4, data_width=16, granularity=8)
+        def process():
+            yield from wb_write(dut.bus, addr=0x0, data=0x5aa5, sel=0b01)
+            yield
+            yield from wb_write(dut.bus, addr=0x1, data=0x5aa5, sel=0b10)
+            yield
+            self.assertEqual((yield from wb_read(dut.bus, addr=0x0, sel=1)), 0x00a5)
+            yield
+            self.assertEqual((yield from wb_read(dut.bus, addr=0x1, sel=1)), 0x5a00)
+        simulation_test(dut, process)
+
+    # TODO test write bursts