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