Compare commits

...

229 Commits
main ... devel

Author SHA1 Message Date
Stefan Holst ea2ef1c3ec update rtd build image 3 weeks ago
Stefan Holst aabef17eea parse sky130 def files 3 weeks ago
stefan f0c10633bf disconnect unused clk from dff to prevent spurious sanity check fails 1 month ago
stefan ed9b5c6cd6 fix sim scheduler for circuits with POs that are also used internally 1 month ago
stefan e19463d249 fix LogicSim for 2v, deterministic sim schedule, towards sub-sims for partitions 1 month ago
stefan 7a69ce9016 sort times 1 month ago
stefan f5af7ec3d9 typing and docs for interpret, mvarray 1 month ago
stefan 53309f9e59 simple incremental sim using dirty flags 1 month ago
stefan c90bc89522 construct input connected to output on passthrough connection 1 month ago
stefan bda89dc1b0 support for simple text-based pattern format 1 month ago
stefan 449bf9a031 more flexible progress logging 2 months ago
stefan df463826ae fix ffr 2 months ago
stefan aebdf9540d pin_name type annotations 2 months ago
stefan 9dc58f8c40 dev deps 2 months ago
stefan e8a9025c56 fix dot graph construction 2 months ago
stefan 8aa0f09254 logic_sim: better docs, deprecate LogicSim, move tests to newer classes 2 months ago
stefan 4f7e605819 use tlib functions to identify ffs and latches 2 months ago
stefan c1ea05555e fix verilog tests 2 months ago
stefan e7b5567d87 let SimOps refuse building broken sim models. 2 months ago
stefan 3a2cfb7788 techlib decides on cell type comb/dff/latch 2 months ago
Stefan Holst ca25942b27 better type annotation, always return list 2 months ago
Stefan Holst f863b70457 use float32 for delays, more robust node-port name splitting 2 months ago
stefan bd23dfb016 get a techlib by name 3 months ago
stefan d3d06722c1 faster mv xor and new merge fct 3 months ago
stefan 1f984c9bd9 faster fanout generator function 3 months ago
stefan 0c8a2b7746 add return type 3 months ago
stefan f66953f2d3 fix sim model gen for undriven outputs 3 months ago
stefan 7203145a1d better docs and repr 3 months ago
stefan 32e0766d41 s_len param for vcd 3 months ago
stefan 9570161744 fix half adder 3 months ago
stefan a594e2fb9a annotation and repr 3 months ago
stefan 1bc9dc0e9d bench parsing docs 3 months ago
Stefan Holst c5bdeb4a68 add type annotation 3 months ago
Stefan Holst 67db64611b fix cuda launch expecting pure ints for grid spec 3 months ago
stefan 7a9ed1e536 vcd improvements 3 months ago
stefan fcd9b78c24 rm test.py 3 months ago
stefan f975765bbb first version of a vcd parser 3 months ago
stefan 3cb1b2bc86 first version of a vcd parser 3 months ago
stefan c4aecbd6c9 skywater 130nm tech library 3 months ago
stefan 25ff1fb2dc fixups for empty standard cells 3 months ago
Stefan Holst f0b55eed27 version bump 8 months ago
Stefan Holst 3700ae9b33 need 3.12 for generic types 8 months ago
Stefan Holst df695e0aa1 for release 0.0.6 8 months ago
Stefan Holst a28128830d beginning simple always block parsing in verilog 8 months ago
Stefan Holst 20cf441abb add doc gen dependencies 9 months ago
Stefan Holst 5ac21edbb1 migrate readme to markdown 9 months ago
Stefan Holst f0d74777af add dff to techlib 9 months ago
Stefan Holst 3848665533 ignore more generated python files 9 months ago
Stefan Holst 541cccd756 fix sim model for PO that also drive further logic 9 months ago
Stefan Holst fed66f85cb fix oob in test 9 months ago
Stefan Holst 2eb10f83b0 add pytest dev dependency 9 months ago
Stefan Holst dd4e5c861a fix sim model generation for circuits with dangling nodes 9 months ago
Stefan Holst 9cebbcce8f support for storing responses separately 9 months ago
stefan 31434cfd51 4v sim, fixed 6v sim, better testing 9 months ago
stefan ce94210b52 adding more types 9 months ago
Stefan Holst 09420fd72c new logic sim interface prototype, simprim lib and test 9 months ago
Stefan Holst 5bcc1c1d77 new synthetic techlib with kyupy simprims 9 months ago
Stefan Holst df8a58f57b generate proper simprims in bench parser 9 months ago
Stefan Holst 3dfd0cb60d euivalence test, op format doc 9 months ago
Stefan Holst 21439e3595 circuit objects and nodes/lines of different circuits are not quivalent. 9 months ago
Stefan Holst 88a5c79161 dot fix and updated intro nb 10 months ago
Stefan Holst 9ca437fe47 fault injection for 4v sim, wave_sim test fix 10 months ago
Stefan Holst 0cdaf71963 fix for latest lark 10 months ago
Stefan Holst 75972d7bb2 numba config deprecated 10 months ago
Stefan Holst 469dc18aa9 lark dependency update 10 months ago
stefan 33addab141 fix output with newest numpy 10 months ago
Stefan Holst 674f3dea4a Merge branch 'main' into devel 1 year ago
Stefan Holst 187d176cfd partial ternary if support 1 year ago
Stefan Holst ba1de0bea9 support for per-simulation delay factors 2 years ago
Stefan Holst 5769aa3716 deactivate delta sim 2 years ago
Stefan Holst e97c370d39 support stuck-at fault model injection for 2-valued logic sim 2 years ago
Stefan Holst 3b7106be80 fix deferred assignments 2 years ago
Stefan Holst d2357859f6 more robust matching and assign processing 2 years ago
Stefan Holst 53629c5c28 support injection into specific sims 2 years ago
Stefan Holst e64845e8c0 fanout generator 2 years ago
Stefan Holst da98ca2db7 signal flips in compiled code 2 years ago
Stefan Holst deb4599206 fix tests 2 years ago
Stefan Holst c1c9ec9aae pin_name, cleanup legacy code 2 years ago
Stefan Holst 4c55dcec60 delta sim for improving fault sim performance 2 years ago
Stefan Holst a4b7364478 mux21 in 6v logic sim, more test fixtures 2 years ago
Stefan Holst f59e97afa9 remove hashes, add lst, overflow, ebuf 2 years ago
Stefan Holst f6baf9cb5e a fast 6v sim 2 years ago
Stefan Holst fc030c6708 allow interconnect annotations without forks 2 years ago
Stefan Holst 795cac0716 initial and final values from mvarrays 2 years ago
Stefan Holst 3a8777e0a3 none-filtering iterator for GrowingList 2 years ago
Stefan Holst 68e8cb844a pass line id to inject_cb 3 years ago
Stefan Holst 1a3b91c1c0 fix comment 3 years ago
Stefan Holst aa7536b8b0 line use and diff 3 years ago
Stefan Holst fccf5e0d84 fix log limit 3 years ago
Stefan Holst a6d1e4099c alap toposort, improve tests 3 years ago
Stefan Holst 1654915ed6 support for partial re-sim 3 years ago
Stefan Holst d2a2484efa fix fault injection 3 years ago
Stefan Holst de79393dfc fix log limiter, use eng notation 3 years ago
Stefan Holst 4bb3f3424a cond in sdf parser. ignored for now. 3 years ago
Stefan Holst a6243b43f6 keep s_nodes 3 years ago
Stefan Holst baeb759824 types, perf op growing list, keep s_nodes 3 years ago
Stefan Holst 967a232b1c fix pulse threshold selection 3 years ago
Stefan Holst 8096416b0e save test position for each pattern 3 years ago
Stefan Holst a4cce9f8c0 Produce stable value when trans. to/from - 3 years ago
Stefan Holst 4f6b733eb4 fix NanGate variants, version bump 3 years ago
Stefan Holst 371bc906b3 Merge branch 'main' into devel 3 years ago
Stefan Holst 0ade89defa remove old test data, intro check 3 years ago
Stefan Holst 7f4026f504 def-file docs 3 years ago
Stefan Holst e6a0d59d44 def-file docs 3 years ago
Stefan Holst 63e5f32e21 better ignore 3 years ago
Stefan Holst 35e727e714 better docs, new techlib as default, fix tests 3 years ago
Stefan Holst 83445e2bbd support for newer NANGATE lib 3 years ago
Stefan Holst c67148c0ee doc fix 3 years ago
Stefan Holst 280c425486 fix test 3 years ago
Stefan Holst 5be82da49a avoid holes in forks, update intro 3 years ago
Stefan Holst b3dbe9765a fix xor in libs, remove old code 3 years ago
Stefan Holst 5e573b0408 fix substitute for inputs with fo, dot graph 3 years ago
Stefan Holst 08d9f5a9bf one-bit busses 3 years ago
Stefan Holst b098fb219d fix for unconnected named pins, double-declaration 3 years ago
Stefan Holst 97387e962b add GSC180nm 3 years ago
Stefan Holst f4d875f7e5 docs 3 years ago
Stefan Holst cf9a98b5ce del deprecated sdf code, explicit tlib use 3 years ago
Stefan Holst d8f605a47a fix double-free when fo goes to same cell 3 years ago
Stefan Holst ec5626b8ca remove old connections in substitute node reuse 3 years ago
Stefan Holst 5a693f7b9b preserve node order during resolve 3 years ago
Stefan Holst 19bbe2c260 update intro 3 years ago
Stefan Holst d3897246c5 move resolving cells to circuit, more doc 3 years ago
Stefan Holst 9bda7a4c57 capitalize tech libs 3 years ago
Stefan Holst 2270a9eee7 fix fork stripping + fork None values 3 years ago
Stefan Holst ea45a326ec add latch, fix xor delays, improve test 3 years ago
Stefan Holst 1e9fe7707b saed32nm 3 years ago
Stefan Holst 50a5d8a290 one cell inherits name in substitute, sim fix 3 years ago
Stefan Holst d97555e9e9 fix simprim cells, add saed90 3 years ago
Stefan Holst 47ee8d5878 improve substitute, update notebook output 3 years ago
Stefan Holst c32584fc76 1to1 fork optimization, fix substitute 3 years ago
Stefan Holst 39b8c1695b full constants support, fix signal declarations 3 years ago
Stefan Holst 80d26b6f0b Add AO*211 and OA*211, fix MUX21 3 years ago
Stefan Holst f7ef78e58d support for limiting log messages 3 years ago
Stefan Holst 7afb13b33b mv_str for single values, remove undue assert 3 years ago
Stefan Holst 153442a10a def file parser 3 years ago
Stefan Holst afb0a64953 wsa accumulation in wavesim 3 years ago
Stefan Holst c49667edc1 remove old code, verilog positional pins 3 years ago
Stefan Holst d921eb5048 sim support for remaining primitives 3 years ago
Stefan Holst 670fb0b3fc circuit node substitution 3 years ago
Stefan Holst f8bf579be2 support concat, bus select, ISOL cells 3 years ago
Stefan Holst f61e2b42e8 support more cells in logic sim 3 years ago
Stefan Holst 4aec335abb verilog: concat assignments, more comments 3 years ago
Stefan Holst 1a9cb396bf tweak repr, doc 3 years ago
Stefan Holst 3875dc38f9 docs 3 years ago
Stefan Holst ecb7171c37 docs 3 years ago
Stefan Holst 8957db48ab docs 3 years ago
Stefan Holst 0b15f9fa18 doc improvements 3 years ago
Stefan Holst dc76a9f517 new into demo 3 years ago
Stefan Holst 0968cb451e docs, fix stil unassigned, fix io_locs for busses 3 years ago
Stefan Holst 947df89434 add AOI21 to logic sim 3 years ago
Stefan Holst f17e461fdd fix reading directly from file handle 3 years ago
Stefan Holst d6d981a351 support for det vars 3 years ago
Stefan Holst 7a060b1831 support for static variations 3 years ago
Stefan Holst 03802ac9f8 make sims pickleable 3 years ago
Stefan Holst 70caea065e more cleanup 3 years ago
Stefan Holst f04f1b0012 cleanup 3 years ago
Stefan Holst 44b0c887d7 random sampling of delays 3 years ago
Stefan Holst 4e2022291e fix cuda ppo_to_ppi 3 years ago
Stefan Holst 5566b80e52 simprim, vat refactor, batchrange 3 years ago
stefan 63c0b48537 bump 3 years ago
stefan 6520ee23ef cleanup and new intro notebook 3 years ago
stefan 1810d40959 pytest work without cuda 3 years ago
Stefan Holst 7430ebb068 jitted logic sim 3 years ago
stefan 89f317b463 better circuit statsu, 2v logic sim 3 years ago
Stefan Holst 753ce566e4 Timer improvements, log in yaml 3 years ago
stefan 1eb8d87884 faster logic sim, removing MVArray, BPArray 3 years ago
Stefan Holst 02f3a0e1b2 correct timing padding 3 years ago
Stefan Holst fc8e65e788 bit-packing utility 3 years ago
Stefan Holst d80a3ae2b1 timer utility 3 years ago
Stefan Holst 7bfc02e683 more on-gpu code, bump python requirement 3 years ago
Stefan Holst 8da4a62bce switch to new wave_sim, silence occupancy warnings 3 years ago
Stefan Holst 3497bfdc75 first gpu-code, cached test fixtures 3 years ago
Stefan Holst f1ebe1487c new wave sim 3 years ago
Stefan Holst f0dac36ac7 interface -> io_nodes, io_loc fix 3 years ago
Stefan Holst b2953aef25 only dff 4 years ago
Stefan Holst 3774b14286 support ppi/ppo 4 years ago
Stefan Holst 4847ad9c40 locating io ports and busses by name 4 years ago
Stefan Holst 6801606dca new common scheduler for simulators 4 years ago
Stefan Holst faf41f0863 ff transitions switch 4 years ago
Stefan Holst 6430f10f73 HADD pin index fix 4 years ago
Stefan Holst fa19af8c31 4-input gate simulator 4 years ago
Stefan Holst 93a0858d2f oai and aoi pin handling fix 4 years ago
Stefan Holst 1f2808ee31 Merge branch 'main' into devel 4 years ago
Stefan Holst 163b348a0c year bump 4 years ago
Stefan Holst ecfc692edc support reset RN for scan cells 4 years ago
Stefan Holst afb7e745a1 adding aoi to logic sim 4 years ago
Stefan Holst 6a8841c3c6 revert wave_eval4 4 years ago
Stefan Holst c530983afa accept I as a first input 4 years ago
Stefan Holst 775b13c694 fix off-by-1 pin index when loading AOI and OAI cells 5 years ago
Stefan Holst 584445f3b1 wave eval for 4-input gates 5 years ago
Stefan Holst 85dd02d4d7 interpret N as unassigned in STIL 5 years ago
Stefan Holst 7c03271048 improve robustness of sdf annotation and wave sim 5 years ago
Stefan Holst 8bbaaf8fae comment change 5 years ago
Stefan Holst d59d6401c8 fix stil loading and logic sim capture 5 years ago
Stefan Holst 387c436207 fix tests, version bump 5 years ago
Stefan Holst b981b1153c add sdata to control individual sims 5 years ago
Stefan Holst 87d93afb44 fix time in unpickled log objects 5 years ago
Stefan Holst c3e4090f31 make nodes and lines hashable again 5 years ago
Stefan Holst 0251d66d28 make circuit pickable and comparable 5 years ago
Stefan Holst 864230b883 initial letch support, fix capture in logic sim 5 years ago
Stefan Holst d05841a6a2 Merge branch 'main' into devel 6 years ago
Stefan Holst c5be32d7e5 doc and indent fix 6 years ago
Stefan Holst 8434f5e694 fixes for IWLS benchmark netlists 6 years ago
Stefan Holst 9ff2369a55 fix parsing older stil files 6 years ago
Stefan Holst 82a53e0171 improve techlib for gsclib, better constant handling in verilog parser 6 years ago
Stefan Holst a2df0e5682 fix ff annotation 6 years ago
Stefan Holst ec37e11fef Merge branch 'main' into devel 6 years ago
Stefan Holst 3a5a3c128b year bump 6 years ago
Stefan Holst ee30898cef docs for numba and cuda 6 years ago
Stefan Holst 62cf56e98a TechLib class, remove unnecessary .index 6 years ago
Stefan Holst dc003fa624 documentation improvements 6 years ago
Stefan Holst 8b5a71f498 documentation improvements 6 years ago
Stefan Holst 9c8dee31b9 assign and capture return arrays, new cycle method for common use pattern 6 years ago
Stefan Holst 2bbdf3ee5d fix logic sim of DFF.QN output 6 years ago
Stefan Holst 35cf63cf38 Make Node and Line indexable, documentation. 6 years ago
Stefan Holst ff4de6d782 de-lint and repr improvements 6 years ago
Stefan Holst c12a30328c better hr_time 6 years ago
Stefan Holst 7e6660002b support ibuff in WaveSim 6 years ago
Stefan Holst dfbc35eeb9 logging range fixes 6 years ago
Stefan Holst 4f531fe4cb implement logging range 6 years ago
Stefan Holst 18c17b5f76 more docs and reprs 6 years ago
Stefan Holst 0bad95e94e LogicSim clean-up and new fault injection facility. version bump. 6 years ago
Stefan Holst 7501613951 remove comments 6 years ago
Stefan Holst 5084f1dd8c demo nb run with cuda 6 years ago
Stefan Holst 7f035c1ac5 Migration to new logic value representation 6 years ago
Stefan Holst 7bcfbf502b Documentation, cleanup, multi-valued logic 6 years ago
Stefan Holst 5830608527 Documenting circuit module 6 years ago
Stefan Holst cff18e0915 start documentation 6 years ago
Stefan Holst a77ac4a397 start designing new data structures for m-valued logic 6 years ago
  1. 14
      .gitignore
  2. 1
      .python-version
  3. 6
      .readthedocs.yaml
  4. 2
      LICENSE.txt
  5. 32
      README.md
  6. 32
      README.rst
  7. 2
      docs/Makefile
  8. 5
      docs/conf.py
  9. 3
      docs/index.rst
  10. 9
      docs/simulators.rst
  11. 1720
      examples/Introduction.ipynb
  12. 18
      pyproject.toml
  13. 46
      src/kyupy/__init__.py
  14. 64
      src/kyupy/atalanta.py
  15. 74
      src/kyupy/bench.py
  16. 141
      src/kyupy/circuit.py
  17. 27
      src/kyupy/def_file.py
  18. 68
      src/kyupy/logic.py
  19. 575
      src/kyupy/logic_sim.py
  20. 17
      src/kyupy/sdf.py
  21. 52
      src/kyupy/sim.py
  22. 12
      src/kyupy/stil.py
  23. 274
      src/kyupy/techlib.py
  24. 218
      src/kyupy/vcd.py
  25. 23
      src/kyupy/verilog.py
  26. 2
      src/kyupy/wave_sim.py
  27. 2
      tests/Makefile
  28. 91
      tests/all_kyupy_simprims.minimal.v
  29. 53
      tests/all_kyupy_simprims.v
  30. 34
      tests/kyupy_simprims.genlib
  31. 8
      tests/map_to_minimal.abc
  32. 5
      tests/minimal.genlib
  33. 29
      tests/test_atalanta.py
  34. 15
      tests/test_circuit.py
  35. 222
      tests/test_logic_sim.py
  36. 2
      tests/test_stil.py
  37. 8
      tests/test_verilog.py
  38. 17
      tests/test_wave_sim.py

14
.gitignore vendored

