From da522cd58d86713b93da1e5f49b0f280a5f35245 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 15 Jun 2012 17:52:19 +0200 Subject: [PATCH] Abstract actor graphs --- examples/dataflow/arithmetic.py | 36 +++++--- migen/flow/actor.py | 7 +- migen/flow/ala.py | 57 +++++++----- migen/flow/composer.py | 81 ++++++++--------- migen/flow/network.py | 149 ++++++++++++++++++++++++++++---- 5 files changed, 235 insertions(+), 95 deletions(-) diff --git a/examples/dataflow/arithmetic.py b/examples/dataflow/arithmetic.py index f6e786778..95c777285 100644 --- a/examples/dataflow/arithmetic.py +++ b/examples/dataflow/arithmetic.py @@ -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() +a1.actor_node.name = "in1" +a2.actor_node.name = "in2" +a3.actor_node.name = "in3" +c3.actor_node.name = "result" -print(verilog.convert(frag)) - -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)) diff --git a/migen/flow/actor.py b/migen/flow/actor.py index 7159c2a7f..71dc1dffd 100644 --- a/migen/flow/actor.py +++ b/migen/flow/actor.py @@ -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): diff --git a/migen/flow/ala.py b/migen/flow/ala.py index b2e844ebd..992141826 100644 --- a/migen/flow/ala.py +++ b/migen/flow/ala.py @@ -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): diff --git a/migen/flow/composer.py b/migen/flow/composer.py index b9237dfb7..2c58c9672 100644 --- a/migen/flow/composer.py +++ b/migen/flow/composer.py @@ -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()) + 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) -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) - -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) diff --git a/migen/flow/network.py b/migen/flow/network.py index 600384436..f3ec31c2d 100644 --- a/migen/flow/network.py +++ b/migen/flow/network.py @@ -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 = " [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