add example stage data signalling properties
[ieee754fpu.git] / src / add / test_buf_pipe.py
1 """ Unit tests for Buffered and Unbuffered pipelines
2
3 contains useful worked examples of how to use the Pipeline API,
4 including:
5
6 * Combinatorial Stage "Chaining"
7 * class-based data stages
8 * nmigen module-based data stages
9 * special nmigen module-based data stage, where the stage *is* the module
10 * Record-based data stages
11 * static-class data stages
12 * multi-stage pipelines (and how to connect them)
13 * how to *use* the pipelines (see Test5) - how to get data in and out
14
15 """
16
17 from nmigen import Module, Signal, Mux
18 from nmigen.hdl.rec import Record
19 from nmigen.compat.sim import run_simulation
20 from nmigen.cli import verilog, rtlil
21
22 from example_buf_pipe import ExampleBufPipe, ExampleBufPipeAdd
23 from example_buf_pipe import ExamplePipeline, UnbufferedPipeline
24 from example_buf_pipe import ExampleStageCls
25 from example_buf_pipe import PrevControl, NextControl, BufferedPipeline
26 from example_buf_pipe import StageChain, ControlBase, StageCls
27
28 from random import randint
29
30
31 def check_o_n_valid(dut, val):
32 o_n_valid = yield dut.n.o_valid
33 assert o_n_valid == val
34
35 def check_o_n_valid2(dut, val):
36 o_n_valid = yield dut.n.o_valid
37 assert o_n_valid == val
38
39
40 def testbench(dut):
41 #yield dut.i_p_rst.eq(1)
42 yield dut.n.i_ready.eq(0)
43 yield dut.p.o_ready.eq(0)
44 yield
45 yield
46 #yield dut.i_p_rst.eq(0)
47 yield dut.n.i_ready.eq(1)
48 yield dut.p.i_data.eq(5)
49 yield dut.p.i_valid.eq(1)
50 yield
51
52 yield dut.p.i_data.eq(7)
53 yield from check_o_n_valid(dut, 0) # effects of i_p_valid delayed
54 yield
55 yield from check_o_n_valid(dut, 1) # ok *now* i_p_valid effect is felt
56
57 yield dut.p.i_data.eq(2)
58 yield
59 yield dut.n.i_ready.eq(0) # begin going into "stall" (next stage says ready)
60 yield dut.p.i_data.eq(9)
61 yield
62 yield dut.p.i_valid.eq(0)
63 yield dut.p.i_data.eq(12)
64 yield
65 yield dut.p.i_data.eq(32)
66 yield dut.n.i_ready.eq(1)
67 yield
68 yield from check_o_n_valid(dut, 1) # buffer still needs to output
69 yield
70 yield from check_o_n_valid(dut, 1) # buffer still needs to output
71 yield
72 yield from check_o_n_valid(dut, 0) # buffer outputted, *now* we're done.
73 yield
74
75
76 def testbench2(dut):
77 #yield dut.p.i_rst.eq(1)
78 yield dut.n.i_ready.eq(0)
79 #yield dut.p.o_ready.eq(0)
80 yield
81 yield
82 #yield dut.p.i_rst.eq(0)
83 yield dut.n.i_ready.eq(1)
84 yield dut.p.i_data.eq(5)
85 yield dut.p.i_valid.eq(1)
86 yield
87
88 yield dut.p.i_data.eq(7)
89 yield from check_o_n_valid2(dut, 0) # effects of i_p_valid delayed 2 clocks
90 yield
91 yield from check_o_n_valid2(dut, 0) # effects of i_p_valid delayed 2 clocks
92
93 yield dut.p.i_data.eq(2)
94 yield
95 yield from check_o_n_valid2(dut, 1) # ok *now* i_p_valid effect is felt
96 yield dut.n.i_ready.eq(0) # begin going into "stall" (next stage says ready)
97 yield dut.p.i_data.eq(9)
98 yield
99 yield dut.p.i_valid.eq(0)
100 yield dut.p.i_data.eq(12)
101 yield
102 yield dut.p.i_data.eq(32)
103 yield dut.n.i_ready.eq(1)
104 yield
105 yield from check_o_n_valid2(dut, 1) # buffer still needs to output
106 yield
107 yield from check_o_n_valid2(dut, 1) # buffer still needs to output
108 yield
109 yield from check_o_n_valid2(dut, 1) # buffer still needs to output
110 yield
111 yield from check_o_n_valid2(dut, 0) # buffer outputted, *now* we're done.
112 yield
113 yield
114 yield
115
116
117 class Test3:
118 def __init__(self, dut, resultfn):
119 self.dut = dut
120 self.resultfn = resultfn
121 self.data = []
122 for i in range(num_tests):
123 #data.append(randint(0, 1<<16-1))
124 self.data.append(i+1)
125 self.i = 0
126 self.o = 0
127
128 def send(self):
129 while self.o != len(self.data):
130 send_range = randint(0, 3)
131 for j in range(randint(1,10)):
132 if send_range == 0:
133 send = True
134 else:
135 send = randint(0, send_range) != 0
136 o_p_ready = yield self.dut.p.o_ready
137 if not o_p_ready:
138 yield
139 continue
140 if send and self.i != len(self.data):
141 yield self.dut.p.i_valid.eq(1)
142 yield self.dut.p.i_data.eq(self.data[self.i])
143 self.i += 1
144 else:
145 yield self.dut.p.i_valid.eq(0)
146 yield
147
148 def rcv(self):
149 while self.o != len(self.data):
150 stall_range = randint(0, 3)
151 for j in range(randint(1,10)):
152 stall = randint(0, stall_range) != 0
153 yield self.dut.n.i_ready.eq(stall)
154 yield
155 o_n_valid = yield self.dut.n.o_valid
156 i_n_ready = yield self.dut.n.i_ready
157 if not o_n_valid or not i_n_ready:
158 continue
159 o_data = yield self.dut.n.o_data
160 self.resultfn(o_data, self.data[self.o], self.i, self.o)
161 self.o += 1
162 if self.o == len(self.data):
163 break
164
165 def test3_resultfn(o_data, expected, i, o):
166 assert o_data == expected + 1, \
167 "%d-%d data %x not match %x\n" \
168 % (i, o, o_data, expected)
169
170 def data_placeholder():
171 data = []
172 for i in range(num_tests):
173 d = PlaceHolder()
174 d.src1 = randint(0, 1<<16-1)
175 d.src2 = randint(0, 1<<16-1)
176 data.append(d)
177 return data
178
179 def data_dict():
180 data = []
181 for i in range(num_tests):
182 data.append({'src1': randint(0, 1<<16-1),
183 'src2': randint(0, 1<<16-1)})
184 return data
185
186
187 class Test5:
188 def __init__(self, dut, resultfn, data=None):
189 self.dut = dut
190 self.resultfn = resultfn
191 if data:
192 self.data = data
193 else:
194 self.data = []
195 for i in range(num_tests):
196 self.data.append((randint(0, 1<<16-1), randint(0, 1<<16-1)))
197 self.i = 0
198 self.o = 0
199
200 def send(self):
201 while self.o != len(self.data):
202 send_range = randint(0, 3)
203 for j in range(randint(1,10)):
204 if send_range == 0:
205 send = True
206 else:
207 send = randint(0, send_range) != 0
208 o_p_ready = yield self.dut.p.o_ready
209 if not o_p_ready:
210 yield
211 continue
212 if send and self.i != len(self.data):
213 yield self.dut.p.i_valid.eq(1)
214 for v in self.dut.set_input(self.data[self.i]):
215 yield v
216 self.i += 1
217 else:
218 yield self.dut.p.i_valid.eq(0)
219 yield
220
221 def rcv(self):
222 while self.o != len(self.data):
223 stall_range = randint(0, 3)
224 for j in range(randint(1,10)):
225 stall = randint(0, stall_range) != 0
226 yield self.dut.n.i_ready.eq(stall)
227 yield
228 o_n_valid = yield self.dut.n.o_valid
229 i_n_ready = yield self.dut.n.i_ready
230 if not o_n_valid or not i_n_ready:
231 continue
232 if isinstance(self.dut.n.o_data, Record):
233 o_data = {}
234 dod = self.dut.n.o_data
235 for k, v in dod.fields.items():
236 o_data[k] = yield v
237 else:
238 o_data = yield self.dut.n.o_data
239 self.resultfn(o_data, self.data[self.o], self.i, self.o)
240 self.o += 1
241 if self.o == len(self.data):
242 break
243
244 def test5_resultfn(o_data, expected, i, o):
245 res = expected[0] + expected[1]
246 assert o_data == res, \
247 "%d-%d data %x not match %s\n" \
248 % (i, o, o_data, repr(expected))
249
250 def testbench4(dut):
251 data = []
252 for i in range(num_tests):
253 #data.append(randint(0, 1<<16-1))
254 data.append(i+1)
255 i = 0
256 o = 0
257 while True:
258 stall = randint(0, 3) != 0
259 send = randint(0, 5) != 0
260 yield dut.n.i_ready.eq(stall)
261 o_p_ready = yield dut.p.o_ready
262 if o_p_ready:
263 if send and i != len(data):
264 yield dut.p.i_valid.eq(1)
265 yield dut.p.i_data.eq(data[i])
266 i += 1
267 else:
268 yield dut.p.i_valid.eq(0)
269 yield
270 o_n_valid = yield dut.n.o_valid
271 i_n_ready = yield dut.n.i_ready
272 if o_n_valid and i_n_ready:
273 o_data = yield dut.n.o_data
274 assert o_data == data[o] + 2, "%d-%d data %x not match %x\n" \
275 % (i, o, o_data, data[o])
276 o += 1
277 if o == len(data):
278 break
279
280 ######################################################################
281 # Test 2 and 4
282 ######################################################################
283
284 class ExampleBufPipe2(ControlBase):
285 """ Example of how to do chained pipeline stages.
286 """
287
288 def elaborate(self, platform):
289 m = Module()
290
291 pipe1 = ExampleBufPipe()
292 pipe2 = ExampleBufPipe()
293
294 m.submodules.pipe1 = pipe1
295 m.submodules.pipe2 = pipe2
296
297 m.d.comb += self.connect([pipe1, pipe2])
298
299 return m
300
301
302 ######################################################################
303 # Test 9
304 ######################################################################
305
306 class ExampleBufPipeChain2(BufferedPipeline):
307 """ connects two stages together as a *single* combinatorial stage.
308 """
309 def __init__(self):
310 stage1 = ExampleStageCls()
311 stage2 = ExampleStageCls()
312 combined = StageChain([stage1, stage2])
313 BufferedPipeline.__init__(self, combined)
314
315
316 def data_chain2():
317 data = []
318 for i in range(num_tests):
319 data.append(randint(0, 1<<16-2))
320 return data
321
322
323 def test9_resultfn(o_data, expected, i, o):
324 res = expected + 2
325 assert o_data == res, \
326 "%d-%d data %x not match %s\n" \
327 % (i, o, o_data, repr(expected))
328
329
330 ######################################################################
331 # Test 6 and 10
332 ######################################################################
333
334 class SetLessThan:
335 def __init__(self, width, signed):
336 self.m = Module()
337 self.src1 = Signal((width, signed), name="src1")
338 self.src2 = Signal((width, signed), name="src2")
339 self.output = Signal(width, name="out")
340
341 def elaborate(self, platform):
342 self.m.d.comb += self.output.eq(Mux(self.src1 < self.src2, 1, 0))
343 return self.m
344
345
346 class LTStage(StageCls):
347 """ module-based stage example
348 """
349 def __init__(self):
350 self.slt = SetLessThan(16, True)
351
352 def ispec(self):
353 return (Signal(16, name="sig1"), Signal(16, "sig2"))
354
355 def ospec(self):
356 return Signal(16, "out")
357
358 def setup(self, m, i):
359 self.o = Signal(16)
360 m.submodules.slt = self.slt
361 m.d.comb += self.slt.src1.eq(i[0])
362 m.d.comb += self.slt.src2.eq(i[1])
363 m.d.comb += self.o.eq(self.slt.output)
364
365 def process(self, i):
366 return self.o
367
368
369 class LTStageDerived(SetLessThan, StageCls):
370 """ special version of a nmigen module where the module is also a stage
371
372 shows that you don't actually need to combinatorially connect
373 to the outputs, or add the module as a submodule: just return
374 the module output parameter(s) from the Stage.process() function
375 """
376
377 def __init__(self):
378 SetLessThan.__init__(self, 16, True)
379
380 def ispec(self):
381 return (Signal(16), Signal(16))
382
383 def ospec(self):
384 return Signal(16)
385
386 def setup(self, m, i):
387 m.submodules.slt = self
388 m.d.comb += self.src1.eq(i[0])
389 m.d.comb += self.src2.eq(i[1])
390
391 def process(self, i):
392 return self.output
393
394
395 class ExampleLTPipeline(UnbufferedPipeline):
396 """ an example of how to use the unbuffered pipeline.
397 """
398
399 def __init__(self):
400 stage = LTStage()
401 UnbufferedPipeline.__init__(self, stage)
402
403
404 class ExampleLTBufferedPipeDerived(BufferedPipeline):
405 """ an example of how to use the buffered pipeline.
406 """
407
408 def __init__(self):
409 stage = LTStageDerived()
410 BufferedPipeline.__init__(self, stage)
411
412
413 def test6_resultfn(o_data, expected, i, o):
414 res = 1 if expected[0] < expected[1] else 0
415 assert o_data == res, \
416 "%d-%d data %x not match %s\n" \
417 % (i, o, o_data, repr(expected))
418
419
420 ######################################################################
421 # Test 7
422 ######################################################################
423
424 class ExampleAddRecordStage(StageCls):
425 """ example use of a Record
426 """
427
428 record_spec = [('src1', 16), ('src2', 16)]
429 def ispec(self):
430 """ returns a Record using the specification
431 """
432 return Record(self.record_spec)
433
434 def ospec(self):
435 return Record(self.record_spec)
436
437 def process(self, i):
438 """ process the input data, returning a dictionary with key names
439 that exactly match the Record's attributes.
440 """
441 return {'src1': i.src1 + 1,
442 'src2': i.src2 + 1}
443
444 ######################################################################
445 # Test 11
446 ######################################################################
447
448 class ExampleAddRecordPlaceHolderStage(StageCls):
449 """ example use of a Record, with a placeholder as the processing result
450 """
451
452 record_spec = [('src1', 16), ('src2', 16)]
453 def ispec(self):
454 """ returns a Record using the specification
455 """
456 return Record(self.record_spec)
457
458 def ospec(self):
459 return Record(self.record_spec)
460
461 def process(self, i):
462 """ process the input data, returning a PlaceHolder class instance
463 with attributes that exactly match those of the Record.
464 """
465 o = PlaceHolder()
466 o.src1 = i.src1 + 1
467 o.src2 = i.src2 + 1
468 return o
469
470
471 class PlaceHolder: pass
472
473
474 class ExampleAddRecordPipe(UnbufferedPipeline):
475 """ an example of how to use the combinatorial pipeline.
476 """
477
478 def __init__(self):
479 stage = ExampleAddRecordStage()
480 UnbufferedPipeline.__init__(self, stage)
481
482
483 def test7_resultfn(o_data, expected, i, o):
484 res = (expected['src1'] + 1, expected['src2'] + 1)
485 assert o_data['src1'] == res[0] and o_data['src2'] == res[1], \
486 "%d-%d data %s not match %s\n" \
487 % (i, o, repr(o_data), repr(expected))
488
489
490 class ExampleAddRecordPlaceHolderPipe(UnbufferedPipeline):
491 """ an example of how to use the combinatorial pipeline.
492 """
493
494 def __init__(self):
495 stage = ExampleAddRecordPlaceHolderStage()
496 UnbufferedPipeline.__init__(self, stage)
497
498
499 def test11_resultfn(o_data, expected, i, o):
500 res1 = expected.src1 + 1
501 res2 = expected.src2 + 1
502 assert o_data['src1'] == res1 and o_data['src2'] == res2, \
503 "%d-%d data %s not match %s\n" \
504 % (i, o, repr(o_data), repr(expected))
505
506
507 ######################################################################
508 # Test 8
509 ######################################################################
510
511
512 class Example2OpClass:
513 """ an example of a class used to store 2 operands.
514 requires an eq function, to conform with the pipeline stage API
515 """
516
517 def __init__(self):
518 self.op1 = Signal(16)
519 self.op2 = Signal(16)
520
521 def eq(self, i):
522 return [self.op1.eq(i.op1), self.op2.eq(i.op2)]
523
524
525 class ExampleAddClassStage(StageCls):
526 """ an example of how to use the buffered pipeline, as a class instance
527 """
528
529 def ispec(self):
530 """ returns an instance of an Example2OpClass.
531 """
532 return Example2OpClass()
533
534 def ospec(self):
535 """ returns an output signal which will happen to contain the sum
536 of the two inputs
537 """
538 return Signal(16)
539
540 def process(self, i):
541 """ process the input data (sums the values in the tuple) and returns it
542 """
543 return i.op1 + i.op2
544
545
546 class ExampleBufPipeAddClass(BufferedPipeline):
547 """ an example of how to use the buffered pipeline, using a class instance
548 """
549
550 def __init__(self):
551 addstage = ExampleAddClassStage()
552 BufferedPipeline.__init__(self, addstage)
553
554
555 class TestInputAdd:
556 """ the eq function, called by set_input, needs an incoming object
557 that conforms to the Example2OpClass.eq function requirements
558 easiest way to do that is to create a class that has the exact
559 same member layout (self.op1, self.op2) as Example2OpClass
560 """
561 def __init__(self, op1, op2):
562 self.op1 = op1
563 self.op2 = op2
564
565
566 def test8_resultfn(o_data, expected, i, o):
567 res = expected.op1 + expected.op2 # these are a TestInputAdd instance
568 assert o_data == res, \
569 "%d-%d data %x not match %s\n" \
570 % (i, o, o_data, repr(expected))
571
572 def data_2op():
573 data = []
574 for i in range(num_tests):
575 data.append(TestInputAdd(randint(0, 1<<16-1), randint(0, 1<<16-1)))
576 return data
577
578
579 ######################################################################
580 # Test 12
581 ######################################################################
582
583 class ExampleStageDelayCls(StageCls):
584 """ an example of how to use the buffered pipeline, in a static class
585 fashion
586 """
587
588 def __init__(self):
589 self.count = Signal(3)
590
591 def ispec(self):
592 return Signal(16, name="example_input_signal")
593
594 def ospec(self):
595 return Signal(16, name="example_output_signal")
596
597 @property
598 def p_o_ready(self):
599 return Const(1)
600
601 @property
602 def n_o_valid(self):
603 return Const(1)
604
605 def process(self, i):
606 """ process the input data and returns it (adds 1)
607 """
608 return i + 1
609
610
611 class ExampleBufDelayedPipe(BufferedPipeline):
612 """ an example of how to use the buffered pipeline.
613 """
614
615 def __init__(self):
616 BufferedPipeline.__init__(self, ExampleStageDelayCls())
617
618
619 class ExampleBufPipe3(ControlBase):
620 """ Example of how to do delayed pipeline, where the stage signals
621 whether it is ready.
622 """
623
624 def elaborate(self, platform):
625 m = Module()
626
627 pipe1 = ExampleBufPipe()
628 pipe2 = ExampleBufDelayedPipe()
629
630 m.submodules.pipe1 = pipe1
631 m.submodules.pipe2 = pipe2
632
633 m.d.comb += self.connect([pipe1, pipe2])
634
635 return m
636
637
638
639 num_tests = 100
640
641 if __name__ == '__main__':
642 print ("test 1")
643 dut = ExampleBufPipe()
644 run_simulation(dut, testbench(dut), vcd_name="test_bufpipe.vcd")
645
646 print ("test 2")
647 dut = ExampleBufPipe2()
648 run_simulation(dut, testbench2(dut), vcd_name="test_bufpipe2.vcd")
649 ports = [dut.p.i_valid, dut.n.i_ready,
650 dut.n.o_valid, dut.p.o_ready] + \
651 [dut.p.i_data] + [dut.n.o_data]
652 vl = rtlil.convert(dut, ports=ports)
653 with open("test_bufpipe2.il", "w") as f:
654 f.write(vl)
655
656
657 print ("test 3")
658 dut = ExampleBufPipe()
659 test = Test3(dut, test3_resultfn)
660 run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe3.vcd")
661
662 print ("test 3.5")
663 dut = ExamplePipeline()
664 test = Test3(dut, test3_resultfn)
665 run_simulation(dut, [test.send, test.rcv], vcd_name="test_combpipe3.vcd")
666
667 print ("test 4")
668 dut = ExampleBufPipe2()
669 run_simulation(dut, testbench4(dut), vcd_name="test_bufpipe4.vcd")
670
671 print ("test 5")
672 dut = ExampleBufPipeAdd()
673 test = Test5(dut, test5_resultfn)
674 run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe5.vcd")
675
676 print ("test 6")
677 dut = ExampleLTPipeline()
678 test = Test5(dut, test6_resultfn)
679 run_simulation(dut, [test.send, test.rcv], vcd_name="test_ltcomb6.vcd")
680
681 ports = [dut.p.i_valid, dut.n.i_ready,
682 dut.n.o_valid, dut.p.o_ready] + \
683 list(dut.p.i_data) + [dut.n.o_data]
684 vl = rtlil.convert(dut, ports=ports)
685 with open("test_ltcomb_pipe.il", "w") as f:
686 f.write(vl)
687
688 print ("test 7")
689 dut = ExampleAddRecordPipe()
690 data=data_dict()
691 test = Test5(dut, test7_resultfn, data=data)
692 run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord.vcd")
693
694 ports = [dut.p.i_valid, dut.n.i_ready,
695 dut.n.o_valid, dut.p.o_ready,
696 dut.p.i_data.src1, dut.p.i_data.src2,
697 dut.n.o_data.src1, dut.n.o_data.src2]
698 vl = rtlil.convert(dut, ports=ports)
699 with open("test_recordcomb_pipe.il", "w") as f:
700 f.write(vl)
701
702 print ("test 8")
703 dut = ExampleBufPipeAddClass()
704 data=data_2op()
705 test = Test5(dut, test8_resultfn, data=data)
706 run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe8.vcd")
707
708 print ("test 9")
709 dut = ExampleBufPipeChain2()
710 ports = [dut.p.i_valid, dut.n.i_ready,
711 dut.n.o_valid, dut.p.o_ready] + \
712 [dut.p.i_data] + [dut.n.o_data]
713 vl = rtlil.convert(dut, ports=ports)
714 with open("test_bufpipechain2.il", "w") as f:
715 f.write(vl)
716
717 data = data_chain2()
718 test = Test5(dut, test9_resultfn, data=data)
719 run_simulation(dut, [test.send, test.rcv],
720 vcd_name="test_bufpipechain2.vcd")
721
722 print ("test 10")
723 dut = ExampleLTBufferedPipeDerived()
724 test = Test5(dut, test6_resultfn)
725 run_simulation(dut, [test.send, test.rcv], vcd_name="test_ltbufpipe10.vcd")
726 vl = rtlil.convert(dut, ports=ports)
727 with open("test_ltbufpipe10.il", "w") as f:
728 f.write(vl)
729
730 print ("test 11")
731 dut = ExampleAddRecordPlaceHolderPipe()
732 data=data_placeholder()
733 test = Test5(dut, test11_resultfn, data=data)
734 run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord.vcd")
735
736
737 print ("test 12")
738 dut = ExampleBufPipe3()
739 run_simulation(dut, testbench2(dut), vcd_name="test_bufpipe12.vcd")
740 ports = [dut.p.i_valid, dut.n.i_ready,
741 dut.n.o_valid, dut.p.o_ready] + \
742 [dut.p.i_data] + [dut.n.o_data]
743 vl = rtlil.convert(dut, ports=ports)
744 with open("test_bufpipe12.il", "w") as f:
745 f.write(vl)
746