mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
Abstract actor graphs
This commit is contained in:
parent
b14be4c8a3
commit
da522cd58d
5 changed files with 235 additions and 95 deletions
|
@ -1,23 +1,39 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
from migen.fhdl import verilog
|
from migen.fhdl import verilog
|
||||||
from migen.flow.ala import *
|
from migen.flow.ala import *
|
||||||
from migen.flow.network import *
|
from migen.flow.network import *
|
||||||
from migen.flow.composer import *
|
from migen.flow.composer import *
|
||||||
|
|
||||||
|
draw = len(sys.argv) > 1 and sys.argv[1] == "draw"
|
||||||
|
|
||||||
|
# Create graph
|
||||||
g = DataFlowGraph()
|
g = DataFlowGraph()
|
||||||
a1 = make_composable(g, Add(BV(16)))
|
a1 = ComposableSource(g, Add(BV(16)))
|
||||||
a2 = make_composable(g, Add(BV(16)))
|
a2 = ComposableSource(g, Add(BV(16)))
|
||||||
a3 = make_composable(g, Add(BV(16)))
|
a3 = ComposableSource(g, Add(BV(16)))
|
||||||
c3 = (a1 + a2)*a3
|
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))
|
# Elaborate
|
||||||
|
print("is_abstract before elaboration: " + str(g.is_abstract()))
|
||||||
if len(sys.argv) > 1 and sys.argv[1] == "draw":
|
if draw:
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import networkx as nx
|
|
||||||
nx.draw(g)
|
nx.draw(g)
|
||||||
plt.show()
|
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
|
self.endpoints[desc[0]] = ep
|
||||||
else:
|
else:
|
||||||
self.endpoints = endpoints
|
self.endpoints = endpoints
|
||||||
|
self.name = None
|
||||||
self.busy = Signal()
|
self.busy = Signal()
|
||||||
|
|
||||||
def token(self, ep):
|
def token(self, ep):
|
||||||
|
@ -70,7 +71,11 @@ class Actor:
|
||||||
return self.get_control_fragment() + self.get_process_fragment()
|
return self.get_control_fragment() + self.get_process_fragment()
|
||||||
|
|
||||||
def __repr__(self):
|
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):
|
class BinaryActor(Actor):
|
||||||
def get_binary_control_fragment(self, stb_i, ack_o, stb_o, ack_i):
|
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
|
from migen.corelogic import divider
|
||||||
|
|
||||||
class _SimpleBinary(CombinatorialActor):
|
class _SimpleBinary(CombinatorialActor):
|
||||||
def __init__(self, op, bv_op, bv_r):
|
def __init__(self, bv_op, bv_r=None):
|
||||||
self.op = op
|
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__(
|
super().__init__(
|
||||||
("operands", Sink, [("a", bv_op), ("b", bv_op)]),
|
("operands", Sink, [("a", bv_op), ("b", bv_op)]),
|
||||||
("result", Source, [("r", bv_r)]))
|
("result", Source, [("r", bv_r)]))
|
||||||
|
@ -18,44 +21,54 @@ class _SimpleBinary(CombinatorialActor):
|
||||||
])
|
])
|
||||||
|
|
||||||
class Add(_SimpleBinary):
|
class Add(_SimpleBinary):
|
||||||
def __init__(self, bv):
|
op = "+"
|
||||||
super().__init__("+", bv, BV(bv.width+1, bv.signed))
|
def get_result_bv(bv):
|
||||||
|
return BV(bv.width+1, bv.signed)
|
||||||
|
|
||||||
class Sub(_SimpleBinary):
|
class Sub(_SimpleBinary):
|
||||||
def __init__(self, bv):
|
op = "-"
|
||||||
super().__init__("-", bv, BV(bv.width+1, bv.signed))
|
def get_result_bv(bv):
|
||||||
|
return BV(bv.width+1, bv.signed)
|
||||||
|
|
||||||
class Mul(_SimpleBinary):
|
class Mul(_SimpleBinary):
|
||||||
def __init__(self, bv):
|
op = "*"
|
||||||
super().__init__("*", bv, BV(2*bv.width, bv.signed))
|
def get_result_bv(bv):
|
||||||
|
return BV(2*bv.width, bv.signed)
|
||||||
|
|
||||||
class And(_SimpleBinary):
|
class And(_SimpleBinary):
|
||||||
def __init__(self, bv):
|
op = "&"
|
||||||
super().__init__("&", bv, bv)
|
def get_result_bv(bv):
|
||||||
|
return bv
|
||||||
|
|
||||||
class Xor(_SimpleBinary):
|
class Xor(_SimpleBinary):
|
||||||
def __init__(self, bv):
|
op = "^"
|
||||||
super().__init__("^", bv, bv)
|
def get_result_bv(bv):
|
||||||
|
return bv
|
||||||
|
|
||||||
class Or(_SimpleBinary):
|
class Or(_SimpleBinary):
|
||||||
def __init__(self, bv):
|
op = "|"
|
||||||
super().__init__("|", bv, bv)
|
def get_result_bv(bv):
|
||||||
|
return bv
|
||||||
|
|
||||||
class LT(_SimpleBinary):
|
class LT(_SimpleBinary):
|
||||||
def __init__(self, bv):
|
op = "<"
|
||||||
super().__init__("<", bv, BV(1))
|
def get_result_bv(bv):
|
||||||
|
return BV(1)
|
||||||
|
|
||||||
class LE(_SimpleBinary):
|
class LE(_SimpleBinary):
|
||||||
def __init__(self, bv):
|
op = "<="
|
||||||
super().__init__("<=", bv, BV(1))
|
def get_result_bv(bv):
|
||||||
|
return BV(1)
|
||||||
|
|
||||||
class EQ(_SimpleBinary):
|
class EQ(_SimpleBinary):
|
||||||
def __init__(self, bv):
|
op = "=="
|
||||||
super().__init__("==", bv, BV(1))
|
def get_result_bv(bv):
|
||||||
|
return BV(1)
|
||||||
|
|
||||||
class NE(_SimpleBinary):
|
class NE(_SimpleBinary):
|
||||||
def __init__(self, bv):
|
op = "!="
|
||||||
super().__init__("!=", bv, BV(1))
|
def get_result_bv(bv):
|
||||||
|
return BV(1)
|
||||||
|
|
||||||
class DivMod(SequentialActor):
|
class DivMod(SequentialActor):
|
||||||
def __init__(self, width):
|
def __init__(self, width):
|
||||||
|
|
|
@ -3,71 +3,62 @@ from migen.flow.ala import *
|
||||||
from migen.flow.plumbing import *
|
from migen.flow.plumbing import *
|
||||||
from migen.flow.network 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)
|
assert id(a.dfg) == id(b.dfg)
|
||||||
return (a.actor.endpoints[a.endp].token_signal(),
|
dfg = a.dfg
|
||||||
b.actor.endpoints[b.endp].token_signal())
|
|
||||||
|
|
||||||
def _simple_binary(a, b, actor_class):
|
bva = a.actor_node.get_dict()["bv_r"]
|
||||||
(signal_self, signal_other) = _get_bin_sigs(a, b)
|
bvb = b.actor_node.get_dict()["bv_r"]
|
||||||
width = max(signal_self.bv.width, signal_other.bv.width)
|
bv_op = BV(max(bva.width, bvb.width), bva.signed and bvb.signed)
|
||||||
signed = signal_self.bv.signed and signal_other.bv.signed
|
bv_r = actor_class.get_result_bv(bv_op)
|
||||||
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():
|
new_actor = ActorNode(actor_class, {"bv_op": bv_op, "bv_r": bv_r})
|
||||||
def __init__(self, dfg, actor, endp):
|
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_node):
|
||||||
self.dfg = dfg
|
self.dfg = dfg
|
||||||
self.actor = actor
|
if not isinstance(actor_node, ActorNode):
|
||||||
self.endp = endp
|
actor_node = ActorNode(actor_node)
|
||||||
|
self.actor_node = actor_node
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return _simple_binary(self, other, Add)
|
return _create(self, other, Add)
|
||||||
def __radd__(self, other):
|
def __radd__(self, other):
|
||||||
return _simple_binary(other, self, Add)
|
return _create(other, self, Add)
|
||||||
def __sub__(self, other):
|
def __sub__(self, other):
|
||||||
return _simple_binary(self, other, Sub)
|
return _create(self, other, Sub)
|
||||||
def __rsub__(self, other):
|
def __rsub__(self, other):
|
||||||
return _simple_binary(other, self, Sub)
|
return _create(other, self, Sub)
|
||||||
def __mul__(self, other):
|
def __mul__(self, other):
|
||||||
return _simple_binary(self, other, Mul)
|
return _create(self, other, Mul)
|
||||||
def __rmul__(self, other):
|
def __rmul__(self, other):
|
||||||
return _simple_binary(other, self, Mul)
|
return _create(other, self, Mul)
|
||||||
def __and__(self, other):
|
def __and__(self, other):
|
||||||
return _simple_binary(self, other, And)
|
return _create(self, other, And)
|
||||||
def __rand__(self, other):
|
def __rand__(self, other):
|
||||||
return _simple_binary(other, self, And)
|
return _create(other, self, And)
|
||||||
def __xor__(self, other):
|
def __xor__(self, other):
|
||||||
return _simple_binary(self, other, Xor)
|
return _create(self, other, Xor)
|
||||||
def __rxor__(self, other):
|
def __rxor__(self, other):
|
||||||
return _simple_binary(other, self, Xor)
|
return _create(other, self, Xor)
|
||||||
def __or__(self, other):
|
def __or__(self, other):
|
||||||
return _simple_binary(self, other, Or)
|
return _create(self, other, Or)
|
||||||
def __ror__(self, other):
|
def __ror__(self, other):
|
||||||
return _simple_binary(other, self, Or)
|
return _create(other, self, Or)
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return _simple_binary(self, other, LT)
|
return _create(self, other, LT)
|
||||||
def __le__(self, other):
|
def __le__(self, other):
|
||||||
return _simple_binary(self, other, LE)
|
return _create(self, other, LE)
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return _simple_binary(self, other, EQ)
|
return _create(self, other, EQ)
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return _simple_binary(self, other, NE)
|
return _create(self, other, NE)
|
||||||
def __gt__(self, other):
|
def __gt__(self, other):
|
||||||
return _simple_binary(other, self, LT)
|
return _create(other, self, LT)
|
||||||
def __ge__(self, other):
|
def __ge__(self, other):
|
||||||
return _simple_binary(other, self, LE)
|
return _create(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
|
|
||||||
|
|
|
@ -4,27 +4,142 @@ from migen.fhdl.structure import *
|
||||||
from migen.flow.actor import *
|
from migen.flow.actor import *
|
||||||
from migen.corelogic.misc import optree
|
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):
|
class DataFlowGraph(MultiDiGraph):
|
||||||
def add_connection(self, source_node, sink_node, source_ep=None, sink_ep=None):
|
def __init__(self):
|
||||||
if source_ep is None:
|
self.elaborated = False
|
||||||
source_eps = source_node.sources()
|
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)
|
assert(len(source_eps) == 1)
|
||||||
source_ep = source_eps[0]
|
d["source"] = source_eps[0]
|
||||||
if sink_ep is None:
|
if d["sink"] is None:
|
||||||
sink_eps = sink_node.sinks()
|
sink_eps = v.actor.sinks()
|
||||||
assert(len(sink_eps) == 1)
|
assert(len(sink_eps) == 1)
|
||||||
sink_ep = sink_eps[0]
|
d["sink"] = sink_eps[0]
|
||||||
self.add_edge(source_node, sink_node, source=source_ep, sink=sink_ep)
|
|
||||||
|
# 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):
|
class CompositeActor(Actor):
|
||||||
def __init__(self, dfg): # TODO: endpoints
|
def __init__(self, dfg):
|
||||||
|
dfg.elaborate()
|
||||||
self.dfg = dfg
|
self.dfg = dfg
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def get_fragment(self):
|
def get_fragment(self):
|
||||||
this_fragments = [get_conn_fragment(x[0].endpoints[x[2]["source"]], x[1].endpoints[x[2]["sink"]])
|
comb = [self.busy.eq(optree("|", [node.actor.busy for node in self.dfg]))]
|
||||||
for x in self.dfg.edges(data=True)]
|
fragment = Fragment(comb)
|
||||||
this = sum(this_fragments, Fragment())
|
for node in self.dfg:
|
||||||
others = sum([node.get_fragment() for node in self.dfg], Fragment())
|
fragment += node.actor.get_fragment()
|
||||||
busy = Fragment([self.busy.eq(optree("|", [node.busy for node in self.dfg]))])
|
for u, v, d in self.dfg.edges_iter(data=True):
|
||||||
return this + others + busy
|
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 a new issue