From d57e0eecf0ea474e3f1aa0d906a7d5450655bb05 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Wed, 25 Mar 2020 13:28:16 +0100 Subject: [PATCH] periph.sram: add SRAMPeripheral --- lambdasoc/periph/sram.py | 105 ++++++++++++++++++++ lambdasoc/test/test_periph_sram.py | 148 +++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 lambdasoc/periph/sram.py create mode 100644 lambdasoc/test/test_periph_sram.py diff --git a/lambdasoc/periph/sram.py b/lambdasoc/periph/sram.py new file mode 100644 index 0000000..9f3b7e1 --- /dev/null +++ b/lambdasoc/periph/sram.py @@ -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 index 0000000..7e4b95e --- /dev/null +++ b/lambdasoc/test/test_periph_sram.py @@ -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 -- 2.30.2