1 """ Combinatorial Multi-input and Multi-output multiplexer blocks
2 conforming to Pipeline API
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.
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 o_ready signals
14 from nmigen
import Signal
, Cat
, Const
, Mux
, Module
, Array
15 from nmigen
.cli
import verilog
, rtlil
16 from nmigen
.lib
.coding
import PriorityEncoder
17 from nmigen
.hdl
.rec
import Record
, Layout
19 from collections
.abc
import Sequence
21 from example_buf_pipe
import eq
, NextControl
, PrevControl
, ExampleStage
24 class MultiInControlBase
:
25 """ Common functions for Pipeline API
27 def __init__(self
, in_multi
=None, p_len
=1):
28 """ Multi-input Control class. Conforms to same API as ControlBase...
29 mostly. has additional indices to the *multiple* input stages
31 * p: contains ready/valid to the previous stages PLURAL
32 * n: contains ready/valid to the next stage
35 * add i_data members to PrevControl and
36 * add o_data member to NextControl
38 # set up input and output IO ACK (prev/next ready/valid)
40 for i
in range(p_len
):
41 p
.append(PrevControl(in_multi
))
43 self
.n
= NextControl()
45 def connect_to_next(self
, nxt
, p_idx
=0):
46 """ helper function to connect to the next stage data/valid/ready.
48 return self
.n
.connect_to_next(nxt
.p
[p_idx
])
50 def _connect_in(self
, prev
, idx
=0, prev_idx
=None):
51 """ helper function to connect stage to an input source. do not
52 use to connect stage-to-stage!
55 return self
.p
[idx
]._connect
_in
(prev
.p
)
56 return self
.p
[idx
]._connect
_in
(prev
.p
[prev_idx
])
58 def _connect_out(self
, nxt
):
59 """ helper function to connect stage to an output source. do not
60 use to connect stage-to-stage!
63 return self
.n
._connect
_out
(nxt
.n
)
64 return self
.n
._connect
_out
(nxt
.n
)
66 def set_input(self
, i
, idx
=0):
67 """ helper function to set the input data
69 return eq(self
.p
[idx
].i_data
, i
)
73 for i
in range(len(self
.p
)):
75 res
+= [p
.i_valid
, p
.o_ready
]
76 if hasattr(p
.i_data
, "ports"):
77 res
+= p
.i_data
.ports()
80 if not isinstance(rres
, Sequence
):
84 res
+= [n
.i_ready
, n
.o_valid
]
85 if hasattr(n
.o_data
, "ports"):
86 res
+= n
.o_data
.ports()
89 if not isinstance(rres
, Sequence
):
95 class MultiOutControlBase
:
96 """ Common functions for Pipeline API
98 def __init__(self
, n_len
=1, in_multi
=None):
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]
103 * p: contains ready/valid to the previou stage
104 * n: contains ready/valid to the next stages PLURAL
107 * add i_data member to PrevControl and
108 * add o_data members to NextControl
111 # set up input and output IO ACK (prev/next ready/valid)
112 self
.p
= PrevControl(in_multi
)
114 for i
in range(n_len
):
115 n
.append(NextControl())
118 def connect_to_next(self
, nxt
, n_idx
=0):
119 """ helper function to connect to the next stage data/valid/ready.
121 return self
.n
[n_idx
].connect_to_next(nxt
.p
)
123 def _connect_in(self
, prev
, idx
=0):
124 """ helper function to connect stage to an input source. do not
125 use to connect stage-to-stage!
127 return self
.n
[idx
]._connect
_in
(prev
.p
)
129 def _connect_out(self
, nxt
, idx
=0, nxt_idx
=None):
130 """ helper function to connect stage to an output source. do not
131 use to connect stage-to-stage!
134 return self
.n
[idx
]._connect
_out
(nxt
.n
)
135 return self
.n
[idx
]._connect
_out
(nxt
.n
[nxt_idx
])
137 def set_input(self
, i
):
138 """ helper function to set the input data
140 return eq(self
.p
.i_data
, i
)
143 res
= [self
.p
.i_valid
, self
.p
.o_ready
]
144 if hasattr(self
.p
.i_data
, "ports"):
145 res
+= self
.p
.i_data
.ports()
149 for i
in range(len(self
.n
)):
151 res
+= [n
.i_ready
, n
.o_valid
]
152 if hasattr(n
.o_data
, "ports"):
153 res
+= n
.o_data
.ports()
159 class CombMultiOutPipeline(MultiOutControlBase
):
160 """ A multi-input Combinatorial block conforming to the Pipeline API
164 p.i_data : stage input data (non-array). shaped according to ispec
165 n.o_data : stage output data array. shaped according to ospec
168 def __init__(self
, stage
, n_len
, n_mux
):
169 MultiOutControlBase
.__init
__(self
, n_len
=n_len
)
173 # set up the input and output data
174 self
.p
.i_data
= stage
.ispec() # input type
175 for i
in range(n_len
):
176 self
.n
[i
].o_data
= stage
.ospec() # output type
178 def elaborate(self
, platform
):
181 if hasattr(self
.n_mux
, "elaborate"): # TODO: identify submodule?
182 m
.submodules
+= self
.n_mux
184 # need buffer register conforming to *input* spec
185 r_data
= self
.stage
.ispec() # input type
186 if hasattr(self
.stage
, "setup"):
187 self
.stage
.setup(m
, r_data
)
189 # multiplexer id taken from n_mux
190 mid
= self
.n_mux
.m_id
193 p_i_valid
= Signal(reset_less
=True)
194 pv
= Signal(reset_less
=True)
195 m
.d
.comb
+= p_i_valid
.eq(self
.p
.i_valid_test
)
196 m
.d
.comb
+= pv
.eq(self
.p
.i_valid
& self
.p
.o_ready
)
198 # all outputs to next stages first initialised to zero (invalid)
199 # the only output "active" is then selected by the muxid
200 for i
in range(len(self
.n
)):
201 m
.d
.comb
+= self
.n
[i
].o_valid
.eq(0)
202 data_valid
= self
.n
[mid
].o_valid
203 m
.d
.comb
+= self
.p
.o_ready
.eq(~data_valid | self
.n
[mid
].i_ready
)
204 m
.d
.comb
+= data_valid
.eq(p_i_valid | \
205 (~self
.n
[mid
].i_ready
& data_valid
))
207 m
.d
.comb
+= eq(r_data
, self
.p
.i_data
)
208 m
.d
.comb
+= eq(self
.n
[mid
].o_data
, self
.stage
.process(r_data
))
213 class CombMultiInPipeline(MultiInControlBase
):
214 """ A multi-input Combinatorial block conforming to the Pipeline API
218 p.i_data : StageInput, shaped according to ispec
220 p.o_data : StageOutput, shaped according to ospec
222 r_data : input_shape according to ispec
223 A temporary (buffered) copy of a prior (valid) input.
224 This is HELD if the output is not ready. It is updated
228 def __init__(self
, stage
, p_len
, p_mux
):
229 MultiInControlBase
.__init
__(self
, p_len
=p_len
)
233 # set up the input and output data
234 for i
in range(p_len
):
235 self
.p
[i
].i_data
= stage
.ispec() # input type
236 self
.n
.o_data
= stage
.ospec()
238 def elaborate(self
, platform
):
241 m
.submodules
+= self
.p_mux
243 # need an array of buffer registers conforming to *input* spec
249 for i
in range(p_len
):
250 r
= self
.stage
.ispec() # input type
252 data_valid
.append(Signal(name
="data_valid", reset_less
=True))
253 p_i_valid
.append(Signal(name
="p_i_valid", reset_less
=True))
254 n_i_readyn
.append(Signal(name
="n_i_readyn", reset_less
=True))
255 if hasattr(self
.stage
, "setup"):
256 self
.stage
.setup(m
, r
)
258 r_data
= Array(r_data
)
259 p_i_valid
= Array(p_i_valid
)
260 n_i_readyn
= Array(n_i_readyn
)
261 data_valid
= Array(data_valid
)
263 nirn
= Signal(reset_less
=True)
264 m
.d
.comb
+= nirn
.eq(~self
.n
.i_ready
)
265 mid
= self
.p_mux
.m_id
266 for i
in range(p_len
):
267 m
.d
.comb
+= data_valid
[i
].eq(0)
268 m
.d
.comb
+= n_i_readyn
[i
].eq(1)
269 m
.d
.comb
+= p_i_valid
[i
].eq(0)
270 m
.d
.comb
+= self
.p
[i
].o_ready
.eq(0)
271 m
.d
.comb
+= p_i_valid
[mid
].eq(self
.p_mux
.active
)
272 m
.d
.comb
+= self
.p
[mid
].o_ready
.eq(~data_valid
[mid
] | self
.n
.i_ready
)
273 m
.d
.comb
+= n_i_readyn
[mid
].eq(nirn
& data_valid
[mid
])
274 anyvalid
= Signal(i
, reset_less
=True)
276 for i
in range(p_len
):
277 av
.append(data_valid
[i
])
279 m
.d
.comb
+= self
.n
.o_valid
.eq(anyvalid
.bool())
280 m
.d
.comb
+= data_valid
[mid
].eq(p_i_valid
[mid
] | \
281 (n_i_readyn
[mid
] & data_valid
[mid
]))
283 for i
in range(p_len
):
284 vr
= Signal(reset_less
=True)
285 m
.d
.comb
+= vr
.eq(self
.p
[i
].i_valid
& self
.p
[i
].o_ready
)
287 m
.d
.comb
+= eq(r_data
[i
], self
.p
[i
].i_data
)
289 m
.d
.comb
+= eq(self
.n
.o_data
, self
.stage
.process(r_data
[mid
]))
294 class CombMuxOutPipe(CombMultiOutPipeline
):
295 def __init__(self
, stage
, n_len
):
296 # HACK: stage is also the n-way multiplexer
297 CombMultiOutPipeline
.__init
__(self
, stage
, n_len
=n_len
, n_mux
=stage
)
299 # HACK: n-mux is also the stage... so set the muxid equal to input mid
300 stage
.m_id
= self
.p
.i_data
.mid
304 class InputPriorityArbiter
:
305 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
307 def __init__(self
, pipe
, num_rows
):
309 self
.num_rows
= num_rows
310 self
.mmax
= int(log(self
.num_rows
) / log(2))
311 self
.m_id
= Signal(self
.mmax
, reset_less
=True) # multiplex id
312 self
.active
= Signal(reset_less
=True)
314 def elaborate(self
, platform
):
317 assert len(self
.pipe
.p
) == self
.num_rows
, \
318 "must declare input to be same size"
319 pe
= PriorityEncoder(self
.num_rows
)
320 m
.submodules
.selector
= pe
322 # connect priority encoder
324 for i
in range(self
.num_rows
):
325 p_i_valid
= Signal(reset_less
=True)
326 m
.d
.comb
+= p_i_valid
.eq(self
.pipe
.p
[i
].i_valid_test
)
327 in_ready
.append(p_i_valid
)
328 m
.d
.comb
+= pe
.i
.eq(Cat(*in_ready
)) # array of input "valids"
329 m
.d
.comb
+= self
.active
.eq(~pe
.n
) # encoder active (one input valid)
330 m
.d
.comb
+= self
.m_id
.eq(pe
.o
) # output one active input
335 return [self
.m_id
, self
.active
]
339 class PriorityCombMuxInPipe(CombMultiInPipeline
):
340 """ an example of how to use the combinatorial pipeline.
343 def __init__(self
, stage
, p_len
=2):
344 p_mux
= InputPriorityArbiter(self
, p_len
)
345 CombMultiInPipeline
.__init
__(self
, stage
, p_len
, p_mux
)
348 if __name__
== '__main__':
350 dut
= PriorityCombMuxInPipe(ExampleStage
)
351 vl
= rtlil
.convert(dut
, ports
=dut
.ports())
352 with
open("test_combpipe.il", "w") as f
: