Merge remote-tracking branch 'origin/master'
[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 nmigen import Module
19 from nmigen.cli import rtlil
20 cxxsim = False
21 if cxxsim:
22 from nmigen.sim.cxxsim import Simulator, Settle
23 else:
24 from nmigen.back.pysim import Simulator, Settle
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.eq(0b11)
46 while True:
47 yield
48 rd_rel_o = yield dut.rd.rel
49 print("rd_rel", rd_rel_o)
50 if rd_rel_o:
51 break
52 yield dut.rd.go.eq(0)
53
54 req_rel_o = yield dut.wr.rel
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
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[0].eq(1)
65 yield Settle()
66 result = yield dut.data_o
67 yield
68 print("result", result)
69 yield dut.wr.go[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_a.eq(inv_a)
81 yield dut.oper_i.imm_data.imm.eq(imm)
82 yield dut.oper_i.imm_data.imm_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.eq(0b11)
90 while True:
91 yield
92 rd_rel_o = yield dut.rd.rel
93 print("rd_rel", rd_rel_o)
94 if rd_rel_o:
95 break
96 yield dut.rd.go.eq(0)
97 else:
98 print("no go rd")
99
100 if len(dut.src_i) == 3:
101 yield dut.rd.go.eq(0b100)
102 while True:
103 yield
104 rd_rel_o = yield dut.rd.rel
105 print("rd_rel", rd_rel_o)
106 if rd_rel_o:
107 break
108 yield dut.rd.go.eq(0)
109 else:
110 print("no 3rd rd")
111
112 req_rel_o = yield dut.wr.rel
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
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[0].eq(1)
123 yield Settle()
124 result = yield dut.data_o
125 yield
126 print("result", result)
127 yield dut.wr.go[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
181 m = Module()
182 alu = Shifter(8)
183 dut = MultiCompUnit(8, alu, CompFSMOpSubset)
184 m.submodules.cu = dut
185
186 vl = rtlil.convert(dut, ports=dut.ports())
187 with open("test_compunit_fsm1.il", "w") as f:
188 f.write(vl)
189
190 sim = Simulator(m)
191 sim.add_clock(1e-6)
192
193 sim.add_sync_process(wrap(scoreboard_sim_fsm(dut)))
194 sim_writer = sim.write_vcd('test_compunit_fsm1.vcd')
195 with sim_writer:
196 sim.run()
197
198
199 def test_compunit():
200
201 m = Module()
202 alu = ALU(16)
203 dut = MultiCompUnit(16, alu, CompALUOpSubset)
204 m.submodules.cu = dut
205
206 vl = rtlil.convert(dut, ports=dut.ports())
207 with open("test_compunit1.il", "w") as f:
208 f.write(vl)
209
210 sim = Simulator(m)
211 sim.add_clock(1e-6)
212
213 sim.add_sync_process(wrap(scoreboard_sim(dut)))
214 sim_writer = sim.write_vcd('test_compunit1.vcd')
215 with sim_writer:
216 sim.run()
217
218
219 class CompUnitParallelTest:
220 def __init__(self, dut):
221 self.dut = dut
222
223 # Operation cycle should not take longer than this:
224 self.MAX_BUSY_WAIT = 50
225
226 # Minimum duration in which issue_i will be kept inactive,
227 # during which busy_o must remain low.
228 self.MIN_BUSY_LOW = 5
229
230 # Number of cycles to stall until the assertion of go.
231 # One value, for each port. Can be zero, for no delay.
232 self.RD_GO_DELAY = [0, 3]
233
234 # store common data for the input operation of the processes
235 # input operation:
236 self.op = 0
237 self.inv_a = self.zero_a = 0
238 self.imm = self.imm_ok = 0
239 self.imm_control = (0, 0)
240 self.rdmaskn = (0, 0)
241 # input data:
242 self.operands = (0, 0)
243
244 # Indicates completion of the sub-processes
245 self.rd_complete = [False, False]
246
247 def driver(self):
248 print("Begin parallel test.")
249 yield from self.operation(5, 2, MicrOp.OP_ADD)
250
251 def operation(self, a, b, op, inv_a=0, imm=0, imm_ok=0, zero_a=0,
252 rdmaskn=(0, 0)):
253 # store data for the operation
254 self.operands = (a, b)
255 self.op = op
256 self.inv_a = inv_a
257 self.imm = imm
258 self.imm_ok = imm_ok
259 self.zero_a = zero_a
260 self.imm_control = (zero_a, imm_ok)
261 self.rdmaskn = rdmaskn
262
263 # Initialize completion flags
264 self.rd_complete = [False, False]
265
266 # trigger operation cycle
267 yield from self.issue()
268
269 # check that the sub-processes completed, before the busy_o cycle ended
270 for completion in self.rd_complete:
271 assert completion
272
273 def issue(self):
274 # issue_i starts inactive
275 yield self.dut.issue_i.eq(0)
276
277 for n in range(self.MIN_BUSY_LOW):
278 yield
279 # busy_o must remain inactive. It cannot rise on its own.
280 busy_o = yield self.dut.busy_o
281 assert not busy_o
282
283 # activate issue_i to begin the operation cycle
284 yield self.dut.issue_i.eq(1)
285
286 # at the same time, present the operation
287 yield self.dut.oper_i.insn_type.eq(self.op)
288 yield self.dut.oper_i.invert_a.eq(self.inv_a)
289 yield self.dut.oper_i.imm_data.imm.eq(self.imm)
290 yield self.dut.oper_i.imm_data.imm_ok.eq(self.imm_ok)
291 yield self.dut.oper_i.zero_a.eq(self.zero_a)
292 rdmaskn = self.rdmaskn[0] | (self.rdmaskn[1] << 1)
293 yield self.dut.rdmaskn.eq(rdmaskn)
294
295 # give one cycle for the CompUnit to latch the data
296 yield
297
298 # busy_o must keep being low in this cycle, because issue_i was
299 # low on the previous cycle.
300 # It cannot rise on its own.
301 # Also, busy_o and issue_i must never be active at the same time, ever.
302 busy_o = yield self.dut.busy_o
303 assert not busy_o
304
305 # Lower issue_i
306 yield self.dut.issue_i.eq(0)
307
308 # deactivate inputs along with issue_i, so we can be sure the data
309 # was latched at the correct cycle
310 # note: rdmaskn must be held, while busy_o is active
311 # TODO: deactivate rdmaskn when the busy_o cycle ends
312 yield self.dut.oper_i.insn_type.eq(0)
313 yield self.dut.oper_i.invert_a.eq(0)
314 yield self.dut.oper_i.imm_data.imm.eq(0)
315 yield self.dut.oper_i.imm_data.imm_ok.eq(0)
316 yield self.dut.oper_i.zero_a.eq(0)
317 yield
318
319 # wait for busy_o to lower
320 # timeout after self.MAX_BUSY_WAIT cycles
321 for n in range(self.MAX_BUSY_WAIT):
322 # sample busy_o in the current cycle
323 busy_o = yield self.dut.busy_o
324 if not busy_o:
325 # operation cycle ends when busy_o becomes inactive
326 break
327 yield
328
329 # if busy_o is still active, a timeout has occurred
330 # TODO: Uncomment this, once the test is complete:
331 # assert not busy_o
332
333 if busy_o:
334 print("If you are reading this, "
335 "it's because the above test failed, as expected,\n"
336 "with a timeout. It must pass, once the test is complete.")
337 return
338
339 print("If you are reading this, "
340 "it's because the above test unexpectedly passed.")
341
342 def rd(self, rd_idx):
343 # wait for issue_i to rise
344 while True:
345 issue_i = yield self.dut.issue_i
346 if issue_i:
347 break
348 # issue_i has not risen yet, so rd must keep low
349 rel = yield self.dut.rd.rel[rd_idx]
350 assert not rel
351 yield
352
353 # we do not want rd to rise on an immediate operand
354 # if it is immediate, exit the process
355 # likewise, if the read mask is active
356 # TODO: don't exit the process, monitor rd instead to ensure it
357 # doesn't rise on its own
358 if self.rdmaskn[rd_idx] or self.imm_control[rd_idx]:
359 self.rd_complete[rd_idx] = True
360 return
361
362 # issue_i has risen. rel must rise on the next cycle
363 rel = yield self.dut.rd.rel[rd_idx]
364 assert not rel
365
366 # stall for additional cycles. Check that rel doesn't fall on its own
367 for n in range(self.RD_GO_DELAY[rd_idx]):
368 yield
369 rel = yield self.dut.rd.rel[rd_idx]
370 assert rel
371
372 # Before asserting "go", make sure "rel" has risen.
373 # The use of Settle allows "go" to be set combinatorially,
374 # rising on the same cycle as "rel".
375 yield Settle()
376 rel = yield self.dut.rd.rel[rd_idx]
377 assert rel
378
379 # assert go for one cycle, passing along the operand value
380 yield self.dut.rd.go[rd_idx].eq(1)
381 yield self.dut.src_i[rd_idx].eq(self.operands[rd_idx])
382 # check that the operand was sent to the alu
383 # TODO: Properly check the alu protocol
384 yield Settle()
385 alu_input = yield self.dut.get_in(rd_idx)
386 assert alu_input == self.operands[rd_idx]
387 yield
388
389 # rel must keep high, since go was inactive in the last cycle
390 rel = yield self.dut.rd.rel[rd_idx]
391 assert rel
392
393 # finish the go one-clock pulse
394 yield self.dut.rd.go[rd_idx].eq(0)
395 yield self.dut.src_i[rd_idx].eq(0)
396 yield
397
398 # rel must have gone low in response to go being high
399 # on the previous cycle
400 rel = yield self.dut.rd.rel[rd_idx]
401 assert not rel
402
403 self.rd_complete[rd_idx] = True
404
405 # TODO: check that rel doesn't rise again until the end of the
406 # busy_o cycle
407
408 def wr(self, wr_idx):
409 # monitor self.dut.wr.req[rd_idx] and sets dut.wr.go[idx] for one cycle
410 yield
411 # TODO: also when dut.wr.go is set, check the output against the
412 # self.expected_o and assert. use dut.get_out(wr_idx) to do so.
413
414 def run_simulation(self, vcd_name):
415 m = Module()
416 m.submodules.cu = self.dut
417 sim = Simulator(m)
418 sim.add_clock(1e-6)
419
420 sim.add_sync_process(wrap(self.driver()))
421 sim.add_sync_process(wrap(self.rd(0)))
422 sim.add_sync_process(wrap(self.rd(1)))
423 sim.add_sync_process(wrap(self.wr(0)))
424 sim_writer = sim.write_vcd(vcd_name)
425 with sim_writer:
426 sim.run()
427
428
429 def test_compunit_regspec2_fsm():
430
431 inspec = [('INT', 'a', '0:15'),
432 ('INT', 'b', '0:15'),
433 ]
434 outspec = [('INT', 'o', '0:15'),
435 ]
436
437 regspec = (inspec, outspec)
438
439 m = Module()
440 alu = Shifter(8)
441 dut = MultiCompUnit(regspec, alu, CompFSMOpSubset)
442 m.submodules.cu = dut
443
444 sim = Simulator(m)
445 sim.add_clock(1e-6)
446
447 sim.add_sync_process(wrap(scoreboard_sim_fsm(dut)))
448 sim_writer = sim.write_vcd('test_compunit_regspec2_fsm.vcd')
449 with sim_writer:
450 sim.run()
451
452
453 def test_compunit_regspec3():
454
455 inspec = [('INT', 'a', '0:15'),
456 ('INT', 'b', '0:15'),
457 ('INT', 'c', '0:15')]
458 outspec = [('INT', 'o', '0:15'),
459 ]
460
461 regspec = (inspec, outspec)
462
463 m = Module()
464 alu = DummyALU(16)
465 dut = MultiCompUnit(regspec, alu, CompALUOpSubset)
466 m.submodules.cu = dut
467
468 sim = Simulator(m)
469 sim.add_clock(1e-6)
470
471 sim.add_sync_process(wrap(scoreboard_sim_dummy(dut)))
472 sim_writer = sim.write_vcd('test_compunit_regspec3.vcd')
473 with sim_writer:
474 sim.run()
475
476
477 def test_compunit_regspec1():
478
479 inspec = [('INT', 'a', '0:15'),
480 ('INT', 'b', '0:15')]
481 outspec = [('INT', 'o', '0:15'),
482 ]
483
484 regspec = (inspec, outspec)
485
486 m = Module()
487 alu = ALU(16)
488 dut = MultiCompUnit(regspec, alu, CompALUOpSubset)
489 m.submodules.cu = dut
490
491 vl = rtlil.convert(dut, ports=dut.ports())
492 with open("test_compunit_regspec1.il", "w") as f:
493 f.write(vl)
494
495 sim = Simulator(m)
496 sim.add_clock(1e-6)
497
498 sim.add_sync_process(wrap(scoreboard_sim(dut)))
499 sim_writer = sim.write_vcd('test_compunit_regspec1.vcd')
500 with sim_writer:
501 sim.run()
502
503 test = CompUnitParallelTest(dut)
504 test.run_simulation("test_compunit_parallel.vcd")
505
506
507 if __name__ == '__main__':
508 test_compunit()
509 test_compunit_fsm()
510 test_compunit_regspec1()
511 test_compunit_regspec3()