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 A simple stalling clock-synchronised pipeline that has no buffering
34 (unlike BufferedHandshake). Data flows on *every* clock cycle when
35 the conditions are right (this is nominally when the input is valid
36 and the output is ready).
38 A stall anywhere along the line will result in a stall back-propagating
39 down the entire chain. The BufferedHandshake by contrast will buffer
40 incoming data, allowing previous stages one clock cycle's grace before
43 An advantage of the UnbufferedPipeline over the Buffered one is
44 that the amount of logic needed (number of gates) is greatly
45 reduced (no second set of buffers basically)
47 The disadvantage of the UnbufferedPipeline is that the valid/ready
48 logic, if chained together, is *combinatorial*, resulting in
49 progressively larger gate delay.
54 A Control class that introduces a single clock delay, passing its
55 data through unaltered. Unlike RegisterPipeline (which relies
56 on UnbufferedPipeline and PassThroughStage) it handles ready/valid
62 A convenience class that, because UnbufferedPipeline introduces a single
63 clock delay, when its stage is a PassThroughStage, it results in a Pipeline
64 stage that, duh, delays its (unmodified) input by one clock cycle.
69 nmigen implementation of buffered pipeline stage, based on zipcpu:
70 https://zipcpu.com/blog/2017/08/14/strategies-for-pipelining.html
72 this module requires quite a bit of thought to understand how it works
73 (and why it is needed in the first place). reading the above is
74 *strongly* recommended.
76 unlike john dawson's IEEE754 FPU STB/ACK signalling, which requires
77 the STB / ACK signals to raise and lower (on separate clocks) before
78 data may proceeed (thus only allowing one piece of data to proceed
79 on *ALTERNATE* cycles), the signalling here is a true pipeline
80 where data will flow on *every* clock when the conditions are right.
82 input acceptance conditions are when:
83 * incoming previous-stage strobe (p.valid_i) is HIGH
84 * outgoing previous-stage ready (p.ready_o) is LOW
86 output transmission conditions are when:
87 * outgoing next-stage strobe (n.valid_o) is HIGH
88 * outgoing next-stage ready (n.ready_i) is LOW
90 the tricky bit is when the input has valid data and the output is not
91 ready to accept it. if it wasn't for the clock synchronisation, it
92 would be possible to tell the input "hey don't send that data, we're
93 not ready". unfortunately, it's not possible to "change the past":
94 the previous stage *has no choice* but to pass on its data.
96 therefore, the incoming data *must* be accepted - and stored: that
97 is the responsibility / contract that this stage *must* accept.
98 on the same clock, it's possible to tell the input that it must
99 not send any more data. this is the "stall" condition.
101 we now effectively have *two* possible pieces of data to "choose" from:
102 the buffered data, and the incoming data. the decision as to which
103 to process and output is based on whether we are in "stall" or not.
104 i.e. when the next stage is no longer ready, the output comes from
105 the buffer if a stall had previously occurred, otherwise it comes
106 direct from processing the input.
108 this allows us to respect a synchronous "travelling STB" with what
109 dan calls a "buffered handshake".
111 it's quite a complex state machine!
116 Synchronised pipeline, Based on:
117 https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
120 from nmigen
import Signal
, Cat
, Const
, Mux
, Module
, Value
, Elaboratable
121 from nmigen
.cli
import verilog
, rtlil
122 from nmigen
.lib
.fifo
import SyncFIFO
, SyncFIFOBuffered
123 from nmigen
.hdl
.ast
import ArrayProxy
124 from nmigen
.hdl
.rec
import Record
, Layout
126 from abc
import ABCMeta
, abstractmethod
127 from collections
.abc
import Sequence
, Iterable
128 from collections
import OrderedDict
129 from queue
import Queue
133 from iocontrol
import (Object
, RecordObject
, _spec
,
134 PrevControl
, NextControl
, StageCls
, Stage
,
135 ControlBase
, StageChain
)
138 class RecordBasedStage(Stage
):
139 """ convenience class which provides a Records-based layout.
140 honestly it's a lot easier just to create a direct Records-based
141 class (see ExampleAddRecordStage)
143 def __init__(self
, in_shape
, out_shape
, processfn
, setupfn
=None):
144 self
.in_shape
= in_shape
145 self
.out_shape
= out_shape
146 self
.__process
= processfn
147 self
.__setup
= setupfn
148 def ispec(self
): return Record(self
.in_shape
)
149 def ospec(self
): return Record(self
.out_shape
)
150 def process(seif
, i
): return self
.__process
(i
)
151 def setup(seif
, m
, i
): return self
.__setup
(m
, i
)
154 class BufferedHandshake(ControlBase
):
155 """ buffered pipeline stage. data and strobe signals travel in sync.
156 if ever the input is ready and the output is not, processed data
157 is shunted in a temporary register.
159 Argument: stage. see Stage API above
161 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
162 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
163 stage-1 p.data_i >>in stage n.data_o out>> stage+1
169 input data p.data_i is read (only), is processed and goes into an
170 intermediate result store [process()]. this is updated combinatorially.
172 in a non-stall condition, the intermediate result will go into the
173 output (update_output). however if ever there is a stall, it goes
174 into r_data instead [update_buffer()].
176 when the non-stall condition is released, r_data is the first
177 to be transferred to the output [flush_buffer()], and the stall
180 on the next cycle (as long as stall is not raised again) the
181 input may begin to be processed and transferred directly to output.
184 def elaborate(self
, platform
):
185 self
.m
= ControlBase
.elaborate(self
, platform
)
187 result
= _spec(self
.sh
.ospec
, "r_tmp")
188 r_data
= _spec(self
.sh
.ospec
, "r_data")
190 # establish some combinatorial temporaries
191 o_n_validn
= Signal(reset_less
=True)
192 n_ready_i
= Signal(reset_less
=True, name
="n_i_rdy_data")
193 nir_por
= Signal(reset_less
=True)
194 nir_por_n
= Signal(reset_less
=True)
195 p_valid_i
= Signal(reset_less
=True)
196 nir_novn
= Signal(reset_less
=True)
197 nirn_novn
= Signal(reset_less
=True)
198 por_pivn
= Signal(reset_less
=True)
199 npnn
= Signal(reset_less
=True)
200 self
.m
.d
.comb
+= [p_valid_i
.eq(self
.p
.valid_i_test
),
201 o_n_validn
.eq(~self
.n
.valid_o
),
202 n_ready_i
.eq(self
.n
.ready_i_test
),
203 nir_por
.eq(n_ready_i
& self
.p
._ready
_o
),
204 nir_por_n
.eq(n_ready_i
& ~self
.p
._ready
_o
),
205 nir_novn
.eq(n_ready_i | o_n_validn
),
206 nirn_novn
.eq(~n_ready_i
& o_n_validn
),
207 npnn
.eq(nir_por | nirn_novn
),
208 por_pivn
.eq(self
.p
._ready
_o
& ~p_valid_i
)
211 # store result of processing in combinatorial temporary
212 self
.m
.d
.comb
+= nmoperator
.eq(result
, self
.data_r
)
214 # if not in stall condition, update the temporary register
215 with self
.m
.If(self
.p
.ready_o
): # not stalled
216 self
.m
.d
.sync
+= nmoperator
.eq(r_data
, result
) # update buffer
218 # data pass-through conditions
219 with self
.m
.If(npnn
):
220 data_o
= self
._postprocess
(result
) # XXX TBD, does nothing right now
221 self
.m
.d
.sync
+= [self
.n
.valid_o
.eq(p_valid_i
), # valid if p_valid
222 nmoperator
.eq(self
.n
.data_o
, data_o
), # update out
224 # buffer flush conditions (NOTE: can override data passthru conditions)
225 with self
.m
.If(nir_por_n
): # not stalled
226 # Flush the [already processed] buffer to the output port.
227 data_o
= self
._postprocess
(r_data
) # XXX TBD, does nothing right now
228 self
.m
.d
.sync
+= [self
.n
.valid_o
.eq(1), # reg empty
229 nmoperator
.eq(self
.n
.data_o
, data_o
), # flush
231 # output ready conditions
232 self
.m
.d
.sync
+= self
.p
._ready
_o
.eq(nir_novn | por_pivn
)
237 class SimpleHandshake(ControlBase
):
238 """ simple handshake control. data and strobe signals travel in sync.
239 implements the protocol used by Wishbone and AXI4.
241 Argument: stage. see Stage API above
243 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
244 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
245 stage-1 p.data_i >>in stage n.data_o out>> stage+1
250 Inputs Temporary Output Data
251 ------- ---------- ----- ----
252 P P N N PiV& ~NiR& N P
259 0 0 1 0 0 0 0 1 process(data_i)
260 0 0 1 1 0 0 0 1 process(data_i)
264 0 1 1 0 0 0 0 1 process(data_i)
265 0 1 1 1 0 0 0 1 process(data_i)
269 1 0 1 0 0 0 0 1 process(data_i)
270 1 0 1 1 0 0 0 1 process(data_i)
272 1 1 0 0 1 0 1 0 process(data_i)
273 1 1 0 1 1 1 1 0 process(data_i)
274 1 1 1 0 1 0 1 1 process(data_i)
275 1 1 1 1 1 0 1 1 process(data_i)
279 def elaborate(self
, platform
):
280 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
283 result
= _spec(self
.sh
.ospec
, "r_tmp")
285 # establish some combinatorial temporaries
286 n_ready_i
= Signal(reset_less
=True, name
="n_i_rdy_data")
287 p_valid_i_p_ready_o
= Signal(reset_less
=True)
288 p_valid_i
= Signal(reset_less
=True)
289 m
.d
.comb
+= [p_valid_i
.eq(self
.p
.valid_i_test
),
290 n_ready_i
.eq(self
.n
.ready_i_test
),
291 p_valid_i_p_ready_o
.eq(p_valid_i
& self
.p
.ready_o
),
294 # store result of processing in combinatorial temporary
295 m
.d
.comb
+= nmoperator
.eq(result
, self
.data_r
)
297 # previous valid and ready
298 with m
.If(p_valid_i_p_ready_o
):
299 data_o
= self
._postprocess
(result
) # XXX TBD, does nothing right now
300 m
.d
.sync
+= [r_busy
.eq(1), # output valid
301 nmoperator
.eq(self
.n
.data_o
, data_o
), # update output
303 # previous invalid or not ready, however next is accepting
304 with m
.Elif(n_ready_i
):
305 data_o
= self
._postprocess
(result
) # XXX TBD, does nothing right now
306 m
.d
.sync
+= [nmoperator
.eq(self
.n
.data_o
, data_o
)]
307 # TODO: could still send data here (if there was any)
308 #m.d.sync += self.n.valid_o.eq(0) # ...so set output invalid
309 m
.d
.sync
+= r_busy
.eq(0) # ...so set output invalid
311 m
.d
.comb
+= self
.n
.valid_o
.eq(r_busy
)
312 # if next is ready, so is previous
313 m
.d
.comb
+= self
.p
._ready
_o
.eq(n_ready_i
)
318 class UnbufferedPipeline(ControlBase
):
319 """ A simple pipeline stage with single-clock synchronisation
320 and two-way valid/ready synchronised signalling.
322 Note that a stall in one stage will result in the entire pipeline
325 Also that unlike BufferedHandshake, the valid/ready signalling does NOT
326 travel synchronously with the data: the valid/ready signalling
327 combines in a *combinatorial* fashion. Therefore, a long pipeline
328 chain will lengthen propagation delays.
330 Argument: stage. see Stage API, above
332 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
333 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
334 stage-1 p.data_i >>in stage n.data_o out>> stage+1
342 p.data_i : StageInput, shaped according to ispec
344 p.data_o : StageOutput, shaped according to ospec
346 r_data : input_shape according to ispec
347 A temporary (buffered) copy of a prior (valid) input.
348 This is HELD if the output is not ready. It is updated
350 result: output_shape according to ospec
351 The output of the combinatorial logic. it is updated
352 COMBINATORIALLY (no clock dependence).
356 Inputs Temp Output Data
378 1 1 0 0 0 1 1 process(data_i)
379 1 1 0 1 1 1 0 process(data_i)
380 1 1 1 0 0 1 1 process(data_i)
381 1 1 1 1 0 1 1 process(data_i)
384 Note: PoR is *NOT* involved in the above decision-making.
387 def elaborate(self
, platform
):
388 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
390 data_valid
= Signal() # is data valid or not
391 r_data
= _spec(self
.sh
.ospec
, "r_tmp") # output type
394 p_valid_i
= Signal(reset_less
=True)
395 pv
= Signal(reset_less
=True)
396 buf_full
= Signal(reset_less
=True)
397 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
398 m
.d
.comb
+= pv
.eq(self
.p
.valid_i
& self
.p
.ready_o
)
399 m
.d
.comb
+= buf_full
.eq(~self
.n
.ready_i_test
& data_valid
)
401 m
.d
.comb
+= self
.n
.valid_o
.eq(data_valid
)
402 m
.d
.comb
+= self
.p
._ready
_o
.eq(~data_valid | self
.n
.ready_i_test
)
403 m
.d
.sync
+= data_valid
.eq(p_valid_i | buf_full
)
406 m
.d
.sync
+= nmoperator
.eq(r_data
, self
.data_r
)
407 data_o
= self
._postprocess
(r_data
) # XXX TBD, does nothing right now
408 m
.d
.comb
+= nmoperator
.eq(self
.n
.data_o
, data_o
)
412 class UnbufferedPipeline2(ControlBase
):
413 """ A simple pipeline stage with single-clock synchronisation
414 and two-way valid/ready synchronised signalling.
416 Note that a stall in one stage will result in the entire pipeline
419 Also that unlike BufferedHandshake, the valid/ready signalling does NOT
420 travel synchronously with the data: the valid/ready signalling
421 combines in a *combinatorial* fashion. Therefore, a long pipeline
422 chain will lengthen propagation delays.
424 Argument: stage. see Stage API, above
426 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
427 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
428 stage-1 p.data_i >>in stage n.data_o out>> stage+1
433 p.data_i : StageInput, shaped according to ispec
435 p.data_o : StageOutput, shaped according to ospec
437 buf : output_shape according to ospec
438 A temporary (buffered) copy of a valid output
439 This is HELD if the output is not ready. It is updated
442 Inputs Temp Output Data
444 P P N N ~NiR& N P (buf_full)
449 0 0 0 0 0 0 1 process(data_i)
450 0 0 0 1 1 1 0 reg (odata, unchanged)
451 0 0 1 0 0 0 1 process(data_i)
452 0 0 1 1 0 0 1 process(data_i)
454 0 1 0 0 0 0 1 process(data_i)
455 0 1 0 1 1 1 0 reg (odata, unchanged)
456 0 1 1 0 0 0 1 process(data_i)
457 0 1 1 1 0 0 1 process(data_i)
459 1 0 0 0 0 1 1 process(data_i)
460 1 0 0 1 1 1 0 reg (odata, unchanged)
461 1 0 1 0 0 1 1 process(data_i)
462 1 0 1 1 0 1 1 process(data_i)
464 1 1 0 0 0 1 1 process(data_i)
465 1 1 0 1 1 1 0 reg (odata, unchanged)
466 1 1 1 0 0 1 1 process(data_i)
467 1 1 1 1 0 1 1 process(data_i)
470 Note: PoR is *NOT* involved in the above decision-making.
473 def elaborate(self
, platform
):
474 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
476 buf_full
= Signal() # is data valid or not
477 buf
= _spec(self
.sh
.ospec
, "r_tmp") # output type
480 p_valid_i
= Signal(reset_less
=True)
481 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
483 m
.d
.comb
+= self
.n
.valid_o
.eq(buf_full | p_valid_i
)
484 m
.d
.comb
+= self
.p
._ready
_o
.eq(~buf_full
)
485 m
.d
.sync
+= buf_full
.eq(~self
.n
.ready_i_test
& self
.n
.valid_o
)
487 data_o
= Mux(buf_full
, buf
, self
.data_r
)
488 data_o
= self
._postprocess
(data_o
) # XXX TBD, does nothing right now
489 m
.d
.comb
+= nmoperator
.eq(self
.n
.data_o
, data_o
)
490 m
.d
.sync
+= nmoperator
.eq(buf
, self
.n
.data_o
)
495 class PassThroughStage(StageCls
):
496 """ a pass-through stage with its input data spec identical to its output,
497 and "passes through" its data from input to output (does nothing).
499 use this basically to explicitly make any data spec Stage-compliant.
500 (many APIs would potentially use a static "wrap" method in e.g.
501 StageCls to achieve a similar effect)
503 def __init__(self
, iospecfn
):
504 self
.iospecfn
= iospecfn
505 def ispec(self
): return self
.iospecfn()
506 def ospec(self
): return self
.iospecfn()
507 def process(self
, i
): return i
510 class PassThroughHandshake(ControlBase
):
511 """ A control block that delays by one clock cycle.
513 Inputs Temporary Output Data
514 ------- ------------------ ----- ----
515 P P N N PiV& PiV| NiR| pvr N P (pvr)
516 i o i o PoR ~PoR ~NoV o o
520 0 0 0 0 0 1 1 0 1 1 odata (unchanged)
521 0 0 0 1 0 1 0 0 1 0 odata (unchanged)
522 0 0 1 0 0 1 1 0 1 1 odata (unchanged)
523 0 0 1 1 0 1 1 0 1 1 odata (unchanged)
525 0 1 0 0 0 0 1 0 0 1 odata (unchanged)
526 0 1 0 1 0 0 0 0 0 0 odata (unchanged)
527 0 1 1 0 0 0 1 0 0 1 odata (unchanged)
528 0 1 1 1 0 0 1 0 0 1 odata (unchanged)
530 1 0 0 0 0 1 1 1 1 1 process(in)
531 1 0 0 1 0 1 0 0 1 0 odata (unchanged)
532 1 0 1 0 0 1 1 1 1 1 process(in)
533 1 0 1 1 0 1 1 1 1 1 process(in)
535 1 1 0 0 1 1 1 1 1 1 process(in)
536 1 1 0 1 1 1 0 0 1 0 odata (unchanged)
537 1 1 1 0 1 1 1 1 1 1 process(in)
538 1 1 1 1 1 1 1 1 1 1 process(in)
543 def elaborate(self
, platform
):
544 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
546 r_data
= _spec(self
.sh
.ospec
, "r_tmp") # output type
549 p_valid_i
= Signal(reset_less
=True)
550 pvr
= Signal(reset_less
=True)
551 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
552 m
.d
.comb
+= pvr
.eq(p_valid_i
& self
.p
.ready_o
)
554 m
.d
.comb
+= self
.p
.ready_o
.eq(~self
.n
.valid_o | self
.n
.ready_i_test
)
555 m
.d
.sync
+= self
.n
.valid_o
.eq(p_valid_i | ~self
.p
.ready_o
)
557 odata
= Mux(pvr
, self
.data_r
, r_data
)
558 m
.d
.sync
+= nmoperator
.eq(r_data
, odata
)
559 r_data
= self
._postprocess
(r_data
) # XXX TBD, does nothing right now
560 m
.d
.comb
+= nmoperator
.eq(self
.n
.data_o
, r_data
)
565 class RegisterPipeline(UnbufferedPipeline
):
566 """ A pipeline stage that delays by one clock cycle, creating a
567 sync'd latch out of data_o and valid_o as an indirect byproduct
568 of using PassThroughStage
570 def __init__(self
, iospecfn
):
571 UnbufferedPipeline
.__init
__(self
, PassThroughStage(iospecfn
))
574 class FIFOControl(ControlBase
):
575 """ FIFO Control. Uses SyncFIFO to store data, coincidentally
576 happens to have same valid/ready signalling as Stage API.
578 data_i -> fifo.din -> FIFO -> fifo.dout -> data_o
580 def __init__(self
, depth
, stage
, in_multi
=None, stage_ctl
=False,
581 fwft
=True, buffered
=False, pipe
=False):
584 * :depth: number of entries in the FIFO
585 * :stage: data processing block
586 * :fwft: first word fall-thru mode (non-fwft introduces delay)
587 * :buffered: use buffered FIFO (introduces extra cycle delay)
589 NOTE 1: FPGAs may have trouble with the defaults for SyncFIFO
590 (fwft=True, buffered=False). XXX TODO: fix this by
591 using Queue in all cases instead.
593 data is processed (and located) as follows:
595 self.p self.stage temp fn temp fn temp fp self.n
596 data_i->process()->result->cat->din.FIFO.dout->cat(data_o)
598 yes, really: cat produces a Cat() which can be assigned to.
599 this is how the FIFO gets de-catted without needing a de-cat
603 assert not (fwft
and buffered
), "buffered cannot do fwft"
607 self
.buffered
= buffered
610 ControlBase
.__init
__(self
, stage
, in_multi
, stage_ctl
)
612 def elaborate(self
, platform
):
613 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
615 # make a FIFO with a signal of equal width to the data_o.
616 (fwidth
, _
) = nmoperator
.shape(self
.n
.data_o
)
618 fifo
= SyncFIFOBuffered(fwidth
, self
.fdepth
)
620 fifo
= Queue(fwidth
, self
.fdepth
, fwft
=self
.fwft
, pipe
=self
.pipe
)
621 m
.submodules
.fifo
= fifo
623 # store result of processing in combinatorial temporary
624 result
= _spec(self
.sh
.ospec
, "r_temp")
625 m
.d
.comb
+= nmoperator
.eq(result
, self
.data_r
)
627 # connect previous rdy/valid/data - do cat on data_i
628 # NOTE: cannot do the PrevControl-looking trick because
629 # of need to process the data. shaaaame....
630 m
.d
.comb
+= [fifo
.we
.eq(self
.p
.valid_i_test
),
631 self
.p
.ready_o
.eq(fifo
.writable
),
632 nmoperator
.eq(fifo
.din
, nmoperator
.cat(result
)),
635 # connect next rdy/valid/data - do cat on data_o (further below)
636 connections
= [self
.n
.valid_o
.eq(fifo
.readable
),
637 fifo
.re
.eq(self
.n
.ready_i_test
),
639 if self
.fwft
or self
.buffered
:
640 m
.d
.comb
+= connections
# combinatorial on next ready/valid
642 m
.d
.sync
+= connections
# unbuffered fwft mode needs sync
643 data_o
= nmoperator
.cat(self
.n
.data_o
).eq(fifo
.dout
)
644 data_o
= self
._postprocess
(data_o
) # XXX TBD, does nothing right now
651 class UnbufferedPipeline(FIFOControl
):
652 def __init__(self
, stage
, in_multi
=None, stage_ctl
=False):
653 FIFOControl
.__init
__(self
, 1, stage
, in_multi
, stage_ctl
,
654 fwft
=True, pipe
=False)
656 # aka "BreakReadyStage" XXX had to set fwft=True to get it to work
657 class PassThroughHandshake(FIFOControl
):
658 def __init__(self
, stage
, in_multi
=None, stage_ctl
=False):
659 FIFOControl
.__init
__(self
, 1, stage
, in_multi
, stage_ctl
,
660 fwft
=True, pipe
=True)
662 # this is *probably* BufferedHandshake, although test #997 now succeeds.
663 class BufferedHandshake(FIFOControl
):
664 def __init__(self
, stage
, in_multi
=None, stage_ctl
=False):
665 FIFOControl
.__init
__(self
, 2, stage
, in_multi
, stage_ctl
,
666 fwft
=True, pipe
=False)
670 # this is *probably* SimpleHandshake (note: memory cell size=0)
671 class SimpleHandshake(FIFOControl):
672 def __init__(self, stage, in_multi=None, stage_ctl=False):
673 FIFOControl.__init__(self, 0, stage, in_multi, stage_ctl,
674 fwft=True, pipe=False)