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