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
11 A convenience class that takes an input shape, output shape, a
12 "processing" function and an optional "setup" function. Honestly
13 though, there's not much more effort to just... create a class
14 that returns a couple of Records (see ExampleAddRecordStage in
20 A convenience class that takes a single function as a parameter,
21 that is chain-called to create the exact same input and output spec.
22 It has a process() function that simply returns its input.
24 Instances of this class are completely redundant if handed to
25 StageChain, however when passed to UnbufferedPipeline they
26 can be used to introduce a single clock delay.
31 A simple stalling clock-synchronised pipeline that has no buffering
32 (unlike BufferedHandshake). Data flows on *every* clock cycle when
33 the conditions are right (this is nominally when the input is valid
34 and the output is ready).
36 A stall anywhere along the line will result in a stall back-propagating
37 down the entire chain. The BufferedHandshake by contrast will buffer
38 incoming data, allowing previous stages one clock cycle's grace before
41 An advantage of the UnbufferedPipeline over the Buffered one is
42 that the amount of logic needed (number of gates) is greatly
43 reduced (no second set of buffers basically)
45 The disadvantage of the UnbufferedPipeline is that the valid/ready
46 logic, if chained together, is *combinatorial*, resulting in
47 progressively larger gate delay.
52 A Control class that introduces a single clock delay, passing its
53 data through unaltered. Unlike RegisterPipeline (which relies
54 on UnbufferedPipeline and PassThroughStage) it handles ready/valid
60 A convenience class that, because UnbufferedPipeline introduces a single
61 clock delay, when its stage is a PassThroughStage, it results in a Pipeline
62 stage that, duh, delays its (unmodified) input by one clock cycle.
67 nmigen implementation of buffered pipeline stage, based on zipcpu:
68 https://zipcpu.com/blog/2017/08/14/strategies-for-pipelining.html
70 this module requires quite a bit of thought to understand how it works
71 (and why it is needed in the first place). reading the above is
72 *strongly* recommended.
74 unlike john dawson's IEEE754 FPU STB/ACK signalling, which requires
75 the STB / ACK signals to raise and lower (on separate clocks) before
76 data may proceeed (thus only allowing one piece of data to proceed
77 on *ALTERNATE* cycles), the signalling here is a true pipeline
78 where data will flow on *every* clock when the conditions are right.
80 input acceptance conditions are when:
81 * incoming previous-stage strobe (p.valid_i) is HIGH
82 * outgoing previous-stage ready (p.ready_o) is LOW
84 output transmission conditions are when:
85 * outgoing next-stage strobe (n.valid_o) is HIGH
86 * outgoing next-stage ready (n.ready_i) is LOW
88 the tricky bit is when the input has valid data and the output is not
89 ready to accept it. if it wasn't for the clock synchronisation, it
90 would be possible to tell the input "hey don't send that data, we're
91 not ready". unfortunately, it's not possible to "change the past":
92 the previous stage *has no choice* but to pass on its data.
94 therefore, the incoming data *must* be accepted - and stored: that
95 is the responsibility / contract that this stage *must* accept.
96 on the same clock, it's possible to tell the input that it must
97 not send any more data. this is the "stall" condition.
99 we now effectively have *two* possible pieces of data to "choose" from:
100 the buffered data, and the incoming data. the decision as to which
101 to process and output is based on whether we are in "stall" or not.
102 i.e. when the next stage is no longer ready, the output comes from
103 the buffer if a stall had previously occurred, otherwise it comes
104 direct from processing the input.
106 this allows us to respect a synchronous "travelling STB" with what
107 dan calls a "buffered handshake".
109 it's quite a complex state machine!
114 Synchronised pipeline, Based on:
115 https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
118 from nmigen
import Signal
, Cat
, Const
, Mux
, Module
, Value
, Elaboratable
119 from nmigen
.cli
import verilog
, rtlil
120 from nmigen
.lib
.fifo
import SyncFIFO
, SyncFIFOBuffered
121 from nmigen
.hdl
.ast
import ArrayProxy
122 from nmigen
.hdl
.rec
import Record
, Layout
124 from abc
import ABCMeta
, abstractmethod
125 from collections
.abc
import Sequence
, Iterable
126 from collections
import OrderedDict
127 from queue
import Queue
131 from iocontrol
import (Object
, RecordObject
, _spec
,
132 PrevControl
, NextControl
, StageCls
, Stage
,
133 ControlBase
, StageChain
)
136 class RecordBasedStage(Stage
):
137 """ convenience class which provides a Records-based layout.
138 honestly it's a lot easier just to create a direct Records-based
139 class (see ExampleAddRecordStage)
141 def __init__(self
, in_shape
, out_shape
, processfn
, setupfn
=None):
142 self
.in_shape
= in_shape
143 self
.out_shape
= out_shape
144 self
.__process
= processfn
145 self
.__setup
= setupfn
146 def ispec(self
): return Record(self
.in_shape
)
147 def ospec(self
): return Record(self
.out_shape
)
148 def process(seif
, i
): return self
.__process
(i
)
149 def setup(seif
, m
, i
): return self
.__setup
(m
, i
)
152 class BufferedHandshake(ControlBase
):
153 """ buffered pipeline stage. data and strobe signals travel in sync.
154 if ever the input is ready and the output is not, processed data
155 is shunted in a temporary register.
157 Argument: stage. see Stage API above
159 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
160 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
161 stage-1 p.data_i >>in stage n.data_o out>> stage+1
167 input data p.data_i is read (only), is processed and goes into an
168 intermediate result store [process()]. this is updated combinatorially.
170 in a non-stall condition, the intermediate result will go into the
171 output (update_output). however if ever there is a stall, it goes
172 into r_data instead [update_buffer()].
174 when the non-stall condition is released, r_data is the first
175 to be transferred to the output [flush_buffer()], and the stall
178 on the next cycle (as long as stall is not raised again) the
179 input may begin to be processed and transferred directly to output.
182 def elaborate(self
, platform
):
183 self
.m
= ControlBase
.elaborate(self
, platform
)
185 result
= _spec(self
.stage
.ospec
, "r_tmp")
186 r_data
= _spec(self
.stage
.ospec
, "r_data")
188 # establish some combinatorial temporaries
189 o_n_validn
= Signal(reset_less
=True)
190 n_ready_i
= Signal(reset_less
=True, name
="n_i_rdy_data")
191 nir_por
= Signal(reset_less
=True)
192 nir_por_n
= Signal(reset_less
=True)
193 p_valid_i
= Signal(reset_less
=True)
194 nir_novn
= Signal(reset_less
=True)
195 nirn_novn
= Signal(reset_less
=True)
196 por_pivn
= Signal(reset_less
=True)
197 npnn
= Signal(reset_less
=True)
198 self
.m
.d
.comb
+= [p_valid_i
.eq(self
.p
.valid_i_test
),
199 o_n_validn
.eq(~self
.n
.valid_o
),
200 n_ready_i
.eq(self
.n
.ready_i_test
),
201 nir_por
.eq(n_ready_i
& self
.p
._ready
_o
),
202 nir_por_n
.eq(n_ready_i
& ~self
.p
._ready
_o
),
203 nir_novn
.eq(n_ready_i | o_n_validn
),
204 nirn_novn
.eq(~n_ready_i
& o_n_validn
),
205 npnn
.eq(nir_por | nirn_novn
),
206 por_pivn
.eq(self
.p
._ready
_o
& ~p_valid_i
)
209 # store result of processing in combinatorial temporary
210 self
.m
.d
.comb
+= nmoperator
.eq(result
, self
.stage
.process(self
.p
.data_i
))
212 # if not in stall condition, update the temporary register
213 with self
.m
.If(self
.p
.ready_o
): # not stalled
214 self
.m
.d
.sync
+= nmoperator
.eq(r_data
, result
) # update buffer
216 # data pass-through conditions
217 with self
.m
.If(npnn
):
218 data_o
= self
._postprocess
(result
) # XXX TBD, does nothing right now
219 self
.m
.d
.sync
+= [self
.n
.valid_o
.eq(p_valid_i
), # valid if p_valid
220 nmoperator
.eq(self
.n
.data_o
, data_o
), # update out
222 # buffer flush conditions (NOTE: can override data passthru conditions)
223 with self
.m
.If(nir_por_n
): # not stalled
224 # Flush the [already processed] buffer to the output port.
225 data_o
= self
._postprocess
(r_data
) # XXX TBD, does nothing right now
226 self
.m
.d
.sync
+= [self
.n
.valid_o
.eq(1), # reg empty
227 nmoperator
.eq(self
.n
.data_o
, data_o
), # flush
229 # output ready conditions
230 self
.m
.d
.sync
+= self
.p
._ready
_o
.eq(nir_novn | por_pivn
)
235 class SimpleHandshake(ControlBase
):
236 """ simple handshake control. data and strobe signals travel in sync.
237 implements the protocol used by Wishbone and AXI4.
239 Argument: stage. see Stage API above
241 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
242 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
243 stage-1 p.data_i >>in stage n.data_o out>> stage+1
248 Inputs Temporary Output Data
249 ------- ---------- ----- ----
250 P P N N PiV& ~NiR& N P
257 0 0 1 0 0 0 0 1 process(data_i)
258 0 0 1 1 0 0 0 1 process(data_i)
262 0 1 1 0 0 0 0 1 process(data_i)
263 0 1 1 1 0 0 0 1 process(data_i)
267 1 0 1 0 0 0 0 1 process(data_i)
268 1 0 1 1 0 0 0 1 process(data_i)
270 1 1 0 0 1 0 1 0 process(data_i)
271 1 1 0 1 1 1 1 0 process(data_i)
272 1 1 1 0 1 0 1 1 process(data_i)
273 1 1 1 1 1 0 1 1 process(data_i)
277 def elaborate(self
, platform
):
278 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
281 result
= _spec(self
.stage
.ospec
, "r_tmp")
283 # establish some combinatorial temporaries
284 n_ready_i
= Signal(reset_less
=True, name
="n_i_rdy_data")
285 p_valid_i_p_ready_o
= Signal(reset_less
=True)
286 p_valid_i
= Signal(reset_less
=True)
287 m
.d
.comb
+= [p_valid_i
.eq(self
.p
.valid_i_test
),
288 n_ready_i
.eq(self
.n
.ready_i_test
),
289 p_valid_i_p_ready_o
.eq(p_valid_i
& self
.p
.ready_o
),
292 # store result of processing in combinatorial temporary
293 m
.d
.comb
+= nmoperator
.eq(result
, self
.stage
.process(self
.p
.data_i
))
295 # previous valid and ready
296 with m
.If(p_valid_i_p_ready_o
):
297 data_o
= self
._postprocess
(result
) # XXX TBD, does nothing right now
298 m
.d
.sync
+= [r_busy
.eq(1), # output valid
299 nmoperator
.eq(self
.n
.data_o
, data_o
), # update output
301 # previous invalid or not ready, however next is accepting
302 with m
.Elif(n_ready_i
):
303 data_o
= self
._postprocess
(result
) # XXX TBD, does nothing right now
304 m
.d
.sync
+= [nmoperator
.eq(self
.n
.data_o
, data_o
)]
305 # TODO: could still send data here (if there was any)
306 #m.d.sync += self.n.valid_o.eq(0) # ...so set output invalid
307 m
.d
.sync
+= r_busy
.eq(0) # ...so set output invalid
309 m
.d
.comb
+= self
.n
.valid_o
.eq(r_busy
)
310 # if next is ready, so is previous
311 m
.d
.comb
+= self
.p
._ready
_o
.eq(n_ready_i
)
316 class UnbufferedPipeline(ControlBase
):
317 """ A simple pipeline stage with single-clock synchronisation
318 and two-way valid/ready synchronised signalling.
320 Note that a stall in one stage will result in the entire pipeline
323 Also that unlike BufferedHandshake, the valid/ready signalling does NOT
324 travel synchronously with the data: the valid/ready signalling
325 combines in a *combinatorial* fashion. Therefore, a long pipeline
326 chain will lengthen propagation delays.
328 Argument: stage. see Stage API, above
330 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
331 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
332 stage-1 p.data_i >>in stage n.data_o out>> stage+1
340 p.data_i : StageInput, shaped according to ispec
342 p.data_o : StageOutput, shaped according to ospec
344 r_data : input_shape according to ispec
345 A temporary (buffered) copy of a prior (valid) input.
346 This is HELD if the output is not ready. It is updated
348 result: output_shape according to ospec
349 The output of the combinatorial logic. it is updated
350 COMBINATORIALLY (no clock dependence).
354 Inputs Temp Output Data
376 1 1 0 0 0 1 1 process(data_i)
377 1 1 0 1 1 1 0 process(data_i)
378 1 1 1 0 0 1 1 process(data_i)
379 1 1 1 1 0 1 1 process(data_i)
382 Note: PoR is *NOT* involved in the above decision-making.
385 def elaborate(self
, platform
):
386 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
388 data_valid
= Signal() # is data valid or not
389 r_data
= _spec(self
.stage
.ospec
, "r_tmp") # output type
392 p_valid_i
= Signal(reset_less
=True)
393 pv
= Signal(reset_less
=True)
394 buf_full
= Signal(reset_less
=True)
395 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
396 m
.d
.comb
+= pv
.eq(self
.p
.valid_i
& self
.p
.ready_o
)
397 m
.d
.comb
+= buf_full
.eq(~self
.n
.ready_i_test
& data_valid
)
399 m
.d
.comb
+= self
.n
.valid_o
.eq(data_valid
)
400 m
.d
.comb
+= self
.p
._ready
_o
.eq(~data_valid | self
.n
.ready_i_test
)
401 m
.d
.sync
+= data_valid
.eq(p_valid_i | buf_full
)
404 m
.d
.sync
+= nmoperator
.eq(r_data
, self
.stage
.process(self
.p
.data_i
))
405 data_o
= self
._postprocess
(r_data
) # XXX TBD, does nothing right now
406 m
.d
.comb
+= nmoperator
.eq(self
.n
.data_o
, data_o
)
410 class UnbufferedPipeline2(ControlBase
):
411 """ A simple pipeline stage with single-clock synchronisation
412 and two-way valid/ready synchronised signalling.
414 Note that a stall in one stage will result in the entire pipeline
417 Also that unlike BufferedHandshake, the valid/ready signalling does NOT
418 travel synchronously with the data: the valid/ready signalling
419 combines in a *combinatorial* fashion. Therefore, a long pipeline
420 chain will lengthen propagation delays.
422 Argument: stage. see Stage API, above
424 stage-1 p.valid_i >>in stage n.valid_o out>> stage+1
425 stage-1 p.ready_o <<out stage n.ready_i <<in stage+1
426 stage-1 p.data_i >>in stage n.data_o out>> stage+1
431 p.data_i : StageInput, shaped according to ispec
433 p.data_o : StageOutput, shaped according to ospec
435 buf : output_shape according to ospec
436 A temporary (buffered) copy of a valid output
437 This is HELD if the output is not ready. It is updated
440 Inputs Temp Output Data
442 P P N N ~NiR& N P (buf_full)
447 0 0 0 0 0 0 1 process(data_i)
448 0 0 0 1 1 1 0 reg (odata, unchanged)
449 0 0 1 0 0 0 1 process(data_i)
450 0 0 1 1 0 0 1 process(data_i)
452 0 1 0 0 0 0 1 process(data_i)
453 0 1 0 1 1 1 0 reg (odata, unchanged)
454 0 1 1 0 0 0 1 process(data_i)
455 0 1 1 1 0 0 1 process(data_i)
457 1 0 0 0 0 1 1 process(data_i)
458 1 0 0 1 1 1 0 reg (odata, unchanged)
459 1 0 1 0 0 1 1 process(data_i)
460 1 0 1 1 0 1 1 process(data_i)
462 1 1 0 0 0 1 1 process(data_i)
463 1 1 0 1 1 1 0 reg (odata, unchanged)
464 1 1 1 0 0 1 1 process(data_i)
465 1 1 1 1 0 1 1 process(data_i)
468 Note: PoR is *NOT* involved in the above decision-making.
471 def elaborate(self
, platform
):
472 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
474 buf_full
= Signal() # is data valid or not
475 buf
= _spec(self
.stage
.ospec
, "r_tmp") # output type
478 p_valid_i
= Signal(reset_less
=True)
479 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
481 m
.d
.comb
+= self
.n
.valid_o
.eq(buf_full | p_valid_i
)
482 m
.d
.comb
+= self
.p
._ready
_o
.eq(~buf_full
)
483 m
.d
.sync
+= buf_full
.eq(~self
.n
.ready_i_test
& self
.n
.valid_o
)
485 data_o
= Mux(buf_full
, buf
, self
.stage
.process(self
.p
.data_i
))
486 data_o
= self
._postprocess
(data_o
) # XXX TBD, does nothing right now
487 m
.d
.comb
+= nmoperator
.eq(self
.n
.data_o
, data_o
)
488 m
.d
.sync
+= nmoperator
.eq(buf
, self
.n
.data_o
)
493 class PassThroughStage(StageCls
):
494 """ a pass-through stage with its input data spec identical to its output,
495 and "passes through" its data from input to output (does nothing).
497 use this basically to explicitly make any data spec Stage-compliant.
498 (many APIs would potentially use a static "wrap" method in e.g.
499 StageCls to achieve a similar effect)
501 def __init__(self
, iospecfn
):
502 self
.iospecfn
= iospecfn
503 def ispec(self
): return self
.iospecfn()
504 def ospec(self
): return self
.iospecfn()
505 def process(self
, i
): return i
508 class PassThroughHandshake(ControlBase
):
509 """ A control block that delays by one clock cycle.
511 Inputs Temporary Output Data
512 ------- ------------------ ----- ----
513 P P N N PiV& PiV| NiR| pvr N P (pvr)
514 i o i o PoR ~PoR ~NoV o o
518 0 0 0 0 0 1 1 0 1 1 odata (unchanged)
519 0 0 0 1 0 1 0 0 1 0 odata (unchanged)
520 0 0 1 0 0 1 1 0 1 1 odata (unchanged)
521 0 0 1 1 0 1 1 0 1 1 odata (unchanged)
523 0 1 0 0 0 0 1 0 0 1 odata (unchanged)
524 0 1 0 1 0 0 0 0 0 0 odata (unchanged)
525 0 1 1 0 0 0 1 0 0 1 odata (unchanged)
526 0 1 1 1 0 0 1 0 0 1 odata (unchanged)
528 1 0 0 0 0 1 1 1 1 1 process(in)
529 1 0 0 1 0 1 0 0 1 0 odata (unchanged)
530 1 0 1 0 0 1 1 1 1 1 process(in)
531 1 0 1 1 0 1 1 1 1 1 process(in)
533 1 1 0 0 1 1 1 1 1 1 process(in)
534 1 1 0 1 1 1 0 0 1 0 odata (unchanged)
535 1 1 1 0 1 1 1 1 1 1 process(in)
536 1 1 1 1 1 1 1 1 1 1 process(in)
541 def elaborate(self
, platform
):
542 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
544 r_data
= _spec(self
.stage
.ospec
, "r_tmp") # output type
547 p_valid_i
= Signal(reset_less
=True)
548 pvr
= Signal(reset_less
=True)
549 m
.d
.comb
+= p_valid_i
.eq(self
.p
.valid_i_test
)
550 m
.d
.comb
+= pvr
.eq(p_valid_i
& self
.p
.ready_o
)
552 m
.d
.comb
+= self
.p
.ready_o
.eq(~self
.n
.valid_o | self
.n
.ready_i_test
)
553 m
.d
.sync
+= self
.n
.valid_o
.eq(p_valid_i | ~self
.p
.ready_o
)
555 odata
= Mux(pvr
, self
.stage
.process(self
.p
.data_i
), r_data
)
556 m
.d
.sync
+= nmoperator
.eq(r_data
, odata
)
557 r_data
= self
._postprocess
(r_data
) # XXX TBD, does nothing right now
558 m
.d
.comb
+= nmoperator
.eq(self
.n
.data_o
, r_data
)
563 class RegisterPipeline(UnbufferedPipeline
):
564 """ A pipeline stage that delays by one clock cycle, creating a
565 sync'd latch out of data_o and valid_o as an indirect byproduct
566 of using PassThroughStage
568 def __init__(self
, iospecfn
):
569 UnbufferedPipeline
.__init
__(self
, PassThroughStage(iospecfn
))
572 class FIFOControl(ControlBase
):
573 """ FIFO Control. Uses SyncFIFO to store data, coincidentally
574 happens to have same valid/ready signalling as Stage API.
576 data_i -> fifo.din -> FIFO -> fifo.dout -> data_o
579 def __init__(self
, depth
, stage
, in_multi
=None, stage_ctl
=False,
580 fwft
=True, buffered
=False, pipe
=False):
583 * depth: number of entries in the FIFO
584 * stage: data processing block
585 * fwft : first word fall-thru mode (non-fwft introduces delay)
586 * buffered: use buffered FIFO (introduces extra cycle delay)
588 NOTE 1: FPGAs may have trouble with the defaults for SyncFIFO
589 (fwft=True, buffered=False)
591 NOTE 2: data_i *must* have a shape function. it can therefore
592 be a Signal, or a Record, or a RecordObject.
594 data is processed (and located) as follows:
596 self.p self.stage temp fn temp fn temp fp self.n
597 data_i->process()->result->cat->din.FIFO.dout->cat(data_o)
599 yes, really: cat produces a Cat() which can be assigned to.
600 this is how the FIFO gets de-catted without needing a de-cat
604 assert not (fwft
and buffered
), "buffered cannot do fwft"
608 self
.buffered
= buffered
611 ControlBase
.__init
__(self
, stage
, in_multi
, stage_ctl
)
613 def elaborate(self
, platform
):
614 self
.m
= m
= ControlBase
.elaborate(self
, platform
)
616 # make a FIFO with a signal of equal width to the data_o.
617 (fwidth
, _
) = nmoperator
.shape(self
.n
.data_o
)
619 fifo
= SyncFIFOBuffered(fwidth
, self
.fdepth
)
621 fifo
= Queue(fwidth
, self
.fdepth
, fwft
=self
.fwft
, pipe
=self
.pipe
)
622 m
.submodules
.fifo
= fifo
624 # store result of processing in combinatorial temporary
625 result
= _spec(self
.stage
.ospec
, "r_temp")
626 m
.d
.comb
+= nmoperator
.eq(result
, self
.stage
.process(self
.p
.data_i
))
628 # connect previous rdy/valid/data - do cat on data_i
629 # NOTE: cannot do the PrevControl-looking trick because
630 # of need to process the data. shaaaame....
631 m
.d
.comb
+= [fifo
.we
.eq(self
.p
.valid_i_test
),
632 self
.p
.ready_o
.eq(fifo
.writable
),
633 nmoperator
.eq(fifo
.din
, nmoperator
.cat(result
)),
636 # connect next rdy/valid/data - do cat on data_o
637 connections
= [self
.n
.valid_o
.eq(fifo
.readable
),
638 fifo
.re
.eq(self
.n
.ready_i_test
),
640 if self
.fwft
or self
.buffered
:
641 m
.d
.comb
+= connections
643 m
.d
.sync
+= connections
# unbuffered fwft mode needs sync
644 data_o
= nmoperator
.cat(self
.n
.data_o
).eq(fifo
.dout
)
645 data_o
= self
._postprocess
(data_o
) # XXX TBD, does nothing right now
652 class UnbufferedPipeline(FIFOControl
):
653 def __init__(self
, stage
, in_multi
=None, stage_ctl
=False):
654 FIFOControl
.__init
__(self
, 1, stage
, in_multi
, stage_ctl
,
655 fwft
=True, pipe
=False)
657 # aka "BreakReadyStage" XXX had to set fwft=True to get it to work
658 class PassThroughHandshake(FIFOControl
):
659 def __init__(self
, stage
, in_multi
=None, stage_ctl
=False):
660 FIFOControl
.__init
__(self
, 1, stage
, in_multi
, stage_ctl
,
661 fwft
=True, pipe
=True)
663 # this is *probably* BufferedHandshake, although test #997 now succeeds.
664 class BufferedHandshake(FIFOControl
):
665 def __init__(self
, stage
, in_multi
=None, stage_ctl
=False):
666 FIFOControl
.__init
__(self
, 2, stage
, in_multi
, stage_ctl
,
667 fwft
=True, pipe
=False)
671 # this is *probably* SimpleHandshake (note: memory cell size=0)
672 class SimpleHandshake(FIFOControl):
673 def __init__(self, stage, in_multi=None, stage_ctl=False):
674 FIFOControl.__init__(self, 0, stage, in_multi, stage_ctl,
675 fwft=True, pipe=False)