Abstract actor graphs
authorSebastien Bourdeauducq <sebastien@milkymist.org>
Fri, 15 Jun 2012 15:52:19 +0000 (17:52 +0200)
committerSebastien Bourdeauducq <sebastien@milkymist.org>
Fri, 15 Jun 2012 15:52:19 +0000 (17:52 +0200)
examples/dataflow/arithmetic.py
migen/flow/actor.py
migen/flow/ala.py
migen/flow/composer.py
migen/flow/network.py

index f6e786778b4d3c5699df379321d7cad489546b12..95c777285aea12c38ebe4941b2b747df50566b52 100644 (file)
@@ -1,23 +1,39 @@
 import sys
 
+import matplotlib.pyplot as plt
+import networkx as nx
+
 from migen.fhdl import verilog
 from migen.flow.ala import *
 from migen.flow.network import *
 from migen.flow.composer import *
 
+draw = len(sys.argv) > 1 and sys.argv[1] == "draw"
+
+# Create graph
 g = DataFlowGraph()
-a1 = make_composable(g, Add(BV(16)))
-a2 = make_composable(g, Add(BV(16)))
-a3 = make_composable(g, Add(BV(16)))
+a1 = ComposableSource(g, Add(BV(16)))
+a2 = ComposableSource(g, Add(BV(16)))
+a3 = ComposableSource(g, Add(BV(16)))
 c3 = (a1 + a2)*a3
-c = CompositeActor(g)
-
-frag = c.get_fragment()
 
-print(verilog.convert(frag))
+a1.actor_node.name = "in1"
+a2.actor_node.name = "in2"
+a3.actor_node.name = "in3"
+c3.actor_node.name = "result"
 
-if len(sys.argv) > 1 and sys.argv[1] == "draw":
-       import matplotlib.pyplot as plt
-       import networkx as nx
+# Elaborate
+print("is_abstract before elaboration: " + str(g.is_abstract()))
+if draw:
        nx.draw(g)
        plt.show()
+g.elaborate()
+print("is_abstract after elaboration : " + str(g.is_abstract()))
+if draw:
+       nx.draw(g)
+       plt.show()
+
+# Convert
+#c = CompositeActor(g)
+#frag = c.get_fragment()
+#print(verilog.convert(frag))
index 7159c2a7fa4e0158edd45bc51b8994facc8a1c74..71dc1dffd1eae912e3e7cccc2cfecf1efa65889a 100644 (file)
@@ -46,6 +46,7 @@ class Actor:
                                self.endpoints[desc[0]] = ep
                else:
                        self.endpoints = endpoints
+               self.name = None
                self.busy = Signal()
 
        def token(self, ep):
@@ -70,7 +71,11 @@ class Actor:
                return self.get_control_fragment() + self.get_process_fragment()
        
        def __repr__(self):
-               return "<" + self.__class__.__name__ + " " + repr(self.sinks()) + " " + repr(self.sources()) + ">"
+               r = "<" + self.__class__.__name__
+               if self.name is not None:
+                       r += ": " + self.name
+               r += ">"
+               return r
 
 class BinaryActor(Actor):
        def get_binary_control_fragment(self, stb_i, ack_o, stb_o, ack_i):
index b2e844ebd2d8adf0a9bb6d8e5dfe00a569ff3bcb..992141826a6cddaae038e02dcbfe2d4c05f58f47 100644 (file)
@@ -5,8 +5,11 @@ from migen.corelogic.record import *
 from migen.corelogic import divider
 
 class _SimpleBinary(CombinatorialActor):
-       def __init__(self, op, bv_op, bv_r):
-               self.op = op
+       def __init__(self, bv_op, bv_r=None):
+               self.bv_op = bv_op
+               if bv_r is None:
+                       bv_r = self.__class__.get_result_bv(bv_op)
+               self.bv_r = bv_r
                super().__init__(
                        ("operands", Sink, [("a", bv_op), ("b", bv_op)]),
                        ("result", Source, [("r", bv_r)]))
@@ -18,44 +21,54 @@ class _SimpleBinary(CombinatorialActor):
                ])
 
 class Add(_SimpleBinary):
-       def __init__(self, bv):
-               super().__init__("+", bv, BV(bv.width+1, bv.signed))
+       op = "+"
+       def get_result_bv(bv):
+               return BV(bv.width+1, bv.signed)
 
 class Sub(_SimpleBinary):
-       def __init__(self, bv):
-               super().__init__("-", bv, BV(bv.width+1, bv.signed))
+       op = "-"
+       def get_result_bv(bv):
+               return BV(bv.width+1, bv.signed)
 
 class Mul(_SimpleBinary):
-       def __init__(self, bv):
-               super().__init__("*", bv, BV(2*bv.width, bv.signed))
+       op = "*"
+       def get_result_bv(bv):
+               return BV(2*bv.width, bv.signed)
 
 class And(_SimpleBinary):
-       def __init__(self, bv):
-               super().__init__("&", bv, bv)
+       op = "&"
+       def get_result_bv(bv):
+               return bv
 
 class Xor(_SimpleBinary):
-       def __init__(self, bv):
-               super().__init__("^", bv, bv)
+       op = "^"
+       def get_result_bv(bv):
+               return bv
 
 class Or(_SimpleBinary):
-       def __init__(self, bv):
-               super().__init__("|", bv, bv)
+       op = "|"
+       def get_result_bv(bv):
+               return bv
 
 class LT(_SimpleBinary):
-       def __init__(self, bv):
-               super().__init__("<", bv, BV(1))
+       op = "<"
+       def get_result_bv(bv):
+               return BV(1)
 
 class LE(_SimpleBinary):
-       def __init__(self, bv):
-               super().__init__("<=", bv, BV(1))
+       op = "<="
+       def get_result_bv(bv):
+               return BV(1)
 
 class EQ(_SimpleBinary):
-       def __init__(self, bv):
-               super().__init__("==", bv, BV(1))
+       op = "=="
+       def get_result_bv(bv):
+               return BV(1)
 
 class NE(_SimpleBinary):
-       def __init__(self, bv):
-               super().__init__("!=", bv, BV(1))
+       op = "!="
+       def get_result_bv(bv):
+               return BV(1)
 
 class DivMod(SequentialActor):
        def __init__(self, width):
index b9237dfb73d31eb819dadee25ffef4a27cb07b33..2c58c9672ea0256b31f5d0e364e3addf0d7d46d1 100644 (file)
@@ -3,71 +3,62 @@ from migen.flow.ala import *
 from migen.flow.plumbing import *
 from migen.flow.network import *
 
-def _get_bin_sigs(a, b):
+def _create(a, b, actor_class):
        assert id(a.dfg) == id(b.dfg)
-       return (a.actor.endpoints[a.endp].token_signal(),
-               b.actor.endpoints[b.endp].token_signal())
-
-def _simple_binary(a, b, actor_class):
-       (signal_self, signal_other) = _get_bin_sigs(a, b)
-       width = max(signal_self.bv.width, signal_other.bv.width)
-       signed = signal_self.bv.signed and signal_other.bv.signed
-       actor = actor_class(BV(width, signed))
-       combinator = Combinator(actor.token("operands").layout(), ["a"], ["b"])
-       a.dfg.add_connection(combinator, actor)
-       a.dfg.add_connection(a.actor, combinator, a.endp, "sink0")
-       a.dfg.add_connection(b.actor, combinator, b.endp, "sink1")
-       return make_composable(a.dfg, actor)
+       dfg = a.dfg
+       
+       bva = a.actor_node.get_dict()["bv_r"]
+       bvb = b.actor_node.get_dict()["bv_r"]
+       bv_op = BV(max(bva.width, bvb.width), bva.signed and bvb.signed)
+       bv_r = actor_class.get_result_bv(bv_op)
+       
+       new_actor = ActorNode(actor_class, {"bv_op": bv_op, "bv_r": bv_r})
+       dfg.add_connection(a.actor_node, new_actor, "result", "operands", sink_subr=["a"])
+       dfg.add_connection(b.actor_node, new_actor, "result", "operands", sink_subr=["b"])
+       
+       return ComposableSource(dfg, new_actor)
 
-class ComposableSource():
-       def __init__(self, dfg, actor, endp):
+class ComposableSource:
+       def __init__(self, dfg, actor_node):
                self.dfg = dfg
-               self.actor = actor
-               self.endp = endp
+               if not isinstance(actor_node, ActorNode):
+                       actor_node = ActorNode(actor_node)
+               self.actor_node = actor_node
        
        def __add__(self, other):
