92ffee20b761e37bd5d89c27d4a1beec7815a57a
[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
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 * data_i : 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 self.stage_ctl = stage_ctl
155 self.maskwid = maskwid
156 if maskwid:
157 self.mask_i = Signal(maskwid) # prev >>in self
158 self.stop_i = Signal(maskwid) # prev >>in self
159 self.i_valid = Signal(i_width, name="p_i_valid") # prev >>in self
160 self._o_ready = Signal(name="p_o_ready") # prev <<out self
161 self.data_i = None # XXX MUST BE ADDED BY USER
162 if stage_ctl:
163 self.s_o_ready = Signal(name="p_s_o_rdy") # prev <<out self
164 self.trigger = Signal(reset_less=True)
165
166 @property
167 def o_ready(self):
168 """ public-facing API: indicates (externally) that stage is ready
169 """
170 if self.stage_ctl:
171 return self.s_o_ready # set dynamically by stage
172 return self._o_ready # return this when not under dynamic control
173
174 def _connect_in(self, prev, direct=False, fn=None,
175 do_data=True, do_stop=True):
176 """ internal helper function to connect stage to an input source.
177 do not use to connect stage-to-stage!
178 """
179 i_valid = prev.i_valid if direct else prev.i_valid_test
180 res = [self.i_valid.eq(i_valid),
181 prev.o_ready.eq(self.o_ready)]
182 if self.maskwid:
183 res.append(self.mask_i.eq(prev.mask_i))
184 if do_stop:
185 res.append(self.stop_i.eq(prev.stop_i))
186 if do_data is False:
187 return res
188 data_i = fn(prev.data_i) if fn is not None else prev.data_i
189 return res + [nmoperator.eq(self.data_i, data_i)]
190
191 @property
192 def i_valid_test(self):
193 vlen = len(self.i_valid)
194 if vlen > 1:
195 # multi-bit case: valid only when i_valid is all 1s
196 all1s = Const(-1, (len(self.i_valid), False))
197 i_valid = (self.i_valid == all1s)
198 else:
199 # single-bit i_valid case
200 i_valid = self.i_valid
201
202 # when stage indicates not ready, incoming data
203 # must "appear" to be not ready too
204 if self.stage_ctl:
205 i_valid = i_valid & self.s_o_ready
206
207 return i_valid
208
209 def elaborate(self, platform):
210 m = Module()
211 m.d.comb += self.trigger.eq(self.i_valid_test & self.o_ready)
212 return m
213
214 def eq(self, i):
215 res = [nmoperator.eq(self.data_i, i.data_i),
216 self.o_ready.eq(i.o_ready),
217 self.i_valid.eq(i.i_valid)]
218 if self.maskwid:
219 res.append(self.mask_i.eq(i.mask_i))
220 return res
221
222 def __iter__(self):
223 yield self.i_valid
224 yield self.o_ready
225 if self.maskwid:
226 yield self.mask_i
227 yield self.stop_i
228 if hasattr(self.data_i, "ports"):
229 yield from self.data_i.ports()
230 elif (isinstance(self.data_i, Sequence) or
231 isinstance(self.data_i, Iterable)):
232 yield from self.data_i
233 else:
234 yield self.data_i
235
236 def ports(self):
237 return list(self)
238
239
240 class NextControl(Elaboratable):
241 """ contains the signals that go *to* the next stage (both in and out)
242 * o_valid: output indicating to next stage that data is valid
243 * i_ready: input from next stage indicating that it can accept data
244 * data_o : an output - MUST be added by the USER of this class
245 """
246 def __init__(self, stage_ctl=False, maskwid=0):
247 self.stage_ctl = stage_ctl
248 self.maskwid = maskwid
249 if maskwid:
250 self.mask_o = Signal(maskwid) # self out>> next
251 self.stop_o = Signal(maskwid) # self out>> next
252 self.o_valid = Signal(name="n_o_valid") # self out>> next
253 self.i_ready = Signal(name="n_i_ready") # self <<in next
254 self.data_o = None # XXX MUST BE ADDED BY USER
255 #if self.stage_ctl:
256 self.d_valid = Signal(reset=1) # INTERNAL (data valid)
257 self.trigger = Signal(reset_less=True)
258
259 @property
260 def i_ready_test(self):
261 if self.stage_ctl:
262 return self.i_ready & self.d_valid
263 return self.i_ready
264
265 def connect_to_next(self, nxt, do_data=True, do_stop=True):
266 """ helper function to connect to the next stage data/valid/ready.
267 data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
268 use this when connecting stage-to-stage
269
270 note: a "connect_from_prev" is completely unnecessary: it's
271 just nxt.connect_to_next(self)
272 """
273 res = [nxt.i_valid.eq(self.o_valid),
274 self.i_ready.eq(nxt.o_ready)]
275 if self.maskwid:
276 res.append(nxt.mask_i.eq(self.mask_o))
277 if do_stop:
278 res.append(nxt.stop_i.eq(self.stop_o))
279 if do_data:
280 res.append(nmoperator.eq(nxt.data_i, self.data_o))
281 print ("connect to next", self, self.maskwid, nxt.data_i,
282 do_data, do_stop)
283 return res
284
285 def _connect_out(self, nxt, direct=False, fn=None,
286 do_data=True, do_stop=True):
287 """ internal helper function to connect stage to an output source.
288 do not use to connect stage-to-stage!
289 """
290 i_ready = nxt.i_ready if direct else nxt.i_ready_test
291 res = [nxt.o_valid.eq(self.o_valid),
292 self.i_ready.eq(i_ready)]
293 if self.maskwid:
294 res.append(nxt.mask_o.eq(self.mask_o))
295 if do_stop:
296 res.append(nxt.stop_o.eq(self.stop_o))
297 if not do_data:
298 return res
299 data_o = fn(nxt.data_o) if fn is not None else nxt.data_o
300 return res + [nmoperator.eq(data_o, self.data_o)]
301
302 def elaborate(self, platform):
303 m = Module()
304 m.d.comb += self.trigger.eq(self.i_ready_test & self.o_valid)
305 return m
306
307 def __iter__(self):
308 yield self.i_ready
309 yield self.o_valid
310 if self.maskwid:
311 yield self.mask_o
312 yield self.stop_o
313 if hasattr(self.data_o, "ports"):
314 yield from self.data_o.ports()
315 elif (isinstance(self.data_o, Sequence) or
316 isinstance(self.data_o, Iterable)):
317 yield from self.data_o
318 else:
319 yield self.data_o
320
321 def ports(self):
322 return list(self)
323