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