detect if data_o or data_i is iterable in NextControl and PrevControl
[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
27 from collections.abc import Sequence, Iterable
28 from collections import OrderedDict
29
30 from nmutil import nmoperator
31
32
33 class Object:
34 def __init__(self):
35 self.fields = OrderedDict()
36
37 def __setattr__(self, k, v):
38 print ("kv", k, v)
39 if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
40 k in dir(Object) or "fields" not in self.__dict__):
41 return object.__setattr__(self, k, v)
42 self.fields[k] = v
43
44 def __getattr__(self, k):
45 if k in self.__dict__:
46 return object.__getattr__(self, k)
47 try:
48 return self.fields[k]
49 except KeyError as e:
50 raise AttributeError(e)
51
52 def __iter__(self):
53 for x in self.fields.values(): # OrderedDict so order is preserved
54 if isinstance(x, Iterable):
55 yield from x
56 else:
57 yield x
58
59 def eq(self, inp):
60 res = []
61 for (k, o) in self.fields.items():
62 i = getattr(inp, k)
63 print ("eq", o, i)
64 rres = o.eq(i)
65 if isinstance(rres, Sequence):
66 res += rres
67 else:
68 res.append(rres)
69 print (res)
70 return res
71
72 def ports(self): # being called "keys" would be much better
73 return list(self)
74
75
76 class RecordObject(Record):
77 def __init__(self, layout=None, name=None):
78 Record.__init__(self, layout=layout or [], name=name)
79
80 def __setattr__(self, k, v):
81 #print (dir(Record))
82 if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
83 k in dir(Record) or "fields" not in self.__dict__):
84 return object.__setattr__(self, k, v)
85 self.fields[k] = v
86 #print ("RecordObject setattr", k, v)
87 if isinstance(v, Record):
88 newlayout = {k: (k, v.layout)}
89 elif isinstance(v, Value):
90 newlayout = {k: (k, v.shape())}
91 else:
92 newlayout = {k: (k, nmoperator.shape(v))}
93 self.layout.fields.update(newlayout)
94
95 def __iter__(self):
96 for x in self.fields.values(): # remember: fields is an OrderedDict
97 if isinstance(x, Record):
98 for f in x.fields.values():
99 yield f
100 elif isinstance(x, Iterable):
101 yield from x # a bit like flatten (nmigen.tools)
102 else:
103 yield x
104
105 def ports(self): # would be better being called "keys"
106 return list(self)
107
108
109 class PrevControl(Elaboratable):
110 """ contains signals that come *from* the previous stage (both in and out)
111 * valid_i: previous stage indicating all incoming data is valid.
112 may be a multi-bit signal, where all bits are required
113 to be asserted to indicate "valid".
114 * ready_o: output to next stage indicating readiness to accept data
115 * data_i : an input - MUST be added by the USER of this class
116 """
117
118 def __init__(self, i_width=1, stage_ctl=False, maskwid=0, offs=0):
119 self.stage_ctl = stage_ctl
120 self.maskwid = maskwid
121 if maskwid:
122 self.mask_i = Signal(maskwid) # prev >>in self
123 self.stop_i = Signal(maskwid) # prev >>in self
124 self.valid_i = Signal(i_width, name="p_valid_i") # prev >>in self
125 self._ready_o = Signal(name="p_ready_o") # prev <<out self
126 self.data_i = None # XXX MUST BE ADDED BY USER
127 if stage_ctl:
128 self.s_ready_o = Signal(name="p_s_o_rdy") # prev <<out self
129 self.trigger = Signal(reset_less=True)
130
131 @property
132 def ready_o(self):
133 """ public-facing API: indicates (externally) that stage is ready
134 """
135 if self.stage_ctl:
136 return self.s_ready_o # set dynamically by stage
137 return self._ready_o # return this when not under dynamic control
138
139 def _connect_in(self, prev, direct=False, fn=None,
140 do_data=True, do_stop=True):
141 """ internal helper function to connect stage to an input source.
142 do not use to connect stage-to-stage!
143 """
144 valid_i = prev.valid_i if direct else prev.valid_i_test
145 res = [self.valid_i.eq(valid_i),
146 prev.ready_o.eq(self.ready_o)]
147 if self.maskwid:
148 res.append(self.mask_i.eq(prev.mask_i))
149 if do_stop:
150 res.append(self.stop_i.eq(prev.stop_i))
151 if do_data is False:
152 return res
153 data_i = fn(prev.data_i) if fn is not None else prev.data_i
154 return res + [nmoperator.eq(self.data_i, data_i)]
155
156 @property
157 def valid_i_test(self):
158 vlen = len(self.valid_i)
159 if vlen > 1:
160 # multi-bit case: valid only when valid_i is all 1s
161 all1s = Const(-1, (len(self.valid_i), False))
162 valid_i = (self.valid_i == all1s)
163 else:
164 # single-bit valid_i case
165 valid_i = self.valid_i
166
167 # when stage indicates not ready, incoming data
168 # must "appear" to be not ready too
169 if self.stage_ctl:
170 valid_i = valid_i & self.s_ready_o
171
172 return valid_i
173
174 def elaborate(self, platform):
175 m = Module()
176 m.d.comb += self.trigger.eq(self.valid_i_test & self.ready_o)
177 return m
178
179 def eq(self, i):
180 res = [nmoperator.eq(self.data_i, i.data_i),
181 self.ready_o.eq(i.ready_o),
182 self.valid_i.eq(i.valid_i)]
183 if self.maskwid:
184 res.append(self.mask_i.eq(i.mask_i))
185 return res
186
187 def __iter__(self):
188 yield self.valid_i
189 yield self.ready_o
190 if self.maskwid:
191 yield self.mask_i
192 yield self.stop_i
193 if hasattr(self.data_i, "ports"):
194 yield from self.data_i.ports()
195 elif (isinstance(self.data_i, Sequence) or
196 isinstance(self.data_i, Iterable)):
197 yield from self.data_i
198 else:
199 yield self.data_i
200
201 def ports(self):
202 return list(self)
203
204
205 class NextControl(Elaboratable):
206 """ contains the signals that go *to* the next stage (both in and out)
207 * valid_o: output indicating to next stage that data is valid
208 * ready_i: input from next stage indicating that it can accept data
209 * data_o : an output - MUST be added by the USER of this class
210 """
211 def __init__(self, stage_ctl=False, maskwid=0):
212 self.stage_ctl = stage_ctl
213 self.maskwid = maskwid
214 if maskwid:
215 self.mask_o = Signal(maskwid) # self out>> next
216 self.stop_o = Signal(maskwid) # self out>> next
217 self.valid_o = Signal(name="n_valid_o") # self out>> next
218 self.ready_i = Signal(name="n_ready_i") # self <<in next
219 self.data_o = None # XXX MUST BE ADDED BY USER
220 #if self.stage_ctl:
221 self.d_valid = Signal(reset=1) # INTERNAL (data valid)
222 self.trigger = Signal(reset_less=True)
223
224 @property
225 def ready_i_test(self):
226 if self.stage_ctl:
227 return self.ready_i & self.d_valid
228 return self.ready_i
229
230 def connect_to_next(self, nxt, do_data=True, do_stop=True):
231 """ helper function to connect to the next stage data/valid/ready.
232 data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
233 use this when connecting stage-to-stage
234
235 note: a "connect_from_prev" is completely unnecessary: it's
236 just nxt.connect_to_next(self)
237 """
238 res = [nxt.valid_i.eq(self.valid_o),
239 self.ready_i.eq(nxt.ready_o)]
240 if self.maskwid:
241 res.append(nxt.mask_i.eq(self.mask_o))
242 if do_stop:
243 res.append(nxt.stop_i.eq(self.stop_o))
244 if do_data:
245 res.append(nmoperator.eq(nxt.data_i, self.data_o))
246 print ("connect to next", self, self.maskwid, nxt.data_i, do_data, do_stop)
247 return res
248
249 def _connect_out(self, nxt, direct=False, fn=None,
250 do_data=True, do_stop=True):
251 """ internal helper function to connect stage to an output source.
252 do not use to connect stage-to-stage!
253 """
254 ready_i = nxt.ready_i if direct else nxt.ready_i_test
255 res = [nxt.valid_o.eq(self.valid_o),
256 self.ready_i.eq(ready_i)]
257 if self.maskwid:
258 res.append(nxt.mask_o.eq(self.mask_o))
259 if do_stop:
260 res.append(nxt.stop_o.eq(self.stop_o))
261 if not do_data:
262 return res
263 data_o = fn(nxt.data_o) if fn is not None else nxt.data_o
264 return res + [nmoperator.eq(data_o, self.data_o)]
265
266 def elaborate(self, platform):
267 m = Module()
268 m.d.comb += self.trigger.eq(self.ready_i_test & self.valid_o)
269 return m
270
271 def __iter__(self):
272 yield self.ready_i
273 yield self.valid_o
274 if self.maskwid:
275 yield self.mask_o
276 yield self.stop_o
277 if hasattr(self.data_o, "ports"):
278 yield from self.data_o.ports()
279 elif (isinstance(self.data_o, Sequence) or
280 isinstance(self.data_o, Iterable)):
281 yield from self.data_o
282 else:
283 yield self.data_o
284
285 def ports(self):
286 return list(self)
287