-               return _simple_binary(self, other, Add)
+               return _create(self, other, Add)
        def __radd__(self, other):
-               return _simple_binary(other, self, Add)
+               return _create(other, self, Add)
        def __sub__(self, other):
-               return _simple_binary(self, other, Sub)
+               return _create(self, other, Sub)
        def __rsub__(self, other):
-               return _simple_binary(other, self, Sub)
+               return _create(other, self, Sub)
        def __mul__(self, other):
-               return _simple_binary(self, other, Mul)
+               return _create(self, other, Mul)
        def __rmul__(self, other):
-               return _simple_binary(other, self, Mul)
+               return _create(other, self, Mul)
        def __and__(self, other):
-               return _simple_binary(self, other, And)
+               return _create(self, other, And)
        def __rand__(self, other):
-               return _simple_binary(other, self, And)
+               return _create(other, self, And)
        def __xor__(self, other):
-               return _simple_binary(self, other, Xor)
+               return _create(self, other, Xor)
        def __rxor__(self, other):
-               return _simple_binary(other, self, Xor)
+               return _create(other, self, Xor)
        def __or__(self, other):
-               return _simple_binary(self, other, Or)
+               return _create(self, other, Or)
        def __ror__(self, other):
-               return _simple_binary(other, self, Or)
+               return _create(other, self, Or)
 
        def __lt__(self, other):
-               return _simple_binary(self, other, LT)
+               return _create(self, other, LT)
        def __le__(self, other):
-               return _simple_binary(self, other, LE)
+               return _create(self, other, LE)
        def __eq__(self, other):
-               return _simple_binary(self, other, EQ)
+               return _create(self, other, EQ)
        def __ne__(self, other):
-               return _simple_binary(self, other, NE)
+               return _create(self, other, NE)
        def __gt__(self, other):
-               return _simple_binary(other, self, LT)
+               return _create(other, self, LT)
        def __ge__(self, other):
-               return _simple_binary(other, self, LE)
-
-def make_composable(dfg, actor):
-       r = [ComposableSource(dfg, actor, k) for k in sorted(actor.sources())]
-       if len(r) > 1:
-               return tuple(r)
-       elif len(r) > 0:
-               return r[0]
-       else:
-               return None
+               return _create(other, self, LE)
index 60038443626c9fad340e8a8c8a8eba0dbe0b5839..f3ec31c2d6e07757dc4add76fd4bf56352294439 100644 (file)
@@ -4,27 +4,142 @@ from migen.fhdl.structure import *
 from migen.flow.actor import *
 from migen.corelogic.misc import optree
 
+# Graph nodes can be either:
+#  (1) a reference to an existing actor
+#  (2) an abstract (class, dictionary) pair meaning that the actor class should be
+#      instantiated with the parameters from the dictionary.
+#      This form is needed to enable actor duplication or sharing during elaboration.
+
+class ActorNode:
+       def __init__(self, actor_class, parameters=None):
+               if isinstance(actor_class, type):
+                       self.actor_class = actor_class
+                       self.parameters = parameters
+               else:
+                       self.actor = actor_class
+               self.name = None
+       
+       def is_abstract(self):
+               return hasattr(self, "actor_class")
+               
+       def instantiate(self):
+               if self.is_abstract():
+                       self.actor = self.actor_class(**self.parameters)
+                       del self.actor_class
+                       del self.parameters
+       
+       def get_dict(self):
+               if self.is_abstract():
+                       return self.parameters
+               else:
+                       return self.actor.__dict__
+       
+       def __repr__(self):
+               if self.is_abstract():
+                       r = "<abstract " + self.actor_class.__name__
+                       if self.name is not None:
+                               r += ": " + self.name
+                       r += ">"
+               else:
+                       r = repr(self.actor)
+               return r
+
 class DataFlowGraph(MultiDiGraph):
-       def add_connection(self, source_node, sink_node, source_ep=None, sink_ep=None):
-               if source_ep is None:
-                       source_eps = source_node.sources()
-                       assert(len(source_eps) == 1)
-                       source_ep = source_eps[0]
-               if sink_ep is None:
-                       sink_eps = sink_node.sinks()
-                       assert(len(sink_eps) == 1)
-                       sink_ep = sink_eps[0]
-               self.add_edge(source_node, sink_node, source=source_ep, sink=sink_ep)
+       def __init__(self):
+               self.elaborated = False
+               super().__init__()
+       
+       def add_connection(self, source_node, sink_node,
+         source_ep=None, sink_ep=None,         # default: assume nodes have 1 source/sink and use that one
+         source_subr=None, sink_subr=None):    # default: use whole record
+               if not isinstance(source_node, ActorNode):
+                       source_node = ActorNode(source_node)
+               if not isinstance(sink_node, ActorNode):
+                       sink_node = ActorNode(sink_node)
+               self.add_edge(source_node, sink_node,
+                       source=source_ep, sink=sink_ep,
+                       source_subr=source_subr, sink_subr=sink_subr)
+       
+       # Returns a dictionary
+       #   source -> [sink1, ..., sinkn]
+       # each element being as a (node, endpoint) pair.
+       # NB: ignores subrecords.
+       def _source_to_sinks(self):
+               d = dict()
+               for u, v, data in self.edges_iter(data=True):
+                       el_src = (u, data["source"])
+                       el_dst = (v, data["sink"])
+                       if el_src in d:
+                               d[el_src].append(el_dst)
+                       else:
+                               d[el_src] = [el_dst]
+               return d
+               
+       # List sources that feed more than one sink.
+       # NB: ignores subrecords.
+       def _list_divergences(self):
+               d = self._source_to_sinks()
+               return dict((k, v) for k, v in d.items() if len(v) > 1)
+       
+       # A graph is abstract if any of these conditions is met:
+       #  (1) A node is an abstract actor.
+       #  (2) A subrecord is used.
+       #  (3) A single source feeds more than one sink.
+       # NB: It is not allowed for a single sink to be fed by more than one source.
+       def is_abstract(self):
+               return any(x.is_abstract() for x in self) \
+                       or any(d["source_subr"] is not None or d["sink_subr"] is not None
+                               for u, v, d in self.edges_iter(data=True)) \
+                       or self._list_divergences()
+       
+       def _eliminate_subrecords(self):
+               pass # TODO
+       
+       def _eliminate_divergences(self):
+               pass # TODO
+       
+       def _instantiate_actors(self):
+               for actor in self:
+                       actor.instantiate()
+               for u, v, d in self.edges_iter(data=True):
+                       if d["source"] is None:
+                               source_eps = u.actor.sources()
+                               assert(len(source_eps) == 1)
+                               d["source"] = source_eps[0]
+                       if d["sink"] is None:
+                               sink_eps = v.actor.sinks()
+                               assert(len(sink_eps) == 1)
+                               d["sink"] = sink_eps[0]
+       
+       # Elaboration turns an abstract DFG into a concrete one.
+       #   Pass 1: eliminate subrecords by inserting Combinator/Splitter actors
+       #   Pass 2: eliminate divergences by inserting Distributor actors
+       #   Pass 3: run optimizer (e.g. share and duplicate actors)
+       #   Pass 4: instantiate all abstract actors and explicit "None" endpoints
+       def elaborate(self, optimizer=None):
+               if self.elaborated:
+                       return
+               self.elaborated = True
+               
+               self._eliminate_subrecords()
+               self._eliminate_divergences()
+               if optimizer is not None:
+                       optimizer(self)
+               self._instantiate_actors()
 
 class CompositeActor(Actor):
-       def __init__(self, dfg): # TODO: endpoints
+       def __init__(self, dfg):
+               dfg.elaborate()
                self.dfg = dfg
                super().__init__()
        
        def get_fragment(self):
-               this_fragments = [get_conn_fragment(x[0].endpoints[x[2]["source"]], x[1].endpoints[x[2]["sink"]])
-                       for x in self.dfg.edges(data=True)]
-               this = sum(this_fragments, Fragment())
-               others = sum([node.get_fragment() for node in self.dfg], Fragment())
-               busy = Fragment([self.busy.eq(optree("|", [node.busy for node in self.dfg]))])
-               return this + others + busy
+               comb = [self.busy.eq(optree("|", [node.actor.busy for node in self.dfg]))]
+               fragment = Fragment(comb)
+               for node in self.dfg:
+                       fragment += node.actor.get_fragment()
+               for u, v, d in self.dfg.edges_iter(data=True):
+                       ep_src = u.actor.endpoints[d["source"]]
+                       ep_dst = v.actor.endpoints[d["sink"]]
+                       fragment += get_conn_fragment(ep_src, ep_dst)
+               return fragment