From 5c991cd3249ba5b38c7e8e383975ef029ac715b0 Mon Sep 17 00:00:00 2001 From: Staf Verhaegen Date: Tue, 23 Jun 2020 12:15:44 +0200 Subject: [PATCH] SRAM bus A low level SRAM interface, code structure is modeled after CSR interface. --- nmigen_soc/sram/__init__.py | 3 + nmigen_soc/sram/bus.py | 83 +++++++++++++++ nmigen_soc/sram/wishbone.py | 110 ++++++++++++++++++++ nmigen_soc/test/test_sram_bus.py | 41 ++++++++ nmigen_soc/test/test_sram_wishbone.py | 143 ++++++++++++++++++++++++++ 5 files changed, 380 insertions(+) create mode 100644 nmigen_soc/sram/__init__.py create mode 100644 nmigen_soc/sram/bus.py create mode 100644 nmigen_soc/sram/wishbone.py create mode 100644 nmigen_soc/test/test_sram_bus.py create mode 100644 nmigen_soc/test/test_sram_wishbone.py diff --git a/nmigen_soc/sram/__init__.py b/nmigen_soc/sram/__init__.py new file mode 100644 index 0000000..9e8a83f --- /dev/null +++ b/nmigen_soc/sram/__init__.py @@ -0,0 +1,3 @@ +from .bus import * +from .wishbone import WishboneSRAMBridge as WishboneBridge + diff --git a/nmigen_soc/sram/bus.py b/nmigen_soc/sram/bus.py new file mode 100644 index 0000000..7b5abf8 --- /dev/null +++ b/nmigen_soc/sram/bus.py @@ -0,0 +1,83 @@ +from nmigen import * + +from ..memory import MemoryMap + +class Interface(Record): + """SRAM interface. + + A low-level interface for accessing SRAM blocks. + + Parameters + ---------- + addr_width : int + Address width. At most ``(2 ** addr_width) * data_width`` bits of memory will be available. + data_width : int + Data width. Word size of the SRAM block. + words : int + The number of words of :arg:`data_width` bits in the SRAM block. This allows to have an + SRAM block not covering the full address space provided by :arg:`addr_width`. Only values + are allowed where MSB of the address is used given the condition of + ``2**(addr_width - 1) < words <= 2**addr_width``. + If no value is specified the full ``2**addr_width`` is used. + name : str + Name of the underlying record. + + Attributes + ---------- + memory_map : MemoryMap + The memory map of the SRAM block; determined by the :arg:`words` and arg:`data_width` + arguments. + a : Signal(addr_width) + Address for reads and writes + d_r : Signal(data_width) + Read data. The SRAM interface is defined asynchronous and no guarantees are made on + timing for availability of the data. + d_w : Signal(data_width) + Write data. The SRAM interface is defined asynchronous and does not define timing + requirement of d_w and we. + we : Signal() + Enable write. The SRAM interface is defined asynchronous and does not define timing + requirement of d_w and we. + ce : Signal() + Enable SRAM block interface. + """ + + def __init__(self, *, addr_width, data_width, words=None, name=None): + if not isinstance(addr_width, int) or addr_width <= 0: + raise ValueError( + "Address width must be a positive integer, not {!r}".format(addr_width) + ) + if not isinstance(data_width, int) or data_width <= 0: + raise ValueError( + "Data width must be a positive integer, not {!r}".format(data_width) + ) + if words is None: + words = 2**addr_width + if not isinstance(words, int) or not (2**(addr_width - 1) < words <= 2**addr_width): + raise ValueError( + "# words has to integer between 2**(addr_width-1) and 2**addr_width, not {!r}" + .format(words), + ) + self.addr_width = addr_width + self.data_width = data_width + self.words = words + self._map = memmap = MemoryMap(addr_width=addr_width, data_width=data_width) + + super().__init__([ + ("a", addr_width), + ("d_r", data_width), + ("d_w", data_width), + ("we", 1), + ("ce", 1), + ], name=name, src_loc_at=1) + + memmap.add_resource(self, size=words) + memmap.freeze() + + @property + def memory_map(self): + return self._map + + def __hash__(self): + """Each object represents a different SRAM block""" + return id(self) diff --git a/nmigen_soc/sram/wishbone.py b/nmigen_soc/sram/wishbone.py new file mode 100644 index 0000000..587eb99 --- /dev/null +++ b/nmigen_soc/sram/wishbone.py @@ -0,0 +1,110 @@ +from nmigen import * +from nmigen.utils import log2_int + +from .bus import Interface +from .. import wishbone as wb, memory as mem + + +__all__ = ["WishboneSRAMBridge"] + + +class WishboneSRAMBridge(Elaboratable): + """Wishbone to SRAM bridge. + + A bus bridge for accessing SRAM blocks from Wishbone. This bridge drives one or more + SRAM blocks from the Wishbone interface. The data width of the individual blocks + determines the granularity of the Wishbone interface and the number of interfaces the + data_width. A dynamic configurable number wait states can be inserted in each + Wishbone bus operation. + + Parameters + ---------- + sram_buses : :class:`..sram.Interface` or iterable of :class:`..sram.Interface` + SRAM buses driven by the bridge. All buses need to have same address and data + widths. + wait_states : :class:Signal or :class:Const + The number of wait states to insert before acknowledging a cycle. This value is + not latched at the beginning of a cycle so should normally be kept stable during + a bus cycle. + + Attributes + ---------- + wb_bus : :class:`..wishbone.Interface` + Wishbone bus provided by the bridge. + """ + def __init__(self, sram_buses, *, wait_states=Const(0, 1), name="wb"): + if isinstance(sram_buses, Interface): + sram_buses = [sram_buses] + else: + try: + for sram_bus in sram_buses: + assert isinstance(sram_bus, Interface) + except: + raise ValueError( + "SRAM buses has to be an iterable of sram.Interface, not {!r}".format(sram_buses) + ) + # Support len(sram_buses) and make sram_buses hashable + sram_buses = tuple(sram_buses) + n_rams = len(sram_buses) + addr_width = sram_buses[0].addr_width + granularity = sram_buses[0].data_width + words = sram_buses[0].words + for sram_bus in sram_buses[1:]: + if sram_bus.addr_width != addr_width: + raise ValueError("All SRAMs have to have the same address width") + if sram_bus.data_width != granularity: + raise ValueError("All SRAMs have to have the same data width") + if sram_bus.words != words: + raise ValueError("All SRAMs have to have the same number of words") + data_width = granularity*len(sram_buses) + + self.sram_buses = sram_buses + self.wait_states = wait_states + self.wb_bus = wb_bus = wb.Interface( + addr_width=addr_width, + data_width=data_width, + granularity=granularity, + name=name, + ) + if n_rams == 1: + wb_bus.memory_map = sram_buses[0].memory_map + else: + size = words*len(sram_buses) + map_addr_width = log2_int(size, need_pow2=False) + memmap = mem.MemoryMap(addr_width=map_addr_width, data_width=granularity) + memmap.add_resource(sram_buses, size=size) + wb_bus.memory_map = memmap + + def elaborate(self, platform): + sram_buses = self.sram_buses + wait_states = self.wait_states + wb_bus = self.wb_bus + + m = Module() + + wb_cycle = Signal() + m.d.comb += wb_cycle.eq(wb_bus.cyc & wb_bus.stb) + + for i, sram_bus in enumerate(sram_buses): + s = slice(i*wb_bus.granularity, (i+1)*wb_bus.granularity) + m.d.comb += [ + sram_bus.a.eq(wb_bus.adr), + sram_bus.ce.eq(wb_cycle & wb_bus.sel[i]), + sram_bus.we.eq(wb_cycle & wb_bus.sel[i] & wb_bus.we), + wb_bus.dat_r[s].eq(sram_bus.d_r), + sram_bus.d_w.eq(wb_bus.dat_w[s]), + ] + + waitcnt = Signal(len(wait_states)) + with m.If(wb_cycle): + with m.If(waitcnt != wait_states): + m.d.comb += wb_bus.ack.eq(0) + m.d.sync += waitcnt.eq(waitcnt + 1) + with m.Else(): + m.d.comb += wb_bus.ack.eq(1) + m.d.sync += waitcnt.eq(0) + with m.Else(): + m.d.comb += wb_bus.ack.eq(0) + m.d.sync += waitcnt.eq(0) + + return m diff --git a/nmigen_soc/test/test_sram_bus.py b/nmigen_soc/test/test_sram_bus.py new file mode 100644 index 0000000..3f3ec4f --- /dev/null +++ b/nmigen_soc/test/test_sram_bus.py @@ -0,0 +1,41 @@ +# nmigen: UnusedElaboratable=no + +import unittest +from nmigen import * +from nmigen.hdl.rec import Layout + +from .. import sram +from ..memory import MemoryMap + + +class InterfaceTestCase(unittest.TestCase): + def test_layout(self): + iface = sram.Interface(addr_width=12, data_width=8) + self.assertEqual(iface.addr_width, 12) + self.assertEqual(iface.data_width, 8) + self.assertEqual(iface.layout, Layout.cast([ + ("a", 12), + ("d_r", 8), + ("d_w", 8), + ("we", 1), + ("ce", 1), + ])) + + def test_wrong_addr_width(self): + with self.assertRaisesRegex( + ValueError, + r"Address width must be a positive integer, not -1", + ): + sram.Interface(addr_width=-1, data_width=8) + + def test_wrong_data_width(self): + with self.assertRaisesRegex( + ValueError, + r"Data width must be a positive integer, not -1", + ): + sram.Interface(addr_width=16, data_width=-1) + + def test_no_set_map(self): + iface = sram.Interface(addr_width=16, data_width=8) + with self.assertRaisesRegex(AttributeError, "can't set attribute"): + iface.memory_map = MemoryMap(addr_width=16, data_width=8) diff --git a/nmigen_soc/test/test_sram_wishbone.py b/nmigen_soc/test/test_sram_wishbone.py new file mode 100644 index 0000000..4870e59 --- /dev/null +++ b/nmigen_soc/test/test_sram_wishbone.py @@ -0,0 +1,143 @@ +# nmigen: UnusedElaboratable=no + +import unittest +from nmigen import * +from nmigen.back.pysim import * + +from .. import sram + + +class WishboneBridgeTestCase(unittest.TestCase): + def test_wrong_sram_buses(self): + with self.assertRaisesRegex( + ValueError, + r"SRAM buses has to be an iterable of sram.Interface, not 'foo'", + ): + sram.WishboneBridge("foo") + with self.assertRaisesRegex( + ValueError, + r"SRAM buses has to be an iterable of sram.Interface, not 'foo'", + ): + sram.WishboneBridge(sram_buses="foo") + + def test_wbbus_single(self): + iface = sram.Interface(addr_width=10, data_width=8) + bridge = sram.WishboneBridge(iface) + self.assertEqual(bridge.wb_bus.addr_width, 10) + self.assertEqual(bridge.wb_bus.data_width, 8) + self.assertEqual(bridge.wb_bus.granularity, 8) + self.assertFalse(hasattr(bridge.wb_bus, "stall")) + + def test_wbbus_multi(self): + ifaces = [sram.Interface(addr_width=10, data_width=8) for _ in range(4)] + bridge = sram.WishboneBridge(ifaces) + self.assertEqual(bridge.wb_bus.addr_width, 10) + self.assertEqual(bridge.wb_bus.data_width, 32) + self.assertEqual(bridge.wb_bus.granularity, 8) + self.assertFalse(hasattr(bridge.wb_bus, "stall")) + + def test_readwrite_single_nowait(self): + iface = sram.Interface(addr_width=10, data_width=8) + dut = sram.WishboneBridge(iface) + + def sim_test(): + yield dut.wb_bus.cyc.eq(1) + yield dut.wb_bus.stb.eq(0) + yield dut.wb_bus.sel.eq(0b1) + yield + self.assertFalse((yield iface.ce), 0) + + yield dut.wb_bus.we.eq(1) + yield + # we is only asserted when in Wishbone cycle + self.assertEqual((yield iface.we), 0) + + yield dut.wb_bus.adr.eq(1) + yield dut.wb_bus.dat_w.eq(0x55) + yield dut.wb_bus.stb.eq(1) + yield + self.assertEqual((yield iface.we), 1) + self.assertEqual((yield iface.a), 1) + self.assertEqual((yield iface.d_w), 0x55) + self.assertEqual((yield iface.ce), 1) + self.assertEqual((yield dut.wb_bus.ack), 1) + + yield dut.wb_bus.stb.eq(0) + yield + self.assertEqual((yield dut.wb_bus.ack), 0) + self.assertEqual((yield iface.we), 0) + self.assertEqual((yield iface.ce), 0) + + yield dut.wb_bus.we.eq(0) + yield dut.wb_bus.stb.eq(1) + yield iface.d_r.eq(0x55) + yield + self.assertEqual((yield dut.wb_bus.ack), 1) + self.assertEqual((yield dut.wb_bus.dat_r), 0x55) + + sim = Simulator(dut) + sim.add_clock(1e-6) + sim.add_sync_process(sim_test) + with sim.write_vcd(vcd_file=open("test.vcd", "w")): + sim.run() + + def test_readwrite_multi_wait1(self): + ifaces = [sram.Interface(addr_width=10, data_width=8) for _ in range(4)] + a = ifaces[0].a + ce = Cat(iface.ce for iface in ifaces) + we = Cat(iface.we for iface in ifaces) + d_w = Cat(iface.d_w for iface in ifaces) + d_r = Cat(iface.d_r for iface in ifaces) + + dut = sram.WishboneBridge(ifaces, wait_states=Const(1, 1)) + + def sim_test(): + yield dut.wb_bus.cyc.eq(1) + yield dut.wb_bus.stb.eq(0) + yield dut.wb_bus.sel.eq(0b1100) + yield + for iface in ifaces: + self.assertFalse((yield iface.ce), 0) + + yield dut.wb_bus.we.eq(1) + yield + # we is only asserted when in Wishbone cycle + self.assertEqual((yield we), 0b0000) + + yield dut.wb_bus.adr.eq(1) + yield dut.wb_bus.dat_w.eq(0xAA55AA55) + yield dut.wb_bus.stb.eq(1) + yield + self.assertEqual((yield we), 0b1100) + self.assertEqual((yield a), 1) + self.assertEqual((yield d_w), 0xAA55AA55) + self.assertEqual((yield ce), 0b1100) + self.assertEqual((yield dut.wb_bus.ack), 0) + yield + yield + yield + self.assertEqual((yield dut.wb_bus.ack), 1) + + yield dut.wb_bus.stb.eq(0) + yield + self.assertEqual((yield dut.wb_bus.ack), 0) + self.assertEqual((yield we), 0b0000) + self.assertEqual((yield ce), 0b0000) + + yield dut.wb_bus.we.eq(0) + yield dut.wb_bus.stb.eq(1) + yield d_r.eq(0xAA550000) + yield + self.assertEqual((yield dut.wb_bus.dat_r), 0xAA550000) + self.assertEqual((yield dut.wb_bus.ack), 0) + yield + yield + yield + self.assertEqual((yield dut.wb_bus.dat_r), 0xAA550000) + self.assertEqual((yield dut.wb_bus.ack), 1) + + sim = Simulator(dut) + sim.add_clock(1e-6) + sim.add_sync_process(sim_test) + with sim.write_vcd(vcd_file=open("test.vcd", "w")): + sim.run()