1 """ Combinatorial Multi-input and Multi-output multiplexer blocks
2 conforming to Pipeline API
4 This work is funded through NLnet under Grant 2019-02-012
8 Multi-input is complex because if any one input is ready, the output
9 can be ready, and the decision comes from a separate module.
11 Multi-output is simple (pretty much identical to UnbufferedPipeline),
12 and the selection is just a mux. The only proviso (difference) being:
13 the outputs not being selected have to have their ready_o signals
16 https://bugs.libre-soc.org/show_bug.cgi?id=538
20 from nmigen
import Signal
, Cat
, Const
, Mux
, Module
, Array
, Elaboratable
21 from nmigen
.cli
import verilog
, rtlil
22 from nmigen
.lib
.coding
import PriorityEncoder
23 from nmigen
.hdl
.rec
import Record
, Layout
24 from nmutil
.stageapi
import _spec
26 from collections
.abc
import Sequence
28 from nmutil
.nmoperator
import eq
29 from nmutil
.iocontrol
import NextControl
, PrevControl
32 class MultiInControlBase(Elaboratable
):
33 """ Common functions for Pipeline API
35 def __init__(self
, in_multi
=None, p_len
=1, maskwid
=0, routemask
=False):
36 """ Multi-input Control class. Conforms to same API as ControlBase...
37 mostly. has additional indices to the *multiple* input stages
39 * p: contains ready/valid to the previous stages PLURAL
40 * n: contains ready/valid to the next stage
43 * add data_i members to PrevControl and
44 * add data_o member to NextControl
46 self
.routemask
= routemask
47 # set up input and output IO ACK (prev/next ready/valid)
48 print ("multi_in", self
, maskwid
, p_len
, routemask
)
50 for i
in range(p_len
):
51 p
.append(PrevControl(in_multi
, maskwid
=maskwid
))
54 nmaskwid
= maskwid
# straight route mask mode
56 nmaskwid
= maskwid
* p_len
# fan-in mode
57 self
.n
= NextControl(maskwid
=nmaskwid
) # masks fan in (Cat)
59 def connect_to_next(self
, nxt
, p_idx
=0):
60 """ helper function to connect to the next stage data/valid/ready.
62 return self
.n
.connect_to_next(nxt
.p
[p_idx
])
64 def _connect_in(self
, prev
, idx
=0, prev_idx
=None):
65 """ helper function to connect stage to an input source. do not
66 use to connect stage-to-stage!
69 return self
.p
[idx
]._connect
_in
(prev
.p
)
70 return self
.p
[idx
]._connect
_in
(prev
.p
[prev_idx
])
72 def _connect_out(self
, nxt
):
73 """ helper function to connect stage to an output source. do not
74 use to connect stage-to-stage!
77 return self
.n
._connect
_out
(nxt
.n
)
78 return self
.n
._connect
_out
(nxt
.n
)
80 def set_input(self
, i
, idx
=0):
81 """ helper function to set the input data
83 return eq(self
.p
[idx
].data_i
, i
)
85 def elaborate(self
, platform
):
87 for i
, p
in enumerate(self
.p
):
88 setattr(m
.submodules
, "p%d" % i
, p
)
89 m
.submodules
.n
= self
.n
101 class MultiOutControlBase(Elaboratable
):
102 """ Common functions for Pipeline API
104 def __init__(self
, n_len
=1, in_multi
=None, maskwid
=0, routemask
=False):
105 """ Multi-output Control class. Conforms to same API as ControlBase...
106 mostly. has additional indices to the multiple *output* stages
107 [MultiInControlBase has multiple *input* stages]
109 * p: contains ready/valid to the previou stage
110 * n: contains ready/valid to the next stages PLURAL
113 * add data_i member to PrevControl and
114 * add data_o members to NextControl
118 nmaskwid
= maskwid
# straight route mask mode
120 nmaskwid
= maskwid
* n_len
# fan-out mode
122 # set up input and output IO ACK (prev/next ready/valid)
123 self
.p
= PrevControl(in_multi
, maskwid
=nmaskwid
)
125 for i
in range(n_len
):
126 n
.append(NextControl(maskwid
=maskwid
))
129 def connect_to_next(self
, nxt
, n_idx
=0):
130 """ helper function to connect to the next stage data/valid/ready.
132 return self
.n
[n_idx
].connect_to_next(nxt
.p
)
134 def _connect_in(self
, prev
, idx
=0):
135 """ helper function to connect stage to an input source. do not
136 use to connect stage-to-stage!
138 return self
.n
[idx
]._connect
_in
(prev
.p
)
140 def _connect_out(self
, nxt
, idx
=0, nxt_idx
=None):
141 """ helper function to connect stage to an output source. do not
142 use to connect stage-to-stage!
145 return self
.n
[idx
]._connect
_out
(nxt
.n
)
146 return self
.n
[idx
]._connect
_out
(nxt
.n
[nxt_idx
])
148 def elaborate(self
, platform
):
150 m
.submodules
.p
= self
.p
151 for i
, n
in enumerate(self
.n
):
152 setattr(m
.submodules
, "n%d" % i
, n
)
155 def set_input(self
, i
):
156 """ helper function to set the input data
158 return eq(self
.p
.data_i
, i
)
169 class CombMultiOutPipeline(MultiOutControlBase
):
170 """ A multi-input Combinatorial block conforming to the Pipeline API
174 p.data_i : stage input data (non-array). shaped according to ispec
175 n.data_o : stage output data array. shaped according to ospec
178 def __init__(self
, stage
, n_len
, n_mux
, maskwid
=0, routemask
=False):
179 MultiOutControlBase
.__init
__(self
, n_len
=n_len
, maskwid
=maskwid
,
182 self
.maskwid
= maskwid
183 self
.routemask
= routemask
186 # set up the input and output data
187 self
.p
.data_i
= _spec(stage
.ispec
, 'data_i') # input type
188 for i
in range(n_len
):
189 name
= 'data_o_%d' % i
190 self
.n
[i
].data_o
= _spec(stage
.ospec
, name
) # output type
192 def process(self
, i
):
193 if hasattr(self
.stage
, "process"):
194 return self
.stage
.process(i
)
197 def elaborate(self
, platform
):
198 m
= MultiOutControlBase
.elaborate(self
, platform
)
200 if hasattr(self
.n_mux
, "elaborate"): # TODO: identify submodule?
201 m
.submodules
.n_mux
= self
.n_mux
203 # need buffer register conforming to *input* spec
204 r_data
= _spec(self
.stage
.ispec
, 'r_data') # input type
205 if hasattr(self
.stage
, "setup"):
206 self
.stage
.setup(m
, r_data
)
208 # multiplexer id taken from n_mux
209 muxid
= self
.n_mux
.m_id
210 print ("self.n_mux", self
.n_mux
)
211 print ("self.n_mux.m_id", self
.n_mux
.m_id
)
213 self
.n_mux
.m_id
.name
= "m_id"
216 p_valid_i
= Signal(reset_less
=True)
217 pv
= Signal(reset_less
=True)
218 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
219 #m.d.comb += pv.eq(self.p.valid_i) #& self.n[muxid].ready_i)
220 m
.d
.comb
+= pv
.eq(self
.p
.valid_i
& self
.p
.ready_o
)
222 # all outputs to next stages first initialised to zero (invalid)
223 # the only output "active" is then selected by the muxid
224 for i
in range(len(self
.n
)):
225 m
.d
.comb
+= self
.n
[i
].valid_o
.eq(0)
228 m
.d
.comb
+= self
.n
[muxid
].valid_o
.eq(pv
)
229 m
.d
.comb
+= self
.p
.ready_o
.eq(self
.n
[muxid
].ready_i
)
231 data_valid
= self
.n
[muxid
].valid_o
232 m
.d
.comb
+= self
.p
.ready_o
.eq(~data_valid | self
.n
[muxid
].ready_i
)
233 m
.d
.comb
+= data_valid
.eq(p_valid_i | \
234 (~self
.n
[muxid
].ready_i
& data_valid
))
239 m
.d
.comb
+= eq(r_data
, self
.p
.data_i
)
240 m
.d
.comb
+= eq(self
.n
[muxid
].data_o
, self
.process(r_data
))
243 if self
.routemask
: # straight "routing" mode - treat like data
244 m
.d
.comb
+= self
.n
[muxid
].stop_o
.eq(self
.p
.stop_i
)
246 m
.d
.comb
+= self
.n
[muxid
].mask_o
.eq(self
.p
.mask_i
)
248 ml
= [] # accumulate output masks
249 ms
= [] # accumulate output stops
251 # conditionally fan-out mask bits, always fan-out stop bits
252 for i
in range(len(self
.n
)):
253 ml
.append(self
.n
[i
].mask_o
)
254 ms
.append(self
.n
[i
].stop_o
)
255 m
.d
.comb
+= Cat(*ms
).eq(self
.p
.stop_i
)
257 m
.d
.comb
+= Cat(*ml
).eq(self
.p
.mask_i
)
261 class CombMultiInPipeline(MultiInControlBase
):
262 """ A multi-input Combinatorial block conforming to the Pipeline API
266 p.data_i : StageInput, shaped according to ispec
268 p.data_o : StageOutput, shaped according to ospec
270 r_data : input_shape according to ispec
271 A temporary (buffered) copy of a prior (valid) input.
272 This is HELD if the output is not ready. It is updated
276 def __init__(self
, stage
, p_len
, p_mux
, maskwid
=0, routemask
=False):
277 MultiInControlBase
.__init
__(self
, p_len
=p_len
, maskwid
=maskwid
,
280 self
.maskwid
= maskwid
283 # set up the input and output data
284 for i
in range(p_len
):
285 name
= 'data_i_%d' % i
286 self
.p
[i
].data_i
= _spec(stage
.ispec
, name
) # input type
287 self
.n
.data_o
= _spec(stage
.ospec
, 'data_o')
289 def process(self
, i
):
290 if hasattr(self
.stage
, "process"):
291 return self
.stage
.process(i
)
294 def elaborate(self
, platform
):
295 m
= MultiInControlBase
.elaborate(self
, platform
)
297 m
.submodules
.p_mux
= self
.p_mux
299 # need an array of buffer registers conforming to *input* spec
305 for i
in range(p_len
):
307 r
= _spec(self
.stage
.ispec
, name
) # input type
309 data_valid
.append(Signal(name
="data_valid", reset_less
=True))
310 p_valid_i
.append(Signal(name
="p_valid_i", reset_less
=True))
311 n_ready_in
.append(Signal(name
="n_ready_in", reset_less
=True))
312 if hasattr(self
.stage
, "setup"):
313 print ("setup", self
, self
.stage
, r
)
314 self
.stage
.setup(m
, r
)
316 r_data
= Array(r_data
)
317 p_valid_i
= Array(p_valid_i
)
318 n_ready_in
= Array(n_ready_in
)
319 data_valid
= Array(data_valid
)
321 nirn
= Signal(reset_less
=True)
322 m
.d
.comb
+= nirn
.eq(~self
.n
.ready_i
)
323 mid
= self
.p_mux
.m_id
324 print ("CombMuxIn mid", self
, self
.stage
, self
.routemask
, mid
, p_len
)
325 for i
in range(p_len
):
326 m
.d
.comb
+= data_valid
[i
].eq(0)
327 m
.d
.comb
+= n_ready_in
[i
].eq(1)
328 m
.d
.comb
+= p_valid_i
[i
].eq(0)
329 #m.d.comb += self.p[i].ready_o.eq(~data_valid[i] | self.n.ready_i)
330 m
.d
.comb
+= self
.p
[i
].ready_o
.eq(0)
332 maskedout
= Signal(reset_less
=True)
333 if hasattr(p
, "mask_i"):
334 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
336 m
.d
.comb
+= maskedout
.eq(1)
337 m
.d
.comb
+= p_valid_i
[mid
].eq(maskedout
& self
.p_mux
.active
)
338 m
.d
.comb
+= self
.p
[mid
].ready_o
.eq(~data_valid
[mid
] | self
.n
.ready_i
)
339 m
.d
.comb
+= n_ready_in
[mid
].eq(nirn
& data_valid
[mid
])
340 anyvalid
= Signal(i
, reset_less
=True)
342 for i
in range(p_len
):
343 av
.append(data_valid
[i
])
345 m
.d
.comb
+= self
.n
.valid_o
.eq(anyvalid
.bool())
346 m
.d
.comb
+= data_valid
[mid
].eq(p_valid_i
[mid
] | \
350 # XXX hack - fixes loop
351 m
.d
.comb
+= eq(self
.n
.stop_o
, self
.p
[-1].stop_i
)
352 for i
in range(p_len
):
354 vr
= Signal(name
="vr%d" % i
, reset_less
=True)
355 maskedout
= Signal(name
="maskedout%d" % i
, reset_less
=True)
356 if hasattr(p
, "mask_i"):
357 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
359 m
.d
.comb
+= maskedout
.eq(1)
360 m
.d
.comb
+= vr
.eq(maskedout
.bool() & p
.valid_i
& p
.ready_o
)
361 #m.d.comb += vr.eq(p.valid_i & p.ready_o)
363 m
.d
.comb
+= eq(self
.n
.mask_o
, self
.p
[i
].mask_i
)
364 m
.d
.comb
+= eq(r_data
[i
], self
.p
[i
].data_i
)
366 ml
= [] # accumulate output masks
367 ms
= [] # accumulate output stops
368 for i
in range(p_len
):
369 vr
= Signal(reset_less
=True)
371 vr
= Signal(reset_less
=True)
372 maskedout
= Signal(reset_less
=True)
373 if hasattr(p
, "mask_i"):
374 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
376 m
.d
.comb
+= maskedout
.eq(1)
377 m
.d
.comb
+= vr
.eq(maskedout
.bool() & p
.valid_i
& p
.ready_o
)
379 m
.d
.comb
+= eq(r_data
[i
], self
.p
[i
].data_i
)
381 mlen
= len(self
.p
[i
].mask_i
)
384 ml
.append(Mux(vr
, self
.p
[i
].mask_i
, Const(0, mlen
)))
385 ms
.append(self
.p
[i
].stop_i
)
387 m
.d
.comb
+= self
.n
.mask_o
.eq(Cat(*ml
))
388 m
.d
.comb
+= self
.n
.stop_o
.eq(Cat(*ms
))
390 m
.d
.comb
+= eq(self
.n
.data_o
, self
.process(r_data
[mid
]))
395 class NonCombMultiInPipeline(MultiInControlBase
):
396 """ A multi-input pipeline block conforming to the Pipeline API
400 p.data_i : StageInput, shaped according to ispec
402 p.data_o : StageOutput, shaped according to ospec
404 r_data : input_shape according to ispec
405 A temporary (buffered) copy of a prior (valid) input.
406 This is HELD if the output is not ready. It is updated
410 def __init__(self
, stage
, p_len
, p_mux
, maskwid
=0, routemask
=False):
411 MultiInControlBase
.__init
__(self
, p_len
=p_len
, maskwid
=maskwid
,
414 self
.maskwid
= maskwid
417 # set up the input and output data
418 for i
in range(p_len
):
419 name
= 'data_i_%d' % i
420 self
.p
[i
].data_i
= _spec(stage
.ispec
, name
) # input type
421 self
.n
.data_o
= _spec(stage
.ospec
, 'data_o')
423 def process(self
, i
):
424 if hasattr(self
.stage
, "process"):
425 return self
.stage
.process(i
)
428 def elaborate(self
, platform
):
429 m
= MultiInControlBase
.elaborate(self
, platform
)
431 m
.submodules
.p_mux
= self
.p_mux
433 # need an array of buffer registers conforming to *input* spec
438 for i
in range(p_len
):
440 r
= _spec(self
.stage
.ispec
, name
) # input type
442 r_busy
.append(Signal(name
="r_busy%d" % i
, reset_less
=True))
443 p_valid_i
.append(Signal(name
="p_valid_i%d" % i
, reset_less
=True))
444 if hasattr(self
.stage
, "setup"):
445 print ("setup", self
, self
.stage
, r
)
446 self
.stage
.setup(m
, r
)
448 r_data
= Array(r_data
)
449 p_valid_i
= Array(p_valid_i
)
450 r_busy
= Array(r_busy
)
452 nirn
= Signal(reset_less
=True)
453 m
.d
.comb
+= nirn
.eq(~self
.n
.ready_i
)
454 mid
= self
.p_mux
.m_id
455 print ("CombMuxIn mid", self
, self
.stage
, self
.routemask
, mid
, p_len
)
456 for i
in range(p_len
):
457 m
.d
.comb
+= r_busy
[i
].eq(0)
458 m
.d
.comb
+= n_ready_in
[i
].eq(1)
459 m
.d
.comb
+= p_valid_i
[i
].eq(0)
460 m
.d
.comb
+= self
.p
[i
].ready_o
.eq(n_ready_in
[i
])
462 maskedout
= Signal(reset_less
=True)
463 if hasattr(p
, "mask_i"):
464 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
466 m
.d
.comb
+= maskedout
.eq(1)
467 m
.d
.comb
+= p_valid_i
[mid
].eq(maskedout
& self
.p_mux
.active
)
468 m
.d
.comb
+= self
.p
[mid
].ready_o
.eq(~data_valid
[mid
] | self
.n
.ready_i
)
469 m
.d
.comb
+= n_ready_in
[mid
].eq(nirn
& data_valid
[mid
])
470 anyvalid
= Signal(i
, reset_less
=True)
472 for i
in range(p_len
):
473 av
.append(data_valid
[i
])
475 m
.d
.comb
+= self
.n
.valid_o
.eq(anyvalid
.bool())
476 m
.d
.comb
+= data_valid
[mid
].eq(p_valid_i
[mid
] | \
480 # XXX hack - fixes loop
481 m
.d
.comb
+= eq(self
.n
.stop_o
, self
.p
[-1].stop_i
)
482 for i
in range(p_len
):
484 vr
= Signal(name
="vr%d" % i
, reset_less
=True)
485 maskedout
= Signal(name
="maskedout%d" % i
, reset_less
=True)
486 if hasattr(p
, "mask_i"):
487 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
489 m
.d
.comb
+= maskedout
.eq(1)
490 m
.d
.comb
+= vr
.eq(maskedout
.bool() & p
.valid_i
& p
.ready_o
)
491 #m.d.comb += vr.eq(p.valid_i & p.ready_o)
493 m
.d
.comb
+= eq(self
.n
.mask_o
, self
.p
[i
].mask_i
)
494 m
.d
.comb
+= eq(r_data
[i
], self
.p
[i
].data_i
)
496 ml
= [] # accumulate output masks
497 ms
= [] # accumulate output stops
498 for i
in range(p_len
):
499 vr
= Signal(reset_less
=True)
501 vr
= Signal(reset_less
=True)
502 maskedout
= Signal(reset_less
=True)
503 if hasattr(p
, "mask_i"):
504 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
506 m
.d
.comb
+= maskedout
.eq(1)
507 m
.d
.comb
+= vr
.eq(maskedout
.bool() & p
.valid_i
& p
.ready_o
)
509 m
.d
.comb
+= eq(r_data
[i
], self
.p
[i
].data_i
)
511 mlen
= len(self
.p
[i
].mask_i
)
514 ml
.append(Mux(vr
, self
.p
[i
].mask_i
, Const(0, mlen
)))
515 ms
.append(self
.p
[i
].stop_i
)
517 m
.d
.comb
+= self
.n
.mask_o
.eq(Cat(*ml
))
518 m
.d
.comb
+= self
.n
.stop_o
.eq(Cat(*ms
))
520 m
.d
.comb
+= eq(self
.n
.data_o
, self
.process(r_data
[mid
]))
525 class CombMuxOutPipe(CombMultiOutPipeline
):
526 def __init__(self
, stage
, n_len
, maskwid
=0, muxidname
=None,
528 muxidname
= muxidname
or "muxid"
529 # HACK: stage is also the n-way multiplexer
530 CombMultiOutPipeline
.__init
__(self
, stage
, n_len
=n_len
,
531 n_mux
=stage
, maskwid
=maskwid
,
534 # HACK: n-mux is also the stage... so set the muxid equal to input muxid
535 muxid
= getattr(self
.p
.data_i
, muxidname
)
536 print ("combmuxout", muxidname
, muxid
)
541 class InputPriorityArbiter(Elaboratable
):
542 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
544 def __init__(self
, pipe
, num_rows
):
546 self
.num_rows
= num_rows
547 self
.mmax
= int(log(self
.num_rows
) / log(2))
548 self
.m_id
= Signal(self
.mmax
, reset_less
=True) # multiplex id
549 self
.active
= Signal(reset_less
=True)
551 def elaborate(self
, platform
):
554 assert len(self
.pipe
.p
) == self
.num_rows
, \
555 "must declare input to be same size"
556 pe
= PriorityEncoder(self
.num_rows
)
557 m
.submodules
.selector
= pe
559 # connect priority encoder
561 for i
in range(self
.num_rows
):
562 p_valid_i
= Signal(reset_less
=True)
563 if self
.pipe
.maskwid
and not self
.pipe
.routemask
:
565 maskedout
= Signal(reset_less
=True)
566 m
.d
.comb
+= maskedout
.eq(p
.mask_i
& ~p
.stop_i
)
567 m
.d
.comb
+= p_valid_i
.eq(maskedout
.bool() & p
.valid_i_test
)
569 m
.d
.comb
+= p_valid_i
.eq(self
.pipe
.p
[i
].valid_i_test
)
570 in_ready
.append(p_valid_i
)
571 m
.d
.comb
+= pe
.i
.eq(Cat(*in_ready
)) # array of input "valids"
572 m
.d
.comb
+= self
.active
.eq(~pe
.n
) # encoder active (one input valid)
573 m
.d
.comb
+= self
.m_id
.eq(pe
.o
) # output one active input
578 return [self
.m_id
, self
.active
]
582 class PriorityCombMuxInPipe(CombMultiInPipeline
):
583 """ an example of how to use the combinatorial pipeline.
586 def __init__(self
, stage
, p_len
=2, maskwid
=0, routemask
=False):
587 p_mux
= InputPriorityArbiter(self
, p_len
)
588 CombMultiInPipeline
.__init
__(self
, stage
, p_len
, p_mux
,
589 maskwid
=maskwid
, routemask
=routemask
)
592 if __name__
== '__main__':
594 from nmutil
.test
.example_buf_pipe
import ExampleStage
595 dut
= PriorityCombMuxInPipe(ExampleStage
)
596 vl
= rtlil
.convert(dut
, ports
=dut
.ports())
597 with
open("test_combpipe.il", "w") as f
: