763091679b1d7ccb6e08395adb3e014667e3f4df
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 ready_o signals
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 iocontrol
import _spec
20 from collections
.abc
import Sequence
22 from example_buf_pipe
import eq
, NextControl
, PrevControl
, ExampleStage
25 class MultiInControlBase(Elaboratable
):
26 """ Common functions for Pipeline API
28 def __init__(self
, in_multi
=None, p_len
=1):
29 """ Multi-input Control class. Conforms to same API as ControlBase...
30 mostly. has additional indices to the *multiple* input stages
32 * p: contains ready/valid to the previous stages PLURAL
33 * n: contains ready/valid to the next stage
36 * add data_i members to PrevControl and
37 * add data_o member to NextControl
39 # set up input and output IO ACK (prev/next ready/valid)
41 for i
in range(p_len
):
42 p
.append(PrevControl(in_multi
))
44 self
.n
= NextControl()
46 def connect_to_next(self
, nxt
, p_idx
=0):
47 """ helper function to connect to the next stage data/valid/ready.
49 return self
.n
.connect_to_next(nxt
.p
[p_idx
])
51 def _connect_in(self
, prev
, idx
=0, prev_idx
=None):
52 """ helper function to connect stage to an input source. do not
53 use to connect stage-to-stage!
56 return self
.p
[idx
]._connect
_in
(prev
.p
)
57 return self
.p
[idx
]._connect
_in
(prev
.p
[prev_idx
])
59 def _connect_out(self
, nxt
):
60 """ helper function to connect stage to an output source. do not
61 use to connect stage-to-stage!
64 return self
.n
._connect
_out
(nxt
.n
)
65 return self
.n
._connect
_out
(nxt
.n
)
67 def set_input(self
, i
, idx
=0):
68 """ helper function to set the input data
70 return eq(self
.p
[idx
].data_i
, i
)
72 def elaborate(self
, platform
):
74 for i
, p
in enumerate(self
.p
):
75 setattr(m
.submodules
, "p%d" % i
, p
)
76 m
.submodules
.n
= self
.n
88 class MultiOutControlBase(Elaboratable
):
89 """ Common functions for Pipeline API
91 def __init__(self
, n_len
=1, in_multi
=None):
92 """ Multi-output Control class. Conforms to same API as ControlBase...
93 mostly. has additional indices to the multiple *output* stages
94 [MultiInControlBase has multiple *input* stages]
96 * p: contains ready/valid to the previou stage
97 * n: contains ready/valid to the next stages PLURAL
100 * add data_i member to PrevControl and
101 * add data_o members to NextControl
104 # set up input and output IO ACK (prev/next ready/valid)
105 self
.p
= PrevControl(in_multi
)
107 for i
in range(n_len
):
108 n
.append(NextControl())
111 def connect_to_next(self
, nxt
, n_idx
=0):
112 """ helper function to connect to the next stage data/valid/ready.
114 return self
.n
[n_idx
].connect_to_next(nxt
.p
)
116 def _connect_in(self
, prev
, idx
=0):
117 """ helper function to connect stage to an input source. do not
118 use to connect stage-to-stage!
120 return self
.n
[idx
]._connect
_in
(prev
.p
)
122 def _connect_out(self
, nxt
, idx
=0, nxt_idx
=None):
123 """ helper function to connect stage to an output source. do not
124 use to connect stage-to-stage!
127 return self
.n
[idx
]._connect
_out
(nxt
.n
)
128 return self
.n
[idx
]._connect
_out
(nxt
.n
[nxt_idx
])
130 def elaborate(self
, platform
):
132 m
.submodules
.p
= self
.p
133 for i
, n
in enumerate(self
.n
):
134 setattr(m
.submodules
, "n%d" % i
, n
)
137 def set_input(self
, i
):
138 """ helper function to set the input data
140 return eq(self
.p
.data_i
, i
)
151 class CombMultiOutPipeline(MultiOutControlBase
):
152 """ A multi-input Combinatorial block conforming to the Pipeline API
156 p.data_i : stage input data (non-array). shaped according to ispec
157 n.data_o : stage output data array. shaped according to ospec
160 def __init__(self
, stage
, n_len
, n_mux
):
161 MultiOutControlBase
.__init
__(self
, n_len
=n_len
)
165 # set up the input and output data
166 self
.p
.data_i
= _spec(stage
.ispec
, 'data_i') # input type
167 for i
in range(n_len
):
168 name
= 'data_o_%d' % i
169 self
.n
[i
].data_o
= _spec(stage
.ospec
, name
) # output type
171 def elaborate(self
, platform
):
172 m
= MultiOutControlBase
.elaborate(self
, platform
)
174 if hasattr(self
.n_mux
, "elaborate"): # TODO: identify submodule?
175 m
.submodules
+= self
.n_mux
177 # need buffer register conforming to *input* spec
178 r_data
= _spec(self
.stage
.ispec
, 'r_data') # input type
179 if hasattr(self
.stage
, "setup"):
180 self
.stage
.setup(m
, r_data
)
182 # multiplexer id taken from n_mux
183 mid
= self
.n_mux
.m_id
186 p_valid_i
= Signal(reset_less
=True)
187 pv
= Signal(reset_less
=True)
188 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
189 m
.d
.comb
+= pv
.eq(self
.p
.valid_i
& self
.p
.ready_o
)
191 # all outputs to next stages first initialised to zero (invalid)
192 # the only output "active" is then selected by the muxid
193 for i
in range(len(self
.n
)):
194 m
.d
.comb
+= self
.n
[i
].valid_o
.eq(0)
195 data_valid
= self
.n
[mid
].valid_o
196 m
.d
.comb
+= self
.p
.ready_o
.eq(~data_valid | self
.n
[mid
].ready_i
)
197 m
.d
.comb
+= data_valid
.eq(p_valid_i | \
198 (~self
.n
[mid
].ready_i
& data_valid
))
200 m
.d
.comb
+= eq(r_data
, self
.p
.data_i
)
201 m
.d
.comb
+= eq(self
.n
[mid
].data_o
, self
.stage
.process(r_data
))
206 class CombMultiInPipeline(MultiInControlBase
):
207 """ A multi-input Combinatorial block conforming to the Pipeline API
211 p.data_i : StageInput, shaped according to ispec
213 p.data_o : StageOutput, shaped according to ospec
215 r_data : input_shape according to ispec
216 A temporary (buffered) copy of a prior (valid) input.
217 This is HELD if the output is not ready. It is updated
221 def __init__(self
, stage
, p_len
, p_mux
):
222 MultiInControlBase
.__init
__(self
, p_len
=p_len
)
226 # set up the input and output data
227 for i
in range(p_len
):
228 name
= 'data_i_%d' % i
229 self
.p
[i
].data_i
= _spec(stage
.ispec
, name
) # input type
230 self
.n
.data_o
= _spec(stage
.ospec
, 'data_o')
232 def elaborate(self
, platform
):
233 m
= MultiInControlBase
.elaborate(self
, platform
)
235 m
.submodules
+= self
.p_mux
237 # need an array of buffer registers conforming to *input* spec
243 for i
in range(p_len
):
245 r
= _spec(self
.stage
.ispec
, name
) # input type
247 data_valid
.append(Signal(name
="data_valid", reset_less
=True))
248 p_valid_i
.append(Signal(name
="p_valid_i", reset_less
=True))
249 n_ready_in
.append(Signal(name
="n_ready_in", reset_less
=True))
250 if hasattr(self
.stage
, "setup"):
251 self
.stage
.setup(m
, r
)
253 r_data
= Array(r_data
)
254 p_valid_i
= Array(p_valid_i
)
255 n_ready_in
= Array(n_ready_in
)
256 data_valid
= Array(data_valid
)
258 nirn
= Signal(reset_less
=True)
259 m
.d
.comb
+= nirn
.eq(~self
.n
.ready_i
)
260 mid
= self
.p_mux
.m_id
261 for i
in range(p_len
):
262 m
.d
.comb
+= data_valid
[i
].eq(0)
263 m
.d
.comb
+= n_ready_in
[i
].eq(1)
264 m
.d
.comb
+= p_valid_i
[i
].eq(0)
265 m
.d
.comb
+= self
.p
[i
].ready_o
.eq(0)
266 m
.d
.comb
+= p_valid_i
[mid
].eq(self
.p_mux
.active
)
267 m
.d
.comb
+= self
.p
[mid
].ready_o
.eq(~data_valid
[mid
] | self
.n
.ready_i
)
268 m
.d
.comb
+= n_ready_in
[mid
].eq(nirn
& data_valid
[mid
])
269 anyvalid
= Signal(i
, reset_less
=True)
271 for i
in range(p_len
):
272 av
.append(data_valid
[i
])
274 m
.d
.comb
+= self
.n
.valid_o
.eq(anyvalid
.bool())
275 m
.d
.comb
+= data_valid
[mid
].eq(p_valid_i
[mid
] | \
276 (n_ready_in
[mid
] & data_valid
[mid
]))
278 for i
in range(p_len
):
279 vr
= Signal(reset_less
=True)
280 m
.d
.comb
+= vr
.eq(self
.p
[i
].valid_i
& self
.p
[i
].ready_o
)
282 m
.d
.comb
+= eq(r_data
[i
], self
.p
[i
].data_i
)
284 m
.d
.comb
+= eq(self
.n
.data_o
, self
.stage
.process(r_data
[mid
]))
289 class CombMuxOutPipe(CombMultiOutPipeline
):
290 def __init__(self
, stage
, n_len
):
291 # HACK: stage is also the n-way multiplexer
292 CombMultiOutPipeline
.__init
__(self
, stage
, n_len
=n_len
, n_mux
=stage
)
294 # HACK: n-mux is also the stage... so set the muxid equal to input mid
295 stage
.m_id
= self
.p
.data_i
.mid
299 class InputPriorityArbiter(Elaboratable
):
300 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
302 def __init__(self
, pipe
, num_rows
):
304 self
.num_rows
= num_rows
305 self
.mmax
= int(log(self
.num_rows
) / log(2))
306 self
.m_id
= Signal(self
.mmax
, reset_less
=True) # multiplex id
307 self
.active
= Signal(reset_less
=True)
309 def elaborate(self
, platform
):
312 assert len(self
.pipe
.p
) == self
.num_rows
, \
313 "must declare input to be same size"
314 pe
= PriorityEncoder(self
.num_rows
)
315 m
.submodules
.selector
= pe
317 # connect priority encoder
319 for i
in range(self
.num_rows
):
320 p_valid_i
= Signal(reset_less
=True)
321 m
.d
.comb
+= p_valid_i
.eq(self
.pipe
.p
[i
].valid_i_test
)
322 in_ready
.append(p_valid_i
)
323 m
.d
.comb
+= pe
.i
.eq(Cat(*in_ready
)) # array of input "valids"
324 m
.d
.comb
+= self
.active
.eq(~pe
.n
) # encoder active (one input valid)
325 m
.d
.comb
+= self
.m_id
.eq(pe
.o
) # output one active input
330 return [self
.m_id
, self
.active
]
334 class PriorityCombMuxInPipe(CombMultiInPipeline
):
335 """ an example of how to use the combinatorial pipeline.
338 def __init__(self
, stage
, p_len
=2):
339 p_mux
= InputPriorityArbiter(self
, p_len
)
340 CombMultiInPipeline
.__init
__(self
, stage
, p_len
, p_mux
)
343 if __name__
== '__main__':
345 dut
= PriorityCombMuxInPipe(ExampleStage
)
346 vl
= rtlil
.convert(dut
, ports
=dut
.ports())
347 with
open("test_combpipe.il", "w") as f
: