--- /dev/null
+""" IO Control API
+
+ Associated development bugs:
+ * http://bugs.libre-riscv.org/show_bug.cgi?id=64
+ * http://bugs.libre-riscv.org/show_bug.cgi?id=57
+
+ Stage API:
+ ---------
+
+ stage requires compliance with a strict API that may be
+ implemented in several means, including as a static class.
+ the methods of a stage instance must be as follows:
+
+ * ispec() - Input data format specification
+ returns an object or a list or tuple of objects, or
+ a Record, each object having an "eq" function which
+ takes responsibility for copying by assignment all
+ sub-objects
+ * ospec() - Output data format specification
+ requirements as for ospec
+ * process(m, i) - Processes an ispec-formatted object
+ returns a combinatorial block of a result that
+ may be assigned to the output, by way of the "eq"
+ function
+ * setup(m, i) - Optional function for setting up submodules
+ may be used for more complex stages, to link
+ the input (i) to submodules. must take responsibility
+ for adding those submodules to the module (m).
+ the submodules must be combinatorial blocks and
+ must have their inputs and output linked combinatorially.
+
+ Both StageCls (for use with non-static classes) and Stage (for use
+ by static classes) are abstract classes from which, for convenience
+ and as a courtesy to other developers, anything conforming to the
+ Stage API may *choose* to derive.
+
+ StageChain:
+ ----------
+
+ A useful combinatorial wrapper around stages that chains them together
+ and then presents a Stage-API-conformant interface. By presenting
+ the same API as the stages it wraps, it can clearly be used recursively.
+
+ ControlBase:
+ -----------
+
+ The base class for pipelines. Contains previous and next ready/valid/data.
+ Also has an extremely useful "connect" function that can be used to
+ connect a chain of pipelines and present the exact same prev/next
+ ready/valid/data API.
+"""
+
+from nmigen import Signal, Cat, Const, Mux, Module, Value, Elaboratable
+from nmigen.cli import verilog, rtlil
+from nmigen.hdl.rec import Record
+
+from abc import ABCMeta, abstractmethod
+from collections.abc import Sequence, Iterable
+from collections import OrderedDict
+import inspect
+
+from nmoperator import eq, cat, shape
+
+
+class Object:
+ def __init__(self):
+ self.fields = OrderedDict()
+
+ def __setattr__(self, k, v):
+ print ("kv", k, v)
+ if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
+ k in dir(Object) or "fields" not in self.__dict__):
+ return object.__setattr__(self, k, v)
+ self.fields[k] = v
+
+ def __getattr__(self, k):
+ if k in self.__dict__:
+ return object.__getattr__(self, k)
+ try:
+ return self.fields[k]
+ except KeyError as e:
+ raise AttributeError(e)
+
+ def __iter__(self):
+ for x in self.fields.values():
+ if isinstance(x, Iterable):
+ yield from x
+ else:
+ yield x
+
+ def eq(self, inp):
+ res = []
+ for (k, o) in self.fields.items():
+ i = getattr(inp, k)
+ print ("eq", o, i)
+ rres = o.eq(i)
+ if isinstance(rres, Sequence):
+ res += rres
+ else:
+ res.append(rres)
+ print (res)
+ return res
+
+ def ports(self):
+ return list(self)
+
+
+class RecordObject(Record):
+ def __init__(self, layout=None, name=None):
+ Record.__init__(self, layout=layout or [], name=None)
+
+ def __setattr__(self, k, v):
+ #print (dir(Record))
+ if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
+ k in dir(Record) or "fields" not in self.__dict__):
+ return object.__setattr__(self, k, v)
+ self.fields[k] = v
+ #print ("RecordObject setattr", k, v)
+ if isinstance(v, Record):
+ newlayout = {k: (k, v.layout)}
+ elif isinstance(v, Value):
+ newlayout = {k: (k, v.shape())}
+ else:
+ newlayout = {k: (k, shape(v))}
+ self.layout.fields.update(newlayout)
+
+ def __iter__(self):
+ for x in self.fields.values():
+ if isinstance(x, Iterable):
+ yield from x
+ else:
+ yield x
+
+ def ports(self):
+ return list(self)
+
+
+def _spec(fn, name=None):
+ if name is None:
+ return fn()
+ varnames = dict(inspect.getmembers(fn.__code__))['co_varnames']
+ if 'name' in varnames:
+ return fn(name=name)
+ return fn()
+
+
+class PrevControl(Elaboratable):
+ """ contains signals that come *from* the previous stage (both in and out)
+ * valid_i: previous stage indicating all incoming data is valid.
+ may be a multi-bit signal, where all bits are required
+ to be asserted to indicate "valid".
+ * ready_o: output to next stage indicating readiness to accept data
+ * data_i : an input - added by the user of this class
+ """
+
+ def __init__(self, i_width=1, stage_ctl=False):
+ self.stage_ctl = stage_ctl
+ self.valid_i = Signal(i_width, name="p_valid_i") # prev >>in self
+ self._ready_o = Signal(name="p_ready_o") # prev <<out self
+ self.data_i = None # XXX MUST BE ADDED BY USER
+ if stage_ctl:
+ self.s_ready_o = Signal(name="p_s_o_rdy") # prev <<out self
+ self.trigger = Signal(reset_less=True)
+
+ @property
+ def ready_o(self):
+ """ public-facing API: indicates (externally) that stage is ready
+ """
+ if self.stage_ctl:
+ return self.s_ready_o # set dynamically by stage
+ return self._ready_o # return this when not under dynamic control
+
+ def _connect_in(self, prev, direct=False, fn=None):
+ """ internal helper function to connect stage to an input source.
+ do not use to connect stage-to-stage!
+ """
+ valid_i = prev.valid_i if direct else prev.valid_i_test
+ data_i = fn(prev.data_i) if fn is not None else prev.data_i
+ return [self.valid_i.eq(valid_i),
+ prev.ready_o.eq(self.ready_o),
+ eq(self.data_i, data_i),
+ ]
+
+ @property
+ def valid_i_test(self):
+ vlen = len(self.valid_i)
+ if vlen > 1:
+ # multi-bit case: valid only when valid_i is all 1s
+ all1s = Const(-1, (len(self.valid_i), False))
+ valid_i = (self.valid_i == all1s)
+ else:
+ # single-bit valid_i case
+ valid_i = self.valid_i
+
+ # when stage indicates not ready, incoming data
+ # must "appear" to be not ready too
+ if self.stage_ctl:
+ valid_i = valid_i & self.s_ready_o
+
+ return valid_i
+
+ def elaborate(self, platform):
+ m = Module()
+ m.d.comb += self.trigger.eq(self.valid_i_test & self.ready_o)
+ return m
+
+ def eq(self, i):
+ return [self.data_i.eq(i.data_i),
+ self.ready_o.eq(i.ready_o),
+ self.valid_i.eq(i.valid_i)]
+
+ def __iter__(self):
+ yield self.valid_i
+ yield self.ready_o
+ if hasattr(self.data_i, "ports"):
+ yield from self.data_i.ports()
+ elif isinstance(self.data_i, Sequence):
+ yield from self.data_i
+ else:
+ yield self.data_i
+
+ def ports(self):
+ return list(self)
+
+
+class NextControl(Elaboratable):
+ """ contains the signals that go *to* the next stage (both in and out)
+ * valid_o: output indicating to next stage that data is valid
+ * ready_i: input from next stage indicating that it can accept data
+ * data_o : an output - added by the user of this class
+ """
+ def __init__(self, stage_ctl=False):
+ self.stage_ctl = stage_ctl
+ self.valid_o = Signal(name="n_valid_o") # self out>> next
+ self.ready_i = Signal(name="n_ready_i") # self <<in next
+ self.data_o = None # XXX MUST BE ADDED BY USER
+ #if self.stage_ctl:
+ self.d_valid = Signal(reset=1) # INTERNAL (data valid)
+ self.trigger = Signal(reset_less=True)
+
+ @property
+ def ready_i_test(self):
+ if self.stage_ctl:
+ return self.ready_i & self.d_valid
+ return self.ready_i
+
+ def connect_to_next(self, nxt):
+ """ helper function to connect to the next stage data/valid/ready.
+ data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
+ use this when connecting stage-to-stage
+ """
+ return [nxt.valid_i.eq(self.valid_o),
+ self.ready_i.eq(nxt.ready_o),
+ eq(nxt.data_i, self.data_o),
+ ]
+
+ def _connect_out(self, nxt, direct=False, fn=None):
+ """ internal helper function to connect stage to an output source.
+ do not use to connect stage-to-stage!
+ """
+ ready_i = nxt.ready_i if direct else nxt.ready_i_test
+ data_o = fn(nxt.data_o) if fn is not None else nxt.data_o
+ return [nxt.valid_o.eq(self.valid_o),
+ self.ready_i.eq(ready_i),
+ eq(data_o, self.data_o),
+ ]
+
+ def elaborate(self, platform):
+ m = Module()
+ m.d.comb += self.trigger.eq(self.ready_i_test & self.valid_o)
+ return m
+
+ def __iter__(self):
+ yield self.ready_i
+ yield self.valid_o
+ if hasattr(self.data_o, "ports"):
+ yield from self.data_o.ports()
+ elif isinstance(self.data_o, Sequence):
+ yield from self.data_o
+ else:
+ yield self.data_o
+
+ def ports(self):
+ return list(self)
+
+
+class StageCls(metaclass=ABCMeta):
+ """ Class-based "Stage" API. requires instantiation (after derivation)
+
+ see "Stage API" above.. Note: python does *not* require derivation
+ from this class. All that is required is that the pipelines *have*
+ the functions listed in this class. Derivation from this class
+ is therefore merely a "courtesy" to maintainers.
+ """
+ @abstractmethod
+ def ispec(self): pass # REQUIRED
+ @abstractmethod
+ def ospec(self): pass # REQUIRED
+ #@abstractmethod
+ #def setup(self, m, i): pass # OPTIONAL
+ @abstractmethod
+ def process(self, i): pass # REQUIRED
+
+
+class Stage(metaclass=ABCMeta):
+ """ Static "Stage" API. does not require instantiation (after derivation)
+
+ see "Stage API" above. Note: python does *not* require derivation
+ from this class. All that is required is that the pipelines *have*
+ the functions listed in this class. Derivation from this class
+ is therefore merely a "courtesy" to maintainers.
+ """
+ @staticmethod
+ @abstractmethod
+ def ispec(): pass
+
+ @staticmethod
+ @abstractmethod
+ def ospec(): pass
+
+ #@staticmethod
+ #@abstractmethod
+ #def setup(m, i): pass
+
+ @staticmethod
+ @abstractmethod
+ def process(i): pass
+
+
+class StageChain(StageCls):
+ """ pass in a list of stages, and they will automatically be
+ chained together via their input and output specs into a
+ combinatorial chain.
+
+ the end result basically conforms to the exact same Stage API.
+
+ * input to this class will be the input of the first stage
+ * output of first stage goes into input of second
+ * output of second goes into input into third (etc. etc.)
+ * the output of this class will be the output of the last stage
+ """
+ def __init__(self, chain, specallocate=False):
+ self.chain = chain
+ self.specallocate = specallocate
+
+ def ispec(self):
+ return _spec(self.chain[0].ispec, "chainin")
+
+ def ospec(self):
+ return _spec(self.chain[-1].ospec, "chainout")
+
+ def _specallocate_setup(self, m, i):
+ for (idx, c) in enumerate(self.chain):
+ if hasattr(c, "setup"):
+ c.setup(m, i) # stage may have some module stuff
+ ofn = self.chain[idx].ospec # last assignment survives
+ o = _spec(ofn, 'chainin%d' % idx)
+ m.d.comb += eq(o, c.process(i)) # process input into "o"
+ if idx == len(self.chain)-1:
+ break
+ ifn = self.chain[idx+1].ispec # new input on next loop
+ i = _spec(ifn, 'chainin%d' % (idx+1))
+ m.d.comb += eq(i, o) # assign to next input
+ return o # last loop is the output
+
+ def _noallocate_setup(self, m, i):
+ for (idx, c) in enumerate(self.chain):
+ if hasattr(c, "setup"):
+ c.setup(m, i) # stage may have some module stuff
+ i = o = c.process(i) # store input into "o"
+ return o # last loop is the output
+
+ def setup(self, m, i):
+ if self.specallocate:
+ self.o = self._specallocate_setup(m, i)
+ else:
+ self.o = self._noallocate_setup(m, i)
+
+ def process(self, i):
+ return self.o # conform to Stage API: return last-loop output
+
+
+class ControlBase(Elaboratable):
+ """ Common functions for Pipeline API
+ """
+ def __init__(self, stage=None, in_multi=None, stage_ctl=False):
+ """ Base class containing ready/valid/data to previous and next stages
+
+ * p: contains ready/valid to the previous stage
+ * n: contains ready/valid to the next stage
+
+ Except when calling Controlbase.connect(), user must also:
+ * add data_i member to PrevControl (p) and
+ * add data_o member to NextControl (n)
+ """
+ self.stage = stage
+
+ # set up input and output IO ACK (prev/next ready/valid)
+ self.p = PrevControl(in_multi, stage_ctl)
+ self.n = NextControl(stage_ctl)
+
+ # set up the input and output data
+ if stage is not None:
+ self.p.data_i = _spec(stage.ispec, "data_i") # input type
+ self.n.data_o = _spec(stage.ospec, "data_o") # output type
+
+ def connect_to_next(self, nxt):
+ """ helper function to connect to the next stage data/valid/ready.
+ """
+ return self.n.connect_to_next(nxt.p)
+
+ def _connect_in(self, prev):
+ """ internal helper function to connect stage to an input source.
+ do not use to connect stage-to-stage!
+ """
+ return self.p._connect_in(prev.p)
+
+ def _connect_out(self, nxt):
+ """ internal helper function to connect stage to an output source.
+ do not use to connect stage-to-stage!
+ """
+ return self.n._connect_out(nxt.n)
+
+ def connect(self, pipechain):
+ """ connects a chain (list) of Pipeline instances together and
+ links them to this ControlBase instance:
+
+ in <----> self <---> out
+ | ^
+ v |
+ [pipe1, pipe2, pipe3, pipe4]
+ | ^ | ^ | ^
+ v | v | v |
+ out---in out--in out---in
+
+ Also takes care of allocating data_i/data_o, by looking up
+ the data spec for each end of the pipechain. i.e It is NOT
+ necessary to allocate self.p.data_i or self.n.data_o manually:
+ this is handled AUTOMATICALLY, here.
+
+ Basically this function is the direct equivalent of StageChain,
+ except that unlike StageChain, the Pipeline logic is followed.
+
+ Just as StageChain presents an object that conforms to the
+ Stage API from a list of objects that also conform to the
+ Stage API, an object that calls this Pipeline connect function
+ has the exact same pipeline API as the list of pipline objects
+ it is called with.
+
+ Thus it becomes possible to build up larger chains recursively.
+ More complex chains (multi-input, multi-output) will have to be
+ done manually.
+ """
+ eqs = [] # collated list of assignment statements
+
+ # connect inter-chain
+ for i in range(len(pipechain)-1):
+ pipe1 = pipechain[i]
+ pipe2 = pipechain[i+1]
+ eqs += pipe1.connect_to_next(pipe2)
+
+ # connect front of chain to ourselves
+ front = pipechain[0]
+ self.p.data_i = _spec(front.stage.ispec, "chainin")
+ eqs += front._connect_in(self)
+
+ # connect end of chain to ourselves
+ end = pipechain[-1]
+ self.n.data_o = _spec(end.stage.ospec, "chainout")
+ eqs += end._connect_out(self)
+
+ return eqs
+
+ def _postprocess(self, i): # XXX DISABLED
+ return i # RETURNS INPUT
+ if hasattr(self.stage, "postprocess"):
+ return self.stage.postprocess(i)
+ return i
+
+ def set_input(self, i):
+ """ helper function to set the input data
+ """
+ return eq(self.p.data_i, i)
+
+ def __iter__(self):
+ yield from self.p
+ yield from self.n
+
+ def ports(self):
+ return list(self)
+
+ def elaborate(self, platform):
+ """ handles case where stage has dynamic ready/valid functions
+ """
+ m = Module()
+ m.submodules.p = self.p
+ m.submodules.n = self.n
+
+ if self.stage is not None and hasattr(self.stage, "setup"):
+ self.stage.setup(m, self.p.data_i)
+
+ if not self.p.stage_ctl:
+ return m
+
+ # intercept the previous (outgoing) "ready", combine with stage ready
+ m.d.comb += self.p.s_ready_o.eq(self.p._ready_o & self.stage.d_ready)
+
+ # intercept the next (incoming) "ready" and combine it with data valid
+ sdv = self.stage.d_valid(self.n.ready_i)
+ m.d.comb += self.n.d_valid.eq(self.n.ready_i & sdv)
+
+ return m
+
+
* http://bugs.libre-riscv.org/show_bug.cgi?id=64
* http://bugs.libre-riscv.org/show_bug.cgi?id=57
- eq:
- --
-
- a strategically very important function that is identical in function
- to nmigen's Signal.eq function, except it may take objects, or a list
- of objects, or a tuple of objects, and where objects may also be
- Records.
-
- Stage API:
- ---------
-
- stage requires compliance with a strict API that may be
- implemented in several means, including as a static class.
- the methods of a stage instance must be as follows:
-
- * ispec() - Input data format specification
- returns an object or a list or tuple of objects, or
- a Record, each object having an "eq" function which
- takes responsibility for copying by assignment all
- sub-objects
- * ospec() - Output data format specification
- requirements as for ospec
- * process(m, i) - Processes an ispec-formatted object
- returns a combinatorial block of a result that
- may be assigned to the output, by way of the "eq"
- function
- * setup(m, i) - Optional function for setting up submodules
- may be used for more complex stages, to link
- the input (i) to submodules. must take responsibility
- for adding those submodules to the module (m).
- the submodules must be combinatorial blocks and
- must have their inputs and output linked combinatorially.
-
- Both StageCls (for use with non-static classes) and Stage (for use
- by static classes) are abstract classes from which, for convenience
- and as a courtesy to other developers, anything conforming to the
- Stage API may *choose* to derive.
-
- StageChain:
- ----------
-
- A useful combinatorial wrapper around stages that chains them together
- and then presents a Stage-API-conformant interface. By presenting
- the same API as the stages it wraps, it can clearly be used recursively.
-
RecordBasedStage:
----------------
StageChain, however when passed to UnbufferedPipeline they
can be used to introduce a single clock delay.
- ControlBase:
- -----------
-
- The base class for pipelines. Contains previous and next ready/valid/data.
- Also has an extremely useful "connect" function that can be used to
- connect a chain of pipelines and present the exact same prev/next
- ready/valid/data API.
-
UnbufferedPipeline:
------------------
import inspect
from nmoperator import eq, cat, shape
-
-
-class Object:
- def __init__(self):
- self.fields = OrderedDict()
-
- def __setattr__(self, k, v):
- print ("kv", k, v)
- if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
- k in dir(Object) or "fields" not in self.__dict__):
- return object.__setattr__(self, k, v)
- self.fields[k] = v
-
- def __getattr__(self, k):
- if k in self.__dict__:
- return object.__getattr__(self, k)
- try:
- return self.fields[k]
- except KeyError as e:
- raise AttributeError(e)
-
- def __iter__(self):
- for x in self.fields.values():
- if isinstance(x, Iterable):
- yield from x
- else:
- yield x
-
- def eq(self, inp):
- res = []
- for (k, o) in self.fields.items():
- i = getattr(inp, k)
- print ("eq", o, i)
- rres = o.eq(i)
- if isinstance(rres, Sequence):
- res += rres
- else:
- res.append(rres)
- print (res)
- return res
-
- def ports(self):
- return list(self)
-
-
-class RecordObject(Record):
- def __init__(self, layout=None, name=None):
- Record.__init__(self, layout=layout or [], name=None)
-
- def __setattr__(self, k, v):
- #print (dir(Record))
- if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
- k in dir(Record) or "fields" not in self.__dict__):
- return object.__setattr__(self, k, v)
- self.fields[k] = v
- #print ("RecordObject setattr", k, v)
- if isinstance(v, Record):
- newlayout = {k: (k, v.layout)}
- elif isinstance(v, Value):
- newlayout = {k: (k, v.shape())}
- else:
- newlayout = {k: (k, shape(v))}
- self.layout.fields.update(newlayout)
-
- def __iter__(self):
- for x in self.fields.values():
- if isinstance(x, Iterable):
- yield from x
- else:
- yield x
-
- def ports(self):
- return list(self)
-
-
-def _spec(fn, name=None):
- if name is None:
- return fn()
- varnames = dict(inspect.getmembers(fn.__code__))['co_varnames']
- if 'name' in varnames:
- return fn(name=name)
- return fn()
-
-
-class PrevControl(Elaboratable):
- """ contains signals that come *from* the previous stage (both in and out)
- * valid_i: previous stage indicating all incoming data is valid.
- may be a multi-bit signal, where all bits are required
- to be asserted to indicate "valid".
- * ready_o: output to next stage indicating readiness to accept data
- * data_i : an input - added by the user of this class
- """
-
- def __init__(self, i_width=1, stage_ctl=False):
- self.stage_ctl = stage_ctl
- self.valid_i = Signal(i_width, name="p_valid_i") # prev >>in self
- self._ready_o = Signal(name="p_ready_o") # prev <<out self
- self.data_i = None # XXX MUST BE ADDED BY USER
- if stage_ctl:
- self.s_ready_o = Signal(name="p_s_o_rdy") # prev <<out self
- self.trigger = Signal(reset_less=True)
-
- @property
- def ready_o(self):
- """ public-facing API: indicates (externally) that stage is ready
- """
- if self.stage_ctl:
- return self.s_ready_o # set dynamically by stage
- return self._ready_o # return this when not under dynamic control
-
- def _connect_in(self, prev, direct=False, fn=None):
- """ internal helper function to connect stage to an input source.
- do not use to connect stage-to-stage!
- """
- valid_i = prev.valid_i if direct else prev.valid_i_test
- data_i = fn(prev.data_i) if fn is not None else prev.data_i
- return [self.valid_i.eq(valid_i),
- prev.ready_o.eq(self.ready_o),
- eq(self.data_i, data_i),
- ]
-
- @property
- def valid_i_test(self):
- vlen = len(self.valid_i)
- if vlen > 1:
- # multi-bit case: valid only when valid_i is all 1s
- all1s = Const(-1, (len(self.valid_i), False))
- valid_i = (self.valid_i == all1s)
- else:
- # single-bit valid_i case
- valid_i = self.valid_i
-
- # when stage indicates not ready, incoming data
- # must "appear" to be not ready too
- if self.stage_ctl:
- valid_i = valid_i & self.s_ready_o
-
- return valid_i
-
- def elaborate(self, platform):
- m = Module()
- m.d.comb += self.trigger.eq(self.valid_i_test & self.ready_o)
- return m
-
- def eq(self, i):
- return [self.data_i.eq(i.data_i),
- self.ready_o.eq(i.ready_o),
- self.valid_i.eq(i.valid_i)]
-
- def __iter__(self):
- yield self.valid_i
- yield self.ready_o
- if hasattr(self.data_i, "ports"):
- yield from self.data_i.ports()
- elif isinstance(self.data_i, Sequence):
- yield from self.data_i
- else:
- yield self.data_i
-
- def ports(self):
- return list(self)
-
-
-class NextControl(Elaboratable):
- """ contains the signals that go *to* the next stage (both in and out)
- * valid_o: output indicating to next stage that data is valid
- * ready_i: input from next stage indicating that it can accept data
- * data_o : an output - added by the user of this class
- """
- def __init__(self, stage_ctl=False):
- self.stage_ctl = stage_ctl
- self.valid_o = Signal(name="n_valid_o") # self out>> next
- self.ready_i = Signal(name="n_ready_i") # self <<in next
- self.data_o = None # XXX MUST BE ADDED BY USER
- #if self.stage_ctl:
- self.d_valid = Signal(reset=1) # INTERNAL (data valid)
- self.trigger = Signal(reset_less=True)
-
- @property
- def ready_i_test(self):
- if self.stage_ctl:
- return self.ready_i & self.d_valid
- return self.ready_i
-
- def connect_to_next(self, nxt):
- """ helper function to connect to the next stage data/valid/ready.
- data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
- use this when connecting stage-to-stage
- """
- return [nxt.valid_i.eq(self.valid_o),
- self.ready_i.eq(nxt.ready_o),
- eq(nxt.data_i, self.data_o),
- ]
-
- def _connect_out(self, nxt, direct=False, fn=None):
- """ internal helper function to connect stage to an output source.
- do not use to connect stage-to-stage!
- """
- ready_i = nxt.ready_i if direct else nxt.ready_i_test
- data_o = fn(nxt.data_o) if fn is not None else nxt.data_o
- return [nxt.valid_o.eq(self.valid_o),
- self.ready_i.eq(ready_i),
- eq(data_o, self.data_o),
- ]
-
- def elaborate(self, platform):
- m = Module()
- m.d.comb += self.trigger.eq(self.ready_i_test & self.valid_o)
- return m
-
- def __iter__(self):
- yield self.ready_i
- yield self.valid_o
- if hasattr(self.data_o, "ports"):
- yield from self.data_o.ports()
- elif isinstance(self.data_o, Sequence):
- yield from self.data_o
- else:
- yield self.data_o
-
- def ports(self):
- return list(self)
-
-
-class StageCls(metaclass=ABCMeta):
- """ Class-based "Stage" API. requires instantiation (after derivation)
-
- see "Stage API" above.. Note: python does *not* require derivation
- from this class. All that is required is that the pipelines *have*
- the functions listed in this class. Derivation from this class
- is therefore merely a "courtesy" to maintainers.
- """
- @abstractmethod
- def ispec(self): pass # REQUIRED
- @abstractmethod
- def ospec(self): pass # REQUIRED
- #@abstractmethod
- #def setup(self, m, i): pass # OPTIONAL
- @abstractmethod
- def process(self, i): pass # REQUIRED
-
-
-class Stage(metaclass=ABCMeta):
- """ Static "Stage" API. does not require instantiation (after derivation)
-
- see "Stage API" above. Note: python does *not* require derivation
- from this class. All that is required is that the pipelines *have*
- the functions listed in this class. Derivation from this class
- is therefore merely a "courtesy" to maintainers.
- """
- @staticmethod
- @abstractmethod
- def ispec(): pass
-
- @staticmethod
- @abstractmethod
- def ospec(): pass
-
- #@staticmethod
- #@abstractmethod
- #def setup(m, i): pass
-
- @staticmethod
- @abstractmethod
- def process(i): pass
-
+from iocontrol import (Object, RecordObject, _spec,
+ PrevControl, NextControl, StageCls, Stage,
+ ControlBase, StageChain)
+
class RecordBasedStage(Stage):
""" convenience class which provides a Records-based layout.
def setup(seif, m, i): return self.__setup(m, i)
-class StageChain(StageCls):
- """ pass in a list of stages, and they will automatically be
- chained together via their input and output specs into a
- combinatorial chain.
-
- the end result basically conforms to the exact same Stage API.
-
- * input to this class will be the input of the first stage
- * output of first stage goes into input of second
- * output of second goes into input into third (etc. etc.)
- * the output of this class will be the output of the last stage
- """
- def __init__(self, chain, specallocate=False):
- self.chain = chain
- self.specallocate = specallocate
-
- def ispec(self):
- return _spec(self.chain[0].ispec, "chainin")
-
- def ospec(self):
- return _spec(self.chain[-1].ospec, "chainout")
-
- def _specallocate_setup(self, m, i):
- for (idx, c) in enumerate(self.chain):
- if hasattr(c, "setup"):
- c.setup(m, i) # stage may have some module stuff
- ofn = self.chain[idx].ospec # last assignment survives
- o = _spec(ofn, 'chainin%d' % idx)
- m.d.comb += eq(o, c.process(i)) # process input into "o"
- if idx == len(self.chain)-1:
- break
- ifn = self.chain[idx+1].ispec # new input on next loop
- i = _spec(ifn, 'chainin%d' % (idx+1))
- m.d.comb += eq(i, o) # assign to next input
- return o # last loop is the output
-
- def _noallocate_setup(self, m, i):
- for (idx, c) in enumerate(self.chain):
- if hasattr(c, "setup"):
- c.setup(m, i) # stage may have some module stuff
- i = o = c.process(i) # store input into "o"
- return o # last loop is the output
-
- def setup(self, m, i):
- if self.specallocate:
- self.o = self._specallocate_setup(m, i)
- else:
- self.o = self._noallocate_setup(m, i)
-
- def process(self, i):
- return self.o # conform to Stage API: return last-loop output
-
-
-class ControlBase(Elaboratable):
- """ Common functions for Pipeline API
- """
- def __init__(self, stage=None, in_multi=None, stage_ctl=False):
- """ Base class containing ready/valid/data to previous and next stages
-
- * p: contains ready/valid to the previous stage
- * n: contains ready/valid to the next stage
-
- Except when calling Controlbase.connect(), user must also:
- * add data_i member to PrevControl (p) and
- * add data_o member to NextControl (n)
- """
- self.stage = stage
-
- # set up input and output IO ACK (prev/next ready/valid)
- self.p = PrevControl(in_multi, stage_ctl)
- self.n = NextControl(stage_ctl)
-
- # set up the input and output data
- if stage is not None:
- self.p.data_i = _spec(stage.ispec, "data_i") # input type
- self.n.data_o = _spec(stage.ospec, "data_o") # output type
-
- def connect_to_next(self, nxt):
- """ helper function to connect to the next stage data/valid/ready.
- """
- return self.n.connect_to_next(nxt.p)
-
- def _connect_in(self, prev):
- """ internal helper function to connect stage to an input source.
- do not use to connect stage-to-stage!
- """
- return self.p._connect_in(prev.p)
-
- def _connect_out(self, nxt):
- """ internal helper function to connect stage to an output source.
- do not use to connect stage-to-stage!
- """
- return self.n._connect_out(nxt.n)
-
- def connect(self, pipechain):
- """ connects a chain (list) of Pipeline instances together and
- links them to this ControlBase instance:
-
- in <----> self <---> out
- | ^
- v |
- [pipe1, pipe2, pipe3, pipe4]
- | ^ | ^ | ^
- v | v | v |
- out---in out--in out---in
-
- Also takes care of allocating data_i/data_o, by looking up
- the data spec for each end of the pipechain. i.e It is NOT
- necessary to allocate self.p.data_i or self.n.data_o manually:
- this is handled AUTOMATICALLY, here.
-
- Basically this function is the direct equivalent of StageChain,
- except that unlike StageChain, the Pipeline logic is followed.
-
- Just as StageChain presents an object that conforms to the
- Stage API from a list of objects that also conform to the
- Stage API, an object that calls this Pipeline connect function
- has the exact same pipeline API as the list of pipline objects
- it is called with.
-
- Thus it becomes possible to build up larger chains recursively.
- More complex chains (multi-input, multi-output) will have to be
- done manually.
- """
- eqs = [] # collated list of assignment statements
-
- # connect inter-chain
- for i in range(len(pipechain)-1):
- pipe1 = pipechain[i]
- pipe2 = pipechain[i+1]
- eqs += pipe1.connect_to_next(pipe2)
-
- # connect front of chain to ourselves
- front = pipechain[0]
- self.p.data_i = _spec(front.stage.ispec, "chainin")
- eqs += front._connect_in(self)
-
- # connect end of chain to ourselves
- end = pipechain[-1]
- self.n.data_o = _spec(end.stage.ospec, "chainout")
- eqs += end._connect_out(self)
-
- return eqs
-
- def _postprocess(self, i): # XXX DISABLED
- return i # RETURNS INPUT
- if hasattr(self.stage, "postprocess"):
- return self.stage.postprocess(i)
- return i
-
- def set_input(self, i):
- """ helper function to set the input data
- """
- return eq(self.p.data_i, i)
-
- def __iter__(self):
- yield from self.p
- yield from self.n
-
- def ports(self):
- return list(self)
-
- def elaborate(self, platform):
- """ handles case where stage has dynamic ready/valid functions
- """
- m = Module()
- m.submodules.p = self.p
- m.submodules.n = self.n
-
- if self.stage is not None and hasattr(self.stage, "setup"):
- self.stage.setup(m, self.p.data_i)
-
- if not self.p.stage_ctl:
- return m
-
- # intercept the previous (outgoing) "ready", combine with stage ready
- m.d.comb += self.p.s_ready_o.eq(self.p._ready_o & self.stage.d_ready)
-
- # intercept the next (incoming) "ready" and combine it with data valid
- sdv = self.stage.d_valid(self.n.ready_i)
- m.d.comb += self.n.d_valid.eq(self.n.ready_i & sdv)
-
- return m
-
-
class BufferedHandshake(ControlBase):
""" buffered pipeline stage. data and strobe signals travel in sync.
if ever the input is ready and the output is not, processed data