From 44150c000c3449ee379cf7287ff633e69633af0a Mon Sep 17 00:00:00 2001 From: Cesar Strauss Date: Sun, 13 Mar 2022 08:56:24 -0300 Subject: [PATCH] Add a Single R/W Port SRAM model Begin making unit tests by checking with Yosys. --- src/soc/regfile/sram_wrapper.py | 111 ++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/soc/regfile/sram_wrapper.py diff --git a/src/soc/regfile/sram_wrapper.py b/src/soc/regfile/sram_wrapper.py new file mode 100644 index 00000000..1eec5724 --- /dev/null +++ b/src/soc/regfile/sram_wrapper.py @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: LGPLv3+ +# Copyright (C) 2022 Cesar Strauss +# Sponsored by NLnet and NGI POINTER under EU Grants 871528 and 957073 +# Part of the Libre-SOC Project. + +""" +Wrapper around a single port (1R or 1W) SRAM, to make a multi-port regfile. + +This SRAM primitive has one cycle delay for reads, and, after a write, +it reads the value just written. The goal is to use it to make at least an +1W2R regfile. + +See https://bugs.libre-soc.org/show_bug.cgi?id=781 and +https://bugs.libre-soc.org/show_bug.cgi?id=502 +""" + +import unittest + +from nmigen import Elaboratable, Module, Memory, Signal +from nmigen.back import rtlil + +from nmutil.formaltest import FHDLTestCase + + +class SinglePortSRAM(Elaboratable): + """ + Model of a single port SRAM, which can be simulated, verified and/or + synthesized to an FPGA. + + :param addr_width: width of the address bus + :param data_width: width of the data bus + :param we_width: number of write enable lines + """ + def __init__(self, addr_width, data_width, we_width): + self.addr_width = addr_width + self.data_width = data_width + self.we_width = we_width + self.d = Signal(data_width) + """ write data""" + self.q = Signal(data_width) + """read data""" + self.a = Signal(addr_width) + """ read/write address""" + self.we = Signal(we_width) + """write enable""" + + def elaborate(self, _): + m = Module() + # backing memory + depth = 1 << self.addr_width + granularity = self.data_width // self.we_width + mem = Memory(width=self.data_width, depth=depth) + # create read and write ports + # By connecting the same address to both ports, they behave, in fact, + # as a single, "half-duplex" port. + # The transparent attribute means that, on a write, we read the new + # value, on the next cycle + # Note that nmigen memories have a one cycle delay, for reads, + # by default + m.submodules.rdport = rdport = mem.read_port(transparent=True) + m.submodules.wrport = wrport = mem.write_port(granularity=granularity) + # duplicate the address to both ports + m.d.comb += wrport.addr.eq(self.a) + m.d.comb += rdport.addr.eq(self.a) + # write enable + m.d.comb += wrport.en.eq(self.we) + # read and write data + m.d.comb += wrport.data.eq(self.d) + m.d.comb += self.q.eq(rdport.data) + return m + + def ports(self): + return [ + self.d, + self.a, + self.we, + self.q + ] + + +def create_ilang(dut, ports, test_name): + vl = rtlil.convert(dut, name=test_name, ports=ports) + with open("%s.il" % test_name, "w") as f: + f.write(vl) + + +class SinglePortSRAMTestCase(FHDLTestCase): + @staticmethod + def test_simple_rtlil(): + """ + Generate a simple SRAM. Try ``read_rtlil mem_simple.il; proc; show`` + from a yosys prompt, to see the memory primitives, and + ``read_rtlil mem_simple.il; synth; show`` to see it implemented as + flip-flop RAM + """ + dut = SinglePortSRAM(2, 4, 2) + create_ilang(dut, dut.ports(), "mem_simple") + + @staticmethod + def test_blkram_rtlil(): + """ + Generates a bigger SRAM. + Try ``read_rtlil mem_blkram.il; synth_ecp5; show`` from a yosys + prompt, to see it implemented as block RAM + """ + dut = SinglePortSRAM(10, 16, 2) + create_ilang(dut, dut.ports(), "mem_blkram") + + +if __name__ == "__main__": + unittest.main() -- 2.30.2