Abstract actor graphs
This commit is contained in:
parent
b14be4c8a3
commit
da522cd58d
|
@ -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))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue