make Stage.process optional
[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.stage.ospec, "r_tmp")
188 r_data = _spec(self.stage.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.stage.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.stage.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.stage.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): self.iospecfn = iospecfn
504 def ispec(self): return self.iospecfn()
505 def ospec(self): return self.iospecfn()
506
507
508 class PassThroughHandshake(ControlBase):
509 """ A control block that delays by one clock cycle.
510
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
515 V R R V V R
516
517 ------- - - - - - -
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)
522 ------- - - - - - -
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)
527 ------- - - - - - -
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)
532 ------- - - - - - -
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)
537 ------- - - - - - -
538
539 """
540
541 def elaborate(self, platform):
542 self.m = m = ControlBase.elaborate(self, platform)
543
544 r_data = _spec(self.stage.ospec, "r_tmp") # output type
545
546 # temporaries
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)
551
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)
554
555 odata = Mux(pvr, self.data_r, 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)
559
560 return m
561
562
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
567 """
568 def __init__(self, iospecfn):
569 UnbufferedPipeline.__init__(self, PassThroughStage(iospecfn))
570
571
572 class FIFOControl(ControlBase):
573 """ FIFO Control. Uses SyncFIFO to store data, coincidentally
574 happens to have same valid/ready signalling as Stage API.
575
576 data_i -> fifo.din -> FIFO -> fifo.dout -> data_o
577 """
578 def __init__(self, depth, stage, in_multi=None, stage_ctl=False,
579 fwft=True, buffered=False, pipe=False):
580 """ FIFO Control
581
582 * :depth: number of entries in the FIFO
583 * :stage: data processing block
584 * :fwft: first word fall-thru mode (non-fwft introduces delay)
585 * :buffered: use buffered FIFO (introduces extra cycle delay)
586
587 NOTE 1: FPGAs may have trouble with the defaults for SyncFIFO
588 (fwft=True, buffered=False). XXX TODO: fix this by
589 using Queue in all cases instead.
590
591 data is processed (and located) as follows:
592
593 self.p self.stage temp fn temp fn temp fp self.n
594 data_i->process()->result->cat->din.FIFO.dout->cat(data_o)
595
596 yes, really: cat produces a Cat() which can be assigned to.
597 this is how the FIFO gets de-catted without needing a de-cat
598 function
599 """
600
601 assert not (fwft and buffered), "buffered cannot do fwft"
602 if buffered:
603 depth += 1
604 self.fwft = fwft
605 self.buffered = buffered
606 self.pipe = pipe
607 self.fdepth = depth
608 ControlBase.__init__(self, stage, in_multi, stage_ctl)
609
610 def elaborate(self, platform):
611 self.m = m = ControlBase.elaborate(self, platform)
612
613 # make a FIFO with a signal of equal width to the data_o.
614 (fwidth, _) = nmoperator.shape(self.n.data_o)
615 if self.buffered:
616 fifo = SyncFIFOBuffered(fwidth, self.fdepth)
617 else:
618 fifo = Queue(fwidth, self.fdepth, fwft=self.fwft, pipe=self.pipe)
619 m.submodules.fifo = fifo
620
621 # store result of processing in combinatorial temporary
622 result = _spec(self.stage.ospec, "r_temp")
623 m.d.comb += nmoperator.eq(result, self.data_r)
624
625 # connect previous rdy/valid/data - do cat on data_i
626 # NOTE: cannot do the PrevControl-looking trick because
627 # of need to process the data. shaaaame....
628 m.d.comb += [fifo.we.eq(self.p.valid_i_test),
629 self.p.ready_o.eq(fifo.writable),
630 nmoperator.eq(fifo.din, nmoperator.cat(result)),
631 ]
632
633 # connect next rdy/valid/data - do cat on data_o (further below)
634 connections = [self.n.valid_o.eq(fifo.readable),
635 fifo.re.eq(self.n.ready_i_test),
636 ]
637 if self.fwft or self.buffered:
638 m.d.comb += connections # combinatorial on next ready/valid
639 else:
640 m.d.sync += connections # unbuffered fwft mode needs sync
641 data_o = nmoperator.cat(self.n.data_o).eq(fifo.dout)
642 data_o = self._postprocess(data_o) # XXX TBD, does nothing right now
643 m.d.comb += data_o
644
645 return m
646
647
648 # aka "RegStage".
649 class UnbufferedPipeline(FIFOControl):
650 def __init__(self, stage, in_multi=None, stage_ctl=False):
651 FIFOControl.__init__(self, 1, stage, in_multi, stage_ctl,
652 fwft=True, pipe=False)
653
654 # aka "BreakReadyStage" XXX had to set fwft=True to get it to work
655 class PassThroughHandshake(FIFOControl):
656 def __init__(self, stage, in_multi=None, stage_ctl=False):
657 FIFOControl.__init__(self, 1, stage, in_multi, stage_ctl,
658 fwft=True, pipe=True)
659
660 # this is *probably* BufferedHandshake, although test #997 now succeeds.
661 class BufferedHandshake(FIFOControl):
662 def __init__(self, stage, in_multi=None, stage_ctl=False):
663 FIFOControl.__init__(self, 2, stage, in_multi, stage_ctl,
664 fwft=True, pipe=False)
665
666
667 """
668 # this is *probably* SimpleHandshake (note: memory cell size=0)
669 class SimpleHandshake(FIFOControl):
670 def __init__(self, stage, in_multi=None, stage_ctl=False):
671 FIFOControl.__init__(self, 0, stage, in_multi, stage_ctl,
672 fwft=True, pipe=False)
673 """