3 This work is funded through NLnet under Grant 2019-02-012
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
14 Important: see Stage API (stageapi.py) in combination with below
16 Main classes: PrevControl and NextControl.
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.
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.
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
34 from collections
.abc
import Sequence
, Iterable
35 from collections
import OrderedDict
37 from nmutil
import nmoperator
42 self
.fields
= OrderedDict()
44 def __setattr__(self
, 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
)
51 def __getattr__(self
, k
):
52 if k
in self
.__dict
__:
53 return object.__getattr
__(self
, k
)
57 raise AttributeError(e
)
60 for x
in self
.fields
.values(): # OrderedDict so order is preserved
61 if isinstance(x
, Iterable
):
68 for (k
, o
) in self
.fields
.items():
72 if isinstance(rres
, Sequence
):
79 def ports(self
): # being called "keys" would be much better
83 def add_prefix_to_record_signals(prefix
, record
):
84 """recursively hunt through Records, modifying names to add a prefix
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
)
93 class RecordObject(Record
):
94 def __init__(self
, layout
=None, name
=None):
96 # name = tracer.get_var_name(depth=2, default="$ro")
97 Record
.__init
__(self
, layout
=layout
or [], name
=name
)
100 def __setattr__(self
, k
, v
):
101 #print(f"RecordObject setattr({k}, {v})")
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
)
107 if self
.name
is None:
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
)
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())}
125 newlayout
= {k
: (k
, nmoperator
.shape(v
))}
126 self
.layout
.fields
.update(newlayout
)
129 for x
in self
.fields
.values(): # remember: fields is an OrderedDict
130 if hasattr(x
, 'ports'):
132 elif isinstance(x
, Record
):
133 for f
in x
.fields
.values():
135 elif isinstance(x
, Iterable
):
136 yield from x
# a bit like flatten (nmigen.tools)
140 def ports(self
): # would be better being called "keys"
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 * i_data : an input - MUST be added by the USER of this class
153 def __init__(self
, i_width
=1, stage_ctl
=False, maskwid
=0, offs
=0,
157 n_piv
= "p_i_valid"+name
158 n_por
= "p_o_ready"+name
160 self
.stage_ctl
= stage_ctl
161 self
.maskwid
= maskwid
163 self
.mask_i
= Signal(maskwid
) # prev >>in self
164 self
.stop_i
= Signal(maskwid
) # prev >>in self
165 self
.i_valid
= Signal(i_width
, name
=n_piv
) # prev >>in self
166 self
._o
_ready
= Signal(name
=n_por
) # prev <<out self
167 self
.i_data
= None # XXX MUST BE ADDED BY USER
169 self
.s_o_ready
= Signal(name
="p_s_o_rdy") # prev <<out self
170 self
.trigger
= Signal(reset_less
=True)
174 """ public-facing API: indicates (externally) that stage is ready
177 return self
.s_o_ready
# set dynamically by stage
178 return self
._o
_ready
# return this when not under dynamic control
180 def _connect_in(self
, prev
, direct
=False, fn
=None,
181 do_data
=True, do_stop
=True):
182 """ internal helper function to connect stage to an input source.
183 do not use to connect stage-to-stage!
185 i_valid
= prev
.i_valid
if direct
else prev
.i_valid_test
186 res
= [self
.i_valid
.eq(i_valid
),
187 prev
.o_ready
.eq(self
.o_ready
)]
189 res
.append(self
.mask_i
.eq(prev
.mask_i
))
191 res
.append(self
.stop_i
.eq(prev
.stop_i
))
194 i_data
= fn(prev
.i_data
) if fn
is not None else prev
.i_data
195 return res
+ [nmoperator
.eq(self
.i_data
, i_data
)]
198 def i_valid_test(self
):
199 vlen
= len(self
.i_valid
)
201 # multi-bit case: valid only when i_valid is all 1s
202 all1s
= Const(-1, (len(self
.i_valid
), False))
203 i_valid
= (self
.i_valid
== all1s
)
205 # single-bit i_valid case
206 i_valid
= self
.i_valid
208 # when stage indicates not ready, incoming data
209 # must "appear" to be not ready too
211 i_valid
= i_valid
& self
.s_o_ready
215 def elaborate(self
, platform
):
217 m
.d
.comb
+= self
.trigger
.eq(self
.i_valid_test
& self
.o_ready
)
221 res
= [nmoperator
.eq(self
.i_data
, i
.i_data
),
222 self
.o_ready
.eq(i
.o_ready
),
223 self
.i_valid
.eq(i
.i_valid
)]
225 res
.append(self
.mask_i
.eq(i
.mask_i
))
234 if hasattr(self
.i_data
, "ports"):
235 yield from self
.i_data
.ports()
236 elif (isinstance(self
.i_data
, Sequence
) or
237 isinstance(self
.i_data
, Iterable
)):
238 yield from self
.i_data
246 class NextControl(Elaboratable
):
247 """ contains the signals that go *to* the next stage (both in and out)
248 * o_valid: output indicating to next stage that data is valid
249 * i_ready: input from next stage indicating that it can accept data
250 * o_data : an output - MUST be added by the USER of this class
252 def __init__(self
, stage_ctl
=False, maskwid
=0, name
=None):
255 n_nov
= "n_o_valid"+name
256 n_nir
= "n_i_ready"+name
258 self
.stage_ctl
= stage_ctl
259 self
.maskwid
= maskwid
261 self
.mask_o
= Signal(maskwid
) # self out>> next
262 self
.stop_o
= Signal(maskwid
) # self out>> next
263 self
.o_valid
= Signal(name
=n_nov
) # self out>> next
264 self
.i_ready
= Signal(name
=n_nir
) # self <<in next
265 self
.o_data
= None # XXX MUST BE ADDED BY USER
267 self
.d_valid
= Signal(reset
=1) # INTERNAL (data valid)
268 self
.trigger
= Signal(reset_less
=True)
271 def i_ready_test(self
):
273 return self
.i_ready
& self
.d_valid
276 def connect_to_next(self
, nxt
, do_data
=True, do_stop
=True):
277 """ helper function to connect to the next stage data/valid/ready.
278 data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
279 use this when connecting stage-to-stage
281 note: a "connect_from_prev" is completely unnecessary: it's
282 just nxt.connect_to_next(self)
284 res
= [nxt
.i_valid
.eq(self
.o_valid
),
285 self
.i_ready
.eq(nxt
.o_ready
)]
287 res
.append(nxt
.mask_i
.eq(self
.mask_o
))
289 res
.append(nxt
.stop_i
.eq(self
.stop_o
))
291 res
.append(nmoperator
.eq(nxt
.i_data
, self
.o_data
))
292 print ("connect to next", self
, self
.maskwid
, nxt
.i_data
,
296 def _connect_out(self
, nxt
, direct
=False, fn
=None,
297 do_data
=True, do_stop
=True):
298 """ internal helper function to connect stage to an output source.
299 do not use to connect stage-to-stage!
301 i_ready
= nxt
.i_ready
if direct
else nxt
.i_ready_test
302 res
= [nxt
.o_valid
.eq(self
.o_valid
),
303 self
.i_ready
.eq(i_ready
)]
305 res
.append(nxt
.mask_o
.eq(self
.mask_o
))
307 res
.append(nxt
.stop_o
.eq(self
.stop_o
))
310 o_data
= fn(nxt
.o_data
) if fn
is not None else nxt
.o_data
311 return res
+ [nmoperator
.eq(o_data
, self
.o_data
)]
313 def elaborate(self
, platform
):
315 m
.d
.comb
+= self
.trigger
.eq(self
.i_ready_test
& self
.o_valid
)
324 if hasattr(self
.o_data
, "ports"):
325 yield from self
.o_data
.ports()
326 elif (isinstance(self
.o_data
, Sequence
) or
327 isinstance(self
.o_data
, Iterable
)):
328 yield from self
.o_data