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))
self.endpoints[desc[0]] = ep
else:
self.endpoints = endpoints
+ self.name = None
self.busy = Signal()
def token(self, ep):
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):
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)]))
])
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):
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)
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