move wrapping of stage into StageHandler
[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 Important: see Stage API (iocontrol.py) in combination with below
9
10 RecordBasedStage:
11 ----------------
12
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
17 examples).
18
19 PassThroughStage:
20 ----------------
21
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.
25
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.
29
30 UnbufferedPipeline:
31 ------------------
32
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).
37
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
41 also having to stall.
42
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)
46
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.
50
51 PassThroughHandshake:
52 ------------------
53
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
57 itself.
58
59 RegisterPipeline:
60 ----------------
61
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.
65
66 BufferedHandshake:
67 ----------------
68
69 nmigen implementation of buffered pipeline stage, based on zipcpu:
70 https://zipcpu.com/blog/2017/08/14/strategies-for-pipelining.html
71
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.
75
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.
81
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
85
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
89
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.
95
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.
100
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.
107
108 this allows us to respect a synchronous "travelling STB" with what
109 dan calls a "buffered handshake".
110
111 it's quite a complex state machine!
112
113 SimpleHandshake
114 ---------------
115
116 Synchronised pipeline, Based on:
117 https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
118 """
119
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
125
126 from abc import ABCMeta, abstractmethod
127 from collections.abc import Sequence, Iterable
128 from collections import OrderedDict
129 from queue import Queue
130 import inspect
131
132 import nmoperator
133 from iocontrol import (Object, RecordObject, _spec,
134 PrevControl, NextControl, StageCls, Stage,
135 ControlBase, StageChain)
136
137
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)
142 """
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)
152
153
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.
158
159 Argument: stage. see Stage API above
160
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
164 | |
165 process --->----^
166 | |
167 +-- r_data ->-+
168
169 input data p.data_i is read (only), is processed and goes into an
170 intermediate result store [process()]. this is updated combinatorially.
171
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()].
175
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
178 condition cleared.
179
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.
182 """
183
184 def elaborate(self, platform):
185 self.m = ControlBase.elaborate(self, platform)
186
187 result = _spec(self.sh.ospec, "r_tmp")
188 r_data = _spec(self.sh.ospec, "r_data")
189
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)
209 ]
210
211 # store result of processing in combinatorial temporary
212 self.m.d.comb += nmoperator.eq(result, self.data_r)
213
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
217
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
223 ]
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
230 ]
231 # output ready conditions
232 self.m.d.sync += self.p._ready_o.eq(nir_novn | por_pivn)
233
234 return self.m
235
236
237 class SimpleHandshake(ControlBase):
238 """ simple handshake control. data and strobe signals travel in sync.
239 implements the protocol used by Wishbone and AXI4.
240
241 Argument: stage. see Stage API above
242
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
246 | |
247 +--process->--^
248 Truth Table
249
250 Inputs Temporary Output Data
251 ------- ---------- ----- ----
252 P P N N PiV& ~NiR& N P
253 i o i o PoR NoV o o
254 V R R V V R
255
256 ------- - - - -
257 0 0 0 0 0 0 >0 0 reg
258 0 0 0 1 0 1 >1 0 reg
259 0 0 1 0 0 0 0 1 process(data_i)
260 0 0 1 1 0 0 0 1 process(data_i)
261 ------- - - - -
262 0 1 0 0 0 0 >0 0 reg
263 0 1 0 1 0 1 >1 0 reg
264 0 1 1 0 0 0 0 1 process(data_i)
265 0 1 1 1 0 0 0 1 process(data_i)
266 ------- - - - -
267 1 0 0 0 0 0 >0 0 reg
268 1 0 0 1 0 1 >1 0 reg
269 1 0 1 0 0 0 0 1 process(data_i)
270 1 0 1 1 0 0 0 1 process(data_i)
271 ------- - - - -
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)
276 ------- - - - -
277 """
278
279 def elaborate(self, platform):
280 self.m = m = ControlBase.elaborate(self, platform)
281
282 r_busy = Signal()
283 result = _spec(self.sh.ospec, "r_tmp")
284
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),
292 ]
293
294 # store result of processing in combinatorial temporary
295 m.d.comb += nmoperator.eq(result, self.data_r)
296
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
302 ]
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
310
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)
314
315 return self.m
316
317
318 class UnbufferedPipeline(ControlBase):
319 """ A simple pipeline stage with single-clock synchronisation
320 and two-way valid/ready synchronised signalling.
321
322 Note that a stall in one stage will result in the entire pipeline
323 chain stalling.
324
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.
329
330 Argument: stage. see Stage API, above
331
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
335 | |
336 r_data result
337 | |
338 +--process ->-+
339
340 Attributes:
341 -----------
342 p.data_i : StageInput, shaped according to ispec
343 The pipeline input
344 p.data_o : StageOutput, shaped according to ospec
345 The pipeline output
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
349 SYNCHRONOUSLY.
350 result: output_shape according to ospec
351 The output of the combinatorial logic. it is updated
352 COMBINATORIALLY (no clock dependence).
353
354 Truth Table
355
356 Inputs Temp Output Data
357 ------- - ----- ----
358 P P N N ~NiR& N P
359 i o i o NoV o o
360 V R R V V R
361
362 ------- - - -
363 0 0 0 0 0 0 1 reg
364 0 0 0 1 1 1 0 reg
365 0 0 1 0 0 0 1 reg
366 0 0 1 1 0 0 1 reg
367 ------- - - -
368 0 1 0 0 0 0 1 reg
369 0 1 0 1 1 1 0 reg
370 0 1 1 0 0 0 1 reg
371 0 1 1 1 0 0 1 reg
372 ------- - - -
373 1 0 0 0 0 1 1 reg
374 1 0 0 1 1 1 0 reg
375 1 0 1 0 0 1 1 reg
376 1 0 1 1 0 1 1 reg
377 ------- - - -
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)
382 ------- - - -
383
384 Note: PoR is *NOT* involved in the above decision-making.
385 """
386
387 def elaborate(self, platform):
388 self.m = m = ControlBase.elaborate(self, platform)
389
390 data_valid = Signal() # is data valid or not
391 r_data = _spec(self.sh.ospec, "r_tmp") # output type
392
393 # some temporaries
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)
400
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)
404
405 with m.If(pv):
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)
409
410 return self.m
411
412 class UnbufferedPipeline2(ControlBase):
413 """ A simple pipeline stage with single-clock synchronisation
414 and two-way valid/ready synchronised signalling.
415
416 Note that a stall in one stage will result in the entire pipeline
417 chain stalling.
418
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.
423
424 Argument: stage. see Stage API, above
425
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
429 | | |
430 +- process-> buf <-+
431 Attributes:
432 -----------
433 p.data_i : StageInput, shaped according to ispec
434 The pipeline input
435 p.data_o : StageOutput, shaped according to ospec
436 The pipeline output
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
440 SYNCHRONOUSLY.
441
442 Inputs Temp Output Data
443 ------- - -----
444 P P N N ~NiR& N P (buf_full)
445 i o i o NoV o o
446 V R R V V R
447
448 ------- - - -
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)
453 ------- - - -
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)
458 ------- - - -
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)
463 ------- - - -
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)
468 ------- - - -
469
470 Note: PoR is *NOT* involved in the above decision-making.
471 """
472
473 def elaborate(self, platform):
474 self.m = m = ControlBase.elaborate(self, platform)
475
476 buf_full = Signal() # is data valid or not
477 buf = _spec(self.sh.ospec, "r_tmp") # output type
478
479 # some temporaries
480 p_valid_i = Signal(reset_less=True)
481 m.d.comb += p_valid_i.eq(self.p.valid_i_test)
482
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)
486
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)
491
492 return self.m
493
494
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).
498
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)
502 """
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
508
509
510 class PassThroughHandshake(ControlBase):
511 """ A control block that delays by one clock cycle.
512
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
517 V R R V V R
518
519 ------- - - - - - -
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)
524 ------- - - - - - -
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)
529 ------- - - - - - -
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)
534 ------- - - - - - -
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)
539 ------- - - - - - -
540
541 """
542
543 def elaborate(self, platform):
544 self.m = m = ControlBase.elaborate(self, platform)
545
546 r_data = _spec(self.sh.ospec, "r_tmp") # output type
547
548 # temporaries
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)
553
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)
556
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)
561
562 return m
563
564
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
569 """
570 def __init__(self, iospecfn):
571 UnbufferedPipeline.__init__(self, PassThroughStage(iospecfn))
572
573
574 class FIFOControl(ControlBase):
575 """ FIFO Control. Uses SyncFIFO to store data, coincidentally
576 happens to have same valid/ready signalling as Stage API.
577
578 data_i -> fifo.din -> FIFO -> fifo.dout -> data_o
579 """
580 def __init__(self, depth, stage, in_multi=None, stage_ctl=False,
581 fwft=True, buffered=False, pipe=False):
582 """ FIFO Control
583
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)
588
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.
592
593 data is processed (and located) as follows:
594
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)
597
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
600 function
601 """
602
603 assert not (fwft and buffered), "buffered cannot do fwft"
604 if buffered:
605 depth += 1
606 self.fwft = fwft
607 self.buffered = buffered
608 self.pipe = pipe
609 self.fdepth = depth
610 ControlBase.__init__(self, stage, in_multi, stage_ctl)
611
612 def elaborate(self, platform):
613 self.m = m = ControlBase.elaborate(self, platform)
614
615 # make a FIFO with a signal of equal width to the data_o.
616 (fwidth, _) = nmoperator.shape(self.n.data_o)
617 if self.buffered:
618 fifo = SyncFIFOBuffered(fwidth, self.fdepth)
619 else:
620 fifo = Queue(fwidth, self.fdepth, fwft=self.fwft, pipe=self.pipe)
621 m.submodules.fifo = fifo
622
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)
626
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)),
633 ]
634
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),
638 ]
639 if self.fwft or self.buffered:
640 m.d.comb += connections # combinatorial on next ready/valid
641 else:
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
645 m.d.comb += data_o
646
647 return m
648
649
650 # aka "RegStage".
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)
655
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)
661
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)
667
668
669 """
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)
675 """