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
, Mux
, Module
, Value
, Elaboratable
83 from nmigen
.cli
import verilog
, rtlil
84 from nmigen
.hdl
.rec
import Record
86 from abc
import ABCMeta
, abstractmethod
87 from collections
.abc
import Sequence
, Iterable
88 from collections
import OrderedDict
96 self
.fields
= OrderedDict()
98 def __setattr__(self
, k
, v
):
100 if (k
.startswith('_') or k
in ["fields", "name", "src_loc"] or
101 k
in dir(Object
) or "fields" not in self
.__dict
__):
102 return object.__setattr
__(self
, k
, v
)
105 def __getattr__(self
, k
):
106 if k
in self
.__dict
__:
107 return object.__getattr
__(self
, k
)
109 return self
.fields
[k
]
110 except KeyError as e
:
111 raise AttributeError(e
)
114 for x
in self
.fields
.values(): # OrderedDict so order is preserved
115 if isinstance(x
, Iterable
):
122 for (k
, o
) in self
.fields
.items():
126 if isinstance(rres
, Sequence
):
133 def ports(self
): # being called "keys" would be much better
137 class RecordObject(Record
):
138 def __init__(self
, layout
=None, name
=None):
139 Record
.__init
__(self
, layout
=layout
or [], name
=None)
141 def __setattr__(self
, k
, v
):
143 if (k
.startswith('_') or k
in ["fields", "name", "src_loc"] or
144 k
in dir(Record
) or "fields" not in self
.__dict
__):
145 return object.__setattr
__(self
, k
, v
)
147 #print ("RecordObject setattr", k, v)
148 if isinstance(v
, Record
):
149 newlayout
= {k
: (k
, v
.layout
)}
150 elif isinstance(v
, Value
):
151 newlayout
= {k
: (k
, v
.shape())}
153 newlayout
= {k
: (k
, nmoperator
.shape(v
))}
154 self
.layout
.fields
.update(newlayout
)
157 for x
in self
.fields
.values(): # remember: fields is an OrderedDict
158 if isinstance(x
, Iterable
):
159 yield from x
# a bit like flatten (nmigen.tools)
163 def ports(self
): # would be better being called "keys"
167 def _spec(fn
, name
=None):
170 varnames
= dict(inspect
.getmembers(fn
.__code
__))['co_varnames']
171 if 'name' in varnames
:
176 class PrevControl(Elaboratable
):
177 """ contains signals that come *from* the previous stage (both in and out)
178 * valid_i: previous stage indicating all incoming data is valid.
179 may be a multi-bit signal, where all bits are required
180 to be asserted to indicate "valid".
181 * ready_o: output to next stage indicating readiness to accept data
182 * data_i : an input - MUST be added by the USER of this class
185 def __init__(self
, i_width
=1, stage_ctl
=False):
186 self
.stage_ctl
= stage_ctl
187 self
.valid_i
= Signal(i_width
, name
="p_valid_i") # prev >>in self
188 self
._ready
_o
= Signal(name
="p_ready_o") # prev <<out self
189 self
.data_i
= None # XXX MUST BE ADDED BY USER
191 self
.s_ready_o
= Signal(name
="p_s_o_rdy") # prev <<out self
192 self
.trigger
= Signal(reset_less
=True)
196 """ public-facing API: indicates (externally) that stage is ready
199 return self
.s_ready_o
# set dynamically by stage
200 return self
._ready
_o
# return this when not under dynamic control
202 def _connect_in(self
, prev
, direct
=False, fn
=None):
203 """ internal helper function to connect stage to an input source.
204 do not use to connect stage-to-stage!
206 valid_i
= prev
.valid_i
if direct
else prev
.valid_i_test
207 data_i
= fn(prev
.data_i
) if fn
is not None else prev
.data_i
208 return [self
.valid_i
.eq(valid_i
),
209 prev
.ready_o
.eq(self
.ready_o
),
210 nmoperator
.eq(self
.data_i
, data_i
),
214 def valid_i_test(self
):
215 vlen
= len(self
.valid_i
)
217 # multi-bit case: valid only when valid_i is all 1s
218 all1s
= Const(-1, (len(self
.valid_i
), False))
219 valid_i
= (self
.valid_i
== all1s
)
221 # single-bit valid_i case
222 valid_i
= self
.valid_i
224 # when stage indicates not ready, incoming data
225 # must "appear" to be not ready too
227 valid_i
= valid_i
& self
.s_ready_o
231 def elaborate(self
, platform
):
233 m
.d
.comb
+= self
.trigger
.eq(self
.valid_i_test
& self
.ready_o
)
237 return [self
.data_i
.eq(i
.data_i
),
238 self
.ready_o
.eq(i
.ready_o
),
239 self
.valid_i
.eq(i
.valid_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):
262 self
.stage_ctl
= stage_ctl
263 self
.valid_o
= Signal(name
="n_valid_o") # self out>> next
264 self
.ready_i
= Signal(name
="n_ready_i") # self <<in next
265 self
.data_o
= 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 ready_i_test(self
):
273 return self
.ready_i
& self
.d_valid
276 def connect_to_next(self
, nxt
):
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 return [nxt
.valid_i
.eq(self
.valid_o
),
282 self
.ready_i
.eq(nxt
.ready_o
),
283 nmoperator
.eq(nxt
.data_i
, self
.data_o
),
286 def _connect_out(self
, nxt
, direct
=False, fn
=None):
287 """ internal helper function to connect stage to an output source.
288 do not use to connect stage-to-stage!
290 ready_i
= nxt
.ready_i
if direct
else nxt
.ready_i_test
291 data_o
= fn(nxt
.data_o
) if fn
is not None else nxt
.data_o
292 return [nxt
.valid_o
.eq(self
.valid_o
),
293 self
.ready_i
.eq(ready_i
),
294 nmoperator
.eq(data_o
, self
.data_o
),
297 def elaborate(self
, platform
):
299 m
.d
.comb
+= self
.trigger
.eq(self
.ready_i_test
& self
.valid_o
)
305 if hasattr(self
.data_o
, "ports"):
306 yield from self
.data_o
.ports()
307 elif isinstance(self
.data_o
, Sequence
):
308 yield from self
.data_o
316 class StageCls(metaclass
=ABCMeta
):
317 """ Class-based "Stage" API. requires instantiation (after derivation)
319 see "Stage API" above.. Note: python does *not* require derivation
320 from this class. All that is required is that the pipelines *have*
321 the functions listed in this class. Derivation from this class
322 is therefore merely a "courtesy" to maintainers.
325 def ispec(self
): pass # REQUIRED
327 def ospec(self
): pass # REQUIRED
329 #def setup(self, m, i): pass # OPTIONAL
331 #def process(self, i): pass # OPTIONAL
334 class Stage(metaclass
=ABCMeta
):
335 """ Static "Stage" API. does not require instantiation (after derivation)
337 see "Stage API" above. Note: python does *not* require derivation
338 from this class. All that is required is that the pipelines *have*
339 the functions listed in this class. Derivation from this class
340 is therefore merely a "courtesy" to maintainers.
352 #def setup(m, i): pass
356 #def process(i): pass
360 """ a convenience wrapper around something that is Stage-API-compliant.
361 (that "something" may be a static class, for example).
363 def __init__(self
, stage
):
366 def ospec(self
, name
):
367 assert self
.stage
is not None
368 return _spec(self
.stage
.ospec
, name
)
370 def ispec(self
, name
):
371 assert self
.stage
is not None
372 return _spec(self
.stage
.ispec
, name
)
374 def process(self
, i
):
375 if self
.stage
and hasattr(self
.stage
, "process"):
376 return self
.stage
.process(i
)
379 def setup(self
, m
, i
):
380 if self
.stage
is not None and hasattr(self
.stage
, "setup"):
381 self
.stage
.setup(m
, i
)
383 def _postprocess(self
, i
): # XXX DISABLED
384 return i
# RETURNS INPUT
385 if hasattr(self
.stage
, "postprocess"):
386 return self
.stage
.postprocess(i
)
390 class StageChain(StageCls
):
391 """ pass in a list of stages, and they will automatically be
392 chained together via their input and output specs into a
393 combinatorial chain, to create one giant combinatorial block.
395 the end result basically conforms to the exact same Stage API.
397 * input to this class will be the input of the first stage
398 * output of first stage goes into input of second
399 * output of second goes into input into third
401 * the output of this class will be the output of the last stage
403 NOTE: whilst this is very similar to ControlBase.connect(), it is
404 *really* important to appreciate that StageChain is pure
405 combinatorial and bypasses (does not involve, at all, ready/valid
406 signalling of any kind).
408 ControlBase.connect on the other hand respects, connects, and uses
409 ready/valid signalling.
413 * :chain: a chain of combinatorial blocks conforming to the Stage API
414 NOTE: StageChain.ispec and ospect have to have something
415 to return (beginning and end specs of the chain),
416 therefore the chain argument must be non-zero length
418 * :specallocate: if set, new input and output data will be allocated
419 and connected (eq'd) to each chained Stage.
420 in some cases if this is not done, the nmigen warning
421 "driving from two sources, module is being flattened"
424 NOTE: do NOT use StageChain with combinatorial blocks that have
425 side-effects (state-based / clock-based input) or conditional
426 (inter-chain) dependencies, unless you really know what you are doing.
428 def __init__(self
, chain
, specallocate
=False):
429 assert len(chain
) > 0, "stage chain must be non-zero length"
431 self
.specallocate
= specallocate
434 """ returns the ispec of the first of the chain
436 return _spec(self
.chain
[0].ispec
, "chainin")
439 """ returns the ospec of the last of the chain
441 return _spec(self
.chain
[-1].ospec
, "chainout")
443 def _specallocate_setup(self
, m
, i
):
444 o
= i
# in case chain is empty
445 for (idx
, c
) in enumerate(self
.chain
):
446 if hasattr(c
, "setup"):
447 c
.setup(m
, i
) # stage may have some module stuff
448 ofn
= self
.chain
[idx
].ospec
# last assignment survives
449 o
= _spec(ofn
, 'chainin%d' % idx
)
450 m
.d
.comb
+= nmoperator
.eq(o
, c
.process(i
)) # process input into "o"
451 if idx
== len(self
.chain
)-1:
453 ifn
= self
.chain
[idx
+1].ispec
# new input on next loop
454 i
= _spec(ifn
, 'chainin%d' % (idx
+1))
455 m
.d
.comb
+= nmoperator
.eq(i
, o
) # assign to next input
456 return o
# last loop is the output
458 def _noallocate_setup(self
, m
, i
):
459 o
= i
# in case chain is empty
460 for (idx
, c
) in enumerate(self
.chain
):
461 if hasattr(c
, "setup"):
462 c
.setup(m
, i
) # stage may have some module stuff
463 i
= o
= c
.process(i
) # store input into "o"
464 return o
# last loop is the output
466 def setup(self
, m
, i
):
467 if self
.specallocate
:
468 self
.o
= self
._specallocate
_setup
(m
, i
)
470 self
.o
= self
._noallocate
_setup
(m
, i
)
472 def process(self
, i
):
473 return self
.o
# conform to Stage API: return last-loop output
476 class ControlBase(StageHelper
, Elaboratable
):
477 """ Common functions for Pipeline API. Note: a "pipeline stage" only
478 exists (conceptually) when a ControlBase derivative is handed
479 a Stage (combinatorial block)
481 def __init__(self
, stage
=None, in_multi
=None, stage_ctl
=False):
482 """ Base class containing ready/valid/data to previous and next stages
484 * p: contains ready/valid to the previous stage
485 * n: contains ready/valid to the next stage
487 Except when calling Controlbase.connect(), user must also:
488 * add data_i member to PrevControl (p) and
489 * add data_o member to NextControl (n)
491 StageHelper
.__init
__(self
, stage
)
493 # set up input and output IO ACK (prev/next ready/valid)
494 self
.p
= PrevControl(in_multi
, stage_ctl
)
495 self
.n
= NextControl(stage_ctl
)
497 # set up the input and output data
498 if stage
is not None:
499 self
._new
_data
(self
, self
, "data")
501 def _new_data(self
, p
, n
, name
):
502 """ allocates new data_i and data_o
504 self
.p
.data_i
= _spec(p
.stage
.ispec
, "%s_i" % name
)
505 self
.n
.data_o
= _spec(n
.stage
.ospec
, "%s_o" % name
)
509 return self
.process(self
.p
.data_i
)
511 def connect_to_next(self
, nxt
):
512 """ helper function to connect to the next stage data/valid/ready.
514 return self
.n
.connect_to_next(nxt
.p
)
516 def _connect_in(self
, prev
):
517 """ internal helper function to connect stage to an input source.
518 do not use to connect stage-to-stage!
520 return self
.p
._connect
_in
(prev
.p
)
522 def _connect_out(self
, nxt
):
523 """ internal helper function to connect stage to an output source.
524 do not use to connect stage-to-stage!
526 return self
.n
._connect
_out
(nxt
.n
)
528 def connect(self
, pipechain
):
529 """ connects a chain (list) of Pipeline instances together and
530 links them to this ControlBase instance:
532 in <----> self <---> out
535 [pipe1, pipe2, pipe3, pipe4]
538 out---in out--in out---in
540 Also takes care of allocating data_i/data_o, by looking up
541 the data spec for each end of the pipechain. i.e It is NOT
542 necessary to allocate self.p.data_i or self.n.data_o manually:
543 this is handled AUTOMATICALLY, here.
545 Basically this function is the direct equivalent of StageChain,
546 except that unlike StageChain, the Pipeline logic is followed.
548 Just as StageChain presents an object that conforms to the
549 Stage API from a list of objects that also conform to the
550 Stage API, an object that calls this Pipeline connect function
551 has the exact same pipeline API as the list of pipline objects
554 Thus it becomes possible to build up larger chains recursively.
555 More complex chains (multi-input, multi-output) will have to be
560 * :pipechain: - a sequence of ControlBase-derived classes
561 (must be one or more in length)
565 * a list of eq assignments that will need to be added in
566 an elaborate() to m.d.comb
568 assert len(pipechain
) > 0, "pipechain must be non-zero length"
569 eqs
= [] # collated list of assignment statements
571 # connect inter-chain
572 for i
in range(len(pipechain
)-1):
574 pipe2
= pipechain
[i
+1]
575 eqs
+= pipe1
.connect_to_next(pipe2
)
577 # connect front and back of chain to ourselves
580 self
._new
_data
(front
, end
, "chain") # NOTE: REPLACES existing data
581 eqs
+= front
._connect
_in
(self
)
582 eqs
+= end
._connect
_out
(self
)
586 def set_input(self
, i
):
587 """ helper function to set the input data
589 return nmoperator
.eq(self
.p
.data_i
, i
)
598 def elaborate(self
, platform
):
599 """ handles case where stage has dynamic ready/valid functions
602 m
.submodules
.p
= self
.p
603 m
.submodules
.n
= self
.n
605 self
.setup(m
, self
.p
.data_i
)
607 if not self
.p
.stage_ctl
:
610 # intercept the previous (outgoing) "ready", combine with stage ready
611 m
.d
.comb
+= self
.p
.s_ready_o
.eq(self
.p
._ready
_o
& self
.stage
.d_ready
)
613 # intercept the next (incoming) "ready" and combine it with data valid
614 sdv
= self
.stage
.d_valid(self
.n
.ready_i
)
615 m
.d
.comb
+= self
.n
.d_valid
.eq(self
.n
.ready_i
& sdv
)