From d2471157442a1f63eb15631bf06c2e83f41795d8 Mon Sep 17 00:00:00 2001 From: Stefan Holst Date: Thu, 27 Nov 2025 21:35:31 +0900 Subject: [PATCH] for release 0.0.6 - sim: added LogicSim2V, LogicSim4V, LogicSim6V (faster and simpler API) - sim: fixed model for: many-input gates in bench; dangling nodes; POs that drive further logic - Circuit: dot output fix and better style, fixed equivalence check of lines and nodes. - Techlib: Added KYUPY library with supported simulation primitives for debug and testing - tests: fixed and added more for better coverage - more type annotations - updated project config and dependencies for better uv experience, newer lark, newer numpy --- .gitignore | 14 +- .python-version | 1 + .readthedocs.yaml | 4 +- LICENSE.txt | 2 +- README.md | 32 + README.rst | 32 - docs/Makefile | 2 +- docs/conf.py | 5 +- docs/index.rst | 3 +- docs/simulators.rst | 9 + examples/Introduction.ipynb | 1720 +++++++++++++++------------- pyproject.toml | 16 +- src/kyupy/__init__.py | 2 - src/kyupy/bench.py | 42 +- src/kyupy/circuit.py | 64 +- src/kyupy/logic.py | 5 +- src/kyupy/logic_sim.py | 525 ++++++++- src/kyupy/sim.py | 16 +- src/kyupy/techlib.py | 41 +- src/kyupy/verilog.py | 22 +- tests/Makefile | 2 + tests/all_kyupy_simprims.minimal.v | 91 ++ tests/all_kyupy_simprims.v | 53 + tests/kyupy_simprims.genlib | 34 + tests/map_to_minimal.abc | 8 + tests/minimal.genlib | 5 + tests/test_circuit.py | 9 +- tests/test_logic_sim.py | 147 ++- tests/test_wave_sim.py | 9 +- 29 files changed, 2016 insertions(+), 899 deletions(-) create mode 100644 .python-version create mode 100644 README.md delete mode 100644 README.rst create mode 100644 tests/Makefile create mode 100644 tests/all_kyupy_simprims.minimal.v create mode 100644 tests/all_kyupy_simprims.v create mode 100644 tests/kyupy_simprims.genlib create mode 100644 tests/map_to_minimal.abc create mode 100644 tests/minimal.genlib diff --git a/.gitignore b/.gitignore index fb95c5d..82d95e7 100644 --- a/.gitignore +++ b/.gitignore @@ -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* diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..3a4f41e --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 7bcfac9..8f28bd3 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,10 +3,10 @@ version: 2 build: os: "ubuntu-20.04" tools: - python: "3.8" + python: "3.10" 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 diff --git a/LICENSE.txt b/LICENSE.txt index 8e73798..a8e5cd6 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..3284fb6 --- /dev/null +++ b/README.md @@ -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. diff --git a/README.rst b/README.rst deleted file mode 100644 index a00df1b..0000000 --- a/README.rst +++ /dev/null @@ -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 `_ 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 `_ and `numba `_. - - -Getting Started ---------------- - -KyuPy is available in `PyPI `_. -It requires Python 3.8 or newer, `lark-parser `_, and `numpy`_. -Although optional, `numba`_ should be installed for best performance. -GPU/CUDA support in numba may `require some additional setup `_. -If numba is not available, KyuPy will automatically fall back to slow, pure Python execution. - -The Jupyter Notebook `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 `_. -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``. diff --git a/docs/Makefile b/docs/Makefile index 0f632bf..b7e770c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,4 +1,4 @@ -# pip install sphinx sphinx-rtd-theme +# pip install sphinx sphinx-rtd-theme myst-parser # # Minimal makefile for Sphinx documentation # diff --git a/docs/conf.py b/docs/conf.py index 83cafc9..cfdc834 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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. diff --git a/docs/index.rst b/docs/index.rst index 06f085d..1936b13 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,5 @@ -.. include:: ../README.rst +.. include:: ../README.md + :parser: myst_parser.sphinx_ API Reference ------------- diff --git a/docs/simulators.rst b/docs/simulators.rst index 44360f7..487ac1f 100644 --- a/docs/simulators.rst +++ b/docs/simulators.rst @@ -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` ----------------------------------------- diff --git a/examples/Introduction.ipynb b/examples/Introduction.ipynb index 2f0a352..251b27c 100644 --- a/examples/Introduction.ipynb +++ b/examples/Introduction.ipynb @@ -222,231 +222,255 @@ "\n", "\n", - "\n", "\n", - "\n", + "\n", "\n", "%3\n", - "\n", - "\n", - "0\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "0 [4]\n", - "DFFX1\n", - "carry\n", - "\n", - "0\n", - "\n", - "\n", - "1\n", - "\n", - "0\n", - "\n", - "1\n", - "__fork__\n", - "cin\n", - "\n", - "0\n", - "\n", - "\n", - "0:o0->1:i0\n", - "\n", - "\n", - "0\n", - "\n", - "\n", - "5\n", - "\n", - " \n", - "\n", - "5 [0]\n", - "input\n", - "clk\n", - "\n", - "0\n", - "\n", - "\n", - "6\n", - "\n", - "0\n", - "\n", - "6\n", - "__fork__\n", - "clk\n", - "\n", - "0\n", - "\n", - "\n", - "5:o0->6:i0\n", - "\n", - "\n", - "3\n", - "\n", + "\n", "\n", - "7\n", - "\n", - " \n", - "\n", - "7 [1]\n", - "input\n", - "a\n", - "\n", - "0\n", + "\n", + "7\n", + "\n", + " \n", + "\n", + "7 [1]\n", + "input\n", + "a\n", + "\n", + "0\n", "\n", "\n", - "8\n", - "\n", - "0\n", - "\n", - "8\n", - "__fork__\n", - "a\n", - "\n", - "0\n", + "\n", + "8\n", + "\n", + "0\n", + "\n", + "8\n", + "__fork__\n", + "a\n", + "\n", + "0\n", "\n", "\n", - "7:o0->8:i0\n", - "\n", - "\n", - "4\n", + "\n", + "7:o0->8:i0\n", + "\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "0 [4]\n", + "DFFX1\n", + "carry\n", + "\n", + "0\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "0\n", + "\n", + "1\n", + "__fork__\n", + "cin\n", + "\n", + "0\n", + "\n", + "\n", + "\n", + "0:o0->1:i0\n", + "\n", + "\n", + "0\n", "\n", "\n", - "9\n", - "\n", - " \n", - "\n", - "9 [2]\n", - "input\n", - "b\n", - "\n", - "0\n", + "\n", + "9\n", + "\n", + " \n", + "\n", + "9 [2]\n", + "input\n", + "b\n", + "\n", + "0\n", "\n", "\n", - "10\n", - "\n", - "0\n", - "\n", - "10\n", - "__fork__\n", - "b\n", - "\n", - "0\n", + "\n", + "10\n", + "\n", + "0\n", + "\n", + "10\n", + "__fork__\n", + "b\n", + "\n", + "0\n", "\n", "\n", - "9:o0->10:i0\n", - "\n", - "\n", - "5\n", + "\n", + "9:o0->10:i0\n", + "\n", + "\n", + "5\n", "\n", - "\n", - "2\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "2\n", - "\n", - "2\n", - "ADDFX1\n", - "adder\n", - "\n", - "0\n", - "\n", - "1\n", + "\n", + "\n", + "5\n", + "\n", + " \n", + "\n", + "5 [0]\n", + "input\n", + "clk\n", + "\n", + "0\n", "\n", - "\n", - "1:o0->2:i2\n", - "\n", - "\n", - "10\n", + "\n", + "\n", + "6\n", + "\n", + "0\n", + "\n", + "6\n", + "__fork__\n", + "clk\n", + "\n", + "0\n", "\n", - "\n", - "6:o0->0:i0\n", - "\n", - "\n", - "7\n", + "\n", + "\n", + "5:o0->6:i0\n", + "\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "2\n", + "\n", + "2\n", + "ADDFX1\n", + "adder\n", + "\n", + "0\n", + "\n", + "1\n", "\n", "\n", - "8:o0->2:i0\n", - "\n", - "\n", - "8\n", + "\n", + "8:o0->2:i0\n", + "\n", + "\n", + "8\n", + "\n", + "\n", + "\n", + "1:o0->2:i2\n", + "\n", + "\n", + "10\n", "\n", "\n", - "10:o0->2:i1\n", - "\n", - "\n", - "9\n", + "\n", + "10:o0->2:i1\n", + "\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "6:o0->0:i0\n", + "\n", + "\n", + "7\n", "\n", "\n", - "3\n", - "\n", - "0\n", - "\n", - "3\n", - "__fork__\n", - "cout\n", - "\n", - "0\n", + "\n", + "3\n", + "\n", + "0\n", + "\n", + "3\n", + "__fork__\n", + "cout\n", + "\n", + "0\n", "\n", "\n", - "2:o0->3:i0\n", - "\n", - "\n", - "1\n", + "\n", + "2:o0->3:i0\n", + "\n", + "\n", + "1\n", "\n", "\n", - "4\n", - "\n", - "0\n", - "\n", - "4\n", - "__fork__\n", - "s\n", - "\n", - "0\n", + "\n", + "4\n", + "\n", + "0\n", + "\n", + "4\n", + "__fork__\n", + "s\n", + "\n", + "0\n", "\n", "\n", - "2:o1->4:i0\n", - "\n", - "\n", - "2\n", + "\n", + "2:o1->4:i0\n", + "\n", + "\n", + "2\n", "\n", "\n", - "3:o0->0:i1\n", - "\n", - "\n", - "6\n", + "\n", + "3:o0->0:i1\n", + "\n", + "\n", + "6\n", "\n", "\n", - "11\n", - "\n", - "0\n", - "\n", - "11 [3]\n", - "output\n", - "s\n", - "\n", - " \n", + "\n", + "11\n", + "\n", + "0\n", + "\n", + "11 [3]\n", + "output\n", + "s\n", + "\n", + " \n", "\n", "\n", - "4:o0->11:i0\n", - "\n", - "\n", - "11\n", + "\n", + "4:o0->11:i0\n", + "\n", + "\n", + "11\n", "\n", "\n", "\n" ], "text/plain": [ - "" + "" ] }, "execution_count": 7, @@ -1432,14 +1456,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "0:DFFX1\"carry\" <7 <6 >0\n", - "5:input\"clk\" >3\n", "7:input\"a\" >4\n", + "0:DFFX1\"carry\" <7 <6 >0\n", "9:input\"b\" >5\n", - "1:__fork__\"cin\" <0 >10\n", - "6:__fork__\"clk\" <3 >7\n", + "5:input\"clk\" >3\n", "8:__fork__\"a\" <4 >8\n", + "1:__fork__\"cin\" <0 >10\n", "10:__fork__\"b\" <5 >9\n", + "6:__fork__\"clk\" <3 >7\n", "2:ADDFX1\"adder\" <8 <9 <10 >1 >2\n", "3:__fork__\"cout\" <1 >6\n", "4:__fork__\"s\" <2 >11\n", @@ -1564,6 +1588,42 @@ "cell_type": "code", "execution_count": 39, "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7:input\"a\" >4\n", + "5:input\"clk\" >3\n", + "0:DFF\"carry\" <6 <7 >12\n", + "9:input\"b\" >5\n", + "8:__fork__\"a\" <4 >8\n", + "6:__fork__\"clk\" <3 >7\n", + "12:__fork__\"carry~Q\" <12 >0\n", + "10:__fork__\"b\" <5 >9\n", + "13:__fork__\"adder~A\" <8 >14 >20\n", + "1:__fork__\"cin\" <0 >10\n", + "14:__fork__\"adder~B\" <9 >15 >21\n", + "15:__fork__\"adder~CI\" <10 >17 >19\n", + "16:XOR2\"adder~AB\" <14 <15 >13\n", + "17:__fork__\"adder~AB\" <13 >16 >18\n", + "2:XOR2\"adder\" <16 <17 >1\n", + "18:AO22\"adder~S\" <18 <19 <20 <21 >2\n", + "3:__fork__\"cout\" <1 >6\n", + "4:__fork__\"s\" <2 >11\n", + "11:output\"s\" <11\n" + ] + } + ], + "source": [ + "for n in adder.topological_order():\n", + " print(n)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, "outputs": [ { "data": { @@ -1571,383 +1631,424 @@ "\n", "\n", - "\n", "\n", - "\n", - "\n", + "\n", + "\n", "%3\n", - "\n", - "\n", - "0\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "0 [4]\n", - "DFF\n", - "carry\n", - "\n", - "0\n", + "\n", + "\n", + "\n", + "7\n", + "\n", + " \n", + "\n", + "7 [1]\n", + "input\n", + "a\n", + "\n", + "0\n", "\n", - "\n", - "12\n", - "\n", - "0\n", - "\n", - "12\n", - "__fork__\n", - "carry~Q\n", - "\n", - "0\n", + "\n", + "\n", + "8\n", + "\n", + "0\n", + "\n", + "8\n", + "__fork__\n", + "a\n", + "\n", + "0\n", "\n", - "\n", - "0:o0->12:i0\n", - "\n", - "\n", - "12\n", + "\n", + "\n", + "7:o0->8:i0\n", + "\n", + "\n", + "4\n", "\n", "\n", - "5\n", - "\n", - " \n", - "\n", - "5 [0]\n", - "input\n", - "clk\n", - "\n", - "0\n", + "\n", + "5\n", + "\n", + " \n", + "\n", + "5 [0]\n", + "input\n", + "clk\n", + "\n", + "0\n", "\n", "\n", - "6\n", - "\n", - "0\n", - "\n", - "6\n", - "__fork__\n", - "clk\n", - "\n", - "0\n", + "\n", + "6\n", + "\n", + "0\n", + "\n", + "6\n", + "__fork__\n", + "clk\n", + "\n", + "0\n", "\n", "\n", - "5:o0->6:i0\n", - "\n", - "\n", - "3\n", + "\n", + "5:o0->6:i0\n", + "\n", + "\n", + "3\n", "\n", - "\n", - "7\n", - "\n", - " \n", - "\n", - "7 [1]\n", - "input\n", - "a\n", - "\n", - "0\n", + "\n", + "\n", + "0\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "0 [4]\n", + "DFF\n", + "carry\n", + "\n", + "0\n", "\n", - "\n", - "8\n", - "\n", - "0\n", - "\n", - "8\n", - "__fork__\n", - "a\n", - "\n", - "0\n", + "\n", + "\n", + "12\n", + "\n", + "0\n", + "\n", + "12\n", + "__fork__\n", + "carry~Q\n", + "\n", + "0\n", "\n", - "\n", - "7:o0->8:i0\n", - "\n", - "\n", - "4\n", + "\n", + "\n", + "0:o0->12:i0\n", + "\n", + "\n", + "12\n", "\n", "\n", - "9\n", - "\n", - " \n", - "\n", - "9 [2]\n", - "input\n", - "b\n", - "\n", - "0\n", + "\n", + "9\n", + "\n", + " \n", + "\n", + "9 [2]\n", + "input\n", + "b\n", + "\n", + "0\n", "\n", "\n", - "10\n", - "\n", - "0\n", - "\n", - "10\n", - "__fork__\n", - "b\n", - "\n", - "0\n", + "\n", + "10\n", + "\n", + "0\n", + "\n", + "10\n", + "__fork__\n", + "b\n", + "\n", + "0\n", "\n", "\n", - "9:o0->10:i0\n", - "\n", - "\n", - "5\n", + "\n", + "9:o0->10:i0\n", + "\n", + "\n", + "5\n", "\n", - "\n", - "1\n", - "\n", - "0\n", - "\n", - "1\n", - "__fork__\n", - "cin\n", - "\n", - "0\n", + "\n", + "\n", + "13\n", + "\n", + "0\n", + "\n", + "13\n", + "__fork__\n", + "adder~A\n", + "\n", + "0\n", + "\n", + "1\n", "\n", - "\n", - "12:o0->1:i0\n", - "\n", - "\n", - "0\n", + "\n", + "\n", + "8:o0->13:i0\n", + "\n", + "\n", + "8\n", "\n", "\n", - "6:o0->0:i1\n", - "\n", - "\n", - "7\n", + "\n", + "6:o0->0:i1\n", + "\n", + "\n", + "7\n", "\n", - "\n", - "13\n", - "\n", - "0\n", - "\n", - "13\n", - "__fork__\n", - "adder~A\n", - "\n", - "0\n", - "\n", - "1\n", + "\n", + "\n", + "1\n", + "\n", + "0\n", + "\n", + "1\n", + "__fork__\n", + "cin\n", + "\n", + "0\n", "\n", - "\n", - "8:o0->13:i0\n", - "\n", - "\n", - "8\n", + "\n", + "\n", + "12:o0->1:i0\n", + "\n", + "\n", + "0\n", "\n", "\n", - "14\n", - "\n", - "0\n", - "\n", - "14\n", - "__fork__\n", - "adder~B\n", - "\n", - "0\n", - "\n", - "1\n", + "\n", + "14\n", + "\n", + "0\n", + "\n", + "14\n", + "__fork__\n", + "adder~B\n", + "\n", + "0\n", + "\n", + "1\n", "\n", "\n", - "10:o0->14:i0\n", - "\n", - "\n", - "9\n", - "\n", - "\n", - "15\n", - "\n", - "0\n", - "\n", - "15\n", - "__fork__\n", - "adder~CI\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "\n", - "1:o0->15:i0\n", - "\n", - "\n", - "10\n", + "\n", + "10:o0->14:i0\n", + "\n", + "\n", + "9\n", "\n", "\n", - "16\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "16\n", - "XOR2\n", - "adder~AB\n", - "\n", - "0\n", + "\n", + "16\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "16\n", + "XOR2\n", + "adder~AB\n", + "\n", + "0\n", "\n", "\n", - "13:o0->16:i0\n", - "\n", - "\n", - "14\n", + "\n", + "13:o0->16:i0\n", + "\n", + "\n", + "14\n", "\n", "\n", - "18\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "2\n", - "\n", - "3\n", - "\n", - "18\n", - "AO22\n", - "adder~S\n", - "\n", - "0\n", + "\n", + "18\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "2\n", + "\n", + "3\n", + "\n", + "18\n", + "AO22\n", + "adder~S\n", + "\n", + "0\n", "\n", "\n", - "13:o1->18:i2\n", - "\n", - "\n", - "20\n", + "\n", + "13:o1->18:i2\n", + "\n", + "\n", + "20\n", + "\n", + "\n", + "\n", + "15\n", + "\n", + "0\n", + "\n", + "15\n", + "__fork__\n", + "adder~CI\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "1:o0->15:i0\n", + "\n", + "\n", + "10\n", "\n", "\n", - "14:o0->16:i1\n", - "\n", - "\n", - "15\n", + "\n", + "14:o0->16:i1\n", + "\n", + "\n", + "15\n", "\n", "\n", - "14:o1->18:i3\n", - "\n", - "\n", - "21\n", + "\n", + "14:o1->18:i3\n", + "\n", + "\n", + "21\n", "\n", "\n", - "2\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "2\n", - "XOR2\n", - "adder\n", - "\n", - "0\n", + "\n", + "2\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "2\n", + "XOR2\n", + "adder\n", + "\n", + "0\n", "\n", "\n", - "15:o0->2:i1\n", - "\n", - "\n", - "17\n", + "\n", + "15:o0->2:i1\n", + "\n", + "\n", + "17\n", "\n", "\n", - "15:o1->18:i1\n", - "\n", - "\n", - "19\n", + "\n", + "15:o1->18:i1\n", + "\n", + "\n", + "19\n", "\n", "\n", - "17\n", - "\n", - "0\n", - "\n", - "17\n", - "__fork__\n", - "adder~AB\n", - "\n", - "0\n", - "\n", - "1\n", + "\n", + "17\n", + "\n", + "0\n", + "\n", + "17\n", + "__fork__\n", + "adder~AB\n", + "\n", + "0\n", + "\n", + "1\n", "\n", "\n", - "16:o0->17:i0\n", - "\n", - "\n", - "13\n", + "\n", + "16:o0->17:i0\n", + "\n", + "\n", + "13\n", "\n", "\n", - "17:o0->2:i0\n", - "\n", - "\n", - "16\n", + "\n", + "17:o0->2:i0\n", + "\n", + "\n", + "16\n", "\n", "\n", - "17:o1->18:i0\n", - "\n", - "\n", - "18\n", + "\n", + "17:o1->18:i0\n", + "\n", + "\n", + "18\n", "\n", "\n", - "3\n", - "\n", - "0\n", - "\n", - "3\n", - "__fork__\n", - "cout\n", - "\n", - "0\n", + "\n", + "3\n", + "\n", + "0\n", + "\n", + "3\n", + "__fork__\n", + "cout\n", + "\n", + "0\n", "\n", "\n", - "2:o0->3:i0\n", - "\n", - "\n", - "1\n", + "\n", + "2:o0->3:i0\n", + "\n", + "\n", + "1\n", "\n", "\n", - "4\n", - "\n", - "0\n", - "\n", - "4\n", - "__fork__\n", - "s\n", - "\n", - "0\n", + "\n", + "4\n", + "\n", + "0\n", + "\n", + "4\n", + "__fork__\n", + "s\n", + "\n", + "0\n", "\n", "\n", - "18:o0->4:i0\n", - "\n", - "\n", - "2\n", + "\n", + "18:o0->4:i0\n", + "\n", + "\n", + "2\n", "\n", "\n", - "3:o0->0:i0\n", - "\n", - "\n", - "6\n", + "\n", + "3:o0->0:i0\n", + "\n", + "\n", + "6\n", "\n", "\n", - "11\n", - "\n", - "0\n", - "\n", - "11 [3]\n", - "output\n", - "s\n", - "\n", - " \n", + "\n", + "11\n", + "\n", + "0\n", + "\n", + "11 [3]\n", + "output\n", + "s\n", + "\n", + " \n", "\n", "\n", - "4:o0->11:i0\n", - "\n", - "\n", - "11\n", + "\n", + "4:o0->11:i0\n", + "\n", + "\n", + "11\n", "\n", "\n", "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 39, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -1965,7 +2066,45 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "adder.eliminate_1to1_forks()" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7:input\"a\" >4\n", + "5:input\"clk\" >3\n", + "0:DFF\"carry\" <1 <3 >12\n", + "9:input\"b\" >5\n", + "10:__fork__\"adder~A\" <4 >14 >6\n", + "6:__fork__\"adder~CI\" <12 >8 >11\n", + "8:__fork__\"adder~B\" <5 >0 >10\n", + "4:XOR2\"adder~AB\" <14 <0 >13\n", + "3:__fork__\"adder~AB\" <13 >9 >7\n", + "2:XOR2\"adder\" <9 <8 >1\n", + "1:AO22\"adder~S\" <7 <11 <6 <10 >2\n", + "11:output\"s\" <2\n" + ] + } + ], + "source": [ + "for n in adder.topological_order():\n", + " print(n)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, "metadata": {}, "outputs": [ { @@ -1974,270 +2113,309 @@ "\n", "\n", - "\n", "\n", - "\n", - "\n", + "\n", + "\n", "%3\n", - "\n", - "\n", - "0\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "0 [4]\n", - "DFF\n", - "carry\n", - "\n", - "0\n", + "\n", + "\n", + "\n", + "7\n", + "\n", + " \n", + "\n", + "7 [1]\n", + "input\n", + "a\n", + "\n", + "0\n", "\n", - "\n", - "6\n", - "\n", - "0\n", - "\n", - "6\n", - "__fork__\n", - "adder~CI\n", - "\n", - "0\n", - "\n", - "1\n", + "\n", + "\n", + "10\n", + "\n", + "0\n", + "\n", + "10\n", + "__fork__\n", + "adder~A\n", + "\n", + "0\n", + "\n", + "1\n", "\n", - "\n", - "0:o0->6:i0\n", - "\n", - "\n", - "12\n", + "\n", + "\n", + "7:o0->10:i0\n", + "\n", + "\n", + "4\n", "\n", "\n", - "5\n", - "\n", - " \n", - "\n", - "5 [0]\n", - "input\n", - "clk\n", - "\n", - "0\n", - "\n", - "\n", - "5:o0->0:i1\n", - "\n", - "\n", - "3\n", + "\n", + "5\n", + "\n", + " \n", + "\n", + "5 [0]\n", + "input\n", + "clk\n", + "\n", + "0\n", + "\n", + "\n", + "\n", + "_3_\n", + "\n", + "_3_\n", + "\n", + "\n", + "\n", + "5:o0->_3_\n", + "\n", + "\n", + "3\n", "\n", - "\n", - "7\n", - "\n", - " \n", - "\n", - "7 [1]\n", - "input\n", - "a\n", - "\n", - "0\n", + "\n", + "\n", + "0\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "0 [4]\n", + "DFF\n", + "carry\n", + "\n", + "0\n", "\n", - "\n", - "10\n", - "\n", - "0\n", - "\n", - "10\n", - "__fork__\n", - "adder~A\n", - "\n", - "0\n", - "\n", - "1\n", + "\n", + "\n", + "6\n", + "\n", + "0\n", + "\n", + "6\n", + "__fork__\n", + "adder~CI\n", + "\n", + "0\n", + "\n", + "1\n", "\n", - "\n", - "7:o0->10:i0\n", - "\n", - "\n", - "4\n", + "\n", + "\n", + "0:o0->6:i0\n", + "\n", + "\n", + "12\n", "\n", "\n", - "9\n", - "\n", - " \n", - "\n", - "9 [2]\n", - "input\n", - "b\n", - "\n", - "0\n", + "\n", + "9\n", + "\n", + " \n", + "\n", + "9 [2]\n", + "input\n", + "b\n", + "\n", + "0\n", "\n", "\n", - "8\n", - "\n", - "0\n", - "\n", - "8\n", - "__fork__\n", - "adder~B\n", - "\n", - "0\n", - "\n", - "1\n", + "\n", + "8\n", + "\n", + "0\n", + "\n", + "8\n", + "__fork__\n", + "adder~B\n", + "\n", + "0\n", + "\n", + "1\n", "\n", "\n", - "9:o0->8:i0\n", - "\n", - "\n", - "5\n", + "\n", + "9:o0->8:i0\n", + "\n", + "\n", + "5\n", "\n", - "\n", - "2\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "2\n", - "XOR2\n", - "adder\n", - "\n", - "0\n", + "\n", + "\n", + "4\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "4\n", + "XOR2\n", + "adder~AB\n", + "\n", + "0\n", "\n", - "\n", - "6:o0->2:i1\n", - "\n", - "\n", - "8\n", + "\n", + "\n", + "10:o0->4:i0\n", + "\n", + "\n", + "14\n", "\n", "\n", - "1\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "2\n", - "\n", - "3\n", - "\n", - "1\n", - "AO22\n", - "adder~S\n", - "\n", - "0\n", - "\n", - "\n", - "6:o1->1:i1\n", - "\n", - "\n", - "11\n", + "\n", + "1\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "2\n", + "\n", + "3\n", + "\n", + "1\n", + "AO22\n", + "adder~S\n", + "\n", + "0\n", "\n", "\n", - "10:o1->1:i2\n", - "\n", - "\n", - "6\n", + "\n", + "10:o1->1:i2\n", + "\n", + "\n", + "6\n", "\n", - "\n", - "4\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "4\n", - "XOR2\n", - "adder~AB\n", - "\n", - "0\n", + "\n", + "\n", + "2\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "2\n", + "XOR2\n", + "adder\n", + "\n", + "0\n", "\n", - "\n", - "10:o0->4:i0\n", - "\n", - "\n", - "14\n", + "\n", + "\n", + "6:o0->2:i1\n", + "\n", + "\n", + "8\n", + "\n", + "\n", + "\n", + "6:o1->1:i1\n", + "\n", + "\n", + "11\n", "\n", "\n", - "8:o0->4:i1\n", - "\n", - "\n", - "0\n", + "\n", + "8:o0->4:i1\n", + "\n", + "\n", + "0\n", "\n", "\n", - "8:o1->1:i3\n", - "\n", - "\n", - "10\n", + "\n", + "8:o1->1:i3\n", + "\n", + "\n", + "10\n", "\n", "\n", - "3\n", - "\n", - "0\n", - "\n", - "3\n", - "__fork__\n", - "adder~AB\n", - "\n", - "0\n", - "\n", - "1\n", + "\n", + "3\n", + "\n", + "0\n", + "\n", + "3\n", + "__fork__\n", + "adder~AB\n", + "\n", + "0\n", + "\n", + "1\n", "\n", "\n", - "4:o0->3:i0\n", - "\n", - "\n", - "13\n", - "\n", - "\n", - "3:o1->1:i0\n", - "\n", - "\n", - "7\n", + "\n", + "4:o0->3:i0\n", + "\n", + "\n", + "13\n", "\n", "\n", - "3:o0->2:i0\n", - "\n", - "\n", - "9\n", + "\n", + "3:o0->2:i0\n", + "\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "3:o1->1:i0\n", + "\n", + "\n", + "7\n", "\n", "\n", - "2:o0->0:i0\n", - "\n", - "\n", - "1\n", + "\n", + "2:o0->0:i0\n", + "\n", + "\n", + "1\n", "\n", "\n", - "11\n", - "\n", - "0\n", - "\n", - "11 [3]\n", - "output\n", - "s\n", - "\n", - " \n", + "\n", + "11\n", + "\n", + "0\n", + "\n", + "11 [3]\n", + "output\n", + "s\n", + "\n", + " \n", "\n", "\n", - "1:o0->11:i0\n", - "\n", - "\n", - "2\n", + "\n", + "1:o0->11:i0\n", + "\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "_3_->0:i1\n", + "\n", + "\n", + "3\n", "\n", "\n", "\n" ], "text/plain": [ - "" + "" ] }, - "execution_count": 40, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "adder.eliminate_1to1_forks()\n", "adder.dot()" ] }, @@ -2250,7 +2428,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 44, "metadata": {}, "outputs": [ { @@ -2278,7 +2456,7 @@ " '__seq__': 417}" ] }, - "execution_count": 41, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -2299,7 +2477,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 45, "metadata": {}, "outputs": [ { @@ -2488,7 +2666,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 46, "metadata": {}, "outputs": [ { @@ -2501,7 +2679,7 @@ " 0:DFF\"carry\" <1 <3 >12]" ] }, - "execution_count": 43, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -2523,7 +2701,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 47, "metadata": {}, "outputs": [ { @@ -2536,7 +2714,7 @@ " [0, 0, 0, 0, 3, 3, 3, 3]], dtype=uint8)" ] }, - "execution_count": 44, + "execution_count": 47, "metadata": {}, "output_type": "execute_result" } @@ -2562,7 +2740,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 48, "metadata": {}, "outputs": [ { @@ -2571,7 +2749,7 @@ "(5, 8)" ] }, - "execution_count": 45, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } @@ -2589,7 +2767,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 49, "metadata": {}, "outputs": [ { @@ -2620,7 +2798,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 50, "metadata": {}, "outputs": [], "source": [ @@ -2633,7 +2811,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 51, "metadata": {}, "outputs": [ { @@ -2642,7 +2820,7 @@ "528" ] }, - "execution_count": 48, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -2653,7 +2831,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 52, "metadata": {}, "outputs": [ { @@ -2662,7 +2840,7 @@ "(528, 678)" ] }, - "execution_count": 49, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } @@ -2673,7 +2851,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 53, "metadata": {}, "outputs": [ { @@ -2682,7 +2860,7 @@ "(528, 678)" ] }, - "execution_count": 50, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } @@ -2693,7 +2871,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 54, "metadata": {}, "outputs": [ { @@ -2714,7 +2892,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 55, "metadata": {}, "outputs": [ { @@ -2749,7 +2927,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 56, "metadata": {}, "outputs": [], "source": [ @@ -2768,18 +2946,18 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 57, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "------------------------------------------------------------------------------------------------------X----XX--000110110011010011101101001011010010110100101101001001100110011001100110011001100011001100110011001100110011001110011001100110011001100110011001011101001011010010110100101101001011010010110100101101001011101100110011001101100110000010010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101011100110011001100110011001100110011111100001001011000100\n", - "--------------------------------------RFRRRRFRRFRRRR1R--------RXXRXRRR--------------------------------00F0F00--RR1RRXRF00011010XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX011X00XXX101X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X0XXXXXXX1XXXXXXXXXXXXXXXXXXXXXXXXRRXRXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX00000000000000000111111000001001RFXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX11XXXXX\n", - "--------------------------------------1X111XX11X1XX1X11XX11XX111X11X11--------------------------------0-XXR00--F1FF1X0110011X01XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXFXX1XFFX1XFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFX0XFXFXFXF11110XX1XX1XXXXXXX1XXXX1XFR1F1XXXXXXXXXXXXXXXXX011101111000101101110111110001001000100010101011011001110111000X1XX11XXXXXXXX11XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX000X011111111111111111111111111111R0F1R0R001RR100FF1XXX\n", - "--------------------------------------------------------------XXXXXXXX--------------------------------0--FX00--F11F1XFRR00R01111XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0XX1XXXXXRX1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX111111111111111111111110X0XX10XXXXXXXXXRXXXRXRF1FFFRFRXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1XXXXXXXXXXXXX0XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX11X1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX011001010010111111111\n", - "--------------------------------------------------------------FFXFFFFF--------------------------------0XXRR00--RF01FXRF00001XX1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX010X010111111111X111111111111111X11100XX0101XXXXXX01XXXX01XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1X1111X110111111XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0RXRXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1XXXXXX\n" + "------------------------------------------------------------------------------------------------------0----00--000110110011010011101101001011010010110100101101001001100110011001100110011001100011001100110011001100110011001110011001100110011001100110011001011101001011010010110100101101001011010010110100101101001011101100110011001101100110000010010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101011100110011001100110011001100110011111100001001011000100\n", + "--------------------------------------RFRRRRFRRFRRRR1R--------R11R1RRR--------------------------------00F0F00--RR1RRXRF00011010XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX011X00XXX101X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X1X0XXXXXXX1XXXXXXXXXXXXXXXXXXXXXXXXRRXRXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX00000000000000000111111000001001RFX1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1X0X0X1XXXXXXX11XXXXX\n", + "--------------------------------------10111001101001011001100111011011--------------------------------0-10R00--F1FF1X0110011001011X1X1X0X1X1X0X1X1X1X0X0X1X1X0X0X101001011001110XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXFXX1XFFX1XFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFXFX0XFXFXFXF11110XX1XX1XXXXXXX1XXXX1XFR1F1XXXXXXXXXXXXXXXXX011101111000101101110111110001001000100010101011011001110111000X1XX11XXXXXXXX11XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX000X011111111111111111111111111111R0F1R0R001RR100FF1XXX\n", + "--------------------------------------------------------------01100100--------------------------------0--F000--F11F1XFRR00R01111XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0XX1XXXXXRX1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX111111111111111111111110X0XX10XXXXXXXX0R000R0RF1FFFRFRXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1XXXXXXXXXXXXX0XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX11X1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX011001010010111111111\n", + "--------------------------------------------------------------FF1FFFFF--------------------------------000RR00--RF01FXRF00001XX1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX010X010111111111X111111111111111X11100XX0101XXXXXX01XXXX01XXXXXXXXXXXXXXXXXXXXXXXXXXXX11X1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1X1111X110111111XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0RXRXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1X1X0X1XXXXXXX1XXXXXX\n" ] } ], @@ -2789,7 +2967,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 58, "metadata": {}, "outputs": [ { @@ -2817,7 +2995,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 59, "metadata": {}, "outputs": [], "source": [ @@ -2831,7 +3009,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 60, "metadata": {}, "outputs": [ { @@ -2864,7 +3042,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 61, "metadata": {}, "outputs": [], "source": [ @@ -2873,7 +3051,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 62, "metadata": {}, "outputs": [ { @@ -2882,7 +3060,7 @@ "(528, 3, 85)" ] }, - "execution_count": 59, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" } @@ -2902,7 +3080,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 63, "metadata": {}, "outputs": [ { @@ -2911,7 +3089,7 @@ "(528, 680)" ] }, - "execution_count": 60, + "execution_count": 63, "metadata": {}, "output_type": "execute_result" } @@ -2931,7 +3109,7 @@ }, { "cell_type": "code", - "execution_count": 61, + "execution_count": 64, "metadata": {}, "outputs": [], "source": [ @@ -2947,7 +3125,7 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 65, "metadata": {}, "outputs": [ { @@ -2981,7 +3159,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 66, "metadata": {}, "outputs": [ { @@ -2990,7 +3168,7 @@ "677" ] }, - "execution_count": 63, + "execution_count": 66, "metadata": {}, "output_type": "execute_result" } @@ -3010,7 +3188,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": 67, "metadata": {}, "outputs": [], "source": [ @@ -3024,7 +3202,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 68, "metadata": {}, "outputs": [ { @@ -3054,7 +3232,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 69, "metadata": {}, "outputs": [ { @@ -3087,7 +3265,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 70, "metadata": {}, "outputs": [ { @@ -3096,7 +3274,7 @@ "1146" ] }, - "execution_count": 67, + "execution_count": 70, "metadata": {}, "output_type": "execute_result" } @@ -3127,7 +3305,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 71, "metadata": {}, "outputs": [], "source": [ @@ -3146,7 +3324,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 72, "metadata": {}, "outputs": [ { @@ -3155,7 +3333,7 @@ "(32032, 2, 2)" ] }, - "execution_count": 69, + "execution_count": 72, "metadata": {}, "output_type": "execute_result" } @@ -3173,7 +3351,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 73, "metadata": {}, "outputs": [ { @@ -3182,7 +3360,7 @@ "79010" ] }, - "execution_count": 70, + "execution_count": 73, "metadata": {}, "output_type": "execute_result" } @@ -3210,7 +3388,7 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 74, "metadata": {}, "outputs": [], "source": [ @@ -3228,17 +3406,17 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 75, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Waveforms : 66856.0 kiB\n", + "Waveforms : 65996.0 kiB\n", "State Allocation Table : 134.3 kiB\n", "Circuit Timing : 1074.1 kiB\n", - "Circuit Netlist : 1171.2 kiB\n", + "Circuit Netlist : 1156.0 kiB\n", "Sequential State : 726.0 kiB\n" ] } @@ -3265,21 +3443,23 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 76, "metadata": {}, "outputs": [], "source": [ "from kyupy import batchrange\n", "import numpy as np\n", + "from numba import config\n", + "config.CUDA_LOW_OCCUPANCY_WARNINGS = False\n", "\n", - "sims = 128 # transition_tests_zf.shape[-1] # Feel free to simulate all tests if CUDA is set up correctly.\n", + "sims = 128 #transition_tests_zf.shape[-1] # Feel free to simulate all tests if CUDA is set up correctly.\n", "\n", "wsim_results = np.zeros((11, wsim.s_len, sims)) # space to store all simulation results\n", "\n", "for offset, size in batchrange(sims, wsim.sims):\n", - " wsim.s[0] = (transition_tests_zf[:,offset:offset+size] >> 1) & 1 # initial value (bit 1)\n", - " wsim.s[1] = 0.0 # transition time\n", - " wsim.s[2] = transition_tests_zf[:,offset:offset+size] & 1 # final value (bit 0)\n", + " wsim.s[0,:,:size] = (transition_tests_zf[:,offset:offset+size] >> 1) & 1 # initial value (bit 1)\n", + " wsim.s[1,:,:size] = 0.0 # transition time\n", + " wsim.s[2,:,:size] = transition_tests_zf[:,offset:offset+size] & 1 # final value (bit 0)\n", " wsim.s_to_c()\n", " wsim.c_prop(sims=size)\n", " wsim.c_to_s(time=1.5) # capture at time 1.5\n", @@ -3308,7 +3488,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 77, "metadata": {}, "outputs": [ { @@ -3317,7 +3497,7 @@ "(11, 528, 128)" ] }, - "execution_count": 74, + "execution_count": 77, "metadata": {}, "output_type": "execute_result" } @@ -3335,7 +3515,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 78, "metadata": {}, "outputs": [ { @@ -3364,7 +3544,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 79, "metadata": {}, "outputs": [ { @@ -3373,7 +3553,7 @@ "127" ] }, - "execution_count": 76, + "execution_count": 79, "metadata": {}, "output_type": "execute_result" } @@ -3393,7 +3573,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 80, "metadata": {}, "outputs": [ { @@ -3402,7 +3582,7 @@ "1.0424000024795532" ] }, - "execution_count": 77, + "execution_count": 80, "metadata": {}, "output_type": "execute_result" } @@ -3420,7 +3600,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 81, "metadata": {}, "outputs": [ { @@ -3429,7 +3609,7 @@ "0.0" ] }, - "execution_count": 78, + "execution_count": 81, "metadata": {}, "output_type": "execute_result" } @@ -3447,7 +3627,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 82, "metadata": {}, "outputs": [ { @@ -3456,7 +3636,7 @@ "0" ] }, - "execution_count": 79, + "execution_count": 82, "metadata": {}, "output_type": "execute_result" } @@ -3489,7 +3669,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.12.2" }, "vscode": { "interpreter": { diff --git a/pyproject.toml b/pyproject.toml index b8aaecb..b95cf27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.10,<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", +] + diff --git a/src/kyupy/__init__.py b/src/kyupy/__init__.py index b473546..a7e9e8b 100644 --- a/src/kyupy/__init__.py +++ b/src/kyupy/__init__.py @@ -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() diff --git a/src/kyupy/bench.py b/src/kyupy/bench.py index aeec5a2..d678db3 100644 --- a/src/kyupy/bench.py +++ b/src/kyupy/bench.py @@ -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): diff --git a/src/kyupy/circuit.py b/src/kyupy/circuit.py index e387ffc..228ecd4 100644 --- a/src/kyupy/circuit.py +++ b/src/kyupy/circuit.py @@ -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}' for i in range(len(n.ins))]) - outs = '|'.join([f'{i}' for i in range(len(n.outs))]) + ins = '{' + '|'.join([f'{i}' for i in range(len(n.ins))]) + '}|' if len(n.ins) > 1 else '' + outs = '|{' + '|'.join([f'{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 diff --git a/src/kyupy/logic.py b/src/kyupy/logic.py index 3e97f8e..c850e27 100644 --- a/src/kyupy/logic.py +++ b/src/kyupy/logic.py @@ -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. diff --git a/src/kyupy/logic_sim.py b/src/kyupy/logic_sim.py index 3ac5233..42798d2 100644 --- a/src/kyupy/logic_sim.py +++ b/src/kyupy/logic_sim.py @@ -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 diff --git a/src/kyupy/sim.py b/src/kyupy/sim.py index 32b7459..c1dfcae 100644 --- a/src/kyupy/sim.py +++ b/src/kyupy/sim.py @@ -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 diff --git a/src/kyupy/techlib.py b/src/kyupy/techlib.py index 6304a56..ab5ea8e 100644 --- a/src/kyupy/techlib.py +++ b/src/kyupy/techlib.py @@ -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) ; @@ -326,7 +365,7 @@ It defines all cells except: negative-edge flip-flops, tri-state, latches, clock SAED90 = TechLib(r""" NBUFFX{2,4,8,16,32}$ input(INP) output(Z) Z=BUF1(INP) ; AOBUFX{1,2,4}$ input(INP) output(Z) Z=BUF1(INP) ; -DELLN{1,2,3}X2$ input(INP) output(Z)Z=BUF1(INP) ; +DELLN{1,2,3}X2$ input(INP) output(Z) Z=BUF1(INP) ; INVX{0,1,2,4,8,16,32}$ input(INP) output(ZN) ZN=INV1(INP) ; AOINVX{1,2,4}$ input(INP) output(ZN) ZN=INV1(INP) ; diff --git a/src/kyupy/verilog.py b/src/kyupy/verilog.py index bcb982f..4aaf411 100644 --- a/src/kyupy/verilog.py +++ b/src/kyupy/verilog.py @@ -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. diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..472d104 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,2 @@ +all: + abc -F map_to_minimal.abc \ No newline at end of file diff --git a/tests/all_kyupy_simprims.minimal.v b/tests/all_kyupy_simprims.minimal.v new file mode 100644 index 0000000..17ebbd9 --- /dev/null +++ b/tests/all_kyupy_simprims.minimal.v @@ -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 + + diff --git a/tests/all_kyupy_simprims.v b/tests/all_kyupy_simprims.v new file mode 100644 index 0000000..3279512 --- /dev/null +++ b/tests/all_kyupy_simprims.v @@ -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 \ No newline at end of file diff --git a/tests/kyupy_simprims.genlib b/tests/kyupy_simprims.genlib new file mode 100644 index 0000000..c7b19b1 --- /dev/null +++ b/tests/kyupy_simprims.genlib @@ -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 \ No newline at end of file diff --git a/tests/map_to_minimal.abc b/tests/map_to_minimal.abc new file mode 100644 index 0000000..4e52091 --- /dev/null +++ b/tests/map_to_minimal.abc @@ -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 diff --git a/tests/minimal.genlib b/tests/minimal.genlib new file mode 100644 index 0000000..7cc487a --- /dev/null +++ b/tests/minimal.genlib @@ -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 \ No newline at end of file diff --git a/tests/test_circuit.py b/tests/test_circuit.py index d4edcd3..834c53f 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -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(): diff --git a/tests/test_logic_sim.py b/tests/test_logic_sim.py index 85a11ff..72095a8 100644 --- a/tests/test_logic_sim.py +++ b/tests/test_logic_sim.py @@ -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)') diff --git a/tests/test_wave_sim.py b/tests/test_wave_sim.py index 2510c28..b7b6676 100644 --- a/tests/test_wave_sim.py +++ b/tests/test_wave_sim.py @@ -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