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