move RecordObject to singlepipe.py for now
[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 eq:
5 --
6
7 a strategically very important function that is identical in function
8 to nmigen's Signal.eq function, except it may take objects, or a list
9 of objects, or a tuple of objects, and where objects may also be
10 Records.
11
12 Stage API:
13 ---------
14
15 stage requires compliance with a strict API that may be
16 implemented in several means, including as a static class.
17 the methods of a stage instance must be as follows:
18
19 * ispec() - Input data format specification
20 returns an object or a list or tuple of objects, or
21 a Record, each object having an "eq" function which
22 takes responsibility for copying by assignment all
23 sub-objects
24 * ospec() - Output data format specification
25 requirements as for ospec
26 * process(m, i) - Processes an ispec-formatted object
27 returns a combinatorial block of a result that
28 may be assigned to the output, by way of the "eq"
29 function
30 * setup(m, i) - Optional function for setting up submodules
31 may be used for more complex stages, to link
32 the input (i) to submodules. must take responsibility
33 for adding those submodules to the module (m).
34 the submodules must be combinatorial blocks and
35 must have their inputs and output linked combinatorially.
36
37 Both StageCls (for use with non-static classes) and Stage (for use
38 by static classes) are abstract classes from which, for convenience
39 and as a courtesy to other developers, anything conforming to the
40 Stage API may *choose* to derive.
41
42 StageChain:
43 ----------
44
45 A useful combinatorial wrapper around stages that chains them together
46 and then presents a Stage-API-conformant interface. By presenting
47 the same API as the stages it wraps, it can clearly be used recursively.
48
49 RecordBasedStage:
50 ----------------
51
52 A convenience class that takes an input shape, output shape, a
53 "processing" function and an optional "setup" function. Honestly
54 though, there's not much more effort to just... create a class
55 that returns a couple of Records (see ExampleAddRecordStage in
56 examples).
57
58 PassThroughStage:
59 ----------------
60
61 A convenience class that takes a single function as a parameter,
62 that is chain-called to create the exact same input and output spec.
63 It has a process() function that simply returns its input.
64
65 Instances of this class are completely redundant if handed to
66 StageChain, however when passed to UnbufferedPipeline they
67 can be used to introduce a single clock delay.
68
69 ControlBase:
70 -----------
71
72 The base class for pipelines. Contains previous and next ready/valid/data.
73 Also has an extremely useful "connect" function that can be used to
74 connect a chain of pipelines and present the exact same prev/next
75 ready/valid/data API.
76
77 UnbufferedPipeline:
78 ------------------
79
80 A simple stalling clock-synchronised pipeline that has no buffering
81 (unlike BufferedHandshake). Data flows on *every* clock cycle when
82 the conditions are right (this is nominally when the input is valid
83 and the output is ready).
84
85 A stall anywhere along the line will result in a stall back-propagating
86 down the entire chain. The BufferedHandshake by contrast will buffer
87 incoming data, allowing previous stages one clock cycle's grace before
88 also having to stall.
89
90 An advantage of the UnbufferedPipeline over the Buffered one is
91 that the amount of logic needed (number of gates) is greatly
92 reduced (no second set of buffers basically)
93
94 The disadvantage of the UnbufferedPipeline is that the valid/ready
95 logic, if chained together, is *combinatorial*, resulting in
96 progressively larger gate delay.
97
98 PassThroughHandshake:
99 ------------------
100
101 A Control class that introduces a single clock delay, passing its
102 data through unaltered. Unlike RegisterPipeline (which relies
103 on UnbufferedPipeline and PassThroughStage) it handles ready/valid
104 itself.
105
106 RegisterPipeline:
107 ----------------
108
109 A convenience class that, because UnbufferedPipeline introduces a single
110 clock delay, when its stage is a PassThroughStage, it results in a Pipeline
111 stage that, duh, delays its (unmodified) input by one clock cycle.
112
113 BufferedHandshake:
114 ----------------
115
116 nmigen implementation of buffered pipeline stage, based on zipcpu:
117 https://zipcpu.com/blog/2017/08/14/strategies-for-pipelining.html
118
119 this module requires quite a bit of thought to understand how it works
120 (and why it is needed in the first place). reading the above is
121 *strongly* recommended.
122
123 unlike john dawson's IEEE754 FPU STB/ACK signalling, which requires
124 the STB / ACK signals to raise and lower (on separate clocks) before
125 data may proceeed (thus only allowing one piece of data to proceed
126 on *ALTERNATE* cycles), the signalling here is a true pipeline
127 where data will flow on *every* clock when the conditions are right.
128
129 input acceptance conditions are when:
130 * incoming previous-stage strobe (p.i_valid) is HIGH
131 * outgoing previous-stage ready (p.o_ready) is LOW
132
133 output transmission conditions are when:
134 * outgoing next-stage strobe (n.o_valid) is HIGH
135 * outgoing next-stage ready (n.i_ready) is LOW
136
137 the tricky bit is when the input has valid data and the output is not
138 ready to accept it. if it wasn't for the clock synchronisation, it
139 would be possible to tell the input "hey don't send that data, we're
140 not ready". unfortunately, it's not possible to "change the past":
141 the previous stage *has no choice* but to pass on its data.
142
143 therefore, the incoming data *must* be accepted - and stored: that
144 is the responsibility / contract that this stage *must* accept.
145 on the same clock, it's possible to tell the input that it must
146 not send any more data. this is the "stall" condition.
147
148 we now effectively have *two* possible pieces of data to "choose" from:
149 the buffered data, and the incoming data. the decision as to which
150 to process and output is based on whether we are in "stall" or not.
151 i.e. when the next stage is no longer ready, the output comes from
152 the buffer if a stall had previously occurred, otherwise it comes
153 direct from processing the input.
154
155 this allows us to respect a synchronous "travelling STB" with what
156 dan calls a "buffered handshake".
157
158 it's quite a complex state machine!
159
160 SimpleHandshake
161 ---------------
162
163 Synchronised pipeline, Based on:
164 https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
165 """
166
167 from nmigen import Signal, Cat, Const, Mux, Module, Value
168 from nmigen.cli import verilog, rtlil
169 from nmigen.lib.fifo import SyncFIFO
170 from nmigen.hdl.ast import ArrayProxy
171 from nmigen.hdl.rec import Record, Layout
172
173 from abc import ABCMeta, abstractmethod
174 from collections.abc import Sequence
175
176
177 class RecordObject(Record):
178 def __init__(self, layout=None, name=None):
179 Record.__init__(self, layout=layout or [], name=None)
180
181 def __setattr__(self, k, v):
182 if k in dir(Record) or "fields" not in self.__dict__:
183 return object.__setattr__(self, k, v)
184 self.__dict__["fields"][k] = v
185 if isinstance(v, Record):
186 newlayout = {k: (k, v.layout)}
187 else:
188 newlayout = {k: (k, v.shape())}
189 self.__dict__["layout"].fields.update(newlayout)
190
191
192
193 class PrevControl:
194 """ contains signals that come *from* the previous stage (both in and out)
195 * i_valid: previous stage indicating all incoming data is valid.
196 may be a multi-bit signal, where all bits are required
197 to be asserted to indicate "valid".
198 * o_ready: output to next stage indicating readiness to accept data
199 * i_data : an input - added by the user of this class
200 """
201
202 def __init__(self, i_width=1, stage_ctl=False):
203 self.stage_ctl = stage_ctl
204 self.i_valid = Signal(i_width, name="p_i_valid") # prev >>in self
205 self._o_ready = Signal(name="p_o_ready") # prev <<out self
206 self.i_data = None # XXX MUST BE ADDED BY USER
207 if stage_ctl:
208 self.s_o_ready = Signal(name="p_s_o_rdy") # prev <<out self
209
210 @property
211 def o_ready(self):
212 """ public-facing API: indicates (externally) that stage is ready
213 """
214 if self.stage_ctl:
215 return self.s_o_ready # set dynamically by stage
216 return self._o_ready # return this when not under dynamic control
217
218 def _connect_in(self, prev, direct=False):
219 """ internal helper function to connect stage to an input source.
220 do not use to connect stage-to-stage!
221 """
222 if direct:
223 i_valid = prev.i_valid
224 else:
225 i_valid = prev.i_valid_test
226 return [self.i_valid.eq(i_valid),
227 prev.o_ready.eq(self.o_ready),
228 eq(self.i_data, prev.i_data),
229 ]
230
231 @property
232 def i_valid_test(self):
233 vlen = len(self.i_valid)
234 if vlen > 1:
235 # multi-bit case: valid only when i_valid is all 1s
236 all1s = Const(-1, (len(self.i_valid), False))
237 i_valid = (self.i_valid == all1s)
238 else:
239 # single-bit i_valid case
240 i_valid = self.i_valid
241
242 # when stage indicates not ready, incoming data
243 # must "appear" to be not ready too
244 if self.stage_ctl:
245 i_valid = i_valid & self.s_o_ready
246
247 return i_valid
248
249
250 class NextControl:
251 """ contains the signals that go *to* the next stage (both in and out)
252 * o_valid: output indicating to next stage that data is valid
253 * i_ready: input from next stage indicating that it can accept data
254 * o_data : an output - added by the user of this class
255 """
256 def __init__(self, stage_ctl=False):
257 self.stage_ctl = stage_ctl
258 self.o_valid = Signal(name="n_o_valid") # self out>> next
259 self.i_ready = Signal(name="n_i_ready") # self <<in next
260 self.o_data = None # XXX MUST BE ADDED BY USER
261 #if self.stage_ctl:
262 self.d_valid = Signal(reset=1) # INTERNAL (data valid)
263
264 @property
265 def i_ready_test(self):
266 if self.stage_ctl:
267 return self.i_ready & self.d_valid
268 return self.i_ready
269
270 def connect_to_next(self, nxt):
271 """ helper function to connect to the next stage data/valid/ready.
272 data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
273 use this when connecting stage-to-stage
274 """
275 return [nxt.i_valid.eq(self.o_valid),
276 self.i_ready.eq(nxt.o_ready),
277 eq(nxt.i_data, self.o_data),
278 ]
279
280 def _connect_out(self, nxt, direct=False):
281 """ internal helper function to connect stage to an output source.
282 do not use to connect stage-to-stage!
283 """
284 if direct:
285 i_ready = nxt.i_ready
286 else:
287 i_ready = nxt.i_ready_test
288 return [nxt.o_valid.eq(self.o_valid),
289 self.i_ready.eq(i_ready),
290 eq(nxt.o_data, self.o_data),
291 ]
292
293
294 class Visitor:
295 """ a helper routine which identifies if it is being passed a list
296 (or tuple) of objects, or signals, or Records, and calls
297 a visitor function.
298
299 the visiting fn is called when an object is identified.
300
301 Record is a special (unusual, recursive) case, where the input may be
302 specified as a dictionary (which may contain further dictionaries,
303 recursively), where the field names of the dictionary must match
304 the Record's field spec. Alternatively, an object with the same
305 member names as the Record may be assigned: it does not have to
306 *be* a Record.
307
308 ArrayProxy is also special-cased, it's a bit messy: whilst ArrayProxy
309 has an eq function, the object being assigned to it (e.g. a python
310 object) might not. despite the *input* having an eq function,
311 that doesn't help us, because it's the *ArrayProxy* that's being
312 assigned to. so.... we cheat. use the ports() function of the
313 python object, enumerate them, find out the list of Signals that way,
314 and assign them.
315 """
316 def visit(self, o, i, act):
317 if isinstance(o, dict):
318 return self.dict_visit(o, i, act)
319
320 res = act.prepare()
321 if not isinstance(o, Sequence):
322 o, i = [o], [i]
323 for (ao, ai) in zip(o, i):
324 #print ("visit", fn, ao, ai)
325 if isinstance(ao, Record):
326 rres = self.record_visit(ao, ai, act)
327 elif isinstance(ao, ArrayProxy) and not isinstance(ai, Value):
328 rres = self.arrayproxy_visit(ao, ai, act)
329 else:
330 rres = act.fn(ao, ai)
331 res += rres
332 return res
333
334 def dict_visit(self, o, i, act):
335 res = act.prepare()
336 for (k, v) in o.items():
337 print ("d-eq", v, i[k])
338 res.append(act.fn(v, i[k]))
339 return res
340
341 def record_visit(self, ao, ai, act):
342 res = act.prepare()
343 for idx, (field_name, field_shape, _) in enumerate(ao.layout):
344 if isinstance(field_shape, Layout):
345 val = ai.fields
346 else:
347 val = ai
348 if hasattr(val, field_name): # check for attribute
349 val = getattr(val, field_name)
350 else:
351 val = val[field_name] # dictionary-style specification
352 val = self.visit(ao.fields[field_name], val, act)
353 if isinstance(val, Sequence):
354 rres += val
355 else:
356 rres.append(val)
357 return res
358
359 def arrayproxy_visit(self, ao, ai, act):
360 res = act.prepare()
361 for p in ai.ports():
362 op = getattr(ao, p.name)
363 #print (op, p, p.name)
364 res.append(fn(op, p))
365 return res
366
367
368 class Eq(Visitor):
369 def __init__(self):
370 self.res = []
371 def prepare(self):
372 return []
373 def fn(self, o, i):
374 rres = o.eq(i)
375 if not isinstance(rres, Sequence):
376 rres = [rres]
377 return rres
378 def __call__(self, o, i):
379 return self.visit(o, i, self)
380
381
382 def eq(o, i):
383 """ makes signals equal: a helper routine which identifies if it is being
384 passed a list (or tuple) of objects, or signals, or Records, and calls
385 the objects' eq function.
386 """
387 return Eq()(o, i)
388
389
390 def flatten(i):
391 """ flattens a compound structure recursively using Cat
392 """
393 if not isinstance(i, Sequence):
394 i = [i]
395 res = []
396 for ai in i:
397 print ("flatten", ai)
398 if isinstance(ai, Record):
399 print ("record", list(ai.layout))
400 rres = []
401 for idx, (field_name, field_shape, _) in enumerate(ai.layout):
402 if isinstance(field_shape, Layout):
403 val = ai.fields
404 else:
405 val = ai
406 if hasattr(val, field_name): # check for attribute
407 val = getattr(val, field_name)
408 else:
409 val = val[field_name] # dictionary-style specification
410 print ("recidx", idx, field_name, field_shape, val)
411 val = flatten(val)
412 print ("recidx flat", idx, val)
413 if isinstance(val, Sequence):
414 rres += val
415 else:
416 rres.append(val)
417
418 elif isinstance(ai, ArrayProxy) and not isinstance(ai, Value):
419 rres = []
420 for p in ai.ports():
421 op = getattr(ai, p.name)
422 #print (op, p, p.name)
423 rres.append(flatten(p))
424 else:
425 rres = ai
426 if not isinstance(rres, Sequence):
427 rres = [rres]
428 res += rres
429 print ("flatten res", res)
430 return Cat(*res)
431
432
433
434 class StageCls(metaclass=ABCMeta):
435 """ Class-based "Stage" API. requires instantiation (after derivation)
436
437 see "Stage API" above.. Note: python does *not* require derivation
438 from this class. All that is required is that the pipelines *have*
439 the functions listed in this class. Derivation from this class
440 is therefore merely a "courtesy" to maintainers.
441 """
442 @abstractmethod
443 def ispec(self): pass # REQUIRED
444 @abstractmethod
445 def ospec(self): pass # REQUIRED
446 #@abstractmethod
447 #def setup(self, m, i): pass # OPTIONAL
448 @abstractmethod
449 def process(self, i): pass # REQUIRED
450
451
452 class Stage(metaclass=ABCMeta):
453 """ Static "Stage" API. does not require instantiation (after derivation)
454
455 see "Stage API" above. Note: python does *not* require derivation
456 from this class. All that is required is that the pipelines *have*
457 the functions listed in this class. Derivation from this class
458 is therefore merely a "courtesy" to maintainers.
459 """
460 @staticmethod
461 @abstractmethod
462 def ispec(): pass
463
464 @staticmethod
465 @abstractmethod
466 def ospec(): pass
467
468 #@staticmethod
469 #@abstractmethod
470 #def setup(m, i): pass
471
472 @staticmethod
473 @abstractmethod
474 def process(i): pass
475
476
477 class RecordBasedStage(Stage):
478 """ convenience class which provides a Records-based layout.
479 honestly it's a lot easier just to create a direct Records-based
480 class (see ExampleAddRecordStage)
481 """
482 def __init__(self, in_shape, out_shape, processfn, setupfn=None):
483 self.in_shape = in_shape
484 self.out_shape = out_shape
485 self.__process = processfn
486 self.__setup = setupfn
487 def ispec(self): return Record(self.in_shape)
488 def ospec(self): return Record(self.out_shape)
489 def process(seif, i): return self.__process(i)
490 def setup(seif, m, i): return self.__setup(m, i)
491
492
493 class StageChain(StageCls):
494 """ pass in a list of stages, and they will automatically be
495 chained together via their input and output specs into a
496 combinatorial chain.
497
498 the end result basically conforms to the exact same Stage API.
499
500 * input to this class will be the input of the first stage
501 * output of first stage goes into input of second
502 * output of second goes into input into third (etc. etc.)
503 * the output of this class will be the output of the last stage
504 """
505 def __init__(self, chain, specallocate=False):
506 self.chain = chain
507 self.specallocate = specallocate
508
509 def ispec(self):
510 return self.chain[0].ispec()
511
512 def ospec(self):
513 return self.chain[-1].ospec()
514
515 def _specallocate_setup(self, m, i):
516 for (idx, c) in enumerate(self.chain):
517 if hasattr(c, "setup"):
518 c.setup(m, i) # stage may have some module stuff
519 o = self.chain[idx].ospec() # last assignment survives
520 m.d.comb += eq(o, c.process(i)) # process input into "o"
521 if idx == len(self.chain)-1:
522 break
523 i = self.chain[idx+1].ispec() # new input on next loop
524 m.d.comb += eq(i, o) # assign to next input
525 return o # last loop is the output
526
527 def _noallocate_setup(self, m, i):
528 for (idx, c) in enumerate(self.chain):
529 if hasattr(c, "setup"):
530 c.setup(m, i) # stage may have some module stuff
531 i = o = c.process(i) # store input into "o"
532 return o # last loop is the output
533
534 def setup(self, m, i):
535 if self.specallocate:
536 self.o = self._specallocate_setup(m, i)
537 else:
538 self.o = self._noallocate_setup(m, i)
539
540 def process(self, i):
541 return self.o # conform to Stage API: return last-loop output
542
543
544 class ControlBase:
545 """ Common functions for Pipeline API
546 """
547 def __init__(self, stage=None, in_multi=None, stage_ctl=False):
548 """ Base class containing ready/valid/data to previous and next stages
549
550 * p: contains ready/valid to the previous stage
551 * n: contains ready/valid to the next stage
552
553 Except when calling Controlbase.connect(), user must also:
554 * add i_data member to PrevControl (p) and
555 * add o_data member to NextControl (n)
556 """
557 self.stage = stage
558
559 # set up input and output IO ACK (prev/next ready/valid)
560 self.p = PrevControl(in_multi, stage_ctl)
561 self.n = NextControl(stage_ctl)
562
563 # set up the input and output data
564 if stage is not None:
565 self.p.i_data = stage.ispec() # input type
566 self.n.o_data = stage.ospec()
567
568 def connect_to_next(self, nxt):
569 """ helper function to connect to the next stage data/valid/ready.
570 """
571 return self.n.connect_to_next(nxt.p)
572
573 def _connect_in(self, prev):
574 """ internal helper function to connect stage to an input source.
575 do not use to connect stage-to-stage!
576 """
577 return self.p._connect_in(prev.p)
578
579 def _connect_out(self, nxt):
580 """ internal helper function to connect stage to an output source.
581 do not use to connect stage-to-stage!
582 """
583 return self.n._connect_out(nxt.n)
584
585 def connect(self, pipechain):
586 """ connects a chain (list) of Pipeline instances together and
587 links them to this ControlBase instance:
588
589 in <----> self <---> out
590 | ^
591 v |
592 [pipe1, pipe2, pipe3, pipe4]
593 | ^ | ^ | ^
594 v | v | v |
595 out---in out--in out---in
596
597 Also takes care of allocating i_data/o_data, by looking up
598 the data spec for each end of the pipechain. i.e It is NOT
599 necessary to allocate self.p.i_data or self.n.o_data manually:
600 this is handled AUTOMATICALLY, here.
601
602 Basically this function is the direct equivalent of StageChain,
603 except that unlike StageChain, the Pipeline logic is followed.
604
605 Just as StageChain presents an object that conforms to the
606 Stage API from a list of objects that also conform to the
607 Stage API, an object that calls this Pipeline connect function
608 has the exact same pipeline API as the list of pipline objects
609 it is called with.
610
611 Thus it becomes possible to build up larger chains recursively.
612 More complex chains (multi-input, multi-output) will have to be
613 done manually.
614 """
615 eqs = [] # collated list of assignment statements
616
617 # connect inter-chain
618 for i in range(len(pipechain)-1):
619 pipe1 = pipechain[i]
620 pipe2 = pipechain[i+1]
621 eqs += pipe1.connect_to_next(pipe2)
622
623 # connect front of chain to ourselves
624 front = pipechain[0]
625 self.p.i_data = front.stage.ispec()
626 eqs += front._connect_in(self)
627
628 # connect end of chain to ourselves
629 end = pipechain[-1]
630 self.n.o_data = end.stage.ospec()
631 eqs += end._connect_out(self)
632
633 return eqs
634
635 def set_input(self, i):
636 """ helper function to set the input data
637 """
638 return eq(self.p.i_data, i)
639
640 def ports(self):
641 res = [self.p.i_valid, self.n.i_ready,
642 self.n.o_valid, self.p.o_ready,
643 ]
644 if hasattr(self.p.i_data, "ports"):
645 res += self.p.i_data.ports()
646 else:
647 res += self.p.i_data
648 if hasattr(self.n.o_data, "ports"):
649 res += self.n.o_data.ports()
650 else:
651 res += self.n.o_data
652 return res
653
654 def _elaborate(self, platform):
655 """ handles case where stage has dynamic ready/valid functions
656 """
657 m = Module()
658
659 if self.stage is not None and hasattr(self.stage, "setup"):
660 self.stage.setup(m, self.p.i_data)
661
662 if not self.p.stage_ctl:
663 return m
664
665 # intercept the previous (outgoing) "ready", combine with stage ready
666 m.d.comb += self.p.s_o_ready.eq(self.p._o_ready & self.stage.d_ready)
667
668 # intercept the next (incoming) "ready" and combine it with data valid
669 sdv = self.stage.d_valid(self.n.i_ready)
670 m.d.comb += self.n.d_valid.eq(self.n.i_ready & sdv)
671
672 return m
673
674
675 class BufferedHandshake(ControlBase):
676 """ buffered pipeline stage. data and strobe signals travel in sync.
677 if ever the input is ready and the output is not, processed data
678 is shunted in a temporary register.
679
680 Argument: stage. see Stage API above
681
682 stage-1 p.i_valid >>in stage n.o_valid out>> stage+1
683 stage-1 p.o_ready <<out stage n.i_ready <<in stage+1
684 stage-1 p.i_data >>in stage n.o_data out>> stage+1
685 | |
686 process --->----^
687 | |
688 +-- r_data ->-+
689
690 input data p.i_data is read (only), is processed and goes into an
691 intermediate result store [process()]. this is updated combinatorially.
692
693 in a non-stall condition, the intermediate result will go into the
694 output (update_output). however if ever there is a stall, it goes
695 into r_data instead [update_buffer()].
696
697 when the non-stall condition is released, r_data is the first
698 to be transferred to the output [flush_buffer()], and the stall
699 condition cleared.
700
701 on the next cycle (as long as stall is not raised again) the
702 input may begin to be processed and transferred directly to output.
703 """
704
705 def elaborate(self, platform):
706 self.m = ControlBase._elaborate(self, platform)
707
708 result = self.stage.ospec()
709 r_data = self.stage.ospec()
710
711 # establish some combinatorial temporaries
712 o_n_validn = Signal(reset_less=True)
713 n_i_ready = Signal(reset_less=True, name="n_i_rdy_data")
714 nir_por = Signal(reset_less=True)
715 nir_por_n = Signal(reset_less=True)
716 p_i_valid = Signal(reset_less=True)
717 nir_novn = Signal(reset_less=True)
718 nirn_novn = Signal(reset_less=True)
719 por_pivn = Signal(reset_less=True)
720 npnn = Signal(reset_less=True)
721 self.m.d.comb += [p_i_valid.eq(self.p.i_valid_test),
722 o_n_validn.eq(~self.n.o_valid),
723 n_i_ready.eq(self.n.i_ready_test),
724 nir_por.eq(n_i_ready & self.p._o_ready),
725 nir_por_n.eq(n_i_ready & ~self.p._o_ready),
726 nir_novn.eq(n_i_ready | o_n_validn),
727 nirn_novn.eq(~n_i_ready & o_n_validn),
728 npnn.eq(nir_por | nirn_novn),
729 por_pivn.eq(self.p._o_ready & ~p_i_valid)
730 ]
731
732 # store result of processing in combinatorial temporary
733 self.m.d.comb += eq(result, self.stage.process(self.p.i_data))
734
735 # if not in stall condition, update the temporary register
736 with self.m.If(self.p.o_ready): # not stalled
737 self.m.d.sync += eq(r_data, result) # update buffer
738
739 # data pass-through conditions
740 with self.m.If(npnn):
741 self.m.d.sync += [self.n.o_valid.eq(p_i_valid), # valid if p_valid
742 eq(self.n.o_data, result), # update output
743 ]
744 # buffer flush conditions (NOTE: can override data passthru conditions)
745 with self.m.If(nir_por_n): # not stalled
746 # Flush the [already processed] buffer to the output port.
747 self.m.d.sync += [self.n.o_valid.eq(1), # reg empty
748 eq(self.n.o_data, r_data), # flush buffer
749 ]
750 # output ready conditions
751 self.m.d.sync += self.p._o_ready.eq(nir_novn | por_pivn)
752
753 return self.m
754
755
756 class SimpleHandshake(ControlBase):
757 """ simple handshake control. data and strobe signals travel in sync.
758 implements the protocol used by Wishbone and AXI4.
759
760 Argument: stage. see Stage API above
761
762 stage-1 p.i_valid >>in stage n.o_valid out>> stage+1
763 stage-1 p.o_ready <<out stage n.i_ready <<in stage+1
764 stage-1 p.i_data >>in stage n.o_data out>> stage+1
765 | |
766 +--process->--^
767 Truth Table
768
769 Inputs Temporary Output
770 ------- ---------- -----
771 P P N N PiV& ~NiV& N P
772 i o i o PoR NoV o o
773 V R R V V R
774
775 ------- - - - -
776 0 0 0 0 0 0 >0 0
777 0 0 0 1 0 1 >1 0
778 0 0 1 0 0 0 0 1
779 0 0 1 1 0 0 0 1
780 ------- - - - -
781 0 1 0 0 0 0 >0 0
782 0 1 0 1 0 1 >1 0
783 0 1 1 0 0 0 0 1
784 0 1 1 1 0 0 0 1
785 ------- - - - -
786 1 0 0 0 0 0 >0 0
787 1 0 0 1 0 1 >1 0
788 1 0 1 0 0 0 0 1
789 1 0 1 1 0 0 0 1
790 ------- - - - -
791 1 1 0 0 1 0 1 0
792 1 1 0 1 1 1 1 0
793 1 1 1 0 1 0 1 1
794 1 1 1 1 1 0 1 1
795 ------- - - - -
796 """
797
798 def elaborate(self, platform):
799 self.m = m = ControlBase._elaborate(self, platform)
800
801 r_busy = Signal()
802 result = self.stage.ospec()
803
804 # establish some combinatorial temporaries
805 n_i_ready = Signal(reset_less=True, name="n_i_rdy_data")
806 p_i_valid_p_o_ready = Signal(reset_less=True)
807 p_i_valid = Signal(reset_less=True)
808 m.d.comb += [p_i_valid.eq(self.p.i_valid_test),
809 n_i_ready.eq(self.n.i_ready_test),
810 p_i_valid_p_o_ready.eq(p_i_valid & self.p.o_ready),
811 ]
812
813 # store result of processing in combinatorial temporary
814 m.d.comb += eq(result, self.stage.process(self.p.i_data))
815
816 # previous valid and ready
817 with m.If(p_i_valid_p_o_ready):
818 m.d.sync += [r_busy.eq(1), # output valid
819 eq(self.n.o_data, result), # update output
820 ]
821 # previous invalid or not ready, however next is accepting
822 with m.Elif(n_i_ready):
823 m.d.sync += [eq(self.n.o_data, result)]
824 # TODO: could still send data here (if there was any)
825 #m.d.sync += self.n.o_valid.eq(0) # ...so set output invalid
826 m.d.sync += r_busy.eq(0) # ...so set output invalid
827
828 m.d.comb += self.n.o_valid.eq(r_busy)
829 # if next is ready, so is previous
830 m.d.comb += self.p._o_ready.eq(n_i_ready)
831
832 return self.m
833
834
835 class UnbufferedPipeline(ControlBase):
836 """ A simple pipeline stage with single-clock synchronisation
837 and two-way valid/ready synchronised signalling.
838
839 Note that a stall in one stage will result in the entire pipeline
840 chain stalling.
841
842 Also that unlike BufferedHandshake, the valid/ready signalling does NOT
843 travel synchronously with the data: the valid/ready signalling
844 combines in a *combinatorial* fashion. Therefore, a long pipeline
845 chain will lengthen propagation delays.
846
847 Argument: stage. see Stage API, above
848
849 stage-1 p.i_valid >>in stage n.o_valid out>> stage+1
850 stage-1 p.o_ready <<out stage n.i_ready <<in stage+1
851 stage-1 p.i_data >>in stage n.o_data out>> stage+1
852 | |
853 r_data result
854 | |
855 +--process ->-+
856
857 Attributes:
858 -----------
859 p.i_data : StageInput, shaped according to ispec
860 The pipeline input
861 p.o_data : StageOutput, shaped according to ospec
862 The pipeline output
863 r_data : input_shape according to ispec
864 A temporary (buffered) copy of a prior (valid) input.
865 This is HELD if the output is not ready. It is updated
866 SYNCHRONOUSLY.
867 result: output_shape according to ospec
868 The output of the combinatorial logic. it is updated
869 COMBINATORIALLY (no clock dependence).
870
871 Truth Table
872
873 Inputs Temp Output
874 ------- - -----
875 P P N N ~NiR& N P
876 i o i o NoV o o
877 V R R V V R
878
879 ------- - - -
880 0 0 0 0 0 0 1
881 0 0 0 1 1 1 0
882 0 0 1 0 0 0 1
883 0 0 1 1 0 0 1
884 ------- - - -
885 0 1 0 0 0 0 1
886 0 1 0 1 1 1 0
887 0 1 1 0 0 0 1
888 0 1 1 1 0 0 1
889 ------- - - -
890 1 0 0 0 0 1 1
891 1 0 0 1 1 1 0
892 1 0 1 0 0 1 1
893 1 0 1 1 0 1 1
894 ------- - - -
895 1 1 0 0 0 1 1
896 1 1 0 1 1 1 0
897 1 1 1 0 0 1 1
898 1 1 1 1 0 1 1
899 ------- - - -
900
901 Note: PoR is *NOT* involved in the above decision-making.
902 """
903
904 def elaborate(self, platform):
905 self.m = m = ControlBase._elaborate(self, platform)
906
907 data_valid = Signal() # is data valid or not
908 r_data = self.stage.ospec() # output type
909
910 # some temporaries
911 p_i_valid = Signal(reset_less=True)
912 pv = Signal(reset_less=True)
913 m.d.comb += p_i_valid.eq(self.p.i_valid_test)
914 m.d.comb += pv.eq(self.p.i_valid & self.p.o_ready)
915
916 m.d.comb += self.n.o_valid.eq(data_valid)
917 m.d.comb += self.p._o_ready.eq(~data_valid | self.n.i_ready_test)
918 m.d.sync += data_valid.eq(p_i_valid | \
919 (~self.n.i_ready_test & data_valid))
920 with m.If(pv):
921 m.d.sync += eq(r_data, self.stage.process(self.p.i_data))
922 m.d.comb += eq(self.n.o_data, r_data)
923
924 return self.m
925
926
927 class UnbufferedPipeline2(ControlBase):
928 """ A simple pipeline stage with single-clock synchronisation
929 and two-way valid/ready synchronised signalling.
930
931 Note that a stall in one stage will result in the entire pipeline
932 chain stalling.
933
934 Also that unlike BufferedHandshake, the valid/ready signalling does NOT
935 travel synchronously with the data: the valid/ready signalling
936 combines in a *combinatorial* fashion. Therefore, a long pipeline
937 chain will lengthen propagation delays.
938
939 Argument: stage. see Stage API, above
940
941 stage-1 p.i_valid >>in stage n.o_valid out>> stage+1
942 stage-1 p.o_ready <<out stage n.i_ready <<in stage+1
943 stage-1 p.i_data >>in stage n.o_data out>> stage+1
944 | | |
945 +- process-> buf <-+
946 Attributes:
947 -----------
948 p.i_data : StageInput, shaped according to ispec
949 The pipeline input
950 p.o_data : StageOutput, shaped according to ospec
951 The pipeline output
952 buf : output_shape according to ospec
953 A temporary (buffered) copy of a valid output
954 This is HELD if the output is not ready. It is updated
955 SYNCHRONOUSLY.
956 """
957
958 def elaborate(self, platform):
959 self.m = m = ControlBase._elaborate(self, platform)
960
961 buf_full = Signal() # is data valid or not
962 buf = self.stage.ospec() # output type
963
964 # some temporaries
965 p_i_valid = Signal(reset_less=True)
966 m.d.comb += p_i_valid.eq(self.p.i_valid_test)
967
968 m.d.comb += self.n.o_valid.eq(buf_full | p_i_valid)
969 m.d.comb += self.p._o_ready.eq(~buf_full)
970 m.d.sync += buf_full.eq(~self.n.i_ready_test & self.n.o_valid)
971
972 odata = Mux(buf_full, buf, self.stage.process(self.p.i_data))
973 m.d.comb += eq(self.n.o_data, odata)
974 m.d.sync += eq(buf, self.n.o_data)
975
976 return self.m
977
978
979 class PassThroughStage(StageCls):
980 """ a pass-through stage which has its input data spec equal to its output,
981 and "passes through" its data from input to output.
982 """
983 def __init__(self, iospecfn):
984 self.iospecfn = iospecfn
985 def ispec(self): return self.iospecfn()
986 def ospec(self): return self.iospecfn()
987 def process(self, i): return i
988
989
990 class PassThroughHandshake(ControlBase):
991 """ A control block that delays by one clock cycle.
992 """
993
994 def elaborate(self, platform):
995 self.m = m = ControlBase._elaborate(self, platform)
996
997 # temporaries
998 p_i_valid = Signal(reset_less=True)
999 pvr = Signal(reset_less=True)
1000 m.d.comb += p_i_valid.eq(self.p.i_valid_test)
1001 m.d.comb += pvr.eq(p_i_valid & self.p.o_ready)
1002
1003 m.d.comb += self.p.o_ready.eq(~self.n.o_valid | self.n.i_ready_test)
1004 m.d.sync += self.n.o_valid.eq(p_i_valid | ~self.p.o_ready)
1005
1006 odata = Mux(pvr, self.stage.process(self.p.i_data), self.n.o_data)
1007 m.d.sync += eq(self.n.o_data, odata)
1008
1009 return m
1010
1011
1012 class RegisterPipeline(UnbufferedPipeline):
1013 """ A pipeline stage that delays by one clock cycle, creating a
1014 sync'd latch out of o_data and o_valid as an indirect byproduct
1015 of using PassThroughStage
1016 """
1017 def __init__(self, iospecfn):
1018 UnbufferedPipeline.__init__(self, PassThroughStage(iospecfn))
1019
1020
1021 class FIFOtest(ControlBase):
1022 """ A test of using a SyncFIFO to see if it will work.
1023 Note: the only things it will accept is a Signal of width "width".
1024 """
1025
1026 def __init__(self, width, depth):
1027
1028 self.fwidth = width
1029 self.fdepth = depth
1030 def iospecfn():
1031 return Signal(width, name="data")
1032 stage = PassThroughStage(iospecfn)
1033 ControlBase.__init__(self, stage=stage)
1034
1035 def elaborate(self, platform):
1036 self.m = m = ControlBase._elaborate(self, platform)
1037
1038 fifo = SyncFIFO(self.fwidth, self.fdepth)
1039 m.submodules.fifo = fifo
1040
1041 # prev: make the FIFO "look" like a PrevControl...
1042 fp = PrevControl()
1043 fp.i_valid = fifo.we
1044 fp._o_ready = fifo.writable
1045 fp.i_data = fifo.din
1046 # ... so we can do this!
1047 m.d.comb += fp._connect_in(self.p, True)
1048
1049 # next: make the FIFO "look" like a NextControl...
1050 fn = NextControl()
1051 fn.o_valid = fifo.readable
1052 fn.i_ready = fifo.re
1053 fn.o_data = fifo.dout
1054 # ... so we can do this!
1055 m.d.comb += fn._connect_out(self.n)
1056
1057 # err... that should be all!
1058 return m
1059