hooray, p_o_ready works
[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, Const
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 send = True
209 o_p_ready = yield self.dut.p.o_ready
210 if not o_p_ready:
211 yield
212 continue
213 if send and self.i != len(self.data):
214 yield self.dut.p.i_valid.eq(1)
215 for v in self.dut.set_input(self.data[self.i]):
216 yield v
217 self.i += 1
218 else:
219 yield self.dut.p.i_valid.eq(0)
220 yield
221
222 def rcv(self):
223 while self.o != len(self.data):
224 stall_range = randint(0, 3)
225 for j in range(randint(1,10)):
226 stall = randint(0, stall_range) != 0
227 yield self.dut.n.i_ready.eq(stall)
228 yield
229 o_n_valid = yield self.dut.n.o_valid
230 i_n_ready = yield self.dut.n.i_ready
231 if not o_n_valid or not i_n_ready:
232 continue
233 if isinstance(self.dut.n.o_data, Record):
234 o_data = {}
235 dod = self.dut.n.o_data
236 for k, v in dod.fields.items():
237 o_data[k] = yield v
238 else:
239 o_data = yield self.dut.n.o_data
240 self.resultfn(o_data, self.data[self.o], self.i, self.o)
241 self.o += 1
242 if self.o == len(self.data):
243 break
244
245 def test5_resultfn(o_data, expected, i, o):
246 res = expected[0] + expected[1]
247 assert o_data == res, \
248 "%d-%d data %x not match %s\n" \
249 % (i, o, o_data, repr(expected))
250
251 def testbench4(dut):
252 data = []
253 for i in range(num_tests):
254 #data.append(randint(0, 1<<16-1))
255 data.append(i+1)
256 i = 0
257 o = 0
258 while True:
259 stall = randint(0, 3) != 0
260 send = randint(0, 5) != 0
261 yield dut.n.i_ready.eq(stall)
262 o_p_ready = yield dut.p.o_ready
263 if o_p_ready:
264 if send and i != len(data):
265 yield dut.p.i_valid.eq(1)
266 yield dut.p.i_data.eq(data[i])
267 i += 1
268 else:
269 yield dut.p.i_valid.eq(0)
270 yield
271 o_n_valid = yield dut.n.o_valid
272 i_n_ready = yield dut.n.i_ready
273 if o_n_valid and i_n_ready:
274 o_data = yield dut.n.o_data
275 assert o_data == data[o] + 2, "%d-%d data %x not match %x\n" \
276 % (i, o, o_data, data[o])
277 o += 1
278 if o == len(data):
279 break
280
281 ######################################################################
282 # Test 2 and 4
283 ######################################################################
284
285 class ExampleBufPipe2(ControlBase):
286 """ Example of how to do chained pipeline stages.
287 """
288
289 def elaborate(self, platform):
290 m = Module()
291
292 pipe1 = ExampleBufPipe()
293 pipe2 = ExampleBufPipe()
294
295 m.submodules.pipe1 = pipe1
296 m.submodules.pipe2 = pipe2
297
298 m.d.comb += self.connect([pipe1, pipe2])
299
300 return m
301
302
303 ######################################################################
304 # Test 9
305 ######################################################################
306
307 class ExampleBufPipeChain2(BufferedPipeline):
308 """ connects two stages together as a *single* combinatorial stage.
309 """
310 def __init__(self):
311 stage1 = ExampleStageCls()
312 stage2 = ExampleStageCls()
313 combined = StageChain([stage1, stage2])
314 BufferedPipeline.__init__(self, combined)
315
316
317 def data_chain2():
318 data = []
319 for i in range(num_tests):
320 data.append(randint(0, 1<<16-2))
321 return data
322
323
324 def test9_resultfn(o_data, expected, i, o):
325 res = expected + 2
326 assert o_data == res, \
327 "%d-%d data %x not match %s\n" \
328 % (i, o, o_data, repr(expected))
329
330
331 ######################################################################
332 # Test 6 and 10
333 ######################################################################
334
335 class SetLessThan:
336 def __init__(self, width, signed):
337 self.m = Module()
338 self.src1 = Signal((width, signed), name="src1")
339 self.src2 = Signal((width, signed), name="src2")
340 self.output = Signal(width, name="out")
341
342 def elaborate(self, platform):
343 self.m.d.comb += self.output.eq(Mux(self.src1 < self.src2, 1, 0))
344 return self.m
345
346
347 class LTStage(StageCls):
348 """ module-based stage example
349 """
350 def __init__(self):
351 self.slt = SetLessThan(16, True)
352
353 def ispec(self):
354 return (Signal(16, name="sig1"), Signal(16, "sig2"))
355
356 def ospec(self):
357 return Signal(16, "out")
358
359 def setup(self, m, i):
360 self.o = Signal(16)
361 m.submodules.slt = self.slt
362 m.d.comb += self.slt.src1.eq(i[0])
363 m.d.comb += self.slt.src2.eq(i[1])
364 m.d.comb += self.o.eq(self.slt.output)
365
366 def process(self, i):
367 return self.o
368
369
370 class LTStageDerived(SetLessThan, StageCls):
371 """ special version of a nmigen module where the module is also a stage
372
373 shows that you don't actually need to combinatorially connect
374 to the outputs, or add the module as a submodule: just return
375 the module output parameter(s) from the Stage.process() function
376 """
377
378 def __init__(self):
379 SetLessThan.__init__(self, 16, True)
380
381 def ispec(self):
382 return (Signal(16), Signal(16))
383
384 def ospec(self):
385 return Signal(16)
386
387 def setup(self, m, i):
388 m.submodules.slt = self
389 m.d.comb += self.src1.eq(i[0])
390 m.d.comb += self.src2.eq(i[1])
391
392 def process(self, i):
393 return self.output
394
395
396 class ExampleLTPipeline(UnbufferedPipeline):
397 """ an example of how to use the unbuffered pipeline.
398 """
399
400 def __init__(self):
401 stage = LTStage()
402 UnbufferedPipeline.__init__(self, stage)
403
404
405 class ExampleLTBufferedPipeDerived(BufferedPipeline):
406 """ an example of how to use the buffered pipeline.
407 """
408
409 def __init__(self):
410 stage = LTStageDerived()
411 BufferedPipeline.__init__(self, stage)
412
413
414 def test6_resultfn(o_data, expected, i, o):
415 res = 1 if expected[0] < expected[1] else 0
416 assert o_data == res, \
417 "%d-%d data %x not match %s\n" \
418 % (i, o, o_data, repr(expected))
419
420
421 ######################################################################
422 # Test 7
423 ######################################################################
424
425 class ExampleAddRecordStage(StageCls):
426 """ example use of a Record
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 dictionary with key names
440 that exactly match the Record's attributes.
441 """
442 return {'src1': i.src1 + 1,
443 'src2': i.src2 + 1}
444
445 ######################################################################
446 # Test 11
447 ######################################################################
448
449 class ExampleAddRecordPlaceHolderStage(StageCls):
450 """ example use of a Record, with a placeholder as the processing result
451 """
452
453 record_spec = [('src1', 16), ('src2', 16)]
454 def ispec(self):
455 """ returns a Record using the specification
456 """
457 return Record(self.record_spec)
458
459 def ospec(self):
460 return Record(self.record_spec)
461
462 def process(self, i):
463 """ process the input data, returning a PlaceHolder class instance
464 with attributes that exactly match those of the Record.
465 """
466 o = PlaceHolder()
467 o.src1 = i.src1 + 1
468 o.src2 = i.src2 + 1
469 return o
470
471
472 class PlaceHolder: pass
473
474
475 class ExampleAddRecordPipe(UnbufferedPipeline):
476 """ an example of how to use the combinatorial pipeline.
477 """
478
479 def __init__(self):
480 stage = ExampleAddRecordStage()
481 UnbufferedPipeline.__init__(self, stage)
482
483
484 def test7_resultfn(o_data, expected, i, o):
485 res = (expected['src1'] + 1, expected['src2'] + 1)
486 assert o_data['src1'] == res[0] and o_data['src2'] == res[1], \
487 "%d-%d data %s not match %s\n" \
488 % (i, o, repr(o_data), repr(expected))
489
490
491 class ExampleAddRecordPlaceHolderPipe(UnbufferedPipeline):
492 """ an example of how to use the combinatorial pipeline.
493 """
494
495 def __init__(self):
496 stage = ExampleAddRecordPlaceHolderStage()
497 UnbufferedPipeline.__init__(self, stage)
498
499
500 def test11_resultfn(o_data, expected, i, o):
501 res1 = expected.src1 + 1
502 res2 = expected.src2 + 1
503 assert o_data['src1'] == res1 and o_data['src2'] == res2, \
504 "%d-%d data %s not match %s\n" \
505 % (i, o, repr(o_data), repr(expected))
506
507
508 ######################################################################
509 # Test 8
510 ######################################################################
511
512
513 class Example2OpClass:
514 """ an example of a class used to store 2 operands.
515 requires an eq function, to conform with the pipeline stage API
516 """
517
518 def __init__(self):
519 self.op1 = Signal(16)
520 self.op2 = Signal(16)
521
522 def eq(self, i):
523 return [self.op1.eq(i.op1), self.op2.eq(i.op2)]
524
525
526 class ExampleAddClassStage(StageCls):
527 """ an example of how to use the buffered pipeline, as a class instance
528 """
529
530 def ispec(self):
531 """ returns an instance of an Example2OpClass.
532 """
533 return Example2OpClass()
534
535 def ospec(self):
536 """ returns an output signal which will happen to contain the sum
537 of the two inputs
538 """
539 return Signal(16)
540
541 def process(self, i):
542 """ process the input data (sums the values in the tuple) and returns it
543 """
544 return i.op1 + i.op2
545
546
547 class ExampleBufPipeAddClass(BufferedPipeline):
548 """ an example of how to use the buffered pipeline, using a class instance
549 """
550
551 def __init__(self):
552 addstage = ExampleAddClassStage()
553 BufferedPipeline.__init__(self, addstage)
554
555
556 class TestInputAdd:
557 """ the eq function, called by set_input, needs an incoming object
558 that conforms to the Example2OpClass.eq function requirements
559 easiest way to do that is to create a class that has the exact
560 same member layout (self.op1, self.op2) as Example2OpClass
561 """
562 def __init__(self, op1, op2):
563 self.op1 = op1
564 self.op2 = op2
565
566
567 def test8_resultfn(o_data, expected, i, o):
568 res = expected.op1 + expected.op2 # these are a TestInputAdd instance
569 assert o_data == res, \
570 "%d-%d data %x not match %s\n" \
571 % (i, o, o_data, repr(expected))
572
573 def data_2op():
574 data = []
575 for i in range(num_tests):
576 data.append(TestInputAdd(randint(0, 1<<16-1), randint(0, 1<<16-1)))
577 return data
578
579
580 ######################################################################
581 # Test 12
582 ######################################################################
583
584 class ExampleStageDelayCls(StageCls):
585 """ an example of how to use the buffered pipeline, in a static class
586 fashion
587 """
588
589 def __init__(self):
590 self.count = Signal(2)
591
592 def ispec(self):
593 return Signal(16, name="example_input_signal")
594
595 def ospec(self):
596 return Signal(16, name="example_output_signal")
597
598 @property
599 def p_o_ready(self):
600 return self.count == 0
601
602 @property
603 def n_o_valid(self):
604 return Const(1)
605
606 def process(self, i):
607 """ process the input data and returns it (adds 1)
608 """
609 return i + 1
610
611 def elaborate(self, platform):
612 m = Module()
613 m.d.sync += self.count.eq(self.count + 1)
614 return m
615
616
617 class ExampleBufDelayedPipe(BufferedPipeline):
618 """ an example of how to use the buffered pipeline.
619 """
620
621 def __init__(self):
622 stage = ExampleStageDelayCls()
623 BufferedPipeline.__init__(self, stage, stage_ctl=True)
624
625 def elaborate(self, platform):
626 m = BufferedPipeline.elaborate(self, platform)
627 m.submodules.stage = self.stage
628 return m
629
630
631 class ExampleBufPipe3(ControlBase):
632 """ Example of how to do delayed pipeline, where the stage signals
633 whether it is ready.
634 """
635
636 def elaborate(self, platform):
637 m = ControlBase._elaborate(self, platform)
638
639 pipe1 = ExampleBufPipe()
640 pipe2 = ExampleBufDelayedPipe()
641
642 m.submodules.pipe1 = pipe1
643 m.submodules.pipe2 = pipe2
644
645 m.d.comb += self.connect([pipe1, pipe2])
646
647 return m
648
649 def data_chain1():
650 data = []
651 for i in range(num_tests):
652 data.append(randint(0, 1<<16-2))
653 return data
654
655
656 def test12_resultfn(o_data, expected, i, o):
657 res = expected + 1
658 assert o_data == res, \
659 "%d-%d data %x not match %s\n" \
660 % (i, o, o_data, repr(expected))
661
662
663
664 num_tests = 100
665
666 if __name__ == '__main__':
667 print ("test 1")
668 dut = ExampleBufPipe()
669 run_simulation(dut, testbench(dut), vcd_name="test_bufpipe.vcd")
670
671 print ("test 2")
672 dut = ExampleBufPipe2()
673 run_simulation(dut, testbench2(dut), vcd_name="test_bufpipe2.vcd")
674 ports = [dut.p.i_valid, dut.n.i_ready,
675 dut.n.o_valid, dut.p.o_ready] + \
676 [dut.p.i_data] + [dut.n.o_data]
677 vl = rtlil.convert(dut, ports=ports)
678 with open("test_bufpipe2.il", "w") as f:
679 f.write(vl)
680
681
682 print ("test 3")
683 dut = ExampleBufPipe()
684 test = Test3(dut, test3_resultfn)
685 run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe3.vcd")
686
687 print ("test 3.5")
688 dut = ExamplePipeline()
689 test = Test3(dut, test3_resultfn)
690 run_simulation(dut, [test.send, test.rcv], vcd_name="test_combpipe3.vcd")
691
692 print ("test 4")
693 dut = ExampleBufPipe2()
694 run_simulation(dut, testbench4(dut), vcd_name="test_bufpipe4.vcd")
695
696 print ("test 5")
697 dut = ExampleBufPipeAdd()
698 test = Test5(dut, test5_resultfn)
699 run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe5.vcd")
700
701 print ("test 6")
702 dut = ExampleLTPipeline()
703 test = Test5(dut, test6_resultfn)
704 run_simulation(dut, [test.send, test.rcv], vcd_name="test_ltcomb6.vcd")
705
706 ports = [dut.p.i_valid, dut.n.i_ready,
707 dut.n.o_valid, dut.p.o_ready] + \
708 list(dut.p.i_data) + [dut.n.o_data]
709 vl = rtlil.convert(dut, ports=ports)
710 with open("test_ltcomb_pipe.il", "w") as f:
711 f.write(vl)
712
713 print ("test 7")
714 dut = ExampleAddRecordPipe()
715 data=data_dict()
716 test = Test5(dut, test7_resultfn, data=data)
717 run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord.vcd")
718
719 ports = [dut.p.i_valid, dut.n.i_ready,
720 dut.n.o_valid, dut.p.o_ready,
721 dut.p.i_data.src1, dut.p.i_data.src2,
722 dut.n.o_data.src1, dut.n.o_data.src2]
723 vl = rtlil.convert(dut, ports=ports)
724 with open("test_recordcomb_pipe.il", "w") as f:
725 f.write(vl)
726
727 print ("test 8")
728 dut = ExampleBufPipeAddClass()
729 data=data_2op()
730 test = Test5(dut, test8_resultfn, data=data)
731 run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe8.vcd")
732
733 print ("test 9")
734 dut = ExampleBufPipeChain2()
735 ports = [dut.p.i_valid, dut.n.i_ready,
736 dut.n.o_valid, dut.p.o_ready] + \
737 [dut.p.i_data] + [dut.n.o_data]
738 vl = rtlil.convert(dut, ports=ports)
739 with open("test_bufpipechain2.il", "w") as f:
740 f.write(vl)
741
742 data = data_chain2()
743 test = Test5(dut, test9_resultfn, data=data)
744 run_simulation(dut, [test.send, test.rcv],
745 vcd_name="test_bufpipechain2.vcd")
746
747 print ("test 10")
748 dut = ExampleLTBufferedPipeDerived()
749 test = Test5(dut, test6_resultfn)
750 run_simulation(dut, [test.send, test.rcv], vcd_name="test_ltbufpipe10.vcd")
751 vl = rtlil.convert(dut, ports=ports)
752 with open("test_ltbufpipe10.il", "w") as f:
753 f.write(vl)
754
755 print ("test 11")
756 dut = ExampleAddRecordPlaceHolderPipe()
757 data=data_placeholder()
758 test = Test5(dut, test11_resultfn, data=data)
759 run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord.vcd")
760
761
762 print ("test 12")
763 #dut = ExampleBufPipe3()
764 dut = ExampleBufDelayedPipe()
765 data = data_chain1()
766 test = Test5(dut, test12_resultfn, data=data)
767 run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe12.vcd")
768 ports = [dut.p.i_valid, dut.n.i_ready,
769 dut.n.o_valid, dut.p.o_ready] + \
770 [dut.p.i_data] + [dut.n.o_data]
771 vl = rtlil.convert(dut, ports=ports)
772 with open("test_bufpipe12.il", "w") as f:
773 f.write(vl)
774