multi-out temporary, simplify graphs
[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 pv = Signal(reset_less=True)
195 m.d.comb += p_i_valid.eq(self.p.i_valid_logic())
196 m.d.comb += pv.eq(self.p.i_valid & self.p.o_ready)
197
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))
206 with m.If(pv):
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))
209
210 return m
211
212
213 class CombMultiInPipeline(MultiInControlBase):
214 """ A multi-input Combinatorial block conforming to the Pipeline API
215
216 Attributes:
217 -----------
218 p.i_data : StageInput, shaped according to ispec
219 The pipeline input
220 p.o_data : StageOutput, shaped according to ospec
221 The pipeline output
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
225 SYNCHRONOUSLY.
226 """
227
228 def __init__(self, stage, p_len, p_mux):
229 MultiInControlBase.__init__(self, p_len=p_len)
230 self.stage = stage
231 self.p_mux = p_mux
232
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()
237
238 def elaborate(self, platform):
239 m = Module()
240
241 m.submodules += self.p_mux
242
243 # need an array of buffer registers conforming to *input* spec
244 r_data = []
245 data_valid = []
246 p_i_valid = []
247 n_i_readyn = []
248 p_len = len(self.p)
249 for i in range(p_len):
250 r = self.stage.ispec() # input type
251 r_data.append(r)
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)
257 if len(r_data) > 1:
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)
262
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)
275 av = []
276 for i in range(p_len):
277 av.append(data_valid[i])
278 anyvalid = Cat(*av)
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]))
282
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)
286 with m.If(vr):
287 m.d.comb += eq(r_data[i], self.p[i].i_data)
288
289 m.d.comb += eq(self.n.o_data, self.stage.process(r_data[mid]))
290
291 return m
292
293
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)
298
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
301
302
303
304 class InputPriorityArbiter:
305 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
306 """
307 def __init__(self, pipe, num_rows):
308 self.pipe = pipe
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)
313
314 def elaborate(self, platform):
315 m = Module()
316
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
321
322 # connect priority encoder
323 in_ready = []
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_logic())
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
331
332 return m
333
334 def ports(self):
335 return [self.m_id, self.active]
336
337
338
339 class PriorityCombMuxInPipe(CombMultiInPipeline):
340 """ an example of how to use the combinatorial pipeline.
341 """
342
343 def __init__(self, stage, p_len=2):
344 p_mux = InputPriorityArbiter(self, p_len)
345 CombMultiInPipeline.__init__(self, stage, p_len, p_mux)
346
347
348 if __name__ == '__main__':
349
350 dut = PriorityCombMuxInPipe(ExampleStage)
351 vl = rtlil.convert(dut, ports=dut.ports())
352 with open("test_combpipe.il", "w") as f:
353 f.write(vl)