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
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
12 from random
import randint
15 def check_o_n_valid(dut
, val
):
16 o_n_valid
= yield dut
.n
.o_valid
17 assert o_n_valid
== val
19 def check_o_n_valid2(dut
, val
):
20 o_n_valid
= yield dut
.n
.o_valid
21 assert o_n_valid
== val
25 #yield dut.i_p_rst.eq(1)
26 yield dut
.n
.i_ready
.eq(0)
27 yield dut
.p
.o_ready
.eq(0)
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)
36 yield dut
.p
.i_data
.eq(7)
37 yield from check_o_n_valid(dut
, 0) # effects of i_p_valid delayed
39 yield from check_o_n_valid(dut
, 1) # ok *now* i_p_valid effect is felt
41 yield dut
.p
.i_data
.eq(2)
43 yield dut
.n
.i_ready
.eq(0) # begin going into "stall" (next stage says ready)
44 yield dut
.p
.i_data
.eq(9)
46 yield dut
.p
.i_valid
.eq(0)
47 yield dut
.p
.i_data
.eq(12)
49 yield dut
.p
.i_data
.eq(32)
50 yield dut
.n
.i_ready
.eq(1)
52 yield from check_o_n_valid(dut
, 1) # buffer still needs to output
54 yield from check_o_n_valid(dut
, 1) # buffer still needs to output
56 yield from check_o_n_valid(dut
, 0) # buffer outputted, *now* we're done.
61 #yield dut.p.i_rst.eq(1)
62 yield dut
.n
.i_ready
.eq(0)
63 #yield dut.p.o_ready.eq(0)
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)
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
75 yield from check_o_n_valid2(dut
, 0) # effects of i_p_valid delayed 2 clocks
77 yield dut
.p
.i_data
.eq(2)
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)
83 yield dut
.p
.i_valid
.eq(0)
84 yield dut
.p
.i_data
.eq(12)
86 yield dut
.p
.i_data
.eq(32)
87 yield dut
.n
.i_ready
.eq(1)
89 yield from check_o_n_valid2(dut
, 1) # buffer still needs to output
91 yield from check_o_n_valid2(dut
, 1) # buffer still needs to output
93 yield from check_o_n_valid2(dut
, 1) # buffer still needs to output
95 yield from check_o_n_valid2(dut
, 0) # buffer outputted, *now* we're done.
102 def __init__(self
, dut
, resultfn
):
104 self
.resultfn
= resultfn
106 for i
in range(num_tests
):
107 #data.append(randint(0, 1<<16-1))
108 self
.data
.append(i
+1)
113 while self
.o
!= len(self
.data
):
114 send_range
= randint(0, 3)
115 for j
in range(randint(1,10)):
119 send
= randint(0, send_range
) != 0
120 o_p_ready
= yield self
.dut
.p
.o_ready
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
])
129 yield self
.dut
.p
.i_valid
.eq(0)
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
)
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
:
143 o_data
= yield self
.dut
.n
.o_data
144 self
.resultfn(o_data
, self
.data
[self
.o
], self
.i
, self
.o
)
146 if self
.o
== len(self
.data
):
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
)
154 def data_placeholder():
156 for i
in range(num_tests
):
158 d
.src1
= randint(0, 1<<16-1)
159 d
.src2
= randint(0, 1<<16-1)
165 for i
in range(num_tests
):
166 data
.append({'src1': randint(0, 1<<16-1),
167 'src2': randint(0, 1<<16-1)})
172 def __init__(self
, dut
, resultfn
, data
=None):
174 self
.resultfn
= resultfn
179 for i
in range(num_tests
):
180 self
.data
.append((randint(0, 1<<16-1), randint(0, 1<<16-1)))
185 while self
.o
!= len(self
.data
):
186 send_range
= randint(0, 3)
187 for j
in range(randint(1,10)):
191 send
= randint(0, send_range
) != 0
192 o_p_ready
= yield self
.dut
.p
.o_ready
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
]):
202 yield self
.dut
.p
.i_valid
.eq(0)
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
)
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
:
216 if isinstance(self
.dut
.n
.o_data
, Record
):
218 dod
= self
.dut
.n
.o_data
219 for k
, v
in dod
.fields
.items():
222 o_data
= yield self
.dut
.n
.o_data
223 self
.resultfn(o_data
, self
.data
[self
.o
], self
.i
, self
.o
)
225 if self
.o
== len(self
.data
):
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
))
236 for i
in range(num_tests
):
237 #data.append(randint(0, 1<<16-1))
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
247 if send
and i
!= len(data
):
248 yield dut
.p
.i_valid
.eq(1)
249 yield dut
.p
.i_data
.eq(data
[i
])
252 yield dut
.p
.i_valid
.eq(0)
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
])
265 class ExampleBufPipe2(PipelineBase
):
267 connect these: ------|---------------|
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
274 PipelineBase
.__init
__(self
)
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
280 self
.pipe1
= ExampleBufPipe()
281 self
.pipe2
= ExampleBufPipe()
283 def elaborate(self
, platform
):
285 m
.submodules
.pipe1
= self
.pipe1
286 m
.submodules
.pipe2
= self
.pipe2
288 # connect inter-pipe input/output valid/ready/data
289 m
.d
.comb
+= self
.pipe1
.connect_to_next(self
.pipe2
)
291 # inputs/outputs to the module: pipe1 connections here (LHS)
292 m
.d
.comb
+= self
.pipe1
.connect_in(self
)
294 # now pipe2 connections (RHS)
295 m
.d
.comb
+= self
.pipe2
.connect_out(self
)
300 class ExampleBufPipeChain2(BufferedPipeline
):
301 """ connects two stages together as a *single* combinatorial stage.
304 stage1
= ExampleStageCls()
305 stage2
= ExampleStageCls()
306 combined
= StageChain([stage1
, stage2
])
307 BufferedPipeline
.__init
__(self
, combined
)
312 for i
in range(num_tests
):
313 data
.append(randint(0, 1<<16-2))
317 def test9_resultfn(o_data
, expected
, i
, o
):
319 assert o_data
== res
, \
320 "%d-%d data %x not match %s\n" \
321 % (i
, o
, o_data
, repr(expected
))
325 def __init__(self
, width
, signed
):
326 self
.src1
= Signal((width
, signed
))
327 self
.src2
= Signal((width
, signed
))
328 self
.output
= Signal(width
)
330 def elaborate(self
, platform
):
332 m
.d
.comb
+= self
.output
.eq(Mux(self
.src1
< self
.src2
, 1, 0))
338 self
.slt
= SetLessThan(16, True)
341 return (Signal(16), Signal(16))
346 def setup(self
, m
, i
):
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
)
353 def process(self
, i
):
357 class LTStageDerived(SetLessThan
):
360 SetLessThan
.__init
__(self
, 16, True)
363 return (Signal(16), Signal(16))
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])
373 def process(self
, i
):
377 class ExampleLTPipeline(UnbufferedPipeline
):
378 """ an example of how to use the combinatorial pipeline.
383 UnbufferedPipeline
.__init
__(self
, stage
)
386 class ExampleLTBufferedPipeDerived(BufferedPipeline
):
387 """ an example of how to use the combinatorial pipeline.
391 stage
= LTStageDerived()
392 BufferedPipeline
.__init
__(self
, stage
)
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
))
402 class ExampleAddRecordStage
:
403 """ example use of a Record
406 record_spec
= [('src1', 16), ('src2', 16)]
408 """ returns a Record using the specification
410 return Record(self
.record_spec
)
413 return Record(self
.record_spec
)
415 def process(self
, i
):
416 """ process the input data, returning a dictionary with key names
417 that exactly match the Record's attributes.
419 return {'src1': i
.src1
+ 1,
423 class ExampleAddRecordPlaceHolderStage
:
424 """ example use of a Record, with a placeholder as the processing result
427 record_spec
= [('src1', 16), ('src2', 16)]
429 """ returns a Record using the specification
431 return Record(self
.record_spec
)
434 return Record(self
.record_spec
)
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.
445 class PlaceHolder
: pass
448 class ExampleAddRecordPipe(UnbufferedPipeline
):
449 """ an example of how to use the combinatorial pipeline.
453 stage
= ExampleAddRecordStage()
454 UnbufferedPipeline
.__init
__(self
, stage
)
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
))
464 class ExampleAddRecordPlaceHolderPipe(UnbufferedPipeline
):
465 """ an example of how to use the combinatorial pipeline.
469 stage
= ExampleAddRecordPlaceHolderStage()
470 UnbufferedPipeline
.__init
__(self
, stage
)
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
))
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
487 self
.op1
= Signal(16)
488 self
.op2
= Signal(16)
491 return [self
.op1
.eq(i
.op1
), self
.op2
.eq(i
.op2
)]
494 class ExampleAddClassStage
:
495 """ an example of how to use the buffered pipeline, as a class instance
499 """ returns an instance of an Example2OpClass.
501 return Example2OpClass()
504 """ returns an output signal which will happen to contain the sum
509 def process(self
, i
):
510 """ process the input data (sums the values in the tuple) and returns it
515 class ExampleBufPipeAddClass(BufferedPipeline
):
516 """ an example of how to use the buffered pipeline, using a class instance
520 addstage
= ExampleAddClassStage()
521 BufferedPipeline
.__init
__(self
, addstage
)
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
530 def __init__(self
, op1
, op2
):
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
))
543 for i
in range(num_tests
):
544 data
.append(TestInputAdd(randint(0, 1<<16-1), randint(0, 1<<16-1)))
547 class InputPriorityArbiter
:
548 def __init__(self
, pipe
, num_rows
):
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)
555 def elaborate(self
, platform
):
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
563 # connect priority encoder
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
576 return [self
.mid
, self
.active
]
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
)
584 def elaborate(self
, platform
):
587 pe
= PriorityEncoder(self
.num_rows
)
588 m
.submodules
.selector
= pe
589 m
.submodules
.out_op
= self
.out_op
590 m
.submodules
+= self
.rs
592 # connect priority encoder
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
))
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
)
603 # encoder active: ack relevant input, record MID, pass output
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
])
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)
621 for i
in range(self
.num_rows
):
623 res
+= inop
.in_op
+ [inop
.stb
]
624 return self
.out_op
.ports() + res
+ [self
.mid
]
630 if __name__
== '__main__':
632 dut
= ExampleBufPipe()
633 run_simulation(dut
, testbench(dut
), vcd_name
="test_bufpipe.vcd")
636 dut
= ExampleBufPipe2()
637 run_simulation(dut
, testbench2(dut
), vcd_name
="test_bufpipe2.vcd")
640 dut
= ExampleBufPipe()
641 test
= Test3(dut
, test3_resultfn
)
642 run_simulation(dut
, [test
.send
, test
.rcv
], vcd_name
="test_bufpipe3.vcd")
645 dut
= ExamplePipeline()
646 test
= Test3(dut
, test3_resultfn
)
647 run_simulation(dut
, [test
.send
, test
.rcv
], vcd_name
="test_combpipe3.vcd")
650 dut
= ExampleBufPipe2()
651 run_simulation(dut
, testbench4(dut
), vcd_name
="test_bufpipe4.vcd")
654 dut
= ExampleBufPipeAdd()
655 test
= Test5(dut
, test5_resultfn
)
656 run_simulation(dut
, [test
.send
, test
.rcv
], vcd_name
="test_bufpipe5.vcd")
659 dut
= ExampleLTPipeline()
660 test
= Test5(dut
, test6_resultfn
)
661 run_simulation(dut
, [test
.send
, test
.rcv
], vcd_name
="test_ltcomb6.vcd")
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
:
671 dut
= ExampleAddRecordPipe()
673 test
= Test5(dut
, test7_resultfn
, data
=data
)
674 run_simulation(dut
, [test
.send
, test
.rcv
], vcd_name
="test_addrecord.vcd")
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
:
685 dut
= ExampleBufPipeAddClass()
687 test
= Test5(dut
, test8_resultfn
, data
=data
)
688 run_simulation(dut
, [test
.send
, test
.rcv
], vcd_name
="test_bufpipe8.vcd")
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
:
700 test
= Test5(dut
, test9_resultfn
, data
=data
)
701 run_simulation(dut
, [test
.send
, test
.rcv
],
702 vcd_name
="test_bufpipechain2.vcd")
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
:
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")