Documenting circuit module
This commit is contained in:
1 parent
cff18e0915
commit
5830608527
5 files changed
+169
-61
No files matched your search
+109
-52
@@ -14,6 +14,9 @@ class GrowingList(list):
|
||||
self.extend([None] * (index + 1 - len(self)))
|
||||
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):
|
||||
def __delitem__(self, index):
|
||||
@@ -26,7 +29,7 @@ class IndexList(list):
|
||||
|
||||
|
||||
class Node:
|
||||
"""A named entity in a circuit (e.g. a gate, a standard cell,
|
||||
"""A node is a named 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.
|
||||
|
||||
The constructor automatically adds the new node to the given circuit.
|
||||
@@ -46,17 +49,17 @@ class Node:
|
||||
"""The name of the node.
|
||||
|
||||
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
|
||||
"""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'.
|
||||
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.
|
||||
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.
|
||||
The :code:`index`, however, is unique among all nodes; a fork cannot have the same index 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 :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
|
||||
"""A unique and consecutive integer index of the node within the circuit.
|
||||
@@ -95,57 +98,67 @@ class Node:
|
||||
|
||||
|
||||
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`).
|
||||
It always connects an output of one `driver` node to an input of one `reader` node.
|
||||
If a signal fans out to multiple readers, a '__fork__' node needs to be added.
|
||||
|
||||
Furthermore, `self.driver_pin` and `self.reader_pin` are the
|
||||
integer indices of the connected pins of the nodes. They always correspond
|
||||
to the positions of the line in the connection lists of the nodes:
|
||||
The constructor automatically adds the new line to the given circuit and inserts references into the connection
|
||||
lists of connected nodes.
|
||||
|
||||
* `self.driver.outs[self.driver_pin] == self`
|
||||
* `self.reader.ins[self.reader_pin] == self`
|
||||
|
||||
A Line always connects a single driver to a single reader. If a signal fans out to
|
||||
multiple readers, a '__fork__' Node needs to be added.
|
||||
When adding a line, input and output pins can either be specified explicitly
|
||||
: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):
|
||||
self.index = len(circuit.lines)
|
||||
circuit.lines.append(self)
|
||||
if type(driver) is Node:
|
||||
self.driver = driver
|
||||
self.driver_pin = len(driver.outs)
|
||||
for pin, line in enumerate(driver.outs):
|
||||
if line is None:
|
||||
self.driver_pin = pin
|
||||
break
|
||||
else:
|
||||
self.driver, self.driver_pin = driver
|
||||
if type(reader) is Node:
|
||||
self.reader = reader
|
||||
self.reader_pin = len(reader.ins)
|
||||
for pin, line in enumerate(reader.ins):
|
||||
if line is None:
|
||||
self.reader_pin = pin
|
||||
break
|
||||
else:
|
||||
self.reader, self.reader_pin = reader
|
||||
self.circuit = circuit
|
||||
"""The :class:`Circuit` object the line is part of.
|
||||
"""
|
||||
self.circuit.lines.append(self)
|
||||
self.index = len(self.circuit.lines) - 1
|
||||
"""A unique and consecutive integer index of the line within the circuit.
|
||||
|
||||
It can be used to store additional data about the line :code:`l`
|
||||
by allocating an array or list :code:`my_data` of length :code:`len(l.circuit.lines)` and
|
||||
accessing it by :code:`my_data[l.index]`.
|
||||
"""
|
||||
if not isinstance(driver, tuple): driver = (driver, driver.outs.free_index())
|
||||
self.driver = driver[0]
|
||||
"""The :class:`Node` object that drives this line.
|
||||
"""
|
||||
self.driver_pin = driver[1]
|
||||
"""The output pin position of the driver node this line is connected to.
|
||||
|
||||
This is the position in the outs-list of the driving node this line referenced from:
|
||||
: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.reader.ins[self.reader_pin] = self
|
||||
|
||||
def remove(self):
|
||||
circuit = None
|
||||
if self.driver is not None:
|
||||
self.driver.outs[self.driver_pin] = None
|
||||
circuit = self.driver.circuit
|
||||
if self.reader is not None:
|
||||
self.reader.ins[self.reader_pin] = None
|
||||
circuit = self.reader.circuit
|
||||
if circuit is not None:
|
||||
del circuit.lines[self.index]
|
||||
"""Removes the line from its circuit and its referencing nodes.
|
||||
|
||||
To keep the indices consecutive, the line with the highest index within the circuit
|
||||
will be assigned the index of the removed line.
|
||||
"""
|
||||
if self.driver is not None: self.driver.outs[self.driver_pin] = None
|
||||
if self.reader is not None: self.reader.ins[self.reader_pin] = None
|
||||
if self.circuit is not None: del self.circuit.lines[self.index]
|
||||
self.driver = None
|
||||
self.reader = None
|
||||
self.circuit = None
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.index}'
|
||||
@@ -157,27 +170,53 @@ class Line:
|
||||
class Circuit:
|
||||
"""A Circuit is a container for interconnected nodes and lines.
|
||||
|
||||
All contained lines have unique indices, so have all contained nodes.
|
||||
These indices can be used to store additional data about nodes or lines
|
||||
by allocating an array `my_data` of length `len(self.nodes)` and then
|
||||
accessing it by `my_data[n.index]`. The indices may change iff lines or
|
||||
nodes are removed from the circuit.
|
||||
It provides access to lines by index and to nodes by index and by name.
|
||||
Nodes come in two flavors: `cells` and `forks` (see :py:attr:`Node.kind`).
|
||||
The name spaces of cells and forks are kept separate.
|
||||
|
||||
Nodes come in two flavors (cells and forks, see `Node`). The names of
|
||||
these nodes are kept unique within these two flavors.
|
||||
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.
|
||||
|
||||
A subset of nodes can be designated as primary input- or output-ports of the circuit.
|
||||
This is done by adding them to the :py:attr:`interface` list.
|
||||
"""
|
||||
def __init__(self, name=None):
|
||||
self.name = name
|
||||
"""The name of the circuit.
|
||||
"""
|
||||
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()
|
||||
"""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()
|
||||
"""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 = {}
|
||||
"""A dictionary to access cells by name.
|
||||
"""
|
||||
self.forks = {}
|
||||
"""A dictionary to access forks by name.
|
||||
"""
|
||||
|
||||
def get_or_add_fork(self, name):
|
||||
return self.forks[name] if name in self.forks else Node(self, name)
|
||||
|
||||
def copy(self):
|
||||
"""Returns a deep copy of the circuit.
|
||||
"""
|
||||
c = Circuit(self.name)
|
||||
for node in self.nodes:
|
||||
Node(c, node.name, node.kind)
|
||||
@@ -194,6 +233,8 @@ class Circuit:
|
||||
return c
|
||||
|
||||
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'
|
||||
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>'
|
||||
|
||||
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)
|
||||
queue = deque(n for n in self.nodes if len(n.ins) == 0 or 'DFF' in n.kind)
|
||||
while len(queue) > 0:
|
||||
@@ -215,12 +261,19 @@ class Circuit:
|
||||
yield n
|
||||
|
||||
def topological_line_order(self):
|
||||
"""Generator function to iterate over all lines in topological order.
|
||||
"""
|
||||
for n in self.topological_order():
|
||||
for line in n.outs:
|
||||
if line is not None:
|
||||
yield line
|
||||
|
||||
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)
|
||||
queue = deque(n for n in self.nodes if len(n.outs) == 0 or 'DFF' in n.kind)
|
||||
while len(queue) > 0:
|
||||
@@ -233,6 +286,10 @@ class Circuit:
|
||||
yield n
|
||||
|
||||
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)
|
||||
for n in origin_nodes:
|
||||
marks[n.index] = True
|
||||
|
||||
@@ -33,6 +33,8 @@ from collections.abc import Iterable
|
||||
import numpy as np
|
||||
|
||||
ZERO = 0b000
|
||||
"""Integer constant ``0b000`` for logic-0.
|
||||
"""
|
||||
UNASSIGNED = 0b001
|
||||
UNKNOWN = 0b010
|
||||
ONE = 0b011
|
||||
|
||||
Reference in new issue
Block a user