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