1 """ key strategic example showing how to do multi-input fan-in into a
2 multi-stage pipeline, then multi-output fanout, with an unary muxid
5 the multiplex ID from the fan-in is passed in to the pipeline, preserved,
6 and used as a routing ID on the fanout.
9 from random
import randint
11 from nmigen
import Module
, Signal
, Cat
, Value
, Elaboratable
, Const
12 from nmigen
.compat
.sim
import run_simulation
13 from nmigen
.cli
import verilog
, rtlil
15 from nmutil
.multipipe
import CombMultiOutPipeline
, CombMuxOutPipe
16 from nmutil
.multipipe
import PriorityCombMuxInPipe
17 from nmutil
.singlepipe
import MaskCancellable
, RecordObject
, Object
19 from nmutil
.test
import StepLimiter
23 class PassData(Object
):
26 self
.muxid
= Signal(2, reset_less
=True)
27 self
.idx
= Signal(8, reset_less
=True)
28 self
.data
= Signal(16, reset_less
=True)
29 self
.operator
= Signal(2, reset_less
=True)
30 self
.routeid
= Signal(2, reset_less
=True) # muxidname
33 class PassThroughStage
:
41 return self
.ispec() # same as ospec
43 def _setup(self
, m
, i
):
51 class SplitRouteStage
:
61 def setup(self
, m
, i
):
64 with m
.If(i
.operator
== Const(1, 2)):
65 # selects 2nd output in CombMuxOutPipe
66 comb
+= self
.o
.routeid
.eq(1)
67 comb
+= self
.o
.data
.eq(i
.data
+ 1) # add 1 to say "we did it"
68 comb
+= self
.o
.operator
.eq(2) # don't get into infinite loop
70 # selects 2nd output in CombMuxOutPipe
71 comb
+= self
.o
.routeid
.eq(0)
77 class DecisionPipe(MaskCancellable
):
78 def __init__(self
, maskwid
):
79 stage
= SplitRouteStage()
80 MaskCancellable
.__init
__(self
, stage
, maskwid
)
83 class RouteBackPipe(CombMuxOutPipe
):
84 """ routes data back to start of pipeline
88 stage
= PassThroughStage()
89 CombMuxOutPipe
.__init
__(self
, stage
, n_len
=2,
90 maskwid
=4, muxidname
="routeid",
94 class MergeRoutePipe(PriorityCombMuxInPipe
):
95 """ merges data coming from end of pipe (with operator now == 1)
99 stage
= PassThroughStage()
100 PriorityCombMuxInPipe
.__init
__(self
, stage
, p_len
=2, maskwid
=4,
104 class PassThroughPipe(MaskCancellable
):
105 def __init__(self
, maskwid
):
106 MaskCancellable
.__init
__(self
, PassThroughStage(), maskwid
)
110 def __init__(self
, dut
, tlen
):
116 for muxid
in range(dut
.num_rows
):
119 self
.sent
[muxid
] = []
120 for i
in range(self
.tlen
):
121 self
.di
[muxid
][i
] = randint(0, 255) + (muxid
<< 8)
122 self
.do
[muxid
][i
] = self
.di
[muxid
][i
]
124 def send(self
, muxid
):
125 for i
in range(self
.tlen
):
126 op2
= self
.di
[muxid
][i
]
127 rs
= self
.dut
.p
[muxid
]
128 yield rs
.i_valid
.eq(1)
129 yield rs
.i_data
.data
.eq(op2
)
130 yield rs
.i_data
.idx
.eq(i
)
131 yield rs
.i_data
.muxid
.eq(muxid
)
132 yield rs
.i_data
.operator
.eq(1)
133 yield rs
.mask_i
.eq(1)
135 o_p_ready
= yield rs
.o_ready
136 step_limiter
= StepLimiter(10000)
140 o_p_ready
= yield rs
.o_ready
142 print("send", muxid
, i
, hex(op2
), op2
)
143 self
.sent
[muxid
].append(i
)
145 yield rs
.i_valid
.eq(0)
146 yield rs
.mask_i
.eq(0)
147 # wait until it's received
148 step_limiter
= StepLimiter(10000)
149 while i
in self
.do
[muxid
]:
153 # wait random period of time before queueing another value
154 for i
in range(randint(0, 3)):
157 yield rs
.i_valid
.eq(0)
160 print("send ended", muxid
)
162 # wait random period of time before queueing another value
163 # for i in range(randint(0, 3)):
166 #send_range = randint(0, 3)
167 # if send_range == 0:
170 # send = randint(0, send_range) != 0
172 def rcv(self
, muxid
):
173 rs
= self
.dut
.p
[muxid
]
174 for _
in StepLimiter(10000):
177 if False and self
.sent
[muxid
] and randint(0, 2) == 0:
178 todel
= self
.sent
[muxid
].pop()
179 print("to delete", muxid
, self
.sent
[muxid
], todel
)
180 if todel
in self
.do
[muxid
]:
181 del self
.do
[muxid
][todel
]
182 yield rs
.stop_i
.eq(1)
183 print("left", muxid
, self
.do
[muxid
])
184 if len(self
.do
[muxid
]) == 0:
187 #stall_range = randint(0, 3)
188 # for j in range(randint(1,10)):
189 # stall = randint(0, stall_range) != 0
190 # yield self.dut.n[0].i_ready.eq(stall)
193 n
= self
.dut
.n
[muxid
]
194 yield n
.i_ready
.eq(1)
196 yield rs
.stop_i
.eq(0) # resets cancel mask
197 o_n_valid
= yield n
.o_valid
198 i_n_ready
= yield n
.i_ready
199 if not o_n_valid
or not i_n_ready
:
202 out_muxid
= yield n
.o_data
.muxid
203 out_i
= yield n
.o_data
.idx
204 out_v
= yield n
.o_data
.data
206 print("recv", out_muxid
, out_i
, hex(out_v
), hex(out_v
))
208 # see if this output has occurred already, delete it if it has
209 assert muxid
== out_muxid
, \
210 "out_muxid %d not correct %d" % (out_muxid
, muxid
)
211 if out_i
not in self
.sent
[muxid
]:
212 print("cancelled/recv", muxid
, out_i
)
214 assert out_i
in self
.do
[muxid
], "out_i %d not in array %s" % \
215 (out_i
, repr(self
.do
[muxid
]))
216 assert self
.do
[muxid
][out_i
] + 1 == out_v
# check data
217 del self
.do
[muxid
][out_i
]
218 todel
= self
.sent
[muxid
].index(out_i
)
219 del self
.sent
[muxid
][todel
]
221 # check if there's any more outputs
222 if len(self
.do
[muxid
]) == 0:
225 print("recv ended", muxid
)
228 class TestPriorityMuxPipe(PriorityCombMuxInPipe
):
229 def __init__(self
, num_rows
):
230 self
.num_rows
= num_rows
231 stage
= PassThroughStage()
232 PriorityCombMuxInPipe
.__init
__(self
, stage
,
233 p_len
=self
.num_rows
, maskwid
=1)
236 class TestMuxOutPipe(CombMuxOutPipe
):
237 def __init__(self
, num_rows
):
238 self
.num_rows
= num_rows
239 stage
= PassThroughStage()
240 CombMuxOutPipe
.__init
__(self
, stage
, n_len
=self
.num_rows
,
244 class TestInOutPipe(Elaboratable
):
245 def __init__(self
, num_rows
=4):
246 self
.num_rows
= nr
= num_rows
247 self
.inpipe
= TestPriorityMuxPipe(nr
) # fan-in (combinatorial)
248 self
.mergein
= MergeRoutePipe() # merge in feedback
249 self
.pipe1
= PassThroughPipe(nr
) # stage 1 (clock-sync)
250 self
.pipe2
= DecisionPipe(nr
) # stage 2 (clock-sync)
251 # self.pipe3 = PassThroughPipe(nr) # stage 3 (clock-sync)
252 # self.pipe4 = PassThroughPipe(nr) # stage 4 (clock-sync)
253 self
.splitback
= RouteBackPipe() # split back to mergein
254 self
.outpipe
= TestMuxOutPipe(nr
) # fan-out (combinatorial)
255 self
.fifoback
= PassThroughPipe(nr
) # temp route-back store
257 self
.p
= self
.inpipe
.p
# kinda annoying,
258 self
.n
= self
.outpipe
.n
# use pipe in/out as this class in/out
259 self
._ports
= self
.inpipe
.ports() + self
.outpipe
.ports()
261 def elaborate(self
, platform
):
263 m
.submodules
.inpipe
= self
.inpipe
264 m
.submodules
.mergein
= self
.mergein
265 m
.submodules
.pipe1
= self
.pipe1
266 m
.submodules
.pipe2
= self
.pipe2
267 #m.submodules.pipe3 = self.pipe3
268 #m.submodules.pipe4 = self.pipe4
269 m
.submodules
.splitback
= self
.splitback
270 m
.submodules
.outpipe
= self
.outpipe
271 m
.submodules
.fifoback
= self
.fifoback
273 m
.d
.comb
+= self
.inpipe
.n
.connect_to_next(self
.mergein
.p
[0])
274 m
.d
.comb
+= self
.mergein
.n
.connect_to_next(self
.pipe1
.p
)
275 m
.d
.comb
+= self
.pipe1
.connect_to_next(self
.pipe2
)
276 #m.d.comb += self.pipe2.connect_to_next(self.pipe3)
277 #m.d.comb += self.pipe3.connect_to_next(self.pipe4)
278 m
.d
.comb
+= self
.pipe2
.connect_to_next(self
.splitback
)
279 m
.d
.comb
+= self
.splitback
.n
[1].connect_to_next(self
.fifoback
.p
)
280 m
.d
.comb
+= self
.fifoback
.n
.connect_to_next(self
.mergein
.p
[1])
281 m
.d
.comb
+= self
.splitback
.n
[0].connect_to_next(self
.outpipe
.p
)
289 @unittest.skip("buggy -- fails due to exceeding step count limit") # FIXME
291 dut
= TestInOutPipe()
292 vl
= rtlil
.convert(dut
, ports
=dut
.ports())
293 with
open("test_inoutmux_feedback_pipe.il", "w") as f
:
298 test
= InputTest(dut
, tlen
)
299 run_simulation(dut
, [test
.rcv(0), # test.rcv(1),
300 #test.rcv(3), test.rcv(2),
301 test
.send(0), # test.send(1),
302 #test.send(3), test.send(2),
304 vcd_name
="test_inoutmux_feedback_pipe.vcd")
307 if __name__
== '__main__':
308 #from cProfile import Profile