8254795ea133798630d87480b73a012f7fca0171
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
10 stage requires compliance with a strict API that may be
11 implemented in several means, including as a static class.
13 Stages do not HOLD data, and they definitely do not contain
14 signalling (ready/valid). They do however specify the FORMAT
15 of the incoming and outgoing data, and they provide a means to
16 PROCESS that data (from incoming format to outgoing format).
18 Stage Blocks really must be combinatorial blocks. It would be ok
19 to have input come in from sync'd sources (clock-driven) however by
20 doing so they would no longer be deterministic, and chaining such
21 blocks with such side-effects together could result in unexpected,
22 unpredictable, unreproduceable behaviour.
23 So generally to be avoided, then unless you know what you are doing.
25 the methods of a stage instance must be as follows:
27 * ispec() - Input data format specification. Takes a bit of explaining.
28 The requirements are: something that eventually derives from
29 nmigen Value must be returned *OR* an iterator or iterable
30 or sequence (list, tuple etc.) or generator must *yield*
31 thing(s) that (eventually) derive from the nmigen Value class.
33 Complex to state, very simple in practice:
34 see test_buf_pipe.py for over 25 worked examples.
36 * ospec() - Output data format specification.
37 format requirements identical to ispec.
39 * process(m, i) - Optional function for processing ispec-formatted data.
40 returns a combinatorial block of a result that
41 may be assigned to the output, by way of the "nmoperator.eq"
42 function. Note that what is returned here can be
43 extremely flexible. Even a dictionary can be returned
44 as long as it has fields that match precisely with the
45 Record into which its values is intended to be assigned.
46 Again: see example unit tests for details.
48 * setup(m, i) - Optional function for setting up submodules.
49 may be used for more complex stages, to link
50 the input (i) to submodules. must take responsibility
51 for adding those submodules to the module (m).
52 the submodules must be combinatorial blocks and
53 must have their inputs and output linked combinatorially.
55 Both StageCls (for use with non-static classes) and Stage (for use
56 by static classes) are abstract classes from which, for convenience
57 and as a courtesy to other developers, anything conforming to the
58 Stage API may *choose* to derive. See Liskov Substitution Principle:
59 https://en.wikipedia.org/wiki/Liskov_substitution_principle
64 A useful combinatorial wrapper around stages that chains them together
65 and then presents a Stage-API-conformant interface. By presenting
66 the same API as the stages it wraps, it can clearly be used recursively.
71 The base class for pipelines. Contains previous and next ready/valid/data.
72 Also has an extremely useful "connect" function that can be used to
73 connect a chain of pipelines and present the exact same prev/next
76 Note: pipelines basically do not become pipelines as such until
77 handed to a derivative of ControlBase. ControlBase itself is *not*
78 strictly considered a pipeline class. Wishbone and AXI4 (master or
79 slave) could be derived from ControlBase, for example.
82 from nmigen
import Signal
, Cat
, Const
, Module
, Value
, Elaboratable
83 from nmigen
.cli
import verilog
, rtlil
84 from nmigen
.hdl
.rec
import Record
86 from collections
.abc
import Sequence
, Iterable
87 from collections
import OrderedDict
89 from nmutil
import nmoperator
94 self
.fields
= OrderedDict()
96 def __setattr__(self
, k
, v
):
98 if (k
.startswith('_') or k
in ["fields", "name", "src_loc"] or
99 k
in dir(Object
) or "fields" not in self
.__dict
__):
100 return object.__setattr
__(self
, k
, v
)
103 def __getattr__(self
, k
):
104 if k
in self
.__dict
__:
105 return object.__getattr
__(self
, k
)
107 return self
.fields
[k
]
108 except KeyError as e
:
109 raise AttributeError(e
)
112 for x
in self
.fields
.values(): # OrderedDict so order is preserved
113 if isinstance(x
, Iterable
):
120 for (k
, o
) in self
.fields
.items():
124 if isinstance(rres
, Sequence
):
131 def ports(self
): # being called "keys" would be much better
135 class RecordObject(Record
):
136 def __init__(self
, layout
=None, name
=None):
137 Record
.__init
__(self
, layout
=layout
or [], name
=name
)
139 def __setattr__(self
, k
, v
):
141 if (k
.startswith('_') or k
in ["fields", "name", "src_loc"] or
142 k
in dir(Record
) or "fields" not in self
.__dict
__):
143 return object.__setattr
__(self
, k
, v
)
145 #print ("RecordObject setattr", k, v)
146 if isinstance(v
, Record
):
147 newlayout
= {k
: (k
, v
.layout
)}
148 elif isinstance(v
, Value
):
149 newlayout
= {k
: (k
, v
.shape())}
151 newlayout
= {k
: (k
, nmoperator
.shape(v
))}
152 self
.layout
.fields
.update(newlayout
)
155 for x
in self
.fields
.values(): # remember: fields is an OrderedDict
156 if isinstance(x
, Iterable
):
157 yield from x
# a bit like flatten (nmigen.tools)
161 def ports(self
): # would be better being called "keys"
165 class PrevControl(Elaboratable
):
166 """ contains signals that come *from* the previous stage (both in and out)
167 * valid_i: previous stage indicating all incoming data is valid.
168 may be a multi-bit signal, where all bits are required
169 to be asserted to indicate "valid".
170 * ready_o: output to next stage indicating readiness to accept data
171 * data_i : an input - MUST be added by the USER of this class
174 def __init__(self
, i_width
=1, stage_ctl
=False, maskwid
=0):
175 self
.stage_ctl
= stage_ctl
176 self
.maskwid
= maskwid
178 self
.mask_i
= Signal(maskwid
) # prev >>in self
179 self
.valid_i
= Signal(i_width
, name
="p_valid_i") # prev >>in self
180 self
._ready
_o
= Signal(name
="p_ready_o") # prev <<out self
181 self
.data_i
= None # XXX MUST BE ADDED BY USER
183 self
.s_ready_o
= Signal(name
="p_s_o_rdy") # prev <<out self
184 self
.trigger
= Signal(reset_less
=True)
188 """ public-facing API: indicates (externally) that stage is ready
191 return self
.s_ready_o
# set dynamically by stage
192 return self
._ready
_o
# return this when not under dynamic control
194 def _connect_in(self
, prev
, direct
=False, fn
=None, do_data
=True):
195 """ internal helper function to connect stage to an input source.
196 do not use to connect stage-to-stage!
198 valid_i
= prev
.valid_i
if direct
else prev
.valid_i_test
199 res
= [self
.valid_i
.eq(valid_i
),
200 prev
.ready_o
.eq(self
.ready_o
)]
202 res
.append(self
.mask_i
.eq(prev
.mask_i
))
205 data_i
= fn(prev
.data_i
) if fn
is not None else prev
.data_i
206 return res
+ [nmoperator
.eq(self
.data_i
, data_i
)]
209 def valid_i_test(self
):
210 vlen
= len(self
.valid_i
)
212 # multi-bit case: valid only when valid_i is all 1s
213 all1s
= Const(-1, (len(self
.valid_i
), False))
214 valid_i
= (self
.valid_i
== all1s
)
216 # single-bit valid_i case
217 valid_i
= self
.valid_i
219 # when stage indicates not ready, incoming data
220 # must "appear" to be not ready too
222 valid_i
= valid_i
& self
.s_ready_o
226 def elaborate(self
, platform
):
228 m
.d
.comb
+= self
.trigger
.eq(self
.valid_i_test
& self
.ready_o
)
232 res
= [nmoperator
.eq(self
.data_i
, i
.data_i
),
233 self
.ready_o
.eq(i
.ready_o
),
234 self
.valid_i
.eq(i
.valid_i
)]
236 res
.append(self
.mask_i
.eq(i
.mask_i
))
244 if hasattr(self
.data_i
, "ports"):
245 yield from self
.data_i
.ports()
246 elif isinstance(self
.data_i
, Sequence
):
247 yield from self
.data_i
255 class NextControl(Elaboratable
):
256 """ contains the signals that go *to* the next stage (both in and out)
257 * valid_o: output indicating to next stage that data is valid
258 * ready_i: input from next stage indicating that it can accept data
259 * data_o : an output - MUST be added by the USER of this class
261 def __init__(self
, stage_ctl
=False, maskwid
=0):
262 self
.stage_ctl
= stage_ctl
263 self
.maskwid
= maskwid
265 self
.mask_o
= Signal(maskwid
) # self out>> next
266 self
.valid_o
= Signal(name
="n_valid_o") # self out>> next
267 self
.ready_i
= Signal(name
="n_ready_i") # self <<in next
268 self
.data_o
= None # XXX MUST BE ADDED BY USER
270 self
.d_valid
= Signal(reset
=1) # INTERNAL (data valid)
271 self
.trigger
= Signal(reset_less
=True)
274 def ready_i_test(self
):
276 return self
.ready_i
& self
.d_valid
279 def connect_to_next(self
, nxt
, do_data
=True):
280 """ helper function to connect to the next stage data/valid/ready.
281 data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
282 use this when connecting stage-to-stage
284 note: a "connect_from_prev" is completely unnecessary: it's
285 just nxt.connect_to_next(self)
287 res
= [nxt
.valid_i
.eq(self
.valid_o
),
288 self
.ready_i
.eq(nxt
.ready_o
)]
290 res
.append(nxt
.mask_i
.eq(self
.mask_o
))
292 res
.append(nmoperator
.eq(nxt
.data_i
, self
.data_o
))
295 def _connect_out(self
, nxt
, direct
=False, fn
=None, do_data
=True):
296 """ internal helper function to connect stage to an output source.
297 do not use to connect stage-to-stage!
299 ready_i
= nxt
.ready_i
if direct
else nxt
.ready_i_test
300 res
= [nxt
.valid_o
.eq(self
.valid_o
),
301 self
.ready_i
.eq(ready_i
)]
303 res
.append(nxt
.mask_o
.eq(self
.mask_o
))
306 data_o
= fn(nxt
.data_o
) if fn
is not None else nxt
.data_o
307 return res
+ [nmoperator
.eq(data_o
, self
.data_o
)]
309 def elaborate(self
, platform
):
311 m
.d
.comb
+= self
.trigger
.eq(self
.ready_i_test
& self
.valid_o
)
319 if hasattr(self
.data_o
, "ports"):
320 yield from self
.data_o
.ports()
321 elif isinstance(self
.data_o
, Sequence
):
322 yield from self
.data_o