51acfba972983095eba257a5ff2ce7c26c25bb88
[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, masklen=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", masklen, p_len)
42 p = []
43 for i in range(p_len):
44 p.append(PrevControl(in_multi, maskwid=masklen))
45 self.p = Array(p)
46 self.n = NextControl(maskwid=masklen*p_len)
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, masklen=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=masklen*n_len)
108 n = []
109 for i in range(n_len):
110 n.append(NextControl(maskwid=masklen))
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, masklen=0):
163 MultiOutControlBase.__init__(self, n_len=n_len, masklen=masklen)
164 self.stage = stage
165 self.masklen = masklen
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 += 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 if self.masklen:
210 ml = [] # accumulate output masks
211 for i in range(len(self.n)):
212 ml.append(self.n[i].mask_o)
213 with m.If(pv):
214 m.d.comb += Cat(*ml).eq(self.p.mask_i)
215 with m.If(pv):
216 m.d.comb += eq(r_data, self.p.data_i)
217 m.d.comb += eq(self.n[muxid].data_o, self.process(r_data))
218
219 return m
220
221
222 class CombMultiInPipeline(MultiInControlBase):
223 """ A multi-input Combinatorial block conforming to the Pipeline API
224
225 Attributes:
226 -----------
227 p.data_i : StageInput, shaped according to ispec
228 The pipeline input
229 p.data_o : StageOutput, shaped according to ospec
230 The pipeline output
231 r_data : input_shape according to ispec
232 A temporary (buffered) copy of a prior (valid) input.
233 This is HELD if the output is not ready. It is updated
234 SYNCHRONOUSLY.
235 """
236
237 def __init__(self, stage, p_len, p_mux, masklen=0):
238 MultiInControlBase.__init__(self, p_len=p_len, masklen=masklen)
239 self.stage = stage
240 self.masklen = masklen
241 self.p_mux = p_mux
242
243 # set up the input and output data
244 for i in range(p_len):
245 name = 'data_i_%d' % i
246 self.p[i].data_i = _spec(stage.ispec, name) # input type
247 self.n.data_o = _spec(stage.ospec, 'data_o')
248
249 def process(self, i):
250 if hasattr(self.stage, "process"):
251 return self.stage.process(i)
252 return i
253
254 def elaborate(self, platform):
255 m = MultiInControlBase.elaborate(self, platform)
256
257 m.submodules += self.p_mux
258
259 # need an array of buffer registers conforming to *input* spec
260 r_data = []
261 data_valid = []
262 p_valid_i = []
263 n_ready_in = []
264 p_len = len(self.p)
265 for i in range(p_len):
266 name = 'r_%d' % i
267 r = _spec(self.stage.ispec, name) # input type
268 r_data.append(r)
269 data_valid.append(Signal(name="data_valid", reset_less=True))
270 p_valid_i.append(Signal(name="p_valid_i", reset_less=True))
271 n_ready_in.append(Signal(name="n_ready_in", reset_less=True))
272 if hasattr(self.stage, "setup"):
273 self.stage.setup(m, r)
274 if len(r_data) > 1:
275 r_data = Array(r_data)
276 p_valid_i = Array(p_valid_i)
277 n_ready_in = Array(n_ready_in)
278 data_valid = Array(data_valid)
279
280 nirn = Signal(reset_less=True)
281 m.d.comb += nirn.eq(~self.n.ready_i)
282 mid = self.p_mux.m_id
283 for i in range(p_len):
284 m.d.comb += data_valid[i].eq(0)
285 m.d.comb += n_ready_in[i].eq(1)
286 m.d.comb += p_valid_i[i].eq(0)
287 m.d.comb += self.p[i].ready_o.eq(0)
288 m.d.comb += p_valid_i[mid].eq(self.p_mux.active)
289 m.d.comb += self.p[mid].ready_o.eq(~data_valid[mid] | self.n.ready_i)
290 m.d.comb += n_ready_in[mid].eq(nirn & data_valid[mid])
291 anyvalid = Signal(i, reset_less=True)
292 av = []
293 for i in range(p_len):
294 av.append(data_valid[i])
295 anyvalid = Cat(*av)
296 m.d.comb += self.n.valid_o.eq(anyvalid.bool())
297 m.d.comb += data_valid[mid].eq(p_valid_i[mid] | \
298 (n_ready_in[mid] & data_valid[mid]))
299
300 ml = [] # accumulate output masks
301 for i in range(p_len):
302 vr = Signal(reset_less=True)
303 m.d.comb += vr.eq(self.p[i].valid_i & self.p[i].ready_o)
304 with m.If(vr):
305 m.d.comb += eq(r_data[i], self.p[i].data_i)
306 if self.masklen:
307 mlen = len(self.p[i].mask_i)
308 s = mlen*i
309 e = mlen*(i+1)
310 ml.append(Mux(vr, self.p[i].mask_i, Const(0, mlen)))
311 if self.masklen:
312 m.d.comb += self.n.mask_o.eq(Cat(*ml))
313
314 m.d.comb += eq(self.n.data_o, self.process(r_data[mid]))
315
316 return m
317
318
319 class CombMuxOutPipe(CombMultiOutPipeline):
320 def __init__(self, stage, n_len, masklen=0):
321 # HACK: stage is also the n-way multiplexer
322 CombMultiOutPipeline.__init__(self, stage, n_len=n_len,
323 n_mux=stage, masklen=masklen)
324
325 # HACK: n-mux is also the stage... so set the muxid equal to input muxid
326 print ("combmuxout", self.p.data_i.muxid)
327 stage.m_id = self.p.data_i.muxid
328
329
330
331 class InputPriorityArbiter(Elaboratable):
332 """ arbitration module for Input-Mux pipe, baed on PriorityEncoder
333 """
334 def __init__(self, pipe, num_rows):
335 self.pipe = pipe
336 self.num_rows = num_rows
337 self.mmax = int(log(self.num_rows) / log(2))
338 self.m_id = Signal(self.mmax, reset_less=True) # multiplex id
339 self.active = Signal(reset_less=True)
340
341 def elaborate(self, platform):
342 m = Module()
343
344 assert len(self.pipe.p) == self.num_rows, \
345 "must declare input to be same size"
346 pe = PriorityEncoder(self.num_rows)
347 m.submodules.selector = pe
348
349 # connect priority encoder
350 in_ready = []
351 for i in range(self.num_rows):
352 p_valid_i = Signal(reset_less=True)
353 m.d.comb += p_valid_i.eq(self.pipe.p[i].valid_i_test)
354 in_ready.append(p_valid_i)
355 m.d.comb += pe.i.eq(Cat(*in_ready)) # array of input "valids"
356 m.d.comb += self.active.eq(~pe.n) # encoder active (one input valid)
357 m.d.comb += self.m_id.eq(pe.o) # output one active input
358
359 return m
360
361 def ports(self):
362 return [self.m_id, self.active]
363
364
365
366 class PriorityCombMuxInPipe(CombMultiInPipeline):
367 """ an example of how to use the combinatorial pipeline.
368 """
369
370 def __init__(self, stage, p_len=2, masklen=0):
371 p_mux = InputPriorityArbiter(self, p_len)
372 CombMultiInPipeline.__init__(self, stage, p_len, p_mux,
373 masklen=masklen)
374
375
376 if __name__ == '__main__':
377
378 from nmutil.test.example_buf_pipe import ExampleStage
379 dut = PriorityCombMuxInPipe(ExampleStage)
380 vl = rtlil.convert(dut, ports=dut.ports())
381 with open("test_combpipe.il", "w") as f:
382 f.write(vl)