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