Allow the formal engine to perform a same-cycle result in the ALU
[soc.git] / src / soc / regfile / regfile.py
1 """Specialist Regfiles
2
3 These are not "normal" binary-indexed regfiles (although that is included).
4 They include *unary* indexed regfiles as well as Dependency-tracked ones
5 (SPR files with 1024 registers, only around 4-5 of which need to be active)
6 and special "split" regfiles that have 8R8W for 8 4-bit quantities and a
7 1R1W to read/write *all* 8 4-bit registers in a single one-off 32-bit way.
8
9 Due to the way that the Dependency Matrices are set up (bit-vectors), the
10 primary focus here is on *unary* indexing.
11
12 Links:
13
14 * https://libre-soc.org/3d_gpu/architecture/regfile
15 * https://bugs.libre-soc.org/show_bug.cgi?id=345
16 * https://bugs.libre-soc.org/show_bug.cgi?id=351
17 * https://bugs.libre-soc.org/show_bug.cgi?id=352
18 """
19
20 from nmigen.compat.sim import run_simulation
21 from nmigen.back.pysim import Settle
22 from nmigen.cli import verilog, rtlil
23
24 from nmigen import Cat, Const, Array, Signal, Elaboratable, Module
25 from nmutil.iocontrol import RecordObject
26 from nmutil.util import treereduce
27 from nmigen.utils import log2_int
28 from nmigen import Memory
29
30 from math import log
31 import operator
32
33
34 class Register(Elaboratable):
35 def __init__(self, width, writethru=True, synced=True, resetval=0):
36 self.width = width
37 self.reset = resetval
38 self.writethru = writethru
39 self.synced = synced
40 self._rdports = []
41 self._wrports = []
42
43 def read_port(self, name=None):
44 port = RecordObject([("ren", 1),
45 ("o_data", self.width)],
46 name=name)
47 self._rdports.append(port)
48 return port
49
50 def write_port(self, name=None):
51 port = RecordObject([("wen", 1),
52 ("i_data", self.width)],
53 name=name)
54 self._wrports.append(port)
55 return port
56
57 def elaborate(self, platform):
58 m = Module()
59 self.reg = reg = Signal(self.width, name="reg", reset=self.reset)
60
61 if self.synced:
62 domain = m.d.sync
63 else:
64 domain = m.d.comb
65
66 # read ports. has write-through detection (returns data written)
67 for rp in self._rdports:
68 domain += rp.o_data.eq(0)
69 with m.If(rp.ren):
70 if self.writethru:
71 wr_detect = Signal(reset_less=False)
72 m.d.comb += wr_detect.eq(0)
73 for wp in self._wrports:
74 with m.If(wp.wen):
75 domain += rp.o_data.eq(wp.i_data)
76 m.d.comb += wr_detect.eq(1)
77 with m.If(~wr_detect):
78 domain += rp.o_data.eq(reg)
79 else:
80 domain += rp.o_data.eq(reg)
81
82 # write ports, delayed by 1 cycle
83 for wp in self._wrports:
84 with m.If(wp.wen):
85 m.d.sync += reg.eq(wp.i_data)
86
87 return m
88
89 def __iter__(self):
90 for p in self._rdports:
91 yield from p
92 for p in self._wrports:
93 yield from p
94
95 def ports(self):
96 res = list(self)
97
98
99 def ortreereduce(tree, attr="o_data"):
100 return treereduce(tree, operator.or_, lambda x: getattr(x, attr))
101
102
103 class RegFileArray(Elaboratable):
104 unary = True
105 """ an array-based register file (register having write-through capability)
106 that has no "address" decoder, instead it has individual write-en
107 and read-en signals (per port).
108 """
109
110 def __init__(self, width, depth, synced=True, fwd_bus_mode=True):
111 self.synced = synced
112 self.width = width
113 self.depth = depth
114 self.regs = Array(Register(width, synced=synced,
115 writethru=fwd_bus_mode) \
116 for _ in range(self.depth))
117 self._rdports = []
118 self._wrports = []
119
120 def read_reg_port(self, name=None):
121 regs = []
122 for i in range(self.depth):
123 port = self.regs[i].read_port("%s%d" % (name, i))
124 regs.append(port)
125 return regs
126
127 def write_reg_port(self, name=None):
128 regs = []
129 for i in range(self.depth):
130 port = self.regs[i].write_port("%s%d" % (name, i))
131 regs.append(port)
132 return regs
133
134 def read_port(self, name=None):
135 regs = self.read_reg_port(name)
136 regs = Array(regs)
137 port = RecordObject([("ren", self.depth),
138 ("o_data", self.width)], name)
139 self._rdports.append((regs, port))
140 return port
141
142 def write_port(self, name=None):
143 regs = self.write_reg_port(name)
144 regs = Array(regs)
145 port = RecordObject([("wen", self.depth),
146 ("i_data", self.width)])
147 self._wrports.append((regs, port))
148 return port
149
150 def _get_en_sig(self, port, typ):
151 wen = []
152 for p in port:
153 wen.append(p[typ])
154 return Cat(*wen)
155
156 def elaborate(self, platform):
157 m = Module()
158 for i, reg in enumerate(self.regs):
159 setattr(m.submodules, "reg_%d" % i, reg)
160
161 if self.synced:
162 domain = m.d.sync
163 else:
164 domain = m.d.comb
165
166 for (regs, p) in self._rdports:
167 #print (p)
168 m.d.comb += self._get_en_sig(regs, 'ren').eq(p.ren)
169 ror = ortreereduce(list(regs))
170 if self.synced:
171 ren_delay = Signal.like(p.ren)
172 m.d.sync += ren_delay.eq(p.ren)
173 with m.If(ren_delay):
174 m.d.comb += p.o_data.eq(ror)
175 else:
176 m.d.comb += p.o_data.eq(ror)
177 for (regs, p) in self._wrports:
178 m.d.comb += self._get_en_sig(regs, 'wen').eq(p.wen)
179 for r in regs:
180 m.d.comb += r.i_data.eq(p.i_data)
181
182 return m
183
184 def __iter__(self):
185 for r in self.regs:
186 yield from r
187
188 def ports(self):
189 return list(self)
190
191
192 class RegFileMem(Elaboratable):
193 unary = False
194 def __init__(self, width, depth, fwd_bus_mode=False, synced=True):
195 self.fwd_bus_mode = fwd_bus_mode
196 self.synced = synced
197 self.width, self.depth = width, depth
198 self.memory = Memory(width=width, depth=depth)
199 self._rdports = {}
200 self._wrports = {}
201
202 def read_port(self, name=None):
203 bsz = log2_int(self.depth, False)
204 port = RecordObject([("addr", bsz),
205 ("ren", 1),
206 ("o_data", self.width)], name=name)
207 if self.synced:
208 domain = "sync"
209 else:
210 domain = "comb"
211 self._rdports[name] = (port, self.memory.read_port(domain=domain))
212 return port
213
214 def write_port(self, name=None):
215 bsz = log2_int(self.depth, False)
216 port = RecordObject([("addr", bsz),
217 ("wen", 1),
218 ("i_data", self.width)], name=name)
219 self._wrports[name] = (port, self.memory.write_port())
220 return port
221
222 def elaborate(self, platform):
223 m = Module()
224 comb = m.d.comb
225
226 # read ports. has write-through detection (returns data written)
227 for name, (rp, rport) in self._rdports.items():
228 setattr(m.submodules, "rp_"+name, rport)
229 wr_detect = Signal(reset_less=False)
230 comb += rport.addr.eq(rp.addr)
231 if self.fwd_bus_mode:
232 with m.If(rp.ren):
233 m.d.comb += wr_detect.eq(0)
234 for _, (wp, wport) in self._wrports.items():
235 addrmatch = Signal(reset_less=False)
236 m.d.comb += addrmatch.eq(wp.addr == rp.addr)
237 with m.If(wp.wen & addrmatch):
238 m.d.comb += rp.o_data.eq(wp.i_data)
239 m.d.comb += wr_detect.eq(1)
240 with m.If(~wr_detect):
241 m.d.comb += rp.o_data.eq(rport.data)
242 else:
243 if self.synced:
244 ren_delay = Signal.like(rp.ren)
245 m.d.sync += ren_delay.eq(rp.ren)
246 with m.If(ren_delay):
247 m.d.comb += rp.o_data.eq(rport.data)
248 else:
249 m.d.comb += rp.o_data.eq(rport.data)
250
251 # write ports, delayed by one cycle (in the memory itself)
252 for name, (port, wp) in self._wrports.items():
253 setattr(m.submodules, "wp_"+name, wp)
254 comb += wp.addr.eq(port.addr)
255 comb += wp.en.eq(port.wen)
256 comb += wp.data.eq(port.i_data)
257
258 return m
259
260
261 class RegFile(Elaboratable):
262 unary = False
263 def __init__(self, width, depth):
264 self.width = width
265 self.depth = depth
266 self._rdports = []
267 self._wrports = []
268
269 def read_port(self, name=None):
270 bsz = int(log(self.width) / log(2))
271 port = RecordObject([("addr", bsz),
272 ("ren", 1),
273 ("o_data", self.width)], name=name)
274 self._rdports.append(port)
275 return port
276
277 def write_port(self, name=None):
278 bsz = int(log(self.width) / log(2))
279 port = RecordObject([("addr", bsz),
280 ("wen", 1),
281 ("i_data", self.width)], name=name)
282 self._wrports.append(port)
283 return port
284
285 def elaborate(self, platform):
286 m = Module()
287 bsz = int(log(self.width) / log(2))
288 regs = Array(Signal(self.width, name="reg") for _ in range(self.depth))
289
290 # read ports. has write-through detection (returns data written)
291 for rp in self._rdports:
292 wr_detect = Signal(reset_less=False)
293 with m.If(rp.ren):
294 m.d.comb += wr_detect.eq(0)
295 for wp in self._wrports:
296 addrmatch = Signal(reset_less=False)
297 m.d.comb += addrmatch.eq(wp.addr == rp.addr)
298 with m.If(wp.wen & addrmatch):
299 m.d.comb += rp.o_data.eq(wp.i_data)
300 m.d.comb += wr_detect.eq(1)
301 with m.If(~wr_detect):
302 m.d.comb += rp.o_data.eq(regs[rp.addr])
303
304 # write ports, delayed by one cycle
305 for wp in self._wrports:
306 with m.If(wp.wen):
307 m.d.sync += regs[wp.addr].eq(wp.i_data)
308
309 return m
310
311 def __iter__(self):
312 yield from self._rdports
313 yield from self._wrports
314
315 def ports(self):
316 res = list(self)
317 for r in res:
318 if isinstance(r, RecordObject):
319 yield from r
320 else:
321 yield r
322
323
324 def regfile_sim(dut, rp, wp):
325 yield wp.addr.eq(1)
326 yield wp.i_data.eq(2)
327 yield wp.wen.eq(1)
328 yield
329 yield wp.wen.eq(0)
330 yield wp.addr.eq(0)
331 yield
332 yield
333 yield rp.ren.eq(1)
334 yield rp.addr.eq(1)
335 yield Settle()
336 data = yield rp.o_data
337 print(data)
338 yield
339 data = yield rp.o_data
340 print(data)
341 yield
342 data2 = yield rp.o_data
343 print(data2)
344 assert data == 2
345 yield
346
347 yield wp.addr.eq(5)
348 yield rp.addr.eq(5)
349 yield rp.ren.eq(1)
350 yield wp.wen.eq(1)
351 yield wp.i_data.eq(6)
352 yield
353 data = yield rp.o_data
354 print(data)
355 assert data == 6
356 yield
357 yield wp.wen.eq(0)
358 yield rp.ren.eq(0)
359 yield
360 data = yield rp.o_data
361 print(data)
362 assert data == 0
363 yield
364 data = yield rp.o_data
365 print(data)
366
367
368 def regfile_array_sim(dut, rp1, rp2, wp, wp2):
369 print("regfile_array_sim")
370 yield wp.i_data.eq(2)
371 yield wp.wen.eq(1 << 1)
372 yield
373 yield wp.wen.eq(0)
374 yield rp1.ren.eq(1 << 1)
375 yield Settle()
376 data = yield rp1.o_data
377 print(data)
378 assert data == 2
379 yield
380
381 yield rp1.ren.eq(1 << 5)
382 yield rp2.ren.eq(1 << 1)
383 yield wp.wen.eq(1 << 5)
384 yield wp.i_data.eq(6)
385 yield Settle()
386 data = yield rp1.o_data
387 assert data == 6
388 print(data)
389 yield
390 yield wp.wen.eq(0)
391 yield rp1.ren.eq(0)
392 yield rp2.ren.eq(0)
393 yield Settle()
394 data1 = yield rp1.o_data
395 print(data1)
396 assert data1 == 0
397 data2 = yield rp2.o_data
398 print(data2)
399 assert data2 == 0
400
401 yield
402 data = yield rp1.o_data
403 print(data)
404 assert data == 0
405
406
407 def test_regfile():
408 dut = RegFile(32, 8)
409 rp = dut.read_port()
410 wp = dut.write_port()
411 vl = rtlil.convert(dut)#, ports=dut.ports())
412 with open("test_regfile.il", "w") as f:
413 f.write(vl)
414
415 run_simulation(dut, regfile_sim(dut, rp, wp), vcd_name='test_regfile.vcd')
416
417 dut = RegFileMem(32, 8, True, False)
418 rp = dut.read_port("rp1")
419 wp = dut.write_port("wp1")
420 vl = rtlil.convert(dut)#, ports=dut.ports())
421 with open("test_regmem.il", "w") as f:
422 f.write(vl)
423
424 run_simulation(dut, regfile_sim(dut, rp, wp), vcd_name='test_regmem.vcd')
425
426 dut = RegFileArray(32, 8, False)
427 rp1 = dut.read_port("read1")
428 rp2 = dut.read_port("read2")
429 wp = dut.write_port("write")
430 wp2 = dut.write_port("write2")
431 ports = dut.ports()
432 print("ports", ports)
433 vl = rtlil.convert(dut, ports=ports)
434 with open("test_regfile_array.il", "w") as f:
435 f.write(vl)
436
437 run_simulation(dut, regfile_array_sim(dut, rp1, rp2, wp, wp2),
438 vcd_name='test_regfile_array.vcd')
439
440
441 if __name__ == '__main__':
442 test_regfile()