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