Abstract actor graphs

This commit is contained in:
Sebastien Bourdeauducq 2012-06-15 17:52:19 +02:00
parent b14be4c8a3
commit da522cd58d
5 changed files with 235 additions and 95 deletions

View 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()
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))

View 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):

View 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):

View 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())
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)

View 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