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