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