1 """ Pipeline and BufferedHandshake implementation, conforming to the same API.
2 For multi-input and multi-output variants, see multipipe.
4 Associated development bugs:
5 * http://bugs.libre-riscv.org/show_bug.cgi?id=64
6 * http://bugs.libre-riscv.org/show_bug.cgi?id=57
8 Important: see Stage API (iocontrol.py) in combination with below
13 A convenience class that takes an input shape, output shape, a
14 "processing" function and an optional "setup" function. Honestly
15 though, there's not much more effort to just... create a class
16 that returns a couple of Records (see ExampleAddRecordStage in
22 A convenience class that takes a single function as a parameter,
23 that is chain-called to create the exact same input and output spec.
24 It has a process() function that simply returns its input.
26 Instances of this class are completely redundant if handed to
27 StageChain, however when passed to UnbufferedPipeline they
28 can be used to introduce a single clock delay.
33 The base class for pipelines. Contains previous and next ready/valid/data.
34 Also has an extremely useful "connect" function that can be used to
35 connect a chain of pipelines and present the exact same prev/next
38 Note: pipelines basically do not become pipelines as such until
39 handed to a derivative of ControlBase. ControlBase itself is *not*
40 strictly considered a pipeline class. Wishbone and AXI4 (master or
41 slave) could be derived from ControlBase, for example.
45 A simple stalling clock-synchronised pipeline that has no buffering
46 (unlike BufferedHandshake). Data flows on *every* clock cycle when
47 the conditions are right (this is nominally when the input is valid
48 and the output is ready).
50 A stall anywhere along the line will result in a stall back-propagating
51 down the entire chain. The BufferedHandshake by contrast will buffer
52 incoming data, allowing previous stages one clock cycle's grace before
55 An advantage of the UnbufferedPipeline over the Buffered one is
56 that the amount of logic needed (number of gates) is greatly
57 reduced (no second set of buffers basically)
59 The disadvantage of the UnbufferedPipeline is that the valid/ready
60 logic, if chained together, is *combinatorial*, resulting in
61 progressively larger gate delay.
66 A Control class that introduces a single clock delay, passing its
67 data through unaltered. Unlike RegisterPipeline (which relies
68 on UnbufferedPipeline and PassThroughStage) it handles ready/valid
74 A convenience class that, because UnbufferedPipeline introduces a single
75 clock delay, when its stage is a PassThroughStage, it results in a Pipeline
76 stage that, duh, delays its (unmodified) input by one clock cycle.
81 nmigen implementation of buffered pipeline stage, based on zipcpu:
82 https://zipcpu.com/blog/2017/08/14/strategies-for-pipelining.html
84 this module requires quite a bit of thought to understand how it works
85 (and why it is needed in the first place). reading the above is
86 *strongly* recommended.
88 unlike john dawson's IEEE754 FPU STB/ACK signalling, which requires
89 the STB / ACK signals to raise and lower (on separate clocks) before
90 data may proceeed (thus only allowing one piece of data to proceed
91 on *ALTERNATE* cycles), the signalling here is a true pipeline
92 where data will flow on *every* clock when the conditions are right.
94 input acceptance conditions are when:
95 * incoming previous-stage strobe (p.valid_i) is HIGH
96 * outgoing previous-stage ready (p.ready_o) is LOW
98 output transmission conditions are when:
99 * outgoing next-stage strobe (n.valid_o) is HIGH
100 * outgoing next-stage ready (n.ready_i) is LOW
102 the tricky bit is when the input has valid data and the output is not
103 ready to accept it. if it wasn't for the clock synchronisation, it
104 would be possible to tell the input "hey don't send that data, we're
105 not ready". unfortunately, it's not possible to "change the past":
106 the previous stage *has no choice* but to pass on its data.
108 therefore, the incoming data *must* be accepted - and stored: that
109 is the responsibility / contract that this stage *must* accept.
110 on the same clock, it's possible to tell the input that it must
111 not send any more data. this is the "stall" condition.
113 we now effectively have *two* possible pieces of data to "choose" from:
114 the buffered data, and the incoming data. the decision as to which
115 to process and output is based on whether we are in "stall" or not.
116 i.e. when the next stage is no longer ready, the output comes from
117 the buffer if a stall had previously occurred, otherwise it comes
118 direct from processing the input.
120 this allows us to respect a synchronous "travelling STB" with what
121 dan calls a "buffered handshake".
123 it's quite a complex state machine!
128 Synchronised pipeline, Based on:
129 https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
132 from nmigen
import Signal
, Mux
, Module
, Elaboratable
133 from nmigen
.cli
import verilog
, rtlil
134 from nmigen
.lib
.fifo
import SyncFIFOBuffered
135 from nmigen
.hdl
.rec
import Record
137 from queue
import Queue
140 from iocontrol
import (PrevControl
, NextControl
, Object
, RecordObject
)
141 from stageapi
import (_spec
, StageCls
, Stage
, StageChain
, StageHelper
)
145 class RecordBasedStage(Stage
):
146 """ convenience class which provides a Records-based layout.
147 honestly it's a lot easier just to create a direct Records-based
148 class (see ExampleAddRecordStage)
150 def __init__(self
, in_shape
, out_shape
, processfn
, setupfn
=None):
151 self
.in_shape
= in_shape
152 self
.out_shape
= out_shape
153 self
.__process
= processfn
154 self
.__setup
= setupfn
155 def ispec(self
): return Record(self
.in_shape
)
156 def ospec(self
): return Record(self
.out_shape
)
157 def process(seif
, i
): return self
.__process
(i
)
158 def setup(seif
, m
, i
): return self
.__setup
(m
, i
)
161 class PassThroughStage(StageCls
):
162 """ a pass-through stage with its input data spec identical to its output,
163 and "passes through" its data from input to output (does nothing).
165 use this basically to explicitly make any data spec Stage-compliant.
166 (many APIs would potentially use a static "wrap" method in e.g.
167 StageCls to achieve a similar effect)
169 def __init__(self
, iospecfn
): self
.iospecfn
= iospecfn
170 def ispec(self
): return self
.iospecfn()
171 def ospec(self
): return self
.iospecfn()
174 class ControlBase(StageHelper
, Elaboratable
):
175 """ Common functions for Pipeline API. Note: a "pipeline stage" only
176 exists (conceptually) when a ControlBase derivative is handed
177 a Stage (combinatorial block)
179 NOTE: ControlBase derives from StageHelper, making it accidentally
180 compliant with the Stage API. Using those functions directly
181 *BYPASSES* a ControlBase instance ready/valid signalling, which
182 clearly should not be done without a really, really good reason.
184 def __init__(self
, stage
=None, in_multi
=None, stage_ctl
=False):
185 """ Base class containing ready/valid/data to previous and next stages
187 * p: contains ready/valid to the previous stage
188 * n: contains ready/valid to the next stage
190 Except when calling Controlbase.connect(), user must also:
191 * add data_i member to PrevControl (p) and
192 * add data_o member to NextControl (n)
193 Calling ControlBase._new_data is a good way to do that.
195 StageHelper
.__init
__(self
, stage
)
197 # set up input and output IO ACK (prev/next ready/valid)
198 self
.p
= PrevControl(in_multi
, stage_ctl
)
199 self
.n
= NextControl(stage_ctl
)
201 # set up the input and output data
202 if stage
is not None:
203 self
._new
_data
(self
, self
, "data")
205 def _new_data(self
, p
, n
, name
):
206 """ allocates new data_i and data_o
208 self
.p
.data_i
= _spec(p
.stage
.ispec
, "%s_i" % name
)
209 self
.n
.data_o
= _spec(n
.stage
.ospec
, "%s_o" % name
)
213 return self
.process(self
.p
.data_i
)
215 def connect_to_next(self
, nxt
):
216 """ helper function to connect to the next stage data/valid/ready.
218 return self
.n
.connect_to_next(nxt
.p
)
220 def _connect_in(self
, prev
):
221 """ internal helper function to connect stage to an input source.
222 do not use to connect stage-to-stage!
224 return self
.p
._connect
_in
(prev
.p
)
226 def _connect_out(self
, nxt
):
227 """ internal helper function to connect stage to an output source.
228 do not use to connect stage-to-stage!
230 return self
.n
._connect
_out
(nxt
.n
)
232 def connect(self
, pipechain
):
233 """ connects a chain (list) of Pipeline instances together and
234 links them to this ControlBase instance:
236 in <----> self <---> out
239 [pipe1, pipe2, pipe3, pipe4]
242 out---in out--in out---in
244 Also takes care of allocating data_i/data_o, by looking up
245 the data spec for each end of the pipechain. i.e It is NOT
246 necessary to allocate self.p.data_i or self.n.data_o manually:
247 this is handled AUTOMATICALLY, here.
249 Basically this function is the direct equivalent of StageChain,
250 except that unlike StageChain, the Pipeline logic is followed.
252 Just as StageChain presents an object that conforms to the
253 Stage API from a list of objects that also conform to the
254 Stage API, an object that calls this Pipeline connect function
255 has the exact same pipeline API as the list of pipline objects
258 Thus it becomes possible to build up larger chains recursively.
259 More complex chains (multi-input, multi-output) will have to be
264 * :pipechain: - a sequence of ControlBase-derived classes
265 (must be one or more in length)
269 * a list of eq assignments that will need to be added in
270 an elaborate() to m.d.comb
272 assert len(pipechain
) > 0, "pipechain must be non-zero length"
273 eqs
= [] # collated list of assignment statements
275 # connect inter-chain
276 for i
in range(len(pipechain
)-1):
277 pipe1
= pipechain
[i
] # earlier
278 pipe2
= pipechain
[i
+1] # later (by 1)
279 eqs
+= pipe1
.connect_to_next(pipe2
) # earlier n to later p
281 # connect front and back of chain to ourselves
282 front
= pipechain
[0] # first in chain
283 end
= pipechain
[-1] # last in chain
284 self
._new
_data
(front
, end
, "chain") # NOTE: REPLACES existing data
285 eqs
+= front
._connect
_in
(self
) # front p to our p
286 eqs
+= end
._connect
_out
(self
) # end n to out n
290 def set_input(self
, i
):
291 """ helper function to set the input data (used in unit tests)
293 return nmoperator
.eq(self
.p
.data_i
, i
)
296 yield from self
.p
# yields ready/valid/data (data also gets yielded)
297 yield from self
.n
# ditto
302 def elaborate(self
, platform
):
303 """ handles case where stage has dynamic ready/valid functions
306 m
.submodules
.p
= self
.p
307 m
.submodules
.n
= self
.n
309 self
.setup(m
, self
.p
.data_i
)
311 if not self
.p
.stage_ctl
:
314 # intercept the previous (outgoing) "ready", combine with stage ready
315 m
.d
.comb
+= self
.p
.s_ready_o
.eq(self
.p
._ready
_o
& self
.stage
.d_ready
)
317 # intercept the next (incoming) "ready" and combine it with data valid
318 sdv
= self
.stage
.d_valid(self
.n
.ready_i
)
319 m
.d
.comb
+= self
.n
.d_valid
.eq(self
.n
.ready_i
& sdv
)
324 class BufferedHandshake(ControlBase
):
325 """ buffered pipeline stage. data and strobe signals travel in sync.
326 if ever the input is ready and the output is not, processed data
327 is shunted in a temporary register.
329 Argument: stage. see Stage API above
331 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
332 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
333 stage-1 p.data_i >>in stage n.data_o out>> stage+1
339 input data p.data_i is read (only), is processed and goes into an
340 intermediate result store [process()]. this is updated combinatorially.
342 in a non-stall condition, the intermediate result will go into the
343 output (update_output). however if ever there is a stall, it goes
344 into r_data instead [update_buffer()].
346 when the non-stall condition is released, r_data is the first
347 to be transferred to the output [flush_buffer()], and the stall
350 on the next cycle (as long as stall is not raised again) the
351 input may begin to be processed and transferred directly to output.
354 def elaborate(self
, platform
):
355 self
.m
= ControlBase
.elaborate(self
, platform
)
357 result
= _spec(self
.stage
.ospec
, "r_tmp")
358 r_data
= _spec(self
.stage
.ospec
, "r_data")
360 # establish some combinatorial temporaries
361 o_n_validn
= Signal(reset_less
=True)
362 n_ready_i
= Signal(reset_less
=True, name
="n_i_rdy_data")
363 nir_por
= Signal(reset_less
=True)
364 nir_por_n
= Signal(reset_less
=True)
365 p_valid_i
= Signal(reset_less
=True)
366 nir_novn
= Signal(reset_less
=True)
367 nirn_novn
= Signal(reset_less
=True)
368 por_pivn
= Signal(reset_less
=True)
369 npnn
= Signal(reset_less
=True)
370 self
.m
.d
.comb
+= [p_valid_i
.eq(self
.p
.valid_i_test
),
371 o_n_validn
.eq(~self
.n
.valid_o
),
372 n_ready_i
.eq(self
.n
.ready_i_test
),
373 nir_por
.eq(n_ready_i
& self
.p
._ready
_o
),
374 nir_por_n
.eq(n_ready_i
& ~self
.p
._ready
_o
),
375 nir_novn
.eq(n_ready_i | o_n_validn
),
376 nirn_novn
.eq(~n_ready_i
& o_n_validn
),
377 npnn
.eq(nir_por | nirn_novn
),
378 por_pivn
.eq(self
.p
._ready
_o
& ~p_valid_i
)
381 # store result of processing in combinatorial temporary
382 self
.m
.d
.comb
+= nmoperator
.eq(result
, self
.data_r
)
384 # if not in stall condition, update the temporary register
385 with self
.m
.If(self
.p
.ready_o
): # not stalled
386 self
.m
.d
.sync
+= nmoperator
.eq(r_data
, result
) # update buffer
388 # data pass-through conditions
389 with self
.m
.If(npnn
):
390 data_o
= self
._postprocess
(result
) # XXX TBD, does nothing right now
391 self
.m
.d
.sync
+= [self
.n
.valid_o
.eq(p_valid_i
), # valid if p_valid
392 nmoperator
.eq(self
.n
.data_o
, data_o
), # update out
394 # buffer flush conditions (NOTE: can override data passthru conditions)
395 with self
.m
.If(nir_por_n
): # not stalled
396 # Flush the [already processed] buffer to the output port.
397 data_o
= self
._postprocess
(r_data
) # XXX TBD, does nothing right now
398 self
.m
.d
.sync
+= [self
.n
.valid_o
.eq(1), # reg empty
399 nmoperator
.eq(self
.n
.data_o
, data_o
), # flush
401 # output ready conditions
402 self
.m
.d
.sync
+= self
.p
._ready
_o
.eq(nir_novn | por_pivn
)
407 class SimpleHandshake(ControlBase
):
408 """ simple handshake control. data and strobe signals travel in sync.
409 implements the protocol used by Wishbone and AXI4.
411 Argument: stage. see Stage API above
413 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
414 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
415 stage-1 p.data_i >>in stage n.data_o out>> stage+1
420 Inputs Temporary Output Data
421 ------- ---------- ----- ----
422 P P N N PiV& ~NiR& N P
429 0 0 1 0 0 0 0 1 process(data_i)
430 0 0 1 1 0 0 0 1 process(data_i)
434 0 1 1 0 0 0 0 1 process(data_i)
435 0 1 1 1 0 0 0 1 process(data_i)
439 1 0 1 0 0 0 0 1 process(data_i)
440 1 0 1 1 0 0 0 1 process(data_i)
442 1 1 0 0 1 0 1 0 process(data_i)
443 1 1 0 1 1 1 1 0 process(data_i)
444 1 1 1 0 1 0 1 1 process(data_i)
445 1 1 1 1 1 0 1 1 process(data_i)
449 def elaborate(self
, platform
):
450 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
453 result
= _spec(self
.stage
.ospec
, "r_tmp")
455 # establish some combinatorial temporaries
456 n_ready_i
= Signal(reset_less
=True, name
="n_i_rdy_data")
457 p_valid_i_p_ready_o
= Signal(reset_less
=True)
458 p_valid_i
= Signal(reset_less
=True)
459 m
.d
.comb
+= [p_valid_i
.eq(self
.p
.valid_i_test
),
460 n_ready_i
.eq(self
.n
.ready_i_test
),
461 p_valid_i_p_ready_o
.eq(p_valid_i
& self
.p
.ready_o
),
464 # store result of processing in combinatorial temporary
465 m
.d
.comb
+= nmoperator
.eq(result
, self
.data_r
)
467 # previous valid and ready
468 with m
.If(p_valid_i_p_ready_o
):
469 data_o
= self
._postprocess
(result
) # XXX TBD, does nothing right now
470 m
.d
.sync
+= [r_busy
.eq(1), # output valid
471 nmoperator
.eq(self
.n
.data_o
, data_o
), # update output
473 # previous invalid or not ready, however next is accepting
474 with m
.Elif(n_ready_i
):
475 data_o
= self
._postprocess
(result
) # XXX TBD, does nothing right now
476 m
.d
.sync
+= [nmoperator
.eq(self
.n
.data_o
, data_o
)]
477 # TODO: could still send data here (if there was any)
478 #m.d.sync += self.n.valid_o.eq(0) # ...so set output invalid
479 m
.d
.sync
+= r_busy
.eq(0) # ...so set output invalid
481 m
.d
.comb
+= self
.n
.valid_o
.eq(r_busy
)
482 # if next is ready, so is previous
483 m
.d
.comb
+= self
.p
._ready
_o
.eq(n_ready_i
)
488 class UnbufferedPipeline(ControlBase
):
489 """ A simple pipeline stage with single-clock synchronisation
490 and two-way valid/ready synchronised signalling.
492 Note that a stall in one stage will result in the entire pipeline
495 Also that unlike BufferedHandshake, the valid/ready signalling does NOT
496 travel synchronously with the data: the valid/ready signalling
497 combines in a *combinatorial* fashion. Therefore, a long pipeline
498 chain will lengthen propagation delays.
500 Argument: stage. see Stage API, above
502 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
503 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
504 stage-1 p.data_i >>in stage n.data_o out>> stage+1
512 p.data_i : StageInput, shaped according to ispec
514 p.data_o : StageOutput, shaped according to ospec
516 r_data : input_shape according to ispec
517 A temporary (buffered) copy of a prior (valid) input.
518 This is HELD if the output is not ready. It is updated
520 result: output_shape according to ospec
521 The output of the combinatorial logic. it is updated
522 COMBINATORIALLY (no clock dependence).
526 Inputs Temp Output Data
548 1 1 0 0 0 1 1 process(data_i)
549 1 1 0 1 1 1 0 process(data_i)
550 1 1 1 0 0 1 1 process(data_i)
551 1 1 1 1 0 1 1 process(data_i)
554 Note: PoR is *NOT* involved in the above decision-making.
557 def elaborate(self
, platform
):
558 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
560 data_valid
= Signal() # is data valid or not
561 r_data
= _spec(self
.stage
.ospec
, "r_tmp") # output type
564 p_valid_i
= Signal(reset_less
=True)
565 pv
= Signal(reset_less
=True)
566 buf_full
= Signal(reset_less
=True)
567 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
568 m
.d
.comb
+= pv
.eq(self
.p
.valid_i
& self
.p
.ready_o
)
569 m
.d
.comb
+= buf_full
.eq(~self
.n
.ready_i_test
& data_valid
)
571 m
.d
.comb
+= self
.n
.valid_o
.eq(data_valid
)
572 m
.d
.comb
+= self
.p
._ready
_o
.eq(~data_valid | self
.n
.ready_i_test
)
573 m
.d
.sync
+= data_valid
.eq(p_valid_i | buf_full
)
576 m
.d
.sync
+= nmoperator
.eq(r_data
, self
.data_r
)
577 data_o
= self
._postprocess
(r_data
) # XXX TBD, does nothing right now
578 m
.d
.comb
+= nmoperator
.eq(self
.n
.data_o
, data_o
)
582 class UnbufferedPipeline2(ControlBase
):
583 """ A simple pipeline stage with single-clock synchronisation
584 and two-way valid/ready synchronised signalling.
586 Note that a stall in one stage will result in the entire pipeline
589 Also that unlike BufferedHandshake, the valid/ready signalling does NOT
590 travel synchronously with the data: the valid/ready signalling
591 combines in a *combinatorial* fashion. Therefore, a long pipeline
592 chain will lengthen propagation delays.
594 Argument: stage. see Stage API, above
596 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
597 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
598 stage-1 p.data_i >>in stage n.data_o out>> stage+1
603 p.data_i : StageInput, shaped according to ispec
605 p.data_o : StageOutput, shaped according to ospec
607 buf : output_shape according to ospec
608 A temporary (buffered) copy of a valid output
609 This is HELD if the output is not ready. It is updated
612 Inputs Temp Output Data
614 P P N N ~NiR& N P (buf_full)
619 0 0 0 0 0 0 1 process(data_i)
620 0 0 0 1 1 1 0 reg (odata, unchanged)
621 0 0 1 0 0 0 1 process(data_i)
622 0 0 1 1 0 0 1 process(data_i)
624 0 1 0 0 0 0 1 process(data_i)
625 0 1 0 1 1 1 0 reg (odata, unchanged)
626 0 1 1 0 0 0 1 process(data_i)
627 0 1 1 1 0 0 1 process(data_i)
629 1 0 0 0 0 1 1 process(data_i)
630 1 0 0 1 1 1 0 reg (odata, unchanged)
631 1 0 1 0 0 1 1 process(data_i)
632 1 0 1 1 0 1 1 process(data_i)
634 1 1 0 0 0 1 1 process(data_i)
635 1 1 0 1 1 1 0 reg (odata, unchanged)
636 1 1 1 0 0 1 1 process(data_i)
637 1 1 1 1 0 1 1 process(data_i)
640 Note: PoR is *NOT* involved in the above decision-making.
643 def elaborate(self
, platform
):
644 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
646 buf_full
= Signal() # is data valid or not
647 buf
= _spec(self
.stage
.ospec
, "r_tmp") # output type
650 p_valid_i
= Signal(reset_less
=True)
651 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
653 m
.d
.comb
+= self
.n
.valid_o
.eq(buf_full | p_valid_i
)
654 m
.d
.comb
+= self
.p
._ready
_o
.eq(~buf_full
)
655 m
.d
.sync
+= buf_full
.eq(~self
.n
.ready_i_test
& self
.n
.valid_o
)
657 data_o
= Mux(buf_full
, buf
, self
.data_r
)
658 data_o
= self
._postprocess
(data_o
) # XXX TBD, does nothing right now
659 m
.d
.comb
+= nmoperator
.eq(self
.n
.data_o
, data_o
)
660 m
.d
.sync
+= nmoperator
.eq(buf
, self
.n
.data_o
)
665 class PassThroughHandshake(ControlBase
):
666 """ A control block that delays by one clock cycle.
668 Inputs Temporary Output Data
669 ------- ------------------ ----- ----
670 P P N N PiV& PiV| NiR| pvr N P (pvr)
671 i o i o PoR ~PoR ~NoV o o
675 0 0 0 0 0 1 1 0 1 1 odata (unchanged)
676 0 0 0 1 0 1 0 0 1 0 odata (unchanged)
677 0 0 1 0 0 1 1 0 1 1 odata (unchanged)
678 0 0 1 1 0 1 1 0 1 1 odata (unchanged)
680 0 1 0 0 0 0 1 0 0 1 odata (unchanged)
681 0 1 0 1 0 0 0 0 0 0 odata (unchanged)
682 0 1 1 0 0 0 1 0 0 1 odata (unchanged)
683 0 1 1 1 0 0 1 0 0 1 odata (unchanged)
685 1 0 0 0 0 1 1 1 1 1 process(in)
686 1 0 0 1 0 1 0 0 1 0 odata (unchanged)
687 1 0 1 0 0 1 1 1 1 1 process(in)
688 1 0 1 1 0 1 1 1 1 1 process(in)
690 1 1 0 0 1 1 1 1 1 1 process(in)
691 1 1 0 1 1 1 0 0 1 0 odata (unchanged)
692 1 1 1 0 1 1 1 1 1 1 process(in)
693 1 1 1 1 1 1 1 1 1 1 process(in)
698 def elaborate(self
, platform
):
699 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
701 r_data
= _spec(self
.stage
.ospec
, "r_tmp") # output type
704 p_valid_i
= Signal(reset_less
=True)
705 pvr
= Signal(reset_less
=True)
706 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
707 m
.d
.comb
+= pvr
.eq(p_valid_i
& self
.p
.ready_o
)
709 m
.d
.comb
+= self
.p
.ready_o
.eq(~self
.n
.valid_o | self
.n
.ready_i_test
)
710 m
.d
.sync
+= self
.n
.valid_o
.eq(p_valid_i | ~self
.p
.ready_o
)
712 odata
= Mux(pvr
, self
.data_r
, r_data
)
713 m
.d
.sync
+= nmoperator
.eq(r_data
, odata
)
714 r_data
= self
._postprocess
(r_data
) # XXX TBD, does nothing right now
715 m
.d
.comb
+= nmoperator
.eq(self
.n
.data_o
, r_data
)
720 class RegisterPipeline(UnbufferedPipeline
):
721 """ A pipeline stage that delays by one clock cycle, creating a
722 sync'd latch out of data_o and valid_o as an indirect byproduct
723 of using PassThroughStage
725 def __init__(self
, iospecfn
):
726 UnbufferedPipeline
.__init
__(self
, PassThroughStage(iospecfn
))
729 class FIFOControl(ControlBase
):
730 """ FIFO Control. Uses Queue to store data, coincidentally
731 happens to have same valid/ready signalling as Stage API.
732 (TODO: remove use of SyncFIFOBuffered)
734 data_i -> fifo.din -> FIFO -> fifo.dout -> data_o
736 def __init__(self
, depth
, stage
, in_multi
=None, stage_ctl
=False,
737 fwft
=True, buffered
=False, pipe
=False):
740 * :depth: number of entries in the FIFO
741 * :stage: data processing block
742 * :fwft: first word fall-thru mode (non-fwft introduces delay)
743 * :buffered: use buffered FIFO (introduces extra cycle delay)
745 NOTE 1: FPGAs may have trouble with the defaults for SyncFIFO
746 (fwft=True, buffered=False). XXX TODO: fix this by
747 using Queue in all cases instead.
749 data is processed (and located) as follows:
751 self.p self.stage temp fn temp fn temp fp self.n
752 data_i->process()->result->cat->din.FIFO.dout->cat(data_o)
754 yes, really: cat produces a Cat() which can be assigned to.
755 this is how the FIFO gets de-catted without needing a de-cat
759 assert not (fwft
and buffered
), "buffered cannot do fwft"
763 self
.buffered
= buffered
766 ControlBase
.__init
__(self
, stage
, in_multi
, stage_ctl
)
768 def elaborate(self
, platform
):
769 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
771 # make a FIFO with a signal of equal width to the data_o.
772 (fwidth
, _
) = nmoperator
.shape(self
.n
.data_o
)
774 fifo
= SyncFIFOBuffered(fwidth
, self
.fdepth
)
776 fifo
= Queue(fwidth
, self
.fdepth
, fwft
=self
.fwft
, pipe
=self
.pipe
)
777 m
.submodules
.fifo
= fifo
779 # store result of processing in combinatorial temporary
780 result
= _spec(self
.stage
.ospec
, "r_temp")
781 m
.d
.comb
+= nmoperator
.eq(result
, self
.data_r
)
783 # connect previous rdy/valid/data - do cat on data_i
784 # NOTE: cannot do the PrevControl-looking trick because
785 # of need to process the data. shaaaame....
786 m
.d
.comb
+= [fifo
.we
.eq(self
.p
.valid_i_test
),
787 self
.p
.ready_o
.eq(fifo
.writable
),
788 nmoperator
.eq(fifo
.din
, nmoperator
.cat(result
)),
791 # connect next rdy/valid/data - do cat on data_o (further below)
792 connections
= [self
.n
.valid_o
.eq(fifo
.readable
),
793 fifo
.re
.eq(self
.n
.ready_i_test
),
795 if self
.fwft
or self
.buffered
:
796 m
.d
.comb
+= connections
# combinatorial on next ready/valid
798 m
.d
.sync
+= connections
# unbuffered fwft mode needs sync
799 data_o
= nmoperator
.cat(self
.n
.data_o
).eq(fifo
.dout
)
800 data_o
= self
._postprocess
(data_o
) # XXX TBD, does nothing right now
807 class UnbufferedPipeline(FIFOControl
):
808 def __init__(self
, stage
, in_multi
=None, stage_ctl
=False):
809 FIFOControl
.__init
__(self
, 1, stage
, in_multi
, stage_ctl
,
810 fwft
=True, pipe
=False)
812 # aka "BreakReadyStage" XXX had to set fwft=True to get it to work
813 class PassThroughHandshake(FIFOControl
):
814 def __init__(self
, stage
, in_multi
=None, stage_ctl
=False):
815 FIFOControl
.__init
__(self
, 1, stage
, in_multi
, stage_ctl
,
816 fwft
=True, pipe
=True)
818 # this is *probably* BufferedHandshake, although test #997 now succeeds.
819 class BufferedHandshake(FIFOControl
):
820 def __init__(self
, stage
, in_multi
=None, stage_ctl
=False):
821 FIFOControl
.__init
__(self
, 2, stage
, in_multi
, stage_ctl
,
822 fwft
=True, pipe
=False)
826 # this is *probably* SimpleHandshake (note: memory cell size=0)
827 class SimpleHandshake(FIFOControl):
828 def __init__(self, stage, in_multi=None, stage_ctl=False):
829 FIFOControl.__init__(self, 0, stage, in_multi, stage_ctl,
830 fwft=True, pipe=False)