create base multi-in ports function
[ieee754fpu.git] / src / add / 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 o_ready signals
10 DEASSERTED.
11 """
12
13 from math import log
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
18
19 from collections.abc import Sequence
20
21 from example_buf_pipe import eq, NextControl, PrevControl, ExampleStage
22
23
24 class MultiInControlBase:
25 """ Common functions for Pipeline API
26 """
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
30
31 * p: contains ready/valid to the previous stages PLURAL
32 * n: contains ready/valid to the next stage
33
34 User must also:
35 * add i_data members to PrevControl and
36 * add o_data member to NextControl
37 """
38 # set up input and output IO ACK (prev/next ready/valid)
39 p = []
40 for i in range(p_len):
41 p.append(PrevControl(in_multi))
42 self.p = Array(p)
43 self.n = NextControl()
44
45 def connect_to_next(self, nxt, p_idx=0):
46 """ helper function to connect to the next stage data/valid/ready.
47 """
48 return self.n.connect_to_next(nxt.p[p_idx])
49
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!
53 """
54 if prev_idx is None:
55 return self.p[idx]._connect_in(prev.p)
56 return self.p[idx]._connect_in(prev.p[prev_idx])
57
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!
61 """
62 if nxt_idx is None:
63 return self.n._connect_out(nxt.n)
64 return self.n._connect_out(nxt.n)
65
66 def set_input(self, i, idx=0):
67 """ helper function to set the input data
68 """
69 return eq(self.p[idx].i_data, i)
70
71 def ports(self):
72 res = []
73 for i in range(len(self.p)):
74 p = self.p[i]
75 res += [p.i_valid, p.o_ready]
76 if hasattr(p.i_data, "ports"):
77 res += p.i_data.ports()
78 else:
79 rres = p.i_data
80 if not isinstance(rres, Sequence):
81 rres = [rres]
82 res += rres
83 n = self.n
84 res += [n.i_ready, n.o_valid]
85 if hasattr(n.o_data, "ports"):
86 res += n.o_data.ports()
87 else:
88 rres = n.o_data
89 if not isinstance(rres, Sequence):
90 rres = [rres]
91 res += rres
92 return res
93
94
95 class MultiOutControlBase:
96 """ Common functions for Pipeline API
97 """
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]
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 i_data member to PrevControl and
108 * add o_data members to NextControl
109 """
110
111 # set up input and output IO ACK (prev/next ready/valid)
112 self.p = PrevControl(in_multi)
113 n = []
114 for i in range(n_len):
115 n.append(NextControl())
116 self.n = Array(n)
117
118 def connect_to_next(self, nxt, n_idx=0):
119 """ helper function to connect to the next stage data/valid/ready.
120 """
121 return self.n[n_idx].connect_to_next(nxt.p)
122
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!
126 """
127 return self.n[idx]._connect_in(prev.p)
128
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!
132 """
133 if nxt_idx is None:
134 return self.n[idx]._connect_out(nxt.n)
135 return self.n[idx]._connect_out(nxt.n[nxt_idx])
136
137 def set_input(self, i):
138 """ helper function to set the input data
139 """
140 return eq(self.p.i_data, i)
141
142 def ports(self):
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()
146 else:
147 res += self.p.i_data
148
149 for i in range(len(self.n)):
150 n = self.n[i]
151 res += [n.i_ready, n.o_valid]
152 if hasattr(n.o_data, "ports"):
153 res += n.o_data.ports()
154 else:
155 res += n.o_data
156 return res
157
158
159 class CombMultiOutPipeline(MultiOutControlBase):
160 """ A multi-input Combinatorial block conforming to the Pipeline API
161
162 Attributes:
163 -----------
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
166 """
167
168 def __init__(self, stage, n_len, n_mux):
169 MultiOutControlBase.__init__(self, n_len=n_len)
170 self.stage = stage
171 self.n_mux = n_mux
172
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
177
178 def elaborate(self, platform):
179 m = Module()
180
181 if hasattr(self.n_mux, "elaborate"): # TODO: identify submodule?
182 m.submodules += self.n_mux
183
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)
188
189 # multiplexer id taken from n_mux
190 mid = self.n_mux.m_id
191
192 # temporaries
193 p_i_valid = Signal(reset_less=True)
194 m.d.comb += p_i_valid.eq(self.p.i_valid_logic())
195
196 # all outputs to next stages first initialised to zero (invalid)
197 # the only output "active" is then selected by the muxid
198 for i in range(len(self.n)):
199 m.d.comb += self.n[i].o_valid.eq(0)
200 data_valid = self.n[mid].o_valid
201 m.d.comb += self.p.o_ready.eq(~data_valid | self.n[mid].i_ready)
202 m.d.comb += data_valid.eq(p_i_valid | \
203 (~self.n[mid].i_ready & data_valid))
204 with m.If(self.p.i_valid & self.p.o_ready):
205 m.d.comb += eq(r_data, self.p.i_data)
206 m.d.comb += eq(self.n[mid].o_data, self.stage.process(r_data))
207
208 return m
209
210
211 class CombMultiInPipeline(MultiInControlBase):
212 """ A multi-input Combinatorial block conforming to the Pipeline API
213
214 Attributes:
215 -----------
216 p.i_data : StageInput, shaped according to ispec
217 The pipeline input
218 p.o_data : StageOutput, shaped according to ospec
219 The pipeline output
220 r_data : input_shape according to ispec
221 A temporary (buffered) copy of a prior (valid) input.
222 This is HELD if the output is not ready. It is updated
223 SYNCHRONOUSLY.
224 """
225
226 def __init__(self, stage, p_len, p_mux):
227 MultiInControlBase.__init__(self, p_len=p_len)
228 self.stage = stage
229 self.p_mux = p_mux
230
231 # set up the input and output data
232 for i in range(p_len):
233 self.p[i].i_data = stage.ispec() # input type
234 self.n.o_data = stage.ospec()
235
236 def elaborate(self, platform):
237 m = Module()
238
239 m.submodules += self.p_mux
240
241 # need an array of buffer registers conforming to *input* spec
242 r_data = []
243 data_valid = []
244 p_i_valid = []
245 n_i_readyn = []
246 p_len = len(self.p)
247 for i in range(p_len):
248 r = self.stage.ispec() # input type
249 r_data.append(r)
250 data_valid.append(Signal(name="data_valid", reset_less=True))
251 p_i_valid.append(Signal(name="p_i_valid", reset_less=True))
252 n_i_readyn.append(Signal(name="n_i_readyn", reset_less=True))
253 if hasattr(self.stage, "setup"):
254 self.stage.setup(m, r)
255 if len(r_data) > 1:
256 r_data = Array(r_data)
257 p_i_valid = Array(p_i_valid)
258 n_i_readyn = Array(n_i_readyn)
259 data_valid = Array(data_valid)
260
261 mid = self.p_mux.m_id
262 for i in range(p_len):
263 m.d.comb += data_valid[i].eq(0)
264 m.d.comb += n_i_readyn[i].eq(1)
265 m.d.comb += p_i_valid[i].eq(0)
266 m.d.comb += self.p[i].o_ready.eq(0)
267 m.d.comb += p_i_valid[mid].eq(self.p_mux.active)
268 m.d.comb += self.p[mid].o_ready.eq(~data_valid[mid] | self.n.i_ready)
269 m.d.comb += n_i_readyn[mid].eq(~self.n.i_ready & data_valid[mid])
270 anyvalid = Signal(i, reset_less=True)
271 av = []
272 for i in range(p_len):
273 av.append(data_valid[i])
274 anyvalid = Cat(*av)
275 m.d.comb += self.n.o_valid.eq(anyvalid.bool())
276 m.d.comb += data_valid[mid].eq(p_i_valid[mid] | \
277 (n_i_readyn[mid] & data_valid[mid]))
278
279 for i in range(p_len):
280 vr = Signal(reset_less=True)
281 m.d.comb += vr.eq(self.p[i].i_valid & self.p[i].o_ready)
282 with m.If(vr):
283 m.d.comb += eq(r_data[i], self.p[i].i_data)
284
285 m.d.comb += eq(self.n.o_data, self.stage.process(r_data[mid]))
286
287 return m
288
289
290 class CombMuxOutPipe(CombMultiOutPipeline):
291 def __init__(self, stage, n_len):
292 # HACK: stage is also the n-way multiplexer
293 CombMultiOutPipeline.__init__(self, stage, n_len=n_len, n_mux=stage)
294
295 # HACK: n-mux is also the stage... so set the muxid equal to input mid
296 stage.m_id = self.p.i_data.mid
297
298
299
300 class InputPriorityArbiter:
301 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
302 """
303 def __init__(self, pipe, num_rows):
304 self.pipe = pipe
305 self.num_rows = num_rows
306 self.mmax = int(log(self.num_rows) / log(2))
307 self.m_id = Signal(self.mmax, reset_less=True) # multiplex id
308 self.active = Signal(reset_less=True)
309
310 def elaborate(self, platform):
311 m = Module()
312
313 assert len(self.pipe.p) == self.num_rows, \
314 "must declare input to be same size"
315 pe = PriorityEncoder(self.num_rows)
316 m.submodules.selector = pe
317
318 # connect priority encoder
319 in_ready = []
320 for i in range(self.num_rows):
321 p_i_valid = Signal(reset_less=True)
322 m.d.comb += p_i_valid.eq(self.pipe.p[i].i_valid_logic())
323 in_ready.append(p_i_valid)
324 m.d.comb += pe.i.eq(Cat(*in_ready)) # array of input "valids"
325 m.d.comb += self.active.eq(~pe.n) # encoder active (one input valid)
326 m.d.comb += self.m_id.eq(pe.o) # output one active input
327
328 return m
329
330 def ports(self):
331 return [self.m_id, self.active]
332
333
334
335 class PriorityCombMuxInPipe(CombMultiInPipeline):
336 """ an example of how to use the combinatorial pipeline.
337 """
338
339 def __init__(self, stage, p_len=2):
340 p_mux = InputPriorityArbiter(self, p_len)
341 CombMultiInPipeline.__init__(self, stage, p_len, p_mux)
342
343
344 if __name__ == '__main__':
345
346 dut = PriorityCombMuxInPipe(ExampleStage)
347 vl = rtlil.convert(dut, ports=dut.ports())
348 with open("test_combpipe.il", "w") as f:
349 f.write(vl)