example_buf_pipe.py
[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
11
12 from random import randint
13
14
15 def check_o_n_valid(dut, val):
16 o_n_valid = yield dut.n[0].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[0].i_ready.eq(0)
27 yield dut.p[0].o_ready.eq(0)
28 yield
29 yield
30 #yield dut.i_p_rst.eq(0)
31 yield dut.n[0].i_ready.eq(1)
32 yield dut.p[0].i_data.eq(5)
33 yield dut.p[0].i_valid.eq(1)
34 yield
35
36 yield dut.p[0].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[0].i_data.eq(2)
42 yield
43 yield dut.n[0].i_ready.eq(0) # begin going into "stall" (next stage says ready)
44 yield dut.p[0].i_data.eq(9)
45 yield
46 yield dut.p[0].i_valid.eq(0)
47 yield dut.p[0].i_data.eq(12)
48 yield
49 yield dut.p[0].i_data.eq(32)
50 yield dut.n[0].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[0].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[0].i_valid.eq(1)
126 yield self.dut.p[0].i_data.eq(self.data[self.i])
127 self.i += 1
128 else:
129 yield self.dut.p[0].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[0].i_ready.eq(stall)
138 yield
139 o_n_valid = yield self.dut.n[0].o_valid
140 i_n_ready = yield self.dut.n[0].i_ready
141 if not o_n_valid or not i_n_ready:
142 continue
143 o_data = yield self.dut.n[0].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[0].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[0].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[0].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[0].i_ready.eq(stall)
211 yield
212 o_n_valid = yield self.dut.n[0].o_valid
213 i_n_ready = yield self.dut.n[0].i_ready
214 if not o_n_valid or not i_n_ready:
215 continue
216 if isinstance(self.dut.n[0].o_data, Record):
217 o_data = {}
218 dod = self.dut.n[0].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[0].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:
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 self.pipe1 = ExampleBufPipe()
275 self.pipe2 = ExampleBufPipe()
276
277 # input
278 self.p = PrevControl()
279 self.p.i_data = Signal(32) # >>in - comes in from the PREVIOUS stage
280
281 # output
282 self.n = NextControl()
283 self.n.o_data = Signal(32) # out>> - goes out to the NEXT stage
284
285 def elaborate(self, platform):
286 m = Module()
287 m.submodules.pipe1 = self.pipe1
288 m.submodules.pipe2 = self.pipe2
289
290 # connect inter-pipe input/output valid/ready/data
291 m.d.comb += self.pipe1.connect_to_next(self.pipe2)
292
293 # inputs/outputs to the module: pipe1 connections here (LHS)
294 m.d.comb += self.pipe1.connect_in(self)
295
296 # now pipe2 connections (RHS)
297 m.d.comb += self.pipe2.connect_out(self)
298
299 return m
300
301
302 class ExampleBufPipeChain2(BufferedPipeline):
303 """ connects two stages together as a *single* combinatorial stage.
304 """
305 def __init__(self):
306 stage1 = ExampleStageCls()
307 stage2 = ExampleStageCls()
308 combined = StageChain([stage1, stage2])
309 BufferedPipeline.__init__(self, combined)
310
311
312 def data_chain2():
313 data = []
314 for i in range(num_tests):
315 data.append(randint(0, 1<<16-2))
316 return data
317
318
319 def test9_resultfn(o_data, expected, i, o):
320 res = expected + 2
321 assert o_data == res, \
322 "%d-%d data %x not match %s\n" \
323 % (i, o, o_data, repr(expected))
324
325
326 class SetLessThan:
327 def __init__(self, width, signed):
328 self.src1 = Signal((width, signed))
329 self.src2 = Signal((width, signed))
330 self.output = Signal(width)
331
332 def elaborate(self, platform):
333 m = Module()
334 m.d.comb += self.output.eq(Mux(self.src1 < self.src2, 1, 0))
335 return m
336
337
338 class LTStage:
339 def __init__(self):
340 self.slt = SetLessThan(16, True)
341
342 def ispec(self):
343 return (Signal(16), Signal(16))
344
345 def ospec(self):
346 return Signal(16)
347
348 def setup(self, m, i):
349 self.o = Signal(16)
350 m.submodules.slt = self.slt
351 m.d.comb += self.slt.src1.eq(i[0])
352 m.d.comb += self.slt.src2.eq(i[1])
353 m.d.comb += self.o.eq(self.slt.output)
354
355 def process(self, i):
356 return self.o
357
358
359 class LTStageDerived(SetLessThan):
360
361 def __init__(self):
362 SetLessThan.__init__(self, 16, True)
363
364 def ispec(self):
365 return (Signal(16), Signal(16))
366
367 def ospec(self):
368 return Signal(16)
369
370 def setup(self, m, i):
371 m.submodules.slt = self
372 m.d.comb += self.src1.eq(i[0])
373 m.d.comb += self.src2.eq(i[1])
374
375 def process(self, i):
376 return self.output
377
378
379 class ExampleLTPipeline(UnbufferedPipeline):
380 """ an example of how to use the combinatorial pipeline.
381 """
382
383 def __init__(self):
384 stage = LTStage()
385 UnbufferedPipeline.__init__(self, stage)
386
387
388 class ExampleLTBufferedPipeDerived(BufferedPipeline):
389 """ an example of how to use the combinatorial pipeline.
390 """
391
392 def __init__(self):
393 stage = LTStageDerived()
394 BufferedPipeline.__init__(self, stage)
395
396
397 def test6_resultfn(o_data, expected, i, o):
398 res = 1 if expected[0] < expected[1] else 0
399 assert o_data == res, \
400 "%d-%d data %x not match %s\n" \
401 % (i, o, o_data, repr(expected))
402
403
404 class ExampleAddRecordStage:
405 """ example use of a Record
406 """
407
408 record_spec = [('src1', 16), ('src2', 16)]
409 def ispec(self):
410 """ returns a Record using the specification
411 """
412 return Record(self.record_spec)
413
414 def ospec(self):
415 return Record(self.record_spec)
416
417 def process(self, i):
418 """ process the input data, returning a dictionary with key names
419 that exactly match the Record's attributes.
420 """
421 return {'src1': i.src1 + 1,
422 'src2': i.src2 + 1}
423
424
425 class ExampleAddRecordPlaceHolderStage:
426 """ example use of a Record, with a placeholder as the processing result
427 """
428
429 record_spec = [('src1', 16), ('src2', 16)]
430 def ispec(self):
431 """ returns a Record using the specification
432 """
433 return Record(self.record_spec)
434
435 def ospec(self):
436 return Record(self.record_spec)
437
438 def process(self, i):
439 """ process the input data, returning a PlaceHolder class instance
440 with attributes that exactly match those of the Record.
441 """
442 o = PlaceHolder()
443 o.src1 = i.src1 + 1
444 o.src2 = i.src2 + 1
445 return o
446
447 class PlaceHolder: pass
448
449
450 class ExampleAddRecordPipe(UnbufferedPipeline):
451 """ an example of how to use the combinatorial pipeline.
452 """
453
454 def __init__(self):
455 stage = ExampleAddRecordStage()
456 UnbufferedPipeline.__init__(self, stage)
457
458
459 def test7_resultfn(o_data, expected, i, o):
460 res = (expected['src1'] + 1, expected['src2'] + 1)
461 assert o_data['src1'] == res[0] and o_data['src2'] == res[1], \
462 "%d-%d data %s not match %s\n" \
463 % (i, o, repr(o_data), repr(expected))
464
465
466 class ExampleAddRecordPlaceHolderPipe(UnbufferedPipeline):
467 """ an example of how to use the combinatorial pipeline.
468 """
469
470 def __init__(self):
471 stage = ExampleAddRecordPlaceHolderStage()
472 UnbufferedPipeline.__init__(self, stage)
473
474
475 def test11_resultfn(o_data, expected, i, o):
476 res1 = expected.src1 + 1
477 res2 = expected.src2 + 1
478 assert o_data['src1'] == res1 and o_data['src2'] == res2, \
479 "%d-%d data %s not match %s\n" \
480 % (i, o, repr(o_data), repr(expected))
481
482
483 class Example2OpClass:
484 """ an example of a class used to store 2 operands.
485 requires an eq function, to conform with the pipeline stage API
486 """
487
488 def __init__(self):
489 self.op1 = Signal(16)
490 self.op2 = Signal(16)
491
492 def eq(self, i):
493 return [self.op1.eq(i.op1), self.op2.eq(i.op2)]
494
495
496 class ExampleAddClassStage:
497 """ an example of how to use the buffered pipeline, as a class instance
498 """
499
500 def ispec(self):
501 """ returns an instance of an Example2OpClass.
502 """
503 return Example2OpClass()
504
505 def ospec(self):
506 """ returns an output signal which will happen to contain the sum
507 of the two inputs
508 """
509 return Signal(16)
510
511 def process(self, i):
512 """ process the input data (sums the values in the tuple) and returns it
513 """
514 return i.op1 + i.op2
515
516
517 class ExampleBufPipeAddClass(BufferedPipeline):
518 """ an example of how to use the buffered pipeline, using a class instance
519 """
520
521 def __init__(self):
522 addstage = ExampleAddClassStage()
523 BufferedPipeline.__init__(self, addstage)
524
525
526 class TestInputAdd:
527 """ the eq function, called by set_input, needs an incoming object
528 that conforms to the Example2OpClass.eq function requirements
529 easiest way to do that is to create a class that has the exact
530 same member layout (self.op1, self.op2) as Example2OpClass
531 """
532 def __init__(self, op1, op2):
533 self.op1 = op1
534 self.op2 = op2
535
536
537 def test8_resultfn(o_data, expected, i, o):
538 res = expected.op1 + expected.op2 # these are a TestInputAdd instance
539 assert o_data == res, \
540 "%d-%d data %x not match %s\n" \
541 % (i, o, o_data, repr(expected))
542
543 def data_2op():
544 data = []
545 for i in range(num_tests):
546 data.append(TestInputAdd(randint(0, 1<<16-1), randint(0, 1<<16-1)))
547 return data
548
549 class InputPriorityArbiter:
550 def __init__(self, pipe, num_rows):
551 self.pipe = pipe
552 self.num_rows = num_rows
553 self.mmax = int(log(self.num_rows) / log(2))
554 self.mid = Signal(self.mmax, reset_less=True) # multiplex id
555 self.active = Signal(reset_less=True)
556
557 def elaborate(self, platform):
558 m = Module()
559
560 assert len(self.pipe.p) == self.num_rows, \
561 "must declare input to be same size"
562 pe = PriorityEncoder(self.num_rows)
563 m.submodules.selector = pe
564
565 # connect priority encoder
566 in_ready = []
567 for i in range(self.num_rows):
568 p_i_valid = Signal(reset_less=True)
569 m.d.comb += p_i_valid.eq(self.pipe[i].i_valid_logic())
570 in_ready.append(p_i_valid)
571 m.d.comb += pe.i.eq(Cat(*in_ready)) # array of input "valids"
572 m.d.comb += self.active.eq(~pe.n) # encoder active (one input valid)
573 m.d.comb += self.mid.eq(pe.o) # output one active input
574
575 return m
576
577 def ports(self):
578 return [self.mid, self.active]
579
580
581 class PriorityUnbufferedPipeline(UnbufferedPipeline):
582 def __init__(self, stage, p_len=4):
583 p_mux = InputPriorityArbiter(self, p_len)
584 UnbufferedPipeline.__init__(stage, p_len=p_len, p_mux=p_mux)
585
586 def elaborate(self, platform):
587 m = Module()
588
589 pe = PriorityEncoder(self.num_rows)
590 m.submodules.selector = pe
591 m.submodules.out_op = self.out_op
592 m.submodules += self.rs
593
594 # connect priority encoder
595 in_ready = []
596 for i in range(self.num_rows):
597 in_ready.append(self.rs[i].ready)
598 m.d.comb += pe.i.eq(Cat(*in_ready))
599
600 active = Signal(reset_less=True)
601 out_en = Signal(reset_less=True)
602 m.d.comb += active.eq(~pe.n) # encoder active
603 m.d.comb += out_en.eq(active & self.out_op.trigger)
604
605 # encoder active: ack relevant input, record MID, pass output
606 with m.If(out_en):
607 rs = self.rs[pe.o]
608 m.d.sync += self.mid.eq(pe.o)
609 m.d.sync += rs.ack.eq(0)
610 m.d.sync += self.out_op.stb.eq(0)
611 for j in range(self.num_ops):
612 m.d.sync += self.out_op.v[j].eq(rs.out_op[j])
613 with m.Else():
614 m.d.sync += self.out_op.stb.eq(1)
615 # acks all default to zero
616 for i in range(self.num_rows):
617 m.d.sync += self.rs[i].ack.eq(1)
618
619 return m
620
621 def ports(self):
622 res = []
623 for i in range(self.num_rows):
624 inop = self.rs[i]
625 res += inop.in_op + [inop.stb]
626 return self.out_op.ports() + res + [self.mid]
627
628
629
630 num_tests = 100
631
632 if __name__ == '__main__':
633 print ("test 1")
634 dut = ExampleBufPipe()
635 run_simulation(dut, testbench(dut), vcd_name="test_bufpipe.vcd")
636
637 print ("test 2")
638 dut = ExampleBufPipe2()
639 run_simulation(dut, testbench2(dut), vcd_name="test_bufpipe2.vcd")
640
641 print ("test 3")
642 dut = ExampleBufPipe()
643 test = Test3(dut, test3_resultfn)
644 run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe3.vcd")
645
646 print ("test 3.5")
647 dut = ExamplePipeline()
648 test = Test3(dut, test3_resultfn)
649 run_simulation(dut, [test.send, test.rcv], vcd_name="test_combpipe3.vcd")
650
651 print ("test 4")
652 dut = ExampleBufPipe2()
653 run_simulation(dut, testbench4(dut), vcd_name="test_bufpipe4.vcd")
654
655 print ("test 5")
656 dut = ExampleBufPipeAdd()
657 test = Test5(dut, test5_resultfn)
658 run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe5.vcd")
659
660 print ("test 6")
661 dut = ExampleLTPipeline()
662 test = Test5(dut, test6_resultfn)
663 run_simulation(dut, [test.send, test.rcv], vcd_name="test_ltcomb6.vcd")
664
665 ports = [dut.p[0].i_valid, dut.n[0].i_ready,
666 dut.n[0].o_valid, dut.p[0].o_ready] + \
667 list(dut.p[0].i_data) + [dut.n[0].o_data]
668 vl = rtlil.convert(dut, ports=ports)
669 with open("test_ltcomb_pipe.il", "w") as f:
670 f.write(vl)
671
672 print ("test 7")
673 dut = ExampleAddRecordPipe()
674 data=data_dict()
675 test = Test5(dut, test7_resultfn, data=data)
676 run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord.vcd")
677
678 ports = [dut.p[0].i_valid, dut.n[0].i_ready,
679 dut.n[0].o_valid, dut.p[0].o_ready,
680 dut.p[0].i_data.src1, dut.p[0].i_data.src2,
681 dut.n[0].o_data.src1, dut.n[0].o_data.src2]
682 vl = rtlil.convert(dut, ports=ports)
683 with open("test_recordcomb_pipe.il", "w") as f:
684 f.write(vl)
685
686 print ("test 8")
687 dut = ExampleBufPipeAddClass()
688 data=data_2op()
689 test = Test5(dut, test8_resultfn, data=data)
690 run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe8.vcd")
691
692 print ("test 9")
693 dut = ExampleBufPipeChain2()
694 ports = [dut.p[0].i_valid, dut.n[0].i_ready,
695 dut.n[0].o_valid, dut.p[0].o_ready] + \
696 [dut.p[0].i_data] + [dut.n[0].o_data]
697 vl = rtlil.convert(dut, ports=ports)
698 with open("test_bufpipechain2.il", "w") as f:
699 f.write(vl)
700
701 data = data_chain2()
702 test = Test5(dut, test9_resultfn, data=data)
703 run_simulation(dut, [test.send, test.rcv],
704 vcd_name="test_bufpipechain2.vcd")
705
706 print ("test 10")
707 dut = ExampleLTBufferedPipeDerived()
708 test = Test5(dut, test6_resultfn)
709 run_simulation(dut, [test.send, test.rcv], vcd_name="test_ltbufpipe10.vcd")
710 vl = rtlil.convert(dut, ports=ports)
711 with open("test_ltbufpipe10.il", "w") as f:
712 f.write(vl)
713
714 print ("test 11")
715 dut = ExampleAddRecordPlaceHolderPipe()
716 data=data_placeholder()
717 test = Test5(dut, test11_resultfn, data=data)
718 run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord.vcd")
719
720