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