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
359 class StageChain(StageCls
):
360 """ pass in a list of stages, and they will automatically be
361 chained together via their input and output specs into a
362 combinatorial chain, to create one giant combinatorial block.
364 the end result basically conforms to the exact same Stage API.
366 * input to this class will be the input of the first stage
367 * output of first stage goes into input of second
368 * output of second goes into input into third
370 * the output of this class will be the output of the last stage
372 NOTE: whilst this is very similar to ControlBase.connect(), it is
373 *really* important to appreciate that StageChain is pure
374 combinatorial and bypasses (does not involve, at all, ready/valid
375 signalling of any kind).
377 ControlBase.connect on the other hand respects, connects, and uses
378 ready/valid signalling.
382 * :chain: a chain of combinatorial blocks conforming to the Stage API
383 NOTE: StageChain.ispec and ospect have to have something
384 to return (beginning and end specs of the chain),
385 therefore the chain argument must be non-zero length
387 * :specallocate: if set, new input and output data will be allocated
388 and connected (eq'd) to each chained Stage.
389 in some cases if this is not done, the nmigen warning
390 "driving from two sources, module is being flattened"
393 NOTE: do NOT use StageChain with combinatorial blocks that have
394 side-effects (state-based / clock-based input) or conditional
395 (inter-chain) dependencies, unless you really know what you are doing.
397 def __init__(self
, chain
, specallocate
=False):
398 assert len(chain
) > 0, "stage chain must be non-zero length"
400 self
.specallocate
= specallocate
403 """ returns the ispec of the first of the chain
405 return _spec(self
.chain
[0].ispec
, "chainin")
408 """ returns the ospec of the last of the chain
410 return _spec(self
.chain
[-1].ospec
, "chainout")
412 def _specallocate_setup(self
, m
, i
):
413 for (idx
, c
) in enumerate(self
.chain
):
414 if hasattr(c
, "setup"):
415 c
.setup(m
, i
) # stage may have some module stuff
416 ofn
= self
.chain
[idx
].ospec
# last assignment survives
417 o
= _spec(ofn
, 'chainin%d' % idx
)
418 m
.d
.comb
+= nmoperator
.eq(o
, c
.process(i
)) # process input into "o"
419 if idx
== len(self
.chain
)-1:
421 ifn
= self
.chain
[idx
+1].ispec
# new input on next loop
422 i
= _spec(ifn
, 'chainin%d' % (idx
+1))
423 m
.d
.comb
+= nmoperator
.eq(i
, o
) # assign to next input
424 return o
# last loop is the output
426 def _noallocate_setup(self
, m
, i
):
427 for (idx
, c
) in enumerate(self
.chain
):
428 if hasattr(c
, "setup"):
429 c
.setup(m
, i
) # stage may have some module stuff
430 i
= o
= c
.process(i
) # store input into "o"
431 return o
# last loop is the output
433 def setup(self
, m
, i
):
434 if self
.specallocate
:
435 self
.o
= self
._specallocate
_setup
(m
, i
)
437 self
.o
= self
._noallocate
_setup
(m
, i
)
439 def process(self
, i
):
440 return self
.o
# conform to Stage API: return last-loop output
444 """ a convenience wrapper around something that is Stage-API-compliant.
445 (that "something" may be a static class, for example).
447 def __init__(self
, stage
):
450 def ospec(self
, name
):
451 assert self
.stage
is not None
452 return _spec(self
.stage
.ospec
, name
)
454 def ispec(self
, name
):
455 assert self
.stage
is not None
456 return _spec(self
.stage
.ispec
, name
)
458 def process(self
, i
):
459 if self
.stage
and hasattr(self
.stage
, "process"):
460 return self
.stage
.process(i
)
463 def setup(self
, m
, i
):
464 if self
.stage
is not None and hasattr(self
.stage
, "setup"):
465 self
.stage
.setup(m
, i
)
467 def _postprocess(self
, i
): # XXX DISABLED
468 return i
# RETURNS INPUT
469 if hasattr(self
.stage
, "postprocess"):
470 return self
.stage
.postprocess(i
)
474 class ControlBase(StageHelper
, Elaboratable
):
475 """ Common functions for Pipeline API. Note: a "pipeline stage" only
476 exists (conceptually) when a ControlBase derivative is handed
477 a Stage (combinatorial block)
479 NOTE: ControlBase derives from StageHelper, making it accidentally
480 compliant with the Stage API. Using those functions directly
481 *BYPASSES* a ControlBase instance ready/valid signalling, which
482 clearly should not be done without a really, really good reason.
484 def __init__(self
, stage
=None, in_multi
=None, stage_ctl
=False):
485 """ Base class containing ready/valid/data to previous and next stages
487 * p: contains ready/valid to the previous stage
488 * n: contains ready/valid to the next stage
490 Except when calling Controlbase.connect(), user must also:
491 * add data_i member to PrevControl (p) and
492 * add data_o member to NextControl (n)
493 Calling ControlBase._new_data is a good way to do that.
495 StageHelper
.__init
__(self
, stage
)
497 # set up input and output IO ACK (prev/next ready/valid)
498 self
.p
= PrevControl(in_multi
, stage_ctl
)
499 self
.n
= NextControl(stage_ctl
)
501 # set up the input and output data
502 if stage
is not None:
503 self
._new
_data
(self
, self
, "data")
505 def _new_data(self
, p
, n
, name
):
506 """ allocates new data_i and data_o
508 self
.p
.data_i
= _spec(p
.stage
.ispec
, "%s_i" % name
)
509 self
.n
.data_o
= _spec(n
.stage
.ospec
, "%s_o" % name
)
513 return self
.process(self
.p
.data_i
)
515 def connect_to_next(self
, nxt
):
516 """ helper function to connect to the next stage data/valid/ready.
518 return self
.n
.connect_to_next(nxt
.p
)
520 def _connect_in(self
, prev
):
521 """ internal helper function to connect stage to an input source.
522 do not use to connect stage-to-stage!
524 return self
.p
._connect
_in
(prev
.p
)
526 def _connect_out(self
, nxt
):
527 """ internal helper function to connect stage to an output source.
528 do not use to connect stage-to-stage!
530 return self
.n
._connect
_out
(nxt
.n
)
532 def connect(self
, pipechain
):
533 """ connects a chain (list) of Pipeline instances together and
534 links them to this ControlBase instance:
536 in <----> self <---> out
539 [pipe1, pipe2, pipe3, pipe4]
542 out---in out--in out---in
544 Also takes care of allocating data_i/data_o, by looking up
545 the data spec for each end of the pipechain. i.e It is NOT
546 necessary to allocate self.p.data_i or self.n.data_o manually:
547 this is handled AUTOMATICALLY, here.
549 Basically this function is the direct equivalent of StageChain,
550 except that unlike StageChain, the Pipeline logic is followed.
552 Just as StageChain presents an object that conforms to the
553 Stage API from a list of objects that also conform to the
554 Stage API, an object that calls this Pipeline connect function
555 has the exact same pipeline API as the list of pipline objects
558 Thus it becomes possible to build up larger chains recursively.
559 More complex chains (multi-input, multi-output) will have to be
564 * :pipechain: - a sequence of ControlBase-derived classes
565 (must be one or more in length)
569 * a list of eq assignments that will need to be added in
570 an elaborate() to m.d.comb
572 assert len(pipechain
) > 0, "pipechain must be non-zero length"
573 eqs
= [] # collated list of assignment statements
575 # connect inter-chain
576 for i
in range(len(pipechain
)-1):
578 pipe2
= pipechain
[i
+1]
579 eqs
+= pipe1
.connect_to_next(pipe2
)
581 # connect front and back of chain to ourselves
584 self
._new
_data
(front
, end
, "chain") # NOTE: REPLACES existing data
585 eqs
+= front
._connect
_in
(self
)
586 eqs
+= end
._connect
_out
(self
)
590 def set_input(self
, i
):
591 """ helper function to set the input data
593 return nmoperator
.eq(self
.p
.data_i
, i
)
602 def elaborate(self
, platform
):
603 """ handles case where stage has dynamic ready/valid functions
606 m
.submodules
.p
= self
.p
607 m
.submodules
.n
= self
.n
609 self
.setup(m
, self
.p
.data_i
)
611 if not self
.p
.stage_ctl
:
614 # intercept the previous (outgoing) "ready", combine with stage ready
615 m
.d
.comb
+= self
.p
.s_ready_o
.eq(self
.p
._ready
_o
& self
.stage
.d_ready
)
617 # intercept the next (incoming) "ready" and combine it with data valid
618 sdv
= self
.stage
.d_valid(self
.n
.ready_i
)
619 m
.d
.comb
+= self
.n
.d_valid
.eq(self
.n
.ready_i
& sdv
)