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