@ -1,12 +1,14 @@
__pycache__ __pycache__/
.ipynb_checkpoints .ipynb_checkpoints
.pytest_cache .pytest_cache
.DS_Store .DS_Store
*.pyc
docs/_build
build
dist
.idea .idea
.vscode .vscode
src/kyupy.egg-info .venv
*.py[oc]
docs/_build
build/
dist/
wheels/
*.egg-info
*nogit* *nogit*

1
.python-version

@ -0,0 +1 @@
3.13

6
.readthedocs.yaml

@ -1,12 +1,12 @@
version: 2 version: 2
build: build:
os: "ubuntu-20.04" os: "ubuntu-24.04"
tools: tools:
python: "3.8" python: "3.12"
jobs: jobs:
post_create_environment: post_create_environment:
- python -m pip install sphinx_rtd_theme - python -m pip install sphinx_rtd_theme myst-parser
sphinx: sphinx:
fail_on_warning: true fail_on_warning: true

2
LICENSE.txt

@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

32
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.

32
README.rst

@ -1,32 +0,0 @@
KyuPy - Pythonic Processing of VLSI Circuits
============================================
KyuPy is a Python package for processing and analysis of non-hierarchical gate-level VLSI designs.
It contains fundamental building blocks for research software in the fields of VLSI test, diagnosis and reliability:
* Efficient data structures for gate-level circuits and related design data.
* Partial `lark <https://github.com/lark-parser/lark>`_ parsers for common design files like
bench, gate-level Verilog, standard delay format (SDF), standard test interface language (STIL), design exchange format (DEF).
* Bit-parallel gate-level 2-, 4-, and 8-valued logic simulation.
* GPU-accelerated high-throughput gate-level timing simulation.
* High-performance through the use of `numpy <https://numpy.org>`_ and `numba <https://numba.pydata.org>`_.
Getting Started
---------------
KyuPy is available in `PyPI <https://pypi.org/project/kyupy>`_.
It requires Python 3.8 or newer, `lark-parser <https://pypi.org/project/lark-parser>`_, and `numpy`_.
Although optional, `numba`_ should be installed for best performance.
GPU/CUDA support in numba may `require some additional setup <https://numba.readthedocs.io/en/stable/cuda/index.html>`_.
If numba is not available, KyuPy will automatically fall back to slow, pure Python execution.
The Jupyter Notebook `Introduction.ipynb <https://github.com/s-holst/kyupy/blob/main/examples/Introduction.ipynb>`_ contains some useful examples to get familiar with the API.
Development
-----------
To work with the latest pre-release source code, clone the `KyuPy GitHub repository <https://github.com/s-holst/kyupy>`_.
Run ``pip install -e .`` within your local checkout to make the package available in your Python environment.
The source code comes with tests that can be run with ``pytest``.

2
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 # Minimal makefile for Sphinx documentation
# #

5
docs/conf.py

@ -20,11 +20,11 @@ sys.path.insert(0, os.path.abspath('../src'))
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = 'KyuPy' project = 'KyuPy'
copyright = '2020-2023, Stefan Holst' copyright = '2020-2026, Stefan Holst'
author = 'Stefan Holst' author = 'Stefan Holst'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = '0.0.5' release = '0.0.7'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
@ -35,6 +35,7 @@ release = '0.0.5'
extensions = [ extensions = [
'sphinx.ext.autodoc', 'sphinx.ext.autodoc',
'sphinx_rtd_theme', 'sphinx_rtd_theme',
'myst_parser',
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.

3
docs/index.rst

@ -1,4 +1,5 @@
.. include:: ../README.rst .. include:: ../README.md
:parser: myst_parser.sphinx_
API Reference API Reference
------------- -------------

9
docs/simulators.rst

@ -14,6 +14,15 @@ Logic Simulation - :mod:`kyupy.logic_sim`
.. autoclass:: kyupy.logic_sim.LogicSim .. autoclass:: kyupy.logic_sim.LogicSim
:members: :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` Timing Simulation - :mod:`kyupy.wave_sim`
----------------------------------------- -----------------------------------------

1720
examples/Introduction.ipynb

File diff suppressed because it is too large Load Diff

18
pyproject.toml

@ -1,15 +1,15 @@
[project] [project]
name = "kyupy" name = "kyupy"
version = "0.0.5" version = "0.0.7"
authors = [ authors = [
{ name="Stefan Holst", email="mail@s-holst.de" }, { name="Stefan Holst", email="mail@s-holst.de" },
] ]
description = 'High-performance processing and analysis of non-hierarchical VLSI designs' description = 'High-performance processing and analysis of non-hierarchical VLSI designs'
readme = "README.rst" readme = "README.md"
requires_python = ">=3.8" requires-python = ">=3.12,<3.14"
dependencies = [ dependencies = [
"numpy>=1.17.0", "numpy>=1.17.0",
"lark-parser>=0.8.0", "lark>=1.3.0",
] ]
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
@ -30,3 +30,13 @@ homepage = "https://github.com/s-holst/kyupy"
requires = ["hatchling"] requires = ["hatchling"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
[dependency-groups]
dev = [
"myst-parser>=4.0.1",
"sphinx>=8.1.3",
"sphinx-rtd-theme>=3.0.2",
"pytest>=9.0.1",
"ipython>=9.14.1",
"jupyter>=1.1.1",
]

46
src/kyupy/__init__.py

@ -12,6 +12,8 @@ import gzip
import numpy as np import numpy as np
type NestedNumericList = list['int|NestedNumericList']
type NestedStrIntDict = dict[str,'int|NestedStrIntDict']
_pop_count_lut = np.asarray([bin(x).count('1') for x in range(256)]) _pop_count_lut = np.asarray([bin(x).count('1') for x in range(256)])
@ -111,9 +113,9 @@ class Timer:
class Timers: class Timers:
def __init__(self, t={}): self.timers = defaultdict(Timer) | t def __init__(self, t={}): self.timers: dict[str, Timer] = defaultdict(Timer) | t
def __getitem__(self, name): return self.timers[name] def __getitem__(self, name): return self.timers[name]
def __repr__(self): return '{' + ', '.join([f'{k}: {v}' for k, v in self.timers.items()]) + '}' def __repr__(self): return '{' + ', '.join([f'{k}: {v:.2f}' for k, v in self.dict().items()]) + '}'
def __add__(self, t): def __add__(self, t):
tmr = Timers(self.timers) tmr = Timers(self.timers)
for k, v in t.timers.items(): tmr.timers[k] += v for k, v in t.timers.items(): tmr.timers[k] += v
@ -121,7 +123,7 @@ class Timers:
def sum(self): def sum(self):
return sum([v.s for v in self.timers.values()]) return sum([v.s for v in self.timers.values()])
def dict(self): def dict(self):
return dict([(k, v.s) for k, v in self.timers.items()]) return dict(sorted([(k, v.s) for k, v in self.timers.items()], key=lambda item: -item[1]))
class Log: class Log:
@ -198,25 +200,35 @@ class Log:
"""Log an error message.""" """Log an error message."""
self.log('E', message) self.log('E', message)
def progress(self):
class Progress:
def __init__(self, l): self.l = l
def __enter__(self):
self.start_time = time.perf_counter()
self.lastlog_time = self.start_time
self.log_interval = 5
return self
def update(self, completion: float, msg: str = ''):
assert completion > 0 and completion <= 1
current_time = time.perf_counter()
if current_time > self.lastlog_time + self.log_interval:
elapsed_time = current_time - self.start_time
total_time = elapsed_time / completion
rem_time = total_time - elapsed_time
self.l.log(':', f'{msg} - {completion*100:.0f}% done {hr_time(elapsed_time)} elapsed {hr_time(rem_time)} remaining')
self.log_interval = min(600, int(self.log_interval*1.5))
self.lastlog_time = current_time
def __exit__(self, *args): pass
return Progress(self)
def range(self, *args): def range(self, *args):
"""A generator that operates just like the ``range()`` built-in, and also occasionally logs the progress """A generator that operates just like the ``range()`` built-in, and also occasionally logs the progress
and compute time estimates.""" and compute time estimates."""
elems = len(range(*args)) elems = len(range(*args))
start_time = time.perf_counter() with self.progress() as p:
lastlog_time = start_time
log_interval = 5
for elem, i in enumerate(range(*args)): for elem, i in enumerate(range(*args)):
yield i yield i
current_time = time.perf_counter() p.update((elem + 1) / elems)
if current_time > lastlog_time + log_interval:
done = (elem + 1) / elems
elapsed_time = current_time - start_time
total_time = elapsed_time / done
rem_time = total_time - elapsed_time
self.log(
':', f'{done*100:.0f}% done {hr_time(elapsed_time)} elapsed {hr_time(rem_time)} remaining')
log_interval = min(600, int(log_interval*1.5))
lastlog_time = current_time
log = Log() log = Log()
@ -293,8 +305,6 @@ if importlib.util.find_spec('numba') is not None:
try: try:
list(numba.cuda.gpus) list(numba.cuda.gpus)
from numba import cuda from numba import cuda
from numba.core import config
config.CUDA_LOW_OCCUPANCY_WARNINGS = False
except CudaSupportError: except CudaSupportError:
log.warn('Cuda unavailable. Falling back to pure Python.') log.warn('Cuda unavailable. Falling back to pure Python.')
cuda = MockCuda() cuda = MockCuda()

64
src/kyupy/atalanta.py

@ -0,0 +1,64 @@
"""A parser and dumper for Atalanta's plain-text test pattern format (*.test).
A *.test file contains one test pattern per line in the form ``<index>: <bits>``,
where ``<bits>`` is a string of ``'0'`` and ``'1'`` characters giving the values of
the primary inputs (in circuit order). Lines starting with ``'*'`` are comments.
This parser also accepts files without ``<index>:`` and patterns elements in 8-valued
logic such as ``'R'``, ``'F'``, ``'X'``.
"""
import re
import numpy as np
from . import readtext, logic
from .circuit import Circuit
_pattern_re = re.compile(r'^\s*(?:\d+:\s*)?([01HLRFX-]+)\s*$')
class TestFile:
"""Intermediate representation of a *.test pattern file.
:param text: The contents of a *.test file.
"""
def __init__(self, text: str):
patterns = []
for line in text.splitlines():
if (m := _pattern_re.match(line)) is not None:
patterns.append(logic.mvarray(m.group(1)))
self.patterns = np.stack(patterns, axis=-1) if patterns \
else np.zeros((0, 0), dtype=np.uint8)
"""The parsed patterns as a mvarray (see :py:mod:`~kyupy.logic`).
The second-to-last axis goes along primary inputs, the last axis goes along patterns.
"""
def tests(self, circuit: Circuit):
"""Assembles and returns the test pattern set for the given circuit.
:param circuit: The circuit to assemble the patterns for. The patterns will follow the
:py:attr:`~kyupy.circuit.Circuit.io_nodes` ordering of the circuit.
:return: A logic array (see :py:mod:`~kyupy.logic`). The values for primary inputs are
filled, all other values are left unassigned.
"""
pi_locs = [i for i, n in enumerate(circuit.io_nodes) if len(n.ins) == 0]
tests = np.full((len(circuit.io_nodes), self.patterns.shape[-1]), logic.UNASSIGNED, dtype=np.uint8)
tests[pi_locs] = self.patterns
return tests
def parse(text) -> TestFile:
"""Parses the given ``text`` and returns a :class:`TestFile` object."""
return TestFile(text)
def load(file) -> TestFile:
"""Parses the contents of ``file`` and returns a :class:`TestFile` object.
Files with `.gz`-suffix are decompressed on-the-fly.
"""
return parse(readtext(file))

74
src/kyupy/bench.py

@ -5,12 +5,20 @@ Historically it was first used in the
`ISCAS89 benchmark set <https://people.engr.ncsu.edu/brglez/CBL/benchmarks/ISCAS89/>`_. `ISCAS89 benchmark set <https://people.engr.ncsu.edu/brglez/CBL/benchmarks/ISCAS89/>`_.
Besides loading these benchmarks, this module is also useful for easily constructing simple circuits: Besides loading these benchmarks, this module is also useful for easily constructing simple circuits:
``c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')``. ``c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)')``.
This parser automatically translates gate to simulation primitives supported by kyupy.
In most cases this means just adding the number of inputs to the name: e.g. NAND -> NAND2, OR -> OR4, INV -> INV1.
Since simulators only support at most four-input-gates, gates with more than four inputs are implemented as tree.
""" """
from lark import Lark, Transformer from lark import Lark, Transformer
from .circuit import Circuit, Node, Line from .circuit import Circuit, Node, Line
from . import readtext from . import readtext, batchrange, log
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): class BenchTransformer(Transformer):
@ -18,16 +26,62 @@ class BenchTransformer(Transformer):
def __init__(self, name): def __init__(self, name):
super().__init__() super().__init__()
self.c = Circuit(name) self.c = Circuit(name)
self.inputs = set()
self.outputs = set()
def start(self, _): return self.c 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 input(self, args):
for input in args[0]:
if input in self.outputs:
new_input = self.c.get_or_add_fork(input.name + '~i')
Line(self.c, new_input, input)
log.warn(f'input-output passthrough, renaming input: {input.name} -> {new_input.name}')
input = new_input
self.inputs.add(input)
self.c.io_nodes.append(input)
def output(self, args):
for output in args[0]:
if output in self.inputs:
new_output = self.c.get_or_add_fork(output.name + '~o')
Line(self.c, output, new_output)
log.warn(f'input-output passthrough, renaming output: {output.name} -> {new_output.name}')
output = new_output
self.outputs.add(output)
self.c.io_nodes.append(output)
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): def assignment(self, args):
name, cell_type, drivers = args name, kind, drivers = args
cell = Node(self.c, str(name), str(cell_type)) 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))) Line(self.c, cell, self.c.get_or_add_fork(str(name)))
for d in drivers: Line(self.c, d, cell) for d in drivers: Line(self.c, d, cell)
@ -35,8 +89,8 @@ class BenchTransformer(Transformer):
GRAMMAR = r""" GRAMMAR = r"""
start: (statement)* start: (statement)*
statement: input | output | assignment statement: input | output | assignment
input: ("INPUT" | "input") parameters -> interface input: ("INPUT" | "input") parameters
output: ("OUTPUT" | "output") parameters -> interface output: ("OUTPUT" | "output") parameters
assignment: NAME "=" NAME parameters assignment: NAME "=" NAME parameters
parameters: "(" [ NAME ( "," NAME )* ] ")" parameters: "(" [ NAME ( "," NAME )* ] ")"
NAME: /[-_a-z0-9]+/i NAME: /[-_a-z0-9]+/i
@ -44,14 +98,14 @@ GRAMMAR = r"""
""" """
def parse(text, name=None): def parse(text, name=None) -> Circuit:
"""Parses the given ``text`` as ISCAS89 bench code. """Parses the given ``text`` as ISCAS89 bench code.
:param text: A string with bench code. :param text: A string with bench code.
:param name: The name of the circuit. Circuit names are not included in bench descriptions. :param name: The name of the circuit. Circuit names are not included in bench descriptions.
:return: A :class:`Circuit` object. :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): def load(file, name=None):

141
src/kyupy/circuit.py

@ -14,12 +14,12 @@ from __future__ import annotations
from collections import deque, defaultdict from collections import deque, defaultdict
import re import re
from typing import Union
import numpy as np import numpy as np
from . import NestedNumericList, NestedStrIntDict
class GrowingList(list): class GrowingList[T](list[T]):
def __setitem__(self, index, value): def __setitem__(self, index, value):
if value is None: self.has_nones = True if value is None: self.has_nones = True
if index == len(self): return super().append(value) if index == len(self): return super().append(value)
@ -28,9 +28,13 @@ class GrowingList(list):
self.has_nones = True self.has_nones = True
super().__setitem__(index, value) super().__setitem__(index, value)
def __getitem__(self, index): # Override __getitem__ to return None when reading beyond the list
if isinstance(index, slice): return super().__getitem__(index) # instead of throwing an exception. Type checker complains about the None return
return super().__getitem__(index) if index < len(self) else None # 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 @property
def free_idx(self): def free_idx(self):
@ -134,10 +138,10 @@ class Node:
This is ok, because (name, kind) is unique within a circuit. 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): def __hash__(self):
return hash((self.name, self.kind)) return hash((self.name, self.kind, id(self.circuit)))
class Line: class Line:
@ -155,7 +159,7 @@ class Line:
Use the explicit case only if connections to specific pins are required. 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. 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 self.circuit = circuit
"""The :class:`Circuit` object the line is part of. """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]`. 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) 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. """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. """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: 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`. :code:`self.driver.outs[self.driver_pin] == self`.
""" """
if not isinstance(reader, tuple): reader = (reader, reader.ins.free_idx) 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. """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. """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: 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 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.reader is not None: self.reader.ins[self.reader_pin] = None
if self.circuit is not None: del self.circuit.lines[self.index] if self.circuit is not None: del self.circuit.lines[self.index]
self.driver = None
self.reader = None
self.circuit = None self.circuit = None
def __index__(self): def __index__(self):
@ -278,20 +280,19 @@ class Circuit:
Use the :class:`Node` constructor and :py:attr:`Node.remove()` to add and remove nodes. Use the :class:`Node` constructor and :py:attr:`Node.remove()` to add and remove nodes.
""" """
@property def s_nodes(self, tlib: 'TechLib') -> list: # type: ignore
def s_nodes(self):
"""A list of all primary I/Os as well as all flip-flops and latches in the circuit (in that order). """A list of all primary I/Os as well as all flip-flops and latches in the circuit (in that order).
The s_nodes list defines the order of all ports and all sequential elements in the circuit. The s_nodes list defines the order of all ports and all sequential elements in the circuit.
This list is constructed on-the-fly. If used in some inner toop, consider caching the list for better performance. This list is constructed on-the-fly. If used in some inner toop, consider caching the list for better performance.
""" """
return list(self.io_nodes) + [n for n in self.nodes if 'dff' in n.kind.lower()] + [n for n in self.nodes if 'latch' in n.kind.lower()] return list(self.io_nodes) + [n for n in self.nodes if tlib.is_dff(n.kind)] + [n for n in self.nodes if tlib.is_latch(n.kind)]
def io_locs(self, prefix): def io_locs(self, prefix):
"""Returns the indices of primary I/Os that start with given name prefix. """Returns a list of indices of primary I/Os that start with given name prefix.
The returned values are used to index into the :py:attr:`io_nodes` array. The returned values are used to index into the :py:attr:`io_nodes` array.
If only one I/O cell matches the given prefix, a single integer is returned. If only one I/O cell matches the given prefix, a list with a single integer is returned.
If a bus matches the given prefix, a sorted list of indices is returned. If a bus matches the given prefix, a sorted list of indices is returned.
Busses are identified by integers in the cell names following the given prefix. Busses are identified by integers in the cell names following the given prefix.
Lists for bus indices are sorted from LSB (e.g. :code:`data[0]`) to MSB (e.g. :code:`data[31]`). Lists for bus indices are sorted from LSB (e.g. :code:`data[0]`) to MSB (e.g. :code:`data[31]`).
@ -301,16 +302,16 @@ class Circuit:
""" """
return self._locs(prefix, list(self.io_nodes)) return self._locs(prefix, list(self.io_nodes))
def s_locs(self, prefix): def s_locs(self, prefix, tlib: 'TechLib'): # type: ignore
"""Returns the indices of I/Os and sequential elements that start with given name prefix. """Returns the indices of I/Os and sequential elements that start with given name prefix.
The returned values are used to index into the :py:attr:`s_nodes` list. The returned values are used to index into the :py:attr:`s_nodes` list.
It works the same as :py:attr:`io_locs`. See there for more details. It works the same as :py:attr:`io_locs`. See there for more details.
""" """
return self._locs(prefix, self.s_nodes) return self._locs(prefix, self.s_nodes(tlib))
def _locs(self, prefix, nodes): def _locs(self, prefix, nodes:list[Node]) -> NestedNumericList: # can return list[list[...]]
d_top = dict() d_top: NestedStrIntDict = dict()
for i, n in enumerate(nodes): for i, n in enumerate(nodes):
if m := re.match(fr'({re.escape(prefix)}.*?)((?:[\d_\[\]])*$)', n.name): if m := re.match(fr'({re.escape(prefix)}.*?)((?:[\d_\[\]])*$)', n.name):
path = [m[1]] + [int(v) for v in re.split(r'[_\[\]]+', m[2]) if len(v) > 0] path = [m[1]] + [int(v) for v in re.split(r'[_\[\]]+', m[2]) if len(v) > 0]
@ -318,16 +319,16 @@ class Circuit:
for j in path[:-1]: for j in path[:-1]:
d[j] = d.get(j, dict()) d[j] = d.get(j, dict())
d = d[j] d = d[j]
assert isinstance(d, dict)
d[path[-1]] = i d[path[-1]] = i
# sort recursively for multi-dimensional lists. # sort recursively for multi-dimensional lists.
def sorted_values(d): return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d def sorted_values(d) -> NestedNumericList: return [sorted_values(v) for k, v in sorted(d.items())] if isinstance(d, dict) else d
l = sorted_values(d_top) l = sorted_values(d_top)
while isinstance(l, list) and len(l) == 1: l = l[0] while isinstance(l, list) and len(l) == 1 and isinstance(l[0], list): l = l[0]
return None if isinstance(l, list) and len(l) == 0 else l return l
@property def stats(self, tlib: 'TechLib'): # type: ignore
def stats(self):
"""A dictionary with the counts of all different elements in the circuit. """A dictionary with the counts of all different elements in the circuit.
The dictionary contains the number of all different kinds of nodes, the number The dictionary contains the number of all different kinds of nodes, the number
@ -345,9 +346,9 @@ class Circuit:
stats['__line__'] = len(self.lines) stats['__line__'] = len(self.lines)
for n in self.cells.values(): for n in self.cells.values():
stats[n.kind] += 1 stats[n.kind] += 1
if 'dff' in n.kind.lower(): stats['__dff__'] += 1 if tlib.is_dff(n.kind): stats['__dff__'] += 1
elif 'latch' in n.kind.lower(): stats['__latch__'] += 1 elif tlib.is_latch(n.kind): stats['__latch__'] += 1
elif 'put' not in n.kind.lower(): stats['__comb__'] += 1 # no input or output elif 'put' not in n.kind.lower(): stats['__comb__'] += 1 # don't count input or output towards comb
stats['__seq__'] = stats['__dff__'] + stats['__latch__'] stats['__seq__'] = stats['__dff__'] + stats['__latch__']
return dict(stats) return dict(stats)
@ -446,7 +447,7 @@ class Circuit:
node_map[n] = Node(self, f'{node.name}~{n.name}', n.kind) node_map[n] = Node(self, f'{node.name}~{n.name}', n.kind)
elif len(n.outs) > 0 and len(n.ins) > 0: # output is also read by impl. circuit, need to add a fork. elif len(n.outs) > 0 and len(n.ins) > 0: # output is also read by impl. circuit, need to add a fork.
node_map[n] = Node(self, f'{node.name}~{n.name}') node_map[n] = Node(self, f'{node.name}~{n.name}')
elif len(n.ins) == 0 and len(n.outs) > 1: # input is read by multiple nodes, need to add fork. elif len(n.ins) == 0 and len(n.outs) != 1: # input is read by multiple nodes (or no nodes), need to add fork.
node_map[n] = Node(self, f'{node.name}~{n.name}') node_map[n] = Node(self, f'{node.name}~{n.name}')
for l in impl.lines: # add all internal lines to main circuit for l in impl.lines: # add all internal lines to main circuit
if l.reader in node_map and l.driver in node_map: if l.reader in node_map and l.driver in node_map:
@ -539,9 +540,6 @@ class Circuit:
for n in state['io_nodes']: for n in state['io_nodes']:
self.io_nodes.append(self.nodes[n]) 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): 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)}}}' return f'{{name: "{self.name}", cells: {len(self.cells)}, forks: {len(self.forks)}, lines: {len(self.lines)}, io_nodes: {len(self.io_nodes)}}}'
@ -582,24 +580,23 @@ class Circuit:
if line is not None: if line is not None:
yield line yield line
def reversed_topological_order(self): def reversed_topological_order(self, tlib: 'TechLib'): # type: ignore
"""Generator function to iterate over all nodes in reversed topological order. """Generator function to iterate over all nodes in reversed topological order.
Nodes without output lines and nodes whose :py:attr:`Node.kind` contains the Nodes without output lines and sequential nodes (flip-flops, latches) are yielded first.
substrings 'dff' or 'latch' are yielded first.
""" """
visit_count = [0] * len(self.nodes) visit_count = [0] * len(self.nodes)
queue = deque(n for n in self.nodes if len(n.outs) == 0 or 'dff' in n.kind.lower() or 'latch' in n.kind.lower()) queue = deque(n for n in self.nodes if len(n.outs) == 0 or tlib.is_dff(n.kind) or tlib.is_latch(n.kind))
while len(queue) > 0: while len(queue) > 0:
n = queue.popleft() n = queue.popleft()
for line in n.ins: for line in n.ins:
pred = line.driver pred = line.driver
visit_count[pred] += 1 visit_count[pred] += 1
if visit_count[pred] == len(pred.outs) and 'dff' not in pred.kind.lower() and 'latch' not in pred.kind.lower(): if visit_count[pred] == len(pred.outs) and not tlib.is_dff(pred.kind) and not tlib.is_latch(pred.kind):
queue.append(pred) queue.append(pred)
yield n yield n
def fanin(self, origin_nodes): def fanin(self, origin_nodes, tlib: 'TechLib'): # type: ignore
"""Generator function to iterate over the fan-in cone of a given list of origin nodes. """Generator function to iterate over the fan-in cone of a given list of origin nodes.
Nodes are yielded in reversed topological order. Nodes are yielded in reversed topological order.
@ -607,31 +604,34 @@ class Circuit:
marks = [False] * len(self.nodes) marks = [False] * len(self.nodes)
for n in origin_nodes: for n in origin_nodes:
marks[n] = True marks[n] = True
for n in self.reversed_topological_order(): for n in self.reversed_topological_order(tlib):
if not marks[n]: if not marks[n]:
for line in n.outs: for line in n.outs.without_nones():
if line is not None:
marks[n] |= marks[line.reader] marks[n] |= marks[line.reader]
if marks[n]: if marks[n]:
yield n yield n
def fanout(self, origin_nodes): def fanout(self, origin_nodes: list[Node], node_filter = lambda n: 'dff' not in n.name.lower()):
"""Generator function to iterate over the fan-out cone of a given list of origin nodes. """Generator function to iterate over the fan-out cone of a given list of origin nodes.
Nodes are yielded in topological order. origin_nodes are yielded first, followed by nodes driven by them in a breadth-first manner.
The search stops at nodes for which node_filter returns False.
Only origin_nodes and nodes for which node_filter returned True are yielded.
By default, search stops at flip-flops.
""" """
marks = [False] * len(self.nodes) queue = deque(origin_nodes)
for n in origin_nodes: yielded = set()
marks[n] = True while len(queue) > 0:
for n in self.topological_order(): n = queue.popleft()
if not marks[n]: for line in n.outs.without_nones():
for line in n.ins.without_nones(): succ = line.reader
marks[n] |= marks[line.driver] if succ not in yielded and node_filter(succ):
if marks[n]: yielded.add(succ)
queue.append(succ)
yield n yield n
def fanout_free_regions(self): def fanout_free_regions(self, tlib: 'TechLib'): # type: ignore
for stem in self.reversed_topological_order(): for stem in self.reversed_topological_order(tlib):
if len(stem.outs) == 1 and 'dff' not in stem.kind.lower(): continue if len(stem.outs) == 1 and 'dff' not in stem.kind.lower(): continue
region = [] region = []
if 'dff' in stem.kind.lower(): if 'dff' in stem.kind.lower():
@ -651,11 +651,11 @@ class Circuit:
region.append(n) region.append(n)
yield stem, region yield stem, region
def dot(self, format='svg'): def dot(self, tlib: 'TechLib', format='svg', graph_attr={}, line_labels={}):
from graphviz import Digraph 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)) s_dict = dict((n, i) for i, n in enumerate(self.s_nodes(tlib)))
node_level = np.zeros(len(self.nodes), dtype=np.uint32) node_level = np.zeros(len(self.nodes), dtype=np.uint32)
level_nodes = defaultdict(list) level_nodes = defaultdict(list)
for n, lv in self.topological_order_with_level(): for n, lv in self.topological_order_with_level():
@ -666,17 +666,24 @@ class Circuit:
with dot.subgraph() as s: with dot.subgraph() as s:
s.attr(rank='same') s.attr(rank='same')
for n in level_nodes[lv]: for n in level_nodes[lv]:
ins = '|'.join([f'<i{i}>{i}' for i in range(len(n.ins))]) ins = '{' + '|'.join([f'<i{i}>{i}' for i in range(len(n.ins))]) + '}|' if len(n.ins) > 1 else ''
outs = '|'.join([f'<o{i}>{i}' for i in range(len(n.outs))]) outs = '|{' + '|'.join([f'<o{i}>{i}' for i in range(len(n.outs))]) + '}' if len(n.outs) > 1 else ''
io = f' [{s_dict[n]}]' if n in s_dict else '' 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: for l in self.lines:
driver, reader = f'{l.driver.index}:o{l.driver_pin}', f'{l.reader.index}:i{l.reader_pin}' driver = f'{l.driver.index}:o{l.driver_pin}' if len(l.driver.outs)>1 else f'{l.driver.index}'
if node_level[l.driver] >= node_level[l.reader]: reader = f'{l.reader.index}:i{l.reader_pin}' if len(l.reader.ins)>1 else f'{l.reader.index}'
dot.edge(driver, reader, style='dotted', label=str(l.index)) label = str(line_labels.get(l, l.index))
pass if node_level[l.driver] == node_level[l.reader]:
dot.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: else:
dot.edge(driver, reader, label=str(l.index)) dot.edge(driver, reader, label=label)
return dot return dot

27
src/kyupy/def_file.py

@ -6,7 +6,7 @@ as an intermediate representation (:class:`DefFile` object).
from collections import defaultdict from collections import defaultdict
from lark import Lark, Transformer, Tree from lark import Lark, Transformer
from kyupy import readtext from kyupy import readtext
@ -85,6 +85,8 @@ class DefFile:
self.pins = {} self.pins = {}
self.specialnets = {} self.specialnets = {}
self.nets = {} self.nets = {}
self.design = ""
self.diearea = 0
class DefTransformer(Transformer): class DefTransformer(Transformer):
@ -140,8 +142,10 @@ class DefTransformer(Transformer):
def comp_stmt(self, args): def comp_stmt(self, args):
name = args[0].value name = args[0].value
kind = args[1].value kind = args[1].value
point = args[2] for aidx in range(2, len(args)):
orientation = args[3].value if args[aidx] in ('PLACED', 'FIXED'):
point = args[aidx+1]
orientation = args[aidx+2].value
self.def_file.components[name] = (kind, point, orientation) self.def_file.components[name] = (kind, point, orientation)
def pins_stmt(self, args): def pins_stmt(self, args):
@ -207,6 +211,7 @@ GRAMMAR = r"""
| /DIEAREA/ point+ ";" | /DIEAREA/ point+ ";"
| /ROW/ ID ID NUMBER NUMBER ID do_step ";" | /ROW/ ID ID NUMBER NUMBER ID do_step ";"
| /TRACKS/ /[XY]/ NUMBER "DO" NUMBER "STEP" NUMBER "LAYER" ID ";" | /TRACKS/ /[XY]/ NUMBER "DO" NUMBER "STEP" NUMBER "LAYER" ID ";"
| /GCELLGRID/ /[XY]/ NUMBER "DO" NUMBER "STEP" NUMBER ";"
| propdef | vias | nondef | comp | pins | pinprop | spnets | nets | propdef | vias | nondef | comp | pins | pinprop | spnets | nets
propdef: "PROPERTYDEFINITIONS" propdef_stmt* "END" "PROPERTYDEFINITIONS" propdef: "PROPERTYDEFINITIONS" propdef_stmt* "END" "PROPERTYDEFINITIONS"
@ -228,7 +233,9 @@ GRAMMAR = r"""
| "+" /VIA/ ID )* ";" | "+" /VIA/ ID )* ";"
comp: "COMPONENTS" NUMBER ";" comp_stmt* "END" "COMPONENTS" comp: "COMPONENTS" NUMBER ";" comp_stmt* "END" "COMPONENTS"
comp_stmt: "-" ID ID "+" "PLACED" point ID ";" comp_stmt: "-" ID ID ( "+" /PLACED/ point ID
| "+" /FIXED/ point ID
| "+" /SOURCE/ ID )* ";"
pins: "PINS" NUMBER ";" pins_stmt* "END" "PINS" pins: "PINS" NUMBER ";" pins_stmt* "END" "PINS"
pins_stmt: "-" ID pins_opt* ";" pins_stmt: "-" ID pins_opt* ";"
@ -239,6 +246,8 @@ GRAMMAR = r"""
| "+" /PORT/ | "+" /PORT/
| "+" /LAYER/ ID point point | "+" /LAYER/ ID point point
| "+" /PLACED/ point ID | "+" /PLACED/ point ID
| "+" /FIXED/ point ID
pinprop: "PINPROPERTIES" NUMBER ";" pinprop_stmt* "END" "PINPROPERTIES" pinprop: "PINPROPERTIES" NUMBER ";" pinprop_stmt* "END" "PINPROPERTIES"
pinprop_stmt: "-" "PIN" ID "+" "PROPERTY" ID STRING ";" pinprop_stmt: "-" "PIN" ID "+" "PROPERTY" ID STRING ";"
@ -266,10 +275,10 @@ GRAMMAR = r"""
wire: ID wire_opt points wire: ID wire_opt points
wire_opt: ( "TAPER" | "TAPERRULE" ID )? ("STYLE" ID)? wire_opt: ( "TAPER" | "TAPERRULE" ID )? ("STYLE" ID)?
points: point ( point | points_via )+ points: point ( point | points_via_or_rect )+
points_via: ID ORIENTATION? points_via_or_rect: ID ( ORIENTATION? | "(" (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) ")" )
point: "(" (NUMBER|/\*/) (NUMBER|/\*/) NUMBER? ")" point: "(" (NUMBER|SIGNED_NUMBER|/\*/) (NUMBER|SIGNED_NUMBER|/\*/) NUMBER? ")"
do_step: "DO" NUMBER "BY" NUMBER "STEP" (NUMBER|SIGNED_NUMBER) (NUMBER|SIGNED_NUMBER) do_step: "DO" NUMBER "BY" NUMBER "STEP" (NUMBER|SIGNED_NUMBER) (NUMBER|SIGNED_NUMBER)
@ -284,9 +293,9 @@ GRAMMAR = r"""
""" """
def parse(text): def parse(text) -> DefFile:
"""Parses the given ``text`` and returns a :class:`DefFile` object.""" """Parses the given ``text`` and returns a :class:`DefFile` object."""
return Lark(GRAMMAR, parser="lalr", transformer=DefTransformer()).parse(text) return Lark(GRAMMAR, parser="lalr", transformer=DefTransformer()).parse(text) # type: ignore
def load(file): def load(file):

