speed up ==, hash, <, >, <=, and >= for plain_data
[nmutil.git] / src / nmutil / iocontrol.py
1 # SPDX-License-Identifier: LGPL-3-or-later
2 """ IO Control API
3
4 This work is funded through NLnet under Grant 2019-02-012
5
6 License: LGPLv3+
7
8
9 Associated development bugs:
10 * http://bugs.libre-riscv.org/show_bug.cgi?id=538
11 * http://bugs.libre-riscv.org/show_bug.cgi?id=148
12 * http://bugs.libre-riscv.org/show_bug.cgi?id=64
13 * http://bugs.libre-riscv.org/show_bug.cgi?id=57
14
15 Important: see Stage API (stageapi.py) in combination with below
16
17 Main classes: PrevControl and NextControl.
18
19 These classes manage the data and the synchronisation state
20 to the previous and next stage, respectively. ready/valid
21 signals are used by the Pipeline classes to tell if data
22 may be safely passed from stage to stage.
23
24 The connection from one stage to the next is carried out with
25 NextControl.connect_to_next. It is *not* necessary to have
26 a PrevControl.connect_to_prev because it is functionally
27 directly equivalent to prev->next->connect_to_next.
28 """
29
30 from nmigen import Signal, Cat, Const, Module, Value, Elaboratable
31 from nmigen.cli import verilog, rtlil
32 from nmigen.hdl.rec import Record
33 from nmigen import tracer
34
35 from collections.abc import Sequence, Iterable
36 from collections import OrderedDict
37
38 from nmutil import nmoperator
39
40
41 class Object:
42 def __init__(self):
43 self.fields = OrderedDict()
44
45 def __setattr__(self, k, v):
46 print("kv", k, v)
47 if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
48 k in dir(Object) or "fields" not in self.__dict__):
49 return object.__setattr__(self, k, v)
50 self.fields[k] = v
51
52 def __getattr__(self, k):
53 if k in self.__dict__:
54 return object.__getattr__(self, k)
55 try:
56 return self.fields[k]
57 except KeyError as e:
58 raise AttributeError(e)
59
60 def __iter__(self):
61 for x in self.fields.values(): # OrderedDict so order is preserved
62 if isinstance(x, Iterable):
63 yield from x
64 else:
65 yield x
66
67 def eq(self, inp):
68 res = []
69 for (k, o) in self.fields.items():
70 i = getattr(inp, k)
71 print("eq", o, i)
72 rres = o.eq(i)
73 if isinstance(rres, Sequence):
74 res += rres
75 else:
76 res.append(rres)
77 print(res)
78 return res
79
80 def ports(self): # being called "keys" would be much better
81 return list(self)
82
83
84 def add_prefix_to_record_signals(prefix, record):
85 """recursively hunt through Records, modifying names to add a prefix
86 """
87 for key, val in record.fields.items():
88 if isinstance(val, Signal):
89 val.name = prefix + val.name
90 elif isinstance(val, Record):
91 add_prefix_to_record_signals(prefix, val)
92
93
94 class RecordObject(Record):
95 def __init__(self, layout=None, name=None):
96 # if name is None:
97 # name = tracer.get_var_name(depth=2, default="$ro")
98 Record.__init__(self, layout=layout or [], name=name)
99
100 def __setattr__(self, k, v):
101 #print(f"RecordObject setattr({k}, {v})")
102 #print (dir(Record))
103 if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
104 k in dir(Record) or "fields" not in self.__dict__):
105 return object.__setattr__(self, k, v)
106
107 if self.name is None:
108 prefix = ""
109 else:
110 prefix = self.name + "_"
111 # Prefix the signal name with the name of the recordobject
112 if isinstance(v, Signal):
113 #print (self, self.name, v.name)
114 v.name = prefix + v.name
115 elif isinstance(v, Record):
116 add_prefix_to_record_signals(prefix, v)
117
118 self.fields[k] = v
119 #print ("RecordObject setattr", k, v)
120 if isinstance(v, Record):
121 newlayout = {k: (k, v.layout)}
122 elif isinstance(v, Value):
123 newlayout = {k: (k, v.shape())}
124 else:
125 newlayout = {k: (k, nmoperator.shape(v))}
126 self.layout.fields.update(newlayout)
127
128 def __iter__(self):
129 for x in self.fields.values(): # remember: fields is an OrderedDict
130 if hasattr(x, 'ports'):
131 yield from x.ports()
132 elif isinstance(x, Record):
133 for f in x.fields.values():
134 yield f
135 elif isinstance(x, Iterable):
136 yield from x # a bit like flatten (nmigen.tools)
137 else:
138 yield x
139
140 def ports(self): # would be better being called "keys"
141 return list(self)
142
143
144 class PrevControl(Elaboratable):
145 """ contains signals that come *from* the previous stage (both in and out)
146 * i_valid: previous stage indicating all incoming data is valid.
147 may be a multi-bit signal, where all bits are required
148 to be asserted to indicate "valid".
149 * o_ready: output to next stage indicating readiness to accept data
150 * i_data : an input - MUST be added by the USER of this class
151 """
152
153 def __init__(self, i_width=1, stage_ctl=False, maskwid=0, offs=0,
154 name=None):
155 if name is None:
156 name = ""
157 n_piv = "p_i_valid"+name
158 n_por = "p_o_ready"+name
159
160 self.stage_ctl = stage_ctl
161 self.maskwid = maskwid
162 if maskwid:
163 self.mask_i = Signal(maskwid) # prev >>in self
164 self.stop_i = Signal(maskwid) # prev >>in self
165 self.i_valid = Signal(i_width, name=n_piv) # prev >>in self
166 self._o_ready = Signal(name=n_por) # prev <<out self
167 self.i_data = None # XXX MUST BE ADDED BY USER
168 if stage_ctl:
169 self.s_o_ready = Signal(name="p_s_o_rdy") # prev <<out self
170 self.trigger = Signal(reset_less=True)
171
172 @property
173 def o_ready(self):
174 """ public-facing API: indicates (externally) that stage is ready
175 """
176 if self.stage_ctl:
177 return self.s_o_ready # set dynamically by stage
178 return self._o_ready # return this when not under dynamic control
179
180 def _connect_in(self, prev, direct=False, fn=None,
181 do_data=True, do_stop=True):
182 """ internal helper function to connect stage to an input source.
183 do not use to connect stage-to-stage!
184 """
185 i_valid = prev.i_valid if direct else prev.i_valid_test
186 res = [self.i_valid.eq(i_valid),
187 prev.o_ready.eq(self.o_ready)]
188 if self.maskwid:
189 res.append(self.mask_i.eq(prev.mask_i))
190 if do_stop:
191 res.append(self.stop_i.eq(prev.stop_i))
192 if do_data is False:
193 return res
194 i_data = fn(prev.i_data) if fn is not None else prev.i_data
195 return res + [nmoperator.eq(self.i_data, i_data)]
196
197 @property
198 def i_valid_test(self):
199 vlen = len(self.i_valid)
200 if vlen > 1:
201 # multi-bit case: valid only when i_valid is all 1s
202 all1s = Const(-1, (len(self.i_valid), False))
203 i_valid = (self.i_valid == all1s)
204 else:
205 # single-bit i_valid case
206 i_valid = self.i_valid
207
208 # when stage indicates not ready, incoming data
209 # must "appear" to be not ready too
210 if self.stage_ctl:
211 i_valid = i_valid & self.s_o_ready
212
213 return i_valid
214
215 def elaborate(self, platform):
216 m = Module()
217 m.d.comb += self.trigger.eq(self.i_valid_test & self.o_ready)
218 return m
219
220 def eq(self, i):
221 res = [nmoperator.eq(self.i_data, i.i_data),
222 self.o_ready.eq(i.o_ready),
223 self.i_valid.eq(i.i_valid)]
224 if self.maskwid:
225 res.append(self.mask_i.eq(i.mask_i))
226 return res
227
228 def __iter__(self):
229 yield self.i_valid
230 yield self.o_ready
231 if self.maskwid:
232 yield self.mask_i
233 yield self.stop_i
234 if hasattr(self.i_data, "ports"):
235 yield from self.i_data.ports()
236 elif (isinstance(self.i_data, Sequence) or
237 isinstance(self.i_data, Iterable)):
238 yield from self.i_data
239 else:
240 yield self.i_data
241
242 def ports(self):
243 return list(self)
244
245
246 class NextControl(Elaboratable):
247 """ contains the signals that go *to* the next stage (both in and out)
248 * o_valid: output indicating to next stage that data is valid
249 * i_ready: input from next stage indicating that it can accept data
250 * o_data : an output - MUST be added by the USER of this class
251 """
252
253 def __init__(self, stage_ctl=False, maskwid=0, name=None):
254 if name is None:
255 name = ""
256 n_nov = "n_o_valid"+name
257 n_nir = "n_i_ready"+name
258
259 self.stage_ctl = stage_ctl
260 self.maskwid = maskwid
261 if maskwid:
262 self.mask_o = Signal(maskwid) # self out>> next
263 self.stop_o = Signal(maskwid) # self out>> next
264 self.o_valid = Signal(name=n_nov) # self out>> next
265 self.i_ready = Signal(name=n_nir) # self <<in next
266 self.o_data = None # XXX MUST BE ADDED BY USER
267 # if self.stage_ctl:
268 self.d_valid = Signal(reset=1) # INTERNAL (data valid)
269 self.trigger = Signal(reset_less=True)
270
271 @property
272 def i_ready_test(self):
273 if self.stage_ctl:
274 return self.i_ready & self.d_valid
275 return self.i_ready
276
277 def connect_to_next(self, nxt, do_data=True, do_stop=True):
278 """ helper function to connect to the next stage data/valid/ready.
279 data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
280 use this when connecting stage-to-stage
281
282 note: a "connect_from_prev" is completely unnecessary: it's
283 just nxt.connect_to_next(self)
284 """
285 res = [nxt.i_valid.eq(self.o_valid),
286 self.i_ready.eq(nxt.o_ready)]
287 if self.maskwid:
288 res.append(nxt.mask_i.eq(self.mask_o))
289 if do_stop:
290 res.append(nxt.stop_i.eq(self.stop_o))
291 if do_data:
292 res.append(nmoperator.eq(nxt.i_data, self.o_data))
293 print("connect to next", self, self.maskwid, nxt.i_data,
294 do_data, do_stop)
295 return res
296
297 def _connect_out(self, nxt, direct=False, fn=None,
298 do_data=True, do_stop=True):
299 """ internal helper function to connect stage to an output source.
300 do not use to connect stage-to-stage!
301 """
302 i_ready = nxt.i_ready if direct else nxt.i_ready_test
303 res = [nxt.o_valid.eq(self.o_valid),
304 self.i_ready.eq(i_ready)]
305 if self.maskwid:
306 res.append(nxt.mask_o.eq(self.mask_o))
307 if do_stop:
308 res.append(nxt.stop_o.eq(self.stop_o))
309 if not do_data:
310 return res
311 o_data = fn(nxt.o_data) if fn is not None else nxt.o_data
312 return res + [nmoperator.eq(o_data, self.o_data)]
313
314 def elaborate(self, platform):
315 m = Module()
316 m.d.comb += self.trigger.eq(self.i_ready_test & self.o_valid)
317 return m
318
319 def __iter__(self):
320 yield self.i_ready
321 yield self.o_valid
322 if self.maskwid:
323 yield self.mask_o
324 yield self.stop_o
325 if hasattr(self.o_data, "ports"):
326 yield from self.o_data.ports()
327 elif (isinstance(self.o_data, Sequence) or
328 isinstance(self.o_data, Iterable)):
329 yield from self.o_data
330 else:
331 yield self.o_data
332
333 def ports(self):
334 return list(self)