Compare commits
2
Commits
7a69ce9016
...
v0.0.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3193df726f | ||
|
|
d247115744 |
No files matched your search
+8
-6
@@ -1,12 +1,14 @@
|
||||
__pycache__
|
||||
__pycache__/
|
||||
.ipynb_checkpoints
|
||||
.pytest_cache
|
||||
.DS_Store
|
||||
*.pyc
|
||||
docs/_build
|
||||
build
|
||||
dist
|
||||
.idea
|
||||
.vscode
|
||||
src/kyupy.egg-info
|
||||
.venv
|
||||
*.py[oc]
|
||||
docs/_build
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
*nogit*
|
||||
@@ -0,0 +1 @@
|
||||
3.13
|
||||
+2
-2
@@ -3,10 +3,10 @@ version: 2
|
||||
build:
|
||||
os: "ubuntu-20.04"
|
||||
tools:
|
||||
python: "3.8"
|
||||
python: "3.12"
|
||||
jobs:
|
||||
post_create_environment:
|
||||
- python -m pip install sphinx_rtd_theme
|
||||
- python -m pip install sphinx_rtd_theme myst-parser
|
||||
|
||||
sphinx:
|
||||
fail_on_warning: true
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2023 Stefan Holst
|
||||
Copyright (c) 2020-2025 Stefan Holst
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
KyuPy - Pythonic Processing of VLSI Circuits
|
||||
============================================
|
||||
|
||||
KyuPy is a Python package for processing and analysis of non-hierarchical gate-level VLSI designs.
|
||||
It contains fundamental building blocks for research software in the fields of VLSI test, diagnosis and reliability:
|
||||
|
||||
* Efficient data structures for gate-level circuits and related design data.
|
||||
* Partial [lark](https://github.com/lark-parser/lark) parsers for common design files like
|
||||
bench, gate-level Verilog, standard delay format (SDF), standard test interface language (STIL), design exchange format (DEF).
|
||||
* Bit-parallel gate-level 2-, 4-, and 8-valued logic simulation.
|
||||
* GPU-accelerated high-throughput gate-level timing simulation.
|
||||
* High-performance through the use of [numpy](https://numpy.org) and [numba](https://numba.pydata.org).
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
KyuPy is available in [PyPI](https://pypi.org/project/kyupy).
|
||||
It requires Python 3.10 or newer, [lark](https://pypi.org/project/lark), and [numpy](https://numpy.org).
|
||||
Although optional, [numba](https://numba.pydata.org) should be installed for best performance. [numba-cuda](https://nvidia.github.io/numba-cuda/) is required for GPU/CUDA acceleration.
|
||||
If numba is not available, KyuPy will automatically fall back to slow, pure Python execution.
|
||||
|
||||
The Jupyter Notebook [Introduction.ipynb](https://github.com/s-holst/kyupy/blob/main/examples/Introduction.ipynb) contains some useful examples to get familiar with the API.
|
||||
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
To work with the latest pre-release source code, clone the [KyuPy GitHub repository](https://github.com/s-holst/kyupy).
|
||||
|
||||
* Using ``pip``: Run ``pip install -e .`` within your local checkout to make the package available in your Python environment. The source code comes with tests that can be run with ``pytest``.
|
||||
* Using ``uv``: Run ``uv run pytest`` within your local checkout to get started.
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
KyuPy - Pythonic Processing of VLSI Circuits
|
||||
============================================
|
||||
|
||||
KyuPy is a Python package for processing and analysis of non-hierarchical gate-level VLSI designs.
|
||||
It contains fundamental building blocks for research software in the fields of VLSI test, diagnosis and reliability:
|
||||
|
||||
* Efficient data structures for gate-level circuits and related design data.
|
||||
* Partial `lark <https://github.com/lark-parser/lark>`_ parsers for common design files like
|
||||
bench, gate-level Verilog, standard delay format (SDF), standard test interface language (STIL), design exchange format (DEF).
|
||||
* Bit-parallel gate-level 2-, 4-, and 8-valued logic simulation.
|
||||
* GPU-accelerated high-throughput gate-level timing simulation.
|
||||
* High-performance through the use of `numpy <https://numpy.org>`_ and `numba <https://numba.pydata.org>`_.
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
KyuPy is available in `PyPI <https://pypi.org/project/kyupy>`_.
|
||||
It requires Python 3.8 or newer, `lark-parser <https://pypi.org/project/lark-parser>`_, and `numpy`_.
|
||||
Although optional, `numba`_ should be installed for best performance.
|
||||
GPU/CUDA support in numba may `require some additional setup <https://numba.readthedocs.io/en/stable/cuda/index.html>`_.
|
||||
If numba is not available, KyuPy will automatically fall back to slow, pure Python execution.
|
||||
|
||||
The Jupyter Notebook `Introduction.ipynb <https://github.com/s-holst/kyupy/blob/main/examples/Introduction.ipynb>`_ contains some useful examples to get familiar with the API.
|
||||
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
To work with the latest pre-release source code, clone the `KyuPy GitHub repository <https://github.com/s-holst/kyupy>`_.
|
||||
Run ``pip install -e .`` within your local checkout to make the package available in your Python environment.
|
||||
The source code comes with tests that can be run with ``pytest``.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# pip install sphinx sphinx-rtd-theme
|
||||
# pip install sphinx sphinx-rtd-theme myst-parser
|
||||
#
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
+3
-2
@@ -20,11 +20,11 @@ sys.path.insert(0, os.path.abspath('../src'))
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'KyuPy'
|
||||
copyright = '2020-2023, Stefan Holst'
|
||||
copyright = '2020-2025, Stefan Holst'
|
||||
author = 'Stefan Holst'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.0.5'
|
||||
release = '0.0.6'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
@@ -35,6 +35,7 @@ release = '0.0.5'
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx_rtd_theme',
|
||||
'myst_parser',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
.. include:: ../README.rst
|
||||
.. include:: ../README.md
|
||||
:parser: myst_parser.sphinx_
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
@@ -14,6 +14,15 @@ Logic Simulation - :mod:`kyupy.logic_sim`
|
||||
.. autoclass:: kyupy.logic_sim.LogicSim
|
||||
:members:
|
||||
|
||||
.. autoclass:: kyupy.logic_sim.LogicSim2V
|
||||
:members:
|
||||
|
||||
.. autoclass:: kyupy.logic_sim.LogicSim4V
|
||||
:members:
|
||||
|
||||
.. autoclass:: kyupy.logic_sim.LogicSim6V
|
||||
:members:
|
||||
|
||||
|
||||
Timing Simulation - :mod:`kyupy.wave_sim`
|
||||
-----------------------------------------
|
||||
|
||||
+964
-784
File diff suppressed because it is too large.
Load diff
+12
-4
@@ -1,15 +1,15 @@
|
||||
[project]
|
||||
name = "kyupy"
|
||||
version = "0.0.5"
|
||||
version = "0.0.6"
|
||||
authors = [
|
||||
{ name="Stefan Holst", email="mail@s-holst.de" },
|
||||
]
|
||||
description = 'High-performance processing and analysis of non-hierarchical VLSI designs'
|
||||
readme = "README.rst"
|
||||
requires_python = ">=3.8"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12,<3.14"
|
||||
dependencies = [
|
||||
"numpy>=1.17.0",
|
||||
"lark-parser>=0.8.0",
|
||||
"lark>=1.3.0",
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
@@ -30,3 +30,11 @@ homepage = "https://github.com/s-holst/kyupy"
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"myst-parser>=4.0.1",
|
||||
"pytest>=9.0.1",
|
||||
"sphinx>=8.1.3",
|
||||
"sphinx-rtd-theme>=3.0.2",
|
||||
]
|
||||
|
||||
@@ -293,8 +293,6 @@ if importlib.util.find_spec('numba') is not None:
|
||||
try:
|
||||
list(numba.cuda.gpus)
|
||||
from numba import cuda
|
||||
from numba.core import config
|
||||
config.CUDA_LOW_OCCUPANCY_WARNINGS = False
|
||||
except CudaSupportError:
|
||||
log.warn('Cuda unavailable. Falling back to pure Python.')
|
||||
cuda = MockCuda()
|
||||
|
||||
+36
-6
@@ -10,7 +10,11 @@ Besides loading these benchmarks, this module is also useful for easily construc
|
||||
from lark import Lark, Transformer
|
||||
|
||||
from .circuit import Circuit, Node, Line
|
||||
from . import readtext
|
||||
from . import readtext, batchrange
|
||||
|
||||
def treeify(l, max_degree=4):
|
||||
if len(l) <= max_degree: return l
|
||||
return treeify([l[bo:bo+bs] for bo, bs in batchrange(len(l), max_degree)])
|
||||
|
||||
|
||||
class BenchTransformer(Transformer):
|
||||
@@ -21,13 +25,39 @@ class BenchTransformer(Transformer):
|
||||
|
||||
def start(self, _): return self.c
|
||||
|
||||
def parameters(self, args): return [self.c.get_or_add_fork(str(name)) for name in args]
|
||||
def parameters(self, args): return [self.c.get_or_add_fork(str(name)) for name in args if name is not None]
|
||||
|
||||
def interface(self, args): self.c.io_nodes.extend(args[0])
|
||||
|
||||
def _cell_tree_inner(self, name, kind, inner_kind, drivers):
|
||||
cell = Node(self.c, name, f'{kind}{len(drivers)}')
|
||||
fork = self.c.get_or_add_fork(name)
|
||||
Line(self.c, cell, fork)
|
||||
for i, d in enumerate(drivers):
|
||||
while isinstance(d, list) and len(d) == 1: d = d[0]
|
||||
if isinstance(d, list):
|
||||
d = self._cell_tree_inner(f'{name}~{i}', inner_kind, inner_kind, d)
|
||||
Line(self.c, d, cell)
|
||||
return fork
|
||||
|
||||
def cell_tree(self, name, kind, drivers):
|
||||
root_kind = kind.upper()
|
||||
inner_kind = root_kind
|
||||
if root_kind == 'NAND': inner_kind = 'AND'
|
||||
if root_kind == 'NOR': inner_kind = 'OR'
|
||||
if root_kind == 'XNOR': inner_kind = 'XOR'
|
||||
return self._cell_tree_inner(name, root_kind, inner_kind, treeify(drivers))
|
||||
|
||||
def assignment(self, args):
|
||||
name, cell_type, drivers = args
|
||||
cell = Node(self.c, str(name), str(cell_type))
|
||||
name, kind, drivers = args
|
||||
if kind.upper() in ('AND', 'NAND', 'OR', 'NOR', 'XOR', 'XNOR'):
|
||||
self.cell_tree(name, kind, drivers)
|
||||
return
|
||||
if kind.upper().startswith('BUF'):
|
||||
kind = 'BUF1'
|
||||
elif kind.upper().startswith('INV') or kind.upper().startswith('NOT'):
|
||||
kind = 'INV1'
|
||||
cell = Node(self.c, str(name), str(kind))
|
||||
Line(self.c, cell, self.c.get_or_add_fork(str(name)))
|
||||
for d in drivers: Line(self.c, d, cell)
|
||||
|
||||
@@ -44,14 +74,14 @@ GRAMMAR = r"""
|
||||
"""
|
||||
|
||||
|
||||
def parse(text, name=None):
|
||||
def parse(text, name=None) -> Circuit:
|
||||
"""Parses the given ``text`` as ISCAS89 bench code.
|
||||
|
||||
:param text: A string with bench code.
|
||||
:param name: The name of the circuit. Circuit names are not included in bench descriptions.
|
||||
:return: A :class:`Circuit` object.
|
||||
"""
|
||||
return Lark(GRAMMAR, parser="lalr", transformer=BenchTransformer(name)).parse(text)
|
||||
return Lark(GRAMMAR, parser="lalr", transformer=BenchTransformer(name)).parse(text) # type: ignore
|
||||
|
||||
|
||||
def load(file, name=None):
|
||||
|
||||
+35
-29
@@ -14,12 +14,12 @@ from __future__ import annotations
|
||||
|
||||
from collections import deque, defaultdict
|
||||
import re
|
||||
from typing import Union
|
||||
from typing import Union, Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
class GrowingList(list):
|
||||
class GrowingList[T](list[T]):
|
||||
def __setitem__(self, index, value):
|
||||
if value is None: self.has_nones = True
|
||||
if index == len(self): return super().append(value)
|
||||
@@ -28,9 +28,13 @@ class GrowingList(list):
|
||||
self.has_nones = True
|
||||
super().__setitem__(index, value)
|
||||
|
||||
def __getitem__(self, index):
|
||||
if isinstance(index, slice): return super().__getitem__(index)
|
||||
return super().__getitem__(index) if index < len(self) else None
|
||||
# Override __getitem__ to return None when reading beyond the list
|
||||
# instead of throwing an exception. Type checker complains about the None return
|
||||
# type, though. Probably not needed anyways.
|
||||
|
||||
#def __getitem__(self, index) -> list[T] | T | None:
|
||||
# if isinstance(index, slice): return super().__getitem__(index)
|
||||
# return super().__getitem__(index) if index < len(self) else None
|
||||
|
||||
@property
|
||||
def free_idx(self):
|
||||
@@ -134,10 +138,10 @@ class Node:
|
||||
|
||||
This is ok, because (name, kind) is unique within a circuit.
|
||||
"""
|
||||
return self.name == other.name and self.kind == other.kind
|
||||
return self.name == other.name and self.kind == other.kind and self.circuit == other.circuit
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.name, self.kind))
|
||||
return hash((self.name, self.kind, id(self.circuit)))
|
||||
|
||||
|
||||
class Line:
|
||||
@@ -155,7 +159,7 @@ class Line:
|
||||
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: Circuit, driver: Union[Node, tuple[Node, int]], reader: Union[Node, tuple[Node, int]]):
|
||||
def __init__(self, circuit: Circuit, driver: Node | tuple[Node, None|int], reader: Node | tuple[Node, None|int]):
|
||||
self.circuit = circuit
|
||||
"""The :class:`Circuit` object the line is part of.
|
||||
"""
|
||||
@@ -168,20 +172,20 @@ class Line:
|
||||
accessing it by :code:`my_data[l.index]` or simply by :code:`my_data[l]`.
|
||||
"""
|
||||
if not isinstance(driver, tuple): driver = (driver, driver.outs.free_idx)
|
||||
self.driver = driver[0]
|
||||
self.driver: Node = driver[0]
|
||||
"""The :class:`Node` object that drives this line.
|
||||
"""
|
||||
self.driver_pin = driver[1]
|
||||
self.driver_pin = driver[1] if driver[1] is not None else self.driver.outs.free_idx
|
||||
"""The output pin position of the driver node this line is connected to.
|
||||
|
||||
This is the position in the list :py:attr:`Node.outs` 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_idx)
|
||||
self.reader = reader[0]
|
||||
self.reader: Node = reader[0]
|
||||
"""The :class:`Node` object that reads this line.
|
||||
"""
|
||||
self.reader_pin = reader[1]
|
||||
self.reader_pin = reader[1] if reader[1] is not None else self.reader.ins.free_idx
|
||||
"""The input pin position of the reader node this line is connected to.
|
||||
|
||||
This is the position in the list :py:attr:`Node.ins` of the reader node this line referenced from:
|
||||
@@ -203,8 +207,6 @@ class Line:
|
||||
for i, l in enumerate(self.driver.outs): l.driver_pin = i
|
||||
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 __index__(self):
|
||||
@@ -309,7 +311,7 @@ class Circuit:
|
||||
"""
|
||||
return self._locs(prefix, self.s_nodes)
|
||||
|
||||
def _locs(self, prefix, nodes):
|
||||
def _locs(self, prefix, nodes:list[Node]) -> Node|list[Any]: # can return list[list[...]]
|
||||
d_top = dict()
|
||||
for i, n in enumerate(nodes):
|
||||
if m := re.match(fr'({re.escape(prefix)}.*?)((?:[\d_\[\]])*$)', n.name):
|
||||
@@ -324,7 +326,7 @@ class Circuit:
|
||||
def sorted_values(d): return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d
|
||||
l = sorted_values(d_top)
|
||||
while isinstance(l, list) and len(l) == 1: l = l[0]
|
||||
return None if isinstance(l, list) and len(l) == 0 else l
|
||||
return l #None if isinstance(l, list) and len(l) == 0 else l
|
||||
|
||||
@property
|
||||
def stats(self):
|
||||
@@ -539,9 +541,6 @@ class Circuit:
|
||||
for n in state['io_nodes']:
|
||||
self.io_nodes.append(self.nodes[n])
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.nodes == other.nodes and self.lines == other.lines and self.io_nodes == other.io_nodes
|
||||
|
||||
def __repr__(self):
|
||||
return f'{{name: "{self.name}", cells: {len(self.cells)}, forks: {len(self.forks)}, lines: {len(self.lines)}, io_nodes: {len(self.io_nodes)}}}'
|
||||
|
||||
@@ -651,9 +650,9 @@ class Circuit:
|
||||
region.append(n)
|
||||
yield stem, region
|
||||
|
||||
def dot(self, format='svg'):
|
||||
def dot(self, format='svg', graph_attr={}, line_labels={}):
|
||||
from graphviz import Digraph
|
||||
dot = Digraph(format=format, graph_attr={'rankdir': 'LR', 'splines': 'true'})
|
||||
dot = Digraph(format=format, graph_attr={'rankdir': 'LR', 'splines': 'true', 'size': '10', 'ranksep': '0.1'} | graph_attr)
|
||||
|
||||
s_dict = dict((n, i) for i, n in enumerate(self.s_nodes))
|
||||
node_level = np.zeros(len(self.nodes), dtype=np.uint32)
|
||||
@@ -666,17 +665,24 @@ class Circuit:
|
||||
with dot.subgraph() as s:
|
||||
s.attr(rank='same')
|
||||
for n in level_nodes[lv]:
|
||||
ins = '|'.join([f'<i{i}>{i}' for i in range(len(n.ins))])
|
||||
outs = '|'.join([f'<o{i}>{i}' for i in range(len(n.outs))])
|
||||
ins = '{' + '|'.join([f'<i{i}>{i}' for i in range(len(n.ins))]) + '}|' if len(n.ins) > 1 else ''
|
||||
outs = '|{' + '|'.join([f'<o{i}>{i}' for i in range(len(n.outs))]) + '}' if len(n.outs) > 1 else ''
|
||||
io = f' [{s_dict[n]}]' if n in s_dict else ''
|
||||
s.node(name=str(n.index), label = f'{{{{{ins}}}|{n.index}{io}\n{n.kind}\n{n.name}|{{{outs}}}}}', shape='record')
|
||||
color = '#f5f5f5' if n.kind == '__fork__' else '#cccccc'
|
||||
kind = '' if n.kind == '__fork__' else fr'\n{n.kind}'
|
||||
s.node(name=str(n.index), label = fr'{{{ins}{n.index}{io}{kind}\n{n.name}{outs}}}', shape='record', style='filled', fillcolor=color)
|
||||
|
||||
for l in self.lines:
|
||||
driver, reader = f'{l.driver.index}:o{l.driver_pin}', f'{l.reader.index}:i{l.reader_pin}'
|
||||
if node_level[l.driver] >= node_level[l.reader]:
|
||||
dot.edge(driver, reader, style='dotted', label=str(l.index))
|
||||
pass
|
||||
driver = f'{l.driver.index}:o{l.driver_pin}' if len(l.driver.outs)>1 else f'{l.driver.index}'
|
||||
reader = f'{l.reader.index}:i{l.reader_pin}' if len(l.reader.ins)>1 else f'{l.reader.index}'
|
||||
label = str(line_labels.get(l, l.index))
|
||||
if node_level[l.driver] == node_level[l.reader]:
|
||||
s.node(f'_{l.index}_')
|
||||
dot.edge(driver, f'_{l.index}_', style='dotted', label=label)
|
||||
dot.edge(f'_{l.index}_', reader, style='dotted', label=label)
|
||||
elif node_level[l.driver] > node_level[l.reader]:
|
||||
dot.edge(driver, reader, style='dotted', label=label)
|
||||
else:
|
||||
dot.edge(driver, reader, label=str(l.index))
|
||||
dot.edge(driver, reader, label=label)
|
||||
|
||||
return dot
|
||||
+3
-2
@@ -47,6 +47,7 @@ The functions in this module use the ``mv...`` and ``bp...`` prefixes to signify
|
||||
from collections.abc import Iterable
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import DTypeLike
|
||||
|
||||
from . import numba, hr_bytes
|
||||
|
||||
@@ -114,7 +115,7 @@ def mvarray(*a):
|
||||
def mv_str(mva, delim='\n'):
|
||||
"""Renders a given multi-valued array into a string.
|
||||
"""
|
||||
sa = np.choose(mva, np.array([*'0X-1PRFN'], dtype=np.unicode_))
|
||||
sa = np.choose(mva, np.array([*'0X-1PRFN'], dtype=np.str_))
|
||||
if not hasattr(mva, 'ndim') or mva.ndim == 0: return sa
|
||||
if mva.ndim == 1: return ''.join(sa)
|
||||
return delim.join([''.join(c) for c in sa.swapaxes(-1,-2)])
|
||||
@@ -433,7 +434,7 @@ def unpackbits(a : np.ndarray):
|
||||
return np.unpackbits(a.view(np.uint8), bitorder='little').reshape(*a.shape, 8*a.itemsize)
|
||||
|
||||
|
||||
def packbits(a, dtype=np.uint8):
|
||||
def packbits(a, dtype:DTypeLike=np.uint8):
|
||||
"""Packs the values of a boolean-valued array ``a`` along its last axis into bits.
|
||||
|
||||
Similar to ``np.packbits``, but returns an array of given dtype and the shape of ``a`` with the last axis removed.
|
||||
|
||||
+507
-18
@@ -10,8 +10,8 @@ import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import numba, logic, hr_bytes, sim, eng, cdiv
|
||||
from .circuit import Circuit
|
||||
from . import numba, logic, hr_bytes, sim, eng, cdiv, batchrange
|
||||
from .circuit import Circuit, Line
|
||||
|
||||
|
||||
class LogicSim(sim.SimOps):
|
||||
@@ -43,6 +43,7 @@ class LogicSim(sim.SimOps):
|
||||
Access this array to assign new values to the (P)PIs or read values from the (P)POs.
|
||||
"""
|
||||
self.s[:,:,1,:] = 255 # unassigned
|
||||
self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
|
||||
|
||||
def __repr__(self):
|
||||
return f'{{name: "{self.circuit.name}", sims: {self.sims}, m: {self.m}, c_bytes: {eng(self.c.nbytes)}}}'
|
||||
@@ -52,7 +53,7 @@ class LogicSim(sim.SimOps):
|
||||
"""
|
||||
self.c[self.pippi_c_locs] = self.s[0, self.pippi_s_locs, :self.mdim]
|
||||
|
||||
def c_prop(self, sims=None, inject_cb=None, fault_line=-1, fault_mask=None, fault_model=2):
|
||||
def c_prop(self, sims=None, inject_cb=None, fault_line:Line|int=-1, fault_mask=None, fault_model=2):
|
||||
"""Propagate the input values through the combinational circuit towards the outputs.
|
||||
|
||||
Performs all logic operations in topological order.
|
||||
@@ -61,21 +62,23 @@ class LogicSim(sim.SimOps):
|
||||
:param inject_cb: A callback function for manipulating intermediate signal values.
|
||||
This function is called with a line and its new logic values (in bit-parallel format) after
|
||||
evaluation of a node. The callback may manipulate the given values in-place, the simulation
|
||||
resumes with the manipulated values after the callback returns.
|
||||
resumes with the manipulated values after the callback returns. Specifying this callback
|
||||
may reduce performance as it disables jit compilation.
|
||||
:type inject_cb: ``f(Line, ndarray)``
|
||||
"""
|
||||
fault_line = int(fault_line)
|
||||
if fault_mask is None:
|
||||
fault_mask = self._full_mask # default: full mask
|
||||
else:
|
||||
if len(fault_mask) < self.c.shape[-1]: # pad mask with 0's if necessary
|
||||
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
|
||||
fault_mask2[:len(fault_mask)] = fault_mask
|
||||
fault_mask = fault_mask2
|
||||
t0 = self.c_locs[self.tmp_idx]
|
||||
t1 = self.c_locs[self.tmp2_idx]
|
||||
if self.m == 2:
|
||||
if inject_cb is None:
|
||||
if fault_mask is None:
|
||||
fault_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
|
||||
else:
|
||||
if len(fault_mask) < self.c.shape[-1]:
|
||||
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
|
||||
fault_mask2[:len(fault_mask)] = fault_mask
|
||||
fault_mask = fault_mask2
|
||||
_prop_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
|
||||
c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
|
||||
else:
|
||||
for op, o0l, i0l, i1l, i2l, i3l in self.ops[:,:6]:
|
||||
o0, i0, i1, i2, i3 = [self.c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
|
||||
@@ -190,6 +193,15 @@ class LogicSim(sim.SimOps):
|
||||
logic.bp4v_or(self.c[o0], self.c[t0], self.c[t1])
|
||||
else: print(f'unknown op {op}')
|
||||
if inject_cb is not None: inject_cb(o0l, self.c[o0])
|
||||
if fault_line >= 0 and o0l == fault_line:
|
||||
if fault_model == 0:
|
||||
self.c[o0] = self.c[o0] & ~fault_mask[np.newaxis]
|
||||
elif fault_model == 1:
|
||||
self.c[o0] = self.c[o0] | fault_mask[np.newaxis]
|
||||
else:
|
||||
self.c[t0, 0] = ~(self.c[o0, 0] & self.c[o0, 1] & fault_mask)
|
||||
self.c[o0, 1] = ~self.c[o0, 0] & ~self.c[o0, 1] & fault_mask
|
||||
self.c[o0, 0] = self.c[t0, 0]
|
||||
else:
|
||||
for op, o0l, i0l, i1l, i2l, i3l in self.ops[:,:6]:
|
||||
o0, i0, i1, i2, i3 = [self.c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
|
||||
@@ -304,8 +316,96 @@ class LogicSim(sim.SimOps):
|
||||
self.s_ppo_to_ppi()
|
||||
|
||||
|
||||
class LogicSim2V(sim.SimOps):
|
||||
"""A bit-parallel naïve combinational simulator for 2-valued logic.
|
||||
|
||||
:param circuit: The circuit to simulate.
|
||||
:param sims: The number of parallel logic simulations to perform.
|
||||
:param c_reuse: If True, intermediate signal values may get overwritten when not needed anymore to save memory.
|
||||
:param strip_forks: If True, forks are not included in the simulation model to save memory and simulation time.
|
||||
Caveat: faults on fanout branches will not be injected if forks are stripped.
|
||||
"""
|
||||
def __init__(self, circuit: Circuit, sims: int = 8, c_reuse: bool = False, strip_forks: bool = False):
|
||||
super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks)
|
||||
self.sims = sims
|
||||
nbytes = cdiv(sims, 8)
|
||||
|
||||
self.c = np.zeros((self.c_len, 1, nbytes), dtype=np.uint8)
|
||||
"""Logic values within the combinational portion of the circuit.
|
||||
|
||||
In bit-parallel (bp) storage format.
|
||||
Storage locations are indirectly addressed.
|
||||
Data for line `l` is in `self.c[self.c_locs[l]]`.
|
||||
"""
|
||||
self.s_assign = np.zeros((self.s_len, self.sims), dtype=np.uint8)
|
||||
"""Logic values assigned to the ports and flip-flops of the circuit.
|
||||
|
||||
The simulator reads (P)PI values from here.
|
||||
Values assigned to PO positions are ignored.
|
||||
This field is a 2-dimensional array and expects values in the mv storage format.
|
||||
First index is the port position (defined by `self.circuit.s_nodes`), second index is the pattern index (0 ... `self.sims-1`).
|
||||
"""
|
||||
self.s_result = np.zeros((self.s_len, self.sims), dtype=np.uint8)
|
||||
"""Logic values at the ports and flip-flops of the circuit after simulation.
|
||||
|
||||
The simulator writes (P)PO values here.
|
||||
Values assigned to PI positions are ignored.
|
||||
This field is a 2-dimensional array and expects values in the mv storage format.
|
||||
First index is the port position (defined by `self.circuit.s_nodes`), second index is the pattern index (0 ... `self.sims-1`).
|
||||
"""
|
||||
self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
|
||||
|
||||
def __repr__(self):
|
||||
return f'{{name: "{self.circuit.name}", sims: {self.sims}, c_bytes: {eng(self.c.nbytes)}}}'
|
||||
|
||||
def s_to_c(self):
|
||||
"""Assigns the values from ``self.s_assign`` to the inputs of the combinational portion.
|
||||
"""
|
||||
self.c[self.pippi_c_locs] = logic.mv_to_bp(self.s_assign[self.pippi_s_locs])[:,:1,:]
|
||||
|
||||
def c_prop(self, fault_line=-1, fault_model=2, fault_mask=None):
|
||||
if fault_mask is None:
|
||||
fault_mask = self._full_mask # default: full mask
|
||||
else:
|
||||
if len(fault_mask) < self.c.shape[-1]: # pad mask with 0's if necessary
|
||||
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
|
||||
fault_mask2[:len(fault_mask)] = fault_mask
|
||||
fault_mask = fault_mask2
|
||||
c_prop_2v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model))
|
||||
|
||||
def c_to_s(self):
|
||||
"""Captures the results of the combinational portion into ``self.s_result``.
|
||||
"""
|
||||
self.s_result[self.poppo_s_locs] = logic.bp_to_mv(self.c[self.poppo_c_locs])[:,:self.sims] * logic.ONE
|
||||
|
||||
def c_ppo_to_ppi(self):
|
||||
"""Copies the result data for all PPOs (flip-flops) to PPIs for a next simulation cycle.
|
||||
"""
|
||||
self.c[self.ppi_c_locs] = self.c[self.ppo_c_locs].copy() # copy prevents undefined behavior if (P)PIs are connected directly to (P)POs
|
||||
|
||||
def allocate(self):
|
||||
"""Allocates a new pattern array with appropriate dimensions ``(self.s_len, self.sims)`` for one-pass simulation.
|
||||
"""
|
||||
return np.full((self.s_len, self.sims), logic.ZERO, dtype=np.uint8)
|
||||
|
||||
def simulate(self, patterns, responses = None, cycles:int = 1, fault_line=-1, fault_model=2, fault_mask=None):
|
||||
assert cycles >= 1
|
||||
for bo, bs in batchrange(patterns.shape[-1], self.sims):
|
||||
self.s_assign[self.pippi_s_locs, :bs] = patterns[self.pippi_s_locs, bo:bo+bs]
|
||||
self.s_to_c()
|
||||
for cycle in range(cycles):
|
||||
self.c_prop(fault_line=fault_line, fault_model=fault_model, fault_mask=fault_mask)
|
||||
if cycle < (cycles-1): self.c_ppo_to_ppi()
|
||||
self.c_to_s()
|
||||
if responses is None:
|
||||
patterns[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
|
||||
else:
|
||||
responses[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
|
||||
return patterns if responses is None else responses
|
||||
|
||||
|
||||
@numba.njit
|
||||
def _prop_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
|
||||
def c_prop_2v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
|
||||
for op, o0l, i0l, i1l, i2l, i3l in ops[:,:6]:
|
||||
o0, i0, i1, i2, i3 = [c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
|
||||
if op == sim.BUF1: c[o0]=c[i0]
|
||||
@@ -343,7 +443,6 @@ def _prop_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
|
||||
elif op == sim.MUX21: c[o0] = (c[i0] & ~c[i2]) | (c[i1] & c[i2])
|
||||
else: print(f'unknown op {op}')
|
||||
if fault_line >= 0 and o0l == fault_line:
|
||||
#n = len(fault_mask)
|
||||
if fault_model == 0:
|
||||
c[o0] = c[o0] & ~fault_mask
|
||||
elif fault_model == 1:
|
||||
@@ -352,9 +451,283 @@ def _prop_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model):
|
||||
c[o0] = c[o0] ^ fault_mask
|
||||
|
||||
|
||||
class LogicSim4V(sim.SimOps):
|
||||
"""A bit-parallel naïve combinational simulator for 4-valued logic.
|
||||
|
||||
Logic values: 0, 1, -, X
|
||||
|
||||
:param circuit: The circuit to simulate.
|
||||
:param sims: The number of parallel logic simulations to perform.
|
||||
:param c_reuse: If True, intermediate signal values may get overwritten when not needed anymore to save memory.
|
||||
:param strip_forks: If True, forks are not included in the simulation model to save memory and simulation time.
|
||||
"""
|
||||
def __init__(self, circuit: Circuit, sims: int = 8, c_reuse: bool = False, strip_forks: bool = False):
|
||||
super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks)
|
||||
self.sims = sims
|
||||
nbytes = cdiv(sims, 8)
|
||||
|
||||
self.c = np.zeros((self.c_len, 2, nbytes), dtype=np.uint8)
|
||||
"""Logic values within the combinational portion of the circuit.
|
||||
|
||||
In bit-parallel (bp) storage format.
|
||||
Storage locations are indirectly addressed.
|
||||
Data for line `l` is in `self.c[self.c_locs[l]]`.
|
||||
"""
|
||||
self.s_assign = np.zeros((self.s_len, self.sims), dtype=np.uint8)
|
||||
"""Logic values assigned to the ports and flip-flops of the circuit.
|
||||
The simulator reads (P)PI values from here.
|
||||
Values assigned to PO positions are ignored.
|
||||
This field is a 2-dimensional array and expects values in the mv storage format.
|
||||
First index is the port position (defined by `self.circuit.s_nodes`), second
|
||||
index is the pattern index (0 ... `self.sims-1`).
|
||||
"""
|
||||
self.s_result = np.zeros((self.s_len, self.sims), dtype=np.uint8)
|
||||
"""Logic values at the ports and flip-flops of the circuit after simulation.
|
||||
The simulator writes (P)PO values here.
|
||||
Values assigned to PI positions are ignored.
|
||||
This field is a 2-dimensional array and expects values in the mv storage format.
|
||||
First index is the port position (defined by `self.circuit.s_nodes`), second
|
||||
index is the pattern index (0 ... `self.sims-1`).
|
||||
"""
|
||||
self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
|
||||
|
||||
def __repr__(self):
|
||||
return f'{{name: "{self.circuit.name}", sims: {self.sims}, c_bytes: {eng(self.c.nbytes)}}}'
|
||||
|
||||
def s_to_c(self):
|
||||
"""Assigns the values from ``self.s_assign`` to the inputs of the combinational portion.
|
||||
"""
|
||||
self.c[self.pippi_c_locs] = logic.mv_to_bp(self.s_assign[self.pippi_s_locs])[:,:2,:]
|
||||
|
||||
def c_prop(self, fault_line=-1, fault_model=2, fault_mask=None):
|
||||
if fault_mask is None:
|
||||
fault_mask = self._full_mask # default: full mask
|
||||
else:
|
||||
if len(fault_mask) < self.c.shape[-1]: # pad mask with 0's if necessary
|
||||
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
|
||||
fault_mask2[:len(fault_mask)] = fault_mask
|
||||
fault_mask = fault_mask2
|
||||
c_prop_4v_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model), self.tmp_idx, self.tmp2_idx)
|
||||
|
||||
def c_to_s(self):
|
||||
"""Captures the results of the combinational portion into ``self.s_result``.
|
||||
"""
|
||||
self.s_result[self.poppo_s_locs] = logic.bp_to_mv(self.c[self.poppo_c_locs])[:,:self.sims]
|
||||
|
||||
def c_ppo_to_ppi(self):
|
||||
"""Copies the result data for all PPOs (flip-flops) to PPIs for a next simulation cycle.
|
||||
"""
|
||||
self.c[self.ppi_c_locs] = self.c[self.ppo_c_locs].copy() # copy prevents undefined behavior if (P)PIs are connected directly to (P)POs
|
||||
|
||||
def allocate(self):
|
||||
"""Allocates a new pattern array with appropriate dimensions ``(self.s_len, self.sims)`` for one-pass simulation.
|
||||
"""
|
||||
return np.full((self.s_len, self.sims), logic.ZERO, dtype=np.uint8)
|
||||
|
||||
def simulate(self, patterns, responses = None, cycles:int = 1, fault_line=-1, fault_model=2, fault_mask=None):
|
||||
assert cycles >= 1
|
||||
for bo, bs in batchrange(patterns.shape[-1], self.sims):
|
||||
self.s_assign[self.pippi_s_locs, :bs] = patterns[self.pippi_s_locs, bo:bo+bs]
|
||||
self.s_to_c()
|
||||
for cycle in range(cycles):
|
||||
self.c_prop(fault_line=fault_line, fault_model=fault_model, fault_mask=fault_mask)
|
||||
if cycle < (cycles-1): self.c_ppo_to_ppi()
|
||||
self.c_to_s()
|
||||
if responses is None:
|
||||
patterns[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
|
||||
else:
|
||||
responses[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
|
||||
return patterns if responses is None else responses
|
||||
|
||||
|
||||
@numba.njit
|
||||
def c_prop_4v_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model, t0_idx, t1_idx):
|
||||
t0 = c[c_locs[t0_idx]]
|
||||
t1 = c[c_locs[t1_idx]]
|
||||
for op, o0l, i0l, i1l, i2l, i3l in ops[:,:6]:
|
||||
o0, i0, i1, i2, i3 = [c[c_locs[x]] for x in (o0l, i0l, i1l, i2l, i3l)]
|
||||
if op == sim.BUF1 or op == sim.INV1:
|
||||
# i0[0,1] -> o0[0,1]
|
||||
# 0 0 0 -> 0 0 0
|
||||
# - 0 1 -> X 1 0
|
||||
# X 1 0 -> X 1 0
|
||||
# 1 1 1 -> 1 1 1
|
||||
o0[0] = i0[0] | i0[1]
|
||||
o0[1] = i0[0] & i0[1]
|
||||
elif op == sim.AND2 or op == sim.NAND2:
|
||||
# i1[0,1] * i0[0,1] -> o0[0,1]
|
||||
# 0 0 0 0 0 0 -> 0 0 0
|
||||
# 0 0 0 - 0 1 -> 0 0 0
|
||||
# 0 0 0 X 1 0 -> 0 0 0
|
||||
# 0 0 0 1 1 1 -> 0 0 0
|
||||
# - 0 1 0 0 0 -> 0 0 0
|
||||
# - 0 1 - 0 1 -> X 1 0
|
||||
# - 0 1 X 1 0 -> X 1 0
|
||||
# - 0 1 1 1 1 -> X 1 0
|
||||
# X 1 0 0 0 0 -> 0 0 0
|
||||
# X 1 0 - 0 1 -> X 1 0
|
||||
# X 1 0 X 1 0 -> X 1 0
|
||||
# X 1 0 1 1 1 -> X 1 0
|
||||
# 1 1 1 0 0 0 -> 0 0 0
|
||||
# 1 1 1 - 0 1 -> X 1 0
|
||||
# 1 1 1 X 1 0 -> X 1 0
|
||||
# 1 1 1 1 1 1 -> 1 1 1
|
||||
o0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1])
|
||||
o0[1] = i0[0] & i0[1] & i1[0] & i1[1]
|
||||
elif op == sim.AND3 or op == sim.NAND3:
|
||||
# i2[0,1] * i1[0,1] * i0[0,1] -> o0[0,1]
|
||||
o0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1]) & (i2[0]|i2[1])
|
||||
o0[1] = i0[0] & i0[1] & i1[0] & i1[1] & i2[0] & i2[1]
|
||||
elif op == sim.AND4 or op == sim.NAND4:
|
||||
# i3[0,1] * i2[0,1] * i1[0,1] * i0[0,1] -> o0[0,1]
|
||||
o0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1]) & (i2[0]|i2[1]) & (i3[0]|i3[1])
|
||||
o0[1] = i0[0] & i0[1] & i1[0] & i1[1] & i2[0] & i2[1] & i3[0] & i3[1]
|
||||
elif op == sim.OR2 or op == sim.NOR2:
|
||||
# i1[0,1] + i0[0,1] -> o0[0,1]
|
||||
# 0 0 0 0 0 0 -> 0 0 0
|
||||
# 0 0 0 - 0 1 -> X 1 0
|
||||
# 0 0 0 X 1 0 -> X 1 0
|
||||
# 0 0 0 1 1 1 -> 1 1 1
|
||||
# - 0 1 0 0 0 -> X 1 0
|
||||
# - 0 1 - 0 1 -> X 1 0
|
||||
# - 0 1 X 1 0 -> X 1 0
|
||||
# - 0 1 1 1 1 -> 1 1 1
|
||||
# X 1 0 0 0 0 -> X 1 0
|
||||
# X 1 0 - 0 1 -> X 1 0
|
||||
# X 1 0 X 1 0 -> X 1 0
|
||||
# X 1 0 1 1 1 -> 1 1 1
|
||||
# 1 1 1 0 0 0 -> 1 1 1
|
||||
# 1 1 1 - 0 1 -> 1 1 1
|
||||
# 1 1 1 X 1 0 -> 1 1 1
|
||||
# 1 1 1 1 1 1 -> 1 1 1
|
||||
o0[0] = i0[0] | i0[1] | i1[0] | i1[1]
|
||||
o0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1])
|
||||
elif op == sim.OR3 or op == sim.NOR3:
|
||||
# i2[0,1] + i1[0,1] + i0[0,1] -> o0[0,1]
|
||||
o0[0] = i0[0] | i0[1] | i1[0] | i1[1] | i2[0] | i2[1]
|
||||
o0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1]) | (i2[0]&i2[1])
|
||||
elif op == sim.OR4 or op == sim.NOR4:
|
||||
# i3[0,1] + i2[0,1] + i1[0,1] + i0[0,1] -> o0[0,1]
|
||||
o0[0] = i0[0] | i0[1] | i1[0] | i1[1] | i2[0] | i2[1] | i3[0] | i3[1]
|
||||
o0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1]) | (i2[0]&i2[1]) | (i3[0]&i3[1])
|
||||
elif op == sim.XOR2 or op == sim.XNOR2:
|
||||
# i1[0,1] ^ i0[0,1] -> o0[0,1]
|
||||
# 0 0 0 0 0 0 -> 0 0 0
|
||||
# 0 0 0 - 0 1 -> X 1 0
|
||||
# 0 0 0 X 1 0 -> X 1 0
|
||||
# 0 0 0 1 1 1 -> 1 1 1
|
||||
# - 0 1 0 0 0 -> X 1 0
|
||||
# - 0 1 - 0 1 -> X 1 0
|
||||
# - 0 1 X 1 0 -> X 1 0
|
||||
# - 0 1 1 1 1 -> X 1 0
|
||||
# X 1 0 0 0 0 -> X 1 0
|
||||
# X 1 0 - 0 1 -> X 1 0
|
||||
# X 1 0 X 1 0 -> X 1 0
|
||||
# X 1 0 1 1 1 -> X 1 0
|
||||
# 1 1 1 0 0 0 -> 1 1 1
|
||||
# 1 1 1 - 0 1 -> X 1 0
|
||||
# 1 1 1 X 1 0 -> X 1 0
|
||||
# 1 1 1 1 1 1 -> 0 0 0
|
||||
o0[0] = (i0[0]|i0[1]|i1[0]|i1[1]) & (~i0[0]|~i0[1]|~i1[0]|~i1[1])
|
||||
o0[1] = (~i0[0]&~i0[1]&i1[0]&i1[1]) | (i0[0]&i0[1]&~i1[0]&~i1[1])
|
||||
elif op == sim.XOR3 or op == sim.XNOR3:
|
||||
# i2[0,1] ^ i1[0,1] ^ i0[0,1] -> o0[0,1]
|
||||
t0[0] = (i0[0]|i0[1]|i1[0]|i1[1]) & (~i0[0]|~i0[1]|~i1[0]|~i1[1])
|
||||
t0[1] = (~i0[0]&~i0[1]&i1[0]&i1[1]) | (i0[0]&i0[1]&~i1[0]&~i1[1])
|
||||
o0[0] = (t0[0]|t0[1]|i2[0]|i2[1]) & (~t0[0]|~t0[1]|~i2[0]|~i2[1])
|
||||
o0[1] = (~t0[0]&~t0[1]&i2[0]&i2[1]) | (t0[0]&t0[1]&~i2[0]&~i2[1])
|
||||
elif op == sim.XOR4 or op == sim.XNOR4:
|
||||
# i3[0,1] ^ i2[0,1] ^ i1[0,1] ^ i0[0,1] -> o0[0,1]
|
||||
t0[0] = (i0[0]|i0[1]|i1[0]|i1[1]) & (~i0[0]|~i0[1]|~i1[0]|~i1[1])
|
||||
t0[1] = (~i0[0]&~i0[1]&i1[0]&i1[1]) | (i0[0]&i0[1]&~i1[0]&~i1[1])
|
||||
t1[0] = (t0[0]|t0[1]|i2[0]|i2[1]) & (~t0[0]|~t0[1]|~i2[0]|~i2[1])
|
||||
t1[1] = (~t0[0]&~t0[1]&i2[0]&i2[1]) | (t0[0]&t0[1]&~i2[0]&~i2[1])
|
||||
o0[0] = (t1[0]|t1[1]|i3[0]|i3[1]) & (~t1[0]|~t1[1]|~i3[0]|~i3[1])
|
||||
o0[1] = (~t1[0]&~t1[1]&i3[0]&i3[1]) | (t1[0]&t1[1]&~i3[0]&~i3[1])
|
||||
elif op == sim.AO21 or op == sim.AOI21:
|
||||
# (i0[0,1] * i1[0,1]) + i2[0,1] -> o0[0,1]
|
||||
t0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1])
|
||||
t0[1] = i0[0] & i0[1] & i1[0] & i1[1]
|
||||
o0[0] = t0[0] | t0[1] | i2[0] | i2[1]
|
||||
o0[1] = (t0[0]&t0[1]) | (i2[0]&i2[1])
|
||||
elif op == sim.OA21 or op == sim.OAI21:
|
||||
# (i0[0,1] + i1[0,1]) * i2[0,1] -> o0[0,1]
|
||||
t0[0] = i0[0] | i0[1] | i1[0] | i1[1]
|
||||
t0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1])
|
||||
o0[0] = (t0[0]|t0[1]) & (i2[0]|i2[1])
|
||||
o0[1] = t0[0] & t0[1] & i2[0] & i2[1]
|
||||
elif op == sim.AO22 or op == sim.AOI22:
|
||||
# (i0[0,1] * i1[0,1]) + (i2[0,1] * i3[0,1]) -> o0[0,1]
|
||||
t0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1])
|
||||
t0[1] = i0[0] & i0[1] & i1[0] & i1[1]
|
||||
t1[0] = (i2[0]|i2[1]) & (i3[0]|i3[1])
|
||||
t1[1] = i2[0] & i2[1] & i3[0] & i3[1]
|
||||
o0[0] = t0[0] | t0[1] | t1[0] | t1[1]
|
||||
o0[1] = (t0[0]&t0[1]) | (t1[0]&t1[1])
|
||||
elif op == sim.OA22 or op == sim.OAI22:
|
||||
# (i0[0,1] + i1[0,1]) * (i2[0,1] + i3[0,1]) -> o0[0,1]
|
||||
t0[0] = i0[0] | i0[1] | i1[0] | i1[1]
|
||||
t0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1])
|
||||
t1[0] = i2[0] | i2[1] | i3[0] | i3[1]
|
||||
t1[1] = (i2[0]&i2[1]) | (i3[0]&i3[1])
|
||||
o0[0] = (t0[0]|t0[1]) & (t1[0]|t1[1])
|
||||
o0[1] = t0[0] & t0[1] & t1[0] & t1[1]
|
||||
elif op == sim.AO211 or op == sim.AOI211:
|
||||
# (i0[0,1] * i1[0,1]) + i2[0,1] + i3[0,1] -> o0[0,1]
|
||||
t0[0] = (i0[0]|i0[1]) & (i1[0]|i1[1])
|
||||
t0[1] = i0[0] & i0[1] & i1[0] & i1[1]
|
||||
o0[0] = t0[0] | t0[1] | i2[0] | i2[1] | i3[0] | i3[1]
|
||||
o0[1] = (t0[0]&t0[1]) | (i2[0]&i2[1]) | (i3[0]&i3[1])
|
||||
elif op == sim.OA211 or op == sim.OAI211:
|
||||
# (i0[0,1] + i1[0,1]) * i2[0,1] * i3[0,1] -> o0[0,1]
|
||||
t0[0] = i0[0] | i0[1] | i1[0] | i1[1]
|
||||
t0[1] = (i0[0]&i0[1]) | (i1[0]&i1[1])
|
||||
o0[0] = (t0[0]|t0[1]) & (i2[0]|i2[1]) & (i3[0]|i3[1])
|
||||
o0[1] = t0[0] & t0[1] & i2[0] & i2[1] & i3[0] & i3[1]
|
||||
elif op == sim.MUX21:
|
||||
# t1[0,1] = ~i2[0,1]
|
||||
t1[0] = ~i2[0] | ~i2[1]
|
||||
t1[1] = ~i2[0] & ~i2[1]
|
||||
# t0 = i0 * t1
|
||||
t0[0] = (i0[0]|i0[1]) & (t1[0]|t1[1])
|
||||
t0[1] = i0[0] & i0[1] & t1[0] & t1[1]
|
||||
# t1 = i1 * i2
|
||||
t1[0] = (i1[0]|i1[1]) & (i2[0]|i2[1])
|
||||
t1[1] = i1[0] & i1[1] & i2[0] & i2[1]
|
||||
# o0 = t0 | t1
|
||||
o0[0] = t0[0] | t0[1] | t1[0] | t1[1]
|
||||
o0[1] = (t0[0]&t0[1]) | (t1[0]&t1[1])
|
||||
else: print(f'unknown op {op}')
|
||||
if (op == sim.INV1 or
|
||||
op == sim.NAND2 or op == sim.NAND3 or op == sim.NAND4 or
|
||||
op == sim.NOR2 or op == sim.NOR3 or op == sim.NOR4 or
|
||||
op == sim.XNOR2 or op == sim.XNOR3 or op == sim.XNOR4 or
|
||||
op == sim.AOI21 or op == sim.OAI21 or
|
||||
op == sim.AOI22 or op == sim.OAI22 or
|
||||
op == sim.AOI211 or op == sim.OAI211):
|
||||
# o0[0,1] -> o0[0,1]
|
||||
# 0 0 0 -> 1 1 1
|
||||
# - 0 1 -> X 1 0
|
||||
# X 1 0 -> X 1 0
|
||||
# 1 1 1 -> 0 0 0
|
||||
t0[0] = ~o0[0] | ~o0[1]
|
||||
o0[1] = ~o0[0] & ~o0[1]
|
||||
o0[0] = t0[0]
|
||||
if fault_line >= 0 and o0l == fault_line:
|
||||
if fault_model == 0:
|
||||
o0[...] = o0 & ~fault_mask
|
||||
elif fault_model == 1:
|
||||
o0[...] = o0 | fault_mask
|
||||
else:
|
||||
t0[0] = ~(o0[0] ^ o0[1]) # mask for 0 and 1
|
||||
o0[...] = o0 ^ (fault_mask & t0[0]) # leaves X and - untouched
|
||||
|
||||
|
||||
class LogicSim6V(sim.SimOps):
|
||||
"""A bit-parallel naïve combinational simulator for 6-valued logic.
|
||||
|
||||
Logic values: 0, 1, R, F, N, P
|
||||
|
||||
:param circuit: The circuit to simulate.
|
||||
:param sims: The number of parallel logic simulations to perform.
|
||||
:param c_reuse: If True, intermediate signal values may get overwritten when not needed anymore to save memory.
|
||||
@@ -376,6 +749,9 @@ class LogicSim6V(sim.SimOps):
|
||||
|
||||
Access this array to assign new values to the (P)PIs or read values from the (P)POs.
|
||||
"""
|
||||
self.s_assign = self.s[0]
|
||||
self.s_result = self.s[1]
|
||||
self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
|
||||
|
||||
def __repr__(self):
|
||||
return f'{{name: "{self.circuit.name}", sims: {self.sims}, c_bytes: {eng(self.c.nbytes)}}}'
|
||||
@@ -385,17 +761,49 @@ class LogicSim6V(sim.SimOps):
|
||||
"""
|
||||
self.c[self.pippi_c_locs] = logic.mv_to_bp(self.s[0, self.pippi_s_locs])
|
||||
|
||||
def c_prop(self):
|
||||
c_prop_cpu(self.ops, self.c, self.c_locs, self.tmp_idx, self.tmp2_idx)
|
||||
def c_prop(self, fault_line=-1, fault_model=2, fault_mask=None):
|
||||
if fault_mask is None:
|
||||
fault_mask = self._full_mask # default: full mask
|
||||
else:
|
||||
if len(fault_mask) < self.c.shape[-1]: # pad mask with 0's if necessary
|
||||
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
|
||||
fault_mask2[:len(fault_mask)] = fault_mask
|
||||
fault_mask = fault_mask2
|
||||
c_prop_6v_cpu(self.ops, self.c, self.c_locs, fault_line, fault_mask, fault_model, self.tmp_idx, self.tmp2_idx)
|
||||
|
||||
def c_to_s(self):
|
||||
"""Captures the results of the combinational portion into ``s[1]``.
|
||||
"""
|
||||
self.s[1, self.poppo_s_locs] = logic.bp_to_mv(self.c[self.poppo_c_locs])[:,:self.sims]
|
||||
|
||||
def c_ppo_to_ppi(self):
|
||||
"""Copies the result data for all PPOs (flip-flops) to PPIs for a next simulation cycle.
|
||||
"""
|
||||
self.c[self.ppi_c_locs] = self.c[self.ppo_c_locs].copy() # copy prevents undefined behavior if (P)PIs are connected directly to (P)POs
|
||||
|
||||
def allocate(self):
|
||||
"""Allocates a new pattern array with appropriate dimensions ``(self.s_len, self.sims)`` for one-pass simulation.
|
||||
"""
|
||||
return np.full((self.s_len, self.sims), logic.ZERO, dtype=np.uint8)
|
||||
|
||||
def simulate(self, patterns, responses = None, cycles:int = 1, fault_line=-1, fault_model=2, fault_mask=None):
|
||||
assert cycles >= 1
|
||||
for bo, bs in batchrange(patterns.shape[-1], self.sims):
|
||||
self.s_assign[self.pippi_s_locs, :bs] = patterns[self.pippi_s_locs, bo:bo+bs]
|
||||
self.s_to_c()
|
||||
for cycle in range(cycles):
|
||||
self.c_prop(fault_line=fault_line, fault_model=fault_model, fault_mask=fault_mask)
|
||||
if cycle < (cycles-1): self.c_ppo_to_ppi()
|
||||
self.c_to_s()
|
||||
if responses is None:
|
||||
patterns[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
|
||||
else:
|
||||
responses[self.poppo_s_locs, bo:bo+bs] = self.s_result[self.poppo_s_locs, :bs]
|
||||
return patterns
|
||||
|
||||
|
||||
@numba.njit
|
||||
def c_prop_cpu(ops, c, c_locs, tmp_idx, tmp2_idx):
|
||||
def c_prop_6v_cpu(ops, c, c_locs, fault_line, fault_mask, fault_model, tmp_idx, tmp2_idx):
|
||||
t0 = c[c_locs[tmp_idx]]
|
||||
t1 = c[c_locs[tmp2_idx]]
|
||||
inv_op = np.array([255, 255, 0], dtype=np.uint8)[np.newaxis, :, np.newaxis]
|
||||
@@ -443,6 +851,84 @@ def c_prop_cpu(ops, c, c_locs, tmp_idx, tmp2_idx):
|
||||
o0[0] = i0[0] ^ i1[0]
|
||||
o0[1] = i0[1] ^ i1[1]
|
||||
o0[2] = i0[2] | i1[2]
|
||||
elif op == sim.XOR3 or op == sim.XNOR3:
|
||||
o0[0] = i0[0] ^ i1[0] ^ i2[0]
|
||||
o0[1] = i0[1] ^ i1[1] ^ i2[1]
|
||||
o0[2] = i0[2] | i1[2] | i2[2]
|
||||
elif op == sim.XOR4 or op == sim.XNOR4:
|
||||
o0[0] = i0[0] ^ i1[0] ^ i2[0] ^ i3[0]
|
||||
o0[1] = i0[1] ^ i1[1] ^ i2[1] ^ i3[1]
|
||||
o0[2] = i0[2] | i1[2] | i2[2] | i3[2]
|
||||
elif op == sim.AO21 or op == sim.AOI21:
|
||||
# (i0[0,1] * i1[0,1]) + i2[0,1] -> o0[0,1]
|
||||
t0[0] = i0[0] & i1[0]
|
||||
t0[1] = i0[1] & i1[1]
|
||||
t0[2] = (i0[2]&(i1[0]|i1[1]|i1[2])|
|
||||
i1[2]&(i0[0]|i0[1]|i0[2]))
|
||||
o0[0] = t0[0] | i2[0]
|
||||
o0[1] = t0[1] | i2[1]
|
||||
o0[2] = (t0[2]&(~i2[0]|~i2[1]|i2[2])|
|
||||
i2[2]&(~t0[0]|~t0[1]|t0[2]))
|
||||
elif op == sim.OA21 or op == sim.OAI21:
|
||||
# (i0[0,1] + i1[0,1]) * i2[0,1] -> o0[0,1]
|
||||
t0[0] = i0[0] | i1[0]
|
||||
t0[1] = i0[1] | i1[1]
|
||||
t0[2] = (i0[2]&(~i1[0]|~i1[1]|i1[2])|
|
||||
i1[2]&(~i0[0]|~i0[1]|i0[2]))
|
||||
o0[0] = t0[0] & i2[0]
|
||||
o0[1] = t0[1] & i2[1]
|
||||
o0[2] = (t0[2]&(i2[0]|i2[1]|i2[2])|
|
||||
i2[2]&(t0[0]|t0[1]|t0[2]))
|
||||
elif op == sim.AO22 or op == sim.AOI22:
|
||||
# (i0[0,1] * i1[0,1]) + (i2[0,1] * i3[0,1]) -> o0[0,1]
|
||||
t0[0] = i0[0] & i1[0]
|
||||
t0[1] = i0[1] & i1[1]
|
||||
t0[2] = (i0[2]&(i1[0]|i1[1]|i1[2])|
|
||||
i1[2]&(i0[0]|i0[1]|i0[2]))
|
||||
t1[0] = i2[0] & i3[0]
|
||||
t1[1] = i2[1] & i3[1]
|
||||
t1[2] = (i2[2]&(i3[0]|i3[1]|i3[2])|
|
||||
i3[2]&(i2[0]|i2[1]|i2[2]))
|
||||
o0[0] = t0[0] | t1[0]
|
||||
o0[1] = t0[1] | t1[1]
|
||||
o0[2] = (t0[2]&(~t1[0]|~t1[1]|t1[2])|
|
||||
t1[2]&(~t0[0]|~t0[1]|t0[2]))
|
||||
elif op == sim.OA22 or op == sim.OAI22:
|
||||
# (i0[0,1] + i1[0,1]) * (i2[0,1] + i3[0,1]) -> o0[0,1]
|
||||
t0[0] = i0[0] | i1[0]
|
||||
t0[1] = i0[1] | i1[1]
|
||||
t0[2] = (i0[2]&(~i1[0]|~i1[1]|i1[2])|
|
||||
i1[2]&(~i0[0]|~i0[1]|i0[2]))
|
||||
t1[0] = i2[0] | i3[0]
|
||||
t1[1] = i2[1] | i3[1]
|
||||
t1[2] = (i2[2]&(~i3[0]|~i3[1]|i3[2])|
|
||||
i3[2]&(~i2[0]|~i2[1]|i2[2]))
|
||||
o0[0] = t0[0] & t1[0]
|
||||
o0[1] = t0[1] & t1[1]
|
||||
o0[2] = (t0[2]&(t1[0]|t1[1]|t1[2])|
|
||||
t1[2]&(t0[0]|t0[1]|t0[2]))
|
||||
elif op == sim.AO211 or op == sim.AOI211:
|
||||
# (i0[0,1] * i1[0,1]) + i2[0,1] + i3[0,1] -> o0[0,1]
|
||||
t0[0] = i0[0] & i1[0]
|
||||
t0[1] = i0[1] & i1[1]
|
||||
t0[2] = (i0[2]&(i1[0]|i1[1]|i1[2])|
|
||||
i1[2]&(i0[0]|i0[1]|i0[2]))
|
||||
o0[0] = t0[0] | i2[0] | i3[0]
|
||||
o0[1] = t0[1] | i2[1] | i3[1]
|
||||
o0[2] = (t0[2]&(~i2[0]|~i2[1]|i2[2])&(~i3[0]|~i3[1]|i3[2])|
|
||||
i2[2]&(~t0[0]|~t0[1]|t0[2])&(~i3[0]|~i3[1]|i3[2])|
|
||||
i3[2]&(~t0[0]|~t0[1]|t0[2])&(~i2[0]|~i2[1]|i2[2]))
|
||||
elif op == sim.OA211 or op == sim.OAI211:
|
||||
# (i0[0,1] + i1[0,1]) * i2[0,1] * i3[0,1] -> o0[0,1]
|
||||
t0[0] = i0[0] | i1[0]
|
||||
t0[1] = i0[1] | i1[1]
|
||||
t0[2] = (i0[2]&(~i1[0]|~i1[1]|i1[2])|
|
||||
i1[2]&(~i0[0]|~i0[1]|i0[2]))
|
||||
o0[0] = t0[0] & i2[0] & i3[0]
|
||||
o0[1] = t0[1] & i2[1] & i3[1]
|
||||
o0[2] = (t0[2]&(i2[0]|i2[1]|i2[2])&(i3[0]|i3[1]|i3[2])|
|
||||
i2[2]&(t0[0]|t0[1]|t0[2])&(i3[0]|i3[1]|i3[2])|
|
||||
i3[2]&(t0[0]|t0[1]|t0[2])&(i2[0]|i2[1]|i2[2]))
|
||||
elif op == sim.MUX21:
|
||||
# t1 = ~i2
|
||||
t1[...] = i2 ^ inv_op
|
||||
@@ -466,5 +952,8 @@ def c_prop_cpu(ops, c, c_locs, tmp_idx, tmp2_idx):
|
||||
if (op == sim.INV1 or
|
||||
op == sim.NAND2 or op == sim.NAND3 or op == sim.NAND4 or
|
||||
op == sim.NOR2 or op == sim.NOR3 or op == sim.NOR4 or
|
||||
op == sim.XNOR2):
|
||||
op == sim.XNOR2 or op == sim.XNOR3 or op == sim.XNOR4 or
|
||||
op == sim.AOI21 or op == sim.OAI21 or
|
||||
op == sim.AOI22 or op == sim.OAI22 or
|
||||
op == sim.AOI211 or op == sim.OAI211):
|
||||
o0[...] = o0 ^ inv_op
|
||||
+10
-6
@@ -4,6 +4,7 @@ from bisect import bisect, insort_left
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import log
|
||||
from .circuit import Circuit
|
||||
|
||||
BUF1 = np.uint16(0b1010_1010_1010_1010)
|
||||
@@ -187,10 +188,10 @@ class SimOps:
|
||||
levels = []
|
||||
|
||||
ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes))
|
||||
ppos = set([n for n in circuit.s_nodes if len(n.ins) > 0])
|
||||
readers = np.array([1 if l.reader in ppos else len(l.reader.outs) for l in circuit.lines], dtype=np.int32) # for ref-counting forks
|
||||
root_nodes = set([n for n in circuit.s_nodes if len(n.ins) > 0] + [n for n in circuit.nodes if len(n.outs) == 0]) # start from POs, PPOs, and any dangling nodes
|
||||
readers = np.array([1 if l.reader in root_nodes else len(l.reader.outs) for l in circuit.lines], dtype=np.int32) # for ref-counting forks
|
||||
|
||||
level_lines = [n.ins[0] for n in ppos] # start from PPOs
|
||||
level_lines = [n.ins[0] for n in root_nodes]
|
||||
# FIXME: Should probably instanciate buffers for PPOs and attach DFF clocks
|
||||
|
||||
while len(level_lines) > 0: # traverse the circuit level-wise back towards (P)PIs
|
||||
@@ -199,6 +200,8 @@ class SimOps:
|
||||
|
||||
for l in level_lines:
|
||||
n = l.driver
|
||||
if len(n.ins) > 4:
|
||||
log.warn(f'too many input pins: {n}')
|
||||
in_idxs = [n.ins[x].index if len(n.ins) > x and n.ins[x] is not None else self.zero_idx for x in [0,1,2,3]]
|
||||
if n in ppio2idx:
|
||||
in_idxs[0] = self.ppi_offset + ppio2idx[n]
|
||||
@@ -223,7 +226,7 @@ class SimOps:
|
||||
sp = prims[2]
|
||||
break
|
||||
if sp is None:
|
||||
print('unknown cell type', kind)
|
||||
log.warn(f'ignored cell of unknown type: {n}')
|
||||
else:
|
||||
level_ops.append((sp, l.index, *in_idxs, *a_ctrl[l]))
|
||||
|
||||
@@ -233,6 +236,7 @@ class SimOps:
|
||||
self.levels = [np.asarray(lv, dtype=np.int32) for lv in levels[::-1]]
|
||||
level_sums = np.cumsum([0]+[len(lv) for lv in self.levels], dtype=np.int32)
|
||||
self.level_starts, self.level_stops = level_sums[:-1], level_sums[1:]
|
||||
# op format: [kind, out0, in0, in1, in2, in3, wsa_acc_pos, wsa_rise, wsa_fall]
|
||||
self.ops = np.vstack(self.levels)
|
||||
|
||||
# create a map from fanout lines to stem lines for fork stripping
|
||||
@@ -268,10 +272,10 @@ class SimOps:
|
||||
|
||||
# allocate and keep memory for PI/PPI, keep memory for PO/PPO (allocated later)
|
||||
for i, n in enumerate(circuit.s_nodes):
|
||||
if len(n.outs) > 0:
|
||||
if 'dff' in n.kind.lower() or len(n.ins) == 0: # PPI or PI
|
||||
self.c_locs[self.ppi_offset + i], self.c_caps[self.ppi_offset + i] = h.alloc(c_caps_min), c_caps_min
|
||||
ref_count[self.ppi_offset + i] += 1
|
||||
if len(n.ins) > 0:
|
||||
if len(n.ins) > 0: # PO
|
||||
i0_idx = stems[n.ins[0]] if stems[n.ins[0]] >= 0 else n.ins[0]
|
||||
ref_count[i0_idx] += 1
|
||||
|
||||
|
||||
@@ -64,6 +64,45 @@ class TechLib:
|
||||
return self.cells[kind][1][pin][1]
|
||||
|
||||
|
||||
KYUPY = TechLib(r"""
|
||||
BUF1 input(i0) output(o) o=BUF1(i0) ;
|
||||
INV1 input(i0) output(o) o=INV1(i0) ;
|
||||
AND2 input(i0,i1) output(o) o=AND2(i0,i1) ;
|
||||
AND3 input(i0,i1,i2) output(o) o=AND3(i0,i1,i2) ;
|
||||
AND4 input(i0,i1,i2,i3) output(o) o=AND4(i0,i1,i2,i3) ;
|
||||
NAND2 input(i0,i1) output(o) o=NAND2(i0,i1) ;
|
||||
NAND3 input(i0,i1,i2) output(o) o=NAND3(i0,i1,i2) ;
|
||||
NAND4 input(i0,i1,i2,i3) output(o) o=NAND4(i0,i1,i2,i3);
|
||||
OR2 input(i0,i1) output(o) o=OR2(i0,i1) ;
|
||||
OR3 input(i0,i1,i2) output(o) o=OR3(i0,i1,i2) ;
|
||||
OR4 input(i0,i1,i2,i3) output(o) o=OR4(i0,i1,i2,i3) ;
|
||||
NOR2 input(i0,i1) output(o) o=NOR2(i0,i1) ;
|
||||
NOR3 input(i0,i1,i2) output(o) o=NOR3(i0,i1,i2) ;
|
||||
NOR4 input(i0,i1,i2,i3) output(o) o=NOR4(i0,i1,i2,i3) ;
|
||||
XOR2 input(i0,i1) output(o) o=XOR2(i0,i1) ;
|
||||
XOR3 input(i0,i1,i2) output(o) o=XOR3(i0,i1,i2) ;
|
||||
XOR4 input(i0,i1,i2,i3) output(o) o=XOR4(i0,i1,i2,i3) ;
|
||||
XNOR2 input(i0,i1) output(o) o=XNOR2(i0,i1) ;
|
||||
XNOR3 input(i0,i1,i2) output(o) o=XNOR3(i0,i1,i2) ;
|
||||
XNOR4 input(i0,i1,i2,i3) output(o) o=XNOR4(i0,i1,i2,i3) ;
|
||||
AO21 input(i0,i1,i2) output(o) o=AO21(i0,i1,i2) ;
|
||||
AO22 input(i0,i1,i2,i3) output(o) o=AO22(i0,i1,i2,i3) ;
|
||||
OA21 input(i0,i1,i2) output(o) o=OA21(i0,i1,i2) ;
|
||||
OA22 input(i0,i1,i2,i3) output(o) o=OA22(i0,i1,i2,i3) ;
|
||||
AOI21 input(i0,i1,i2) output(o) o=AOI21(i0,i1,i2) ;
|
||||
AOI22 input(i0,i1,i2,i3) output(o) o=AOI22(i0,i1,i2,i3) ;
|
||||
OAI21 input(i0,i1,i2) output(o) o=OAI21(i0,i1,i2) ;
|
||||
OAI22 input(i0,i1,i2,i3) output(o) o=OAI22(i0,i1,i2,i3) ;
|
||||
AO211 input(i0,i1,i2,i3) output(o) o=AO211(i0,i1,i2,i3) ;
|
||||
OA211 input(i0,i1,i2,i3) output(o) o=OA211(i0,i1,i2,i3) ;
|
||||
AOI211 input(i0,i1,i2,i3) output(o) o=AOI211(i0,i1,i2,i3) ;
|
||||
OAI211 input(i0,i1,i2,i3) output(o) o=OAI211(i0,i1,i2,i3) ;
|
||||
MUX21 input(i0,i1,i2) output(o) o=MUX21(i0,i1,i2) ;
|
||||
DFF input(D,CLK) output(Q) Q=DFF(D,CLK) ;
|
||||
""")
|
||||
"""A synthetic library of all KyuPy simulation primitives.
|
||||
"""
|
||||
|
||||
GSC180 = TechLib(r"""
|
||||
BUFX{1,3} input(A) output(Y) Y=BUF1(A) ;
|
||||
CLKBUFX{1,2,3} input(A) output(Y) Y=BUF1(A) ;
|
||||
|
||||
+14
-8
@@ -10,7 +10,7 @@ from lark import Lark, Transformer, Tree
|
||||
|
||||
from . import log, readtext
|
||||
from .circuit import Circuit, Node, Line
|
||||
from .techlib import NANGATE
|
||||
from .techlib import KYUPY
|
||||
|
||||
Instantiation = namedtuple('Instantiation', ['type', 'name', 'pins'])
|
||||
|
||||
@@ -35,7 +35,7 @@ class SignalDeclaration:
|
||||
|
||||
|
||||
class VerilogTransformer(Transformer):
|
||||
def __init__(self, branchforks=False, tlib=NANGATE):
|
||||
def __init__(self, branchforks=False, tlib=KYUPY):
|
||||
super().__init__()
|
||||
self.branchforks = branchforks
|
||||
self.tlib = tlib
|
||||
@@ -61,6 +61,10 @@ class VerilogTransformer(Transformer):
|
||||
pinmap[idx] = p
|
||||
return Instantiation(args[0], args[1], pinmap)
|
||||
|
||||
@staticmethod
|
||||
def simple_dff(args):
|
||||
return Instantiation('DFF', args[1], {'d': args[2], 'clk': args[0], 'q': args[1]} )
|
||||
|
||||
def range(self, args):
|
||||
left = int(args[0].value)
|
||||
right = int(args[1].value) if len(args) > 1 else left
|
||||
@@ -96,8 +100,7 @@ class VerilogTransformer(Transformer):
|
||||
sel = args[0]
|
||||
ctrue = args[1]
|
||||
cfalse = args[2]
|
||||
print(f"got ternary if {args[0]} {args[1]}")
|
||||
|
||||
log.warn(f"FIXME: not implemented: ternary if {args[0]} {args[1]}")
|
||||
return args[1]
|
||||
|
||||
def declaration(self, kind, args):
|
||||
@@ -111,6 +114,7 @@ class VerilogTransformer(Transformer):
|
||||
def output(self, args): return self.declaration("output", args)
|
||||
def inout(self, args): return self.declaration("input", args) # just treat as input
|
||||
def wire(self, args): return self.declaration("wire", args)
|
||||
def reg(self, args): pass #return self.declaration("wire", args) # treat as wire
|
||||
|
||||
def module(self, args):
|
||||
c = Circuit(args[0])
|
||||
@@ -236,14 +240,16 @@ GRAMMAR = r"""
|
||||
start: (module)*
|
||||
module: "module" name parameters ";" (_statement)* "endmodule"
|
||||
parameters: "(" [ _namelist ] ")"
|
||||
_statement: input | output | inout | tri | wire | assign | instantiation
|
||||
_statement: input | output | inout | tri | wire | reg | assign | instantiation | simple_dff
|
||||
input: "input" range? _namelist ";"
|
||||
output: "output" range? _namelist ";"
|
||||
inout: "inout" range? _namelist ";"
|
||||
tri: "tri" range? _namelist ";"
|
||||
wire: "wire" range? _namelist ";"
|
||||
reg: "reg" range? _namelist ";"
|
||||
assign: "assign" sigsel "=" sigsel ";"
|
||||
instantiation: name name "(" [ pin ( "," pin )* ] ")" ";"
|
||||
simple_dff: "always" "@" "(" "posedge" name ")" sigsel "<=" sigsel ";"
|
||||
pin: namedpin | sigsel
|
||||
namedpin: "." name "(" sigsel? ")"
|
||||
range: "[" /[0-9]+/ (":" /[0-9]+/)? "]"
|
||||
@@ -259,7 +265,7 @@ GRAMMAR = r"""
|
||||
"""
|
||||
|
||||
|
||||
def parse(text, tlib=NANGATE, branchforks=False):
|
||||
def parse(text, tlib=KYUPY, branchforks=False) -> Circuit:
|
||||
"""Parses the given ``text`` as Verilog code.
|
||||
|
||||
:param text: A string with Verilog code.
|
||||
@@ -270,10 +276,10 @@ def parse(text, tlib=NANGATE, branchforks=False):
|
||||
(see :py:func:`~kyupy.sdf.DelayFile.interconnects()`).
|
||||
:return: A :py:class:`~kyupy.circuit.Circuit` object.
|
||||
"""
|
||||
return Lark(GRAMMAR, parser="lalr", transformer=VerilogTransformer(branchforks, tlib)).parse(text)
|
||||
return Lark(GRAMMAR, parser="lalr", transformer=VerilogTransformer(branchforks, tlib)).parse(text) # type: ignore
|
||||
|
||||
|
||||
def load(file, tlib=NANGATE, branchforks=False):
|
||||
def load(file, tlib=KYUPY, branchforks=False):
|
||||
"""Parses the contents of ``file`` as Verilog code.
|
||||
|
||||
:param file: A file name or a file handle. Files with `.gz`-suffix are decompressed on-the-fly.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
all:
|
||||
abc -F map_to_minimal.abc
|
||||
@@ -0,0 +1,91 @@
|
||||
// Benchmark "all_kyupy_primitives" written by ABC on Thu Nov 6 13:13:21 2025
|
||||
|
||||
module all_kyupy_primitives (
|
||||
i0, i1, i2, i3,
|
||||
\o[0] , \o[1] , \o[2] , \o[3] , \o[4] , \o[5] , \o[6] , \o[7] , \o[8] ,
|
||||
\o[9] , \o[10] , \o[11] , \o[12] , \o[13] , \o[14] , \o[15] , \o[16] ,
|
||||
\o[17] , \o[18] , \o[19] , \o[20] , \o[21] , \o[22] , \o[23] , \o[24] ,
|
||||
\o[25] , \o[26] , \o[27] , \o[28] , \o[29] , \o[30] , \o[31] , \o[32] );
|
||||
input i0, i1, i2, i3;
|
||||
output \o[0] , \o[1] , \o[2] , \o[3] , \o[4] , \o[5] , \o[6] , \o[7] ,
|
||||
\o[8] , \o[9] , \o[10] , \o[11] , \o[12] , \o[13] , \o[14] , \o[15] ,
|
||||
\o[16] , \o[17] , \o[18] , \o[19] , \o[20] , \o[21] , \o[22] , \o[23] ,
|
||||
\o[24] , \o[25] , \o[26] , \o[27] , \o[28] , \o[29] , \o[30] , \o[31] ,
|
||||
\o[32] ;
|
||||
wire new_n45, new_n48, new_n51, new_n54, new_n55, new_n56, new_n57,
|
||||
new_n60, new_n61, new_n62, new_n63, new_n64, new_n65, new_n66, new_n67,
|
||||
new_n68, new_n69, new_n72, new_n73, new_n74, new_n75, new_n76, new_n77,
|
||||
new_n78, new_n79, new_n80, new_n81, new_n86, new_n87, new_n91, new_n92,
|
||||
new_n96, new_n99, new_n102, new_n103, new_n104;
|
||||
INV1 g00(.i0(i1), .o(\o[1] ));
|
||||
AND2 g01(.i0(i1), .i1(i0), .o(\o[2] ));
|
||||
AND2 g02(.i0(\o[2] ), .i1(i2), .o(\o[3] ));
|
||||
AND2 g03(.i0(\o[3] ), .i1(i3), .o(\o[4] ));
|
||||
INV1 g04(.i0(\o[2] ), .o(\o[5] ));
|
||||
INV1 g05(.i0(\o[3] ), .o(\o[6] ));
|
||||
INV1 g06(.i0(\o[4] ), .o(\o[7] ));
|
||||
INV1 g07(.i0(i0), .o(new_n45));
|
||||
AND2 g08(.i0(\o[1] ), .i1(new_n45), .o(\o[11] ));
|
||||
INV1 g09(.i0(\o[11] ), .o(\o[8] ));
|
||||
INV1 g10(.i0(i2), .o(new_n48));
|
||||
AND2 g11(.i0(\o[11] ), .i1(new_n48), .o(\o[12] ));
|
||||
INV1 g12(.i0(\o[12] ), .o(\o[9] ));
|
||||
INV1 g13(.i0(i3), .o(new_n51));
|
||||
AND2 g14(.i0(\o[12] ), .i1(new_n51), .o(\o[13] ));
|
||||
INV1 g15(.i0(\o[13] ), .o(\o[10] ));
|
||||
AND2 g16(.i0(\o[1] ), .i1(i0), .o(new_n54));
|
||||
INV1 g17(.i0(new_n54), .o(new_n55));
|
||||
AND2 g18(.i0(i1), .i1(new_n45), .o(new_n56));
|
||||
INV1 g19(.i0(new_n56), .o(new_n57));
|
||||
AND2 g20(.i0(new_n57), .i1(new_n55), .o(\o[17] ));
|
||||
INV1 g21(.i0(\o[17] ), .o(\o[14] ));
|
||||
AND2 g22(.i0(i2), .i1(i1), .o(new_n60));
|
||||
INV1 g23(.i0(new_n60), .o(new_n61));
|
||||
AND2 g24(.i0(new_n48), .i1(\o[1] ), .o(new_n62));
|
||||
INV1 g25(.i0(new_n62), .o(new_n63));
|
||||
AND2 g26(.i0(new_n63), .i1(new_n61), .o(new_n64));
|
||||
INV1 g27(.i0(new_n64), .o(new_n65));
|
||||
AND2 g28(.i0(new_n65), .i1(i0), .o(new_n66));
|
||||
INV1 g29(.i0(new_n66), .o(new_n67));
|
||||
AND2 g30(.i0(new_n64), .i1(new_n45), .o(new_n68));
|
||||
INV1 g31(.i0(new_n68), .o(new_n69));
|
||||
AND2 g32(.i0(new_n69), .i1(new_n67), .o(\o[18] ));
|
||||
INV1 g33(.i0(\o[18] ), .o(\o[15] ));
|
||||
AND2 g34(.i0(i3), .i1(new_n48), .o(new_n72));
|
||||
INV1 g35(.i0(new_n72), .o(new_n73));
|
||||
AND2 g36(.i0(new_n51), .i1(i2), .o(new_n74));
|
||||
INV1 g37(.i0(new_n74), .o(new_n75));
|
||||
AND2 g38(.i0(new_n75), .i1(new_n73), .o(new_n76));
|
||||
INV1 g39(.i0(new_n76), .o(new_n77));
|
||||
AND2 g40(.i0(new_n77), .i1(\o[17] ), .o(new_n78));
|
||||
INV1 g41(.i0(new_n78), .o(new_n79));
|
||||
AND2 g42(.i0(new_n76), .i1(\o[14] ), .o(new_n80));
|
||||
INV1 g43(.i0(new_n80), .o(new_n81));
|
||||
AND2 g44(.i0(new_n81), .i1(new_n79), .o(\o[19] ));
|
||||
INV1 g45(.i0(\o[19] ), .o(\o[16] ));
|
||||
AND2 g46(.i0(\o[5] ), .i1(new_n48), .o(\o[24] ));
|
||||
INV1 g47(.i0(\o[24] ), .o(\o[20] ));
|
||||
AND2 g48(.i0(i3), .i1(i2), .o(new_n86));
|
||||
INV1 g49(.i0(new_n86), .o(new_n87));
|
||||
AND2 g50(.i0(new_n87), .i1(\o[5] ), .o(\o[25] ));
|
||||
INV1 g51(.i0(\o[25] ), .o(\o[21] ));
|
||||
AND2 g52(.i0(\o[8] ), .i1(i2), .o(\o[22] ));
|
||||
AND2 g53(.i0(new_n51), .i1(new_n48), .o(new_n91));
|
||||
INV1 g54(.i0(new_n91), .o(new_n92));
|
||||
AND2 g55(.i0(new_n92), .i1(\o[8] ), .o(\o[23] ));
|
||||
INV1 g56(.i0(\o[22] ), .o(\o[26] ));
|
||||
INV1 g57(.i0(\o[23] ), .o(\o[27] ));
|
||||
AND2 g58(.i0(\o[5] ), .i1(new_n51), .o(new_n96));
|
||||
AND2 g59(.i0(new_n96), .i1(new_n48), .o(\o[30] ));
|
||||
INV1 g60(.i0(\o[30] ), .o(\o[28] ));
|
||||
AND2 g61(.i0(\o[8] ), .i1(i3), .o(new_n99));
|
||||
AND2 g62(.i0(new_n99), .i1(i2), .o(\o[29] ));
|
||||
INV1 g63(.i0(\o[29] ), .o(\o[31] ));
|
||||
AND2 g64(.i0(new_n48), .i1(i0), .o(new_n102));
|
||||
INV1 g65(.i0(new_n102), .o(new_n103));
|
||||
AND2 g66(.i0(new_n103), .i1(new_n61), .o(new_n104));
|
||||
INV1 g67(.i0(new_n104), .o(\o[32] ));
|
||||
BUF1 g68(.i0(i0), .o(\o[0] ));
|
||||
endmodule
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
module all_kyupy_primitives (i0, i1, i2, i3, o);
|
||||
input i0;
|
||||
input i1;
|
||||
input i2;
|
||||
input i3;
|
||||
output [32:0] o;
|
||||
|
||||
BUF1 buf1_0 (.i0(i0), .o(o[0]));
|
||||
INV1 inv1_0 (.i0(i1), .o(o[1]));
|
||||
|
||||
AND2 and2_0 (.i0(i0), .i1(i1), .o(o[2]));
|
||||
AND3 and3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[3]));
|
||||
AND4 and4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[4]));
|
||||
|
||||
NAND2 nand2_0 (.i0(i0), .i1(i1), .o(o[5]));
|
||||
NAND3 nand3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[6]));
|
||||
NAND4 nand4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[7]));
|
||||
|
||||
OR2 or2_0 (.i0(i0), .i1(i1), .o(o[8]));
|
||||
OR3 or3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[9]));
|
||||
OR4 or4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[10]));
|
||||
|
||||
NOR2 nor2_0 (.i0(i0), .i1(i1), .o(o[11]));
|
||||
NOR3 nor3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[12]));
|
||||
NOR4 nor4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[13]));
|
||||
|
||||
XOR2 xor2_0 (.i0(i0), .i1(i1), .o(o[14]));
|
||||
XOR3 xor3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[15]));
|
||||
XOR4 xor4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[16]));
|
||||
|
||||
XNOR2 xnor2_0 (.i0(i0), .i1(i1), .o(o[17]));
|
||||
XNOR3 xnor3_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[18]));
|
||||
XNOR4 xnor4_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[19]));
|
||||
|
||||
AO21 ao21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[20]));
|
||||
AO22 ao22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[21]));
|
||||
OA21 oa21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[22]));
|
||||
OA22 oa22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[23]));
|
||||
|
||||
AOI21 aoi21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[24]));
|
||||
AOI22 aoi22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[25]));
|
||||
OAI21 oai21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[26]));
|
||||
OAI22 oai22_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[27]));
|
||||
|
||||
AO211 ao211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[28]));
|
||||
OA211 oa211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[29]));
|
||||
|
||||
AOI211 aoi211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[30]));
|
||||
OAI211 oai211_0 (.i0(i0), .i1(i1), .i2(i2), .i3(i3), .o(o[31]));
|
||||
|
||||
MUX21 mux21_0 (.i0(i0), .i1(i1), .i2(i2), .o(o[32]));
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,34 @@
|
||||
# library of all KyuPy simulation primitives defined in kyupy.sim
|
||||
GATE BUF1 1 o=i0; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE INV1 1 o=!i0; PIN * INV 1 999 1 0 1 0
|
||||
GATE AND2 1 o=i0*i1; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE AND3 1 o=i0*i1*i2; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE AND4 1 o=i0*i1*i2*i3; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE NAND2 1 o=!(i0*i1); PIN * INV 1 999 1 0 1 0
|
||||
GATE NAND3 1 o=!(i0*i1*i2); PIN * INV 1 999 1 0 1 0
|
||||
GATE NAND4 1 o=!(i0*i1*i2*i3); PIN * INV 1 999 1 0 1 0
|
||||
GATE OR2 1 o=i0+i1; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE OR3 1 o=i0+i1+i2; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE OR4 1 o=i0+i1+i2+i3; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE NOR2 1 o=!(i0+i1); PIN * INV 1 999 1 0 1 0
|
||||
GATE NOR3 1 o=!(i0+i1+i2); PIN * INV 1 999 1 0 1 0
|
||||
GATE NOR4 1 o=!(i0+i1+i2+i3); PIN * INV 1 999 1 0 1 0
|
||||
GATE XOR2 1 o=i0^i1; PIN * UNKNOWN 1 999 1 0 1 0
|
||||
GATE XOR3 1 o=i0^i1^i2; PIN * UNKNOWN 1 999 1 0 1 0
|
||||
GATE XOR4 1 o=i0^i1^i2^i3; PIN * UNKNOWN 1 999 1 0 1 0
|
||||
GATE XNOR2 1 o=!(i0^i1); PIN * UNKNOWN 1 999 1 0 1 0
|
||||
GATE XNOR3 1 o=!(i0^i1^i2); PIN * UNKNOWN 1 999 1 0 1 0
|
||||
GATE XNOR4 1 o=!(i0^i1^i2^i3); PIN * UNKNOWN 1 999 1 0 1 0
|
||||
GATE AO21 1 o=(i0*i1)+i2; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE AO22 1 o=(i0*i1)+(i2*i3); PIN * NONINV 1 999 1 0 1 0
|
||||
GATE OA21 1 o=(i0+i1)*i2; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE OA22 1 o=(i0+i1)*(i2+i3); PIN * NONINV 1 999 1 0 1 0
|
||||
GATE AOI21 1 o=!( (i0*i1)+i2 ); PIN * INV 1 999 1 0 1 0
|
||||
GATE AOI22 1 o=!( (i0*i1)+(i2*i3) ); PIN * INV 1 999 1 0 1 0
|
||||
GATE OAI21 1 o=!( (i0+i1)*i2 ); PIN * INV 1 999 1 0 1 0
|
||||
GATE OAI22 1 o=!( (i0+i1)*(i2+i3) ); PIN * INV 1 999 1 0 1 0
|
||||
GATE AO211 1 o=(i0*i1)+i2+i3; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE OA211 1 o=(i0+i1)*i2*i3; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE AOI211 1 o=!( (i0*i1)+i2+i3 ); PIN * INV 1 999 1 0 1 0
|
||||
GATE OAI211 1 o=!( (i0+i1)*i2*i3 ); PIN * INV 1 999 1 0 1 0
|
||||
GATE MUX21 1 o=(i0*!i2)+(i1*i2); PIN * UNKNOWN 1 999 1 0 1 0
|
||||
@@ -0,0 +1,8 @@
|
||||
# abc -f map_to_minimal.abc
|
||||
|
||||
read kyupy_simprims.genlib
|
||||
read -m all_kyupy_simprims.v
|
||||
fraig
|
||||
read minimal.genlib
|
||||
map
|
||||
write all_kyupy_simprims.minimal.v
|
||||
@@ -0,0 +1,5 @@
|
||||
# A minimal library for generating logically equivalent circuits with
|
||||
# minimum number of gate types.
|
||||
GATE BUF1 1 o=i0; PIN * NONINV 1 999 1 0 1 0
|
||||
GATE INV1 1 o=!i0; PIN * INV 1 999 1 0 1 0
|
||||
GATE AND2 1 o=i0*i1; PIN * NONINV 1 999 1 0 1 0
|
||||
@@ -131,7 +131,14 @@ def test_pickle(mydir):
|
||||
cs = pickle.dumps(c)
|
||||
assert cs is not None
|
||||
c2 = pickle.loads(cs)
|
||||
assert c == c2
|
||||
assert len(c.nodes) == len(c2.nodes)
|
||||
|
||||
def test_equivalency():
|
||||
c1 = bench.parse('input(a) output(z) z=not(a)')
|
||||
c2 = bench.parse('input(a) output(z) z=not(a)')
|
||||
assert c1 != c2
|
||||
assert c1.lines[0] != c2.lines[0]
|
||||
assert c1.nodes[0] != c2.nodes[0]
|
||||
|
||||
|
||||
def test_substitute():
|
||||
|
||||
+144
-3
@@ -1,14 +1,117 @@
|
||||
import numpy as np
|
||||
|
||||
from kyupy.logic_sim import LogicSim, LogicSim6V
|
||||
from kyupy import bench, logic, sim
|
||||
from kyupy.logic_sim import LogicSim, LogicSim2V, LogicSim4V, LogicSim6V
|
||||
from kyupy import bench, logic, sim, verilog
|
||||
from kyupy.logic import mvarray, bparray, bp_to_mv, mv_to_bp
|
||||
from kyupy.techlib import SAED90
|
||||
|
||||
def test_dangling():
|
||||
c = verilog.parse('''
|
||||
module test(i, o);
|
||||
input i; output o; wire dangling;
|
||||
INVX0 dff1 (.INP(i),.ZN(o));
|
||||
assign dangling=o;
|
||||
endmodule
|
||||
''', tlib=SAED90)
|
||||
c.resolve_tlib_cells(SAED90)
|
||||
sim = LogicSim2V(c)
|
||||
p = sim.allocate()
|
||||
p[...] = 0
|
||||
sim.simulate(p)
|
||||
assert p[1,0] == logic.ONE
|
||||
|
||||
def assert_equal_shape_and_contents(actual, desired):
|
||||
desired = np.array(desired, dtype=np.uint8)
|
||||
assert actual.shape == desired.shape
|
||||
np.testing.assert_allclose(actual, desired)
|
||||
|
||||
def test_LogicSim2V_minimal():
|
||||
c = bench.parse('input(i0, i1) output(o0, o1, o2) o0=AND2(i0,i1) o1=INV1(i0) o2=BUF1(i0)')
|
||||
sim = LogicSim2V(c)
|
||||
expect = mvarray('00010',
|
||||
'01010',
|
||||
'10001',
|
||||
'11101')
|
||||
actual = sim.simulate(expect.copy())
|
||||
np.testing.assert_array_equal(
|
||||
expect[c.io_locs('o')],
|
||||
actual[c.io_locs('o')])
|
||||
|
||||
def test_LogicSim4V_minimal():
|
||||
c = bench.parse('input(i0, i1) output(o0, o1, o2) o0=AND2(i0,i1) o1=INV1(i0) o2=BUF1(i0)')
|
||||
sim = LogicSim4V(c, 16)
|
||||
expect = mvarray('00010', '01010', '0-010', '0X010',
|
||||
'10001', '11101', '1-X01', '1XX01',
|
||||
'-00XX', '-1XXX', '--XXX', '-XXXX',
|
||||
'X00XX', 'X1XXX', 'X-XXX', 'XXXXX',)
|
||||
actual = sim.simulate(expect.copy())
|
||||
np.testing.assert_array_equal(
|
||||
expect[c.io_locs('o')],
|
||||
actual[c.io_locs('o')])
|
||||
|
||||
def test_LogicSim6V_minimal():
|
||||
c = bench.parse('input(i0, i1) output(o0, o1, o2) o0=AND2(i0,i1) o1=INV1(i0) o2=BUF1(i0)')
|
||||
sim = LogicSim6V(c, 36)
|
||||
expect = mvarray('00010', '01010', '0R010', '0F010', '0P010', '0N010',
|
||||
'10001', '11101', '1RR01', '1FF01', '1PP01', '1NN01',
|
||||
'R00FR', 'R1RFR', 'RRRFR', 'RFPFR', 'RPPFR', 'RNRFR',
|
||||
'F00RF', 'F1FRF', 'FRPRF', 'FFFRF', 'FPPRF', 'FNFRF',
|
||||
'P00NP', 'P1PNP', 'PRPNP', 'PFPNP', 'PPPNP', 'PNPNP',
|
||||
'N00PN', 'N1NPN', 'NRRPN', 'NFFPN', 'NPPPN', 'NNNPN')
|
||||
actual = sim.simulate(expect.copy())
|
||||
np.testing.assert_array_equal(
|
||||
expect[c.io_locs('o')],
|
||||
actual[c.io_locs('o')])
|
||||
|
||||
def test_LogicSim2V_simprims(mydir):
|
||||
c1 = verilog.load(mydir / 'all_kyupy_simprims.v')
|
||||
c2 = verilog.load(mydir / 'all_kyupy_simprims.minimal.v')
|
||||
pi_count = sum([len(n.ins)==0 for n in c1.io_nodes])
|
||||
sim1 = LogicSim2V(c1, sims=2**pi_count)
|
||||
sim2 = LogicSim2V(c2, sims=2**pi_count)
|
||||
tests1 = sim1.allocate()
|
||||
tests1[sim1.pippi_s_locs] = np.fromfunction(lambda x, y: logic.ONE * ((2**x & y) != 0), (pi_count, sim1.sims), dtype=int) # exhaustive test
|
||||
tests2 = tests1.copy()
|
||||
sim1.simulate(tests1)
|
||||
sim2.simulate(tests2)
|
||||
np.testing.assert_array_equal(
|
||||
tests1[c1.io_locs('o')],
|
||||
tests2[c2.io_locs('o')])
|
||||
|
||||
def test_LogicSim4V_simprims(mydir):
|
||||
c1 = verilog.load(mydir / 'all_kyupy_simprims.v')
|
||||
c2 = verilog.load(mydir / 'all_kyupy_simprims.minimal.v')
|
||||
pi_count = sum([len(n.ins)==0 for n in c1.io_nodes])
|
||||
sim1 = LogicSim4V(c1, sims=4**pi_count)
|
||||
sim2 = LogicSim4V(c2, sims=4**pi_count)
|
||||
tests1 = sim1.allocate()
|
||||
tests1[sim1.pippi_s_locs] = np.fromfunction(lambda x, y: (y//(4**x))%4, (pi_count, sim1.sims), dtype=int) # exhaustive test
|
||||
tests2 = tests1.copy()
|
||||
sim1.simulate(tests1)
|
||||
sim2.simulate(tests2)
|
||||
np.testing.assert_array_equal(
|
||||
tests1[c1.io_locs('o')],
|
||||
tests2[c2.io_locs('o')])
|
||||
|
||||
def test_LogicSim6V_simprims(mydir):
|
||||
c1 = verilog.load(mydir / 'all_kyupy_simprims.v')
|
||||
c2 = verilog.load(mydir / 'all_kyupy_simprims.minimal.v')
|
||||
pi_count = sum([len(n.ins)==0 for n in c1.io_nodes])
|
||||
sim1 = LogicSim6V(c1, sims=6**pi_count)
|
||||
sim2 = LogicSim6V(c2, sims=6**pi_count)
|
||||
tests1 = sim1.allocate()
|
||||
tests1[sim1.pippi_s_locs] = logic.mvarray('01RFPN')[np.fromfunction(lambda x, y: (y//(6**x))%6, (pi_count, sim1.sims), dtype=int)] # exhaustive test
|
||||
tests2 = tests1.copy()
|
||||
sim1.simulate(tests1)
|
||||
sim2.simulate(tests2)
|
||||
for loc1, loc2 in zip(c1.io_locs('o'), c2.io_locs('o')):
|
||||
n = c1.s_nodes[loc1]
|
||||
if (tests1[loc1] != tests2[loc2]).any():
|
||||
print(f'Mismatch at output {n}')
|
||||
np.testing.assert_array_equal(
|
||||
tests1[c1.io_locs('o')],
|
||||
tests2[c2.io_locs('o')])
|
||||
|
||||
def test_2v():
|
||||
c = bench.parse(f'''
|
||||
input(i3, i2, i1, i0)
|
||||
@@ -75,7 +178,7 @@ def test_2v():
|
||||
|
||||
def test_4v():
|
||||
c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')
|
||||
s = LogicSim(c, 16, m=8) # FIXME: m=4
|
||||
s = LogicSim(c, 16, m=4)
|
||||
assert s.s_len == 5
|
||||
bpa = bparray(
|
||||
'00---', '01---', '0----', '0X---',
|
||||
@@ -93,6 +196,44 @@ def test_4v():
|
||||
'--0XX', '--X1X', '--XXX', '--XXX',
|
||||
'--0XX', '--X1X', '--XXX', '--XXX'))
|
||||
|
||||
def test_4v_fault():
|
||||
c = bench.parse('input(x, y) output(a) a=and(x,y)')
|
||||
s = LogicSim(c, 16, m=4)
|
||||
assert s.s_len == 3
|
||||
bpa = bparray(
|
||||
'00-', '01-', '0--', '0X-',
|
||||
'10-', '11-', '1--', '1X-',
|
||||
'-0-', '-1-', '---', '-X-',
|
||||
'X0-', 'X1-', 'X--', 'XX-')
|
||||
s.s[0] = bpa
|
||||
s.s_to_c()
|
||||
s.c_prop()
|
||||
s.c_to_s()
|
||||
mva = bp_to_mv(s.s[1])
|
||||
assert_equal_shape_and_contents(mva, mvarray(
|
||||
'--0', '--0', '--0', '--0',
|
||||
'--0', '--1', '--X', '--X',
|
||||
'--0', '--X', '--X', '--X',
|
||||
'--0', '--X', '--X', '--X'))
|
||||
fault_line = s.circuit.cells['a'].ins[0]
|
||||
s.s_to_c()
|
||||
s.c_prop(fault_line=fault_line, fault_model=1)
|
||||
s.c_to_s()
|
||||
mva = bp_to_mv(s.s[1])
|
||||
assert_equal_shape_and_contents(mva, mvarray(
|
||||
'--0', '--1', '--X', '--X',
|
||||
'--0', '--1', '--X', '--X',
|
||||
'--0', '--1', '--X', '--X',
|
||||
'--0', '--1', '--X', '--X'))
|
||||
s.s_to_c()
|
||||
s.c_prop(fault_line=fault_line, fault_model=0)
|
||||
s.c_to_s()
|
||||
mva = bp_to_mv(s.s[1])
|
||||
assert_equal_shape_and_contents(mva, mvarray(
|
||||
'--0', '--0', '--0', '--0',
|
||||
'--0', '--0', '--0', '--0',
|
||||
'--0', '--0', '--0', '--0',
|
||||
'--0', '--0', '--0', '--0'))
|
||||
|
||||
def test_6v():
|
||||
c = bench.parse('input(x, y) output(a, o, n, xo, no) a=AND2(x,y) o=OR2(x,y) n=INV1(x) xo=XOR2(x,y) no=NOR2(x,y)')
|
||||
|
||||
@@ -25,10 +25,11 @@ def test_xnor2_delays():
|
||||
delays[0, 1, 1, 1] = 0.036 # B fall -> Z fall
|
||||
|
||||
simctl_int = np.asarray([0], dtype=np.int32)
|
||||
simctl_float = np.asarray([1], dtype=np.float32)
|
||||
|
||||
def wave_assert(inputs, output):
|
||||
for i, a in zip(inputs, c.reshape(-1,16)): a[:len(i)] = i
|
||||
wave_eval_cpu(op, c, c_locs, c_caps, ebuf, 0, delays, simctl_int, 0, 0)
|
||||
wave_eval_cpu(op, c, c_locs, c_caps, ebuf, 0, delays, simctl_int, simctl_float, 0, 0)
|
||||
for i, v in enumerate(output): np.testing.assert_allclose(c.reshape(-1,16)[2,i], v)
|
||||
|
||||
wave_assert([[TMIN,TMAX],[TMIN,TMAX]], [TMIN,TMAX]) # XNOR(1,1) => 1
|
||||
@@ -40,11 +41,10 @@ def test_xnor2_delays():
|
||||
|
||||
def test_nand_delays():
|
||||
op = (sim.NAND4, 4, 0, 1, 2, 3, -1, 0, 0)
|
||||
#op = (0b0111, 4, 0, 1)
|
||||
c = np.full((5*16, 1), TMAX, dtype=np.float32) # 5 waveforms of capacity 16
|
||||
c_locs = np.zeros((5,), dtype='int')
|
||||
c_caps = np.zeros((5,), dtype='int')
|
||||
ebuf = np.zeros((4, 1, 2), dtype=np.int32)
|
||||
ebuf = np.zeros((5, 1, 2), dtype=np.int32)
|
||||
|
||||
for i in range(5): c_locs[i], c_caps[i] = i*16, 16 # 1:1 mapping
|
||||
|
||||
@@ -63,10 +63,11 @@ def test_nand_delays():
|
||||
delays[0, 3, :, 1] = 0.8
|
||||
|
||||
simctl_int = np.asarray([0], dtype=np.int32)
|
||||
simctl_float = np.asarray([1], dtype=np.float32)
|
||||
|
||||
def wave_assert(inputs, output):
|
||||
for i, a in zip(inputs, c.reshape(-1,16)): a[:len(i)] = i
|
||||
wave_eval_cpu(op, c, c_locs, c_caps, ebuf, 0, delays, simctl_int, 0, 0)
|
||||
wave_eval_cpu(op, c, c_locs, c_caps, ebuf, 0, delays, simctl_int, simctl_float, 0, 0)
|
||||
for i, v in enumerate(output): np.testing.assert_allclose(c.reshape(-1,16)[4,i], v)
|
||||
|
||||
wave_assert([[TMAX,TMAX],[TMAX,TMAX],[TMIN,TMAX],[TMIN,TMAX]], [TMIN,TMAX]) # NAND(0,0,1,1) => 1
|
||||
|
||||
Reference in new issue
Block a user