"""
Graph representation of the logic
Naming convention:
Vertex
Edge
Element := Vertex | Edge
"""
from __future__ import annotations
from typing import Iterator, Optional
from python2verilog.ir import expressions as expr
from python2verilog.utils.typed import guard, typed, typed_strict
[docs]
def get_variables(exp: expr.Expression) -> Iterator[expr.Var]:
"""
Gets variables from expression
"""
if isinstance(exp, expr.UBinOp):
yield from get_variables(exp.left)
yield from get_variables(exp.right)
elif isinstance(exp, expr.UnaryOp):
yield from get_variables(exp.expr)
elif isinstance(exp, expr.Var):
yield exp
elif isinstance(exp, (expr.UInt, expr.Int)):
pass
else:
raise RuntimeError(f"{type(exp)}")
[docs]
class Element:
"""
Element, base class for vertex or edge
"""
def __init__(self, unique_id: str, name: str = ""):
self.name = typed_strict(name, str)
self.unique_id = typed_strict(unique_id, str)
def __hash__(self) -> int:
return hash(self.unique_id)
def __eq__(self, __value: object):
if isinstance(__value, Element):
return self.unique_id == __value.unique_id
return False
[docs]
def visit_nonclocked(self) -> Iterator[Element]:
"""
Yields self and recursively yields optimal nonclocked children of element
:return: [children_branch_0, children_branch_1, ...]
"""
yield from ()
[docs]
def view_children(self) -> str:
"""
Views children of node
"""
return str(list(self.visit_nonclocked()))
[docs]
def children(self) -> Iterator[Element]:
"""
Gets children of node
"""
yield from ()
[docs]
def exclusions(self) -> Iterator[str]:
"""
Yields all exclusion groups that will be read or written to
within this group of nonclocked nodes.
The reason reads are also included is because checking a callee's
ready signal more than once in a clock cycle is usually incorrect.
"""
yield from ()
[docs]
class BasicElement(Element):
"""
Basic element with a single child
"""
def __init__(
self,
unique_id: str,
*args,
child: Optional[Element] = None,
**kwargs,
):
super().__init__(unique_id, *args, **kwargs)
self._child = typed(child, Element)
self._optimal_child: Optional[Element] = None
[docs]
def visit_nonclocked(self) -> Iterator[Element]:
if isinstance(self, ClockedEdge):
yield self
yield self.optimal_child
elif self.optimal_child:
yield self
yield from self.optimal_child.visit_nonclocked()
@property
def child(self) -> Element:
"""
child or optimal_child if no child
"""
assert self._child, f"{self} {self.view_children()}"
return typed_strict(self._child, Element)
@child.setter
def child(self, other: Element):
self._child = typed_strict(other, Element)
[docs]
def has_child(self) -> bool:
"""
Returns True if has child
"""
return self._child is not None
[docs]
def children(self):
if self._child:
yield self._child
if self._optimal_child:
yield self._optimal_child
@property
def optimal_child(self):
"""
Optimal child or child otherwise
"""
return self._optimal_child if self._optimal_child else self._child
@optimal_child.setter
def optimal_child(self, other: Element):
self._optimal_child = typed_strict(other, Element)
[docs]
class Node(Element):
"""
Vertex
"""
def __repr__(self) -> str:
return self.name
[docs]
class IfElseNode(Node, Element):
"""
Represents an if-else statement
"""
def __init__(
self,
unique_id: str,
*args,
true_edge: Edge,
false_edge: Edge,
condition: expr.Expression,
**kwargs,
):
super().__init__(unique_id, *args, **kwargs)
self.true_edge = typed_strict(true_edge, Edge)
self.false_edge = typed_strict(false_edge, Edge)
self.condition = typed_strict(condition, expr.Expression)
self._optimal_true_edge: Optional[Edge] = None
self._optimal_false_edge: Optional[Edge] = None
@property
def optimal_true_edge(self):
"""
optimal true edge or edge otherwise
"""
return self._optimal_true_edge if self._optimal_true_edge else self.true_edge
@optimal_true_edge.setter
def optimal_true_edge(self, other: Edge):
self._optimal_true_edge = typed_strict(other, Edge)
@property
def optimal_false_edge(self):
"""
optimal false edge
"""
return self._optimal_false_edge if self._optimal_false_edge else self.false_edge
@optimal_false_edge.setter
def optimal_false_edge(self, other: Edge):
self._optimal_false_edge = typed_strict(other, Edge)
[docs]
def children(self) -> Iterator[Edge]:
"""
Gets edges
"""
if self.true_edge:
yield self.true_edge
if self.false_edge:
yield self.false_edge
if self._optimal_true_edge:
yield self._optimal_true_edge
if self._optimal_false_edge:
yield self._optimal_false_edge
[docs]
def exclusions(self):
for var in get_variables(self.condition):
if isinstance(var, expr.ExclusiveVar):
yield var.exclusive_group
yield from self.optimal_true_edge.exclusions()
yield from self.optimal_false_edge.exclusions()
def __repr__(self):
return f"If{self.condition}"
[docs]
def visit_nonclocked(self) -> Iterator[Element]:
yield self
yield Node(unique_id="", name="True Branch")
yield from self.optimal_true_edge.visit_nonclocked()
yield Node(unique_id="", name="False Branch")
yield from self.optimal_false_edge.visit_nonclocked()
[docs]
class BasicNode(Node, BasicElement):
"""
Basic node.
Has one child.
"""
def __init__(self, unique_id: str, *args, child: Edge | None = None, **kwargs):
super().__init__(unique_id, *args, **kwargs)
self._child = child
@property
def edge(self) -> Edge:
"""
Gets edge
"""
assert guard(self._child, Edge)
return self._child
@edge.setter
def edge(self, other: Edge):
assert guard(other, Edge)
self._child = other
[docs]
class AssignNode(BasicNode):
"""
Represents a non-blocking assignment,
i.e. assignments that do not block the execution of
the next statements, without a clock cycle having to pass
"""
def __init__(
self,
unique_id: str,
*args,
lvalue: expr.Var,
rvalue: expr.Expression,
child: Optional[Edge] = None,
**kwargs,
):
super().__init__(unique_id, *args, child=child, **kwargs)
self.lvalue = typed_strict(lvalue, expr.Var)
self.rvalue = typed_strict(rvalue, expr.Expression)
self._child = typed(child, Edge)
def __repr__(self):
return f"{self.lvalue} = {self.rvalue}"
[docs]
def exclusions(self):
for var in get_variables(self.lvalue):
if isinstance(var, expr.ExclusiveVar):
yield var.exclusive_group
yield from get_variables(self.rvalue)
if self._child:
yield from self.child.exclusions()
[docs]
class Edge(BasicElement):
"""
Represents an edge between two vertices
"""
def __init__(self, unique_id: str, *args, child: Element | None = None, **kwargs):
super().__init__(unique_id, *args, child=child, **kwargs)
self._child = child
[docs]
def get_name(self):
"""
Gets edge name
"""
return self.name
[docs]
class NonClockedEdge(Edge):
"""
Represents a non-clocked edge,
i.e. no clock cycle has to pass for the next node to be executed
"""
[docs]
def exclusions(self):
yield from self.child.exclusions()
def __repr__(self) -> str:
return "=>"
[docs]
class ClockedEdge(Edge):
"""
Represents a clocked edge,
i.e. a clock cycle has to pass for the next node to be executed
"""
[docs]
def exclusions(self):
yield from ()
def __repr__(self):
return "=/>"
[docs]
def create_networkx_adjacency_list(node: Element):
"""
Creates adjacency list from a node
Assumes names are unique
"""
adjacency_list = {}
def traverse_graph(curr_node: Element, visited: set[Element]):
if not curr_node:
return
nonlocal adjacency_list
if curr_node in visited:
return
visited.add(curr_node)
children = curr_node.children()
adjacency_list[curr_node] = children
for child in children:
traverse_graph(child, visited)
traverse_graph(node, set())
return adjacency_list
[docs]
def create_cytoscape_elements(node: Element):
"""
Creates adjacency list from a node
Assumes names are unique
"""
nodes = []
edges = []
def traverse_graph(curr_node: Element, visited: set[str]):
if not curr_node:
return
nonlocal nodes
if curr_node.unique_id in visited:
return
visited.add(curr_node.unique_id)
children = curr_node.children()
if not isinstance(curr_node, Edge):
nodes.append(
{
"data": {
"id": curr_node.unique_id,
"label": str(curr_node),
"class": str(curr_node.__class__.__name__),
}
}
)
for child in curr_node.children():
assert guard(child, BasicElement)
edges.append(
{
"data": {
"source": curr_node.unique_id,
"target": child.child.unique_id,
"class": str(child.__class__.__name__),
"label": str(child),
}
}
)
for child in children:
assert guard(child, BasicElement)
traverse_graph(child.child, visited)
traverse_graph(node, set())
return {"nodes": nodes, "edges": edges}