move singlepipe, multipipe, nmoperator and pipeline.py to nmutil
[ieee754fpu.git] / src / nmutil / 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 stageapi 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 process(self, i):
172 if hasattr(self.stage, "process"):
173 return self.stage.process(i)
174 return i
175
176 def elaborate(self, platform):
177 m = MultiOutControlBase.elaborate(self, platform)
178
179 if hasattr(self.n_mux, "elaborate"): # TODO: identify submodule?
180 m.submodules += self.n_mux
181
182 # need buffer register conforming to *input* spec
183 r_data = _spec(self.stage.ispec, 'r_data') # input type
184 if hasattr(self.stage, "setup"):
185 self.stage.setup(m, r_data)
186
187 # multiplexer id taken from n_mux
188 mid = self.n_mux.m_id
189
190 # temporaries
191 p_valid_i = Signal(reset_less=True)
192 pv = Signal(reset_less=True)
193 m.d.comb += p_valid_i.eq(self.p.valid_i_test)
194 m.d.comb += pv.eq(self.p.valid_i & self.p.ready_o)
195
196 # all outputs to next stages first initialised to zero (invalid)
197 # the only output "active" is then selected by the muxid
198 for i in range(len(self.n)):
199 m.d.comb += self.n[i].valid_o.eq(0)
200 data_valid = self.n[mid].valid_o
201 m.d.comb += self.p.ready_o.eq(~data_valid | self.n[mid].ready_i)
202 m.d.comb += data_valid.eq(p_valid_i | \
203 (~self.n[mid].ready_i & data_valid))
204 with m.If(pv):
205 m.d.comb += eq(r_data, self.p.data_i)
206 m.d.comb += eq(self.n[mid].data_o, self.process(r_data))
207
208 return m
209
210
211 class CombMultiInPipeline(MultiInControlBase):
212 """ A multi-input Combinatorial block conforming to the Pipeline API
213
214 Attributes:
215 -----------
216 p.data_i : StageInput, shaped according to ispec
217 The pipeline input
218 p.data_o : StageOutput, shaped according to ospec
219 The pipeline output
220 r_data : input_shape according to ispec
221 A temporary (buffered) copy of a prior (valid) input.
222 This is HELD if the output is not ready. It is updated
223 SYNCHRONOUSLY.
224 """
225
226 def __init__(self, stage, p_len, p_mux):
227 MultiInControlBase.__init__(self, p_len=p_len)
228 self.stage = stage
229 self.p_mux = p_mux
230
231 # set up the input and output data
232 for i in range(p_len):
233 name = 'data_i_%d' % i
234 self.p[i].data_i = _spec(stage.ispec, name) # input type
235 self.n.data_o = _spec(stage.ospec, 'data_o')
236
237 def process(self, i):
238 if hasattr(self.stage, "process"):
239 return self.stage.process(i)
240 return i
241
242 def elaborate(self, platform):
243 m = MultiInControlBase.elaborate(self, platform)
244
245 m.submodules += self.p_mux
246
247 # need an array of buffer registers conforming to *input* spec
248 r_data = []
249 data_valid = []
250 p_valid_i = []
251 n_ready_in = []
252 p_len = len(self.p)
253 for i in range(p_len):
254 name = 'r_%d' % i
255 r = _spec(self.stage.ispec, name) # input type
256 r_data.append(r)
257 data_valid.append(Signal(name="data_valid", reset_less=True))
258 p_valid_i.append(Signal(name="p_valid_i", reset_less=True))
259 n_ready_in.append(Signal(name="n_ready_in", reset_less=True))
260 if hasattr(self.stage, "setup"):
261 self.stage.setup(m, r)
262 if len(r_data) > 1:
263 r_data = Array(r_data)
264 p_valid_i = Array(p_valid_i)
265 n_ready_in = Array(n_ready_in)
266 data_valid = Array(data_valid)
267
268 nirn = Signal(reset_less=True)
269 m.d.comb += nirn.eq(~self.n.ready_i)
270 mid = self.p_mux.m_id
271 for i in range(p_len):
272 m.d.comb += data_valid[i].eq(0)
273 m.d.comb += n_ready_in[i].eq(1)
274 m.d.comb += p_valid_i[i].eq(0)
275 m.d.comb += self.p[i].ready_o.eq(0)
276 m.d.comb += p_valid_i[mid].eq(self.p_mux.active)
277 m.d.comb += self.p[mid].ready_o.eq(~data_valid[mid] | self.n.ready_i)
278 m.d.comb += n_ready_in[mid].eq(nirn & data_valid[mid])
279 anyvalid = Signal(i, reset_less=True)
280 av = []
281 for i in range(p_len):
282 av.append(data_valid[i])
283 anyvalid = Cat(*av)
284 m.d.comb += self.n.valid_o.eq(anyvalid.bool())
285 m.d.comb += data_valid[mid].eq(p_valid_i[mid] | \
286 (n_ready_in[mid] & data_valid[mid]))
287
288 for i in range(p_len):
289 vr = Signal(reset_less=True)
290 m.d.comb += vr.eq(self.p[i].valid_i & self.p[i].ready_o)
291 with m.If(vr):
292 m.d.comb += eq(r_data[i], self.p[i].data_i)
293
294 m.d.comb += eq(self.n.data_o, self.process(r_data[mid]))
295
296 return m
297
298
299 class CombMuxOutPipe(CombMultiOutPipeline):
300 def __init__(self, stage, n_len):
301 # HACK: stage is also the n-way multiplexer
302 CombMultiOutPipeline.__init__(self, stage, n_len=n_len, n_mux=stage)
303
304 # HACK: n-mux is also the stage... so set the muxid equal to input mid
305 stage.m_id = self.p.data_i.mid
306
307
308
309 class InputPriorityArbiter(Elaboratable):
310 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
311 """
312 def __init__(self, pipe, num_rows):
313 self.pipe = pipe
314 self.num_rows = num_rows
315 self.mmax = int(log(self.num_rows) / log(2))
316 self.m_id = Signal(self.mmax, reset_less=True) # multiplex id
317 self.active = Signal(reset_less=True)
318
319 def elaborate(self, platform):
320 m = Module()
321
322 assert len(self.pipe.p) == self.num_rows, \
323 "must declare input to be same size"
324 pe = PriorityEncoder(self.num_rows)
325 m.submodules.selector = pe
326
327 # connect priority encoder
328 in_ready = []
329 for i in range(self.num_rows):
330 p_valid_i = Signal(reset_less=True)
331 m.d.comb += p_valid_i.eq(self.pipe.p[i].valid_i_test)
332 in_ready.append(p_valid_i)
333 m.d.comb += pe.i.eq(Cat(*in_ready)) # array of input "valids"
334 m.d.comb += self.active.eq(~pe.n) # encoder active (one input valid)
335 m.d.comb += self.m_id.eq(pe.o) # output one active input
336
337 return m
338
339 def ports(self):
340 return [self.m_id, self.active]
341
342
343
344 class PriorityCombMuxInPipe(CombMultiInPipeline):
345 """ an example of how to use the combinatorial pipeline.
346 """
347
348 def __init__(self, stage, p_len=2):
349 p_mux = InputPriorityArbiter(self, p_len)
350 CombMultiInPipeline.__init__(self, stage, p_len, p_mux)
351
352
353 if __name__ == '__main__':
354
355 dut = PriorityCombMuxInPipe(ExampleStage)
356 vl = rtlil.convert(dut, ports=dut.ports())
357 with open("test_combpipe.il", "w") as f:
358 f.write(vl)