Browse Source

Documenting circuit module

devel
Stefan Holst 4 years ago
parent
commit
5830608527
  1. 4
      README.rst
  2. 18
      docs/datastructures.rst
  3. 161
      src/kyupy/circuit.py
  4. 2
      src/kyupy/logic.py
  5. 45
      tests/test_circuit.py

4
README.rst

@ -16,8 +16,8 @@ Getting Started
--------------- ---------------
KyuPy is available in `PyPI <https://pypi.org/project/kyupy>`_. KyuPy is available in `PyPI <https://pypi.org/project/kyupy>`_.
It requires Python 3.6 or newer, `lark-parser <https://pypi.org/project/lark-parser>`_, and `numpy <https://pypi.org/project/numpy>`_. It requires Python 3.6 or newer, `lark-parser <https://pypi.org/project/lark-parser>`_, and `numpy`_.
Although optional, `numba <https://pypi.org/project/numba>`_ should be installed for best performance. Although optional, `numba`_ should be installed for best performance.
GPU/CUDA support in numba may `require some additional setup <https://numba.pydata.org/numba-doc/latest/cuda/index.html>`_. GPU/CUDA support in numba may `require some additional setup <https://numba.pydata.org/numba-doc/latest/cuda/index.html>`_.
If numba is not available, KyuPy will automatically fall back to slow, pure Python execution. If numba is not available, KyuPy will automatically fall back to slow, pure Python execution.

18
docs/datastructures.rst

@ -1,26 +1,30 @@
Core Data Structures Data Structures
==================== ===============
KyuPy provides two types of core data structures, one for gate-level circuits, and a few others for representing and storing logic data and signal values. KyuPy provides two types of core data structures, one for gate-level circuits, and a few others for representing and storing logic data and signal values.
The data structures are designed to work together nicely with numpy arrays. The data structures are designed to work together nicely with numpy arrays.
For example, all the nodes and connections in the circuit graph have numerical indices that can be used to access ndarrays with associated data. For example, all the nodes and connections in the circuit graph have consecutive integer indices that can be used to access ndarrays with associated data.
Circuit graphs also define an ordering of inputs, outputs and other nodes to easily process test vector data and alike. Circuit graphs also define an ordering of inputs, outputs and other nodes to easily process test vector data and alike.
Module :mod:`kyupy.circuit` Circuit Graph - :mod:`kyupy.circuit`
--------------------------- ------------------------------------
.. automodule:: kyupy.circuit .. automodule:: kyupy.circuit
.. autoclass:: kyupy.circuit.Node .. autoclass:: kyupy.circuit.Node
:members: :members:
.. autoclass:: kyupy.circuit.Line
:members:
.. autoclass:: kyupy.circuit.Circuit .. autoclass:: kyupy.circuit.Circuit
:members: :members:
Logic Values and Arrays M-Valued Logic - :mod:`kyupy.logic`
----------------------- -----------------------------------
.. automodule:: kyupy.logic .. automodule:: kyupy.logic
:members:
.. autoclass:: kyupy.logic.MVArray .. autoclass:: kyupy.logic.MVArray
:members: :members:

161
src/kyupy/circuit.py

@ -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 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. 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.
Nodes come in two flavors (cells and forks, see `Node`). The names of The indices of nodes and lines are kept consecutive and unique.
these nodes are kept unique within these two flavors. 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): 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

2
src/kyupy/logic.py

@ -33,6 +33,8 @@ from collections.abc import Iterable
import numpy as np import numpy as np
ZERO = 0b000 ZERO = 0b000
"""Integer constant ``0b000`` for logic-0.
"""
UNASSIGNED = 0b001 UNASSIGNED = 0b001
UNKNOWN = 0b010 UNKNOWN = 0b010
ONE = 0b011 ONE = 0b011

45
tests/test_circuit.py

@ -1,6 +1,51 @@
from kyupy.circuit import Circuit, Node, Line from kyupy.circuit import Circuit, Node, Line
def test_lines():
c = Circuit()
n1 = Node(c, 'n1')
n2 = Node(c, 'n2')
line = Line(c, n1, n2)
assert line.driver == n1
assert line.reader == n2
assert line.driver_pin == 0
assert line.reader_pin == 0
assert n1.outs[0] == line
assert n2.ins[0] == line
line2 = Line(c, n1, (n2, 2))
assert line2.driver == n1
assert line2.reader == n2
assert line2.driver_pin == 1
assert line2.reader_pin == 2
assert n1.outs[0] == line
assert n1.outs[1] == line2
assert n2.ins[1] is None
assert n2.ins[2] == line2
line3 = Line(c, n1, n2)
assert line3.driver_pin == 2
assert line3.reader_pin == 1
assert n1.outs[2] == line3
assert n2.ins[1] == line3
assert n2.ins[2] == line2
assert len(c.lines) == 3
line3.remove()
assert len(c.lines) == 2
assert c.lines[0].index == 0
assert c.lines[1].index == 1
assert n1.outs[2] is None
assert n2.ins[1] is None
assert n2.ins[2] == line2
def test_circuit(): def test_circuit():
c = Circuit() c = Circuit()
in1 = Node(c, 'in1', 'buf') in1 = Node(c, 'in1', 'buf')

Loading…
Cancel
Save