move flexible ports fn to MultiOutControlBase
[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 res += [self.p[i].i_valid, self.p[i].o_ready,
75 self.p[i].i_data]# XXX need flattening!]
76 res += [self.n.i_ready, self.n.o_valid,
77 self.n.o_data] # XXX need flattening!]
78 return res
79
80
81 class MultiOutControlBase:
82 """ Common functions for Pipeline API
83 """
84 def __init__(self, n_len=1, in_multi=None):
85 """ Multi-output Control class. Conforms to same API as ControlBase...
86 mostly. has additional indices to the multiple *output* stages
87 [MultiInControlBase has multiple *input* stages]
88
89 * p: contains ready/valid to the previou stage
90 * n: contains ready/valid to the next stages PLURAL
91
92 User must also:
93 * add i_data member to PrevControl and
94 * add o_data members to NextControl
95 """
96
97 # set up input and output IO ACK (prev/next ready/valid)
98 self.p = PrevControl(in_multi)
99 n = []
100 for i in range(n_len):
101 n.append(NextControl())
102 self.n = Array(n)
103
104 def connect_to_next(self, nxt, n_idx=0):
105 """ helper function to connect to the next stage data/valid/ready.
106 """
107 return self.n[n_idx].connect_to_next(nxt.p)
108
109 def _connect_in(self, prev, idx=0):
110 """ helper function to connect stage to an input source. do not
111 use to connect stage-to-stage!
112 """
113 return self.n[idx]._connect_in(prev.p)
114
115 def _connect_out(self, nxt, idx=0, nxt_idx=None):
116 """ helper function to connect stage to an output source. do not
117 use to connect stage-to-stage!
118 """
119 if nxt_idx is None:
120 return self.n[idx]._connect_out(nxt.n)
121 return self.n[idx]._connect_out(nxt.n[nxt_idx])
122
123 def set_input(self, i):
124 """ helper function to set the input data
125 """
126 return eq(self.p.i_data, i)
127
128 def ports(self):
129 res = [self.p.i_valid, self.p.o_ready]
130 if hasattr(self.p.i_data, "ports"):
131 res += self.p.i_data.ports()
132 else:
133 res += self.p.i_data
134
135 for i in range(len(self.n)):
136 n = self.n[i]
137 res += [n.i_ready, n.o_valid]
138 if hasattr(n.o_data, "ports"):
139 res += n.o_data.ports()
140 else:
141 res += n.o_data
142 return res
143
144
145 class CombMultiOutPipeline(MultiOutControlBase):
146 """ A multi-input Combinatorial block conforming to the Pipeline API
147
148 Attributes:
149 -----------
150 p.i_data : stage input data (non-array). shaped according to ispec
151 n.o_data : stage output data array. shaped according to ospec
152 """
153
154 def __init__(self, stage, n_len, n_mux):
155 MultiOutControlBase.__init__(self, n_len=n_len)
156 self.stage = stage
157 self.n_mux = n_mux
158
159 # set up the input and output data
160 self.p.i_data = stage.ispec() # input type
161 for i in range(n_len):
162 self.n[i].o_data = stage.ospec() # output type
163
164 def elaborate(self, platform):
165 m = Module()
166
167 if hasattr(self.n_mux, "elaborate"): # TODO: identify submodule?
168 m.submodules += self.n_mux
169
170 # need buffer register conforming to *input* spec
171 r_data = self.stage.ispec() # input type
172 if hasattr(self.stage, "setup"):
173 self.stage.setup(m, r_data)
174
175 # multiplexer id taken from n_mux
176 mid = self.n_mux.m_id
177
178 # temporaries
179 p_i_valid = Signal(reset_less=True)
180 m.d.comb += p_i_valid.eq(self.p.i_valid_logic())
181
182 # all outputs to next stages first initialised to zero (invalid)
183 # the only output "active" is then selected by the muxid
184 for i in range(len(self.n)):
185 m.d.comb += self.n[i].o_valid.eq(0)
186 data_valid = self.n[mid].o_valid
187 m.d.comb += self.p.o_ready.eq(~data_valid | self.n[mid].i_ready)
188 m.d.comb += data_valid.eq(p_i_valid | \
189 (~self.n[mid].i_ready & data_valid))
190 with m.If(self.p.i_valid & self.p.o_ready):
191 m.d.comb += eq(r_data, self.p.i_data)
192 m.d.comb += eq(self.n[mid].o_data, self.stage.process(r_data))
193
194 return m
195
196
197 class CombMultiInPipeline(MultiInControlBase):
198 """ A multi-input Combinatorial block conforming to the Pipeline API
199
200 Attributes:
201 -----------
202 p.i_data : StageInput, shaped according to ispec
203 The pipeline input
204 p.o_data : StageOutput, shaped according to ospec
205 The pipeline output
206 r_data : input_shape according to ispec
207 A temporary (buffered) copy of a prior (valid) input.
208 This is HELD if the output is not ready. It is updated
209 SYNCHRONOUSLY.
210 """
211
212 def __init__(self, stage, p_len, p_mux):
213 MultiInControlBase.__init__(self, p_len=p_len)
214 self.stage = stage
215 self.p_mux = p_mux
216
217 # set up the input and output data
218 for i in range(p_len):
219 self.p[i].i_data = stage.ispec() # input type
220 self.n.o_data = stage.ospec()
221
222 def elaborate(self, platform):
223 m = Module()
224
225 m.submodules += self.p_mux
226
227 # need an array of buffer registers conforming to *input* spec
228 r_data = []
229 data_valid = []
230 p_i_valid = []
231 n_i_readyn = []
232 p_len = len(self.p)
233 for i in range(p_len):
234 r = self.stage.ispec() # input type
235 r_data.append(r)
236 data_valid.append(Signal(name="data_valid", reset_less=True))
237 p_i_valid.append(Signal(name="p_i_valid", reset_less=True))
238 n_i_readyn.append(Signal(name="n_i_readyn", reset_less=True))
239 if hasattr(self.stage, "setup"):
240 self.stage.setup(m, r)
241 if len(r_data) > 1:
242 r_data = Array(r_data)
243 p_i_valid = Array(p_i_valid)
244 n_i_readyn = Array(n_i_readyn)
245 data_valid = Array(data_valid)
246
247 mid = self.p_mux.m_id
248 for i in range(p_len):
249 m.d.comb += data_valid[i].eq(0)
250 m.d.comb += n_i_readyn[i].eq(1)
251 m.d.comb += p_i_valid[i].eq(0)
252 m.d.comb += self.p[i].o_ready.eq(0)
253 m.d.comb += p_i_valid[mid].eq(self.p_mux.active)
254 m.d.comb += self.p[mid].o_ready.eq(~data_valid[mid] | self.n.i_ready)
255 m.d.comb += n_i_readyn[mid].eq(~self.n.i_ready & data_valid[mid])
256 anyvalid = Signal(i, reset_less=True)
257 av = []
258 for i in range(p_len):
259 av.append(data_valid[i])
260 anyvalid = Cat(*av)
261 m.d.comb += self.n.o_valid.eq(anyvalid.bool())
262 m.d.comb += data_valid[mid].eq(p_i_valid[mid] | \
263 (n_i_readyn[mid] & data_valid[mid]))
264
265 for i in range(p_len):
266 vr = Signal(reset_less=True)
267 m.d.comb += vr.eq(self.p[i].i_valid & self.p[i].o_ready)
268 with m.If(vr):
269 m.d.comb += eq(r_data[i], self.p[i].i_data)
270
271 m.d.comb += eq(self.n.o_data, self.stage.process(r_data[mid]))
272
273 return m
274
275
276 class CombMuxOutPipe(CombMultiOutPipeline):
277 def __init__(self, stage, n_len):
278 # HACK: stage is also the n-way multiplexer
279 CombMultiOutPipeline.__init__(self, stage, n_len=n_len, n_mux=stage)
280
281 # HACK: n-mux is also the stage... so set the muxid equal to input mid
282 stage.m_id = self.p.i_data.mid
283
284
285
286 class InputPriorityArbiter:
287 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
288 """
289 def __init__(self, pipe, num_rows):
290 self.pipe = pipe
291 self.num_rows = num_rows
292 self.mmax = int(log(self.num_rows) / log(2))
293 self.m_id = Signal(self.mmax, reset_less=True) # multiplex id
294 self.active = Signal(reset_less=True)
295
296 def elaborate(self, platform):
297 m = Module()
298
299 assert len(self.pipe.p) == self.num_rows, \
300 "must declare input to be same size"
301 pe = PriorityEncoder(self.num_rows)
302 m.submodules.selector = pe
303
304 # connect priority encoder
305 in_ready = []
306 for i in range(self.num_rows):
307 p_i_valid = Signal(reset_less=True)
308 m.d.comb += p_i_valid.eq(self.pipe.p[i].i_valid_logic())
309 in_ready.append(p_i_valid)
310 m.d.comb += pe.i.eq(Cat(*in_ready)) # array of input "valids"
311 m.d.comb += self.active.eq(~pe.n) # encoder active (one input valid)
312 m.d.comb += self.m_id.eq(pe.o) # output one active input
313
314 return m
315
316 def ports(self):
317 return [self.m_id, self.active]
318
319
320
321 class PriorityCombMuxInPipe(CombMultiInPipeline):
322 """ an example of how to use the combinatorial pipeline.
323 """
324
325 def __init__(self, stage, p_len=2):
326 p_mux = InputPriorityArbiter(self, p_len)
327 CombMultiInPipeline.__init__(self, stage, p_len, p_mux)
328
329
330 if __name__ == '__main__':
331
332 dut = PriorityCombMuxInPipe(ExampleStage)
333 vl = rtlil.convert(dut, ports=dut.ports())
334 with open("test_combpipe.il", "w") as f:
335 f.write(vl)