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.
7 Wrapper around a single port (1R or 1W) SRAM, to make a multi-port regfile.
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
13 See https://bugs.libre-soc.org/show_bug.cgi?id=781 and
14 https://bugs.libre-soc.org/show_bug.cgi?id=502
19 from nmigen
import Elaboratable
, Module
, Memory
, Signal
20 from nmigen
.back
import rtlil
21 from nmigen
.sim
import Simulator
23 from nmutil
.formaltest
import FHDLTestCase
24 from nmutil
.gtkw
import write_gtkw
27 class SinglePortSRAM(Elaboratable
):
29 Model of a single port SRAM, which can be simulated, verified and/or
30 synthesized to an FPGA.
32 :param addr_width: width of the address bus
33 :param data_width: width of the data bus
34 :param we_width: number of write enable lines
36 def __init__(self
, addr_width
, data_width
, we_width
):
37 self
.addr_width
= addr_width
38 self
.data_width
= data_width
39 self
.we_width
= we_width
40 self
.d
= Signal(data_width
)
42 self
.q
= Signal(data_width
)
44 self
.a
= Signal(addr_width
)
45 """ read/write address"""
46 self
.we
= Signal(we_width
)
49 def elaborate(self
, _
):
52 depth
= 1 << self
.addr_width
53 granularity
= self
.data_width
// self
.we_width
54 mem
= Memory(width
=self
.data_width
, depth
=depth
)
55 # create read and write ports
56 # By connecting the same address to both ports, they behave, in fact,
57 # as a single, "half-duplex" port.
58 # The transparent attribute means that, on a write, we read the new
59 # value, on the next cycle
60 # Note that nmigen memories have a one cycle delay, for reads,
62 m
.submodules
.rdport
= rdport
= mem
.read_port(transparent
=True)
63 m
.submodules
.wrport
= wrport
= mem
.write_port(granularity
=granularity
)
64 # duplicate the address to both ports
65 m
.d
.comb
+= wrport
.addr
.eq(self
.a
)
66 m
.d
.comb
+= rdport
.addr
.eq(self
.a
)
68 m
.d
.comb
+= wrport
.en
.eq(self
.we
)
70 m
.d
.comb
+= wrport
.data
.eq(self
.d
)
71 m
.d
.comb
+= self
.q
.eq(rdport
.data
)
83 def create_ilang(dut
, ports
, test_name
):
84 vl
= rtlil
.convert(dut
, name
=test_name
, ports
=ports
)
85 with
open("%s.il" % test_name
, "w") as f
:
89 class SinglePortSRAMTestCase(FHDLTestCase
):
91 def test_simple_rtlil():
93 Generate a simple SRAM. Try ``read_rtlil mem_simple.il; proc; show``
94 from a yosys prompt, to see the memory primitives, and
95 ``read_rtlil mem_simple.il; synth; show`` to see it implemented as
98 dut
= SinglePortSRAM(2, 4, 2)
99 create_ilang(dut
, dut
.ports(), "mem_simple")
102 def test_blkram_rtlil():
104 Generates a bigger SRAM.
105 Try ``read_rtlil mem_blkram.il; synth_ecp5; show`` from a yosys
106 prompt, to see it implemented as block RAM
108 dut
= SinglePortSRAM(10, 16, 2)
109 create_ilang(dut
, dut
.ports(), "mem_blkram")
111 def test_sram_model(self
):
113 Simulate some read/write/modify operations on the SRAM model
115 dut
= SinglePortSRAM(7, 32, 4)
120 # 1) write 0x12_34_56_78 to address 0
122 yield dut
.d
.eq(0x12_34_56_78)
123 yield dut
.we
.eq(0b1111)
125 # 2) write 0x9A_BC_DE_F0 to address 1
127 yield dut
.d
.eq(0x9A_BC_DE_F0)
128 yield dut
.we
.eq(0b1111)
130 # ... and read value just written to address 0
131 self
.assertEqual((yield dut
.q
), 0x12_34_56_78)
132 # 3) prepare to read from address 0
134 yield dut
.we
.eq(0b0000)
137 # ... and read value just written to address 1
138 self
.assertEqual((yield dut
.q
), 0x9A_BC_DE_F0)
139 # 4) prepare to read from address 1
142 # ... and read value from address 0
143 self
.assertEqual((yield dut
.q
), 0x12_34_56_78)
144 # 5) write 0x9A and 0xDE to bytes 1 and 3, leaving
145 # bytes 0 and 2 unchanged
147 yield dut
.d
.eq(0x9A_FF_DE_FF)
148 yield dut
.we
.eq(0b1010)
150 # ... and read value from address 1
151 self
.assertEqual((yield dut
.q
), 0x9A_BC_DE_F0)
152 # 6) nothing more to do
156 # ... other than confirm that bytes 1 and 3 were modified
158 self
.assertEqual((yield dut
.q
), 0x9A_34_DE_78)
160 sim
.add_sync_process(process
)
161 traces
= ['rdport.clk', 'a[6:0]', 'we[3:0]', 'd[31:0]', 'q[31:0]']
162 write_gtkw('test_sram_model.gtkw', 'test_sram_model.vcd',
163 traces
, module
='top')
164 sim_writer
= sim
.write_vcd('test_sram_model.vcd')
169 if __name__
== "__main__":