route-back experimentation
[ieee754fpu.git] / src / nmutil / multipipe.py
1 """ Combinatorial Multi-input and Multi-output multiplexer blocks
2 conforming to Pipeline API
3
4 Multi-input is complex because if any one input is ready, the output
5 can be ready, and the decision comes from a separate module.
6
7 Multi-output is simple (pretty much identical to UnbufferedPipeline),
8 and the selection is just a mux. The only proviso (difference) being:
9 the outputs not being selected have to have their ready_o signals
10 DEASSERTED.
11 """
12
13 from math import log
14 from nmigen import Signal, Cat, Const, Mux, Module, Array, Elaboratable
15 from nmigen.cli import verilog, rtlil
16 from nmigen.lib.coding import PriorityEncoder
17 from nmigen.hdl.rec import Record, Layout
18 from nmutil.stageapi import _spec
19
20 from collections.abc import Sequence
21
22 from .nmoperator import eq
23 from .iocontrol import NextControl, PrevControl
24
25
26 class MultiInControlBase(Elaboratable):
27 """ Common functions for Pipeline API
28 """
29 def __init__(self, in_multi=None, p_len=1, maskwid=0, routemask=False):
30 """ Multi-input Control class. Conforms to same API as ControlBase...
31 mostly. has additional indices to the *multiple* input stages
32
33 * p: contains ready/valid to the previous stages PLURAL
34 * n: contains ready/valid to the next stage
35
36 User must also:
37 * add data_i members to PrevControl and
38 * add data_o member to NextControl
39 """
40 self.routemask = routemask
41 # set up input and output IO ACK (prev/next ready/valid)
42 print ("multi_in", maskwid, p_len)
43 p = []
44 for i in range(p_len):
45 p.append(PrevControl(in_multi, maskwid=maskwid))
46 self.p = Array(p)
47 if routemask:
48 nmaskwid = maskwid # straight route mask mode
49 else:
50 nmaskwid = maskwid * p_len # fan-in mode
51 self.n = NextControl(maskwid=maskwid*p_len) # masks fan in (Cat)
52
53 def connect_to_next(self, nxt, p_idx=0):
54 """ helper function to connect to the next stage data/valid/ready.
55 """
56 return self.n.connect_to_next(nxt.p[p_idx])
57
58 def _connect_in(self, prev, idx=0, prev_idx=None):
59 """ helper function to connect stage to an input source. do not
60 use to connect stage-to-stage!
61 """
62 if prev_idx is None:
63 return self.p[idx]._connect_in(prev.p)
64 return self.p[idx]._connect_in(prev.p[prev_idx])
65
66 def _connect_out(self, nxt):
67 """ helper function to connect stage to an output source. do not
68 use to connect stage-to-stage!
69 """
70 if nxt_idx is None:
71 return self.n._connect_out(nxt.n)
72 return self.n._connect_out(nxt.n)
73
74 def set_input(self, i, idx=0):
75 """ helper function to set the input data
76 """
77 return eq(self.p[idx].data_i, i)
78
79 def elaborate(self, platform):
80 m = Module()
81 for i, p in enumerate(self.p):
82 setattr(m.submodules, "p%d" % i, p)
83 m.submodules.n = self.n
84 return m
85
86 def __iter__(self):
87 for p in self.p:
88 yield from p
89 yield from self.n
90
91 def ports(self):
92 return list(self)
93
94
95 class MultiOutControlBase(Elaboratable):
96 """ Common functions for Pipeline API
97 """
98 def __init__(self, n_len=1, in_multi=None, maskwid=0, routemask=False):
99 """ Multi-output Control class. Conforms to same API as ControlBase...
100 mostly. has additional indices to the multiple *output* stages
101 [MultiInControlBase has multiple *input* stages]
102
103 * p: contains ready/valid to the previou stage
104 * n: contains ready/valid to the next stages PLURAL
105
106 User must also:
107 * add data_i member to PrevControl and
108 * add data_o members to NextControl
109 """
110
111 if routemask:
112 nmaskwid = maskwid # straight route mask mode
113 else:
114 nmaskwid = maskwid * n_len # fan-out mode
115
116 # set up input and output IO ACK (prev/next ready/valid)
117 self.p = PrevControl(in_multi, maskwid=nmaskwid)
118 n = []
119 for i in range(n_len):
120 n.append(NextControl(maskwid=maskwid))
121 self.n = Array(n)
122
123 def connect_to_next(self, nxt, n_idx=0):
124 """ helper function to connect to the next stage data/valid/ready.
125 """
126 return self.n[n_idx].connect_to_next(nxt.p)
127
128 def _connect_in(self, prev, idx=0):
129 """ helper function to connect stage to an input source. do not
130 use to connect stage-to-stage!
131 """
132 return self.n[idx]._connect_in(prev.p)
133
134 def _connect_out(self, nxt, idx=0, nxt_idx=None):
135 """ helper function to connect stage to an output source. do not
136 use to connect stage-to-stage!
137 """
138 if nxt_idx is None:
139 return self.n[idx]._connect_out(nxt.n)
140 return self.n[idx]._connect_out(nxt.n[nxt_idx])
141
142 def elaborate(self, platform):
143 m = Module()
144 m.submodules.p = self.p
145 for i, n in enumerate(self.n):
146 setattr(m.submodules, "n%d" % i, n)
147 return m
148
149 def set_input(self, i):
150 """ helper function to set the input data
151 """
152 return eq(self.p.data_i, i)
153
154 def __iter__(self):
155 yield from self.p
156 for n in self.n:
157 yield from n
158
159 def ports(self):
160 return list(self)
161
162
163 class CombMultiOutPipeline(MultiOutControlBase):
164 """ A multi-input Combinatorial block conforming to the Pipeline API
165
166 Attributes:
167 -----------
168 p.data_i : stage input data (non-array). shaped according to ispec
169 n.data_o : stage output data array. shaped according to ospec
170 """
171
172 def __init__(self, stage, n_len, n_mux, maskwid=0, routemask=False):
173 MultiOutControlBase.__init__(self, n_len=n_len, maskwid=maskwid,
174 routemask=routemask)
175 self.stage = stage
176 self.maskwid = maskwid
177 self.routemask = routemask
178 self.n_mux = n_mux
179
180 # set up the input and output data
181 self.p.data_i = _spec(stage.ispec, 'data_i') # input type
182 for i in range(n_len):
183 name = 'data_o_%d' % i
184 self.n[i].data_o = _spec(stage.ospec, name) # output type
185
186 def process(self, i):
187 if hasattr(self.stage, "process"):
188 return self.stage.process(i)
189 return i
190
191 def elaborate(self, platform):
192 m = MultiOutControlBase.elaborate(self, platform)
193
194 if hasattr(self.n_mux, "elaborate"): # TODO: identify submodule?
195 m.submodules.n_mux = self.n_mux
196
197 # need buffer register conforming to *input* spec
198 r_data = _spec(self.stage.ispec, 'r_data') # input type
199 if hasattr(self.stage, "setup"):
200 self.stage.setup(m, r_data)
201
202 # multiplexer id taken from n_mux
203 muxid = self.n_mux.m_id
204 print ("self.n_mux", self.n_mux)
205 print ("self.n_mux.m_id", self.n_mux.m_id)
206
207 # temporaries
208 p_valid_i = Signal(reset_less=True)
209 pv = Signal(reset_less=True)
210 m.d.comb += p_valid_i.eq(self.p.valid_i_test)
211 m.d.comb += pv.eq(self.p.valid_i) #& self.n[muxid].ready_i)
212
213 # all outputs to next stages first initialised to zero (invalid)
214 # the only output "active" is then selected by the muxid
215 for i in range(len(self.n)):
216 m.d.comb += self.n[i].valid_o.eq(0)
217 #with m.If(pv):
218 m.d.comb += self.n[muxid].valid_o.eq(pv)
219 m.d.comb += self.p.ready_o.eq(self.n[muxid].ready_i)
220
221 # send data on
222 #with m.If(pv):
223 m.d.comb += eq(r_data, self.p.data_i)
224 m.d.comb += eq(self.n[muxid].data_o, self.process(r_data))
225
226 if self.maskwid:
227 if self.routemask: # straight "routing" mode - treat like data
228 m.d.comb += self.n[muxid].stop_o.eq(self.p.stop_i)
229 #with m.If(pv):
230 m.d.comb += self.n[muxid].mask_o.eq(self.p.mask_i)
231 else:
232 ml = [] # accumulate output masks
233 ms = [] # accumulate output stops
234 # fan-out mode.
235 # conditionally fan-out mask bits, always fan-out stop bits
236 for i in range(len(self.n)):
237 ml.append(self.n[i].mask_o)
238 ms.append(self.n[i].stop_o)
239 m.d.comb += Cat(*ms).eq(self.p.stop_i)
240 with m.If(pv):
241 m.d.comb += Cat(*ml).eq(self.p.mask_i)
242 return m
243
244
245 class CombMultiInPipeline(MultiInControlBase):
246 """ A multi-input Combinatorial block conforming to the Pipeline API
247
248 Attributes:
249 -----------
250 p.data_i : StageInput, shaped according to ispec
251 The pipeline input
252 p.data_o : StageOutput, shaped according to ospec
253 The pipeline output
254 r_data : input_shape according to ispec
255 A temporary (buffered) copy of a prior (valid) input.
256 This is HELD if the output is not ready. It is updated
257 SYNCHRONOUSLY.
258 """
259
260 def __init__(self, stage, p_len, p_mux, maskwid=0, routemask=False):
261 MultiInControlBase.__init__(self, p_len=p_len, maskwid=maskwid,
262 routemask=routemask)
263 self.stage = stage
264 self.maskwid = maskwid
265 self.p_mux = p_mux
266
267 # set up the input and output data
268 for i in range(p_len):
269 name = 'data_i_%d' % i
270 self.p[i].data_i = _spec(stage.ispec, name) # input type
271 self.n.data_o = _spec(stage.ospec, 'data_o')
272
273 def process(self, i):
274 if hasattr(self.stage, "process"):
275 return self.stage.process(i)
276 return i
277
278 def elaborate(self, platform):
279 m = MultiInControlBase.elaborate(self, platform)
280
281 m.submodules.p_mux = self.p_mux
282
283 # need an array of buffer registers conforming to *input* spec
284 r_data = []
285 data_valid = []
286 p_valid_i = []
287 n_ready_in = []
288 p_len = len(self.p)
289 for i in range(p_len):
290 name = 'r_%d' % i
291 r = _spec(self.stage.ispec, name) # input type
292 r_data.append(r)
293 data_valid.append(Signal(name="data_valid", reset_less=True))
294 p_valid_i.append(Signal(name="p_valid_i", reset_less=True))
295 n_ready_in.append(Signal(name="n_ready_in", reset_less=True))
296 if hasattr(self.stage, "setup"):
297 print ("setup", self, self.stage, r)
298 self.stage.setup(m, r)
299 if len(r_data) > 1:
300 r_data = Array(r_data)
301 p_valid_i = Array(p_valid_i)
302 n_ready_in = Array(n_ready_in)
303 data_valid = Array(data_valid)
304
305 nirn = Signal(reset_less=True)
306 m.d.comb += nirn.eq(~self.n.ready_i)
307 mid = self.p_mux.m_id
308 print ("CombMuxIn mid", self, self.stage, self.routemask, mid, p_len)
309 for i in range(p_len):
310 m.d.comb += data_valid[i].eq(0)
311 m.d.comb += n_ready_in[i].eq(1)
312 m.d.comb += p_valid_i[i].eq(0)
313 m.d.comb += self.p[i].ready_o.eq(0)
314 m.d.comb += p_valid_i[mid].eq(self.p_mux.active)
315 m.d.comb += self.p[mid].ready_o.eq(~data_valid[mid] | self.n.ready_i)
316 m.d.comb += n_ready_in[mid].eq(nirn & data_valid[mid])
317 anyvalid = Signal(i, reset_less=True)
318 av = []
319 for i in range(p_len):
320 av.append(data_valid[i])
321 anyvalid = Cat(*av)
322 m.d.comb += self.n.valid_o.eq(anyvalid.bool())
323 m.d.comb += data_valid[mid].eq(p_valid_i[mid] | \
324 (n_ready_in[mid] & data_valid[mid]))
325
326 if self.routemask:
327 for i in range(p_len):
328 p = self.p[i]
329 vr = Signal(reset_less=True)
330 maskedout = Signal(reset_less=True)
331 m.d.comb += maskedout.eq(p.mask_i & ~p.stop_i)
332 m.d.comb += vr.eq(maskedout.bool() & p.valid_i & p.ready_o)
333 with m.If(vr):
334 m.d.comb += eq(self.n.mask_o, self.p[i].mask_i)
335 m.d.comb += eq(r_data[i], self.p[i].data_i)
336 m.d.comb += eq(self.n.stop_o, self.p[i].stop_i)
337 else:
338 ml = [] # accumulate output masks
339 ms = [] # accumulate output stops
340 for i in range(p_len):
341 vr = Signal(reset_less=True)
342 p = self.p[i]
343 vr = Signal(reset_less=True)
344 maskedout = Signal(reset_less=True)
345 m.d.comb += maskedout.eq(p.mask_i & ~p.stop_i)
346 m.d.comb += vr.eq(maskedout.bool() & p.valid_i & p.ready_o)
347 with m.If(vr):
348 m.d.comb += eq(r_data[i], self.p[i].data_i)
349 if self.maskwid:
350 mlen = len(self.p[i].mask_i)
351 s = mlen*i
352 e = mlen*(i+1)
353 ml.append(Mux(vr, self.p[i].mask_i, Const(0, mlen)))
354 ms.append(self.p[i].stop_i)
355 if self.maskwid:
356 m.d.comb += self.n.mask_o.eq(Cat(*ml))
357 m.d.comb += self.n.stop_o.eq(Cat(*ms))
358
359 m.d.comb += eq(self.n.data_o, self.process(r_data[mid]))
360
361 return m
362
363
364 class CombMuxOutPipe(CombMultiOutPipeline):
365 def __init__(self, stage, n_len, maskwid=0, muxidname=None,
366 routemask=False):
367 muxidname = muxidname or "muxid"
368 # HACK: stage is also the n-way multiplexer
369 CombMultiOutPipeline.__init__(self, stage, n_len=n_len,
370 n_mux=stage, maskwid=maskwid,
371 routemask=routemask)
372
373 # HACK: n-mux is also the stage... so set the muxid equal to input muxid
374 muxid = getattr(self.p.data_i, muxidname)
375 print ("combmuxout", muxidname, muxid)
376 stage.m_id = muxid
377
378
379
380 class InputPriorityArbiter(Elaboratable):
381 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
382 """
383 def __init__(self, pipe, num_rows):
384 self.pipe = pipe
385 self.num_rows = num_rows
386 self.mmax = int(log(self.num_rows) / log(2))
387 self.m_id = Signal(self.mmax, reset_less=True) # multiplex id
388 self.active = Signal(reset_less=True)
389
390 def elaborate(self, platform):
391 m = Module()
392
393 assert len(self.pipe.p) == self.num_rows, \
394 "must declare input to be same size"
395 pe = PriorityEncoder(self.num_rows)
396 m.submodules.selector = pe
397
398 # connect priority encoder
399 in_ready = []
400 for i in range(self.num_rows):
401 p_valid_i = Signal(reset_less=True)
402 if self.pipe.maskwid and not self.pipe.routemask:
403 p = self.pipe.p[i]
404 maskedout = Signal(reset_less=True)
405 m.d.comb += maskedout.eq(p.mask_i & ~p.stop_i)
406 m.d.comb += p_valid_i.eq(maskedout.bool() & p.valid_i_test)
407 else:
408 m.d.comb += p_valid_i.eq(self.pipe.p[i].valid_i_test)
409 in_ready.append(p_valid_i)
410 m.d.comb += pe.i.eq(Cat(*in_ready)) # array of input "valids"
411 m.d.comb += self.active.eq(~pe.n) # encoder active (one input valid)
412 m.d.comb += self.m_id.eq(pe.o) # output one active input
413
414 return m
415
416 def ports(self):
417 return [self.m_id, self.active]
418
419
420
421 class PriorityCombMuxInPipe(CombMultiInPipeline):
422 """ an example of how to use the combinatorial pipeline.
423 """
424
425 def __init__(self, stage, p_len=2, maskwid=0, routemask=False):
426 p_mux = InputPriorityArbiter(self, p_len)
427 CombMultiInPipeline.__init__(self, stage, p_len, p_mux,
428 maskwid=maskwid, routemask=routemask)
429
430
431 if __name__ == '__main__':
432
433 from nmutil.test.example_buf_pipe import ExampleStage
434 dut = PriorityCombMuxInPipe(ExampleStage)
435 vl = rtlil.convert(dut, ports=dut.ports())
436 with open("test_combpipe.il", "w") as f:
437 f.write(vl)