Add a Single R/W Port SRAM model
[soc.git] / src / soc / regfile / sram_wrapper.py
1 # SPDX-License-Identifier: LGPLv3+
2 # Copyright (C) 2022 Cesar Strauss <cestrauss@gmail.com>
3 # Sponsored by NLnet and NGI POINTER under EU Grants 871528 and 957073
4 # Part of the Libre-SOC Project.
5
6 """
7 Wrapper around a single port (1R or 1W) SRAM, to make a multi-port regfile.
8
9 This SRAM primitive has one cycle delay for reads, and, after a write,
10 it reads the value just written. The goal is to use it to make at least an
11 1W2R regfile.
12
13 See https://bugs.libre-soc.org/show_bug.cgi?id=781 and
14 https://bugs.libre-soc.org/show_bug.cgi?id=502
15 """
16
17 import unittest
18
19 from nmigen import Elaboratable, Module, Memory, Signal
20 from nmigen.back import rtlil
21
22 from nmutil.formaltest import FHDLTestCase
23
24
25 class SinglePortSRAM(Elaboratable):
26 """
27 Model of a single port SRAM, which can be simulated, verified and/or
28 synthesized to an FPGA.
29
30 :param addr_width: width of the address bus
31 :param data_width: width of the data bus
32 :param we_width: number of write enable lines
33 """
34 def __init__(self, addr_width, data_width, we_width):
35 self.addr_width = addr_width
36 self.data_width = data_width
37 self.we_width = we_width
38 self.d = Signal(data_width)
39 """ write data"""
40 self.q = Signal(data_width)
41 """read data"""
42 self.a = Signal(addr_width)
43 """ read/write address"""
44 self.we = Signal(we_width)
45 """write enable"""
46
47 def elaborate(self, _):
48 m = Module()
49 # backing memory
50 depth = 1 << self.addr_width
51 granularity = self.data_width // self.we_width
52 mem = Memory(width=self.data_width, depth=depth)
53 # create read and write ports
54 # By connecting the same address to both ports, they behave, in fact,
55 # as a single, "half-duplex" port.
56 # The transparent attribute means that, on a write, we read the new
57 # value, on the next cycle
58 # Note that nmigen memories have a one cycle delay, for reads,
59 # by default
60 m.submodules.rdport = rdport = mem.read_port(transparent=True)
61 m.submodules.wrport = wrport = mem.write_port(granularity=granularity)
62 # duplicate the address to both ports
63 m.d.comb += wrport.addr.eq(self.a)
64 m.d.comb += rdport.addr.eq(self.a)
65 # write enable
66 m.d.comb += wrport.en.eq(self.we)
67 # read and write data
68 m.d.comb += wrport.data.eq(self.d)
69 m.d.comb += self.q.eq(rdport.data)
70 return m
71
72 def ports(self):
73 return [
74 self.d,
75 self.a,
76 self.we,
77 self.q
78 ]
79
80
81 def create_ilang(dut, ports, test_name):
82 vl = rtlil.convert(dut, name=test_name, ports=ports)
83 with open("%s.il" % test_name, "w") as f:
84 f.write(vl)
85
86
87 class SinglePortSRAMTestCase(FHDLTestCase):
88 @staticmethod
89 def test_simple_rtlil():
90 """
91 Generate a simple SRAM. Try ``read_rtlil mem_simple.il; proc; show``
92 from a yosys prompt, to see the memory primitives, and
93 ``read_rtlil mem_simple.il; synth; show`` to see it implemented as
94 flip-flop RAM
95 """
96 dut = SinglePortSRAM(2, 4, 2)
97 create_ilang(dut, dut.ports(), "mem_simple")
98
99 @staticmethod
100 def test_blkram_rtlil():
101 """
102 Generates a bigger SRAM.
103 Try ``read_rtlil mem_blkram.il; synth_ecp5; show`` from a yosys
104 prompt, to see it implemented as block RAM
105 """
106 dut = SinglePortSRAM(10, 16, 2)
107 create_ilang(dut, dut.ports(), "mem_blkram")
108
109
110 if __name__ == "__main__":
111 unittest.main()