-""" Pipeline and BufferedHandshake implementation, conforming to the same API.
- For multi-input and multi-output variants, see multipipe.
+""" Pipeline API. For multi-input and multi-output variants, see multipipe.
Associated development bugs:
* http://bugs.libre-riscv.org/show_bug.cgi?id=64
* http://bugs.libre-riscv.org/show_bug.cgi?id=57
- Important: see Stage API (iocontrol.py) in combination with below
+ Important: see Stage API (stageapi.py) in combination with below
RecordBasedStage:
----------------
https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
"""
-from nmigen import Signal, Cat, Const, Mux, Module, Value, Elaboratable
+from nmigen import Signal, Mux, Module, Elaboratable
from nmigen.cli import verilog, rtlil
-from nmigen.lib.fifo import SyncFIFO, SyncFIFOBuffered
-from nmigen.hdl.ast import ArrayProxy
from nmigen.hdl.rec import Record
-from abc import ABCMeta, abstractmethod
-from collections.abc import Sequence, Iterable
-from collections import OrderedDict
from queue import Queue
import inspect
+from iocontrol import (PrevControl, NextControl, Object, RecordObject)
+from stageapi import (_spec, StageCls, Stage, StageChain, StageHelper)
import nmoperator
-from iocontrol import (Object, RecordObject)
-from stageapi import (_spec, PrevControl, NextControl, StageCls, Stage,
- StageChain, StageHelper)
+class RecordBasedStage(Stage):
+ """ convenience class which provides a Records-based layout.
+ honestly it's a lot easier just to create a direct Records-based
+ class (see ExampleAddRecordStage)
+ """
+ def __init__(self, in_shape, out_shape, processfn, setupfn=None):
+ self.in_shape = in_shape
+ self.out_shape = out_shape
+ self.__process = processfn
+ self.__setup = setupfn
+ def ispec(self): return Record(self.in_shape)
+ def ospec(self): return Record(self.out_shape)
+ def process(seif, i): return self.__process(i)
+ def setup(seif, m, i): return self.__setup(m, i)
+
+
+class PassThroughStage(StageCls):
+ """ a pass-through stage with its input data spec identical to its output,
+ and "passes through" its data from input to output (does nothing).
+
+ use this basically to explicitly make any data spec Stage-compliant.
+ (many APIs would potentially use a static "wrap" method in e.g.
+ StageCls to achieve a similar effect)
+ """
+ def __init__(self, iospecfn): self.iospecfn = iospecfn
+ def ispec(self): return self.iospecfn()
+ def ospec(self): return self.iospecfn()
+
class ControlBase(StageHelper, Elaboratable):
""" Common functions for Pipeline API. Note: a "pipeline stage" only
# set up the input and output data
if stage is not None:
- self._new_data(self, self, "data")
+ self._new_data("data")
- def _new_data(self, p, n, name):
+ def _new_data(self, name):
""" allocates new data_i and data_o
"""
- self.p.data_i = _spec(p.stage.ispec, "%s_i" % name)
- self.n.data_o = _spec(n.stage.ospec, "%s_o" % name)
+ self.p.data_i, self.n.data_o = self.new_specs(name)
@property
def data_r(self):
an elaborate() to m.d.comb
"""
assert len(pipechain) > 0, "pipechain must be non-zero length"
+ assert self.stage is None, "do not use connect with a stage"
eqs = [] # collated list of assignment statements
# connect inter-chain
# connect front and back of chain to ourselves
front = pipechain[0] # first in chain
end = pipechain[-1] # last in chain
- self._new_data(front, end, "chain") # NOTE: REPLACES existing data
+ self.set_specs(front, end) # sets up ispec/ospec functions
+ self._new_data("chain") # NOTE: REPLACES existing data
eqs += front._connect_in(self) # front p to our p
- eqs += end._connect_out(self) # end n to out n
+ eqs += end._connect_out(self) # end n to our n
return eqs
return m
-class RecordBasedStage(Stage):
- """ convenience class which provides a Records-based layout.
- honestly it's a lot easier just to create a direct Records-based
- class (see ExampleAddRecordStage)
- """
- def __init__(self, in_shape, out_shape, processfn, setupfn=None):
- self.in_shape = in_shape
- self.out_shape = out_shape
- self.__process = processfn
- self.__setup = setupfn
- def ispec(self): return Record(self.in_shape)
- def ospec(self): return Record(self.out_shape)
- def process(seif, i): return self.__process(i)
- def setup(seif, m, i): return self.__setup(m, i)
-
class BufferedHandshake(ControlBase):
""" buffered pipeline stage. data and strobe signals travel in sync.
return self.m
-class PassThroughStage(StageCls):
- """ a pass-through stage with its input data spec identical to its output,
- and "passes through" its data from input to output (does nothing).
-
- use this basically to explicitly make any data spec Stage-compliant.
- (many APIs would potentially use a static "wrap" method in e.g.
- StageCls to achieve a similar effect)
- """
- def __init__(self, iospecfn): self.iospecfn = iospecfn
- def ispec(self): return self.iospecfn()
- def ospec(self): return self.iospecfn()
-
-
class PassThroughHandshake(ControlBase):
""" A control block that delays by one clock cycle.
class FIFOControl(ControlBase):
- """ FIFO Control. Uses SyncFIFO to store data, coincidentally
+ """ FIFO Control. Uses Queue to store data, coincidentally
happens to have same valid/ready signalling as Stage API.
data_i -> fifo.din -> FIFO -> fifo.dout -> data_o
"""
def __init__(self, depth, stage, in_multi=None, stage_ctl=False,
- fwft=True, buffered=False, pipe=False):
+ fwft=True, pipe=False):
""" FIFO Control
- * :depth: number of entries in the FIFO
- * :stage: data processing block
- * :fwft: first word fall-thru mode (non-fwft introduces delay)
- * :buffered: use buffered FIFO (introduces extra cycle delay)
+ * :depth: number of entries in the FIFO
+ * :stage: data processing block
+ * :fwft: first word fall-thru mode (non-fwft introduces delay)
+ * :pipe: specifies pipe mode.
+
+ when fwft = True it indicates that transfers may occur
+ combinatorially through stage processing in the same clock cycle.
+ This requires that the Stage be a Moore FSM:
+ https://en.wikipedia.org/wiki/Moore_machine
- NOTE 1: FPGAs may have trouble with the defaults for SyncFIFO
- (fwft=True, buffered=False). XXX TODO: fix this by
- using Queue in all cases instead.
+ when fwft = False it indicates that all output signals are
+ produced only from internal registers or memory, i.e. that the
+ Stage is a Mealy FSM:
+ https://en.wikipedia.org/wiki/Mealy_machine
data is processed (and located) as follows:
this is how the FIFO gets de-catted without needing a de-cat
function
"""
-
- assert not (fwft and buffered), "buffered cannot do fwft"
- if buffered:
- depth += 1
self.fwft = fwft
- self.buffered = buffered
self.pipe = pipe
self.fdepth = depth
ControlBase.__init__(self, stage, in_multi, stage_ctl)
# make a FIFO with a signal of equal width to the data_o.
(fwidth, _) = nmoperator.shape(self.n.data_o)
- if self.buffered:
- fifo = SyncFIFOBuffered(fwidth, self.fdepth)
- else:
- fifo = Queue(fwidth, self.fdepth, fwft=self.fwft, pipe=self.pipe)
+ fifo = Queue(fwidth, self.fdepth, fwft=self.fwft, pipe=self.pipe)
m.submodules.fifo = fifo
- # store result of processing in combinatorial temporary
- result = _spec(self.stage.ospec, "r_temp")
- m.d.comb += nmoperator.eq(result, self.data_r)
-
- # connect previous rdy/valid/data - do cat on data_i
- # NOTE: cannot do the PrevControl-looking trick because
- # of need to process the data. shaaaame....
- m.d.comb += [fifo.we.eq(self.p.valid_i_test),
- self.p.ready_o.eq(fifo.writable),
- nmoperator.eq(fifo.din, nmoperator.cat(result)),
- ]
-
- # connect next rdy/valid/data - do cat on data_o (further below)
- connections = [self.n.valid_o.eq(fifo.readable),
- fifo.re.eq(self.n.ready_i_test),
- ]
- if self.fwft or self.buffered:
- m.d.comb += connections # combinatorial on next ready/valid
+ def processfn(data_i):
+ # store result of processing in combinatorial temporary
+ result = _spec(self.stage.ospec, "r_temp")
+ m.d.comb += nmoperator.eq(result, self.process(data_i))
+ return nmoperator.cat(result)
+
+ ## prev: make the FIFO (Queue object) "look" like a PrevControl...
+ m.submodules.fp = fp = PrevControl()
+ fp.valid_i, fp._ready_o, fp.data_i = fifo.we, fifo.writable, fifo.din
+ m.d.comb += fp._connect_in(self.p, fn=processfn)
+
+ # next: make the FIFO (Queue object) "look" like a NextControl...
+ m.submodules.fn = fn = NextControl()
+ fn.valid_o, fn.ready_i, fn.data_o = fifo.readable, fifo.re, fifo.dout
+ connections = fn._connect_out(self.n, fn=nmoperator.cat)
+
+ # ok ok so we can't just do the ready/valid eqs straight:
+ # first 2 from connections are the ready/valid, 3rd is data.
+ if self.fwft:
+ m.d.comb += connections[:2] # combinatorial on next ready/valid
else:
- m.d.sync += connections # unbuffered fwft mode needs sync
- data_o = nmoperator.cat(self.n.data_o).eq(fifo.dout)
+ m.d.sync += connections[:2] # non-fwft mode needs sync
+ data_o = connections[2] # get the data
data_o = self._postprocess(data_o) # XXX TBD, does nothing right now
m.d.comb += data_o