68
src/kyupy/logic.py

@ -47,8 +47,9 @@ The functions in this module use the ``mv...`` and ``bp...`` prefixes to signify
from collections.abc import Iterable from collections.abc import Iterable
import numpy as np import numpy as np
from numpy.typing import DTypeLike
from . import numba, hr_bytes from . import numba, hr_bytes, NestedNumericList
ZERO = 0b000 ZERO = 0b000
@ -80,7 +81,30 @@ on a signal. ``'N'``, ``'n'``, and ``'v'`` are interpreted as ``NPULSE``.
""" """
def interpret(value): # 0 X - 1 P R F N
_mv_xor = np.array([[0,1,1,3,4,5,6,7], # 0
[1,1,1,1,1,1,1,1], # X
[1,1,1,1,1,1,1,1], # -
[3,1,1,0,7,6,5,4], # 1
[4,1,1,7,4,5,6,7], # P
[5,1,1,6,5,4,7,6], # R
[6,1,1,5,6,7,4,5], # F
[7,1,1,4,7,6,5,4], # N
], dtype=np.uint8)
# 0 X - 1 P R F N
_mv_merge = np.array([[0,0,0,1,1,1,1,1], # 0
[0,1,1,3,4,5,6,7], # X
[0,1,2,3,4,5,6,7], # -
[1,3,3,3,1,1,1,1], # 1
[1,4,4,1,4,1,1,1], # P
[1,5,5,1,1,5,1,1], # R
[1,6,6,1,1,1,6,1], # F
[1,7,7,1,1,1,1,7], # N
], dtype=np.uint8)
def interpret(value) -> int|NestedNumericList:
"""Converts characters, strings, and lists of them to lists of logic constants defined above. """Converts characters, strings, and lists of them to lists of logic constants defined above.
:param value: A character (string of length 1), Boolean, Integer, None, or Iterable. :param value: A character (string of length 1), Boolean, Integer, None, or Iterable.
@ -99,11 +123,12 @@ def interpret(value):
return UNKNOWN return UNKNOWN
def mvarray(*a): def mvarray(*a) -> np.ndarray[tuple[int]|tuple[int,int],np.dtype[np.uint8]]:
"""Converts (lists of) Boolean values or strings into a multi-valued array. """Converts (lists of) Boolean values or strings into a multi-valued array.
The given values are interpreted and the axes are arranged as per KyuPy's convention. Iterable over characters, Booleans, Integers (0,1), None are interpreted as a single pattern.
Use this function to convert strings into multi-valued arrays. Multi-character strings are Iterables over characters and are interpreted as a single pattern.
Lists of said patterns form a pattern set as ndarray with the axes are arranged as per KyuPy's convention.
""" """
mva = np.array(interpret(a), dtype=np.uint8) mva = np.array(interpret(a), dtype=np.uint8)
if mva.ndim < 2: return mva if mva.ndim < 2: return mva
@ -114,7 +139,7 @@ def mvarray(*a):
def mv_str(mva, delim='\n'): def mv_str(mva, delim='\n'):
"""Renders a given multi-valued array into a string. """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 not hasattr(mva, 'ndim') or mva.ndim == 0: return sa
if mva.ndim == 1: return ''.join(sa) if mva.ndim == 1: return ''.join(sa)
return delim.join([''.join(c) for c in sa.swapaxes(-1,-2)]) return delim.join([''.join(c) for c in sa.swapaxes(-1,-2)])
@ -190,19 +215,26 @@ def mv_and(x1, x2, out=None):
return out return out
def _mv_xor(out, *ins): def mv_xor(x1, x2, out=None):
any_unknown = (ins[0] == UNKNOWN) | (ins[0] == UNASSIGNED) """A multi-valued XOR operator.
for inp in ins[1:]: any_unknown |= (inp == UNKNOWN) | (inp == UNASSIGNED)
out[...] = ZERO :param x1: A multi-valued array.
for inp in ins: :param x2: A multi-valued array.
np.bitwise_xor(out, inp & 0b011, out=out) :param out: An optional storage destination. If None, a new multi-valued array is returned.
np.bitwise_or(out, inp & 0b100, out=out) :return: A multi-valued array with the result.
np.putmask(out, any_unknown, UNKNOWN) """
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
out[...] = _mv_xor[x1, x2]
return out
def mv_xor(x1, x2, out=None): def mv_merge(x1, x2, out=None):
"""A multi-valued XOR operator. """A multi-valued merge operator.
Merging identical values ``a`` and ``a`` yields ``a``.
Merging ``X`` and ``-`` yields ``X``.
Merging any value ``a`` with ``X`` or ``-`` yields ``a``.
Merging any other values ``a != b`` yields ``X``.
:param x1: A multi-valued array. :param x1: A multi-valued array.
:param x2: A multi-valued array. :param x2: A multi-valued array.
@ -210,7 +242,7 @@ def mv_xor(x1, x2, out=None):
:return: A multi-valued array with the result. :return: A multi-valued array with the result.
""" """
out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8) out = out or np.empty(np.broadcast(x1, x2).shape, dtype=np.uint8)
_mv_xor(out, x1, x2) out[...] = _mv_merge[x1, x2]
return out return out
@ -433,7 +465,7 @@ def unpackbits(a : np.ndarray):
return np.unpackbits(a.view(np.uint8), bitorder='little').reshape(*a.shape, 8*a.itemsize) 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. """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. Similar to ``np.packbits``, but returns an array of given dtype and the shape of ``a`` with the last axis removed.

575
src/kyupy/logic_sim.py

@ -1,22 +1,38 @@
"""A high-throughput combinational logic simulator. """High-throughput combinational logic simulators.
The class :py:class:`~kyupy.logic_sim.LogicSim` performs parallel simulations of the combinational part of a circuit. These simulators take batches of input assignments to a logic circuit and compute batches of output values.
The logic operations are performed bit-parallel on packed numpy arrays (see bit-parallel (bp) array description in :py:mod:`~kyupy.logic`). Each batch is simulated in data-parallel fashion using packed numpy arrays (see bit-parallel (bp) array description in :py:mod:`~kyupy.logic`) for each signal in the circuit.
Simple sequential circuits can be simulated by repeated assignments and propagations. All simulations within a batch are mutually independent.
However, this simulator ignores the clock network and simply assumes that all state-elements are clocked all the time. Inner simulation loops (that iterate over the topologically sorted circuit model) are just-in-time (JIT) compiled using numba.
JIT compiling triggered by the first simulation may take several seconds.
If numba is not available, inner loop is executed in the python interpreter and is significantly slower.
Circuits passed to these simulators must contain only cells defined in the :py:class:`~kyupy.techlib.KYUPY` cell library.
Other circuits must be mapped to that library first using :py:method:`~kyupy.circuit.resolve_tlib_cells()`.
Sequential circuits are supported with some caveats.
The simulation model is built by stripping out all flip-flops, clock, and asynchronous set/reset logic.
Values stored in the flip-flops become part of the input assignments and output values (pseudo-primary I/O).
Only the combinational portion is actually simulated.
Simulation results remain correct under the assumption that all flip-clops are clocked all the time and set/reset logic is passive.
""" """
import math import math
import warnings
import numpy as np import numpy as np
from . import numba, logic, hr_bytes, sim, eng, cdiv from . import numba, logic, hr_bytes, sim, eng, cdiv, batchrange
from .circuit import Circuit from .circuit import Circuit, Line
class LogicSim(sim.SimOps): class LogicSim(sim.SimOps):
"""A bit-parallel naïve combinational simulator for 2-, 4-, or 8-valued logic. """A bit-parallel naïve combinational simulator for 2-, 4-, or 8-valued logic.
.. deprecated::
Use the specialized :py:class:`LogicSim2V`, :py:class:`LogicSim4V`, or :py:class:`LogicSim6V`
simulators instead.
:param circuit: The circuit to simulate. :param circuit: The circuit to simulate.
:param sims: The number of parallel logic simulations to perform. :param sims: The number of parallel logic simulations to perform.
:param m: The arity of the logic, must be 2, 4, or 8. :param m: The arity of the logic, must be 2, 4, or 8.
@ -24,6 +40,9 @@ class LogicSim(sim.SimOps):
:param strip_forks: If True, forks are not included in the simulation model to save memory and simulation time. :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, m: int = 8, c_reuse: bool = False, strip_forks: bool = False): def __init__(self, circuit: Circuit, sims: int = 8, m: int = 8, c_reuse: bool = False, strip_forks: bool = False):
warnings.warn(
'LogicSim is deprecated; use LogicSim2V, LogicSim4V, or LogicSim6V instead.',
DeprecationWarning, stacklevel=2)
assert m in [2, 4, 8] assert m in [2, 4, 8]
super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks) super().__init__(circuit, c_reuse=c_reuse, strip_forks=strip_forks)
self.m = m self.m = m
@ -32,6 +51,7 @@ class LogicSim(sim.SimOps):
nbytes = cdiv(sims, 8) nbytes = cdiv(sims, 8)
self.c = np.zeros((self.c_len, self.mdim, nbytes), dtype=np.uint8) self.c = np.zeros((self.c_len, self.mdim, nbytes), dtype=np.uint8)
self.c_dirty = np.full(self.c_len, 1, dtype=np.uint8)
self.s = np.zeros((2, self.s_len, 3, nbytes), dtype=np.uint8) self.s = np.zeros((2, self.s_len, 3, nbytes), dtype=np.uint8)
"""Logic values of the sequential elements (flip-flops) and ports. """Logic values of the sequential elements (flip-flops) and ports.
@ -43,6 +63,7 @@ class LogicSim(sim.SimOps):
Access this array to assign new values to the (P)PIs or read values from the (P)POs. Access this array to assign new values to the (P)PIs or read values from the (P)POs.
""" """
self.s[:,:,1,:] = 255 # unassigned self.s[:,:,1,:] = 255 # unassigned
self._full_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8)
def __repr__(self): def __repr__(self):
return f'{{name: "{self.circuit.name}", sims: {self.sims}, m: {self.m}, c_bytes: {eng(self.c.nbytes)}}}' return f'{{name: "{self.circuit.name}", sims: {self.sims}, m: {self.m}, c_bytes: {eng(self.c.nbytes)}}}'
@ -52,7 +73,7 @@ class LogicSim(sim.SimOps):
""" """
self.c[self.pippi_c_locs] = self.s[0, self.pippi_s_locs, :self.mdim] 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. """Propagate the input values through the combinational circuit towards the outputs.
Performs all logic operations in topological order. Performs all logic operations in topological order.
@ -61,21 +82,23 @@ class LogicSim(sim.SimOps):
:param inject_cb: A callback function for manipulating intermediate signal values. :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 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 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)`` :type inject_cb: ``f(Line, ndarray)``
""" """
t0 = self.c_locs[self.tmp_idx] fault_line = int(fault_line)
t1 = self.c_locs[self.tmp2_idx]
if self.m == 2:
if inject_cb is None:
if fault_mask is None: if fault_mask is None:
fault_mask = np.full(self.c.shape[-1], 255, dtype=np.uint8) fault_mask = self._full_mask # default: full mask
else: else:
if len(fault_mask) < self.c.shape[-1]: 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 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
fault_mask2[:len(fault_mask)] = fault_mask fault_mask2[:len(fault_mask)] = fault_mask
fault_mask = fault_mask2 fault_mask = fault_mask2
_prop_cpu(self.ops, self.c_locs, self.c, int(fault_line), fault_mask, int(fault_model)) t0 = self.c_locs[self.tmp_idx]
t1 = self.c_locs[self.tmp2_idx]
if self.m == 2:
if inject_cb is None:
c_prop_2v_cpu(self.ops, self.c_locs, self.c, self.c_dirty, int(fault_line), fault_mask, int(fault_model))
else: else:
for op, o0l, i0l, i1l, i2l, i3l in self.ops[:,:6]: 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)] o0, i0, i1, i2, i3 = [self.c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
@ -190,6 +213,15 @@ class LogicSim(sim.SimOps):
logic.bp4v_or(self.c[o0], self.c[t0], self.c[t1]) logic.bp4v_or(self.c[o0], self.c[t0], self.c[t1])
else: print(f'unknown op {op}') else: print(f'unknown op {op}')
if inject_cb is not None: inject_cb(o0l, self.c[o0]) 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: else:
for op, o0l, i0l, i1l, i2l, i3l in self.ops[:,:6]: 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)] o0, i0, i1, i2, i3 = [self.c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
@ -304,10 +336,113 @@ class LogicSim(sim.SimOps):
self.s_ppo_to_ppi() 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.c_dirty = np.full(self.c_len, 1, dtype=np.uint8)
"""Marker for logic values that have changed recently for use in incremental simulation.
A node n will be evaluated by `c_prop()` if and only if at least one of its inputs is marked dirty (`c_dirty[c_locs[n.ins[i]]] == 1`).
If a node is evaluated, its output is marked dirty.
By default, this array is all-1, therefore every node is evaluated every time by `c_prop()`.
Enable incremental simulation by setting this array to all-0, change signal values in `c` and set `c_dirty` to 1 for all changed signals.
Next call to `c_prop()` will only evaluate nodes that read from the changed signals.
To restore the original simulation state, revert the signal value changes in `c`, and call `c_prop()` again.
Caveats:
- Injected changes that are located downstream of other changes will have no effect.
- Only works with `c_reuse=False` (default).
"""
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, self.c_dirty, 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 @numba.njit
def _prop_cpu(ops, c_locs, c, fault_line, fault_mask, fault_model): def c_prop_2v_cpu(ops, c_locs, c, c_dirty, fault_line, fault_mask, fault_model):
for op, o0l, i0l, i1l, i2l, i3l in ops[:,:6]: 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)] o0, i0, i1, i2, i3 = [c_locs[x] for x in (o0l, i0l, i1l, i2l, i3l)]
if fault_line < 0 or o0l != fault_line: # fault injection forces node evaluation
if not (c_dirty[i0] | c_dirty[i1] | c_dirty[i2] | c_dirty[i3]): continue
if op == sim.BUF1: c[o0]=c[i0] if op == sim.BUF1: c[o0]=c[i0]
elif op == sim.INV1: c[o0] = ~c[i0] elif op == sim.INV1: c[o0] = ~c[i0]
elif op == sim.AND2: c[o0] = c[i0] & c[i1] elif op == sim.AND2: c[o0] = c[i0] & c[i1]
@ -343,18 +478,292 @@ 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]) elif op == sim.MUX21: c[o0] = (c[i0] & ~c[i2]) | (c[i1] & c[i2])
else: print(f'unknown op {op}') else: print(f'unknown op {op}')
if fault_line >= 0 and o0l == fault_line: if fault_line >= 0 and o0l == fault_line:
#n = len(fault_mask)
if fault_model == 0: if fault_model == 0:
c[o0] = c[o0] & ~fault_mask c[o0] = c[o0] & ~fault_mask
elif fault_model == 1: elif fault_model == 1:
c[o0] = c[o0] | fault_mask c[o0] = c[o0] | fault_mask
else: elif fault_model == 2:
c[o0] = c[o0] ^ fault_mask c[o0] = c[o0] ^ fault_mask
c_dirty[o0] = 1
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.full((self.s_len, self.sims), logic.UNASSIGNED, 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.full((self.s_len, self.sims), logic.UNASSIGNED, 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}, s_len: {self.s_len}, 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): class LogicSim6V(sim.SimOps):
"""A bit-parallel naïve combinational simulator for 6-valued logic. """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 circuit: The circuit to simulate.
:param sims: The number of parallel logic simulations to perform. :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 c_reuse: If True, intermediate signal values may get overwritten when not needed anymore to save memory.
@ -366,7 +775,7 @@ class LogicSim6V(sim.SimOps):
nbytes = cdiv(sims, 8) nbytes = cdiv(sims, 8)
self.c = np.zeros((self.c_len, 3, nbytes), dtype=np.uint8) self.c = np.zeros((self.c_len, 3, nbytes), dtype=np.uint8)
self.s = np.zeros((2, self.s_len, self.sims), dtype=np.uint8) self.s = np.full((2, self.s_len, self.sims), logic.UNASSIGNED, dtype=np.uint8)
"""Logic values of the sequential elements (flip-flops) and ports. """Logic values of the sequential elements (flip-flops) and ports.
It is a pair of arrays in mv storage format: It is a pair of arrays in mv storage format:
@ -376,6 +785,9 @@ class LogicSim6V(sim.SimOps):
Access this array to assign new values to the (P)PIs or read values from the (P)POs. 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): def __repr__(self):
return f'{{name: "{self.circuit.name}", sims: {self.sims}, c_bytes: {eng(self.c.nbytes)}}}' return f'{{name: "{self.circuit.name}", sims: {self.sims}, c_bytes: {eng(self.c.nbytes)}}}'
@ -385,17 +797,49 @@ class LogicSim6V(sim.SimOps):
""" """
self.c[self.pippi_c_locs] = logic.mv_to_bp(self.s[0, self.pippi_s_locs]) self.c[self.pippi_c_locs] = logic.mv_to_bp(self.s[0, self.pippi_s_locs])
def c_prop(self): def c_prop(self, fault_line=-1, fault_model=2, fault_mask=None):
c_prop_cpu(self.ops, self.c, self.c_locs, self.tmp_idx, self.tmp2_idx) 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): def c_to_s(self):
"""Captures the results of the combinational portion into ``s[1]``. """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] 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 @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]] t0 = c[c_locs[tmp_idx]]
t1 = c[c_locs[tmp2_idx]] t1 = c[c_locs[tmp2_idx]]
inv_op = np.array([255, 255, 0], dtype=np.uint8)[np.newaxis, :, np.newaxis] inv_op = np.array([255, 255, 0], dtype=np.uint8)[np.newaxis, :, np.newaxis]
@ -443,6 +887,84 @@ def c_prop_cpu(ops, c, c_locs, tmp_idx, tmp2_idx):
o0[0] = i0[0] ^ i1[0] o0[0] = i0[0] ^ i1[0]
o0[1] = i0[1] ^ i1[1] o0[1] = i0[1] ^ i1[1]
o0[2] = i0[2] | i1[2] 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: elif op == sim.MUX21:
# t1 = ~i2 # t1 = ~i2
t1[...] = i2 ^ inv_op t1[...] = i2 ^ inv_op
@ -466,5 +988,8 @@ def c_prop_cpu(ops, c, c_locs, tmp_idx, tmp2_idx):
if (op == sim.INV1 or if (op == sim.INV1 or
op == sim.NAND2 or op == sim.NAND3 or op == sim.NAND4 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.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 o0[...] = o0 ^ inv_op

17
src/kyupy/sdf.py

@ -59,7 +59,7 @@ class DelayFile:
if name not in circuit.cells: name = name.replace('[', '_').replace(']', '_') if name not in circuit.cells: name = name.replace('[', '_').replace(']', '_')
return circuit.cells.get(name, None) return circuit.cells.get(name, None)
delays = np.zeros((len(circuit.lines), 2, 2, 3)) # dataset last during construction. delays = np.zeros((len(circuit.lines), 2, 2, 3), dtype=np.float32) # dataset last during construction.
with log.limit(50): with log.limit(50):
for name, iopaths in self.cells.items(): for name, iopaths in self.cells.items():
@ -101,14 +101,19 @@ class DelayFile:
* Axis 3: polarity of the transition, 0='rising/posedge', 1='falling/negedge' * Axis 3: polarity of the transition, 0='rising/posedge', 1='falling/negedge'
""" """
delays = np.zeros((len(circuit.lines), 2, 2, 3)) # dataset last during construction. delays = np.zeros((len(circuit.lines), 2, 2, 3), dtype=np.float32) # dataset last during construction.
def split_node_port(n_str):
if (split := n_str.rfind('.')) > 0 or (split := n_str.rfind('/')) > 0:
return n_str[:split], n_str[split+1:]
return (n_str, None)
nonfork_annotations = 0 nonfork_annotations = 0
for n1, n2, *delvals in self._interconnects: for n1, n2, *delvals in self._interconnects:
delvals = [d if len(d) > 0 else [0, 0, 0] for d in delvals] delvals = [d if len(d) > 0 else [0, 0, 0] for d in delvals]
if max(max(delvals)) == 0: continue if max(max(delvals)) == 0: continue
cn1, pn1 = (n1, None) if (slash := n1.rfind('/')) < 0 else (n1[:slash], n1[slash+1:]) cn1, pn1 = split_node_port(n1)
cn2, pn2 = (n2, None) if (slash := n2.rfind('/')) < 0 else (n2[:slash], n2[slash+1:]) cn2, pn2 = split_node_port(n2)
cn1 = cn1.replace('\\','') cn1 = cn1.replace('\\','')
cn2 = cn2.replace('\\','') cn2 = cn2.replace('\\','')
c1, c2 = circuit.cells[cn1], circuit.cells[cn2] c1, c2 = circuit.cells[cn1], circuit.cells[cn2]
@ -217,9 +222,9 @@ def parse(text):
return Lark(GRAMMAR, parser="lalr", transformer=SdfTransformer()).parse(text) return Lark(GRAMMAR, parser="lalr", transformer=SdfTransformer()).parse(text)
def load(file): def load(file) -> DelayFile:
"""Parses the contents of ``file`` and returns a :class:`DelayFile` object. """Parses the contents of ``file`` and returns a :class:`DelayFile` object.
Files with `.gz`-suffix are decompressed on-the-fly. Files with `.gz`-suffix are decompressed on-the-fly.
""" """
return parse(readtext(file)) return parse(readtext(file)) # type: ignore

52
src/kyupy/sim.py

@ -4,7 +4,9 @@ from bisect import bisect, insort_left
import numpy as np import numpy as np
from . import log
from .circuit import Circuit from .circuit import Circuit
from .techlib import KYUPY
BUF1 = np.uint16(0b1010_1010_1010_1010) BUF1 = np.uint16(0b1010_1010_1010_1010)
INV1 = ~BUF1 INV1 = ~BUF1
@ -158,7 +160,7 @@ class SimOps:
"""A static scheduler that translates a Circuit into a topologically sorted list of basic logic operations (self.ops) and """A static scheduler that translates a Circuit into a topologically sorted list of basic logic operations (self.ops) and
a memory mapping (self.c_locs, self.c_caps) for use in simulators. a memory mapping (self.c_locs, self.c_caps) for use in simulators.
:param circuit: The circuit to create a schedule for. :param circuit: The circuit to create a schedule for. Must contain cells from KYUPY techlib only.
:param strip_forks: If enabled, the scheduler will not include fork nodes to safe simulation time. :param strip_forks: If enabled, the scheduler will not include fork nodes to safe simulation time.
Stripping forks will cause interconnect delay annotations of lines read by fork nodes to be ignored. Stripping forks will cause interconnect delay annotations of lines read by fork nodes to be ignored.
:param c_reuse: If enabled, memory of intermediate signal waveforms will be re-used. This greatly reduces :param c_reuse: If enabled, memory of intermediate signal waveforms will be re-used. This greatly reduces
@ -166,7 +168,7 @@ class SimOps:
""" """
def __init__(self, circuit: Circuit, c_caps=1, c_caps_min=1, a_ctrl=None, c_reuse=False, strip_forks=False): def __init__(self, circuit: Circuit, c_caps=1, c_caps_min=1, a_ctrl=None, c_reuse=False, strip_forks=False):
self.circuit = circuit self.circuit = circuit
self.s_len = len(circuit.s_nodes) self.s_len = len(circuit.s_nodes(KYUPY))
if isinstance(c_caps, int): if isinstance(c_caps, int):
c_caps = [c_caps] * (len(circuit.lines)+3) c_caps = [c_caps] * (len(circuit.lines)+3)
@ -186,11 +188,11 @@ class SimOps:
# ALAP-toposort the circuit into self.ops # ALAP-toposort the circuit into self.ops
levels = [] levels = []
ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes)) ppio2idx = dict((n, i) for i, n in enumerate(circuit.s_nodes(KYUPY)))
ppos = set([n for n in circuit.s_nodes if len(n.ins) > 0]) root_nodes = set([n for n in circuit.nodes if len(n.outs) == 0 or KYUPY.is_dff(n.kind)]) # start from POs (that are unused by circuit itself), PPOs, and any dangling nodes
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 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 sorted(root_nodes, key=lambda n: n.index) if len(n.ins) > 0 ]
# FIXME: Should probably instanciate buffers for PPOs and attach DFF clocks # 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 while len(level_lines) > 0: # traverse the circuit level-wise back towards (P)PIs
@ -199,9 +201,15 @@ class SimOps:
for l in level_lines: for l in level_lines:
n = l.driver n = l.driver
assert len(n.ins) <= 4, f'Too many input pins for node {n}. Map circuit to KYUPY techlib first using resolve_tlib_cells().'
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]] 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: if n in ppio2idx:
in_idxs[1] = self.zero_idx
if 'dff' in n.kind.lower() or in_idxs[0] == self.zero_idx:
in_idxs[0] = self.ppi_offset + ppio2idx[n] in_idxs[0] = self.ppi_offset + ppio2idx[n]
else: # this is a PO that is also used within the circuit. Treat as normal fork with ref counting. TODO: check strip_forks=True case for this.
readers[n.ins[0]] -= 1
if readers[n.ins[0]] == 0: prev_level_lines.append(n.ins[0])
if l.driver_pin == 1 and 'dff' in n.kind.lower(): # second output of DFF is inverted if l.driver_pin == 1 and 'dff' in n.kind.lower(): # second output of DFF is inverted
level_ops.append((INV1, l.index, *in_idxs, *a_ctrl[l])) level_ops.append((INV1, l.index, *in_idxs, *a_ctrl[l]))
else: else:
@ -222,9 +230,7 @@ class SimOps:
if in_idxs[2] == self.zero_idx: if in_idxs[2] == self.zero_idx:
sp = prims[2] sp = prims[2]
break break
if sp is None: assert sp is not None, f'Unsupported node type {n}. Map circuit to KYUPY techlib first using resolce_tlib_cells().'
print('unknown cell type', kind)
else:
level_ops.append((sp, l.index, *in_idxs, *a_ctrl[l])) level_ops.append((sp, l.index, *in_idxs, *a_ctrl[l]))
if len(level_ops) > 0: levels.append(level_ops) if len(level_ops) > 0: levels.append(level_ops)
@ -233,6 +239,7 @@ class SimOps:
self.levels = [np.asarray(lv, dtype=np.int32) for lv in levels[::-1]] 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) 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:] 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) self.ops = np.vstack(self.levels)
# create a map from fanout lines to stem lines for fork stripping # create a map from fanout lines to stem lines for fork stripping
@ -267,11 +274,11 @@ class SimOps:
ref_count[self.tmp2_idx] += 1 ref_count[self.tmp2_idx] += 1
# allocate and keep memory for PI/PPI, keep memory for PO/PPO (allocated later) # allocate and keep memory for PI/PPI, keep memory for PO/PPO (allocated later)
for i, n in enumerate(circuit.s_nodes): for i, n in enumerate(circuit.s_nodes(KYUPY)):
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 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 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] i0_idx = stems[n.ins[0]] if stems[n.ins[0]] >= 0 else n.ins[0]
ref_count[i0_idx] += 1 ref_count[i0_idx] += 1
@ -306,10 +313,15 @@ class SimOps:
self.c_locs[lidx], self.c_caps[lidx] = self.c_locs[stem], self.c_caps[stem] self.c_locs[lidx], self.c_caps[lidx] = self.c_locs[stem], self.c_caps[stem]
# copy memory location to PO/PPO area # copy memory location to PO/PPO area
for i, n in enumerate(circuit.s_nodes): for i, n in enumerate(circuit.s_nodes(KYUPY)):
if len(n.ins) > 0: if len(n.ins) > 0:
self.c_locs[self.ppo_offset + i], self.c_caps[self.ppo_offset + i] = self.c_locs[n.ins[0]], self.c_caps[n.ins[0]] self.c_locs[self.ppo_offset + i], self.c_caps[self.ppo_offset + i] = self.c_locs[n.ins[0]], self.c_caps[n.ins[0]]
# sanity check
for oidx, io in zip(*np.nonzero(self.c_locs[self.ops[:, 1:6]] < 0)):
log.error(f'unallocated {["o0","i0","i1","i2","i3"][io]} in op {self.ops[oidx]}')
assert np.min(self.c_locs[self.ops[:, 1:6]]) >= 0, "Unallocated signal lines."
# line use information # line use information
self.line_use_start = np.full(self.c_locs_len, -1, dtype=np.int32) self.line_use_start = np.full(self.c_locs_len, -1, dtype=np.int32)
self.line_use_stop = np.full(self.c_locs_len, len(self.levels), dtype=np.int32) self.line_use_stop = np.full(self.c_locs_len, len(self.levels), dtype=np.int32)
@ -326,11 +338,16 @@ class SimOps:
self.prim_counts = dict(d) self.prim_counts = dict(d)
self.pi_s_locs = np.flatnonzero(self.c_locs[self.ppi_offset+np.arange(len(self.circuit.io_nodes))] >= 0) self.pi_s_locs = np.flatnonzero(self.c_locs[self.ppi_offset+np.arange(len(self.circuit.io_nodes))] >= 0)
"""Locations in pattern array (axis 0) of all primary inputs (excluding flip-flops)."""
self.po_s_locs = np.flatnonzero(self.c_locs[self.ppo_offset+np.arange(len(self.circuit.io_nodes))] >= 0) self.po_s_locs = np.flatnonzero(self.c_locs[self.ppo_offset+np.arange(len(self.circuit.io_nodes))] >= 0)
"""Locations in pattern array (axis 0) of all primary outputs (excluding flip-flops)."""
self.ppio_s_locs = np.arange(len(self.circuit.io_nodes), self.s_len) self.ppio_s_locs = np.arange(len(self.circuit.io_nodes), self.s_len)
"""Locations in pattern array (axis 0) of all flip-flops. Pseudo-primary inputs (PPI) and pseudo-primary outputs (PPO) share the same location."""
self.pippi_s_locs = np.concatenate([self.pi_s_locs, self.ppio_s_locs]) self.pippi_s_locs = np.concatenate([self.pi_s_locs, self.ppio_s_locs])
"""Locations in pattern array (axis 0) of all primary and pseudo-primary inputs (input ports and flip-flops)."""
self.poppo_s_locs = np.concatenate([self.po_s_locs, self.ppio_s_locs]) self.poppo_s_locs = np.concatenate([self.po_s_locs, self.ppio_s_locs])
"""Locations in pattern array (axis 0) of all primary and pseudo-primary outputs (output ports and flip-flops)."""
self.pi_c_locs = self.c_locs[self.ppi_offset+self.pi_s_locs] self.pi_c_locs = self.c_locs[self.ppi_offset+self.pi_s_locs]
self.po_c_locs = self.c_locs[self.ppo_offset+self.po_s_locs] self.po_c_locs = self.c_locs[self.ppo_offset+self.po_s_locs]
@ -339,3 +356,12 @@ class SimOps:
self.pippi_c_locs = np.concatenate([self.pi_c_locs, self.ppi_c_locs]) self.pippi_c_locs = np.concatenate([self.pi_c_locs, self.ppi_c_locs])
self.poppo_c_locs = np.concatenate([self.po_c_locs, self.ppo_c_locs]) self.poppo_c_locs = np.concatenate([self.po_c_locs, self.ppo_c_locs])
def cone_ops(self, origin_lines: set[int]):
active_lines = {l for l in origin_lines}
ops = []
for kind, out0, in0, in1, in2, in3, *params in self.ops:
if in0 in active_lines or in1 in active_lines or in2 in active_lines or in3 in active_lines or out0 in origin_lines:
ops.append((kind, out0, in0, in1, in2, in3, *params))
active_lines.add(out0)
return np.asarray(ops, dtype=np.int32)

