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 Stage Blocks really must be combinatorial blocks. It would be ok
14 to have input come in from sync'd sources (clock-driven) however by
15 doing so they would no longer be deterministic, and chaining such
16 blocks with such side-effects together could result in unexpected,
17 unpredictable, unreproduceable behaviour.
18 So generally to be avoided, then unless you know what you are doing.
20 the methods of a stage instance must be as follows:
22 * ispec() - Input data format specification. Takes a bit of explaining.
23 The requirements are: something that eventually derives from
24 nmigen Value must be returned *OR* an iterator or iterable
25 or sequence (list, tuple etc.) or generator must *yield*
26 thing(s) that (eventually) derive from the nmigen Value
27 class. Complex to state, very simple in practice:
28 see test_buf_pipe.py for over 25 working examples.
30 * ospec() - Output data format specification.
31 requirements identical to ispec
33 * process(m, i) - Processes an ispec-formatted object/sequence
34 returns a combinatorial block of a result that
35 may be assigned to the output, by way of the "nmoperator.eq"
36 function. Note that what is returned here can be
37 extremely flexible. Even a dictionary can be returned
38 as long as it has fields that match precisely with the
39 Record into which its values is intended to be assigned.
40 Again: see example unit tests for details.
42 * setup(m, i) - Optional function for setting up submodules
43 may be used for more complex stages, to link
44 the input (i) to submodules. must take responsibility
45 for adding those submodules to the module (m).
46 the submodules must be combinatorial blocks and
47 must have their inputs and output linked combinatorially.
49 Both StageCls (for use with non-static classes) and Stage (for use
50 by static classes) are abstract classes from which, for convenience
51 and as a courtesy to other developers, anything conforming to the
52 Stage API may *choose* to derive. See Liskov Substitution Principle:
53 https://en.wikipedia.org/wiki/Liskov_substitution_principle
58 A useful combinatorial wrapper around stages that chains them together
59 and then presents a Stage-API-conformant interface. By presenting
60 the same API as the stages it wraps, it can clearly be used recursively.
65 The base class for pipelines. Contains previous and next ready/valid/data.
66 Also has an extremely useful "connect" function that can be used to
67 connect a chain of pipelines and present the exact same prev/next
70 Note: pipelines basically do not become pipelines as such until
71 handed to a derivative of ControlBase. ControlBase itself is *not*
72 strictly considered a pipeline class. Wishbone and AXI4 (master or
73 slave) could be derived from ControlBase, for example.
76 from nmigen
import Signal
, Cat
, Const
, Mux
, Module
, Value
, Elaboratable
77 from nmigen
.cli
import verilog
, rtlil
78 from nmigen
.hdl
.rec
import Record
80 from abc
import ABCMeta
, abstractmethod
81 from collections
.abc
import Sequence
, Iterable
82 from collections
import OrderedDict
90 self
.fields
= OrderedDict()
92 def __setattr__(self
, k
, v
):
94 if (k
.startswith('_') or k
in ["fields", "name", "src_loc"] or
95 k
in dir(Object
) or "fields" not in self
.__dict
__):
96 return object.__setattr
__(self
, k
, v
)
99 def __getattr__(self
, k
):
100 if k
in self
.__dict
__:
101 return object.__getattr
__(self
, k
)
103 return self
.fields
[k
]
104 except KeyError as e
:
105 raise AttributeError(e
)
108 for x
in self
.fields
.values(): # OrderedDict so order is preserved
109 if isinstance(x
, Iterable
):
116 for (k
, o
) in self
.fields
.items():
120 if isinstance(rres
, Sequence
):
127 def ports(self
): # being called "keys" would be much better
131 class RecordObject(Record
):
132 def __init__(self
, layout
=None, name
=None):
133 Record
.__init
__(self
, layout
=layout
or [], name
=None)
135 def __setattr__(self
, k
, v
):
137 if (k
.startswith('_') or k
in ["fields", "name", "src_loc"] or
138 k
in dir(Record
) or "fields" not in self
.__dict
__):
139 return object.__setattr
__(self
, k
, v
)
141 #print ("RecordObject setattr", k, v)
142 if isinstance(v
, Record
):
143 newlayout
= {k
: (k
, v
.layout
)}
144 elif isinstance(v
, Value
):
145 newlayout
= {k
: (k
, v
.shape())}
147 newlayout
= {k
: (k
, nmoperator
.shape(v
))}
148 self
.layout
.fields
.update(newlayout
)
151 for x
in self
.fields
.values(): # remember: fields is an OrderedDict
152 if isinstance(x
, Iterable
):
153 yield from x
# a bit like flatten (nmigen.tools)
157 def ports(self
): # would be better being called "keys"
161 def _spec(fn
, name
=None):
164 varnames
= dict(inspect
.getmembers(fn
.__code
__))['co_varnames']
165 if 'name' in varnames
:
170 class PrevControl(Elaboratable
):
171 """ contains signals that come *from* the previous stage (both in and out)
172 * valid_i: previous stage indicating all incoming data is valid.
173 may be a multi-bit signal, where all bits are required
174 to be asserted to indicate "valid".
175 * ready_o: output to next stage indicating readiness to accept data
176 * data_i : an input - MUST be added by the USER of this class
179 def __init__(self
, i_width
=1, stage_ctl
=False):
180 self
.stage_ctl
= stage_ctl
181 self
.valid_i
= Signal(i_width
, name
="p_valid_i") # prev >>in self
182 self
._ready
_o
= Signal(name
="p_ready_o") # prev <<out self
183 self
.data_i
= None # XXX MUST BE ADDED BY USER
185 self
.s_ready_o
= Signal(name
="p_s_o_rdy") # prev <<out self
186 self
.trigger
= Signal(reset_less
=True)
190 """ public-facing API: indicates (externally) that stage is ready
193 return self
.s_ready_o
# set dynamically by stage
194 return self
._ready
_o
# return this when not under dynamic control
196 def _connect_in(self
, prev
, direct
=False, fn
=None):
197 """ internal helper function to connect stage to an input source.
198 do not use to connect stage-to-stage!
200 valid_i
= prev
.valid_i
if direct
else prev
.valid_i_test
201 data_i
= fn(prev
.data_i
) if fn
is not None else prev
.data_i
202 return [self
.valid_i
.eq(valid_i
),
203 prev
.ready_o
.eq(self
.ready_o
),
204 nmoperator
.eq(self
.data_i
, data_i
),
208 def valid_i_test(self
):
209 vlen
= len(self
.valid_i
)
211 # multi-bit case: valid only when valid_i is all 1s
212 all1s
= Const(-1, (len(self
.valid_i
), False))
213 valid_i
= (self
.valid_i
== all1s
)
215 # single-bit valid_i case
216 valid_i
= self
.valid_i
218 # when stage indicates not ready, incoming data
219 # must "appear" to be not ready too
221 valid_i
= valid_i
& self
.s_ready_o
225 def elaborate(self
, platform
):
227 m
.d
.comb
+= self
.trigger
.eq(self
.valid_i_test
& self
.ready_o
)
231 return [self
.data_i
.eq(i
.data_i
),
232 self
.ready_o
.eq(i
.ready_o
),
233 self
.valid_i
.eq(i
.valid_i
)]
238 if hasattr(self
.data_i
, "ports"):
239 yield from self
.data_i
.ports()
240 elif isinstance(self
.data_i
, Sequence
):
241 yield from self
.data_i
249 class NextControl(Elaboratable
):
250 """ contains the signals that go *to* the next stage (both in and out)
251 * valid_o: output indicating to next stage that data is valid
252 * ready_i: input from next stage indicating that it can accept data
253 * data_o : an output - MUST be added by the USER of this class
255 def __init__(self
, stage_ctl
=False):
256 self
.stage_ctl
= stage_ctl
257 self
.valid_o
= Signal(name
="n_valid_o") # self out>> next
258 self
.ready_i
= Signal(name
="n_ready_i") # self <<in next
259 self
.data_o
= None # XXX MUST BE ADDED BY USER
261 self
.d_valid
= Signal(reset
=1) # INTERNAL (data valid)
262 self
.trigger
= Signal(reset_less
=True)
265 def ready_i_test(self
):
267 return self
.ready_i
& self
.d_valid
270 def connect_to_next(self
, nxt
):
271 """ helper function to connect to the next stage data/valid/ready.
272 data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
273 use this when connecting stage-to-stage
275 return [nxt
.valid_i
.eq(self
.valid_o
),
276 self
.ready_i
.eq(nxt
.ready_o
),
277 nmoperator
.eq(nxt
.data_i
, self
.data_o
),
280 def _connect_out(self
, nxt
, direct
=False, fn
=None):
281 """ internal helper function to connect stage to an output source.
282 do not use to connect stage-to-stage!
284 ready_i
= nxt
.ready_i
if direct
else nxt
.ready_i_test
285 data_o
= fn(nxt
.data_o
) if fn
is not None else nxt
.data_o
286 return [nxt
.valid_o
.eq(self
.valid_o
),
287 self
.ready_i
.eq(ready_i
),
288 nmoperator
.eq(data_o
, self
.data_o
),
291 def elaborate(self
, platform
):
293 m
.d
.comb
+= self
.trigger
.eq(self
.ready_i_test
& self
.valid_o
)
299 if hasattr(self
.data_o
, "ports"):
300 yield from self
.data_o
.ports()
301 elif isinstance(self
.data_o
, Sequence
):
302 yield from self
.data_o
310 class StageCls(metaclass
=ABCMeta
):
311 """ Class-based "Stage" API. requires instantiation (after derivation)
313 see "Stage API" above.. Note: python does *not* require derivation
314 from this class. All that is required is that the pipelines *have*
315 the functions listed in this class. Derivation from this class
316 is therefore merely a "courtesy" to maintainers.
319 def ispec(self
): pass # REQUIRED
321 def ospec(self
): pass # REQUIRED
323 #def setup(self, m, i): pass # OPTIONAL
325 def process(self
, i
): pass # REQUIRED
328 class Stage(metaclass
=ABCMeta
):
329 """ Static "Stage" API. does not require instantiation (after derivation)
331 see "Stage API" above. Note: python does *not* require derivation
332 from this class. All that is required is that the pipelines *have*
333 the functions listed in this class. Derivation from this class
334 is therefore merely a "courtesy" to maintainers.
346 #def setup(m, i): pass
353 class StageChain(StageCls
):
354 """ pass in a list of stages, and they will automatically be
355 chained together via their input and output specs into a
356 combinatorial chain, to create one giant combinatorial block.
358 the end result basically conforms to the exact same Stage API.
360 * input to this class will be the input of the first stage
361 * output of first stage goes into input of second
362 * output of second goes into input into third
364 * the output of this class will be the output of the last stage
366 NOTE: whilst this is very similar to ControlBase.connect(), it is
367 *really* important to appreciate that StageChain is pure
368 combinatorial and bypasses (does not involve, at all, ready/valid
369 signalling of any kind).
371 ControlBase.connect on the other hand respects, connects, and uses
372 ready/valid signalling.
376 * :chain: a chain of combinatorial blocks conforming to the Stage API
377 NOTE: StageChain.ispec and ospect have to have something
378 to return (beginning and end specs of the chain),
379 therefore the chain argument must be non-zero length
381 * :specallocate: if set, new input and output data will be allocated
382 and connected (eq'd) to each chained Stage.
383 in some cases if this is not done, the nmigen warning
384 "driving from two sources, module is being flattened"
387 NOTE: do NOT use StageChain with combinatorial blocks that have
388 side-effects (state-based / clock-based input) or conditional
389 (inter-chain) dependencies, unless you really know what you are doing.
391 def __init__(self
, chain
, specallocate
=False):
392 assert len(chain
) > 0, "stage chain must be non-zero length"
394 self
.specallocate
= specallocate
397 """ returns the ispec of the first of the chain
399 return _spec(self
.chain
[0].ispec
, "chainin")
402 """ returns the ospec of the last of the chain
404 return _spec(self
.chain
[-1].ospec
, "chainout")
406 def _specallocate_setup(self
, m
, i
):
407 o
= i
# in case chain is empty
408 for (idx
, c
) in enumerate(self
.chain
):
409 if hasattr(c
, "setup"):
410 c
.setup(m
, i
) # stage may have some module stuff
411 ofn
= self
.chain
[idx
].ospec
# last assignment survives
412 o
= _spec(ofn
, 'chainin%d' % idx
)
413 m
.d
.comb
+= nmoperator
.eq(o
, c
.process(i
)) # process input into "o"
414 if idx
== len(self
.chain
)-1:
416 ifn
= self
.chain
[idx
+1].ispec
# new input on next loop
417 i
= _spec(ifn
, 'chainin%d' % (idx
+1))
418 m
.d
.comb
+= nmoperator
.eq(i
, o
) # assign to next input
419 return o
# last loop is the output
421 def _noallocate_setup(self
, m
, i
):
422 o
= i
# in case chain is empty
423 for (idx
, c
) in enumerate(self
.chain
):
424 if hasattr(c
, "setup"):
425 c
.setup(m
, i
) # stage may have some module stuff
426 i
= o
= c
.process(i
) # store input into "o"
427 return o
# last loop is the output
429 def setup(self
, m
, i
):
430 if self
.specallocate
:
431 self
.o
= self
._specallocate
_setup
(m
, i
)
433 self
.o
= self
._noallocate
_setup
(m
, i
)
435 def process(self
, i
):
436 return self
.o
# conform to Stage API: return last-loop output
439 class ControlBase(Elaboratable
):
440 """ Common functions for Pipeline API. Note: a "pipeline stage" only
441 exists (conceptually) when a ControlBase derivative is handed
442 a Stage (combinatorial block)
444 def __init__(self
, stage
=None, in_multi
=None, stage_ctl
=False):
445 """ Base class containing ready/valid/data to previous and next stages
447 * p: contains ready/valid to the previous stage
448 * n: contains ready/valid to the next stage
450 Except when calling Controlbase.connect(), user must also:
451 * add data_i member to PrevControl (p) and
452 * add data_o member to NextControl (n)
456 # set up input and output IO ACK (prev/next ready/valid)
457 self
.p
= PrevControl(in_multi
, stage_ctl
)
458 self
.n
= NextControl(stage_ctl
)
460 # set up the input and output data
461 if stage
is not None:
462 self
.p
.data_i
= _spec(stage
.ispec
, "data_i") # input type
463 self
.n
.data_o
= _spec(stage
.ospec
, "data_o") # output type
465 def connect_to_next(self
, nxt
):
466 """ helper function to connect to the next stage data/valid/ready.
468 return self
.n
.connect_to_next(nxt
.p
)
470 def _connect_in(self
, prev
):
471 """ internal helper function to connect stage to an input source.
472 do not use to connect stage-to-stage!
474 return self
.p
._connect
_in
(prev
.p
)
476 def _connect_out(self
, nxt
):
477 """ internal helper function to connect stage to an output source.
478 do not use to connect stage-to-stage!
480 return self
.n
._connect
_out
(nxt
.n
)
482 def connect(self
, pipechain
):
483 """ connects a chain (list) of Pipeline instances together and
484 links them to this ControlBase instance:
486 in <----> self <---> out
489 [pipe1, pipe2, pipe3, pipe4]
492 out---in out--in out---in
494 Also takes care of allocating data_i/data_o, by looking up
495 the data spec for each end of the pipechain. i.e It is NOT
496 necessary to allocate self.p.data_i or self.n.data_o manually:
497 this is handled AUTOMATICALLY, here.
499 Basically this function is the direct equivalent of StageChain,
500 except that unlike StageChain, the Pipeline logic is followed.
502 Just as StageChain presents an object that conforms to the
503 Stage API from a list of objects that also conform to the
504 Stage API, an object that calls this Pipeline connect function
505 has the exact same pipeline API as the list of pipline objects
508 Thus it becomes possible to build up larger chains recursively.
509 More complex chains (multi-input, multi-output) will have to be
514 * :pipechain: - a sequence of ControlBase-derived classes
515 (must be one or more in length)
519 * a list of eq assignments that will need to be added in
520 an elaborate() to m.d.comb
522 assert len(pipechain
) > 0, "pipechain must be non-zero length"
523 eqs
= [] # collated list of assignment statements
525 # connect inter-chain
526 for i
in range(len(pipechain
)-1):
528 pipe2
= pipechain
[i
+1]
529 eqs
+= pipe1
.connect_to_next(pipe2
)
531 # connect front of chain to ourselves
533 self
.p
.data_i
= _spec(front
.stage
.ispec
, "chainin")
534 eqs
+= front
._connect
_in
(self
)
536 # connect end of chain to ourselves
538 self
.n
.data_o
= _spec(end
.stage
.ospec
, "chainout")
539 eqs
+= end
._connect
_out
(self
)
545 return self
.stage
.process(self
.p
.data_i
)
547 def _postprocess(self
, i
): # XXX DISABLED
548 return i
# RETURNS INPUT
549 if hasattr(self
.stage
, "postprocess"):
550 return self
.stage
.postprocess(i
)
553 def set_input(self
, i
):
554 """ helper function to set the input data
556 return nmoperator
.eq(self
.p
.data_i
, i
)
565 def elaborate(self
, platform
):
566 """ handles case where stage has dynamic ready/valid functions
569 m
.submodules
.p
= self
.p
570 m
.submodules
.n
= self
.n
572 if self
.stage
is not None and hasattr(self
.stage
, "setup"):
573 self
.stage
.setup(m
, self
.p
.data_i
)
575 if not self
.p
.stage_ctl
:
578 # intercept the previous (outgoing) "ready", combine with stage ready
579 m
.d
.comb
+= self
.p
.s_ready_o
.eq(self
.p
._ready
_o
& self
.stage
.d_ready
)
581 # intercept the next (incoming) "ready" and combine it with data valid
582 sdv
= self
.stage
.d_valid(self
.n
.ready_i
)
583 m
.d
.comb
+= self
.n
.d_valid
.eq(self
.n
.ready_i
& sdv
)