fc21db82ee9ea77b1cf31f2d6c2f704c4346de1c
[soc.git] / src / soc / experiment / test / test_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 soc.experiment.alu_fsm import Shifter, CompFSMOpSubset
14 from soc.fu.alu.alu_input_record import CompALUOpSubset
15 from soc.experiment.alu_hier import ALU, DummyALU
16 from soc.experiment.compalu_multi import MultiCompUnit
17 from soc.decoder.power_enums import MicrOp
18 from nmutil.gtkw import write_gtkw
19 from nmigen import Module
20 from nmigen.cli import rtlil
21
22 # NOTE: to use cxxsim, export NMIGEN_SIM_MODE=cxxsim from the shell
23 # Also, check out the cxxsim nmigen branch, and latest yosys from git
24 from nmutil.sim_tmp_alternative import Simulator, Settle, is_engine_pysim
25
26
27 def wrap(process):
28 def wrapper():
29 yield from process
30 return wrapper
31
32
33 def op_sim_fsm(dut, a, b, direction):
34 print("op_sim_fsm", a, b, direction)
35 yield dut.issue_i.eq(0)
36 yield
37 yield dut.src_i[0].eq(a)
38 yield dut.src_i[1].eq(b)
39 yield dut.oper_i.sdir.eq(direction)
40 yield dut.issue_i.eq(1)
41 yield
42 yield dut.issue_i.eq(0)
43 yield
44
45 yield dut.rd.go_i.eq(0b11)
46 while True:
47 yield
48 rd_rel_o = yield dut.rd.rel_o
49 print("rd_rel", rd_rel_o)
50 if rd_rel_o:
51 break
52 yield dut.rd.go_i.eq(0)
53
54 req_rel_o = yield dut.wr.rel_o
55 result = yield dut.data_o
56 print("req_rel", req_rel_o, result)
57 while True:
58 req_rel_o = yield dut.wr.rel_o
59 result = yield dut.data_o
60 print("req_rel", req_rel_o, result)
61 if req_rel_o:
62 break
63 yield
64 yield dut.wr.go_i[0].eq(1)
65 yield Settle()
66 result = yield dut.data_o
67 yield
68 print("result", result)
69 yield dut.wr.go_i[0].eq(0)
70 yield
71 return result
72
73
74 def op_sim(dut, a, b, op, inv_a=0, imm=0, imm_ok=0, zero_a=0):
75 yield dut.issue_i.eq(0)
76 yield
77 yield dut.src_i[0].eq(a)
78 yield dut.src_i[1].eq(b)
79 yield dut.oper_i.insn_type.eq(op)
80 yield dut.oper_i.invert_in.eq(inv_a)
81 yield dut.oper_i.imm_data.data.eq(imm)
82 yield dut.oper_i.imm_data.ok.eq(imm_ok)
83 yield dut.oper_i.zero_a.eq(zero_a)
84 yield dut.issue_i.eq(1)
85 yield
86 yield dut.issue_i.eq(0)
87 yield
88 if not imm_ok or not zero_a:
89 yield dut.rd.go_i.eq(0b11)
90 while True:
91 yield
92 rd_rel_o = yield dut.rd.rel_o
93 print("rd_rel", rd_rel_o)
94 if rd_rel_o:
95 break
96 yield dut.rd.go_i.eq(0)
97 else:
98 print("no go rd")
99
100 if len(dut.src_i) == 3:
101 yield dut.rd.go_i.eq(0b100)
102 while True:
103 yield
104 rd_rel_o = yield dut.rd.rel_o
105 print("rd_rel", rd_rel_o)
106 if rd_rel_o:
107 break
108 yield dut.rd.go_i.eq(0)
109 else:
110 print("no 3rd rd")
111
112 req_rel_o = yield dut.wr.rel_o
113 result = yield dut.data_o
114 print("req_rel", req_rel_o, result)
115 while True:
116 req_rel_o = yield dut.wr.rel_o
117 result = yield dut.data_o
118 print("req_rel", req_rel_o, result)
119 if req_rel_o:
120 break
121 yield
122 yield dut.wr.go_i[0].eq(1)
123 yield Settle()
124 result = yield dut.data_o
125 yield
126 print("result", result)
127 yield dut.wr.go_i[0].eq(0)
128 yield
129 return result
130
131
132 def scoreboard_sim_fsm(dut):
133 result = yield from op_sim_fsm(dut, 13, 2, 1)
134 assert result == 3, result
135
136 result = yield from op_sim_fsm(dut, 3, 4, 0)
137 assert result == 48, result
138
139 result = yield from op_sim_fsm(dut, 21, 0, 0)
140 assert result == 21, result
141
142
143 def scoreboard_sim_dummy(dut):
144 result = yield from op_sim(dut, 5, 2, MicrOp.OP_NOP, inv_a=0,
145 imm=8, imm_ok=1)
146 assert result == 5, result
147
148 result = yield from op_sim(dut, 9, 2, MicrOp.OP_NOP, inv_a=0,
149 imm=8, imm_ok=1)
150 assert result == 9, result
151
152
153 def scoreboard_sim(dut):
154 # zero (no) input operands test
155 result = yield from op_sim(dut, 5, 2, MicrOp.OP_ADD, zero_a=1,
156 imm=8, imm_ok=1)
157 assert result == 8
158
159 result = yield from op_sim(dut, 5, 2, MicrOp.OP_ADD, inv_a=0,
160 imm=8, imm_ok=1)
161 assert result == 13
162
163 result = yield from op_sim(dut, 5, 2, MicrOp.OP_ADD)
164 assert result == 7
165
166 result = yield from op_sim(dut, 5, 2, MicrOp.OP_ADD, inv_a=1)
167 assert result == 65532
168
169 result = yield from op_sim(dut, 5, 2, MicrOp.OP_ADD, zero_a=1)
170 assert result == 2
171
172 # test combinatorial zero-delay operation
173 # In the test ALU, any operation other than ADD, MUL or SHR
174 # is zero-delay, and do a subtraction.
175 result = yield from op_sim(dut, 5, 2, MicrOp.OP_NOP)
176 assert result == 3
177
178
179 def test_compunit_fsm():
180 top = "top.cu" if is_engine_pysim() else "cu"
181 traces = [
182 'clk', 'src1_i[7:0]', 'src2_i[7:0]', 'oper_i_None__sdir', 'cu_issue_i',
183 'cu_busy_o', 'cu_rd__rel_o[1:0]', 'cu_rd__go_i[1:0]',
184 'cu_wr__rel_o', 'cu_wr__go_i', 'dest1_o[7:0]',
185 ('alu', {'module': top+'.alu'}, [
186 'p_data_i[7:0]', 'p_shift_i[7:0]', 'op__sdir',
187 'p_valid_i', 'p_ready_o', 'n_valid_o', 'n_ready_i',
188 'n_data_o[7:0]'
189 ])
190
191 ]
192 write_gtkw(
193 "test_compunit_fsm1.gtkw",
194 "test_compunit_fsm1.vcd",
195 traces,
196 module=top
197 )
198 m = Module()
199 alu = Shifter(8)
200 dut = MultiCompUnit(8, alu, CompFSMOpSubset)
201 m.submodules.cu = dut
202
203 vl = rtlil.convert(dut, ports=dut.ports())
204 with open("test_compunit_fsm1.il", "w") as f:
205 f.write(vl)
206
207 sim = Simulator(m)
208 sim.add_clock(1e-6)
209
210 sim.add_sync_process(wrap(scoreboard_sim_fsm(dut)))
211 sim_writer = sim.write_vcd('test_compunit_fsm1.vcd')
212 with sim_writer:
213 sim.run()
214
215
216 def test_compunit():
217
218 m = Module()
219 alu = ALU(16)
220 dut = MultiCompUnit(16, alu, CompALUOpSubset)
221 m.submodules.cu = dut
222
223 vl = rtlil.convert(dut, ports=dut.ports())
224 with open("test_compunit1.il", "w") as f:
225 f.write(vl)
226
227 sim = Simulator(m)
228 sim.add_clock(1e-6)
229
230 sim.add_sync_process(wrap(scoreboard_sim(dut)))
231 sim_writer = sim.write_vcd('test_compunit1.vcd')
232 with sim_writer:
233 sim.run()
234
235
236 class CompUnitParallelTest:
237 def __init__(self, dut):
238 self.dut = dut
239
240 # Operation cycle should not take longer than this:
241 self.MAX_BUSY_WAIT = 50
242
243 # Minimum duration in which issue_i will be kept inactive,
244 # during which busy_o must remain low.
245 self.MIN_BUSY_LOW = 5
246
247 # Number of cycles to stall until the assertion of go.
248 # One value, for each port. Can be zero, for no delay.
249 self.RD_GO_DELAY = [0, 3]
250
251 # store common data for the input operation of the processes
252 # input operation:
253 self.op = 0
254 self.inv_a = self.zero_a = 0
255 self.imm = self.imm_ok = 0
256 self.imm_control = (0, 0)
257 self.rdmaskn = (0, 0)
258 # input data:
259 self.operands = (0, 0)
260
261 # Indicates completion of the sub-processes
262 self.rd_complete = [False, False]
263
264 def driver(self):
265 print("Begin parallel test.")
266 yield from self.operation(5, 2, MicrOp.OP_ADD)
267
268 def operation(self, a, b, op, inv_a=0, imm=0, imm_ok=0, zero_a=0,
269 rdmaskn=(0, 0)):
270 # store data for the operation
271 self.operands = (a, b)
272 self.op = op
273 self.inv_a = inv_a
274 self.imm = imm
275 self.imm_ok = imm_ok
276 self.zero_a = zero_a
277 self.imm_control = (zero_a, imm_ok)
278 self.rdmaskn = rdmaskn
279
280 # Initialize completion flags
281 self.rd_complete = [False, False]
282
283 # trigger operation cycle
284 yield from self.issue()
285
286 # check that the sub-processes completed, before the busy_o cycle ended
287 for completion in self.rd_complete:
288 assert completion
289
290 def issue(self):
291 # issue_i starts inactive
292 yield self.dut.issue_i.eq(0)
293
294 for n in range(self.MIN_BUSY_LOW):
295 yield
296 # busy_o must remain inactive. It cannot rise on its own.
297 busy_o = yield self.dut.busy_o
298 assert not busy_o
299
300 # activate issue_i to begin the operation cycle
301 yield self.dut.issue_i.eq(1)
302
303 # at the same time, present the operation
304 yield self.dut.oper_i.insn_type.eq(self.op)
305 yield self.dut.oper_i.invert_in.eq(self.inv_a)
306 yield self.dut.oper_i.imm_data.data.eq(self.imm)
307 yield self.dut.oper_i.imm_data.ok.eq(self.imm_ok)
308 yield self.dut.oper_i.zero_a.eq(self.zero_a)
309 rdmaskn = self.rdmaskn[0] | (self.rdmaskn[1] << 1)
310 yield self.dut.rdmaskn.eq(rdmaskn)
311
312 # give one cycle for the CompUnit to latch the data
313 yield
314
315 # busy_o must keep being low in this cycle, because issue_i was
316 # low on the previous cycle.
317 # It cannot rise on its own.
318 # Also, busy_o and issue_i must never be active at the same time, ever.
319 busy_o = yield self.dut.busy_o
320 assert not busy_o
321
322 # Lower issue_i
323 yield self.dut.issue_i.eq(0)
324
325 # deactivate inputs along with issue_i, so we can be sure the data
326 # was latched at the correct cycle
327 # note: rdmaskn must be held, while busy_o is active
328 # TODO: deactivate rdmaskn when the busy_o cycle ends
329 yield self.dut.oper_i.insn_type.eq(0)
330 yield self.dut.oper_i.invert_in.eq(0)
331 yield self.dut.oper_i.imm_data.data.eq(0)
332 yield self.dut.oper_i.imm_data.ok.eq(0)
333 yield self.dut.oper_i.zero_a.eq(0)
334 yield
335
336 # wait for busy_o to lower
337 # timeout after self.MAX_BUSY_WAIT cycles
338 for n in range(self.MAX_BUSY_WAIT):
339 # sample busy_o in the current cycle
340 busy_o = yield self.dut.busy_o
341 if not busy_o:
342 # operation cycle ends when busy_o becomes inactive
343 break
344 yield
345
346 # if busy_o is still active, a timeout has occurred
347 # TODO: Uncomment this, once the test is complete:
348 # assert not busy_o
349
350 if busy_o:
351 print("If you are reading this, "
352 "it's because the above test failed, as expected,\n"
353 "with a timeout. It must pass, once the test is complete.")
354 return
355
356 print("If you are reading this, "
357 "it's because the above test unexpectedly passed.")
358
359 def rd(self, rd_idx):
360 # wait for issue_i to rise
361 while True:
362 issue_i = yield self.dut.issue_i
363 if issue_i:
364 break
365 # issue_i has not risen yet, so rd must keep low
366 rel = yield self.dut.rd.rel_o[rd_idx]
367 assert not rel
368 yield
369
370 # we do not want rd to rise on an immediate operand
371 # if it is immediate, exit the process
372 # likewise, if the read mask is active
373 # TODO: don't exit the process, monitor rd instead to ensure it
374 # doesn't rise on its own
375 if self.rdmaskn[rd_idx] or self.imm_control[rd_idx]:
376 self.rd_complete[rd_idx] = True
377 return
378
379 # issue_i has risen. rel must rise on the next cycle
380 rel = yield self.dut.rd.rel_o[rd_idx]
381 assert not rel
382
383 # stall for additional cycles. Check that rel doesn't fall on its own
384 for n in range(self.RD_GO_DELAY[rd_idx]):
385 yield
386 rel = yield self.dut.rd.rel_o[rd_idx]
387 assert rel
388
389 # Before asserting "go", make sure "rel" has risen.
390 # The use of Settle allows "go" to be set combinatorially,
391 # rising on the same cycle as "rel".
392 yield Settle()
393 rel = yield self.dut.rd.rel_o[rd_idx]
394 assert rel
395
396 # assert go for one cycle, passing along the operand value
397 yield self.dut.rd.go_i[rd_idx].eq(1)
398 yield self.dut.src_i[rd_idx].eq(self.operands[rd_idx])
399 # check that the operand was sent to the alu
400 # TODO: Properly check the alu protocol
401 yield Settle()
402 alu_input = yield self.dut.get_in(rd_idx)
403 assert alu_input == self.operands[rd_idx]
404 yield
405
406 # rel must keep high, since go was inactive in the last cycle
407 rel = yield self.dut.rd.rel_o[rd_idx]
408 assert rel
409
410 # finish the go one-clock pulse
411 yield self.dut.rd.go_i[rd_idx].eq(0)
412 yield self.dut.src_i[rd_idx].eq(0)
413 yield
414
415 # rel must have gone low in response to go being high
416 # on the previous cycle
417 rel = yield self.dut.rd.rel_o[rd_idx]
418 assert not rel
419
420 self.rd_complete[rd_idx] = True
421
422 # TODO: check that rel doesn't rise again until the end of the
423 # busy_o cycle
424
425 def wr(self, wr_idx):
426 # monitor self.dut.wr.req[rd_idx] and sets dut.wr.go[idx] for one cycle
427 yield
428 # TODO: also when dut.wr.go is set, check the output against the
429 # self.expected_o and assert. use dut.get_out(wr_idx) to do so.
430
431 def run_simulation(self, vcd_name):
432 m = Module()
433 m.submodules.cu = self.dut
434 sim = Simulator(m)
435 sim.add_clock(1e-6)
436
437 sim.add_sync_process(wrap(self.driver()))
438 sim.add_sync_process(wrap(self.rd(0)))
439 sim.add_sync_process(wrap(self.rd(1)))
440 sim.add_sync_process(wrap(self.wr(0)))
441 sim_writer = sim.write_vcd(vcd_name)
442 with sim_writer:
443 sim.run()
444
445
446 def test_compunit_regspec2_fsm():
447
448 inspec = [('INT', 'a', '0:15'),
449 ('INT', 'b', '0:15'),
450 ]
451 outspec = [('INT', 'o', '0:15'),
452 ]
453
454 regspec = (inspec, outspec)
455
456 m = Module()
457 alu = Shifter(8)
458 dut = MultiCompUnit(regspec, alu, CompFSMOpSubset)
459 m.submodules.cu = dut
460
461 sim = Simulator(m)
462 sim.add_clock(1e-6)
463
464 sim.add_sync_process(wrap(scoreboard_sim_fsm(dut)))
465 sim_writer = sim.write_vcd('test_compunit_regspec2_fsm.vcd')
466 with sim_writer:
467 sim.run()
468
469
470 def test_compunit_regspec3():
471
472 inspec = [('INT', 'a', '0:15'),
473 ('INT', 'b', '0:15'),
474 ('INT', 'c', '0:15')]
475 outspec = [('INT', 'o', '0:15'),
476 ]
477
478 regspec = (inspec, outspec)
479
480 m = Module()
481 alu = DummyALU(16)
482 dut = MultiCompUnit(regspec, alu, CompALUOpSubset)
483 m.submodules.cu = dut
484
485 sim = Simulator(m)
486 sim.add_clock(1e-6)
487
488 sim.add_sync_process(wrap(scoreboard_sim_dummy(dut)))
489 sim_writer = sim.write_vcd('test_compunit_regspec3.vcd')
490 with sim_writer:
491 sim.run()
492
493
494 def test_compunit_regspec1():
495
496 inspec = [('INT', 'a', '0:15'),
497 ('INT', 'b', '0:15')]
498 outspec = [('INT', 'o', '0:15'),
499 ]
500
501 regspec = (inspec, outspec)
502
503 m = Module()
504 alu = ALU(16)
505 dut = MultiCompUnit(regspec, alu, CompALUOpSubset)
506 m.submodules.cu = dut
507
508 vl = rtlil.convert(dut, ports=dut.ports())
509 with open("test_compunit_regspec1.il", "w") as f:
510 f.write(vl)
511
512 sim = Simulator(m)
513 sim.add_clock(1e-6)
514
515 sim.add_sync_process(wrap(scoreboard_sim(dut)))
516 sim_writer = sim.write_vcd('test_compunit_regspec1.vcd')
517 with sim_writer:
518 sim.run()
519
520 test = CompUnitParallelTest(dut)
521 test.run_simulation("test_compunit_parallel.vcd")
522
523
524 if __name__ == '__main__':
525 test_compunit()
526 test_compunit_fsm()
527 test_compunit_regspec1()
528 test_compunit_regspec3()