concatenate mask bits on fan-in, split on fan-out
[ieee754fpu.git] / src / nmutil / iocontrol.py
1 """ IO Control API
2
3 Associated development bugs:
4 * http://bugs.libre-riscv.org/show_bug.cgi?id=64
5 * http://bugs.libre-riscv.org/show_bug.cgi?id=57
6
7 Stage API:
8 ---------
9
10 stage requires compliance with a strict API that may be
11 implemented in several means, including as a static class.
12
13 Stages do not HOLD data, and they definitely do not contain
14 signalling (ready/valid). They do however specify the FORMAT
15 of the incoming and outgoing data, and they provide a means to
16 PROCESS that data (from incoming format to outgoing format).
17
18 Stage Blocks really must be combinatorial blocks. It would be ok
19 to have input come in from sync'd sources (clock-driven) however by
20 doing so they would no longer be deterministic, and chaining such
21 blocks with such side-effects together could result in unexpected,
22 unpredictable, unreproduceable behaviour.
23 So generally to be avoided, then unless you know what you are doing.
24
25 the methods of a stage instance must be as follows:
26
27 * ispec() - Input data format specification. Takes a bit of explaining.
28 The requirements are: something that eventually derives from
29 nmigen Value must be returned *OR* an iterator or iterable
30 or sequence (list, tuple etc.) or generator must *yield*
31 thing(s) that (eventually) derive from the nmigen Value class.
32
33 Complex to state, very simple in practice:
34 see test_buf_pipe.py for over 25 worked examples.
35
36 * ospec() - Output data format specification.
37 format requirements identical to ispec.
38
39 * process(m, i) - Optional function for processing ispec-formatted data.
40 returns a combinatorial block of a result that
41 may be assigned to the output, by way of the "nmoperator.eq"
42 function. Note that what is returned here can be
43 extremely flexible. Even a dictionary can be returned
44 as long as it has fields that match precisely with the
45 Record into which its values is intended to be assigned.
46 Again: see example unit tests for details.
47
48 * setup(m, i) - Optional function for setting up submodules.
49 may be used for more complex stages, to link
50 the input (i) to submodules. must take responsibility
51 for adding those submodules to the module (m).
52 the submodules must be combinatorial blocks and
53 must have their inputs and output linked combinatorially.
54
55 Both StageCls (for use with non-static classes) and Stage (for use
56 by static classes) are abstract classes from which, for convenience
57 and as a courtesy to other developers, anything conforming to the
58 Stage API may *choose* to derive. See Liskov Substitution Principle:
59 https://en.wikipedia.org/wiki/Liskov_substitution_principle
60
61 StageChain:
62 ----------
63
64 A useful combinatorial wrapper around stages that chains them together
65 and then presents a Stage-API-conformant interface. By presenting
66 the same API as the stages it wraps, it can clearly be used recursively.
67
68 ControlBase:
69 -----------
70
71 The base class for pipelines. Contains previous and next ready/valid/data.
72 Also has an extremely useful "connect" function that can be used to
73 connect a chain of pipelines and present the exact same prev/next
74 ready/valid/data API.
75
76 Note: pipelines basically do not become pipelines as such until
77 handed to a derivative of ControlBase. ControlBase itself is *not*
78 strictly considered a pipeline class. Wishbone and AXI4 (master or
79 slave) could be derived from ControlBase, for example.
80 """
81
82 from nmigen import Signal, Cat, Const, Module, Value, Elaboratable
83 from nmigen.cli import verilog, rtlil
84 from nmigen.hdl.rec import Record
85
86 from collections.abc import Sequence, Iterable
87 from collections import OrderedDict
88
89 from nmutil import nmoperator
90
91
92 class Object:
93 def __init__(self):
94 self.fields = OrderedDict()
95
96 def __setattr__(self, k, v):
97 print ("kv", k, v)
98 if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
99 k in dir(Object) or "fields" not in self.__dict__):
100 return object.__setattr__(self, k, v)
101 self.fields[k] = v
102
103 def __getattr__(self, k):
104 if k in self.__dict__:
105 return object.__getattr__(self, k)
106 try:
107 return self.fields[k]
108 except KeyError as e:
109 raise AttributeError(e)
110
111 def __iter__(self):
112 for x in self.fields.values(): # OrderedDict so order is preserved
113 if isinstance(x, Iterable):
114 yield from x
115 else:
116 yield x
117
118 def eq(self, inp):
119 res = []
120 for (k, o) in self.fields.items():
121 i = getattr(inp, k)
122 print ("eq", o, i)
123 rres = o.eq(i)
124 if isinstance(rres, Sequence):
125 res += rres
126 else:
127 res.append(rres)
128 print (res)
129 return res
130
131 def ports(self): # being called "keys" would be much better
132 return list(self)
133
134
135 class RecordObject(Record):
136 def __init__(self, layout=None, name=None):
137 Record.__init__(self, layout=layout or [], name=name)
138
139 def __setattr__(self, k, v):
140 #print (dir(Record))
141 if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
142 k in dir(Record) or "fields" not in self.__dict__):
143 return object.__setattr__(self, k, v)
144 self.fields[k] = v
145 #print ("RecordObject setattr", k, v)
146 if isinstance(v, Record):
147 newlayout = {k: (k, v.layout)}
148 elif isinstance(v, Value):
149 newlayout = {k: (k, v.shape())}
150 else:
151 newlayout = {k: (k, nmoperator.shape(v))}
152 self.layout.fields.update(newlayout)
153
154 def __iter__(self):
155 for x in self.fields.values(): # remember: fields is an OrderedDict
156 if isinstance(x, Iterable):
157 yield from x # a bit like flatten (nmigen.tools)
158 else:
159 yield x
160
161 def ports(self): # would be better being called "keys"
162 return list(self)
163
164
165 class PrevControl(Elaboratable):
166 """ contains signals that come *from* the previous stage (both in and out)
167 * valid_i: previous stage indicating all incoming data is valid.
168 may be a multi-bit signal, where all bits are required
169 to be asserted to indicate "valid".
170 * ready_o: output to next stage indicating readiness to accept data
171 * data_i : an input - MUST be added by the USER of this class
172 """
173
174 def __init__(self, i_width=1, stage_ctl=False, maskwid=0, offs=0):
175 self.stage_ctl = stage_ctl
176 self.maskwid = maskwid
177 if maskwid:
178 self.mask_i = Signal(maskwid) # prev >>in self
179 self.stop_i = Signal(maskwid) # prev >>in self
180 self.valid_i = Signal(i_width, name="p_valid_i") # prev >>in self
181 self._ready_o = Signal(name="p_ready_o") # prev <<out self
182 self.data_i = None # XXX MUST BE ADDED BY USER
183 if stage_ctl:
184 self.s_ready_o = Signal(name="p_s_o_rdy") # prev <<out self
185 self.trigger = Signal(reset_less=True)
186
187 @property
188 def ready_o(self):
189 """ public-facing API: indicates (externally) that stage is ready
190 """
191 if self.stage_ctl:
192 return self.s_ready_o # set dynamically by stage
193 return self._ready_o # return this when not under dynamic control
194
195 def _connect_in(self, prev, direct=False, fn=None,
196 do_data=True, do_stop=True):
197 """ internal helper function to connect stage to an input source.
198 do not use to connect stage-to-stage!
199 """
200 valid_i = prev.valid_i if direct else prev.valid_i_test
201 res = [self.valid_i.eq(valid_i),
202 prev.ready_o.eq(self.ready_o)]
203 if self.maskwid:
204 res.append(self.mask_i.eq(prev.mask_i))
205 if do_stop:
206 res.append(self.stop_i.eq(prev.stop_i))
207 if do_data is False:
208 return res
209 data_i = fn(prev.data_i) if fn is not None else prev.data_i
210 return res + [nmoperator.eq(self.data_i, data_i)]
211
212 @property
213 def valid_i_test(self):
214 vlen = len(self.valid_i)
215 if vlen > 1:
216 # multi-bit case: valid only when valid_i is all 1s
217 all1s = Const(-1, (len(self.valid_i), False))
218 valid_i = (self.valid_i == all1s)
219 else:
220 # single-bit valid_i case
221 valid_i = self.valid_i
222
223 # when stage indicates not ready, incoming data
224 # must "appear" to be not ready too
225 if self.stage_ctl:
226 valid_i = valid_i & self.s_ready_o
227
228 return valid_i
229
230 def elaborate(self, platform):
231 m = Module()
232 m.d.comb += self.trigger.eq(self.valid_i_test & self.ready_o)
233 return m
234
235 def eq(self, i):
236 res = [nmoperator.eq(self.data_i, i.data_i),
237 self.ready_o.eq(i.ready_o),
238 self.valid_i.eq(i.valid_i)]
239 if self.maskwid:
240 res.append(self.mask_i.eq(i.mask_i))
241 return res
242
243 def __iter__(self):
244 yield self.valid_i
245 yield self.ready_o
246 if self.maskwid:
247 yield self.mask_i
248 yield self.stop_i
249 if hasattr(self.data_i, "ports"):
250 yield from self.data_i.ports()
251 elif isinstance(self.data_i, Sequence):
252 yield from self.data_i
253 else:
254 yield self.data_i
255
256 def ports(self):
257 return list(self)
258
259
260 class NextControl(Elaboratable):
261 """ contains the signals that go *to* the next stage (both in and out)
262 * valid_o: output indicating to next stage that data is valid
263 * ready_i: input from next stage indicating that it can accept data
264 * data_o : an output - MUST be added by the USER of this class
265 """
266 def __init__(self, stage_ctl=False, maskwid=0):
267 self.stage_ctl = stage_ctl
268 self.maskwid = maskwid
269 if maskwid:
270 self.mask_o = Signal(maskwid) # self out>> next
271 self.stop_o = Signal(maskwid) # self out>> next
272 self.valid_o = Signal(name="n_valid_o") # self out>> next
273 self.ready_i = Signal(name="n_ready_i") # self <<in next
274 self.data_o = None # XXX MUST BE ADDED BY USER
275 #if self.stage_ctl:
276 self.d_valid = Signal(reset=1) # INTERNAL (data valid)
277 self.trigger = Signal(reset_less=True)
278
279 @property
280 def ready_i_test(self):
281 if self.stage_ctl:
282 return self.ready_i & self.d_valid
283 return self.ready_i
284
285 def connect_to_next(self, nxt, do_data=True, do_stop=True):
286 """ helper function to connect to the next stage data/valid/ready.
287 data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
288 use this when connecting stage-to-stage
289
290 note: a "connect_from_prev" is completely unnecessary: it's
291 just nxt.connect_to_next(self)
292 """
293 res = [nxt.valid_i.eq(self.valid_o),
294 self.ready_i.eq(nxt.ready_o)]
295 if self.maskwid:
296 res.append(nxt.mask_i.eq(self.mask_o))
297 if do_stop:
298 res.append(nxt.stop_i.eq(self.stop_o))
299 if do_data:
300 res.append(nmoperator.eq(nxt.data_i, self.data_o))
301 return res
302
303 def _connect_out(self, nxt, direct=False, fn=None,
304 do_data=True, do_stop=True):
305 """ internal helper function to connect stage to an output source.
306 do not use to connect stage-to-stage!
307 """
308 ready_i = nxt.ready_i if direct else nxt.ready_i_test
309 res = [nxt.valid_o.eq(self.valid_o),
310 self.ready_i.eq(ready_i)]
311 if self.maskwid:
312 res.append(nxt.mask_o.eq(self.mask_o))
313 if do_stop:
314 res.append(nxt.stop_o.eq(self.stop_o))
315 if not do_data:
316 return res
317 data_o = fn(nxt.data_o) if fn is not None else nxt.data_o
318 return res + [nmoperator.eq(data_o, self.data_o)]
319
320 def elaborate(self, platform):
321 m = Module()
322 m.d.comb += self.trigger.eq(self.ready_i_test & self.valid_o)
323 return m
324
325 def __iter__(self):
326 yield self.ready_i
327 yield self.valid_o
328 if self.maskwid:
329 yield self.mask_o
330 yield self.stop_o
331 if hasattr(self.data_o, "ports"):
332 yield from self.data_o.ports()
333 elif isinstance(self.data_o, Sequence):
334 yield from self.data_o
335 else:
336 yield self.data_o
337
338 def ports(self):
339 return list(self)
340