restore old Multi-in/out behaviour
[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 m.d.comb += pv.eq(self.p.valid_i & self.p.ready_o)
213
214 # all outputs to next stages first initialised to zero (invalid)
215 # the only output "active" is then selected by the muxid
216 for i in range(len(self.n)):
217 m.d.comb += self.n[i].valid_o.eq(0)
218 if self.routemask:
219 #with m.If(pv):
220 m.d.comb += self.n[muxid].valid_o.eq(pv)
221 m.d.comb += self.p.ready_o.eq(self.n[muxid].ready_i)
222 else:
223 data_valid = self.n[muxid].valid_o
224 m.d.comb += self.p.ready_o.eq(~data_valid | self.n[muxid].ready_i)
225 m.d.comb += data_valid.eq(p_valid_i | \
226 (~self.n[muxid].ready_i & data_valid))
227
228
229 # send data on
230 #with m.If(pv):
231 m.d.comb += eq(r_data, self.p.data_i)
232 m.d.comb += eq(self.n[muxid].data_o, self.process(r_data))
233
234 if self.maskwid:
235 if self.routemask: # straight "routing" mode - treat like data
236 m.d.comb += self.n[muxid].stop_o.eq(self.p.stop_i)
237 with m.If(pv):
238 m.d.comb += self.n[muxid].mask_o.eq(self.p.mask_i)
239 else:
240 ml = [] # accumulate output masks
241 ms = [] # accumulate output stops
242 # fan-out mode.
243 # conditionally fan-out mask bits, always fan-out stop bits
244 for i in range(len(self.n)):
245 ml.append(self.n[i].mask_o)
246 ms.append(self.n[i].stop_o)
247 m.d.comb += Cat(*ms).eq(self.p.stop_i)
248 with m.If(pv):
249 m.d.comb += Cat(*ml).eq(self.p.mask_i)
250 return m
251
252
253 class CombMultiInPipeline(MultiInControlBase):
254 """ A multi-input Combinatorial block conforming to the Pipeline API
255
256 Attributes:
257 -----------
258 p.data_i : StageInput, shaped according to ispec
259 The pipeline input
260 p.data_o : StageOutput, shaped according to ospec
261 The pipeline output
262 r_data : input_shape according to ispec
263 A temporary (buffered) copy of a prior (valid) input.
264 This is HELD if the output is not ready. It is updated
265 SYNCHRONOUSLY.
266 """
267
268 def __init__(self, stage, p_len, p_mux, maskwid=0, routemask=False):
269 MultiInControlBase.__init__(self, p_len=p_len, maskwid=maskwid,
270 routemask=routemask)
271 self.stage = stage
272 self.maskwid = maskwid
273 self.p_mux = p_mux
274
275 # set up the input and output data
276 for i in range(p_len):
277 name = 'data_i_%d' % i
278 self.p[i].data_i = _spec(stage.ispec, name) # input type
279 self.n.data_o = _spec(stage.ospec, 'data_o')
280
281 def process(self, i):
282 if hasattr(self.stage, "process"):
283 return self.stage.process(i)
284 return i
285
286 def elaborate(self, platform):
287 m = MultiInControlBase.elaborate(self, platform)
288
289 m.submodules.p_mux = self.p_mux
290
291 # need an array of buffer registers conforming to *input* spec
292 r_data = []
293 data_valid = []
294 p_valid_i = []
295 n_ready_in = []
296 p_len = len(self.p)
297 for i in range(p_len):
298 name = 'r_%d' % i
299 r = _spec(self.stage.ispec, name) # input type
300 r_data.append(r)
301 data_valid.append(Signal(name="data_valid", reset_less=True))
302 p_valid_i.append(Signal(name="p_valid_i", reset_less=True))
303 n_ready_in.append(Signal(name="n_ready_in", reset_less=True))
304 if hasattr(self.stage, "setup"):
305 print ("setup", self, self.stage, r)
306 self.stage.setup(m, r)
307 if len(r_data) > 1:
308 r_data = Array(r_data)
309 p_valid_i = Array(p_valid_i)
310 n_ready_in = Array(n_ready_in)
311 data_valid = Array(data_valid)
312
313 nirn = Signal(reset_less=True)
314 m.d.comb += nirn.eq(~self.n.ready_i)
315 mid = self.p_mux.m_id
316 print ("CombMuxIn mid", self, self.stage, self.routemask, mid, p_len)
317 for i in range(p_len):
318 m.d.comb += data_valid[i].eq(0)
319 m.d.comb += n_ready_in[i].eq(1)
320 m.d.comb += p_valid_i[i].eq(0)
321 m.d.comb += self.p[i].ready_o.eq(0)
322 p = self.p[mid]
323 maskedout = Signal(reset_less=True)
324 if hasattr(p, "mask_i"):
325 m.d.comb += maskedout.eq(p.mask_i & ~p.stop_i)
326 else:
327 m.d.comb += maskedout.eq(1)
328 m.d.comb += p_valid_i[mid].eq(maskedout & self.p_mux.active)
329 m.d.comb += self.p[mid].ready_o.eq(~data_valid[mid] | self.n.ready_i)
330 m.d.comb += n_ready_in[mid].eq(nirn & data_valid[mid])
331 anyvalid = Signal(i, reset_less=True)
332 av = []
333 for i in range(p_len):
334 av.append(data_valid[i])
335 anyvalid = Cat(*av)
336 m.d.comb += self.n.valid_o.eq(anyvalid.bool())
337 m.d.comb += data_valid[mid].eq(p_valid_i[mid] | \
338 (n_ready_in[mid] ))
339
340 if self.routemask:
341 # XXX hack - fixes loop
342 m.d.comb += eq(self.n.stop_o, self.p[0].stop_i)
343 for i in range(p_len):
344 p = self.p[i]
345 vr = Signal(reset_less=True)
346 maskedout = Signal(reset_less=True)
347 if hasattr(p, "mask_i"):
348 m.d.comb += maskedout.eq(p.mask_i & ~p.stop_i)
349 else:
350 m.d.comb += maskedout.eq(1)
351 m.d.comb += vr.eq(maskedout.bool() & p.valid_i & p.ready_o)
352 #m.d.comb += vr.eq(p.valid_i & p.ready_o)
353 with m.If(vr):
354 m.d.comb += eq(self.n.mask_o, self.p[i].mask_i)
355 m.d.comb += eq(r_data[i], self.p[i].data_i)
356 else:
357 ml = [] # accumulate output masks
358 ms = [] # accumulate output stops
359 for i in range(p_len):
360 vr = Signal(reset_less=True)
361 p = self.p[i]
362 vr = Signal(reset_less=True)
363 maskedout = Signal(reset_less=True)
364 if hasattr(p, "mask_i"):
365 m.d.comb += maskedout.eq(p.mask_i & ~p.stop_i)
366 else:
367 m.d.comb += maskedout.eq(1)
368 m.d.comb += vr.eq(maskedout.bool() & p.valid_i & p.ready_o)
369 with m.If(vr):
370 m.d.comb += eq(r_data[i], self.p[i].data_i)
371 if self.maskwid:
372 mlen = len(self.p[i].mask_i)
373 s = mlen*i
374 e = mlen*(i+1)
375 ml.append(Mux(vr, self.p[i].mask_i, Const(0, mlen)))
376 ms.append(self.p[i].stop_i)
377 if self.maskwid:
378 m.d.comb += self.n.mask_o.eq(Cat(*ml))
379 m.d.comb += self.n.stop_o.eq(Cat(*ms))
380
381 m.d.comb += eq(self.n.data_o, self.process(r_data[mid]))
382
383 return m
384
385
386 class CombMuxOutPipe(CombMultiOutPipeline):
387 def __init__(self, stage, n_len, maskwid=0, muxidname=None,
388 routemask=False):
389 muxidname = muxidname or "muxid"
390 # HACK: stage is also the n-way multiplexer
391 CombMultiOutPipeline.__init__(self, stage, n_len=n_len,
392 n_mux=stage, maskwid=maskwid,
393 routemask=routemask)
394
395 # HACK: n-mux is also the stage... so set the muxid equal to input muxid
396 muxid = getattr(self.p.data_i, muxidname)
397 print ("combmuxout", muxidname, muxid)
398 stage.m_id = muxid
399
400
401
402 class InputPriorityArbiter(Elaboratable):
403 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
404 """
405 def __init__(self, pipe, num_rows):
406 self.pipe = pipe
407 self.num_rows = num_rows
408 self.mmax = int(log(self.num_rows) / log(2))
409 self.m_id = Signal(self.mmax, reset_less=True) # multiplex id
410 self.active = Signal(reset_less=True)
411
412 def elaborate(self, platform):
413 m = Module()
414
415 assert len(self.pipe.p) == self.num_rows, \
416 "must declare input to be same size"
417 pe = PriorityEncoder(self.num_rows)
418 m.submodules.selector = pe
419
420 # connect priority encoder
421 in_ready = []
422 for i in range(self.num_rows):
423 p_valid_i = Signal(reset_less=True)
424 if self.pipe.maskwid and not self.pipe.routemask:
425 p = self.pipe.p[i]
426 maskedout = Signal(reset_less=True)
427 m.d.comb += maskedout.eq(p.mask_i & ~p.stop_i)
428 m.d.comb += p_valid_i.eq(maskedout.bool() & p.valid_i_test)
429 else:
430 m.d.comb += p_valid_i.eq(self.pipe.p[i].valid_i_test)
431 in_ready.append(p_valid_i)
432 m.d.comb += pe.i.eq(Cat(*in_ready)) # array of input "valids"
433 m.d.comb += self.active.eq(~pe.n) # encoder active (one input valid)
434 m.d.comb += self.m_id.eq(pe.o) # output one active input
435
436 return m
437
438 def ports(self):
439 return [self.m_id, self.active]
440
441
442
443 class PriorityCombMuxInPipe(CombMultiInPipeline):
444 """ an example of how to use the combinatorial pipeline.
445 """
446
447 def __init__(self, stage, p_len=2, maskwid=0, routemask=False):
448 p_mux = InputPriorityArbiter(self, p_len)
449 CombMultiInPipeline.__init__(self, stage, p_len, p_mux,
450 maskwid=maskwid, routemask=routemask)
451
452
453 if __name__ == '__main__':
454
455 from nmutil.test.example_buf_pipe import ExampleStage
456 dut = PriorityCombMuxInPipe(ExampleStage)
457 vl = rtlil.convert(dut, ports=dut.ports())
458 with open("test_combpipe.il", "w") as f:
459 f.write(vl)