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