add in write-mask into MultiCompUnit and MCU-ALU unit test: bug detected in
[soc.git] / src / soc / experiment / compalu_multi.py
1 """Computation Unit (aka "ALU Manager").
2
3 Manages a Pipeline or FSM, ensuring that the start and end time are 100%
4 monitored. At no time may the ALU proceed without this module notifying
5 the Dependency Matrices. At no time is a result production "abandoned".
6 This module blocks (indicates busy) starting from when it first receives
7 an opcode until it receives notification that
8 its result(s) have been successfully stored in the regfile(s)
9
10 Documented at http://libre-soc.org/3d_gpu/architecture/compunit
11 """
12
13 from nmigen.compat.sim import run_simulation, Settle
14 from nmigen.cli import verilog, rtlil
15 from nmigen import Module, Signal, Mux, Elaboratable, Repl, Array, Cat, Const
16 from nmigen.hdl.rec import (Record, DIR_FANIN, DIR_FANOUT)
17
18 from nmutil.latch import SRLatch, latchregister
19 from nmutil.iocontrol import RecordObject
20
21 from soc.decoder.power_decoder2 import Data
22 from soc.decoder.power_enums import InternalOp
23 from soc.fu.regspec import RegSpec, RegSpecALUAPI
24
25
26 def find_ok(fields):
27 """find_ok helper function - finds field ending in "_ok"
28 """
29 for field_name in fields:
30 if field_name.endswith("_ok"):
31 return field_name
32 return None
33
34
35 def go_record(n, name):
36 r = Record([('go', n, DIR_FANIN),
37 ('rel', n, DIR_FANOUT)], name=name)
38 r.go.reset_less = True
39 r.rel.reset_less = True
40 return r
41
42
43 # see https://libre-soc.org/3d_gpu/architecture/regfile/ section on regspecs
44
45 class CompUnitRecord(RegSpec, RecordObject):
46 """CompUnitRecord
47
48 base class for Computation Units, to provide a uniform API
49 and allow "record.connect" etc. to be used, particularly when
50 it comes to connecting multiple Computation Units up as a block
51 (very laborious)
52
53 LDSTCompUnitRecord should derive from this class and add the
54 additional signals it requires
55
56 :subkls: the class (not an instance) needed to construct the opcode
57 :rwid: either an integer (specifies width of all regs) or a "regspec"
58
59 see https://libre-soc.org/3d_gpu/architecture/regfile/ section on regspecs
60 """
61 def __init__(self, subkls, rwid, n_src=None, n_dst=None, name=None):
62 RegSpec.__init__(self, rwid, n_src, n_dst)
63 RecordObject.__init__(self, name)
64 self._subkls = subkls
65 n_src, n_dst = self._n_src, self._n_dst
66
67 # create source operands
68 src = []
69 for i in range(n_src):
70 j = i + 1 # name numbering to match src1/src2
71 name = "src%d_i" % j
72 rw = self._get_srcwid(i)
73 sreg = Signal(rw, name=name, reset_less=True)
74 setattr(self, name, sreg)
75 src.append(sreg)
76 self._src_i = src
77
78 # create dest operands
79 dst = []
80 for i in range(n_dst):
81 j = i + 1 # name numbering to match dest1/2...
82 name = "dest%d_o" % j
83 rw = self._get_dstwid(i)
84 dreg = Signal(rw, name=name, reset_less=True)
85 setattr(self, name, dreg)
86 dst.append(dreg)
87 self._dest = dst
88
89 # operation / data input
90 self.oper_i = subkls(name="oper_i") # operand
91
92 # create read/write and other scoreboard signalling
93 self.rd = go_record(n_src, name="rd") # read in, req out
94 self.wr = go_record(n_dst, name="wr") # write in, req out
95 self.rdmaskn = Signal(n_src, reset_less=True) # read mask
96 self.wrmask = Signal(n_dst, reset_less=True) # write mask
97 self.issue_i = Signal(reset_less=True) # fn issue in
98 self.shadown_i = Signal(reset=1) # shadow function, defaults to ON
99 self.go_die_i = Signal() # go die (reset)
100
101 # output (busy/done)
102 self.busy_o = Signal(reset_less=True) # fn busy out
103 self.done_o = Signal(reset_less=True)
104
105
106 class MultiCompUnit(RegSpecALUAPI, Elaboratable):
107 def __init__(self, rwid, alu, opsubsetkls, n_src=2, n_dst=1):
108 """MultiCompUnit
109
110 * :rwid: width of register latches (TODO: allocate per regspec)
111 * :alu: ALU (pipeline, FSM) - must conform to nmutil Pipe API
112 * :opsubsetkls: subset of Decode2ExecuteType
113 * :n_src: number of src operands
114 * :n_dst: number of destination operands
115 """
116 RegSpecALUAPI.__init__(self, rwid, alu)
117 self.opsubsetkls = opsubsetkls
118 self.cu = cu = CompUnitRecord(opsubsetkls, rwid, n_src, n_dst)
119 n_src, n_dst = self.n_src, self.n_dst = cu._n_src, cu._n_dst
120 print ("n_src %d n_dst %d" % (self.n_src, self.n_dst))
121
122 # convenience names for src operands
123 for i in range(n_src):
124 j = i + 1 # name numbering to match src1/src2
125 name = "src%d_i" % j
126 setattr(self, name, getattr(cu, name))
127
128 # convenience names for dest operands
129 for i in range(n_dst):
130 j = i + 1 # name numbering to match dest1/2...
131 name = "dest%d_o" % j
132 setattr(self, name, getattr(cu, name))
133
134 # more convenience names
135 self.rd = cu.rd
136 self.wr = cu.wr
137 self.rdmaskn = cu.rdmaskn
138 self.wrmask = cu.wrmask
139 self.go_rd_i = self.rd.go # temporary naming
140 self.go_wr_i = self.wr.go # temporary naming
141 self.rd_rel_o = self.rd.rel # temporary naming
142 self.req_rel_o = self.wr.rel # temporary naming
143 self.issue_i = cu.issue_i
144 self.shadown_i = cu.shadown_i
145 self.go_die_i = cu.go_die_i
146
147 # operation / data input
148 self.oper_i = cu.oper_i
149 self.src_i = cu._src_i
150
151 self.busy_o = cu.busy_o
152 self.dest = cu._dest
153 self.data_o = self.dest[0] # Dest out
154 self.done_o = cu.done_o
155
156
157 def _mux_op(self, m, sl, op_is_imm, imm, i):
158 # select imm if opcode says so. however also change the latch
159 # to trigger *from* the opcode latch instead.
160 src_or_imm = Signal(self.cu._get_srcwid(i), reset_less=True)
161 src_sel = Signal(reset_less=True)
162 m.d.comb += src_sel.eq(Mux(op_is_imm, self.opc_l.q, self.src_l.q[i]))
163 m.d.comb += src_or_imm.eq(Mux(op_is_imm, imm, self.src_i[i]))
164 # overwrite 1st src-latch with immediate-muxed stuff
165 sl[i][0] = src_or_imm
166 sl[i][2] = src_sel
167 sl[i][3] = ~op_is_imm # change rd.rel[i] gate condition
168
169 def elaborate(self, platform):
170 m = Module()
171 m.submodules.alu = self.alu
172 m.submodules.src_l = src_l = SRLatch(False, self.n_src, name="src")
173 m.submodules.opc_l = opc_l = SRLatch(sync=False, name="opc")
174 m.submodules.req_l = req_l = SRLatch(False, self.n_dst, name="req")
175 m.submodules.rst_l = rst_l = SRLatch(sync=False, name="rst")
176 m.submodules.rok_l = rok_l = SRLatch(sync=False, name="rdok")
177 self.opc_l, self.src_l = opc_l, src_l
178
179 # ALU only proceeds when all src are ready. rd_rel_o is delayed
180 # so combine it with go_rd_i. if all bits are set we're good
181 all_rd = Signal(reset_less=True)
182 m.d.comb += all_rd.eq(self.busy_o & rok_l.q &
183 (((~self.rd.rel) | self.rd.go).all()))
184
185 # generate read-done pulse
186 all_rd_dly = Signal(reset_less=True)
187 all_rd_pulse = Signal(reset_less=True)
188 m.d.sync += all_rd_dly.eq(all_rd)
189 m.d.comb += all_rd_pulse.eq(all_rd & ~all_rd_dly)
190
191 # create rising pulse from alu valid condition.
192 alu_done = Signal(reset_less=True)
193 alu_done_dly = Signal(reset_less=True)
194 alu_pulse = Signal(reset_less=True)
195 alu_pulsem = Signal(self.n_dst, reset_less=True)
196 m.d.comb += alu_done.eq(self.alu.n.valid_o)
197 m.d.sync += alu_done_dly.eq(alu_done)
198 m.d.comb += alu_pulse.eq(alu_done & ~alu_done_dly)
199 m.d.comb += alu_pulsem.eq(Repl(alu_pulse, self.n_dst))
200
201 # write_requests all done
202 # req_done works because any one of the last of the writes
203 # is enough, when combined with when read-phase is done (rst_l.q)
204 wr_any = Signal(reset_less=True)
205 req_done = Signal(reset_less=True)
206 m.d.comb += self.done_o.eq(self.busy_o & ~(self.wr.rel.bool()))
207 m.d.comb += wr_any.eq(self.wr.go.bool())
208 m.d.comb += req_done.eq(wr_any & ~self.alu.n.ready_i & (req_l.q == 0))
209
210 # shadow/go_die
211 reset = Signal(reset_less=True)
212 rst_r = Signal(reset_less=True) # reset latch off
213 reset_w = Signal(self.n_dst, reset_less=True)
214 reset_r = Signal(self.n_src, reset_less=True)
215 m.d.comb += reset.eq(req_done | self.go_die_i)
216 m.d.comb += rst_r.eq(self.issue_i | self.go_die_i)
217 m.d.comb += reset_w.eq(self.wr.go | Repl(self.go_die_i, self.n_dst))
218 m.d.comb += reset_r.eq(self.rd.go | Repl(self.go_die_i, self.n_src))
219
220 # read-done,wr-proceed latch
221 m.d.comb += rok_l.s.eq(self.issue_i) # set up when issue starts
222 m.d.comb += rok_l.r.eq(self.alu.n.valid_o & self.busy_o) # ALU done
223
224 # wr-done, back-to-start latch
225 m.d.comb += rst_l.s.eq(all_rd) # set when read-phase is fully done
226 m.d.comb += rst_l.r.eq(rst_r) # *off* on issue
227
228 # opcode latch (not using go_rd_i) - inverted so that busy resets to 0
229 m.d.sync += opc_l.s.eq(self.issue_i) # set on issue
230 m.d.sync += opc_l.r.eq(req_done) # reset on ALU
231
232 # src operand latch (not using go_wr_i)
233 m.d.sync += src_l.s.eq(Repl(self.issue_i, self.n_src))
234 m.d.sync += src_l.r.eq(reset_r)
235
236 # dest operand latch (not using issue_i)
237 m.d.comb += req_l.s.eq(alu_pulsem)
238 m.d.comb += req_l.r.eq(reset_w)
239
240 # create a latch/register for the operand
241 oper_r = self.opsubsetkls(name="oper_r")
242 latchregister(m, self.oper_i, oper_r, self.issue_i, "oper_l")
243
244 # and for each output from the ALU: capture when ALU output is valid
245 drl = []
246 wrok = []
247 for i in range(self.n_dst):
248 name = "data_r%d" % i
249 lro = self.get_out(i)
250 ok = Const(1, 1)
251 if isinstance(lro, Record):
252 data_r = Record.like(lro, name=name)
253 print ("wr fields", i, lro, data_r.fields)
254 # bye-bye abstract interface design..
255 fname = find_ok(data_r.fields)
256 if fname:
257 ok = data_r[fname]
258 else:
259 data_r = Signal.like(lro, name=name, reset_less=True)
260 wrok.append(ok)
261 latchregister(m, lro, data_r, alu_pulsem, name + "_l")
262 drl.append(data_r)
263
264 # ok, above we collated anything with an "ok" on the output side
265 # now actually use those to create a write-mask. this basically
266 # is now the Function Unit API tells the Comp Unit "do not request
267 # a regfile port because this particular output is not valid"
268 m.d.comb += self.wrmask.eq(Cat(*wrok))
269
270 # pass the operation to the ALU
271 m.d.comb += self.get_op().eq(oper_r)
272
273 # create list of src/alu-src/src-latch. override 1st and 2nd one below.
274 # in the case, for ALU and Logical pipelines, we assume RB is the
275 # 2nd operand in the input "regspec". see for example
276 # soc.fu.alu.pipe_data.ALUInputData
277 sl = []
278 print ("src_i", self.src_i)
279 for i in range(self.n_src):
280 sl.append([self.src_i[i], self.get_in(i), src_l.q[i], Const(1,1)])
281
282 # if the operand subset has "zero_a" we implicitly assume that means
283 # src_i[0] is an INT reg type where zero can be multiplexed in, instead.
284 # see https://bugs.libre-soc.org/show_bug.cgi?id=336
285 if hasattr(oper_r, "zero_a"):
286 # select zero imm if opcode says so. however also change the latch
287 # to trigger *from* the opcode latch instead.
288 self._mux_op(m, sl, oper_r.zero_a, 0, 0)
289
290 # if the operand subset has "imm_data" we implicitly assume that means
291 # "this is an INT ALU/Logical FU jobbie, RB is muxed with the immediate"
292 if hasattr(oper_r, "imm_data"):
293 # select immediate if opcode says so. however also change the latch
294 # to trigger *from* the opcode latch instead.
295 op_is_imm = oper_r.imm_data.imm_ok
296 imm = oper_r.imm_data.imm
297 self._mux_op(m, sl, op_is_imm, imm, 1)
298
299 # create a latch/register for src1/src2 (even if it is a copy of imm)
300 for i in range(self.n_src):
301 src, alusrc, latch, _ = sl[i]
302 latchregister(m, src, alusrc, latch, name="src_r%d" % i)
303
304 # -----
305 # ALU connection / interaction
306 # -----
307
308 # on a go_read, tell the ALU we're accepting data.
309 m.submodules.alui_l = alui_l = SRLatch(False, name="alui")
310 m.d.comb += self.alu.p.valid_i.eq(alui_l.q)
311 m.d.sync += alui_l.r.eq(self.alu.p.ready_o & alui_l.q)
312 m.d.comb += alui_l.s.eq(all_rd_pulse)
313
314 # ALU output "ready" side. alu "ready" indication stays hi until
315 # ALU says "valid".
316 m.submodules.alu_l = alu_l = SRLatch(False, name="alu")
317 m.d.comb += self.alu.n.ready_i.eq(alu_l.q)
318 m.d.sync += alu_l.r.eq(self.alu.n.valid_o & alu_l.q)
319 m.d.comb += alu_l.s.eq(all_rd_pulse)
320
321 # -----
322 # outputs
323 # -----
324
325 slg = Cat(*map(lambda x: x[3], sl)) # get req gate conditions
326 # all request signals gated by busy_o. prevents picker problems
327 m.d.comb += self.busy_o.eq(opc_l.q) # busy out
328
329 # read-release gated by busy (and read-mask)
330 bro = Repl(self.busy_o, self.n_src)
331 m.d.comb += self.rd.rel.eq(src_l.q & bro & slg & ~self.rdmaskn)
332
333 # write-release gated by busy and by shadow (and write-mask)
334 brd = Repl(self.busy_o & self.shadown_i, self.n_dst)
335 m.d.comb += self.wr.rel.eq(req_l.q & brd & self.wrmask)
336
337 # output the data from the latch on go_write
338 for i in range(self.n_dst):
339 with m.If(self.wr.go[i]):
340 m.d.comb += self.dest[i].eq(drl[i])
341
342 return m
343
344 def __iter__(self):
345 yield self.rd.go
346 yield self.wr.go
347 yield self.issue_i
348 yield self.shadown_i
349 yield self.go_die_i
350 yield from self.oper_i.ports()
351 yield self.src1_i
352 yield self.src2_i
353 yield self.busy_o
354 yield self.rd.rel
355 yield self.wr.rel
356 yield self.data_o
357
358 def ports(self):
359 return list(self)
360
361
362 def op_sim(dut, a, b, op, inv_a=0, imm=0, imm_ok=0, zero_a=0):
363 yield dut.issue_i.eq(0)
364 yield
365 yield dut.src_i[0].eq(a)
366 yield dut.src_i[1].eq(b)
367 yield dut.oper_i.insn_type.eq(op)
368 yield dut.oper_i.invert_a.eq(inv_a)
369 yield dut.oper_i.imm_data.imm.eq(imm)
370 yield dut.oper_i.imm_data.imm_ok.eq(imm_ok)
371 yield dut.oper_i.zero_a.eq(zero_a)
372 yield dut.issue_i.eq(1)
373 yield
374 yield dut.issue_i.eq(0)
375 yield
376 if not imm_ok or not zero_a:
377 yield dut.rd.go.eq(0b11)
378 while True:
379 yield
380 rd_rel_o = yield dut.rd.rel
381 print ("rd_rel", rd_rel_o)
382 if rd_rel_o:
383 break
384 yield dut.rd.go.eq(0)
385 if len(dut.src_i) == 3:
386 yield dut.rd.go.eq(0b100)
387 while True:
388 yield
389 rd_rel_o = yield dut.rd.rel
390 print ("rd_rel", rd_rel_o)
391 if rd_rel_o:
392 break
393 yield dut.rd.go.eq(0)
394
395 req_rel_o = yield dut.wr.rel
396 result = yield dut.data_o
397 print ("req_rel", req_rel_o, result)
398 while True:
399 req_rel_o = yield dut.wr.rel
400 result = yield dut.data_o
401 print ("req_rel", req_rel_o, result)
402 if req_rel_o:
403 break
404 yield
405 yield dut.wr.go[0].eq(1)
406 yield Settle()
407 result = yield dut.data_o
408 yield
409 print ("result", result)
410 yield dut.wr.go[0].eq(0)
411 yield
412 return result
413
414
415 def scoreboard_sim_dummy(dut):
416 result = yield from op_sim(dut, 5, 2, InternalOp.OP_NOP, inv_a=0,
417 imm=8, imm_ok=1)
418 assert result == 5, result
419
420 result = yield from op_sim(dut, 9, 2, InternalOp.OP_NOP, inv_a=0,
421 imm=8, imm_ok=1)
422 assert result == 9, result
423
424
425 def scoreboard_sim(dut):
426 result = yield from op_sim(dut, 5, 2, InternalOp.OP_ADD, inv_a=0,
427 imm=8, imm_ok=1)
428 assert result == 13
429
430 result = yield from op_sim(dut, 5, 2, InternalOp.OP_ADD)
431 assert result == 7
432
433 result = yield from op_sim(dut, 5, 2, InternalOp.OP_ADD, inv_a=1)
434 assert result == 65532
435
436 result = yield from op_sim(dut, 5, 2, InternalOp.OP_ADD, zero_a=1,
437 imm=8, imm_ok=1)
438 assert result == 8
439
440 result = yield from op_sim(dut, 5, 2, InternalOp.OP_ADD, zero_a=1)
441 assert result == 2
442
443
444 def test_compunit():
445 from alu_hier import ALU
446 from soc.fu.alu.alu_input_record import CompALUOpSubset
447
448 m = Module()
449 alu = ALU(16)
450 dut = MultiCompUnit(16, alu, CompALUOpSubset)
451 m.submodules.cu = dut
452
453 vl = rtlil.convert(dut, ports=dut.ports())
454 with open("test_compunit1.il", "w") as f:
455 f.write(vl)
456
457 run_simulation(m, scoreboard_sim(dut), vcd_name='test_compunit1.vcd')
458
459
460 class CompUnitParallelTest:
461 def __init__(self, dut):
462 self.dut = dut
463
464 # Operation cycle should not take longer than this:
465 self.MAX_BUSY_WAIT = 50
466
467 # Minimum duration in which issue_i will be kept inactive,
468 # during which busy_o must remain low.
469 self.MIN_BUSY_LOW = 5
470
471 # Number of cycles to stall until the assertion of go.
472 # One value, for each port. Can be zero, for no delay.
473 self.RD_GO_DELAY = [0, 3]
474
475 # store common data for the input operation of the processes
476 # input operation:
477 self.op = 0
478 self.inv_a = self.zero_a = 0
479 self.imm = self.imm_ok = 0
480 # input data:
481 self.a = self.b = 0
482
483 def driver(self):
484 print("Begin parallel test.")
485 yield from self.operation(5, 2, InternalOp.OP_ADD, inv_a=0,
486 imm=8, imm_ok=1)
487
488 def operation(self, a, b, op, inv_a=0, imm=0, imm_ok=0, zero_a=0):
489 # store data for the operation
490 self.a = a
491 self.b = b
492 self.op = op
493 self.inv_a = inv_a
494 self.imm = imm
495 self.imm_ok = imm_ok
496 self.zero_a = zero_a
497
498 # trigger operation cycle
499 yield from self.issue()
500
501 def issue(self):
502 # issue_i starts inactive
503 yield self.dut.issue_i.eq(0)
504
505 for n in range(self.MIN_BUSY_LOW):
506 yield
507 # busy_o must remain inactive. It cannot rise on its own.
508 busy_o = yield self.dut.busy_o
509 assert not busy_o
510
511 # activate issue_i to begin the operation cycle
512 yield self.dut.issue_i.eq(1)
513
514 # at the same time, present the operation
515 yield self.dut.oper_i.insn_type.eq(self.op)
516 yield self.dut.oper_i.invert_a.eq(self.inv_a)
517 yield self.dut.oper_i.imm_data.imm.eq(self.imm)
518 yield self.dut.oper_i.imm_data.imm_ok.eq(self.imm_ok)
519 yield self.dut.oper_i.zero_a.eq(self.zero_a)
520
521 # give one cycle for the CompUnit to latch the data
522 yield
523
524 # busy_o must keep being low in this cycle, because issue_i was
525 # low on the previous cycle.
526 # It cannot rise on its own.
527 # Also, busy_o and issue_i must never be active at the same time, ever.
528 busy_o = yield self.dut.busy_o
529 assert not busy_o
530
531 # Lower issue_i
532 yield self.dut.issue_i.eq(0)
533
534 # deactivate inputs along with issue_i, so we can be sure the data
535 # was latched at the correct cycle
536 yield self.dut.oper_i.insn_type.eq(0)
537 yield self.dut.oper_i.invert_a.eq(0)
538 yield self.dut.oper_i.imm_data.imm.eq(0)
539 yield self.dut.oper_i.imm_data.imm_ok.eq(0)
540 yield self.dut.oper_i.zero_a.eq(0)
541 yield
542
543 # wait for busy_o to lower
544 # timeout after self.MAX_BUSY_WAIT cycles
545 for n in range(self.MAX_BUSY_WAIT):
546 # sample busy_o in the current cycle
547 busy_o = yield self.dut.busy_o
548 if not busy_o:
549 # operation cycle ends when busy_o becomes inactive
550 break
551 yield
552
553 # if busy_o is still active, a timeout has occurred
554 # TODO: Uncomment this, once the test is complete:
555 # assert not busy_o
556
557 if busy_o:
558 print("If you are reading this, "
559 "it's because the above test failed, as expected,\n"
560 "with a timeout. It must pass, once the test is complete.")
561 return
562
563 print("If you are reading this, "
564 "it's because the above test unexpectedly passed.")
565
566 def rd(self, rd_idx):
567 # wait for issue_i to rise
568 while True:
569 issue_i = yield self.dut.issue_i
570 if issue_i:
571 break
572 # issue_i has not risen yet, so rd must keep low
573 rel = yield self.dut.rd.rel[rd_idx]
574 assert not rel
575 yield
576
577 # we do not want rd to rise on an immediate operand
578 # if it is immediate, exit the process
579 # TODO: don't exit the process, monitor rd instead to ensure it
580 # doesn't rise on its own
581 if (self.zero_a and rd_idx == 0) or (self.imm_ok and rd_idx == 1):
582 return
583
584 # issue_i has risen. rel must rise on the next cycle
585 rel = yield self.dut.rd.rel[rd_idx]
586 assert not rel
587
588 # stall for additional cycles. Check that rel doesn't fall on its own
589 for n in range(self.RD_GO_DELAY[rd_idx]):
590 yield
591 rel = yield self.dut.rd.rel[rd_idx]
592 assert rel
593
594 # Before asserting "go", make sure "rel" has risen.
595 # The use of Settle allows "go" to be set combinatorially,
596 # rising on the same cycle as "rel".
597 yield Settle()
598 rel = yield self.dut.rd.rel[rd_idx]
599 assert rel
600
601 # assert go for one cycle
602 yield self.dut.rd.go[rd_idx].eq(1)
603 yield
604
605 # rel must keep high, since go was inactive in the last cycle
606 rel = yield self.dut.rd.rel[rd_idx]
607 assert rel
608
609 # finish the go one-clock pulse
610 yield self.dut.rd.go[rd_idx].eq(0)
611 yield
612
613 # rel must have gone low in response to go being high
614 # on the previous cycle
615 rel = yield self.dut.rd.rel[rd_idx]
616 assert not rel
617
618 # TODO: also when dut.rd.go is set, put the expected value into
619 # the src_i. use dut.get_in[rd_idx] to do so
620
621 def wr(self, wr_idx):
622 # monitor self.dut.wr.req[rd_idx] and sets dut.wr.go[idx] for one cycle
623 yield
624 # TODO: also when dut.wr.go is set, check the output against the
625 # self.expected_o and assert. use dut.get_out(wr_idx) to do so.
626
627 def run_simulation(self, vcd_name):
628 run_simulation(self.dut, [self.driver(),
629 self.rd(0), # one read port (a)
630 self.rd(1), # one read port (b)
631 self.wr(0), # one write port (o)
632 ],
633 vcd_name=vcd_name)
634
635
636 def test_compunit_regspec3():
637 from alu_hier import DummyALU
638 from soc.fu.alu.alu_input_record import CompALUOpSubset
639
640 inspec = [('INT', 'a', '0:15'),
641 ('INT', 'b', '0:15'),
642 ('INT', 'c', '0:15')]
643 outspec = [('INT', 'o', '0:15'),
644 ]
645
646 regspec = (inspec, outspec)
647
648 m = Module()
649 alu = DummyALU(16)
650 dut = MultiCompUnit(regspec, alu, CompALUOpSubset)
651 m.submodules.cu = dut
652
653 run_simulation(m, scoreboard_sim_dummy(dut),
654 vcd_name='test_compunit_regspec3.vcd')
655
656
657 def test_compunit_regspec1():
658 from alu_hier import ALU
659 from soc.fu.alu.alu_input_record import CompALUOpSubset
660
661 inspec = [('INT', 'a', '0:15'),
662 ('INT', 'b', '0:15')]
663 outspec = [('INT', 'o', '0:15'),
664 ]
665
666 regspec = (inspec, outspec)
667
668 m = Module()
669 alu = ALU(16)
670 dut = MultiCompUnit(regspec, alu, CompALUOpSubset)
671 m.submodules.cu = dut
672
673 vl = rtlil.convert(dut, ports=dut.ports())
674 with open("test_compunit_regspec1.il", "w") as f:
675 f.write(vl)
676
677 run_simulation(m, scoreboard_sim(dut),
678 vcd_name='test_compunit_regspec1.vcd')
679
680 test = CompUnitParallelTest(dut)
681 test.run_simulation("test_compunit_parallel.vcd")
682
683
684 if __name__ == '__main__':
685 test_compunit()
686 test_compunit_regspec1()
687 test_compunit_regspec3()