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