12
src/kyupy/stil.py

@ -106,7 +106,7 @@ class StilFile:
tests[pi_map, i] = logic.mvarray(p.capture['_pi'][0]) tests[pi_map, i] = logic.mvarray(p.capture['_pi'][0])
return tests return tests
def tests_loc(self, circuit, init_filter=None, launch_filter=None): def tests_loc(self, circuit, tlib, init_filter=None, launch_filter=None):
"""Assembles and returns a LoC scan test pattern set for given circuit. """Assembles and returns a LoC scan test pattern set for given circuit.
This function assumes a launch-on-capture (LoC) delay test. This function assumes a launch-on-capture (LoC) delay test.
@ -139,7 +139,9 @@ class StilFile:
init[scan_maps[si_port], i] = pattern init[scan_maps[si_port], i] = pattern
init[pi_map, i] = logic.mvarray(p.launch['_pi'][0] if '_pi' in p.launch else p.capture['_pi'][0]) init[pi_map, i] = logic.mvarray(p.launch['_pi'][0] if '_pi' in p.launch else p.capture['_pi'][0])
if init_filter: init = init_filter(init) if init_filter: init = init_filter(init)
sim8v = LogicSim(circuit, init.shape[-1], m=8) circuit_resolved = circuit.copy()
circuit_resolved.resolve_tlib_cells(tlib)
sim8v = LogicSim(circuit_resolved, init.shape[-1], m=8)
sim8v.s[0] = logic.mv_to_bp(init) sim8v.s[0] = logic.mv_to_bp(init)
sim8v.s_to_c() sim8v.s_to_c()
sim8v.c_prop() sim8v.c_prop()
@ -263,12 +265,12 @@ GRAMMAR = r"""
""" """
def parse(text): def parse(text) -> StilFile:
"""Parses the given ``text`` and returns a :class:`StilFile` object.""" """Parses the given ``text`` and returns a :class:`StilFile` object."""
return Lark(GRAMMAR, parser="lalr", transformer=StilTransformer()).parse(text) return Lark(GRAMMAR, parser="lalr", transformer=StilTransformer()).parse(text) # type: ignore
def load(file): def load(file) -> StilFile:
"""Parses the contents of ``file`` and returns a :class:`StilFile` object. """Parses the contents of ``file`` and returns a :class:`StilFile` object.
Files with `.gz`-suffix are decompressed on-the-fly. Files with `.gz`-suffix are decompressed on-the-fly.

274
src/kyupy/techlib.py

@ -10,7 +10,6 @@ from itertools import product
from . import bench from . import bench
class TechLib: class TechLib:
"""Class for standard cell library definitions. """Class for standard cell library definitions.
@ -39,9 +38,11 @@ class TechLib:
else: else:
pin_dict[n.name] = (o_idx, True) pin_dict[n.name] = (o_idx, True)
o_idx += 1 o_idx += 1
has_dff = 'DFF' in set(n.kind for n in c.cells.values())
has_latch = 'LATCH' in set(n.kind for n in c.cells.values())
parts = [s[1:-1].split(',') if s[0] == '{' else [s] for s in re.split(r'({[^}]+})', c.name) if len(s) > 0] parts = [s[1:-1].split(',') if s[0] == '{' else [s] for s in re.split(r'({[^}]+})', c.name) if len(s) > 0]
for name in [''.join(item) for item in product(*parts)]: for name in [''.join(item) for item in product(*parts)]:
self.cells[name] = (c, pin_dict) self.cells[name] = (c, pin_dict, has_dff, has_latch)
def pin_index(self, kind, pin): def pin_index(self, kind, pin):
"""Returns a pin list position for a given node kind and pin name.""" """Returns a pin list position for a given node kind and pin name."""
@ -49,13 +50,13 @@ class TechLib:
assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}' assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}'
return self.cells[kind][1][pin][0] return self.cells[kind][1][pin][0]
def pin_name(self, kind, pos, output=False): def pin_name(self, kind: str, pos: int, output=False) -> str:
"""Returns the pin name for a given node kind, list position, and direction.""" """Returns the pin name for a given node kind, list position, and direction."""
assert kind in self.cells, f'Unknown cell: {kind}' assert kind in self.cells, f'Unknown cell: {kind}'
for name, (ppos, isout) in self.cells[kind][1].items(): for name, (ppos, isout) in self.cells[kind][1].items():
if isout == output and ppos == pos: if isout == output and ppos == pos:
return name return name
return None raise ValueError(f'Invalid {"output" if output else "input"} pin position {pos} for cell: {kind}')
def pin_is_output(self, kind, pin): def pin_is_output(self, kind, pin):
"""Returns True, if given pin name of a node kind is an output.""" """Returns True, if given pin name of a node kind is an output."""
@ -63,6 +64,66 @@ class TechLib:
assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}' assert pin in self.cells[kind][1], f'Unknown pin: {pin} for cell {kind}'
return self.cells[kind][1][pin][1] return self.cells[kind][1][pin][1]
def is_dff(self, kind):
"""Returns True, if given node kind is a d-flip-flop."""
if kind == '__fork__': return False
if kind == '__const0__': return False
if kind == '__const1__': return False
if kind == 'input': return False
if kind == 'output': return False
assert kind in self.cells, f'Unknown cell: {kind}'
return self.cells[kind][2]
def is_latch(self, kind):
"""Returns True, if given node kind is a latch."""
if kind == '__fork__': return False
if kind == '__const0__': return False
if kind == '__const1__': return False
if kind == 'input': return False
if kind == 'output': return False
assert kind in self.cells, f'Unknown cell: {kind}'
return self.cells[kind][3]
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) ;
LATCH input(D,CLK) output(Q) Q=LATCH(D,CLK) ;
""")
"""A synthetic library of all KyuPy simulation primitives.
"""
GSC180 = TechLib(r""" GSC180 = TechLib(r"""
BUFX{1,3} input(A) output(Y) Y=BUF1(A) ; BUFX{1,3} input(A) output(Y) Y=BUF1(A) ;
@ -89,7 +150,7 @@ OAI21X1 input(A0,A1,B0) output(Y) Y=OAI21(A0,A1,B0) ;
OAI22X1 input(A0,A1,B0,B1) output(Y) Y=OAI22(A0,A1,B0,B1) ; OAI22X1 input(A0,A1,B0,B1) output(Y) Y=OAI22(A0,A1,B0,B1) ;
OAI33X1 input(A0,A1,A2,B0,B1,B2) output(Y) AA=OR2(A0,A1) BB=OR2(B0,B1) Y=OAI22(AA,A2,BB,B2) ; OAI33X1 input(A0,A1,A2,B0,B1,B2) output(Y) AA=OR2(A0,A1) BB=OR2(B0,B1) Y=OAI22(AA,A2,BB,B2) ;
ADDFX1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ; ADDFX1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ;
ADDHX1 input(A,B) output(CO,S) CO=XOR2(A,B) S=AND2(A,B) ; ADDHX1 input(A,B) output(CO,S) CO=AND2(A,B) S=XOR2(A,B) ;
DFFX1 input(CK,D) output(Q,QN) Q=DFF(D,CK) QN=INV1(Q) ; DFFX1 input(CK,D) output(Q,QN) Q=DFF(D,CK) QN=INV1(Q) ;
DFFSRX1 input(CK,D,RN,SN) output(Q,QN) DR=AND2(D,RN) SET=INV1(SN) DRS=OR2(DR,SET) Q=DFF(DRS,CK) QN=INV1(Q) ; DFFSRX1 input(CK,D,RN,SN) output(Q,QN) DR=AND2(D,RN) SET=INV1(SN) DRS=OR2(DR,SET) Q=DFF(DRS,CK) QN=INV1(Q) ;
@ -133,7 +194,7 @@ XNOR2_X1 input(A1,A2) output(ZN) ZN=XNOR2(A1,A2) ;
MUX2_X1 input(I0,I1,S) output(Z) Z=MUX21(I0,I1,S) ; MUX2_X1 input(I0,I1,S) output(Z) Z=MUX21(I0,I1,S) ;
HA_X1 input(A,B) output(CO,S) CO=XOR2(A,B) S=AND2(A,B) ; HA_X1 input(A,B) output(CO,S) CO=AND2(A,B) S=XOR2(A,B) ;
FA_X1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(CI,A,B) ; FA_X1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(CI,A,B) ;
AOI21_X{1,2} input(A1,A2,B) output(ZN) ZN=AOI21(A1,A2,B) ; AOI21_X{1,2} input(A1,A2,B) output(ZN) ZN=AOI21(A1,A2,B) ;
@ -186,7 +247,7 @@ XNOR2_X{1,2} input(A,B) output(ZN) ZN=XNOR2(A,B) ;
MUX2_X{1,2} input(A,B,S) output(Z) Z=MUX21(A,B,S) ; MUX2_X{1,2} input(A,B,S) output(Z) Z=MUX21(A,B,S) ;
HA_X1 input(A,B) output(CO,S) CO=XOR2(A,B) S=AND2(A,B) ; HA_X1 input(A,B) output(CO,S) CO=AND2(A,B) S=XOR2(A,B) ;
FA_X1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(CI,A,B) ; FA_X1 input(A,B,CI) output(CO,S) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(CI,A,B) ;
AOI21_X{1,2,4} input(A,B1,B2) output(ZN) ZN=AOI21(B1,B2,A) ; AOI21_X{1,2,4} input(A,B1,B2) output(ZN) ZN=AOI21(B1,B2,A) ;
@ -301,7 +362,7 @@ MUX41X{1,2}$ input(A1,A2,A3,A4,S0,S1) output(Y) A=MUX21(A1,A2,S0) B=MUX21(A3,A4,
DEC24X{1,2}$ input(A0,A1) output(Y0,Y1,Y2,Y3) A0B=INV1(A0) A1B=INV1(A1) Y0=NOR2(A0,A1) Y1=AND(A0,A1B) Y2=AND(A0B,A1) Y3=AND(A0,A1) ; DEC24X{1,2}$ input(A0,A1) output(Y0,Y1,Y2,Y3) A0B=INV1(A0) A1B=INV1(A1) Y0=NOR2(A0,A1) Y1=AND(A0,A1B) Y2=AND(A0B,A1) Y3=AND(A0,A1) ;
FADDX{1,2}$ input(A,B,CI) output(S,CO) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ; FADDX{1,2}$ input(A,B,CI) output(S,CO) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ;
HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=XOR2(A0,B0) SO=AND2(A0,B0) ; HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=AND2(A0,B0) SO=XOR2(A0,B0) ;
{,AO}DFFARX{1,2}$ input(D,CLK,RSTB) output(Q,QN) DR=AND2(D,RSTB) Q=DFF(DR,CLK) QN=INV1(Q) ; {,AO}DFFARX{1,2}$ input(D,CLK,RSTB) output(Q,QN) DR=AND2(D,RSTB) Q=DFF(DR,CLK) QN=INV1(Q) ;
DFFASRX{1,2}$ input(D,CLK,RSTB,SETB) output(Q,QN) DR=AND2(D,RSTB) SET=INV1(SETB) DRS=OR2(DR,SET) Q=DFF(DRS,CLK) QN=INV1(Q) ; DFFASRX{1,2}$ input(D,CLK,RSTB,SETB) output(Q,QN) DR=AND2(D,RSTB) SET=INV1(SETB) DRS=OR2(DR,SET) Q=DFF(DRS,CLK) QN=INV1(Q) ;
@ -326,7 +387,7 @@ It defines all cells except: negative-edge flip-flops, tri-state, latches, clock
SAED90 = TechLib(r""" SAED90 = TechLib(r"""
NBUFFX{2,4,8,16,32}$ input(INP) output(Z) Z=BUF1(INP) ; NBUFFX{2,4,8,16,32}$ input(INP) output(Z) Z=BUF1(INP) ;
AOBUFX{1,2,4}$ 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) ; 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) ; AOINVX{1,2,4}$ input(INP) output(ZN) ZN=INV1(INP) ;
@ -391,7 +452,7 @@ MUX41X{1,2}$ input(IN1,IN2,IN3,IN4,S0,S1) output(Q) A=MUX21(IN1,IN2,S0) B=MUX21(
DEC24X{1,2}$ input(IN1,IN2) output(Q0,Q1,Q2,Q3) IN1B=INV1(IN1) IN2B=INV1(IN2) Q0=NOR2(IN1,IN2) Q1=AND(IN1,IN2B) Q2=AND(IN1B,IN2) Q3=AND(IN1,IN2) ; DEC24X{1,2}$ input(IN1,IN2) output(Q0,Q1,Q2,Q3) IN1B=INV1(IN1) IN2B=INV1(IN2) Q0=NOR2(IN1,IN2) Q1=AND(IN1,IN2B) Q2=AND(IN1B,IN2) Q3=AND(IN1,IN2) ;
FADDX{1,2}$ input(A,B,CI) output(S,CO) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ; FADDX{1,2}$ input(A,B,CI) output(S,CO) AB=XOR2(A,B) CO=XOR2(AB,CI) S=AO22(AB,CI,A,B) ;
HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=XOR2(A0,B0) SO=AND2(A0,B0) ; HADDX{1,2}$ input(A0,B0) output(SO,C1) C1=AND2(A0,B0) SO=XOR2(A0,B0) ;
{,AO}DFFARX{1,2}$ input(D,CLK,RSTB) output(Q,QN) DR=AND2(D,RSTB) Q=DFF(DR,CLK) QN=INV1(Q) ; {,AO}DFFARX{1,2}$ input(D,CLK,RSTB) output(Q,QN) DR=AND2(D,RSTB) Q=DFF(DR,CLK) QN=INV1(Q) ;
DFFASRX{1,2}$ input(D,CLK,RSTB,SETB) output(Q,QN) DR=AND2(D,RSTB) SET=INV1(SETB) DRS=OR2(DR,SET) Q=DFF(DRS,CLK) QN=INV1(Q) ; DFFASRX{1,2}$ input(D,CLK,RSTB,SETB) output(Q,QN) DR=AND2(D,RSTB) SET=INV1(SETB) DRS=OR2(DR,SET) Q=DFF(DRS,CLK) QN=INV1(Q) ;
@ -411,3 +472,196 @@ LATCHX{1,2}$ input(D,CLK) output(Q,QN) Q=LATCH(D,CLK) QN=INV1(Q) ;
"""The SAED 90nm educational technology library. """The SAED 90nm educational technology library.
It defines all cells except: negative-edge flip-flops, tri-state, latches, clock gating, level shifters It defines all cells except: negative-edge flip-flops, tri-state, latches, clock gating, level shifters
""" """
SKY130 = TechLib(r"""
$decap_{3,4,6,8,12} ;
$fill_{1,2,4,8} ;
$tap_{1,2} ;
$tapvgnd_1 ;
$tapvgnd2_1 ;
$tapvpwrvgnd_1 ;
$lpflow_decapkapwr_{3,4,6,8,12} ;
$lpflow_bleeder_1 input(SHORT) ;
$conb_1 output(HI,LO) HI=__const1__() LO=__const0__() ;
$macro_sparecell output(LO) LO=__const0__() ;
$diode_2 input(DIODE) ;
$probe_p_8 input(A) output(X) X=BUF1(A) ;
$probec_p_8 input(A) output(X) X=BUF1(A) ;
$inv_{1,2,4,6,8,12,16} input(A) output(Y) Y=INV1(A) ;
$buf_{1,2,4,6,8,12,16} input(A) output(X) X=BUF1(A) ;
$bufbuf_{8,16} input(A) output(X) X=BUF1(A) ;
$bufinv_{8,16} input(A) output(Y) Y=INV1(A) ;
$clkbuf_{1,2,4,8,16} input(A) output(X) X=BUF1(A) ;
$clkinv_{1,2,4,8,16} input(A) output(Y) Y=INV1(A) ;
$clkinvlp_{2,4} input(A) output(Y) Y=INV1(A) ;
$clkdlybuf4s15_{1,2} input(A) output(X) X=BUF1(A) ;
$clkdlybuf4s18_{1,2} input(A) output(X) X=BUF1(A) ;
$clkdlybuf4s25_{1,2} input(A) output(X) X=BUF1(A) ;
$clkdlybuf4s50_{1,2} input(A) output(X) X=BUF1(A) ;
$dlygate4sd1_1 input(A) output(X) X=BUF1(A) ;
$dlygate4sd2_1 input(A) output(X) X=BUF1(A) ;
$dlygate4sd3_1 input(A) output(X) X=BUF1(A) ;
$dlymetal6s2s_1 input(A) output(X) X=BUF1(A) ;
$dlymetal6s4s_1 input(A) output(X) X=BUF1(A) ;
$dlymetal6s6s_1 input(A) output(X) X=BUF1(A) ;
$lpflow_clkbufkapwr_{1,2,4,8,16} input(A) output(X) X=BUF1(A) ;
$lpflow_clkinvkapwr_{1,2,4,8,16} input(A) output(Y) Y=INV1(A) ;
$lpflow_lsbuf_lh_hl_isowell_tap_{1,2,4} input(A) output(X) X=BUF1(A) ;
$lpflow_lsbuf_lh_isowell_4 input(A) output(X) X=BUF1(A) ;
$lpflow_lsbuf_lh_isowell_tap_{1,2,4} input(A) output(X) X=BUF1(A) ;
$and2_{0,1,2,4} input(A,B) output(X) X=AND2(A,B) ;
$and2b_{1,2,4} input(A_N,B) output(X) AN=INV1(A_N) X=AND2(AN,B) ;
$and3_{1,2,4} input(A,B,C) output(X) X=AND3(A,B,C) ;
$and3b_{1,2,4} input(A_N,B,C) output(X) AN=INV1(A_N) X=AND3(AN,B,C) ;
$and4_{1,2,4} input(A,B,C,D) output(X) X=AND4(A,B,C,D) ;
$and4b_{1,2,4} input(A_N,B,C,D) output(X) AN=INV1(A_N) X=AND4(AN,B,C,D) ;
$and4bb_{1,2,4} input(A_N,B_N,C,D) output(X) AN=INV1(A_N) BN=INV1(B_N) X=AND4(AN,BN,C,D) ;
$or2_{0,1,2,4} input(A,B) output(X) X=OR2(A,B) ;
$or2b_{1,2,4} input(A,B_N) output(X) BN=INV1(B_N) X=OR2(A,BN) ;
$or3_{1,2,4} input(A,B,C) output(X) X=OR3(A,B,C) ;
$or3b_{1,2,4} input(A,B,C_N) output(X) CN=INV1(C_N) X=OR3(A,B,CN) ;
$or4_{1,2,4} input(A,B,C,D) output(X) X=OR4(A,B,C,D) ;
$or4b_{1,2,4} input(A,B,C,D_N) output(X) DN=INV1(D_N) X=OR4(A,B,C,DN) ;
$or4bb_{1,2,4} input(A,B,C_N,D_N) output(X) CN=INV1(C_N) DN=INV1(D_N) X=OR4(A,B,CN,DN) ;
$nand2_{1,2,4,8} input(A,B) output(Y) Y=NAND2(A,B) ;
$nand2b_{1,2,4} input(A_N,B) output(Y) AN=INV1(A_N) Y=NAND2(AN,B) ;
$nand3_{1,2,4} input(A,B,C) output(Y) Y=NAND3(A,B,C) ;
$nand3b_{1,2,4} input(A_N,B,C) output(Y) AN=INV1(A_N) Y=NAND3(AN,B,C) ;
$nand4_{1,2,4} input(A,B,C,D) output(Y) Y=NAND4(A,B,C,D) ;
$nand4b_{1,2,4} input(A_N,B,C,D) output(Y) AN=INV1(A_N) Y=NAND4(AN,B,C,D) ;
$nand4bb_{1,2,4} input(A_N,B_N,C,D) output(Y) AN=INV1(A_N) BN=INV1(B_N) Y=NAND4(AN,BN,C,D) ;
$nor2_{1,2,4,8} input(A,B) output(Y) Y=NOR2(A,B) ;
$nor2b_{1,2,4} input(A,B_N) output(Y) BN=INV1(B_N) Y=NOR2(A,BN) ;
$nor3_{1,2,4} input(A,B,C) output(Y) Y=NOR3(A,B,C) ;
$nor3b_{1,2,4} input(A,B,C_N) output(Y) CN=INV1(C_N) Y=NOR3(A,B,CN) ;
$nor4_{1,2,4} input(A,B,C,D) output(Y) Y=NOR4(A,B,C,D) ;
$nor4b_{1,2,4} input(A,B,C,D_N) output(Y) DN=INV1(D_N) Y=NOR4(A,B,C,DN) ;
$nor4bb_{1,2,4} input(A,B,C_N,D_N) output(Y) CN=INV1(C_N) DN=INV1(D_N) Y=NOR4(A,B,CN,DN) ;
$xor2_{1,2,4} input(A,B) output(X) X=XOR2(A,B) ;
$xor3_{1,2,4} input(A,B,C) output(X) X=XOR3(A,B,C) ;
$xnor2_{1,2,4} input(A,B) output(Y) Y=XNOR2(A,B) ;
$xnor3_{1,2,4} input(A,B,C) output(X) X=XNOR3(A,B,C) ;
$maj3_{1,2,4} input(A,B,C) output(X) AB=AND2(A,B) BC=AND2(B,C) AC=AND2(A,C) X=OR3(AB,BC,AC) ;
$mux2_{1,2,4,8} input(A0,A1,S) output(X) X=MUX21(A0,A1,S) ;
$mux2i_{1,2,4} input(A0,A1,S) output(Y) M=MUX21(A0,A1,S) Y=INV1(M) ;
$mux4_{1,2,4} input(A0,A1,A2,A3,S0,S1) output(X) M0=MUX21(A0,A1,S0) M1=MUX21(A2,A3,S0) X=MUX21(M0,M1,S1) ;
$ha_{1,2,4} input(A,B) output(COUT,SUM) SUM=XOR2(A,B) COUT=AND2(A,B) ;
$fa_{1,2,4} input(A,B,CIN) output(COUT,SUM) AB=XOR2(A,B) SUM=XOR2(AB,CIN) COUT=AO22(A,B,AB,CIN) ;
$fah_1 input(A,B,CI) output(COUT,SUM) AB=XOR2(A,B) SUM=XOR2(AB,CI) COUT=AO22(A,B,AB,CI) ;
$fahcin_1 input(A,B,CIN) output(COUT,SUM) AB=XOR2(A,B) SUM=XOR2(AB,CIN) COUT=AO22(A,B,AB,CIN) ;
$fahcon_1 input(A,B,CI) output(COUT_N,SUM) AB=XOR2(A,B) SUM=XOR2(AB,CI) T=AO22(A,B,AB,CI) COUT_N=INV1(T) ;
$a21o_{1,2,4} input(A1,A2,B1) output(X) X=AO21(A1,A2,B1) ;
$a21bo_{1,2,4} input(A1,A2,B1_N) output(X) BN=INV1(B1_N) X=AO21(A1,A2,BN) ;
$a22o_{1,2,4} input(A1,A2,B1,B2) output(X) X=AO22(A1,A2,B1,B2) ;
$a2bb2o_{1,2,4} input(A1_N,A2_N,B1,B2) output(X) AN1=INV1(A1_N) AN2=INV1(A2_N) X=AO22(AN1,AN2,B1,B2) ;
$a211o_{1,2,4} input(A1,A2,B1,C1) output(X) X=AO211(A1,A2,B1,C1) ;
$a221o_{1,2,4} input(A1,A2,B1,B2,C1) output(X) T=AO22(A1,A2,B1,B2) X=OR2(T,C1) ;
$a31o_{1,2,4} input(A1,A2,A3,B1) output(X) AA=AND3(A1,A2,A3) X=OR2(AA,B1) ;
$a311o_{1,2,4} input(A1,A2,A3,B1,C1) output(X) T=AND2(A1,A2) X=AO211(T,A3,B1,C1) ;
$a32o_{1,2,4} input(A1,A2,A3,B1,B2) output(X) T=AND2(A1,A2) X=AO22(T,A3,B1,B2) ;
$a41o_{1,2,4} input(A1,A2,A3,A4,B1) output(X) T1=AND2(A1,A2) T2=AND2(T1,A3) X=AO21(T2,A4,B1) ;
$a2111o_{1,2,4} input(A1,A2,B1,C1,D1) output(X) T=AO211(A1,A2,B1,C1) X=OR2(T,D1) ;
$a21oi_{1,2,4} input(A1,A2,B1) output(Y) Y=AOI21(A1,A2,B1) ;
$a21boi_{0,1,2,4} input(A1,A2,B1_N) output(Y) BN=INV1(B1_N) Y=AOI21(A1,A2,BN) ;
$a22oi_{1,2,4} input(A1,A2,B1,B2) output(Y) Y=AOI22(A1,A2,B1,B2) ;
$a2bb2oi_{1,2,4} input(A1_N,A2_N,B1,B2) output(Y) AN1=INV1(A1_N) AN2=INV1(A2_N) Y=AOI22(AN1,AN2,B1,B2) ;
$a211oi_{1,2,4} input(A1,A2,B1,C1) output(Y) Y=AOI211(A1,A2,B1,C1) ;
$a221oi_{1,2,4} input(A1,A2,B1,B2,C1) output(Y) T=AO22(A1,A2,B1,B2) Y=NOR2(T,C1) ;
$a222oi_1 input(A1,A2,B1,B2,C1,C2) output(Y) AB=AO22(A1,A2,B1,B2) Y=AOI21(C1,C2,AB) ;
$a31oi_{1,2,4} input(A1,A2,A3,B1) output(Y) AA=AND3(A1,A2,A3) Y=NOR2(AA,B1) ;
$a311oi_{1,2,4} input(A1,A2,A3,B1,C1) output(Y) T=AND2(A1,A2) Y=AOI211(T,A3,B1,C1) ;
$a32oi_{1,2,4} input(A1,A2,A3,B1,B2) output(Y) T=AND2(A1,A2) Y=AOI22(T,A3,B1,B2) ;
$a41oi_{1,2,4} input(A1,A2,A3,A4,B1) output(Y) T1=AND2(A1,A2) T2=AND2(T1,A3) Y=AOI21(T2,A4,B1) ;
$a2111oi_{0,1,2,4} input(A1,A2,B1,C1,D1) output(Y) T=AO211(A1,A2,B1,C1) Y=NOR2(T,D1) ;
$o21a_{1,2,4} input(A1,A2,B1) output(X) X=OA21(A1,A2,B1) ;
$o21ba_{1,2,4} input(A1,A2,B1_N) output(X) BN=INV1(B1_N) X=OA21(A1,A2,BN) ;
$o22a_{1,2,4} input(A1,A2,B1,B2) output(X) X=OA22(A1,A2,B1,B2) ;
$o2bb2a_{1,2,4} input(A1_N,A2_N,B1,B2) output(X) T=NAND2(A1_N,A2_N) X=OA21(B1,B2,T) ;
$o211a_{1,2,4} input(A1,A2,B1,C1) output(X) X=OA211(A1,A2,B1,C1) ;
$o221a_{1,2,4} input(A1,A2,B1,B2,C1) output(X) T=OA22(A1,A2,B1,B2) X=AND2(T,C1) ;
$o31a_{1,2,4} input(A1,A2,A3,B1) output(X) T=OR3(A1,A2,A3) X=AND2(T,B1) ;
$o311a_{1,2,4} input(A1,A2,A3,B1,C1) output(X) T=OR2(A1,A2) X=OA211(T,A3,B1,C1) ;
$o32a_{1,2,4} input(A1,A2,A3,B1,B2) output(X) T=OR3(A1,A2,A3) X=OA21(B1,B2,T) ;
$o41a_{1,2,4} input(A1,A2,A3,A4,B1) output(X) T=OR4(A1,A2,A3,A4) X=AND2(T,B1) ;
$o2111a_{1,2,4} input(A1,A2,B1,C1,D1) output(X) T=OA211(A1,A2,B1,C1) X=AND2(T,D1) ;
$o21ai_{0,1,2,4} input(A1,A2,B1) output(Y) Y=OAI21(A1,A2,B1) ;
$o21bai_{1,2,4} input(A1,A2,B1_N) output(Y) BN=INV1(B1_N) Y=OAI21(A1,A2,BN) ;
$o22ai_{1,2,4} input(A1,A2,B1,B2) output(Y) Y=OAI22(A1,A2,B1,B2) ;
$o2bb2ai_{1,2,4} input(A1_N,A2_N,B1,B2) output(Y) T=NAND2(A1_N,A2_N) Y=OAI21(B1,B2,T) ;
$o211ai_{1,2,4} input(A1,A2,B1,C1) output(Y) Y=OAI211(A1,A2,B1,C1) ;
$o221ai_{1,2,4} input(A1,A2,B1,B2,C1) output(Y) T=OA22(A1,A2,B1,B2) Y=NAND2(T,C1) ;
$o31ai_{1,2,4} input(A1,A2,A3,B1) output(Y) T=OR3(A1,A2,A3) Y=NAND2(T,B1) ;
$o311ai_{0,1,2,4} input(A1,A2,A3,B1,C1) output(Y) T=OR2(A1,A2) Y=OAI211(T,A3,B1,C1) ;
$o32ai_{1,2,4} input(A1,A2,A3,B1,B2) output(Y) T=OR3(A1,A2,A3) Y=OAI21(B1,B2,T) ;
$o41ai_{1,2,4} input(A1,A2,A3,A4,B1) output(Y) T=OR4(A1,A2,A3,A4) Y=NAND2(T,B1) ;
$o2111ai_{1,2,4} input(A1,A2,B1,C1,D1) output(Y) T=OA211(A1,A2,B1,C1) Y=NAND2(T,D1) ;
$dfxtp_{1,2,4} input(CLK,D) output(Q) Q=DFF(D,CLK) ;
$dfxbp_{1,2} input(CLK,D) output(Q,Q_N) Q=DFF(D,CLK) Q_N=INV1(Q) ;
$dfrtp_{1,2,4} input(CLK,D,RESET_B) output(Q) DR=AND2(D,RESET_B) Q=DFF(DR,CLK) ;
$dfrbp_{1,2} input(CLK,D,RESET_B) output(Q,Q_N) DR=AND2(D,RESET_B) Q=DFF(DR,CLK) Q_N=INV1(Q) ;
$dfstp_{1,2,4} input(CLK,D,SET_B) output(Q) S=INV1(SET_B) DS=OR2(D,S) Q=DFF(DS,CLK) ;
$dfsbp_{1,2} input(CLK,D,SET_B) output(Q,Q_N) S=INV1(SET_B) DS=OR2(D,S) Q=DFF(DS,CLK) Q_N=INV1(Q) ;
$dfbbp_1 input(CLK,D,SET_B,RESET_B) output(Q,Q_N) DR=AND2(D,RESET_B) S=INV1(SET_B) DRS=OR2(DR,S) Q=DFF(DRS,CLK) Q_N=INV1(Q) ;
$dfrtn_1 input(CLK_N,D,RESET_B) output(Q) CLKN=INV1(CLK_N) DR=AND2(D,RESET_B) Q=DFF(DR,CLKN) ;
$dfbbn_{1,2} input(CLK_N,D,SET_B,RESET_B) output(Q,Q_N) CLKN=INV1(CLK_N) DR=AND2(D,RESET_B) S=INV1(SET_B) DRS=OR2(DR,S) Q=DFF(DRS,CLKN) Q_N=INV1(Q) ;
$sdfxtp_{1,2,4} input(CLK,D,SCD,SCE) output(Q) DI=MUX21(D,SCD,SCE) Q=DFF(DI,CLK) ;
$sdfxbp_{1,2} input(CLK,D,SCD,SCE) output(Q,Q_N) DI=MUX21(D,SCD,SCE) Q=DFF(DI,CLK) Q_N=INV1(Q) ;
$sdfrtp_{1,2,4} input(CLK,D,SCD,SCE,RESET_B) output(Q) DR=AND2(D,RESET_B) DI=MUX21(DR,SCD,SCE) Q=DFF(DI,CLK) ;
$sdfrbp_{1,2} input(CLK,D,SCD,SCE,RESET_B) output(Q,Q_N) DR=AND2(D,RESET_B) DI=MUX21(DR,SCD,SCE) Q=DFF(DI,CLK) Q_N=INV1(Q) ;
$sdfstp_{1,2,4} input(CLK,D,SCD,SCE,SET_B) output(Q) S=INV1(SET_B) DS=OR2(D,S) DI=MUX21(DS,SCD,SCE) Q=DFF(DI,CLK) ;
$sdfsbp_{1,2} input(CLK,D,SCD,SCE,SET_B) output(Q,Q_N) S=INV1(SET_B) DS=OR2(D,S) DI=MUX21(DS,SCD,SCE) Q=DFF(DI,CLK) Q_N=INV1(Q) ;
$sdfbbp_1 input(CLK,D,SCD,SCE,SET_B,RESET_B) output(Q,Q_N) DR=AND2(D,RESET_B) S=INV1(SET_B) DRS=OR2(DR,S) DI=MUX21(DRS,SCD,SCE) Q=DFF(DI,CLK) Q_N=INV1(Q) ;
$sdfrtn_1 input(CLK_N,D,SCD,SCE,RESET_B) output(Q) CLKN=INV1(CLK_N) DR=AND2(D,RESET_B) DI=MUX21(DR,SCD,SCE) Q=DFF(DI,CLKN) ;
$sdfbbn_{1,2} input(CLK_N,D,SCD,SCE,SET_B,RESET_B) output(Q,Q_N) CLKN=INV1(CLK_N) DR=AND2(D,RESET_B) S=INV1(SET_B) DRS=OR2(DR,S) DI=MUX21(DRS,SCD,SCE) Q=DFF(DI,CLKN) Q_N=INV1(Q) ;
$edfxtp_1 input(CLK,D,DE) output(Q) GCK=AND2(CLK,DE) Q=DFF(D,GCK) ;
$edfxbp_1 input(CLK,D,DE) output(Q,Q_N) GCK=AND2(CLK,DE) Q=DFF(D,GCK) Q_N=INV1(Q) ;
$sedfxtp_{1,2,4} input(CLK,D,DE,SCD,SCE) output(Q) DI=MUX21(D,SCD,SCE) GCK=AND2(CLK,DE) Q=DFF(DI,GCK) ;
$sedfxbp_{1,2} input(CLK,D,DE,SCD,SCE) output(Q,Q_N) DI=MUX21(D,SCD,SCE) GCK=AND2(CLK,DE) Q=DFF(DI,GCK) Q_N=INV1(Q) ;
$dlxtp_1 input(D,GATE) output(Q) Q=LATCH(D,GATE) ;
$dlxtn_{1,2,4} input(D,GATE_N) output(Q) GN=INV1(GATE_N) Q=LATCH(D,GN) ;
$dlxbp_1 input(D,GATE) output(Q,Q_N) Q=LATCH(D,GATE) Q_N=INV1(Q) ;
$dlxbn_{1,2} input(D,GATE_N) output(Q,Q_N) GN=INV1(GATE_N) Q=LATCH(D,GN) Q_N=INV1(Q) ;
$dlrtp_{1,2,4} input(D,GATE,RESET_B) output(Q) DR=AND2(D,RESET_B) Q=LATCH(DR,GATE) ;
$dlrtn_{1,2,4} input(D,GATE_N,RESET_B) output(Q) GN=INV1(GATE_N) DR=AND2(D,RESET_B) Q=LATCH(DR,GN) ;
$dlrbp_{1,2} input(D,GATE,RESET_B) output(Q,Q_N) DR=AND2(D,RESET_B) Q=LATCH(DR,GATE) Q_N=INV1(Q) ;
$dlrbn_{1,2} input(D,GATE_N,RESET_B) output(Q,Q_N) GN=INV1(GATE_N) DR=AND2(D,RESET_B) Q=LATCH(DR,GN) Q_N=INV1(Q) ;
$dlclkp_{1,2,4} input(CLK,GATE) output(GCLK) GCLK=AND2(CLK,GATE) ;
$sdlclkp_{1,2,4} input(CLK,GATE,SCE) output(GCLK) G=OR2(GATE,SCE) GCLK=AND2(CLK,G) ;
$ebufn_{1,2,4,8} input(A,TE_B) output(Z) TE=INV1(TE_B) Z=AND2(A,TE) ;
$einvn_{0,1,2,4,8} input(A,TE_B) output(Z) TE=INV1(TE_B) ZI=INV1(A) Z=AND2(ZI,TE) ;
$einvp_{1,2,4,8} input(A,TE) output(Z) ZI=INV1(A) Z=AND2(ZI,TE) ;
$lpflow_inputiso0n_1 input(A,SLEEP_B) output(X) X=AND2(A,SLEEP_B) ;
$lpflow_inputiso0p_1 input(A,SLEEP) output(X) SB=INV1(SLEEP) X=AND2(A,SB) ;
$lpflow_inputiso1n_1 input(A,SLEEP_B) output(X) X=AND2(A,SLEEP_B) ;
$lpflow_inputiso1p_1 input(A,SLEEP) output(X) SB=INV1(SLEEP) X=AND2(A,SB) ;
$lpflow_isobufsrc_{1,2,4,8,16} input(A,SLEEP) output(X) AI=INV1(A) X=OR2(AI,SLEEP) ;
$lpflow_isobufsrckapwr_16 input(A,SLEEP) output(X) AI=INV1(A) X=OR2(AI,SLEEP) ;
$lpflow_inputisolatch_1 input(D,SLEEP_B) output(Q) Q=LATCH(D,SLEEP_B) ;
""".replace('$','sky130_fd_sc_hd__'))
"""SkyWater 130nm High Density Digital Standard Cells (skywater-pdk-libs-sky130_fd_sc_hd).
"""
techlib_by_name = {k: v for k, v in globals().items() if isinstance(v, TechLib)}

218
src/kyupy/vcd.py

@ -0,0 +1,218 @@
"""A simple parser for Verilog Change Dump (VCD) files.
This parser loads the changes into a ndarray of logic values.
Axis 0 is the time step, axis 1 is the variable data.
All variable values are flattened. Offsets are stored in each variable metadata.
"""
from collections import namedtuple
from dataclasses import dataclass, field
from lark import Lark, Transformer
import numpy as np
from . import readtext, logic
@dataclass
class Var:
type: str
width: int
idcode: str
reference: str
scope: 'Scope'
locs: list[int] = field(default_factory=list)
class Scope:
def __init__(self, parent, name='', type='module') -> None:
self.name = name
self.type = type
self.parent_scope = parent
self.sub_scopes : list[Scope] = []
self.vars : list = []
@property
def path(self) -> str:
parts = []
s = self
while s is not None and s.name:
parts.append(s.name)
s = s.parent_scope
return '/'.join(reversed(parts))
def __repr__(self) -> str:
return self.path
class VcdHeader:
def __init__(self) -> None:
self.comment: str = ''
self.date: str = ''
self.timescale: str = ''
self.version: str = ''
self.root_scope = Scope(None)
self.current_scope = self.root_scope
def scope(self, name, type):
new_scope = Scope(self.current_scope, name, type)
self.current_scope.sub_scopes.append(new_scope)
self.current_scope = new_scope
def upscope(self):
assert self.current_scope.parent_scope is not None, "root scope has no parent"
self.current_scope = self.current_scope.parent_scope
def add_var(self, type: str, width: int, idcode: str, reference: str):
self.current_scope.vars.append(Var(type, width, idcode, reference, self.current_scope))
def __repr__(self):
lines = [
'VcdHeader(',
f' comment = {self.comment!r}',
f' date = {self.date!r}',
f' timescale = {self.timescale!r}',
f' version = {self.version!r}',
' scopes:',
]
def fmt_scope(scope, indent=4):
lines.append(f'{" " * (indent // 2)}{scope.type}:{scope.name} ({len(scope.vars)} vars)')
for sub in scope.sub_scopes:
fmt_scope(sub, indent + 2)
for sub in self.root_scope.sub_scopes:
fmt_scope(sub)
lines.append(')')
return '\n'.join(lines)
class VcdVarMap:
def __init__(self, header: VcdHeader, var_locs = lambda _: []) -> None:
self.var_list: list[Var] = []
def collect(scope):
for v in scope.vars:
v.locs = var_locs(v)
if v.locs is not None and len(v.locs) > 0:
assert len(v.locs) == v.width, f'{v.reference}: len(locs)={len(v.locs)} != width={v.width}'
self.var_list.append(v)
for sub in scope.sub_scopes:
collect(sub)
collect(header.root_scope)
self.total_width = max((max(v.locs) for v in self.var_list), default=-1) + 1
self.idcode2var = {var.idcode: var for var in self.var_list}
class VcdData:
def __init__(self, var_map: VcdVarMap, steps, data):
self.var_map = var_map
self.steps = steps
self.data = data
def __repr__(self) -> str:
return f'{{variables: {len(self.var_map.var_list)}, bits: {self.data.shape[0]}, steps: {self.data.shape[1]}}}'
class VcdHeaderTransformer(Transformer):
def __init__(self):
super().__init__()
self.header = VcdHeader()
def comment(self, args): self.header.comment = args[0].value.strip()
def date(self, args): self.header.date = args[0].value.strip()
def timescale(self, args): self.header.timescale = args[0].value.strip()
def version(self, args): self.header.version = args[0].value.strip()
def scope(self, args):
type, name = args
self.header.scope(name.value, type.value)
def upscope(self, args):
self.header.upscope()
def var(self, args):
type, width, idcode, reference = args
reference = reference.value.strip()
width = int(width)
self.header.add_var(type.value, width, idcode.value, reference)
def start(self, args):
return self.header
#return tuple(args) if len(args) > 1 else (args[0], None)
GRAMMAR = r"""
start: (_declaration_command)*
_declaration_command: ( comment | date | scope | timescale | upscope | var | version ) "$end"
comment: "$comment" TEXT?
date: "$date" TEXT
scope: "$scope" /[^\s]+/ /[^\s]+/
timescale: "$timescale" TEXT
upscope: "$upscope"
var: "$var" /[^\s]+/ /[^\s]+/ /[^\s]+/ TEXT
version: "$version" TEXT
TEXT: /(?:(?!\$end)[\s\S])+/
%ignore ( /\r?\n/ )+
%ignore /[\t \f]+/
"""
def load(file, var_locs = lambda _: [], step_filter = lambda *_: True, s_len = None) -> VcdData:
"""Parses the contents of ``file`` as Verilog Change Dump (VCD).
:param file: A file name or a file handle. Files with `.gz`-suffix are decompressed on-the-fly.
:param var_locs: A callback ``(var) -> list[int]`` mapping each variable to ndarray column indices. Empty list drops the variable.
:param step_filter: A callback ``(time, values, var_map) -> bool`` to select which timesteps to include.
:param s_len: length of the first axis of returned pattern data.
:return: A VcdData object with metadata and an ndarray with all values.
"""
vcd = readtext(file)
header_size = vcd.find('$enddefinitions')
assert header_size > 0, "invalid VCD file: end of header not found"
vcd_header_str = vcd[:header_size]
vcd_header : VcdHeader = Lark(GRAMMAR, parser="lalr", lexer='contextual', transformer=VcdHeaderTransformer()).parse(vcd_header_str) # type: ignore
vcd_data = vcd[header_size:].splitlines()
var_map = VcdVarMap(vcd_header, var_locs)
width = s_len if s_len is not None else var_map.total_width
chunk_size = 10240
chunks = []
chunk = np.full((chunk_size, width), logic.UNASSIGNED, dtype=np.uint8)
_val_map = {'0': logic.ZERO, '1': logic.ONE,
'x': logic.UNKNOWN, 'X': logic.UNKNOWN,
'z': logic.UNASSIGNED, 'Z': logic.UNASSIGNED}
_pad_char = {'0': '0', '1': '0', 'x': 'x', 'X': 'x', 'z': 'z', 'Z': 'z'}
current_time = None
steps = []
step_idx = 0
for line in vcd_data:
if not line or line[0] == '$':
continue
if line[0] == '#':
if step_filter(current_time, chunk[step_idx], var_map):
step_idx += 1
steps.append(current_time)
if step_idx >= chunk_size:
chunks.append(chunk)
chunk = np.empty((chunk_size, width), dtype=np.uint8)
chunk[0] = chunks[-1][-1]
step_idx = 0
else:
chunk[step_idx] = chunk[step_idx - 1]
current_time = int(line[1:])
continue
if line[0] in ('b', 'B'):
value_str, idcode = line[1:].split(None, 1)
else:
value_str, idcode = line[0], line[1:]
var = var_map.idcode2var.get(idcode)
if var is None:
continue
if len(value_str) < var.width:
value_str = _pad_char.get(value_str[0], '0') * (var.width - len(value_str)) + value_str
for i, c in enumerate(value_str):
if var.locs[i] >= 0:
chunk[step_idx, var.locs[i]] = _val_map.get(c, logic.UNKNOWN)
chunks.append(chunk[:step_idx])
data = np.concatenate(chunks, axis=0).T
return VcdData(var_map, steps, data)

23
src/kyupy/verilog.py

@ -10,7 +10,7 @@ from lark import Lark, Transformer, Tree
from . import log, readtext from . import log, readtext
from .circuit import Circuit, Node, Line from .circuit import Circuit, Node, Line
from .techlib import NANGATE from .techlib import KYUPY
Instantiation = namedtuple('Instantiation', ['type', 'name', 'pins']) Instantiation = namedtuple('Instantiation', ['type', 'name', 'pins'])
@ -35,7 +35,7 @@ class SignalDeclaration:
class VerilogTransformer(Transformer): class VerilogTransformer(Transformer):
def __init__(self, branchforks=False, tlib=NANGATE): def __init__(self, branchforks=False, tlib=KYUPY):
super().__init__() super().__init__()
self.branchforks = branchforks self.branchforks = branchforks
self.tlib = tlib self.tlib = tlib
@ -52,6 +52,7 @@ class VerilogTransformer(Transformer):
@staticmethod @staticmethod
def instantiation(args): def instantiation(args):
pinmap = {} pinmap = {}
if args[2] is not None:
for idx, pin in enumerate(args[2:]): for idx, pin in enumerate(args[2:]):
p = pin.children[0] p = pin.children[0]
if isinstance(p, tuple): # named pin if isinstance(p, tuple): # named pin
@ -61,6 +62,10 @@ class VerilogTransformer(Transformer):
pinmap[idx] = p pinmap[idx] = p
return Instantiation(args[0], args[1], pinmap) 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): def range(self, args):
left = int(args[0].value) left = int(args[0].value)
right = int(args[1].value) if len(args) > 1 else left right = int(args[1].value) if len(args) > 1 else left
@ -96,8 +101,7 @@ class VerilogTransformer(Transformer):
sel = args[0] sel = args[0]
ctrue = args[1] ctrue = args[1]
cfalse = args[2] 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] return args[1]
def declaration(self, kind, args): def declaration(self, kind, args):
@ -111,6 +115,7 @@ class VerilogTransformer(Transformer):
def output(self, args): return self.declaration("output", args) def output(self, args): return self.declaration("output", args)
def inout(self, args): return self.declaration("input", args) # just treat as input def inout(self, args): return self.declaration("input", args) # just treat as input
def wire(self, args): return self.declaration("wire", args) 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): def module(self, args):
c = Circuit(args[0]) c = Circuit(args[0])
@ -236,14 +241,16 @@ GRAMMAR = r"""
start: (module)* start: (module)*
module: "module" name parameters ";" (_statement)* "endmodule" module: "module" name parameters ";" (_statement)* "endmodule"
parameters: "(" [ _namelist ] ")" 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 ";" input: "input" range? _namelist ";"
output: "output" range? _namelist ";" output: "output" range? _namelist ";"
inout: "inout" range? _namelist ";" inout: "inout" range? _namelist ";"
tri: "tri" range? _namelist ";" tri: "tri" range? _namelist ";"
wire: "wire" range? _namelist ";" wire: "wire" range? _namelist ";"
reg: "reg" range? _namelist ";"
assign: "assign" sigsel "=" sigsel ";" assign: "assign" sigsel "=" sigsel ";"
instantiation: name name "(" [ pin ( "," pin )* ] ")" ";" instantiation: name name "(" [ pin ( "," pin )* ] ")" ";"
simple_dff: "always" "@" "(" "posedge" name ")" sigsel "<=" sigsel ";"
pin: namedpin | sigsel pin: namedpin | sigsel
namedpin: "." name "(" sigsel? ")" namedpin: "." name "(" sigsel? ")"
range: "[" /[0-9]+/ (":" /[0-9]+/)? "]" range: "[" /[0-9]+/ (":" /[0-9]+/)? "]"
@ -259,7 +266,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. """Parses the given ``text`` as Verilog code.
:param text: A string with Verilog code. :param text: A string with Verilog code.
@ -270,10 +277,10 @@ def parse(text, tlib=NANGATE, branchforks=False):
(see :py:func:`~kyupy.sdf.DelayFile.interconnects()`). (see :py:func:`~kyupy.sdf.DelayFile.interconnects()`).
:return: A :py:class:`~kyupy.circuit.Circuit` object. :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. """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. :param file: A file name or a file handle. Files with `.gz`-suffix are decompressed on-the-fly.

2
src/kyupy/wave_sim.py

@ -434,7 +434,7 @@ class WaveSimCuda(WaveSim):
grid_dim = self._grid_dim(self.sims, self.s_len) grid_dim = self._grid_dim(self.sims, self.s_len)
wave_assign_gpu[grid_dim, self._block_dim](self.c, self.s, self.c_locs, self.ppi_offset) wave_assign_gpu[grid_dim, self._block_dim](self.c, self.s, self.c_locs, self.ppi_offset)
def _grid_dim(self, x, y): return cdiv(x, self._block_dim[0]), cdiv(y, self._block_dim[1]) def _grid_dim(self, x, y): return cdiv(int(x), self._block_dim[0]), cdiv(int(y), self._block_dim[1])
def c_prop(self, sims=None, seed=1, op_from=0, op_to=None, delta=0): def c_prop(self, sims=None, seed=1, op_from=0, op_to=None, delta=0):
sims = min(sims or self.sims, self.sims) sims = min(sims or self.sims, self.sims)

2
tests/Makefile

@ -0,0 +1,2 @@
all:
abc -F map_to_minimal.abc

91
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

53
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

34
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

8
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

5
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

29
tests/test_atalanta.py

@ -0,0 +1,29 @@
import numpy as np
from kyupy import atalanta, logic
def test_parse():
text = '\n'.join([
'* Test pattern file',
'1: 0011',
'2: 1100',
'1010',
])
tf = atalanta.parse(text)
# second-to-last axis = inputs, last axis = patterns
assert tf.patterns.shape == (4, 3)
assert tf.patterns.dtype == np.uint8
assert set(np.unique(tf.patterns)) <= {logic.ZERO, logic.ONE}
# patterns are stored along the last axis
assert list(tf.patterns[:, 0]) == [logic.ZERO, logic.ZERO, logic.ONE, logic.ONE]
assert list(tf.patterns[:, 1]) == [logic.ONE, logic.ONE, logic.ZERO, logic.ZERO]
assert list(tf.patterns[:, 2]) == [logic.ONE, logic.ZERO, logic.ONE, logic.ZERO]
def test_empty():
tf = atalanta.parse('* only comments\n* nothing else\n')
assert tf.patterns.shape == (0, 0)
assert tf.patterns.dtype == np.uint8

15
tests/test_circuit.py

@ -2,7 +2,7 @@ import pickle
from kyupy.circuit import GrowingList, Circuit, Node, Line from kyupy.circuit import GrowingList, Circuit, Node, Line
from kyupy import verilog, bench from kyupy import verilog, bench
from kyupy.techlib import SAED32 from kyupy.techlib import KYUPY, SAED32
def test_growing_list(): def test_growing_list():
gl = GrowingList() gl = GrowingList()
@ -131,7 +131,14 @@ def test_pickle(mydir):
cs = pickle.dumps(c) cs = pickle.dumps(c)
assert cs is not None assert cs is not None
c2 = pickle.loads(cs) 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(): def test_substitute():
@ -148,7 +155,7 @@ def test_substitute():
def test_resolve(mydir): def test_resolve(mydir):
c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32) c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32)
s_names = [n.name for n in c.s_nodes] s_names = [n.name for n in c.s_nodes(SAED32)]
c.resolve_tlib_cells(SAED32) c.resolve_tlib_cells(SAED32)
s_names_prim = [n.name for n in c.s_nodes] s_names_prim = [n.name for n in c.s_nodes(KYUPY)]
assert s_names == s_names_prim, 'resolve_tlib_cells does not preserve names or order of s_nodes' assert s_names == s_names_prim, 'resolve_tlib_cells does not preserve names or order of s_nodes'

222
tests/test_logic_sim.py

@ -1,14 +1,129 @@
import numpy as np import numpy as np
from kyupy.logic_sim import LogicSim, LogicSim6V from kyupy.logic_sim import LogicSim, LogicSim2V, LogicSim4V, LogicSim6V
from kyupy import bench, logic, sim from kyupy import bench, logic, sim, verilog
from kyupy.logic import mvarray, bparray, bp_to_mv, mv_to_bp from kyupy.logic import mv_str, mvarray, bparray, bp_to_mv, mv_to_bp
from kyupy.techlib import SAED90, KYUPY
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 test_used_output():
c = bench.parse('input(i0) output(o0) output(o1) o0=BUF1(i0) o1=BUF(o0)')
sim = LogicSim2V(c)
expect = mvarray('000', '111')
actual = sim.simulate(expect.copy())
np.testing.assert_array_equal(
expect[c.io_locs('o0')],
actual[c.io_locs('o0')])
np.testing.assert_array_equal(
expect[c.io_locs('o1')],
actual[c.io_locs('o1')])
def assert_equal_shape_and_contents(actual, desired): def assert_equal_shape_and_contents(actual, desired):
desired = np.array(desired, dtype=np.uint8) desired = np.array(desired, dtype=np.uint8)
assert actual.shape == desired.shape assert actual.shape == desired.shape
np.testing.assert_allclose(actual, desired) 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(KYUPY)[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(): def test_2v():
c = bench.parse(f''' c = bench.parse(f'''
input(i3, i2, i1, i0) input(i3, i2, i1, i0)
@ -47,14 +162,12 @@ def test_2v():
o31=OAI211(i0,i1,i2,i3) o31=OAI211(i0,i1,i2,i3)
o32=MUX21(i0,i1,i2) o32=MUX21(i0,i1,i2)
''') ''')
s = LogicSim(c, 16, m=2) s = LogicSim2V(c, 16)
bpa = logic.bparray([f'{i:04b}'+('-'*(s.s_len-4)) for i in range(16)]) s.s_assign[...] = logic.mvarray([f'{i:04b}'+('-'*(s.s_len-4)) for i in range(16)])
s.s[0] = bpa
s.s_to_c() s.s_to_c()
s.c_prop() s.c_prop()
s.c_to_s() s.c_to_s()
mva = logic.bp_to_mv(s.s[1]) for res, exp in zip(logic.packbits(s.s_result[4:], dtype=np.uint32), [
for res, exp in zip(logic.packbits(mva[4:], dtype=np.uint32), [
sim.BUF1, sim.INV1, sim.BUF1, sim.INV1,
sim.AND2, sim.AND3, sim.AND4, sim.AND2, sim.AND3, sim.AND4,
sim.NAND2, sim.NAND3, sim.NAND4, sim.NAND2, sim.NAND3, sim.NAND4,
@ -75,24 +188,56 @@ def test_2v():
def test_4v(): def test_4v():
c = bench.parse('input(x, y) output(a, o, n) a=and(x,y) o=or(x,y) n=not(x)') 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 = LogicSim4V(c, 16)
assert s.s_len == 5 assert s.s_len == 5
bpa = bparray( s.s_assign[...] = mvarray(
'00---', '01---', '0----', '0X---', '00---', '01---', '0----', '0X---',
'10---', '11---', '1----', '1X---', '10---', '11---', '1----', '1X---',
'-0---', '-1---', '-----', '-X---', '-0---', '-1---', '-----', '-X---',
'X0---', 'X1---', 'X----', 'XX---') 'X0---', 'X1---', 'X----', 'XX---')
s.s[0] = bpa
s.s_to_c() s.s_to_c()
s.c_prop() s.c_prop()
s.c_to_s() s.c_to_s()
mva = bp_to_mv(s.s[1]) assert_equal_shape_and_contents(s.s_result, mvarray(
assert_equal_shape_and_contents(mva, mvarray(
'--001', '--011', '--0X1', '--0X1', '--001', '--011', '--0X1', '--0X1',
'--010', '--110', '--X10', '--X10', '--010', '--110', '--X10', '--X10',
'--0XX', '--X1X', '--XXX', '--XXX', '--0XX', '--X1X', '--XXX', '--XXX',
'--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 = LogicSim4V(c, 16)
assert s.s_len == 3
s.s_assign[...] = mvarray(
'00-', '01-', '0--', '0X-',
'10-', '11-', '1--', '1X-',
'-0-', '-1-', '---', '-X-',
'X0-', 'X1-', 'X--', 'XX-')
s.s_to_c()
s.c_prop()
s.c_to_s()
assert_equal_shape_and_contents(s.s_result, 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.index, fault_model=1)
s.c_to_s()
assert_equal_shape_and_contents(s.s_result, 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.index, fault_model=0)
s.c_to_s()
assert_equal_shape_and_contents(s.s_result, mvarray(
'--0', '--0', '--0', '--0',
'--0', '--0', '--0', '--0',
'--0', '--0', '--0', '--0',
'--0', '--0', '--0', '--0'))
def test_6v(): 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)') 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)')
@ -114,11 +259,11 @@ def test_6v():
resp = s.s[1].copy() resp = s.s[1].copy()
exp_resp = np.copy(mva) exp_resp = np.copy(mva)
exp_resp[:2] = logic.ZERO exp_resp[:2] = logic.UNASSIGNED
np.testing.assert_allclose(resp, exp_resp) np.testing.assert_allclose(resp, exp_resp)
def test_8v(): def xtest_8v():
c = bench.parse('input(x, y) output(a, o, n, xo) a=and(x,y) o=or(x,y) n=not(x) xo=xor(x,y)') c = bench.parse('input(x, y) output(a, o, n, xo) a=and(x,y) o=or(x,y) n=not(x) xo=xor(x,y)')
s = LogicSim(c, 64, m=8) s = LogicSim(c, 64, m=8)
assert s.s_len == 6 assert s.s_len == 6
@ -145,8 +290,8 @@ def test_8v():
np.testing.assert_allclose(resp, exp_resp) np.testing.assert_allclose(resp, exp_resp)
def test_loop(): def xtest_loop():
c = bench.parse('q=dff(d) d=not(q)') c = bench.parse('q=DFF(d) d=NOT(q)')
s = LogicSim(c, 4, m=8) s = LogicSim(c, 4, m=8)
assert s.s_len == 1 assert s.s_len == 1
mva = mvarray([['0'], ['1'], ['R'], ['F']]) mva = mvarray([['0'], ['1'], ['R'], ['F']])
@ -172,8 +317,8 @@ def test_loop():
# assert resp[3] == 'F' # assert resp[3] == 'F'
def test_latch(): def xtest_latch():
c = bench.parse('input(d, t) output(q) q=latch(d, t)') c = bench.parse('input(d, t) output(q) q=LATCH(d, t)')
s = LogicSim(c, 8, m=8) s = LogicSim(c, 8, m=8)
assert s.s_len == 4 assert s.s_len == 4
mva = mvarray('00-0', '00-1', '01-0', '01-1', '10-0', '10-1', '11-0', '11-1') mva = mvarray('00-0', '00-1', '01-0', '01-1', '10-0', '10-1', '11-0', '11-1')
@ -186,19 +331,6 @@ def test_latch():
# assert resp[i] == exp[i] # assert resp[i] == exp[i]
def test_b01(mydir):
c = bench.load(mydir / 'b01.bench')
# 8-valued
s = LogicSim(c, 8, m=8)
mva = np.zeros((s.s_len, 8), dtype=np.uint8)
s.s[0] = mv_to_bp(mva)
s.s_to_c()
s.c_prop()
s.c_to_s()
bp_to_mv(s.s[1])
def sim_and_compare(c, test_resp, m=8): def sim_and_compare(c, test_resp, m=8):
tests, resp = test_resp tests, resp = test_resp
lsim = LogicSim(c, m=m, sims=tests.shape[1]) lsim = LogicSim(c, m=m, sims=tests.shape[1])
@ -215,14 +347,14 @@ def sim_and_compare(c, test_resp, m=8):
print(f'mismatch pattern:{pat} ppio:{idx} exp:{logic.mv_str(resp[idx,pat])} act:{logic.mv_str(resp_sim[idx,pat])}') print(f'mismatch pattern:{pat} ppio:{idx} exp:{logic.mv_str(resp[idx,pat])} act:{logic.mv_str(resp_sim[idx,pat])}')
assert len(idxs) == 0 assert len(idxs) == 0
def sim_and_compare_6v(c, test_resp): def sim_and_compare_cls(c, test_resp, SimCls):
tests, resp = test_resp tests, resp = test_resp
lsim = LogicSim6V(c, sims=tests.shape[1]) lsim = SimCls(c, sims=tests.shape[1])
lsim.s[0] = tests lsim.s_assign[...] = tests
lsim.s_to_c() lsim.s_to_c()
lsim.c_prop() lsim.c_prop()
lsim.c_to_s() lsim.c_to_s()
resp_sim = lsim.s[1] resp_sim = lsim.s_result
idxs, pats = np.nonzero(((resp == logic.ONE) & (resp_sim != logic.ONE)) | ((resp == logic.ZERO) & (resp_sim != logic.ZERO))) idxs, pats = np.nonzero(((resp == logic.ONE) & (resp_sim != logic.ONE)) | ((resp == logic.ZERO) & (resp_sim != logic.ZERO)))
for i, (idx, pat) in enumerate(zip(idxs, pats)): for i, (idx, pat) in enumerate(zip(idxs, pats)):
if i >= 10: if i >= 10:
@ -233,28 +365,32 @@ def sim_and_compare_6v(c, test_resp):
def test_b15_2ig_sa_2v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp): def test_b15_2ig_sa_2v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=2) sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim2V)
def test_b15_2ig_sa_4v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp): def test_b15_2ig_sa_4v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=4) sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim4V)
def test_b15_2ig_sa_6v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp): def test_b15_2ig_sa_6v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
sim_and_compare_6v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp) sim_and_compare_cls(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, LogicSim6V)
def test_b15_2ig_sa_8v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp): def xtest_b15_2ig_sa_8v(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp):
sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=8) sim_and_compare(b15_2ig_circuit_resolved, b15_2ig_sa_nf_test_resp, m=8)
def test_b15_4ig_sa_2v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp): def test_b15_4ig_sa_2v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=2) sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim2V)
def test_b15_4ig_sa_4v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp): def test_b15_4ig_sa_4v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=4) sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim4V)
def test_b15_4ig_sa_6v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
sim_and_compare_cls(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, LogicSim6V)
def test_b15_4ig_sa_8v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp): def xtest_b15_4ig_sa_8v(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp):
sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=8) sim_and_compare(b15_4ig_circuit_resolved, b15_4ig_sa_rf_test_resp, m=8)

2
tests/test_stil.py

@ -14,7 +14,7 @@ def test_b15(mydir):
assert len(resp) > 0 assert len(resp) > 0
s2 = stil.load(mydir / 'b15_2ig.tf_nf.stil.gz') s2 = stil.load(mydir / 'b15_2ig.tf_nf.stil.gz')
tests = s2.tests_loc(b15) tests = s2.tests_loc(b15, SAED32)
resp = s2.responses(b15) resp = s2.responses(b15)
assert len(tests) > 0 assert len(tests) > 0
assert len(resp) > 0 assert len(resp) > 0

8
tests/test_verilog.py

@ -9,7 +9,7 @@ def test_b01(mydir):
assert len(c.nodes) == 139 assert len(c.nodes) == 139
assert len(c.lines) == 203 assert len(c.lines) == 203
stats = c.stats stats = c.stats(SAED90)
assert stats['input'] == 6 assert stats['input'] == 6
assert stats['output'] == 3 assert stats['output'] == 3
assert stats['__seq__'] == 5 assert stats['__seq__'] == 5
@ -19,7 +19,7 @@ def test_b15(mydir):
c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32) c = verilog.load(mydir / 'b15_4ig.v.gz', tlib=SAED32)
assert len(c.nodes) == 12067 assert len(c.nodes) == 12067
assert len(c.lines) == 20731 assert len(c.lines) == 20731
stats = c.stats stats = c.stats(SAED32)
assert stats['input'] == 40 assert stats['input'] == 40
assert stats['output'] == 71 assert stats['output'] == 71
assert stats['__seq__'] == 417 assert stats['__seq__'] == 417
@ -29,7 +29,7 @@ def test_gates(mydir):
c = verilog.load(mydir / 'gates.v', tlib=NANGATE45) c = verilog.load(mydir / 'gates.v', tlib=NANGATE45)
assert len(c.nodes) == 18 assert len(c.nodes) == 18
assert len(c.lines) == 21 assert len(c.lines) == 21
stats = c.stats stats = c.stats(NANGATE45)
assert stats['input'] == 3 assert stats['input'] == 3
assert stats['output'] == 4 assert stats['output'] == 4
assert stats['__seq__'] == 0 assert stats['__seq__'] == 0
@ -39,7 +39,7 @@ def test_halton2(mydir):
c = verilog.load(mydir / 'rng_haltonBase2.synth_yosys.v', tlib=SAED90) c = verilog.load(mydir / 'rng_haltonBase2.synth_yosys.v', tlib=SAED90)
assert len(c.nodes) == 146 assert len(c.nodes) == 146
assert len(c.lines) == 210 assert len(c.lines) == 210
stats = c.stats stats = c.stats(SAED90)
assert stats['input'] == 2 assert stats['input'] == 2
assert stats['output'] == 12 assert stats['output'] == 12
assert stats['__seq__'] == 12 assert stats['__seq__'] == 12

17
tests/test_wave_sim.py

@ -1,7 +1,7 @@
import numpy as np import numpy as np
from kyupy.wave_sim import WaveSim, WaveSimCuda, wave_eval_cpu, TMIN, TMAX from kyupy.wave_sim import WaveSim, WaveSimCuda, wave_eval_cpu, TMIN, TMAX
from kyupy.logic_sim import LogicSim from kyupy.logic_sim import LogicSim6V
from kyupy import logic, bench, sim from kyupy import logic, bench, sim
from kyupy.logic import mvarray from kyupy.logic import mvarray
@ -25,10 +25,11 @@ def test_xnor2_delays():
delays[0, 1, 1, 1] = 0.036 # B fall -> Z fall delays[0, 1, 1, 1] = 0.036 # B fall -> Z fall
simctl_int = np.asarray([0], dtype=np.int32) simctl_int = np.asarray([0], dtype=np.int32)
simctl_float = np.asarray([1], dtype=np.float32)
def wave_assert(inputs, output): def wave_assert(inputs, output):
for i, a in zip(inputs, c.reshape(-1,16)): a[:len(i)] = i 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) 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 wave_assert([[TMIN,TMAX],[TMIN,TMAX]], [TMIN,TMAX]) # XNOR(1,1) => 1
@ -40,11 +41,10 @@ def test_xnor2_delays():
def test_nand_delays(): def test_nand_delays():
op = (sim.NAND4, 4, 0, 1, 2, 3, -1, 0, 0) 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 = np.full((5*16, 1), TMAX, dtype=np.float32) # 5 waveforms of capacity 16
c_locs = np.zeros((5,), dtype='int') c_locs = np.zeros((5,), dtype='int')
c_caps = 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 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 delays[0, 3, :, 1] = 0.8
simctl_int = np.asarray([0], dtype=np.int32) simctl_int = np.asarray([0], dtype=np.int32)
simctl_float = np.asarray([1], dtype=np.float32)
def wave_assert(inputs, output): def wave_assert(inputs, output):
for i, a in zip(inputs, c.reshape(-1,16)): a[:len(i)] = i 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) 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 wave_assert([[TMAX,TMAX],[TMAX,TMAX],[TMIN,TMAX],[TMIN,TMAX]], [TMIN,TMAX]) # NAND(0,0,1,1) => 1
@ -174,12 +175,12 @@ def compare_to_logic_sim(wsim: WaveSim):
resp |= ((resp ^ (resp >> 1)) & 1) << 2 # transitions resp |= ((resp ^ (resp >> 1)) & 1) << 2 # transitions
resp[wsim.pi_s_locs] = logic.UNASSIGNED resp[wsim.pi_s_locs] = logic.UNASSIGNED
lsim = LogicSim(wsim.circuit, tests.shape[-1]) lsim = LogicSim6V(wsim.circuit, tests.shape[-1])
lsim.s[0] = logic.mv_to_bp(tests) lsim.s_assign[...] = tests
lsim.s_to_c() lsim.s_to_c()
lsim.c_prop() lsim.c_prop()
lsim.c_to_s() lsim.c_to_s()
exp = logic.bp_to_mv(lsim.s[1])[:,:tests.shape[-1]] exp = lsim.s_result[:,:tests.shape[-1]]
resp[resp == logic.PPULSE] = logic.ZERO resp[resp == logic.PPULSE] = logic.ZERO
resp[resp == logic.NPULSE] = logic.ONE resp[resp == logic.NPULSE] = logic.ONE

Loading…
Cancel
Save