add bug #148 record
[ieee754fpu.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, Iterable):
98 yield from x # a bit like flatten (nmigen.tools)
99 else:
100 yield x
101
102 def ports(self): # would be better being called "keys"
103 return list(self)
104
105
106 class PrevControl(Elaboratable):
107 """ contains signals that come *from* the previous stage (both in and out)
108 * valid_i: previous stage indicating all incoming data is valid.
109 may be a multi-bit signal, where all bits are required
110 to be asserted to indicate "valid".
111 * ready_o: output to next stage indicating readiness to accept data
112 * data_i : an input - MUST be added by the USER of this class
113 """
114
115 def __init__(self, i_width=1, stage_ctl=False, maskwid=0, offs=0):
116 self.stage_ctl = stage_ctl
117 self.maskwid = maskwid
118 if maskwid:
119 self.mask_i = Signal(maskwid) # prev >>in self
120 self.stop_i = Signal(maskwid) # prev >>in self
121 self.valid_i = Signal(i_width, name="p_valid_i") # prev >>in self
122 self._ready_o = Signal(name="p_ready_o") # prev <<out self
123 self.data_i = None # XXX MUST BE ADDED BY USER
124 if stage_ctl:
125 self.s_ready_o = Signal(name="p_s_o_rdy") # prev <<out self
126 self.trigger = Signal(reset_less=True)
127
128 @property
129 def ready_o(self):
130 """ public-facing API: indicates (externally) that stage is ready
131 """
132 if self.stage_ctl:
133 return self.s_ready_o # set dynamically by stage
134 return self._ready_o # return this when not under dynamic control
135
136 def _connect_in(self, prev, direct=False, fn=None,
137 do_data=True, do_stop=True):
138 """ internal helper function to connect stage to an input source.
139 do not use to connect stage-to-stage!
140 """
141 valid_i = prev.valid_i if direct else prev.valid_i_test
142 res = [self.valid_i.eq(valid_i),
143 prev.ready_o.eq(self.ready_o)]
144 if self.maskwid:
145 res.append(self.mask_i.eq(prev.mask_i))
146 if do_stop:
147 res.append(self.stop_i.eq(prev.stop_i))
148 if do_data is False:
149 return res
150 data_i = fn(prev.data_i) if fn is not None else prev.data_i
151 return res + [nmoperator.eq(self.data_i, data_i)]
152
153 @property
154 def valid_i_test(self):
155 vlen = len(self.valid_i)
156 if vlen > 1:
157 # multi-bit case: valid only when valid_i is all 1s
158 all1s = Const(-1, (len(self.valid_i), False))
159 valid_i = (self.valid_i == all1s)
160 else:
161 # single-bit valid_i case
162 valid_i = self.valid_i
163
164 # when stage indicates not ready, incoming data
165 # must "appear" to be not ready too
166 if self.stage_ctl:
167 valid_i = valid_i & self.s_ready_o
168
169 return valid_i
170
171 def elaborate(self, platform):
172 m = Module()
173 m.d.comb += self.trigger.eq(self.valid_i_test & self.ready_o)
174 return m
175
176 def eq(self, i):
177 res = [nmoperator.eq(self.data_i, i.data_i),
178 self.ready_o.eq(i.ready_o),
179 self.valid_i.eq(i.valid_i)]
180 if self.maskwid:
181 res.append(self.mask_i.eq(i.mask_i))
182 return res
183
184 def __iter__(self):
185 yield self.valid_i
186 yield self.ready_o
187 if self.maskwid:
188 yield self.mask_i
189 yield self.stop_i
190 if hasattr(self.data_i, "ports"):
191 yield from self.data_i.ports()
192 elif isinstance(self.data_i, Sequence):
193 yield from self.data_i
194 else:
195 yield self.data_i
196
197 def ports(self):
198 return list(self)
199
200
201 class NextControl(Elaboratable):
202 """ contains the signals that go *to* the next stage (both in and out)
203 * valid_o: output indicating to next stage that data is valid
204 * ready_i: input from next stage indicating that it can accept data
205 * data_o : an output - MUST be added by the USER of this class
206 """
207 def __init__(self, stage_ctl=False, maskwid=0):
208 self.stage_ctl = stage_ctl
209 self.maskwid = maskwid
210 if maskwid:
211 self.mask_o = Signal(maskwid) # self out>> next
212 self.stop_o = Signal(maskwid) # self out>> next
213 self.valid_o = Signal(name="n_valid_o") # self out>> next
214 self.ready_i = Signal(name="n_ready_i") # self <<in next
215 self.data_o = None # XXX MUST BE ADDED BY USER
216 #if self.stage_ctl:
217 self.d_valid = Signal(reset=1) # INTERNAL (data valid)
218 self.trigger = Signal(reset_less=True)
219
220 @property
221 def ready_i_test(self):
222 if self.stage_ctl:
223 return self.ready_i & self.d_valid
224 return self.ready_i
225
226 def connect_to_next(self, nxt, do_data=True, do_stop=True):
227 """ helper function to connect to the next stage data/valid/ready.
228 data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
229 use this when connecting stage-to-stage
230
231 note: a "connect_from_prev" is completely unnecessary: it's
232 just nxt.connect_to_next(self)
233 """
234 res = [nxt.valid_i.eq(self.valid_o),
235 self.ready_i.eq(nxt.ready_o)]
236 if self.maskwid:
237 res.append(nxt.mask_i.eq(self.mask_o))
238 if do_stop:
239 res.append(nxt.stop_i.eq(self.stop_o))
240 if do_data:
241 res.append(nmoperator.eq(nxt.data_i, self.data_o))
242 print ("connect to next", self, self.maskwid, nxt.data_i, do_data, do_stop)
243 return res
244
245 def _connect_out(self, nxt, direct=False, fn=None,
246 do_data=True, do_stop=True):
247 """ internal helper function to connect stage to an output source.
248 do not use to connect stage-to-stage!
249 """
250 ready_i = nxt.ready_i if direct else nxt.ready_i_test
251 res = [nxt.valid_o.eq(self.valid_o),
252 self.ready_i.eq(ready_i)]
253 if self.maskwid:
254 res.append(nxt.mask_o.eq(self.mask_o))
255 if do_stop:
256 res.append(nxt.stop_o.eq(self.stop_o))
257 if not do_data:
258 return res
259 data_o = fn(nxt.data_o) if fn is not None else nxt.data_o
260 return res + [nmoperator.eq(data_o, self.data_o)]
261
262 def elaborate(self, platform):
263 m = Module()
264 m.d.comb += self.trigger.eq(self.ready_i_test & self.valid_o)
265 return m
266
267 def __iter__(self):
268 yield self.ready_i
269 yield self.valid_o
270 if self.maskwid:
271 yield self.mask_o
272 yield self.stop_o
273 if hasattr(self.data_o, "ports"):
274 yield from self.data_o.ports()
275 elif isinstance(self.data_o, Sequence):
276 yield from self.data_o
277 else:
278 yield self.data_o
279
280 def ports(self):
281 return list(self)
282