add comments
[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 = []
130 res += [self.p.i_valid, self.p.o_ready,
131 self.p.i_data] # XXX need flattening!
132 for i in range(len(self.n)):
133 res += [self.n[i].i_ready, self.n[i].o_valid,
134 self.n[i].o_data] # XXX need flattening!
135 return res
136
137
138
139 class CombMultiOutPipeline(MultiOutControlBase):
140 """ A multi-input Combinatorial block conforming to the Pipeline API
141
142 Attributes:
143 -----------
144 p.i_data : stage input data (non-array). shaped according to ispec
145 n.o_data : stage output data array. shaped according to ospec
146 """
147
148 def __init__(self, stage, n_len, n_mux):
149 MultiOutControlBase.__init__(self, n_len=n_len)
150 self.stage = stage
151 self.n_mux = n_mux
152
153 # set up the input and output data
154 self.p.i_data = stage.ispec() # input type
155 for i in range(n_len):
156 self.n[i].o_data = stage.ospec() # output type
157
158 def elaborate(self, platform):
159 m = Module()
160
161 if hasattr(self.n_mux, "elaborate"): # TODO: identify submodule?
162 m.submodules += self.n_mux
163
164 # need buffer register conforming to *input* spec
165 r_data = self.stage.ispec() # input type
166 if hasattr(self.stage, "setup"):
167 self.stage.setup(m, r_data)
168
169 # multiplexer id taken from n_mux
170 mid = self.n_mux.m_id
171
172 # temporaries
173 p_i_valid = Signal(reset_less=True)
174 m.d.comb += p_i_valid.eq(self.p.i_valid_logic())
175
176 # all outputs to next stages first initialised to zero (invalid)
177 # the only output "active" is then selected by the muxid
178 for i in range(len(self.n)):
179 m.d.comb += self.n[i].o_valid.eq(0)
180 data_valid = self.n[mid].o_valid
181 m.d.comb += self.p.o_ready.eq(~data_valid | self.n[mid].i_ready)
182 m.d.comb += data_valid.eq(p_i_valid | \
183 (~self.n[mid].i_ready & data_valid))
184 with m.If(self.p.i_valid & self.p.o_ready):
185 m.d.comb += eq(r_data, self.p.i_data)
186 m.d.comb += eq(self.n[mid].o_data, self.stage.process(r_data))
187
188 return m
189
190
191 class CombMultiInPipeline(MultiInControlBase):
192 """ A multi-input Combinatorial block conforming to the Pipeline API
193
194 Attributes:
195 -----------
196 p.i_data : StageInput, shaped according to ispec
197 The pipeline input
198 p.o_data : StageOutput, shaped according to ospec
199 The pipeline output
200 r_data : input_shape according to ispec
201 A temporary (buffered) copy of a prior (valid) input.
202 This is HELD if the output is not ready. It is updated
203 SYNCHRONOUSLY.
204 """
205
206 def __init__(self, stage, p_len, p_mux):
207 MultiInControlBase.__init__(self, p_len=p_len)
208 self.stage = stage
209 self.p_mux = p_mux
210
211 # set up the input and output data
212 for i in range(p_len):
213 self.p[i].i_data = stage.ispec() # input type
214 self.n.o_data = stage.ospec()
215
216 def elaborate(self, platform):
217 m = Module()
218
219 m.submodules += self.p_mux
220
221 # need an array of buffer registers conforming to *input* spec
222 r_data = []
223 data_valid = []
224 p_i_valid = []
225 n_i_readyn = []
226 p_len = len(self.p)
227 for i in range(p_len):
228 r = self.stage.ispec() # input type
229 r_data.append(r)
230 data_valid.append(Signal(name="data_valid", reset_less=True))
231 p_i_valid.append(Signal(name="p_i_valid", reset_less=True))
232 n_i_readyn.append(Signal(name="n_i_readyn", reset_less=True))
233 if hasattr(self.stage, "setup"):
234 self.stage.setup(m, r)
235 if len(r_data) > 1:
236 r_data = Array(r_data)
237 p_i_valid = Array(p_i_valid)
238 n_i_readyn = Array(n_i_readyn)
239 data_valid = Array(data_valid)
240
241 mid = self.p_mux.m_id
242 for i in range(p_len):
243 m.d.comb += data_valid[i].eq(0)
244 m.d.comb += n_i_readyn[i].eq(1)
245 m.d.comb += p_i_valid[i].eq(0)
246 m.d.comb += self.p[i].o_ready.eq(0)
247 m.d.comb += p_i_valid[mid].eq(self.p_mux.active)
248 m.d.comb += self.p[mid].o_ready.eq(~data_valid[mid] | self.n.i_ready)
249 m.d.comb += n_i_readyn[mid].eq(~self.n.i_ready & data_valid[mid])
250 anyvalid = Signal(i, reset_less=True)
251 av = []
252 for i in range(p_len):
253 av.append(data_valid[i])
254 anyvalid = Cat(*av)
255 m.d.comb += self.n.o_valid.eq(anyvalid.bool())
256 m.d.comb += data_valid[mid].eq(p_i_valid[mid] | \
257 (n_i_readyn[mid] & data_valid[mid]))
258
259 for i in range(p_len):
260 vr = Signal(reset_less=True)
261 m.d.comb += vr.eq(self.p[i].i_valid & self.p[i].o_ready)
262 with m.If(vr):
263 m.d.comb += eq(r_data[i], self.p[i].i_data)
264
265 m.d.comb += eq(self.n.o_data, self.stage.process(r_data[mid]))
266
267 return m
268
269
270 class InputPriorityArbiter:
271 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
272 """
273 def __init__(self, pipe, num_rows):
274 self.pipe = pipe
275 self.num_rows = num_rows
276 self.mmax = int(log(self.num_rows) / log(2))
277 self.m_id = Signal(self.mmax, reset_less=True) # multiplex id
278 self.active = Signal(reset_less=True)
279
280 def elaborate(self, platform):
281 m = Module()
282
283 assert len(self.pipe.p) == self.num_rows, \
284 "must declare input to be same size"
285 pe = PriorityEncoder(self.num_rows)
286 m.submodules.selector = pe
287
288 # connect priority encoder
289 in_ready = []
290 for i in range(self.num_rows):
291 p_i_valid = Signal(reset_less=True)
292 m.d.comb += p_i_valid.eq(self.pipe.p[i].i_valid_logic())
293 in_ready.append(p_i_valid)
294 m.d.comb += pe.i.eq(Cat(*in_ready)) # array of input "valids"
295 m.d.comb += self.active.eq(~pe.n) # encoder active (one input valid)
296 m.d.comb += self.m_id.eq(pe.o) # output one active input
297
298 return m
299
300 def ports(self):
301 return [self.m_id, self.active]
302
303
304
305 class ExamplePipeline(CombMultiInPipeline):
306 """ an example of how to use the combinatorial pipeline.
307 """
308
309 def __init__(self, p_len=2):
310 p_mux = InputPriorityArbiter(self, p_len)
311 CombMultiInPipeline.__init__(self, ExampleStage, p_len, p_mux)
312
313
314 if __name__ == '__main__':
315
316 dut = ExamplePipeline()
317 vl = rtlil.convert(dut, ports=dut.ports())
318 with open("test_combpipe.il", "w") as f:
319 f.write(vl)