speed up ==, hash, <, >, <=, and >= for plain_data
[nmutil.git] / src / nmutil / test / test_inout_feedback_pipe.py
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
3 and cancellation
4
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.
7 """
8
9 from random import randint
10 from math import log
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
14
15 from nmutil.multipipe import CombMultiOutPipeline, CombMuxOutPipe
16 from nmutil.multipipe import PriorityCombMuxInPipe
17 from nmutil.singlepipe import MaskCancellable, RecordObject, Object
18
19 from nmutil.test import StepLimiter
20 import unittest
21
22
23 class PassData(Object):
24 def __init__(self):
25 Object.__init__(self)
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
31
32
33 class PassThroughStage:
34 def __init__(self):
35 self.o = self.ospec()
36
37 def ispec(self):
38 return PassData()
39
40 def ospec(self):
41 return self.ispec() # same as ospec
42
43 def _setup(self, m, i):
44 comb = m.d.comb
45 #comb += self.o.eq(i)
46
47 def process(self, i):
48 return i
49
50
51 class SplitRouteStage:
52 def __init__(self):
53 self.o = self.ospec()
54
55 def ispec(self):
56 return PassData()
57
58 def ospec(self):
59 return PassData()
60
61 def setup(self, m, i):
62 comb = m.d.comb
63 comb += self.o.eq(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
69 with m.Else():
70 # selects 2nd output in CombMuxOutPipe
71 comb += self.o.routeid.eq(0)
72
73 def process(self, i):
74 return self.o
75
76
77 class DecisionPipe(MaskCancellable):
78 def __init__(self, maskwid):
79 stage = SplitRouteStage()
80 MaskCancellable.__init__(self, stage, maskwid)
81
82
83 class RouteBackPipe(CombMuxOutPipe):
84 """ routes data back to start of pipeline
85 """
86
87 def __init__(self):
88 stage = PassThroughStage()
89 CombMuxOutPipe.__init__(self, stage, n_len=2,
90 maskwid=4, muxidname="routeid",
91 routemask=True)
92
93
94 class MergeRoutePipe(PriorityCombMuxInPipe):
95 """ merges data coming from end of pipe (with operator now == 1)
96 """
97
98 def __init__(self):
99 stage = PassThroughStage()
100 PriorityCombMuxInPipe.__init__(self, stage, p_len=2, maskwid=4,
101 routemask=True)
102
103
104 class PassThroughPipe(MaskCancellable):
105 def __init__(self, maskwid):
106 MaskCancellable.__init__(self, PassThroughStage(), maskwid)
107
108
109 class InputTest:
110 def __init__(self, dut, tlen):
111 self.dut = dut
112 self.di = {}
113 self.do = {}
114 self.sent = {}
115 self.tlen = tlen
116 for muxid in range(dut.num_rows):
117 self.di[muxid] = {}
118 self.do[muxid] = {}
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]
123
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)
134 yield
135 o_p_ready = yield rs.o_ready
136 step_limiter = StepLimiter(10000)
137 while not o_p_ready:
138 step_limiter.step()
139 yield
140 o_p_ready = yield rs.o_ready
141
142 print("send", muxid, i, hex(op2), op2)
143 self.sent[muxid].append(i)
144
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]:
150 step_limiter.step()
151 yield
152
153 # wait random period of time before queueing another value
154 for i in range(randint(0, 3)):
155 yield
156
157 yield rs.i_valid.eq(0)
158 yield
159
160 print("send ended", muxid)
161
162 # wait random period of time before queueing another value
163 # for i in range(randint(0, 3)):
164 # yield
165
166 #send_range = randint(0, 3)
167 # if send_range == 0:
168 # send = True
169 # else:
170 # send = randint(0, send_range) != 0
171
172 def rcv(self, muxid):
173 rs = self.dut.p[muxid]
174 for _ in StepLimiter(10000):
175
176 # check cancellation
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:
185 break
186
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)
191 # yield
192
193 n = self.dut.n[muxid]
194 yield n.i_ready.eq(1)
195 yield
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:
200 continue
201
202 out_muxid = yield n.o_data.muxid
203 out_i = yield n.o_data.idx
204 out_v = yield n.o_data.data
205
206 print("recv", out_muxid, out_i, hex(out_v), hex(out_v))
207
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)
213 continue
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]
220
221 # check if there's any more outputs
222 if len(self.do[muxid]) == 0:
223 break
224
225 print("recv ended", muxid)
226
227
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)
234
235
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,
241 maskwid=1)
242
243
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
256
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()
260
261 def elaborate(self, platform):
262 m = Module()
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
272
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)
282
283 return m
284
285 def ports(self):
286 return self._ports
287
288
289 @unittest.skip("buggy -- fails due to exceeding step count limit") # FIXME
290 def test1():
291 dut = TestInOutPipe()
292 vl = rtlil.convert(dut, ports=dut.ports())
293 with open("test_inoutmux_feedback_pipe.il", "w") as f:
294 f.write(vl)
295
296 tlen = 5
297
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),
303 ],
304 vcd_name="test_inoutmux_feedback_pipe.vcd")
305
306
307 if __name__ == '__main__':
308 #from cProfile import Profile
309 #p = Profile()
310 # p.enable()
311 test1()
312 # p.disable()
313 # p.print_stats()