split out IO control classes to separate module
[ieee754fpu.git] / src / add / singlepipe.py
1 """ Pipeline and BufferedHandshake implementation, conforming to the same API.
2 For multi-input and multi-output variants, see multipipe.
3
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
7
8 RecordBasedStage:
9 ----------------
10
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
15 examples).
16
17 PassThroughStage:
18 ----------------
19
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.
23
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.
27
28 UnbufferedPipeline:
29 ------------------
30
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).
35
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
39 also having to stall.
40
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)
44
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.
48
49 PassThroughHandshake:
50 ------------------
51
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
55 itself.
56
57 RegisterPipeline:
58 ----------------
59
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.
63
64 BufferedHandshake:
65 ----------------
66
67 nmigen implementation of buffered pipeline stage, based on zipcpu:
68 https://zipcpu.com/blog/2017/08/14/strategies-for-pipelining.html
69
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.
73
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.
79
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
83
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
87
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.
93
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.
98
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.
105
106 this allows us to respect a synchronous "travelling STB" with what
107 dan calls a "buffered handshake".
108
109 it's quite a complex state machine!
110
111 SimpleHandshake
112 ---------------
113
114 Synchronised pipeline, Based on:
115 https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
116 """
117
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
123
124 from abc import ABCMeta, abstractmethod
125 from collections.abc import Sequence, Iterable
126 from collections import OrderedDict
127 from queue import Queue
128 import inspect
129
130 from nmoperator import eq, cat, shape
131 from iocontrol import (Object, RecordObject, _spec,
132 PrevControl, NextControl, StageCls, Stage,
133 ControlBase, StageChain)
134
135
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)
140 """
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)
150
151
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.
156
157 Argument: stage. see Stage API above
158
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
162 | |
163 process --->----^
164 | |
165 +-- r_data ->-+
166
167 input data p.data_i is read (only), is processed and goes into an
168 intermediate result store [process()]. this is updated combinatorially.
169
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()].
173
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
176 condition cleared.
177
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.
180 """
181
182 def elaborate(self, platform):
183 self.m = ControlBase.elaborate(self, platform)
184
185 result = _spec(self.stage.ospec, "r_tmp")
186 r_data = _spec(self.stage.ospec, "r_data")
187
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)
207 ]
208
209 # store result of processing in combinatorial temporary
210 self.m.d.comb += eq(result, self.stage.process(self.p.data_i))
211
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 += eq(r_data, result) # update buffer
215
216 # data pass-through conditions
217 with self.m.If(npnn):
218 data_o = self._postprocess(result)
219 self.m.d.sync += [self.n.valid_o.eq(p_valid_i), # valid if p_valid
220 eq(self.n.data_o, data_o), # update output
221 ]
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)
226 self.m.d.sync += [self.n.valid_o.eq(1), # reg empty
227 eq(self.n.data_o, data_o), # flush buffer
228 ]
229 # output ready conditions
230 self.m.d.sync += self.p._ready_o.eq(nir_novn | por_pivn)
231
232 return self.m
233
234
235 class SimpleHandshake(ControlBase):
236 """ simple handshake control. data and strobe signals travel in sync.
237 implements the protocol used by Wishbone and AXI4.
238
239 Argument: stage. see Stage API above
240
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
244 | |
245 +--process->--^
246 Truth Table
247
248 Inputs Temporary Output Data
249 ------- ---------- ----- ----
250 P P N N PiV& ~NiR& N P
251 i o i o PoR NoV o o
252 V R R V V R
253
254 ------- - - - -
255 0 0 0 0 0 0 >0 0 reg
256 0 0 0 1 0 1 >1 0 reg
257 0 0 1 0 0 0 0 1 process(data_i)
258 0 0 1 1 0 0 0 1 process(data_i)
259 ------- - - - -
260 0 1 0 0 0 0 >0 0 reg
261 0 1 0 1 0 1 >1 0 reg
262 0 1 1 0 0 0 0 1 process(data_i)
263 0 1 1 1 0 0 0 1 process(data_i)
264 ------- - - - -
265 1 0 0 0 0 0 >0 0 reg
266 1 0 0 1 0 1 >1 0 reg
267 1 0 1 0 0 0 0 1 process(data_i)
268 1 0 1 1 0 0 0 1 process(data_i)
269 ------- - - - -
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)
274 ------- - - - -
275 """
276
277 def elaborate(self, platform):
278 self.m = m = ControlBase.elaborate(self, platform)
279
280 r_busy = Signal()
281 result = _spec(self.stage.ospec, "r_tmp")
282
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),
290 ]
291
292 # store result of processing in combinatorial temporary
293 m.d.comb += eq(result, self.stage.process(self.p.data_i))
294
295 # previous valid and ready
296 with m.If(p_valid_i_p_ready_o):
297 data_o = self._postprocess(result)
298 m.d.sync += [r_busy.eq(1), # output valid
299 eq(self.n.data_o, data_o), # update output
300 ]
301 # previous invalid or not ready, however next is accepting
302 with m.Elif(n_ready_i):
303 data_o = self._postprocess(result)
304 m.d.sync += [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
308
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)
312
313 return self.m
314
315
316 class UnbufferedPipeline(ControlBase):
317 """ A simple pipeline stage with single-clock synchronisation
318 and two-way valid/ready synchronised signalling.
319
320 Note that a stall in one stage will result in the entire pipeline
321 chain stalling.
322
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.
327
328 Argument: stage. see Stage API, above
329
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
333 | |
334 r_data result
335 | |
336 +--process ->-+
337
338 Attributes:
339 -----------
340 p.data_i : StageInput, shaped according to ispec
341 The pipeline input
342 p.data_o : StageOutput, shaped according to ospec
343 The pipeline output
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
347 SYNCHRONOUSLY.
348 result: output_shape according to ospec
349 The output of the combinatorial logic. it is updated
350 COMBINATORIALLY (no clock dependence).
351
352 Truth Table
353
354 Inputs Temp Output Data
355 ------- - ----- ----
356 P P N N ~NiR& N P
357 i o i o NoV o o
358 V R R V V R
359
360 ------- - - -
361 0 0 0 0 0 0 1 reg
362 0 0 0 1 1 1 0 reg
363 0 0 1 0 0 0 1 reg
364 0 0 1 1 0 0 1 reg
365 ------- - - -
366 0 1 0 0 0 0 1 reg
367 0 1 0 1 1 1 0 reg
368 0 1 1 0 0 0 1 reg
369 0 1 1 1 0 0 1 reg
370 ------- - - -
371 1 0 0 0 0 1 1 reg
372 1 0 0 1 1 1 0 reg
373 1 0 1 0 0 1 1 reg
374 1 0 1 1 0 1 1 reg
375 ------- - - -
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)
380 ------- - - -
381
382 Note: PoR is *NOT* involved in the above decision-making.
383 """
384
385 def elaborate(self, platform):
386 self.m = m = ControlBase.elaborate(self, platform)
387
388 data_valid = Signal() # is data valid or not
389 r_data = _spec(self.stage.ospec, "r_tmp") # output type
390
391 # some temporaries
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)
398
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)
402
403 with m.If(pv):
404 m.d.sync += eq(r_data, self.stage.process(self.p.data_i))
405 data_o = self._postprocess(r_data)
406 m.d.comb += eq(self.n.data_o, data_o)
407
408 return self.m
409
410 class UnbufferedPipeline2(ControlBase):
411 """ A simple pipeline stage with single-clock synchronisation
412 and two-way valid/ready synchronised signalling.
413
414 Note that a stall in one stage will result in the entire pipeline
415 chain stalling.
416
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.
421
422 Argument: stage. see Stage API, above
423
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
427 | | |
428 +- process-> buf <-+
429 Attributes:
430 -----------
431 p.data_i : StageInput, shaped according to ispec
432 The pipeline input
433 p.data_o : StageOutput, shaped according to ospec
434 The pipeline output
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
438 SYNCHRONOUSLY.
439
440 Inputs Temp Output Data
441 ------- - -----
442 P P N N ~NiR& N P (buf_full)
443 i o i o NoV o o
444 V R R V V R
445
446 ------- - - -
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)
451 ------- - - -
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)
456 ------- - - -
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)
461 ------- - - -
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)
466 ------- - - -
467
468 Note: PoR is *NOT* involved in the above decision-making.
469 """
470
471 def elaborate(self, platform):
472 self.m = m = ControlBase.elaborate(self, platform)
473
474 buf_full = Signal() # is data valid or not
475 buf = _spec(self.stage.ospec, "r_tmp") # output type
476
477 # some temporaries
478 p_valid_i = Signal(reset_less=True)
479 m.d.comb += p_valid_i.eq(self.p.valid_i_test)
480
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)
484
485 data_o = Mux(buf_full, buf, self.stage.process(self.p.data_i))
486 data_o = self._postprocess(data_o)
487 m.d.comb += eq(self.n.data_o, data_o)
488 m.d.sync += eq(buf, self.n.data_o)
489
490 return self.m
491
492
493 class PassThroughStage(StageCls):
494 """ a pass-through stage which has its input data spec equal to its output,
495 and "passes through" its data from input to output.
496 """
497 def __init__(self, iospecfn):
498 self.iospecfn = iospecfn
499 def ispec(self): return self.iospecfn()
500 def ospec(self): return self.iospecfn()
501 def process(self, i): return i
502
503
504 class PassThroughHandshake(ControlBase):
505 """ A control block that delays by one clock cycle.
506
507 Inputs Temporary Output Data
508 ------- ------------------ ----- ----
509 P P N N PiV& PiV| NiR| pvr N P (pvr)
510 i o i o PoR ~PoR ~NoV o o
511 V R R V V R
512
513 ------- - - - - - -
514 0 0 0 0 0 1 1 0 1 1 odata (unchanged)
515 0 0 0 1 0 1 0 0 1 0 odata (unchanged)
516 0 0 1 0 0 1 1 0 1 1 odata (unchanged)
517 0 0 1 1 0 1 1 0 1 1 odata (unchanged)
518 ------- - - - - - -
519 0 1 0 0 0 0 1 0 0 1 odata (unchanged)
520 0 1 0 1 0 0 0 0 0 0 odata (unchanged)
521 0 1 1 0 0 0 1 0 0 1 odata (unchanged)
522 0 1 1 1 0 0 1 0 0 1 odata (unchanged)
523 ------- - - - - - -
524 1 0 0 0 0 1 1 1 1 1 process(in)
525 1 0 0 1 0 1 0 0 1 0 odata (unchanged)
526 1 0 1 0 0 1 1 1 1 1 process(in)
527 1 0 1 1 0 1 1 1 1 1 process(in)
528 ------- - - - - - -
529 1 1 0 0 1 1 1 1 1 1 process(in)
530 1 1 0 1 1 1 0 0 1 0 odata (unchanged)
531 1 1 1 0 1 1 1 1 1 1 process(in)
532 1 1 1 1 1 1 1 1 1 1 process(in)
533 ------- - - - - - -
534
535 """
536
537 def elaborate(self, platform):
538 self.m = m = ControlBase.elaborate(self, platform)
539
540 r_data = _spec(self.stage.ospec, "r_tmp") # output type
541
542 # temporaries
543 p_valid_i = Signal(reset_less=True)
544 pvr = Signal(reset_less=True)
545 m.d.comb += p_valid_i.eq(self.p.valid_i_test)
546 m.d.comb += pvr.eq(p_valid_i & self.p.ready_o)
547
548 m.d.comb += self.p.ready_o.eq(~self.n.valid_o | self.n.ready_i_test)
549 m.d.sync += self.n.valid_o.eq(p_valid_i | ~self.p.ready_o)
550
551 odata = Mux(pvr, self.stage.process(self.p.data_i), r_data)
552 m.d.sync += eq(r_data, odata)
553 r_data = self._postprocess(r_data)
554 m.d.comb += eq(self.n.data_o, r_data)
555
556 return m
557
558
559 class RegisterPipeline(UnbufferedPipeline):
560 """ A pipeline stage that delays by one clock cycle, creating a
561 sync'd latch out of data_o and valid_o as an indirect byproduct
562 of using PassThroughStage
563 """
564 def __init__(self, iospecfn):
565 UnbufferedPipeline.__init__(self, PassThroughStage(iospecfn))
566
567
568 class FIFOControl(ControlBase):
569 """ FIFO Control. Uses SyncFIFO to store data, coincidentally
570 happens to have same valid/ready signalling as Stage API.
571
572 data_i -> fifo.din -> FIFO -> fifo.dout -> data_o
573 """
574
575 def __init__(self, depth, stage, in_multi=None, stage_ctl=False,
576 fwft=True, buffered=False, pipe=False):
577 """ FIFO Control
578
579 * depth: number of entries in the FIFO
580 * stage: data processing block
581 * fwft : first word fall-thru mode (non-fwft introduces delay)
582 * buffered: use buffered FIFO (introduces extra cycle delay)
583
584 NOTE 1: FPGAs may have trouble with the defaults for SyncFIFO
585 (fwft=True, buffered=False)
586
587 NOTE 2: data_i *must* have a shape function. it can therefore
588 be a Signal, or a Record, or a RecordObject.
589
590 data is processed (and located) as follows:
591
592 self.p self.stage temp fn temp fn temp fp self.n
593 data_i->process()->result->cat->din.FIFO.dout->cat(data_o)
594
595 yes, really: cat produces a Cat() which can be assigned to.
596 this is how the FIFO gets de-catted without needing a de-cat
597 function
598 """
599
600 assert not (fwft and buffered), "buffered cannot do fwft"
601 if buffered:
602 depth += 1
603 self.fwft = fwft
604 self.buffered = buffered
605 self.pipe = pipe
606 self.fdepth = depth
607 ControlBase.__init__(self, stage, in_multi, stage_ctl)
608
609 def elaborate(self, platform):
610 self.m = m = ControlBase.elaborate(self, platform)
611
612 # make a FIFO with a signal of equal width to the data_o.
613 (fwidth, _) = shape(self.n.data_o)
614 if self.buffered:
615 fifo = SyncFIFOBuffered(fwidth, self.fdepth)
616 else:
617 fifo = Queue(fwidth, self.fdepth, fwft=self.fwft, pipe=self.pipe)
618 m.submodules.fifo = fifo
619
620 # store result of processing in combinatorial temporary
621 result = _spec(self.stage.ospec, "r_temp")
622 m.d.comb += eq(result, self.stage.process(self.p.data_i))
623
624 # connect previous rdy/valid/data - do cat on data_i
625 # NOTE: cannot do the PrevControl-looking trick because
626 # of need to process the data. shaaaame....
627 m.d.comb += [fifo.we.eq(self.p.valid_i_test),
628 self.p.ready_o.eq(fifo.writable),
629 eq(fifo.din, cat(result)),
630 ]
631
632 # connect next rdy/valid/data - do cat on data_o
633 connections = [self.n.valid_o.eq(fifo.readable),
634 fifo.re.eq(self.n.ready_i_test),
635 ]
636 if self.fwft or self.buffered:
637 m.d.comb += connections
638 else:
639 m.d.sync += connections # unbuffered fwft mode needs sync
640 data_o = cat(self.n.data_o).eq(fifo.dout)
641 data_o = self._postprocess(data_o)
642 m.d.comb += data_o
643
644 return m
645
646
647 # aka "RegStage".
648 class UnbufferedPipeline(FIFOControl):
649 def __init__(self, stage, in_multi=None, stage_ctl=False):
650 FIFOControl.__init__(self, 1, stage, in_multi, stage_ctl,
651 fwft=True, pipe=False)
652
653 # aka "BreakReadyStage" XXX had to set fwft=True to get it to work
654 class PassThroughHandshake(FIFOControl):
655 def __init__(self, stage, in_multi=None, stage_ctl=False):
656 FIFOControl.__init__(self, 1, stage, in_multi, stage_ctl,
657 fwft=True, pipe=True)
658
659 # this is *probably* BufferedHandshake, although test #997 now succeeds.
660 class BufferedHandshake(FIFOControl):
661 def __init__(self, stage, in_multi=None, stage_ctl=False):
662 FIFOControl.__init__(self, 2, stage, in_multi, stage_ctl,
663 fwft=True, pipe=False)
664
665
666 """
667 # this is *probably* SimpleHandshake (note: memory cell size=0)
668 class SimpleHandshake(FIFOControl):
669 def __init__(self, stage, in_multi=None, stage_ctl=False):
670 FIFOControl.__init__(self, 0, stage, in_multi, stage_ctl,
671 fwft=True, pipe=False)
672 """