1 # SPDX-License-Identifier: LGPL-3-or-later
2 """ Combinatorial Multi-input and Multi-output multiplexer blocks
3 conforming to Pipeline API
5 This work is funded through NLnet under Grant 2019-02-012
9 Multi-input is complex because if any one input is ready, the output
10 can be ready, and the decision comes from a separate module.
12 Multi-output is simple (pretty much identical to UnbufferedPipeline),
13 and the selection is just a mux. The only proviso (difference) being:
14 the outputs not being selected have to have their o_ready signals
17 https://bugs.libre-soc.org/show_bug.cgi?id=538
21 from nmigen
import Signal
, Cat
, Const
, Mux
, Module
, Array
, Elaboratable
22 from nmigen
.cli
import verilog
, rtlil
23 from nmigen
.lib
.coding
import PriorityEncoder
24 from nmigen
.hdl
.rec
import Record
, Layout
25 from nmutil
.stageapi
import _spec
27 from collections
.abc
import Sequence
29 from nmutil
.nmoperator
import eq
30 from nmutil
.iocontrol
import NextControl
, PrevControl
33 class MultiInControlBase(Elaboratable
):
34 """ Common functions for Pipeline API
37 def __init__(self
, in_multi
=None, p_len
=1, maskwid
=0, routemask
=False):
38 """ Multi-input Control class. Conforms to same API as ControlBase...
39 mostly. has additional indices to the *multiple* input stages
41 * p: contains ready/valid to the previous stages PLURAL
42 * n: contains ready/valid to the next stage
45 * add i_data members to PrevControl and
46 * add o_data member to NextControl
48 self
.routemask
= routemask
49 # set up input and output IO ACK (prev/next ready/valid)
50 print("multi_in", self
, maskwid
, p_len
, routemask
)
52 for i
in range(p_len
):
53 p
.append(PrevControl(in_multi
, maskwid
=maskwid
))
56 nmaskwid
= maskwid
# straight route mask mode
58 nmaskwid
= maskwid
* p_len
# fan-in mode
59 self
.n
= NextControl(maskwid
=nmaskwid
) # masks fan in (Cat)
61 def connect_to_next(self
, nxt
, p_idx
=0):
62 """ helper function to connect to the next stage data/valid/ready.
64 return self
.n
.connect_to_next(nxt
.p
[p_idx
])
66 def _connect_in(self
, prev
, idx
=0, prev_idx
=None):
67 """ helper function to connect stage to an input source. do not
68 use to connect stage-to-stage!
71 return self
.p
[idx
]._connect
_in
(prev
.p
)
72 return self
.p
[idx
]._connect
_in
(prev
.p
[prev_idx
])
74 def _connect_out(self
, nxt
):
75 """ helper function to connect stage to an output source. do not
76 use to connect stage-to-stage!
79 return self
.n
._connect
_out
(nxt
.n
)
80 return self
.n
._connect
_out
(nxt
.n
)
82 def set_input(self
, i
, idx
=0):
83 """ helper function to set the input data
85 return eq(self
.p
[idx
].i_data
, i
)
87 def elaborate(self
, platform
):
89 for i
, p
in enumerate(self
.p
):
90 setattr(m
.submodules
, "p%d" % i
, p
)
91 m
.submodules
.n
= self
.n
103 class MultiOutControlBase(Elaboratable
):
104 """ Common functions for Pipeline API
107 def __init__(self
, n_len
=1, in_multi
=None, maskwid
=0, routemask
=False):
108 """ Multi-output Control class. Conforms to same API as ControlBase...
109 mostly. has additional indices to the multiple *output* stages
110 [MultiInControlBase has multiple *input* stages]
112 * p: contains ready/valid to the previou stage
113 * n: contains ready/valid to the next stages PLURAL
116 * add i_data member to PrevControl and
117 * add o_data members to NextControl
121 nmaskwid
= maskwid
# straight route mask mode
123 nmaskwid
= maskwid
* n_len
# fan-out mode
125 # set up input and output IO ACK (prev/next ready/valid)
126 self
.p
= PrevControl(in_multi
, maskwid
=nmaskwid
)
128 for i
in range(n_len
):
129 n
.append(NextControl(maskwid
=maskwid
))
132 def connect_to_next(self
, nxt
, n_idx
=0):
133 """ helper function to connect to the next stage data/valid/ready.
135 return self
.n
[n_idx
].connect_to_next(nxt
.p
)
137 def _connect_in(self
, prev
, idx
=0):
138 """ helper function to connect stage to an input source. do not
139 use to connect stage-to-stage!
141 return self
.n
[idx
]._connect
_in
(prev
.p
)
143 def _connect_out(self
, nxt
, idx
=0, nxt_idx
=None):
144 """ helper function to connect stage to an output source. do not
145 use to connect stage-to-stage!
148 return self
.n
[idx
]._connect
_out
(nxt
.n
)
149 return self
.n
[idx
]._connect
_out
(nxt
.n
[nxt_idx
])
151 def elaborate(self
, platform
):
153 m
.submodules
.p
= self
.p
154 for i
, n
in enumerate(self
.n
):
155 setattr(m
.submodules
, "n%d" % i
, n
)
158 def set_input(self
, i
):
159 """ helper function to set the input data
161 return eq(self
.p
.i_data
, i
)
172 class CombMultiOutPipeline(MultiOutControlBase
):
173 """ A multi-input Combinatorial block conforming to the Pipeline API
177 p.i_data : stage input data (non-array). shaped according to ispec
178 n.o_data : stage output data array. shaped according to ospec
181 def __init__(self
, stage
, n_len
, n_mux
, maskwid
=0, routemask
=False):
182 MultiOutControlBase
.__init
__(self
, n_len
=n_len
, maskwid
=maskwid
,
185 self
.maskwid
= maskwid
186 self
.routemask
= routemask
189 # set up the input and output data
190 self
.p
.i_data
= _spec(stage
.ispec
, 'i_data') # input type
191 for i
in range(n_len
):
192 name
= 'o_data_%d' % i
193 self
.n
[i
].o_data
= _spec(stage
.ospec
, name
) # output type
195 def process(self
, i
):
196 if hasattr(self
.stage
, "process"):
197 return self
.stage
.process(i
)
200 def elaborate(self
, platform
):
201 m
= MultiOutControlBase
.elaborate(self
, platform
)
203 if hasattr(self
.n_mux
, "elaborate"): # TODO: identify submodule?
204 m
.submodules
.n_mux
= self
.n_mux
206 # need buffer register conforming to *input* spec
207 r_data
= _spec(self
.stage
.ispec
, 'r_data') # input type
208 if hasattr(self
.stage
, "setup"):
209 self
.stage
.setup(m
, r_data
)
211 # multiplexer id taken from n_mux
212 muxid
= self
.n_mux
.m_id
213 print("self.n_mux", self
.n_mux
)
214 print("self.n_mux.m_id", self
.n_mux
.m_id
)
216 self
.n_mux
.m_id
.name
= "m_id"
219 p_i_valid
= Signal(reset_less
=True)
220 pv
= Signal(reset_less
=True)
221 m
.d
.comb
+= p_i_valid
.eq(self
.p
.i_valid_test
)
222 # m.d.comb += pv.eq(self.p.i_valid) #& self.n[muxid].i_ready)
223 m
.d
.comb
+= pv
.eq(self
.p
.i_valid
& self
.p
.o_ready
)
225 # all outputs to next stages first initialised to zero (invalid)
226 # the only output "active" is then selected by the muxid
227 for i
in range(len(self
.n
)):
228 m
.d
.comb
+= self
.n
[i
].o_valid
.eq(0)
231 m
.d
.comb
+= self
.n
[muxid
].o_valid
.eq(pv
)
232 m
.d
.comb
+= self
.p
.o_ready
.eq(self
.n
[muxid
].i_ready
)
234 data_valid
= self
.n
[muxid
].o_valid
235 m
.d
.comb
+= self
.p
.o_ready
.eq(self
.n
[muxid
].i_ready
)
236 m
.d
.comb
+= data_valid
.eq(p_i_valid |
237 (~self
.n
[muxid
].i_ready
& data_valid
))
241 m
.d
.comb
+= eq(r_data
, self
.p
.i_data
)
242 #m.d.comb += eq(self.n[muxid].o_data, self.process(r_data))
243 for i
in range(len(self
.n
)):
244 with m
.If(muxid
== i
):
245 m
.d
.comb
+= eq(self
.n
[i
].o_data
, self
.process(r_data
))
248 if self
.routemask
: # straight "routing" mode - treat like data
249 m
.d
.comb
+= self
.n
[muxid
].stop_o
.eq(self
.p
.stop_i
)
251 m
.d
.comb
+= self
.n
[muxid
].mask_o
.eq(self
.p
.mask_i
)
253 ml
= [] # accumulate output masks
254 ms
= [] # accumulate output stops
256 # conditionally fan-out mask bits, always fan-out stop bits
257 for i
in range(len(self
.n
)):
258 ml
.append(self
.n
[i
].mask_o
)
259 ms
.append(self
.n
[i
].stop_o
)
260 m
.d
.comb
+= Cat(*ms
).eq(self
.p
.stop_i
)
262 m
.d
.comb
+= Cat(*ml
).eq(self
.p
.mask_i
)
266 class CombMultiInPipeline(MultiInControlBase
):
267 """ A multi-input Combinatorial block conforming to the Pipeline API
271 p.i_data : StageInput, shaped according to ispec
273 p.o_data : StageOutput, shaped according to ospec
275 r_data : input_shape according to ispec
276 A temporary (buffered) copy of a prior (valid) input.
277 This is HELD if the output is not ready. It is updated
281 def __init__(self
, stage
, p_len
, p_mux
, maskwid
=0, routemask
=False):
282 MultiInControlBase
.__init
__(self
, p_len
=p_len
, maskwid
=maskwid
,
285 self
.maskwid
= maskwid
288 # set up the input and output data
289 for i
in range(p_len
):
290 name
= 'i_data_%d' % i
291 self
.p
[i
].i_data
= _spec(stage
.ispec
, name
) # input type
292 self
.n
.o_data
= _spec(stage
.ospec
, 'o_data')
294 def process(self
, i
):
295 if hasattr(self
.stage
, "process"):
296 return self
.stage
.process(i
)
299 def elaborate(self
, platform
):
300 m
= MultiInControlBase
.elaborate(self
, platform
)
302 m
.submodules
.p_mux
= self
.p_mux
304 # need an array of buffer registers conforming to *input* spec
310 for i
in range(p_len
):
312 r
= _spec(self
.stage
.ispec
, name
) # input type
314 data_valid
.append(Signal(name
="data_valid", reset_less
=True))
315 p_i_valid
.append(Signal(name
="p_i_valid", reset_less
=True))
316 n_i_readyn
.append(Signal(name
="n_i_readyn", reset_less
=True))
317 if hasattr(self
.stage
, "setup"):
318 print("setup", self
, self
.stage
, r
)
319 self
.stage
.setup(m
, r
)
320 if True: # len(r_data) > 1: # hmm always create an Array even of len 1
321 p_i_valid
= Array(p_i_valid
)
322 n_i_readyn
= Array(n_i_readyn
)
323 data_valid
= Array(data_valid
)
325 nirn
= Signal(reset_less
=True)
326 m
.d
.comb
+= nirn
.eq(~self
.n
.i_ready
)
327 mid
= self
.p_mux
.m_id
328 print("CombMuxIn mid", self
, self
.stage
, self
.routemask
, mid
, p_len
)
329 for i
in range(p_len
):
330 m
.d
.comb
+= data_valid
[i
].eq(0)
331 m
.d
.comb
+= n_i_readyn
[i
].eq(1)
332 m
.d
.comb
+= p_i_valid
[i
].eq(0)
333 #m.d.comb += self.p[i].o_ready.eq(~data_valid[i] | self.n.i_ready)
334 m
.d
.comb
+= self
.p
[i
].o_ready
.eq(0)
336 maskedout
= Signal(reset_less
=True)
337 if hasattr(p
, "mask_i"):
338 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
340 m
.d
.comb
+= maskedout
.eq(1)
341 m
.d
.comb
+= p_i_valid
[mid
].eq(maskedout
& self
.p_mux
.active
&
343 m
.d
.comb
+= self
.p
[mid
].o_ready
.eq(~data_valid
[mid
] | self
.n
.i_ready
)
344 m
.d
.comb
+= n_i_readyn
[mid
].eq(nirn
& data_valid
[mid
])
345 anyvalid
= Signal(i
, reset_less
=True)
347 for i
in range(p_len
):
348 av
.append(data_valid
[i
])
350 m
.d
.comb
+= self
.n
.o_valid
.eq(anyvalid
.bool())
351 m
.d
.comb
+= data_valid
[mid
].eq(p_i_valid
[mid
] |
355 # XXX hack - fixes loop
356 m
.d
.comb
+= eq(self
.n
.stop_o
, self
.p
[-1].stop_i
)
357 for i
in range(p_len
):
359 vr
= Signal(name
="vr%d" % i
, reset_less
=True)
360 maskedout
= Signal(name
="maskedout%d" % i
, reset_less
=True)
361 if hasattr(p
, "mask_i"):
362 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
364 m
.d
.comb
+= maskedout
.eq(1)
365 m
.d
.comb
+= vr
.eq(maskedout
.bool() & p
.i_valid
& p
.o_ready
)
366 #m.d.comb += vr.eq(p.i_valid & p.o_ready)
368 m
.d
.comb
+= eq(self
.n
.mask_o
, self
.p
[i
].mask_i
)
369 m
.d
.comb
+= eq(r_data
[i
], self
.process(self
.p
[i
].i_data
))
371 m
.d
.comb
+= eq(self
.n
.o_data
, r_data
[i
])
373 ml
= [] # accumulate output masks
374 ms
= [] # accumulate output stops
375 for i
in range(p_len
):
376 vr
= Signal(reset_less
=True)
378 vr
= Signal(reset_less
=True)
379 maskedout
= Signal(reset_less
=True)
380 if hasattr(p
, "mask_i"):
381 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
383 m
.d
.comb
+= maskedout
.eq(1)
384 m
.d
.comb
+= vr
.eq(maskedout
.bool() & p
.i_valid
& p
.o_ready
)
386 m
.d
.comb
+= eq(r_data
[i
], self
.process(self
.p
[i
].i_data
))
388 m
.d
.comb
+= eq(self
.n
.o_data
, r_data
[i
])
390 mlen
= len(self
.p
[i
].mask_i
)
393 ml
.append(Mux(vr
, self
.p
[i
].mask_i
, Const(0, mlen
)))
394 ms
.append(self
.p
[i
].stop_i
)
396 m
.d
.comb
+= self
.n
.mask_o
.eq(Cat(*ml
))
397 m
.d
.comb
+= self
.n
.stop_o
.eq(Cat(*ms
))
399 #print ("o_data", self.n.o_data, "r_data[mid]", mid, r_data[mid])
400 #m.d.comb += eq(self.n.o_data, r_data[mid])
405 class NonCombMultiInPipeline(MultiInControlBase
):
406 """ A multi-input pipeline block conforming to the Pipeline API
410 p.i_data : StageInput, shaped according to ispec
412 p.o_data : StageOutput, shaped according to ospec
414 r_data : input_shape according to ispec
415 A temporary (buffered) copy of a prior (valid) input.
416 This is HELD if the output is not ready. It is updated
420 def __init__(self
, stage
, p_len
, p_mux
, maskwid
=0, routemask
=False):
421 MultiInControlBase
.__init
__(self
, p_len
=p_len
, maskwid
=maskwid
,
424 self
.maskwid
= maskwid
427 # set up the input and output data
428 for i
in range(p_len
):
429 name
= 'i_data_%d' % i
430 self
.p
[i
].i_data
= _spec(stage
.ispec
, name
) # input type
431 self
.n
.o_data
= _spec(stage
.ospec
, 'o_data')
433 def process(self
, i
):
434 if hasattr(self
.stage
, "process"):
435 return self
.stage
.process(i
)
438 def elaborate(self
, platform
):
439 m
= MultiInControlBase
.elaborate(self
, platform
)
441 m
.submodules
.p_mux
= self
.p_mux
443 # need an array of buffer registers conforming to *input* spec
448 for i
in range(p_len
):
450 r
= _spec(self
.stage
.ispec
, name
) # input type
452 r_busy
.append(Signal(name
="r_busy%d" % i
, reset_less
=True))
453 p_i_valid
.append(Signal(name
="p_i_valid%d" % i
, reset_less
=True))
454 if hasattr(self
.stage
, "setup"):
455 print("setup", self
, self
.stage
, r
)
456 self
.stage
.setup(m
, r
)
458 r_data
= Array(r_data
)
459 p_i_valid
= Array(p_i_valid
)
460 r_busy
= Array(r_busy
)
462 nirn
= Signal(reset_less
=True)
463 m
.d
.comb
+= nirn
.eq(~self
.n
.i_ready
)
464 mid
= self
.p_mux
.m_id
465 print("CombMuxIn mid", self
, self
.stage
, self
.routemask
, mid
, p_len
)
466 for i
in range(p_len
):
467 m
.d
.comb
+= r_busy
[i
].eq(0)
468 m
.d
.comb
+= n_i_readyn
[i
].eq(1)
469 m
.d
.comb
+= p_i_valid
[i
].eq(0)
470 m
.d
.comb
+= self
.p
[i
].o_ready
.eq(n_i_readyn
[i
])
472 maskedout
= Signal(reset_less
=True)
473 if hasattr(p
, "mask_i"):
474 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
476 m
.d
.comb
+= maskedout
.eq(1)
477 m
.d
.comb
+= p_i_valid
[mid
].eq(maskedout
& self
.p_mux
.active
)
478 m
.d
.comb
+= self
.p
[mid
].o_ready
.eq(~data_valid
[mid
] | self
.n
.i_ready
)
479 m
.d
.comb
+= n_i_readyn
[mid
].eq(nirn
& data_valid
[mid
])
480 anyvalid
= Signal(i
, reset_less
=True)
482 for i
in range(p_len
):
483 av
.append(data_valid
[i
])
485 m
.d
.comb
+= self
.n
.o_valid
.eq(anyvalid
.bool())
486 m
.d
.comb
+= data_valid
[mid
].eq(p_i_valid
[mid
] |
490 # XXX hack - fixes loop
491 m
.d
.comb
+= eq(self
.n
.stop_o
, self
.p
[-1].stop_i
)
492 for i
in range(p_len
):
494 vr
= Signal(name
="vr%d" % i
, reset_less
=True)
495 maskedout
= Signal(name
="maskedout%d" % i
, reset_less
=True)
496 if hasattr(p
, "mask_i"):
497 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
499 m
.d
.comb
+= maskedout
.eq(1)
500 m
.d
.comb
+= vr
.eq(maskedout
.bool() & p
.i_valid
& p
.o_ready
)
501 #m.d.comb += vr.eq(p.i_valid & p.o_ready)
503 m
.d
.comb
+= eq(self
.n
.mask_o
, self
.p
[i
].mask_i
)
504 m
.d
.comb
+= eq(r_data
[i
], self
.p
[i
].i_data
)
506 ml
= [] # accumulate output masks
507 ms
= [] # accumulate output stops
508 for i
in range(p_len
):
509 vr
= Signal(reset_less
=True)
511 vr
= Signal(reset_less
=True)
512 maskedout
= Signal(reset_less
=True)
513 if hasattr(p
, "mask_i"):
514 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
516 m
.d
.comb
+= maskedout
.eq(1)
517 m
.d
.comb
+= vr
.eq(maskedout
.bool() & p
.i_valid
& p
.o_ready
)
519 m
.d
.comb
+= eq(r_data
[i
], self
.p
[i
].i_data
)
521 mlen
= len(self
.p
[i
].mask_i
)
524 ml
.append(Mux(vr
, self
.p
[i
].mask_i
, Const(0, mlen
)))
525 ms
.append(self
.p
[i
].stop_i
)
527 m
.d
.comb
+= self
.n
.mask_o
.eq(Cat(*ml
))
528 m
.d
.comb
+= self
.n
.stop_o
.eq(Cat(*ms
))
530 m
.d
.comb
+= eq(self
.n
.o_data
, self
.process(r_data
[mid
]))
535 class CombMuxOutPipe(CombMultiOutPipeline
):
536 def __init__(self
, stage
, n_len
, maskwid
=0, muxidname
=None,
538 muxidname
= muxidname
or "muxid"
539 # HACK: stage is also the n-way multiplexer
540 CombMultiOutPipeline
.__init
__(self
, stage
, n_len
=n_len
,
541 n_mux
=stage
, maskwid
=maskwid
,
544 # HACK: n-mux is also the stage... so set the muxid equal to input muxid
545 muxid
= getattr(self
.p
.i_data
, muxidname
)
546 print("combmuxout", muxidname
, muxid
)
550 class InputPriorityArbiter(Elaboratable
):
551 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
554 def __init__(self
, pipe
, num_rows
):
556 self
.num_rows
= num_rows
557 self
.mmax
= int(log(self
.num_rows
) / log(2))
558 self
.m_id
= Signal(self
.mmax
, reset_less
=True) # multiplex id
559 self
.active
= Signal(reset_less
=True)
561 def elaborate(self
, platform
):
564 assert len(self
.pipe
.p
) == self
.num_rows
, \
565 "must declare input to be same size"
566 pe
= PriorityEncoder(self
.num_rows
)
567 m
.submodules
.selector
= pe
569 # connect priority encoder
571 for i
in range(self
.num_rows
):
572 p_i_valid
= Signal(reset_less
=True)
573 if self
.pipe
.maskwid
and not self
.pipe
.routemask
:
575 maskedout
= Signal(reset_less
=True)
576 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
577 m
.d
.comb
+= p_i_valid
.eq(maskedout
.bool() & p
.i_valid_test
)
579 m
.d
.comb
+= p_i_valid
.eq(self
.pipe
.p
[i
].i_valid_test
)
580 in_ready
.append(p_i_valid
)
581 m
.d
.comb
+= pe
.i
.eq(Cat(*in_ready
)) # array of input "valids"
582 m
.d
.comb
+= self
.active
.eq(~pe
.n
) # encoder active (one input valid)
583 m
.d
.comb
+= self
.m_id
.eq(pe
.o
) # output one active input
588 return [self
.m_id
, self
.active
]
591 class PriorityCombMuxInPipe(CombMultiInPipeline
):
592 """ an example of how to use the combinatorial pipeline.
595 def __init__(self
, stage
, p_len
=2, maskwid
=0, routemask
=False):
596 p_mux
= InputPriorityArbiter(self
, p_len
)
597 CombMultiInPipeline
.__init
__(self
, stage
, p_len
, p_mux
,
598 maskwid
=maskwid
, routemask
=routemask
)
601 if __name__
== '__main__':
603 from nmutil
.test
.example_buf_pipe
import ExampleStage
604 dut
= PriorityCombMuxInPipe(ExampleStage
)
605 vl
= rtlil
.convert(dut
, ports
=dut
.ports())
606 with
open("test_combpipe.il", "w") as f
: