@ -14,6 +14,9 @@ class GrowingList(list):
self . extend ( [ None ] * ( index + 1 - len ( self ) ) )
self . extend ( [ None ] * ( index + 1 - len ( self ) ) )
super ( ) . __setitem__ ( index , value )
super ( ) . __setitem__ ( index , value )
def free_index ( self ) :
return next ( ( i for i , x in enumerate ( self ) if x is None ) , len ( self ) )
class IndexList ( list ) :
class IndexList ( list ) :
def __delitem__ ( self , index ) :
def __delitem__ ( self , index ) :
@ -26,7 +29,7 @@ class IndexList(list):
class Node :
class Node :
""" A named entity in a circuit (e.g. a gate, a standard cell,
""" A node is a n amed entity in a circuit (e.g. a gate, a standard cell,
a named signal , or a fan - out point ) that is connected to other nodes via lines .
a named signal , or a fan - out point ) that is connected to other nodes via lines .
The constructor automatically adds the new node to the given circuit .
The constructor automatically adds the new node to the given circuit .
@ -46,17 +49,17 @@ class Node:
""" The name of the node.
""" The name of the node.
Names must be unique among all forks and all cells in the circuit .
Names must be unique among all forks and all cells in the circuit .
However , a fork ( : code : ` kind == ' __fork__ ' ` ) and a cell with the same name may coexist .
However , a fork ( : py : attr : ` kind ` is set to ' __fork__ ' ) and a cell with the same name may coexist .
"""
"""
self . kind = kind
self . kind = kind
""" A string describing the type of the node.
""" A string describing the type of the node.
Common types are the names from a standard cell library or general gate names like ' AND ' or ' NOR ' .
Common types are the names from a standard cell library or general gate names like ' AND ' or ' NOR ' .
If : code : ` kind ` is set to ' __fork__ ' , it receives special treatment .
If : py : attr : ` kind ` is set to ' __fork__ ' , it receives special treatment .
A ` fork ` describes a named signal or a fan - out point in the circuit and not a physical ` cell ` like a gate .
A ` fork ` describes a named signal or a fan - out point in the circuit and not a physical ` cell ` like a gate .
In the circuit , the namespaces of forks and cells are kept separate .
In the circuit , the namespaces of forks and cells are kept separate .
While : code : ` name ` must be unique among all forks and all cells , a fork can have the same name as a cell .
While : py : attr : ` name ` must be unique among all forks and all cells , a fork can have the same name as a cell .
The : code : ` index ` , however , is unique among all nodes ; a fork cannot have the same index as a cell .
The : py : attr : ` index ` , however , is unique among all nodes ; a fork cannot have the same index as a cell .
"""
"""
self . index = len ( circuit . nodes ) - 1
self . index = len ( circuit . nodes ) - 1
""" A unique and consecutive integer index of the node within the circuit.
""" A unique and consecutive integer index of the node within the circuit.
@ -95,57 +98,67 @@ class Node:
class Line :
class Line :
""" A directional 1:1 connection between two nodes.
""" A line is a directional 1:1 connection between two nodes.
It always connects an output of a node ( called ` driver ` ) to an input of a node
( called ` reader ` ) and has a circuit - unique index ( ` self . index ` ) .
Furthermore , ` self . driver_pin ` and ` self . reader_pin ` are the
It always connects an output of one ` driver ` node to an input of one ` reader ` node .
integer indices of the connected pins of the nodes . They always correspond
If a signal fans out to multiple readers , a ' __fork__ ' node needs to be added .
to the positions of the line in the connection lists of the nodes :
* ` self . driver . outs [ self . driver_pin ] == self `
The constructor automatically adds the new line to the given circuit and inserts references into the connection
* ` self . reader . ins [ self . reader_pin ] == self `
lists of connected nodes .
A Line always connects a single driver to a single reader . If a signal fans out to
When adding a line , input and output pins can either be specified explicitly
multiple readers , a ' __fork__ ' Node needs to be added .
: code : ` Line ( circuit , ( driver , 2 ) , ( reader , 0 ) ) ` , or implicitly : code : ` Line ( circuit , driver , reader ) ` .
In the implicit case , the line will be connected to the first free pin of the node .
Use the explicit case only if connections to specific pins are required .
It may overwrite any previous line references in the connection list of the nodes .
"""
"""
def __init__ ( self , circuit , driver , reader ) :
def __init__ ( self , circuit , driver , reader ) :
self . index = len ( circuit . lines )
self . circuit = circuit
circuit . lines . append ( self )
""" The :class:`Circuit` object the line is part of.
if type ( driver ) is Node :
"""
self . driver = driver
self . circuit . lines . append ( self )
self . driver_pin = len ( driver . outs )
self . index = len ( self . circuit . lines ) - 1
for pin , line in enumerate ( driver . outs ) :
""" A unique and consecutive integer index of the line within the circuit.
if line is None :
self . driver_pin = pin
It can be used to store additional data about the line : code : ` l `
break
by allocating an array or list : code : ` my_data ` of length : code : ` len ( l . circuit . lines ) ` and
else :
accessing it by : code : ` my_data [ l . index ] ` .
self . driver , self . driver_pin = driver
"""
if type ( reader ) is Node :
if not isinstance ( driver , tuple ) : driver = ( driver , driver . outs . free_index ( ) )
self . reader = reader
self . driver = driver [ 0 ]
self . reader_pin = len ( reader . ins )
""" The :class:`Node` object that drives this line.
for pin , line in enumerate ( reader . ins ) :
"""
if line is None :
self . driver_pin = driver [ 1 ]
self . reader_pin = pin
""" The output pin position of the driver node this line is connected to.
break
else :
This is the position in the outs - list of the driving node this line referenced from :
self . reader , self . reader_pin = reader
: code : ` self . driver . outs [ self . driver_pin ] == self ` .
"""
if not isinstance ( reader , tuple ) : reader = ( reader , reader . ins . free_index ( ) )
self . reader = reader [ 0 ]
""" The :class:`Node` object that reads this line.
"""
self . reader_pin = reader [ 1 ]
""" The input pin position of the reader node this line is connected to.
This is the position in the ins - list of the reader node this line referenced from :
: code : ` self . reader . ins [ self . reader_pin ] == self ` .
"""
self . driver . outs [ self . driver_pin ] = self
self . driver . outs [ self . driver_pin ] = self
self . reader . ins [ self . reader_pin ] = self
self . reader . ins [ self . reader_pin ] = self
def remove ( self ) :
def remove ( self ) :
circuit = None
""" Removes the line from its circuit and its referencing nodes.
if self . driver is not None :
self . driver . outs [ self . driver_pin ] = None
To keep the indices consecutive , the line with the highest index within the circuit
circuit = self . driver . circuit
will be assigned the index of the removed line .
if self . reader is not None :
"""
self . reader . ins [ self . reader_pin ] = None
if self . driver is not None : self . driver . outs [ self . driver_pin ] = None
circuit = self . reader . circuit
if self . reader is not None : self . reader . ins [ self . reader_pin ] = None
if circuit is not None :
if self . circuit is not None : del self . circuit . lines [ self . index ]
del circuit . lines [ self . index ]
self . driver = None
self . driver = None
self . reader = None
self . reader = None
self . circuit = None
def __repr__ ( self ) :
def __repr__ ( self ) :
return f ' { self . index } '
return f ' { self . index } '
@ -157,27 +170,53 @@ class Line:
class Circuit :
class Circuit :
""" A Circuit is a container for interconnected nodes and lines.
""" A Circuit is a container for interconnected nodes and lines.
All contained lines have unique indices , so have all contained nodes .
It provides access to lines by index and to nodes by index and by name .
These indices can be used to store additional data about nodes or lines
Nodes come in two flavors : ` cells ` and ` forks ` ( see : py : attr : ` Node . kind ` ) .
by allocating an array ` my_data ` of length ` len ( self . nodes ) ` and then
The name spaces of cells and forks are kept separate .
accessing it by ` my_data [ n . index ] ` . The indices may change iff lines or
nodes are removed from the circuit .
The indices of nodes and lines are kept consecutive and unique .
Whenever lines or nodes are removed from the circuit , the indices of some other lines or nodes may change
to enforce consecutiveness .
Nodes come in two flavors ( cells and forks , see ` Node ` ) . The names of
A subset of nodes can be designated as primary input - or output - ports of the circuit .
these nodes are kept unique within these two flavors .
This is done by adding them to the : py : attr : ` interface ` list .
"""
"""
def __init__ ( self , name = None ) :
def __init__ ( self , name = None ) :
self . name = name
self . name = name
""" The name of the circuit.
"""
self . nodes = IndexList ( )
self . nodes = IndexList ( )
""" A list of all :class:`Node` objects contained in the circuit.
The position of a node in this list equals its index : code : ` self . nodes [ 42 ] . index == 42 ` .
"""
self . lines = IndexList ( )
self . lines = IndexList ( )
""" A list of all :class:`Line` objects contained in the circuit.
The position of a line in this list equals its index : code : ` self . lines [ 42 ] . index == 42 ` .
"""
self . interface = GrowingList ( )
self . interface = GrowingList ( )
""" A list of nodes that are designated as primary input- or output-ports.
Port - nodes are contained in : py : attr : ` nodes ` as well as : py : attr : ` interface ` .
The position of a node in the interface list corresponds to positions of logic values in test vectors .
The port direction is not stored explicitly .
Usually , nodes in the interface list without any lines in their : py : attr : ` Node . ins ` list are primary inputs ,
and nodes without any lines in their : py : attr : ` Node . outs ` list are regarded as primary outputs .
"""
self . cells = { }
self . cells = { }
""" A dictionary to access cells by name.
"""
self . forks = { }
self . forks = { }
""" A dictionary to access forks by name.
"""
def get_or_add_fork ( self , name ) :
def get_or_add_fork ( self , name ) :
return self . forks [ name ] if name in self . forks else Node ( self , name )
return self . forks [ name ] if name in self . forks else Node ( self , name )
def copy ( self ) :
def copy ( self ) :
""" Returns a deep copy of the circuit.
"""
c = Circuit ( self . name )
c = Circuit ( self . name )
for node in self . nodes :
for node in self . nodes :
Node ( c , node . name , node . kind )
Node ( c , node . name , node . kind )
@ -194,6 +233,8 @@ class Circuit:
return c
return c
def dump ( self ) :
def dump ( self ) :
""" Returns a string representation of the circuit and all its nodes.
"""
header = f ' { self . name } ( { " , " . join ( [ str ( n . index ) for n in self . interface ] ) } ) \n '
header = f ' { self . name } ( { " , " . join ( [ str ( n . index ) for n in self . interface ] ) } ) \n '
return header + ' \n ' . join ( [ str ( n ) for n in self . nodes ] )
return header + ' \n ' . join ( [ str ( n ) for n in self . nodes ] )
@ -202,6 +243,11 @@ class Circuit:
return f ' <Circuit { name } with { len ( self . nodes ) } nodes, { len ( self . lines ) } lines, { len ( self . interface ) } ports> '
return f ' <Circuit { name } with { len ( self . nodes ) } nodes, { len ( self . lines ) } lines, { len ( self . interface ) } ports> '
def topological_order ( self ) :
def topological_order ( self ) :
""" Generator function to iterate over all nodes in topological order.
Nodes without input lines and nodes whose : py : attr : ` Node . kind ` contains the substring ' DFF ' are
yielded first .
"""
visit_count = [ 0 ] * len ( self . nodes )
visit_count = [ 0 ] * len ( self . nodes )
queue = deque ( n for n in self . nodes if len ( n . ins ) == 0 or ' DFF ' in n . kind )
queue = deque ( n for n in self . nodes if len ( n . ins ) == 0 or ' DFF ' in n . kind )
while len ( queue ) > 0 :
while len ( queue ) > 0 :
@ -215,12 +261,19 @@ class Circuit:
yield n
yield n
def topological_line_order ( self ) :
def topological_line_order ( self ) :
""" Generator function to iterate over all lines in topological order.
"""
for n in self . topological_order ( ) :
for n in self . topological_order ( ) :
for line in n . outs :
for line in n . outs :
if line is not None :
if line is not None :
yield line
yield line
def reversed_topological_order ( self ) :
def reversed_topological_order ( self ) :
""" Generator function to iterate over all nodes in reversed topological order.
Nodes without output lines and nodes whose : py : attr : ` Node . kind ` contains the substring ' DFF ' are
yielded first .
"""
visit_count = [ 0 ] * len ( self . nodes )
visit_count = [ 0 ] * len ( self . nodes )
queue = deque ( n for n in self . nodes if len ( n . outs ) == 0 or ' DFF ' in n . kind )
queue = deque ( n for n in self . nodes if len ( n . outs ) == 0 or ' DFF ' in n . kind )
while len ( queue ) > 0 :
while len ( queue ) > 0 :
@ -233,6 +286,10 @@ class Circuit:
yield n
yield n
def fanin ( self , origin_nodes ) :
def fanin ( self , origin_nodes ) :
""" Generator function to iterate over the fan-in cone of a given list of origin nodes.
Nodes are yielded in reversed topological order .
"""
marks = [ False ] * len ( self . nodes )
marks = [ False ] * len ( self . nodes )
for n in origin_nodes :
for n in origin_nodes :
marks [ n . index ] = True
marks [ n . index ] = True