├── .buildkite └── pipeline.yml ├── .github └── workflows │ ├── deploy.yml │ ├── linux.yml │ └── macos.yml ├── .gitignore ├── .travis └── deploy.sh ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── codecov.yml ├── conftest.py ├── doc ├── actions.md ├── assert_immediate.md ├── property.md ├── pysv.md ├── tester.md └── verilog_integration.ipynb ├── docs ├── action_generators.html ├── actions.html ├── array.html ├── circuit_utils.html ├── common.html ├── cosa_target.html ├── functional_tester.html ├── index.html ├── logging.html ├── magma_simulator_target.html ├── random.html ├── select_path.html ├── symbolic_tester.html ├── system_verilog_target.html ├── target.html ├── test_vector_generator.html ├── test_vectors.html ├── tester.html ├── tuple.html ├── util.html ├── utils.html ├── value.html ├── value_utils.html ├── vector_builder.html ├── verilator_target.html ├── verilator_utils.html ├── verilog_target.html ├── verilog_utils.html └── wrapper.html ├── examples ├── myinv.py ├── myinv.sp ├── sram_snm.py ├── sram_snm.sp ├── sv_tb │ └── sv_tb.py └── test_simple_alu.py ├── fault.sublime-project ├── fault ├── __init__.py ├── action_generators.py ├── actions.py ├── array.py ├── assert_immediate.py ├── assert_utils.py ├── circuit_utils.py ├── codegen.py ├── common.py ├── config.py ├── expression.py ├── fault_errors.py ├── file.py ├── functional_tester.py ├── infix.py ├── logging.py ├── magma_simulator_target.py ├── magma_utils.py ├── ms_types.py ├── netlister.py ├── pono_target.py ├── power_tester.py ├── property.py ├── pwl.py ├── pysv.py ├── random.py ├── ready_valid.py ├── result_parse.py ├── select_path.py ├── spice.py ├── spice_target.py ├── subprocess_run.py ├── sva.py ├── system_verilog_target.py ├── target.py ├── test_vector_generator.py ├── test_vectors.py ├── tester │ ├── __init__.py │ ├── base.py │ ├── control.py │ ├── interactive_tester.py │ ├── sequence_tester.py │ ├── staged_tester.py │ ├── symbolic_tester.py │ ├── synchronous.py │ └── utils.py ├── tester_samples.py ├── user_cfg.py ├── util.py ├── utils.py ├── value.py ├── value_utils.py ├── vector_builder.py ├── verilator_target.py ├── verilator_utils.py ├── verilog_target.py ├── verilog_utils.py ├── verilogams.py ├── verilogams_target.py ├── wrapped_internal_port.py └── wrapper.py ├── gold ├── test_system_verilog_target_packed_arrays_False__test_packed_arrays_stimulate_bulk_tb.sv ├── test_system_verilog_target_packed_arrays_False__test_packed_arrays_stimulate_by_element_tb.sv ├── test_system_verilog_target_packed_arrays_True__test_packed_arrays_stimulate_bulk_tb.sv └── test_system_verilog_target_packed_arrays_True__test_packed_arrays_stimulate_by_element_tb.sv ├── setup.cfg ├── setup.py ├── test.pkl ├── tests ├── SB_DFF.v ├── __init__.py ├── build │ └── .gitignore ├── common.py ├── sb_dff_sim.v ├── simple_alu.v ├── spice │ ├── my_init_cond.sp │ ├── myblend.sp │ ├── mybuf.sp │ ├── mybus.sp │ ├── myinv.sp │ ├── mynand.sp │ ├── mynor.sp │ └── mysram.sp ├── test_action.py ├── test_assert_immediate.py ├── test_bidir.py ├── test_config.py ├── test_constrained_random.py ├── test_coverage.py ├── test_def_vlog.py ├── test_env_mod.py ├── test_error_handling.py ├── test_expressions.py ├── test_ext_tb.py ├── test_ext_vlog.py ├── test_fork.py ├── test_functional_tester.py ├── test_get_value_analog.py ├── test_get_value_digital.py ├── test_hi_z.py ├── test_inc_dir.py ├── test_include_verilog.py ├── test_init_cond.py ├── test_inv_tf.py ├── test_kratos_debug.py ├── test_logging.py ├── test_logic.py ├── test_magma_opts.py ├── test_magma_protocol.py ├── test_magma_simulator_target.py ├── test_no_coreir.py ├── test_param_vlog.py ├── test_power_domains.py ├── test_property.py ├── test_protocol.py ├── test_pwl_gen.py ├── test_pysv.py ├── test_ready_valid.py ├── test_real_val.py ├── test_select_model.py ├── test_setattr_interface.py ├── test_spice_bus.py ├── test_spice_port_order.py ├── test_system_verilog_target.py ├── test_test_vectors.py ├── test_tester │ ├── __init__.py │ ├── test_call.py │ ├── test_control.py │ ├── test_core.py │ ├── test_interactive.py │ ├── test_magma_usernamespace.py │ ├── test_sequence_tester.py │ ├── test_symbolic_tester.py │ ├── test_synchronous.py │ └── test_timing.py ├── test_top_module.py ├── test_value_utils.py ├── test_vams_wrap.py ├── test_vector_builder.py ├── test_verilator_target.py ├── test_verilog_target.py ├── test_while_loop.py └── verilog │ ├── bidir.v │ ├── clkdelay.sv │ ├── defadd.sv │ ├── error_task.sv │ ├── fatal_task.sv │ ├── hizmod.v │ ├── myblend.sv │ ├── mybuf.v │ ├── mybuf_inc_test.v │ ├── myinv.v │ ├── myinv_extra_module.v │ ├── mynand.v │ ├── mynor.v │ ├── mysram.v │ ├── mytb.sv │ ├── paramadd.sv │ ├── realadd.sv │ └── simple_alu_pd.sv └── tutorial ├── .gitignore ├── README.md ├── exercise_1.py ├── exercise_2.py ├── passthrough.py ├── reset_tester.py ├── test.py └── tff.py /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - command: | 3 | # tool setup 4 | source /cad/modules/tcl/init/bash 5 | module load base vcs xcelium/19.03.003 hspice spectre/18.10.314 verdi 6 | 7 | # create conda env 8 | wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh 9 | chmod +x Miniconda3-latest-Linux-x86_64.sh 10 | ./Miniconda3-latest-Linux-x86_64.sh -b -u -p $$PWD/miniconda 11 | export PATH=$$PWD/miniconda/bin:$$PATH 12 | conda install python==3.8 -y -q 13 | 14 | # install python dependencies for testing 15 | pip install "pytest<6" 16 | pip install coverage pytest-pycodestyle 17 | pip install --upgrade "mantle>=2.0.0" 18 | pip install vcdvcd decorator kratos importlib_resources 19 | pip install DeCiDa scipy numpy 20 | 21 | # install fault 22 | pip install -e . 23 | 24 | # install kratos runtime 25 | pip install kratos-runtime 26 | 27 | # use the latest cmake 28 | pip install cmake 29 | 30 | # run tests 31 | coverage run -m pytest --pycodestyle tests/ -v -r s 32 | 33 | # upload coverage results 34 | bash <(curl -s https://codecov.io/bash) 35 | label: "test" 36 | timeout_in_minutes: 60 37 | agents: 38 | fault2: "true" 39 | 40 | # - command: | 41 | # # set up environment 42 | # source /etc/environment 43 | # echo $$PATH 44 | # 45 | # # create conda env 46 | # wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh 47 | # chmod +x Miniconda3-latest-Linux-x86_64.sh 48 | # ./Miniconda3-latest-Linux-x86_64.sh -b -u -p $$PWD/miniconda 49 | # export PATH=$$PWD/miniconda/bin:$$PATH 50 | # conda install python==3.8 -y -q 51 | # 52 | # # install python dependencies for testing 53 | # pip install wheel 54 | # pip install "pytest<6" 55 | # pip install pytest-cov pytest-pycodestyle 56 | # pip install vcdvcd decorator kratos 57 | # pip install --upgrade "mantle>=2.0.0" 58 | # pip install DeCiDa scipy numpy 59 | # 60 | # # use the latest cmake 61 | # pip install cmake 62 | # 63 | # # install fault 64 | # pip install -e . 65 | # 66 | # # run tests 67 | # pytest --pycodestyle --cov-report=xml --cov=fault tests/ -v -r s 68 | # 69 | # # upload coverage results 70 | # bash <(curl -s https://codecov.io/bash) 71 | # 72 | # # deactivate virtual environment 73 | # deactivate 74 | # label: "fpga_verif" 75 | # timeout_in_minutes: 60 76 | # agents: 77 | # fpga_verif: "true" 78 | 79 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | if: "!contains(github.event.head_commit.message, 'skip ci')" 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Checkout submodules 17 | shell: bash 18 | run: | 19 | auth_header="$(git config --local --get http.https://github.com/.extraheader)" 20 | git submodule sync --recursive 21 | git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 22 | - uses: actions/setup-python@v2 23 | with: 24 | python-version: '3.8' 25 | - name: Install verilator 26 | shell: bash 27 | run: | 28 | sudo apt install -y verilator libgmp-dev libmpfr-dev libmpc-dev 29 | verilator --version 30 | - name: Install Python packages 31 | shell: bash -l {0} 32 | run: | 33 | pip install importlib_resources 34 | pip install "pytest<6" 35 | pip install pytest-cov pytest-pycodestyle 36 | pip install mantle>=2.0.0 # for tests.common 37 | pip install vcdvcd decorator kratos 38 | pip install . 39 | 40 | - name: Pytest 41 | shell: bash -l {0} 42 | run: | 43 | pytest --pycodestyle --cov-report=xml --cov=fault tests/ -v -r s 44 | - name: Coverage 45 | shell: bash -l {0} 46 | run: | 47 | bash <(curl -s https://codecov.io/bash) 48 | - name: Install deploy packages 49 | shell: bash -l {0} 50 | run: | 51 | pip install twine 52 | - name: Upload to PyPI 53 | shell: bash -l {0} 54 | run: | 55 | source .travis/deploy.sh 56 | env: 57 | PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} 58 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | if: "!contains(github.event.head_commit.message, 'skip ci')" 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-python@v2 14 | with: 15 | python-version: '3.8' 16 | - name: Install dependencies 17 | shell: bash 18 | run: | 19 | sudo apt install -y verilator libgmp-dev libmpfr-dev libmpc-dev iverilog 20 | verilator --version 21 | - name: Install Python packages 22 | shell: bash -l {0} 23 | run: | 24 | pip install importlib_resources 25 | pip install "pytest<6" 26 | pip install pytest-cov pytest-pycodestyle 27 | pip install mantle>=2.0.0 # for tests.common 28 | pip install vcdvcd decorator kratos 29 | pip install smt-switch pono 30 | pip install . 31 | 32 | - name: Pytest 33 | shell: bash -l {0} 34 | run: | 35 | export LD_LIBRARY_PATH=/usr/lib:$LD_LIBRARY_PATH 36 | pytest --pycodestyle --cov-report=xml --cov=fault tests/ -v -r s 37 | - name: Coverage 38 | shell: bash -l {0} 39 | run: | 40 | bash <(curl -s https://codecov.io/bash) 41 | 42 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: macos-latest 9 | if: "!contains(github.event.head_commit.message, 'skip ci')" 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Checkout submodules 14 | shell: bash 15 | run: | 16 | auth_header="$(git config --local --get http.https://github.com/.extraheader)" 17 | git submodule sync --recursive 18 | git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 19 | - name: Setup Env 20 | uses: conda-incubator/setup-miniconda@v2 21 | with: 22 | auto-update-conda: true 23 | python-version: 3.8 24 | - name: Install verilator 25 | shell: bash 26 | run: | 27 | brew update 28 | brew install verilator icarus-verilog 29 | verilator --version 30 | iverilog -V 31 | - name: Install Python packages 32 | shell: bash -l {0} 33 | run: | 34 | pip install importlib_resources 35 | pip install "pytest<6" 36 | pip install pytest-cov pytest-pycodestyle 37 | pip install mantle>=2.0.0 # for tests.common 38 | pip install vcdvcd decorator kratos 39 | pip install . 40 | - name: Pytest 41 | shell: bash -l {0} 42 | run: | 43 | pytest --pycodestyle --cov-report=xml --cov=fault tests/ -v -r s 44 | - name: Coverage 45 | shell: bash -l {0} 46 | run: | 47 | bash <(curl -s https://codecov.io/bash) 48 | 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .pytest_cache 3 | .cache 4 | .idea 5 | build 6 | dist 7 | fault.egg-info 8 | pytest.ini 9 | vnc_logs 10 | .coverage 11 | .magma 12 | coverage.xml 13 | parser.out 14 | parsetab.py 15 | .ast_tools 16 | *.sublime_workspace 17 | -------------------------------------------------------------------------------- /.travis/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo [distutils] > ~/.pypirc 4 | echo index-servers = >> ~/.pypirc 5 | echo " pypi" >> ~/.pypirc 6 | echo >> ~/.pypirc 7 | echo [pypi] >> ~/.pypirc 8 | echo repository=https://upload.pypi.org/legacy/ >> ~/.pypirc 9 | echo username=__token__ >> ~/.pypirc 10 | echo password=$PYPI_TOKEN >> ~/.pypirc 11 | 12 | python setup.py sdist build 13 | 14 | twine upload dist/* 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [2.0.10] 8 | ### Fixed 9 | - https://github.com/leonardt/fault/pull/107 fixes a bug in how SV integer 10 | literals are generated 11 | 12 | ## [2.0.10] 13 | ### Fixed 14 | - Fixes bug in tester.compile 15 | 16 | ## [2.0.9] 17 | ### Fixed 18 | - Fixes bug related to char promotion in verilator backend 19 | 20 | ## [2.0.8] 21 | ### Added 22 | - https://github.com/leonardt/fault/pull/96 adds support for 23 | `fault.config.set_test_dir`. Call with 'callee_file_dir' to have the 24 | `directory` parameter to `compile_and_run` relative to the calling file 25 | (default is relative to where Python is invoked) 26 | - Adds support for poking `fault.AnyValue` (X) using the `"system-verilog"` 27 | target 28 | - https://github.com/leonardt/fault/pull/102 Adds support for more options for 29 | `"ncsim"` simulator 30 | - Adds switch to suppress warnings. 31 | - Adds switch to dump VCD 32 | - Adds parameter to specify the number of cycles 33 | - https://github.com/leonardt/fault/pull/98 Adds support for file i/o for 34 | system verilog targets 35 | 36 | ### Fixed 37 | - https://github.com/leonardt/fault/pull/99 fixes a bug for equality checks 38 | - https://github.com/leonardt/fault/pull/100 fixes a bug for checking the 39 | status code of verilator commands 40 | - https://github.com/leonardt/fault/pull/101 fixes a bug for expect/poke of 41 | signed values 42 | 43 | ## [2.0.7] 44 | ### Added 45 | - Adds support for loop and file i/o actions 46 | - Added ability to skip verilator compile using `VerilatorTarget` 47 | 48 | ### Changes 49 | - Changed print action interface to match standard `printf` interface (ala C) 50 | 51 | 52 | ## [2.0.4] 53 | ### Fixes 54 | - Fixes issue with handling wide signals (greater than 32 bits). 55 | 56 | ## [2.0.1] 57 | ### Fixes 58 | - Fixes for upstream changes to magma `Array` and `Bits` type constructor 59 | syntax. 60 | 61 | ## [2.0.0] 62 | ### Changes 63 | - Updates to using hwtypes and uses the new hwtypes syntax 64 | 65 | ## [1.0.8] 66 | ### Fixes 67 | - Fixes issue with tests that use setattr only for top interface ports. In this 68 | case, the top circuit header does not need to be included for debug signals. 69 | 70 | ## [1.0.7] 71 | ### Fixes 72 | - Fixes backwards compatability issues with verilator 73 | 74 | ## [1.0.6] 75 | ### Fixes 76 | - Fixes verilator version guard for top circuit prefix 77 | - Fixes support for poking coreir_arst register 78 | 79 | ## [1.0.5] 80 | ### Fixes 81 | - Fixes verilator version guard for including top circuit header 82 | 83 | ## [1.0.4] 84 | ### Added 85 | - Adds support for arrays and tuples in setattr interface 86 | 87 | ## [1.0.3] 88 | ### Fixed 89 | - Fixed bug in .sv file logic for VerilogTarget 90 | 91 | ## [1.0.2] 92 | ### Added 93 | - Added support for .sv files to VerilogTarget 94 | 95 | ## [1.0.1] 96 | ### Fixed 97 | - Fixed functional tester's use of self.circuit which was not updated for the 98 | new Tester setattr interface 99 | 100 | ## 1.0.0 101 | ### Added 102 | - Added preliminary support for peek and expect on internal signals and poke on 103 | internal registers 104 | 105 | [Unreleased]: https://github.com/leonardt/fault/compare/v1.0.8...HEAD 106 | [1.0.7]: https://github.com/leonardt/fault/compare/v1.0.7...v1.0.8 107 | [1.0.7]: https://github.com/leonardt/fault/compare/v1.0.6...v1.0.7 108 | [1.0.6]: https://github.com/leonardt/fault/compare/v1.0.5...v1.0.6 109 | [1.0.5]: https://github.com/leonardt/fault/compare/v1.0.4...v1.0.5 110 | [1.0.4]: https://github.com/leonardt/fault/compare/v1.0.3...v1.0.4 111 | [1.0.3]: https://github.com/leonardt/fault/compare/v1.0.2...v1.0.3 112 | [1.0.2]: https://github.com/leonardt/fault/compare/v1.0.1...v1.0.2 113 | [1.0.1]: https://github.com/leonardt/fault/compare/v1.0.0...v1.0.1 114 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 Leonard Truong 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # configuration related to pull request comments 2 | comment: no # do not comment PR with the result 3 | 4 | coverage: 5 | range: 50..90 # coverage lower than 50 is red, higher than 90 green, between color code 6 | 7 | status: 8 | project: # settings affecting project coverage 9 | default: 10 | target: auto # auto % coverage target 11 | threshold: 5% # allow for 5% reduction of coverage without failing 12 | 13 | # do not run coverage on patch nor changes 14 | patch: false 15 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import logging 3 | from magma.util import reset_global_context 4 | 5 | collect_ignore = ["src"] # pip folder that contains dependencies like magma 6 | 7 | 8 | @pytest.fixture(autouse=True) 9 | def magma_test(): 10 | reset_global_context() 11 | logging.getLogger().setLevel(logging.DEBUG) 12 | -------------------------------------------------------------------------------- /doc/assert_immediate.md: -------------------------------------------------------------------------------- 1 | # assert_immediate, assert_final, and assert_initial 2 | 3 | `fault` provides the ability to define immediate assertions using the 4 | `assert_immediate` function. These assertions are expected to hold true 5 | throughout the entire simulation. 6 | 7 | There are also the two variants: 8 | * `assert_final`: holds true at the end of simulation 9 | * `assert_initial`: holds true at the beginning of simulation 10 | 11 | Here is the interface (`assert_final` and `assert_initial` share the same interface): 12 | ```python 13 | def assert_immediate(cond, success_msg=None, failure_msg=None, severity="error", 14 | on=None, name=None): 15 | """ 16 | cond: m.Bit 17 | success_msg (optional): passed to $display on success 18 | failure_msg (optional): passed to else $ on failure (strings are 19 | wrapped in quotes, integers are passed without 20 | quotes (for $fatal)) 21 | severity (optional): "error", "fatal", or "warning" 22 | name (optional): Adds `{name}: ` prefix to assertion 23 | compile_guard (optional): a string or list of strings corresponding to 24 | macro variables used to guard the assertion with 25 | verilog `ifdef statements 26 | """ 27 | ``` 28 | 29 | Here is an example that will fail when run: 30 | ```python 31 | class Foo(m.Circuit): 32 | io = m.IO( 33 | I0=m.In(m.Bit), 34 | I1=m.In(m.Bit) 35 | ) 36 | f.assert_immediate(~(io.I0 & io.I1), failure_msg="Failed!") 37 | 38 | tester = f.Tester(Foo) 39 | tester.circuit.I0 = 1 40 | tester.circuit.I1 = 1 41 | tester.eval() 42 | tester.compile_and_run("verilator", magma_opts={"inline": True}, 43 | flags=['--assert']) 44 | ``` 45 | 46 | If we change the input values, it will pass: 47 | ```python 48 | tester = f.Tester(Foo) 49 | tester.circuit.I0 = 0 50 | tester.circuit.I1 = 1 51 | tester.eval() 52 | tester.compile_and_run("verilator", magma_opts={"inline": True}, 53 | flags=['--assert']) 54 | ``` 55 | 56 | ## Display Values on Failure 57 | The `assert_immediate` `failure_msg` parameter can accept a tuple of the form `("", *display_args)`. This is useful for displaying debug information upon an assertion failure. Here is an example: 58 | ```python 59 | class Foo(m.Circuit): 60 | io = m.IO( 61 | I0=m.In(m.Bit), 62 | I1=m.In(m.Bit) 63 | ) 64 | f.assert_immediate( 65 | io.I0 == io.I1, 66 | failure_msg=("io.I0 -> %x != %x <- io.I1", io.I0, io.I1) 67 | ) 68 | 69 | tester = f.Tester(Foo) 70 | tester.circuit.I0 = 1 71 | tester.circuit.I1 = 0 72 | tester.eval() 73 | tester.compile_and_run("verilator", magma_opts={"inline": True}, 74 | flags=['--assert']) 75 | ```` 76 | 77 | This produces the error message: 78 | ``` 79 | %Error: Foo.v:29: Assertion failed in TOP.Foo: io.I0 -> 1 != 0 <- io.I1 80 | ``` 81 | -------------------------------------------------------------------------------- /doc/tester.md: -------------------------------------------------------------------------------- 1 | # Inspecting 2 | 3 | The tester defines a `__str__` method that includes an enumeration of the action sequence. It can be useful for mapping errors in the test bench back to the associated action. 4 | 5 | For example, this code 6 | ```python 7 | tester = fault.Tester(circ, circ.CLK, default_print_format_str="%08x") 8 | tester.poke(circ.I, 0) 9 | tester.eval() 10 | tester.expect(circ.O, 0) 11 | tester.poke(circ.CLK, 0) 12 | tester.step() 13 | tester.print(circ.O) 14 | print(tester) 15 | ``` 16 | 17 | will print out 18 | 19 | ``` 20 | 21 | Actions: 22 | 0: Poke(BasicClkCircuit.I, 0) 23 | 1: Eval() 24 | 2: Expect(BasicClkCircuit.O, 0) 25 | 3: Poke(BasicClkCircuit.CLK, 0) 26 | 4: Step(BasicClkCircuit.CLK, steps=1) 27 | 5: Print(BasicClkCircuit.O, "%08x") 28 | 29 | Actions: 30 | 0: Poke(BasicClkCircuit.I, 0) 31 | 1: Eval() 32 | 2: Expect(BasicClkCircuit.O, 0) 33 | 3: Poke(BasicClkCircuit.CLK, 0) 34 | 4: Step(BasicClkCircuit.CLK, steps=1) 35 | 5: Print(BasicClkCircuit.O, "%08x") 36 | ``` 37 | -------------------------------------------------------------------------------- /examples/myinv.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | from pathlib import Path 4 | 5 | # declare circuit 6 | dut = m.DeclareCircuit('myinv', 'in_', m.BitIn, 'out', m.BitOut, 7 | 'vdd', m.BitIn, 'vss', m.BitIn) 8 | 9 | # define the test 10 | t = fault.Tester(dut) 11 | t.poke(dut.vss, False) 12 | t.poke(dut.vdd, True) 13 | t.poke(dut.in_, False) 14 | t.expect(dut.out, True) 15 | t.poke(dut.in_, True) 16 | t.expect(dut.out, False) 17 | 18 | # run the test 19 | t.compile_and_run(target='spice', simulator='ngspice', vsup=1.5, 20 | model_paths=[Path('myinv.sp').resolve()]) 21 | -------------------------------------------------------------------------------- /examples/myinv.sp: -------------------------------------------------------------------------------- 1 | * NMOS/PMOS models from 2 | * https://people.rit.edu/lffeee/SPICE_Examples.pdf 3 | 4 | .model EENMOS NMOS (VTO=0.4 KP=432E-6 GAMMA=0.2 PHI=.88) 5 | .model EEPMOS PMOS (VTO=-0.4 KP=122E-6 GAMMA=0.2 PHI=.88) 6 | 7 | .subckt myinv in_ out vdd vss 8 | 9 | MP0 out in_ vdd vdd EEPMOS w=0.7u l=0.1u 10 | MN0 out in_ vss vss EENMOS w=0.4u l=0.1u 11 | 12 | .ends 13 | -------------------------------------------------------------------------------- /examples/sram_snm.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | from pathlib import Path 4 | 5 | 6 | # define circuit name 7 | CIRCUIT_NAME = 'sram_snm' 8 | 9 | # define path to circuit 10 | THIS_DIR = Path(__file__).resolve().parent 11 | CIRCUIT_PATH = THIS_DIR / f'{CIRCUIT_NAME}.sp' 12 | 13 | # define simulator name (ngspice, spectre, or hspice) 14 | SIMULATOR = 'ngspice' 15 | 16 | # define the supply voltage 17 | VDD = 1.5 18 | 19 | # define the amount to time to wait before checking 20 | # the bit value 21 | TD = 100e-9 22 | 23 | # define the number of iterations to run 24 | N_ITER = 15 25 | 26 | 27 | def run(noise=0.0): 28 | # declare circuit 29 | dut = m.DeclareCircuit( 30 | CIRCUIT_NAME, 31 | 'lbl', m.BitInOut, 32 | 'lblb', m.BitInOut, 33 | 'vdd', m.BitIn, 34 | 'vss', m.BitIn, 35 | 'wl', m.BitIn 36 | ) 37 | 38 | # instantiate the tester 39 | tester = fault.Tester(dut, poke_delay_default=0) 40 | 41 | # initialize 42 | tester.poke(dut.lbl, fault.HiZ) 43 | tester.poke(dut.lblb, fault.HiZ) 44 | tester.poke(dut.vdd, True) 45 | tester.poke(dut.vss, False) 46 | tester.poke(dut.wl, True) 47 | tester.delay(TD) 48 | 49 | # read back data, expecting "0" 50 | tester.expect(dut.lbl, False) 51 | tester.expect(dut.lblb, True) 52 | 53 | # specify initial conditions 54 | 55 | ic = {tester.internal('lbl_x'): noise, 56 | tester.internal('lblb_x'): VDD - noise, 57 | dut.lbl: VDD, 58 | dut.lblb: VDD} 59 | 60 | # run the test 61 | tester.compile_and_run( 62 | ic=ic, 63 | vsup=VDD, 64 | target='spice', 65 | simulator=SIMULATOR, 66 | model_paths=[CIRCUIT_PATH] 67 | ) 68 | 69 | 70 | def main(): 71 | dn, up = 0, VDD 72 | for k in range(N_ITER): 73 | noise = 0.5 * (up + dn) 74 | print(f'Iteration {k}: noise={noise}') 75 | try: 76 | run(noise=noise) 77 | except (fault.A2DError, AssertionError): 78 | up = noise 79 | else: 80 | dn = noise 81 | print(f'Noise: {0.5*(up+dn)}') 82 | 83 | 84 | if __name__ == '__main__': 85 | main() 86 | -------------------------------------------------------------------------------- /examples/sram_snm.sp: -------------------------------------------------------------------------------- 1 | * NMOS/PMOS models from 2 | * https://people.rit.edu/lffeee/SPICE_Examples.pdf 3 | 4 | .model EENMOS NMOS (VTO=0.4 KP=432E-6 GAMMA=0.2 PHI=.88) 5 | .model EEPMOS PMOS (VTO=-0.4 KP=122E-6 GAMMA=0.2 PHI=.88) 6 | 7 | .subckt sram_snm lbl lblb vdd vss wl 8 | 9 | * inverter with lblb output 10 | MP0 lblb_x lbl_x vdd vdd EEPMOS w=0.7u l=0.1u 11 | MN0 lblb_x lbl_x vss vss EENMOS w=0.4u l=0.1u 12 | 13 | * inverter with lbl output 14 | MP1 lbl_x lblb_x vdd vdd EEPMOS w=0.7u l=0.1u 15 | MN1 lbl_x lblb_x vss vss EENMOS w=0.4u l=0.1u 16 | 17 | * access switches 18 | MN2 lbl wl lbl_x vss EENMOS w=1.2u l=0.1u 19 | MN3 lblb wl lblb_x vss EENMOS w=1.2u l=0.1u 20 | 21 | .ends 22 | -------------------------------------------------------------------------------- /examples/sv_tb/sv_tb.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import magma as m 4 | import fault 5 | 6 | 7 | class Queue(m.Generator): 8 | def __init__(self, T, num_bits, with_bug=False): 9 | assert num_bits >= 0 10 | self.io = m.IO( 11 | # Flipped since enq/deq is from perspective of the client 12 | enq=m.DeqIO[T], 13 | deq=m.EnqIO[T] 14 | ) + m.ClockIO() 15 | 16 | ram = m.Memory(2 ** num_bits, T)() 17 | _Pointer = m.mantle.Counter( 18 | (2 ** (num_bits + 1)), 19 | has_enable=True, 20 | has_cout=False 21 | ) 22 | enq_ptr = _Pointer() 23 | deq_ptr = _Pointer() 24 | maybe_full = m.Register(init=False, has_enable=True)() 25 | 26 | ptr_match = enq_ptr.O == deq_ptr.O 27 | empty = ptr_match & ~maybe_full.O 28 | if with_bug: 29 | # never full 30 | full = False 31 | else: 32 | full = ptr_match & maybe_full.O 33 | 34 | self.io.deq.valid @= ~empty 35 | self.io.enq.ready @= ~full 36 | 37 | do_enq = self.io.enq.fired() 38 | do_deq = self.io.deq.fired() 39 | 40 | ram.write(self.io.enq.data, enq_ptr.O[:-1], m.enable(do_enq)) 41 | 42 | enq_ptr.CE @= m.enable(do_enq) 43 | deq_ptr.CE @= m.enable(do_deq) 44 | 45 | maybe_full.I @= m.enable(do_enq) 46 | maybe_full.CE @= m.enable(do_enq != do_deq) 47 | self.io.deq.data @= ram[deq_ptr.O[:-1]] 48 | 49 | def ispow2(n): 50 | return (n & (n - 1) == 0) and n != 0 51 | 52 | 53 | def test_queue(with_bug): 54 | T = m.Bits[8] 55 | Queue4x8 = Queue(T, 2, with_bug=with_bug) 56 | 57 | class Monitor(m.Circuit): 58 | io = m.IO( 59 | enq=m.In(m.ReadyValid[T]), 60 | deq=m.In(m.ReadyValid[T]) 61 | ) + m.ClockIO() 62 | m.inline_verilog("""\ 63 | reg [7:0] data [0:3]; 64 | reg [2:0] write_pointer; 65 | reg [2:0] read_pointer; 66 | wire wen; 67 | wire ren; 68 | wire full; 69 | wire empty; 70 | assign wen = {io.enq.valid} & {io.enq.ready}; 71 | assign ren = {io.deq.ready} & {io.deq.valid}; 72 | assign empty = write_pointer == read_pointer; 73 | assign full = ((write_pointer[1:0] == read_pointer[1:0]) & 74 | (write_pointer[2] == ~read_pointer[2])); 75 | always @(posedge {io.CLK}) begin 76 | if (wen) begin 77 | assert (!full) else $error("Trying to write to full buffer"); 78 | data[write_pointer[1:0]] <= {io.enq.data}; 79 | write_pointer <= write_pointer + 1; 80 | end 81 | if (ren) begin 82 | assert (!empty) else $error("Trying to read from empty buffer"); 83 | assert ({io.deq.data} == data[read_pointer[1:0]]) else 84 | $error("Got wrong read data: io.deq.data %x != %x", 85 | {io.deq.data}, data[read_pointer[1:0]]); 86 | read_pointer <= read_pointer + 1; 87 | end 88 | end""") 89 | 90 | class DUT(m.Circuit): 91 | io = m.IO( 92 | enq=m.DeqIO[T], 93 | deq=m.EnqIO[T], 94 | ) + m.ClockIO() 95 | queue = Queue4x8() 96 | queue.enq @= io.enq 97 | queue.deq @= io.deq 98 | 99 | monitor = Monitor() 100 | monitor.enq.valid @= io.enq.valid 101 | monitor.enq.data @= io.enq.data 102 | monitor.enq.ready @= queue.enq.ready 103 | 104 | monitor.deq.valid @= queue.deq.valid 105 | monitor.deq.data @= queue.deq.data 106 | monitor.deq.ready @= io.deq.ready 107 | 108 | tester = fault.SynchronousTester(DUT, DUT.CLK) 109 | data = [0xDE, 0xAD, 0xBE, 0xEF] 110 | for i in range(32): 111 | tester.circuit.enq.data = random.choice(data) 112 | tester.circuit.enq.valid = random.randint(0, 1) 113 | tester.circuit.deq.ready = random.randint(0, 1) 114 | tester.advance_cycle() 115 | try: 116 | tester.compile_and_run( 117 | "verilator", 118 | flags=["--assert"], 119 | magma_output="mlir-verilog" 120 | ) 121 | assert not with_bug 122 | except AssertionError: 123 | assert with_bug 124 | 125 | 126 | test_queue(True) 127 | 128 | # Need to reset between test runs 129 | m.frontend.coreir_.ResetCoreIR() 130 | m.generator.reset_generator_cache() 131 | test_queue(False) 132 | -------------------------------------------------------------------------------- /examples/test_simple_alu.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import operator 3 | import fault 4 | import pytest 5 | from hwtypes import BitVector 6 | import os 7 | 8 | 9 | class ConfigReg(m.Circuit): 10 | io = m.IO(D=m.In(m.Bits[2]), Q=m.Out(m.Bits[2])) + \ 11 | m.ClockIO(has_ce=True) 12 | 13 | reg = m.Register(m.Bits[2], has_enable=True)(name="conf_reg") 14 | io.Q @= reg(io.D, CE=io.CE) 15 | 16 | 17 | class SimpleALU(m.Circuit): 18 | io = m.IO(a=m.In(m.UInt[16]), 19 | b=m.In(m.UInt[16]), 20 | c=m.Out(m.UInt[16]), 21 | config_data=m.In(m.Bits[2]), 22 | config_en=m.In(m.Enable), 23 | ) + m.ClockIO() 24 | 25 | opcode = ConfigReg(name="config_reg")(io.config_data, CE=io.config_en) 26 | io.c @= m.mux( 27 | [io.a + io.b, io.a - io.b, io.a * io.b, io.a ^ io.b], opcode) 28 | 29 | 30 | def test_simple_alu(): 31 | ops = [operator.add, operator.sub, operator.mul, operator.xor] 32 | tester = fault.Tester(SimpleALU, SimpleALU.CLK) 33 | tester.circuit.CLK = 0 34 | tester.circuit.config_en = 1 35 | for i in range(0, 4): 36 | tester.circuit.config_data = i 37 | tester.step(2) 38 | tester.circuit.a = 3 39 | tester.circuit.b = 2 40 | tester.eval() 41 | tester.circuit.c.expect(ops[i](3, 2)) 42 | 43 | tester.compile_and_run("verilator", flags=["-Wno-fatal"], directory="build") 44 | 45 | 46 | @pytest.mark.parametrize("opcode, op", 47 | enumerate(["add", "sub", "mul", "floordiv"])) 48 | def test_simple_alu_parametrized(opcode, op): 49 | op = getattr(operator, op) 50 | tester = fault.Tester(SimpleALU, SimpleALU.CLK) 51 | tester.circuit.CLK = 0 52 | tester.circuit.config_en = 1 53 | tester.circuit.config_data = opcode 54 | tester.step(4) 55 | tester.circuit.a = 3 56 | tester.circuit.b = 2 57 | tester.eval() 58 | tester.circuit.c.expect(op(BitVector[16](3), BitVector[16](2))) 59 | 60 | os.system("rm -r build/*") 61 | tester.compile_and_run("verilator", flags=["-Wno-fatal"], directory="build") 62 | 63 | 64 | @pytest.mark.parametrize("target,strategy", [("verilator", "rejection"), 65 | ("verilator", "smt"), 66 | ("cosa", None)]) 67 | def test_simple_alu_assume_guarantee(target, strategy): 68 | tester = fault.SymbolicTester(SimpleALU, SimpleALU.CLK, num_tests=100, 69 | random_strategy=strategy) 70 | tester.circuit.CLK = 0 71 | tester.circuit.config_en = 1 72 | tester.circuit.config_data = 0 73 | tester.step(2) 74 | tester.circuit.config_en = 0 75 | tester.step(2) 76 | if target == "verilator": 77 | # NOTE: Currently the cosa backend does not support the expect action 78 | tester.circuit.config_reg.Q.expect(0) 79 | tester.circuit.a.assume(lambda a: a < BitVector[16](32768)) 80 | tester.circuit.b.assume(lambda b: b < BitVector[16](32768)) 81 | # tester.circuit.b.assume(lambda b: b >= BitVector[16](32768)) 82 | 83 | tester.circuit.c.guarantee(lambda a, b, c: (c >= a) and (c >= b)) 84 | kwargs = {} 85 | if target == "verilator": 86 | kwargs["flags"] = ["-Wno-fatal"] 87 | kwargs["magma_opts"] = {"verilator_debug": True} 88 | elif target == "cosa": 89 | kwargs["magma_opts"] = {"passes": ["rungenerators", "flatten", 90 | "cullgraph"]} 91 | os.system("rm -r build/*") 92 | tester.compile_and_run(target, directory="build", **kwargs) 93 | -------------------------------------------------------------------------------- /fault.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "." 6 | } 7 | ], 8 | "settings": 9 | { 10 | "LSP": 11 | { 12 | "pyls": 13 | { 14 | "enabled": true 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /fault/__init__.py: -------------------------------------------------------------------------------- 1 | from .wrapped_internal_port import WrappedVerilogInternalPort 2 | from .ms_types import (RealIn, RealOut, RealType, 3 | ElectIn, ElectOut, ElectType) 4 | from .tester import (Tester, SymbolicTester, PythonTester, TesterBase, 5 | SynchronousTester) 6 | from .power_tester import PowerTester 7 | from .value import Value, AnyValue, UnknownValue, HiZ 8 | import fault.random 9 | from .verilogams import VAMSWrap 10 | from .tester_samples import (SRAMTester, InvTester, BufTester, 11 | NandTester, NorTester) 12 | from .random import random_bit, random_bv 13 | from .util import clog2 14 | from .spice_target import A2DError 15 | 16 | from fault.property import (assert_, implies, delay, posedge, repeat, goto, 17 | sequence, eventually, onehot0, onehot, countones, 18 | isunknown, past, rose, fell, stable, not_, 19 | throughout, until, until_with, inside, cover, 20 | assume) 21 | from fault.sva import sva 22 | from fault.assert_immediate import assert_immediate, assert_final, assert_initial 23 | from fault.expression import abs, min, max, signed, integer 24 | from fault.pysv import PysvMonitor, python_monitor 25 | from fault.ready_valid import run_ready_valid_test, ReadyValidTester 26 | -------------------------------------------------------------------------------- /fault/action_generators.py: -------------------------------------------------------------------------------- 1 | import fault 2 | from fault.common import get_renamed_port 3 | 4 | 5 | def generate_actions_from_streams(circuit, functional_model, streams, 6 | num_vectors=10, input_mapping=None): 7 | tester = fault.Tester(circuit) 8 | 9 | for i in range(num_vectors): 10 | inputs = [] 11 | for name, stream in streams.items(): 12 | if callable(stream): 13 | val = stream(name, getattr(circuit, name)) 14 | else: 15 | val = stream 16 | inputs.append(val) 17 | # stream_port = get_renamed_port(circuit, name) 18 | tester.poke(getattr(circuit, name), inputs[-1]) 19 | tester.eval() 20 | # Used to handle differences between circuit's interface and 21 | # functional_model interface. For example, the simple_cb interface 22 | # is packed for the genesis version 23 | if input_mapping: 24 | inputs = input_mapping(*inputs) 25 | functional_model(*inputs) 26 | for name, port in circuit.interface.items(): 27 | if port.is_output(): 28 | # Handle renamed output ports 29 | fn_model_port = get_renamed_port(circuit, name) 30 | tester.expect(getattr(circuit, name), 31 | getattr(functional_model, fn_model_port)) 32 | return tester.actions 33 | -------------------------------------------------------------------------------- /fault/array.py: -------------------------------------------------------------------------------- 1 | class Array: 2 | def __init__(self, value, N): 3 | self.value = value 4 | self.N = N 5 | 6 | def __getitem__(self, index): 7 | return self.value[index] 8 | 9 | def __setitem__(self, index, value): 10 | self.value[index] = value 11 | 12 | def __eq__(self, other): 13 | if not isinstance(other, Array): 14 | return False 15 | return self.N == other.N and self.value == other.value 16 | 17 | def __len__(self): 18 | return self.N 19 | 20 | @property 21 | def flattened_length(self): 22 | if isinstance(self.value, Array): 23 | return self.N * self.value.flattened_length 24 | else: 25 | return self.N 26 | 27 | def flattened(self): 28 | if isinstance(self.value, Array): 29 | return self.value.flattened() 30 | else: 31 | return self.value 32 | 33 | def __str__(self): 34 | return f"Array({self.value}, {self.N})" 35 | 36 | def __repr__(self): 37 | return f"Array({self.value}, {self.N})" 38 | -------------------------------------------------------------------------------- /fault/assert_immediate.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | from fault.assert_utils import add_compile_guards, get_when_cond 3 | 4 | 5 | def _make_assert(type_, cond, success_msg=None, failure_msg=None, 6 | severity="error", name=None, compile_guard=None, delay=False): 7 | if (when_cond := get_when_cond()) is not None: 8 | cond = ~when_cond | cond 9 | 10 | success_msg_str = "" 11 | if success_msg is not None: 12 | success_msg_str = f" $display(\"{success_msg}\");" 13 | failure_msg_str = "" 14 | format_args = {} 15 | if failure_msg is not None: 16 | if isinstance(failure_msg, str): 17 | failure_msg = f"\"{failure_msg}\"" 18 | elif isinstance(failure_msg, int): 19 | failure_msg = str(failure_msg) 20 | elif isinstance(failure_msg, tuple): 21 | msg, *args = failure_msg 22 | failure_msg = f"\"{msg}\"" 23 | for i in range(len(args)): 24 | failure_msg += f", {{_fault_assert_immediate_arg_{i}}}" 25 | format_args[f"_fault_assert_immediate_arg_{i}"] = args[i] 26 | else: 27 | raise TypeError( 28 | f"Unexpected type for failure_msg={type(failure_msg)}") 29 | failure_msg_str = f" else ${severity}({failure_msg});" 30 | name_str = "" if name is None else f"{name}: " 31 | delay_str = "" if not delay else "#1" 32 | assert_str = """\ 33 | {type_} begin 34 | {delay_str} 35 | {name_str}assert ({cond}){success_msg_str}{failure_msg_str}; 36 | end 37 | """ 38 | assert_str = add_compile_guards(compile_guard, assert_str) 39 | m.inline_verilog(assert_str, **format_args, type_=type_) 40 | 41 | 42 | def _add_docstr(fn): 43 | # The three assert_* variants share the same interface, so we use a simple 44 | # decorator to reapply the docstring to each rather than repeating the 45 | # string in each function 46 | fn.__doc__ = """ 47 | cond: m.Bit 48 | success_msg (optional): passed to $display on success 49 | failure_msg (optional): passed to else $ on failure (strings are 50 | wrapped in quotes, integers are passed without 51 | quotes (for $fatal)), can also pass a tuple of the 52 | form `("", *display_args)` to 53 | display debug information upon failure 54 | severity (optional): "error", "fatal", or "warning" 55 | name (optional): Adds `{name}: ` prefix to assertion 56 | compile_guard (optional): a string or list of strings corresponding to 57 | macro variables used to guard the assertion with 58 | verilog `ifdef statements 59 | """ 60 | return fn 61 | 62 | 63 | @_add_docstr 64 | def assert_immediate(cond, success_msg=None, failure_msg=None, severity="error", 65 | name=None, compile_guard=None): 66 | _make_assert("always @(*)", cond, success_msg, failure_msg, severity, name, 67 | compile_guard) 68 | 69 | 70 | @_add_docstr 71 | def assert_final(cond, success_msg=None, failure_msg=None, severity="error", 72 | name=None, compile_guard=None): 73 | _make_assert("final", cond, success_msg, failure_msg, severity, name, 74 | compile_guard) 75 | 76 | 77 | @_add_docstr 78 | def assert_initial(cond, success_msg=None, failure_msg=None, severity="error", 79 | name=None, compile_guard=None): 80 | _make_assert("initial", cond, success_msg, failure_msg, severity, name, 81 | compile_guard, delay=True) 82 | -------------------------------------------------------------------------------- /fault/assert_utils.py: -------------------------------------------------------------------------------- 1 | from magma.bit import Bit 2 | from magma.when import get_curr_block as get_curr_when_block, no_when 3 | 4 | 5 | def add_compile_guards(compile_guard, verilog_str): 6 | if compile_guard is None: 7 | return verilog_str 8 | if not isinstance(compile_guard, (str, list)): 9 | raise TypeError("Expected string or list for compile_guard") 10 | if isinstance(compile_guard, str): 11 | compile_guard = [compile_guard] 12 | for guard in reversed(compile_guard): 13 | # Add tabs to line for indent 14 | nested_verilog_str = "\n ".join(verilog_str.splitlines()) 15 | verilog_str = f"""\ 16 | `ifdef {guard} 17 | {nested_verilog_str} 18 | `endif 19 | """ 20 | return verilog_str 21 | 22 | 23 | def get_when_cond(): 24 | """If active when cond, return a boolean that is true when the when 25 | condition is true. 26 | """ 27 | if not get_curr_when_block(): 28 | return None 29 | with no_when(): 30 | when_cond = Bit() 31 | when_cond @= 0 32 | when_cond @= 1 33 | return when_cond 34 | -------------------------------------------------------------------------------- /fault/circuit_utils.py: -------------------------------------------------------------------------------- 1 | def check_interface_is_subset(circuit1, circuit2): 2 | """ 3 | Checks that the interface of circuit1 is a subset of circuit2 4 | 5 | Subset is defined as circuit2 contains all the ports of circuit1. Ports are 6 | matched by name comparison, then the types are checked to see if one could 7 | be converted to another. 8 | """ 9 | circuit1_port_names = circuit1.interface.ports.keys() 10 | for name in circuit1_port_names: 11 | if name not in circuit2.interface.ports: 12 | raise ValueError(f"{circuit2} (circuit2) does not have port {name}") 13 | circuit1_kind = type(type(getattr(circuit1, name))) 14 | circuit2_kind = type(type(getattr(circuit2, name))) 15 | circuit1_sub_circuit2 = issubclass(circuit1_kind, circuit2_kind) 16 | circuit2_sub_circuit1 = issubclass(circuit2_kind, circuit1_kind) 17 | # Check that the type of one could be converted to the other 18 | if not (circuit1_sub_circuit2 or circuit2_sub_circuit1): 19 | raise ValueError(f"Port {name} types don't match:" 20 | f" Type0={circuit1_kind}," 21 | f" Type1={circuit2_kind}") 22 | -------------------------------------------------------------------------------- /fault/codegen.py: -------------------------------------------------------------------------------- 1 | from itertools import count 2 | 3 | 4 | class CodeGenerator: 5 | def __init__(self, tab=' ', nl='\n'): 6 | # save settings 7 | self.tab = tab 8 | self.nl = nl 9 | 10 | # initialize 11 | self.reset() 12 | 13 | def reset(self): 14 | self.text = '' 15 | self.tab_count = 0 16 | self.inst_count = count() 17 | 18 | def indent(self): 19 | self.tab_count += 1 20 | 21 | def dedent(self): 22 | self.tab_count -= 1 23 | assert self.tab_count >= 0, 'Cannot have a negative number of tabs.' 24 | 25 | def print(self, *args): 26 | for arg in args: 27 | self.text += arg 28 | 29 | def emptyln(self): 30 | self.print(f'{self.nl}') 31 | 32 | def println(self, *args): 33 | for arg in args: 34 | self.print(f'{self.tab_count*self.tab}{arg}{self.nl}') 35 | 36 | def println_comma_sep(self, *args, indent=True): 37 | if indent: 38 | self.indent() 39 | for k, arg in enumerate(args): 40 | line = f'{arg}' 41 | if k != len(args) - 1: 42 | line += ',' 43 | self.println(line) 44 | if indent: 45 | self.dedent() 46 | 47 | def write_to_file(self, fname): 48 | with open(fname, 'w') as f: 49 | f.write(self.text) 50 | -------------------------------------------------------------------------------- /fault/common.py: -------------------------------------------------------------------------------- 1 | def get_renamed_port(circuit, name): 2 | if hasattr(circuit, "renamed_ports"): 3 | for key, value in circuit.renamed_ports.items(): 4 | if value == name: 5 | return key 6 | return name 7 | -------------------------------------------------------------------------------- /fault/config.py: -------------------------------------------------------------------------------- 1 | __TEST_DIR = 'normal' 2 | 3 | 4 | def set_test_dir(target): 5 | """ 6 | Set to 'callee_file_dir' to have the `directory` parameter to 7 | `compile_and_run` relative to the calling file (default is relative to 8 | where Python is invoked) 9 | """ 10 | global __TEST_DIR 11 | assert target in ['normal', 'callee_file_dir'] 12 | __TEST_DIR = target 13 | 14 | 15 | def get_test_dir(): 16 | return __TEST_DIR 17 | -------------------------------------------------------------------------------- /fault/fault_errors.py: -------------------------------------------------------------------------------- 1 | class FaultError(Exception): 2 | pass 3 | 4 | 5 | class A2DError(FaultError): 6 | pass 7 | 8 | 9 | class ExpectError(FaultError): 10 | pass 11 | -------------------------------------------------------------------------------- /fault/file.py: -------------------------------------------------------------------------------- 1 | import fault.actions as actions 2 | import os 3 | 4 | 5 | class File: 6 | def __init__(self, name, tester, mode, chunk_size, endianness): 7 | self.name = name 8 | self.tester = tester 9 | self.mode = mode 10 | self.chunk_size = chunk_size 11 | basename = os.path.basename(self.name) 12 | filename = os.path.splitext(basename)[0] 13 | filename = filename.replace(".", "_") 14 | self.name_without_ext = filename 15 | self.endianness = endianness 16 | 17 | def __str__(self): 18 | return f'File<"{self.name}">' 19 | -------------------------------------------------------------------------------- /fault/functional_tester.py: -------------------------------------------------------------------------------- 1 | import fault 2 | from fault.common import get_renamed_port 3 | from fault import AnyValue 4 | 5 | 6 | class FunctionalTester(fault.Tester): 7 | """ 8 | This Tester provides a convenience mechanism for verifying a DUT against a 9 | functional model. The basic pattern is that every time `eval` is invoked 10 | on the Tester, a check is done to verify that the current outputs of the 11 | functional model are equivalent to the outputs of the DUT. This pattern 12 | works best with a model that is fairly low-level (e.g. cycle accurate). The 13 | user has the flexibility to relax accuracy of the model by setting the 14 | outputs of the functional model to be `fault.AnyValue`. Anything is equal 15 | to `fault.AnyValue`, so the user can manage when to actually perform the 16 | consistency check by only updating `fault.AnyValue` at the appropriate 17 | time. 18 | """ 19 | def __init__(self, circuit, clock, functional_model, input_mapping=None): 20 | super().__init__(circuit, clock) 21 | self.functional_model = functional_model 22 | self.input_mapping = input_mapping 23 | 24 | def expect(self, port, value): 25 | raise RuntimeError("Cannot call expect on FunctionTester, expectations" 26 | " are automatically generated based on the" 27 | " functional model") 28 | 29 | def eval(self): 30 | super().eval() 31 | for name, port in self._circuit.interface.ports.items(): 32 | if port.is_input(): 33 | fn_model_port = get_renamed_port(self._circuit, name) 34 | super().expect(port, getattr(self.functional_model, 35 | fn_model_port)) 36 | 37 | def expect_any_outputs(self): 38 | for name, port in self._circuit.interface.ports.items(): 39 | if port.is_input(): 40 | fn_model_port = get_renamed_port(self._circuit, name) 41 | setattr(self.functional_model, fn_model_port, AnyValue) 42 | -------------------------------------------------------------------------------- /fault/infix.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | 4 | class Infix: 5 | def __init__(self, func): 6 | self.func = func 7 | 8 | def __or__(self, other): 9 | return self.func(other) 10 | 11 | def __ror__(self, other): 12 | return Infix(partial(self.func, other)) 13 | -------------------------------------------------------------------------------- /fault/logging.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import print_function 3 | 4 | import logging 5 | import traceback 6 | import inspect 7 | import sys 8 | 9 | 10 | log = logging.getLogger("fault") 11 | 12 | 13 | def info(message, *args, **kwargs): 14 | log.info(message, *args, **kwargs) 15 | 16 | 17 | def debug(message, *args, **kwargs): 18 | log.debug(message, *args, **kwargs) 19 | 20 | 21 | def warning(message, *args, **kwargs): 22 | log.warning(message, *args, **kwargs) 23 | 24 | 25 | def error(message, *args, **kwargs): 26 | log.error(message, *args, **kwargs) 27 | -------------------------------------------------------------------------------- /fault/magma_utils.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | 3 | 4 | def is_recursive_type(T): 5 | return (issubclass(T, m.Tuple) or issubclass(T, m.Array) and 6 | not issubclass(T.T, m.Digital)) 7 | -------------------------------------------------------------------------------- /fault/ms_types.py: -------------------------------------------------------------------------------- 1 | from magma import Digital, Direction 2 | 3 | 4 | class RealType(Digital): 5 | pass 6 | 7 | 8 | RealIn = RealType[Direction.In] 9 | RealOut = RealType[Direction.Out] 10 | RealInOut = RealType[Direction.InOut] 11 | 12 | 13 | class ElectType(Digital): 14 | pass 15 | 16 | 17 | ElectIn = ElectType[Direction.In] 18 | ElectOut = ElectType[Direction.Out] 19 | ElectInOut = ElectType[Direction.InOut] 20 | -------------------------------------------------------------------------------- /fault/netlister.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from fault.subprocess_run import subprocess_run 3 | 4 | 5 | si_env_tmpl = '''\ 6 | simLibName = "{lib}" 7 | simCellName = "{cell}" 8 | simViewName = "{view}" 9 | simSimulator = "auCdl" 10 | simNotIncremental = 't 11 | simReNetlistAll = nil 12 | simViewList = '("auCdl" "schematic") 13 | simStopList = '("auCdl") 14 | hnlNetlistFileName = "netlist" 15 | resistorModel = "" 16 | shortRES = 2000.0 17 | preserveRES = 't 18 | checkRESVAL = 't 19 | checkRESSIZE = 'nil 20 | preserveCAP = 't 21 | checkCAPVAL = 't 22 | checkCAPAREA = 'nil 23 | preserveDIO = 't 24 | checkDIOAREA = 't 25 | checkDIOPERI = 't 26 | checkCAPPERI = 'nil 27 | simPrintInhConnAttributes = 'nil 28 | checkScale = "meter" 29 | checkLDD = 'nil 30 | pinMAP = 'nil 31 | preserveBangInNetlist = 'nil 32 | shrinkFACTOR = 0.0 33 | globalPowerSig = "" 34 | globalGndSig = "" 35 | displayPININFO = 't 36 | preserveALL = 't 37 | setEQUIV = "" 38 | incFILE = "" 39 | auCdlDefNetlistProc = "ansCdlSubcktCall" 40 | ''' 41 | 42 | 43 | def si_netlist(lib, cell, cds_lib='cds.lib', cwd='.', view='schematic', 44 | out='netlist', del_incl=True, env=None): 45 | # path wrapping 46 | cwd = Path(cwd) 47 | out = Path(out) 48 | 49 | # write si.env file 50 | si_env = si_env_tmpl.format(lib=lib, cell=cell, view=view) 51 | with open(cwd / 'si.env', 'w') as f: 52 | f.write(si_env) 53 | 54 | # run netlister 55 | args = [] 56 | args += ['si'] 57 | args += ['-cdslib', f'{cds_lib}'] 58 | args += ['-batch'] 59 | args += ['-command', 'netlist'] 60 | subprocess_run(args, cwd=cwd, env=env) 61 | 62 | # get netlist text and filter out include statementw 63 | with open(cwd / 'netlist', 'r') as f: 64 | lines = f.readlines() 65 | text = '' 66 | for line in lines: 67 | line_lower = line.strip().lower() 68 | if del_incl and line_lower.startswith('.include'): 69 | continue 70 | else: 71 | text += line 72 | 73 | # write netlist to desired file 74 | with open(out, 'w') as f: 75 | f.write(text) 76 | -------------------------------------------------------------------------------- /fault/power_tester.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | from fault import Tester 3 | 4 | 5 | class PowerTester(Tester): 6 | def __init__(self, circuit: m.Circuit, clock: m.Clock = None): 7 | super().__init__(circuit, clock) 8 | self.supply0s = [] 9 | self.supply1s = [] 10 | self.tris = [] 11 | 12 | def add_power(self, port): 13 | self.supply1s.append(port.name.name) 14 | 15 | def add_ground(self, port): 16 | self.supply0s.append(port.name.name) 17 | 18 | def add_tri(self, port): 19 | self.tris.append(port.name.name) 20 | 21 | def run(self, target="system-verilog"): 22 | power_args = { 23 | "supply0s": self.supply0s, 24 | "supply1s": self.supply1s, 25 | "tris": self.tris, 26 | } 27 | self.targets[target].run(self.actions, power_args) 28 | -------------------------------------------------------------------------------- /fault/pwl.py: -------------------------------------------------------------------------------- 1 | def pwc_to_pwl(pwc, t_stop, t_tr, init=0): 2 | # add initial value if necessary 3 | if len(pwc) == 0 or pwc[0][0] != 0: 4 | pwc = pwc.copy() 5 | pwc.insert(0, (0, init)) 6 | 7 | # then create piecewise-linear implementation 8 | # of the piecewise-constant stimulus 9 | retval = [pwc[0]] 10 | for k in range(1, len(pwc)): 11 | t_prev, v_prev = pwc[k - 1] 12 | t_curr, v_curr = pwc[k] 13 | 14 | retval += [(t_curr, v_prev)] 15 | retval += [(t_curr + t_tr, v_curr)] 16 | 17 | # add final value 18 | retval += [(t_stop, pwc[-1][1])] 19 | 20 | # return new waveform 21 | return retval 22 | -------------------------------------------------------------------------------- /fault/ready_valid.py: -------------------------------------------------------------------------------- 1 | from typing import Mapping 2 | 3 | import magma as m 4 | from fault.tester.synchronous import SynchronousTester 5 | from fault.property import assert_, posedge 6 | 7 | 8 | def wrap_with_sequence(ckt, sequences): 9 | class Wrapper(m.Circuit): 10 | io = m.IO(_fault_rv_tester_done_=m.Out(m.Bit)) 11 | 12 | inst = ckt() 13 | 14 | # We lift non sequence ports as is, and sequence ports as monitors. 15 | normal_lifted = [] 16 | monitors = [] 17 | for key, value in ckt.interface.items(): 18 | if key == "_fault_rv_tester_done_": 19 | raise RuntimeError("Reserved port name used") 20 | 21 | if key not in sequences: 22 | io += m.IO(**{key: value}) 23 | normal_lifted.append(key) 24 | else: 25 | io += m.IO(**{key: m.Out(value)}) 26 | monitors.append(key) 27 | 28 | for key in normal_lifted: 29 | m.wire(io[key], getattr(inst, key)) 30 | 31 | done = m.Bit(1) 32 | for key, value in sequences.items(): 33 | port = getattr(inst, key) 34 | n = len(value) 35 | # Counter to advance through sequence 36 | count = m.Register(m.UInt[(n - 1).bit_length()], 37 | has_enable=True)() 38 | count.I @= count.O + 1 39 | 40 | # Current sequence elem chosen by counter 41 | curr = m.mux(value, count.O) 42 | 43 | # Still sequence elements to process 44 | not_done = (count.O != (n - 1)) 45 | 46 | # NOTE: Here we could add logic for random stalls 47 | port_T = type(port) 48 | if m.is_consumer(port_T): 49 | # Advance count when circuit is ready and we are valid (not 50 | # done with sequence) 51 | count.CE @= not_done & port.ready 52 | port.data @= curr 53 | port.valid @= not_done 54 | elif m.is_producer(port_T): 55 | # Advance count when circuit produces valid and we are 56 | # ready (not done with sequence) 57 | count.CE @= not_done & port.valid 58 | port.ready @= not_done 59 | assert_(~(not_done & port.valid) | (port.data == curr), 60 | on=posedge(io.CLK)) 61 | else: 62 | raise NotImplementedError(port_T) 63 | # Done when all counters reach final element 64 | done = done & (count.O == (n - 1)) 65 | io._fault_rv_tester_done_ @= done 66 | 67 | for key in monitors: 68 | value = getattr(inst, key) 69 | port = io[key] 70 | if m.is_producer(value): 71 | port.ready @= value.ready.value() 72 | port.valid @= value.valid 73 | port.data @= value.data 74 | else: 75 | port.ready @= value.ready 76 | port.valid @= value.valid.value() 77 | port.data @= value.data.value() 78 | return Wrapper 79 | 80 | 81 | def _add_verilator_assert_flag(kwargs): 82 | # We need to include "-assert" flat to the verilator command. 83 | if not kwargs: 84 | kwargs = {"flags": ["-assert"]} 85 | elif not kwargs.get("flags"): 86 | kwargs["flags"] = ["-assert"] 87 | elif "-assert" not in kwargs["flags"]: 88 | kwargs["flags"].append("-assert") 89 | return kwargs 90 | 91 | 92 | def run_ready_valid_test(ckt: m.DefineCircuitKind, sequences: Mapping, 93 | target, synthesizable: bool = True, 94 | compile_and_run_args=[], compile_and_run_kwargs={}): 95 | if target == "verilator": 96 | compile_and_run_kwargs = \ 97 | _add_verilator_assert_flag(compile_and_run_kwargs) 98 | if synthesizable: 99 | wrapped = wrap_with_sequence(ckt, sequences) 100 | tester = SynchronousTester(wrapped) 101 | tester.wait_until_high(wrapped._fault_rv_tester_done_, timeout=1000) 102 | tester.compile_and_run(target, *compile_and_run_args, 103 | **compile_and_run_kwargs, disp_type="realtime") 104 | return 105 | raise NotImplementedError() 106 | 107 | 108 | class ReadyValidTester(SynchronousTester): 109 | def __init__(self, ckt: m.DefineCircuitKind, sequences: Mapping, 110 | *args, **kwargs): 111 | self.wrapped = wrap_with_sequence(ckt, sequences) 112 | super().__init__(self.wrapped, *args, **kwargs) 113 | 114 | def finish_sequences(self, timeout=1000): 115 | self.wait_until_high(self.wrapped._fault_rv_tester_done_, 116 | timeout=timeout) 117 | 118 | def expect_sequences_finished(self): 119 | self.expect(self.wrapped._fault_rv_tester_done_, 1) 120 | 121 | def compile_and_run(self, target, *args, **kwargs): 122 | if target == "verilator": 123 | kwargs = _add_verilator_assert_flag(kwargs) 124 | super().compile_and_run(*args, **kwargs) 125 | -------------------------------------------------------------------------------- /fault/result_parse.py: -------------------------------------------------------------------------------- 1 | try: 2 | import numpy as np 3 | from scipy.interpolate import interp1d 4 | import decida.Data 5 | except ModuleNotFoundError: 6 | print('Failed to import libraries for results parsing. Capabilities may be limited.') # noqa 7 | 8 | 9 | class SpiceResult: 10 | def __init__(self, t, v): 11 | self.t = t 12 | self.v = v 13 | self.func = interp1d(t, v, bounds_error=False, fill_value=(v[0], v[-1])) 14 | 15 | def __call__(self, t): 16 | return self.func(t) 17 | 18 | 19 | # temporary measure -- CSDF parsing is broken in 20 | # DeCiDa so a simple parser is implemented here 21 | class CSDFData: 22 | def __init__(self): 23 | self._names = {'time': 0} 24 | self._data = None 25 | self.data = {} 26 | 27 | def names(self): 28 | return list(self._names.keys()) 29 | 30 | def get(self, name): 31 | return self._data[:, self._names[name]] 32 | 33 | def read(self, file): 34 | # read in flat data vector 35 | mode = None 36 | data = [] 37 | with open(file, 'r') as f: 38 | for line in f: 39 | # determine mode 40 | line = line.strip().split() 41 | if not line: 42 | continue 43 | elif line[0] == '#N': 44 | mode = '#N' 45 | line.pop(0) 46 | elif line[0] == '#C': 47 | mode = '#C' 48 | line.pop(0) 49 | data.append(float(line.pop(0))) 50 | line.pop(0) 51 | elif line[0] == '#;': 52 | break 53 | # parse depending on mode 54 | if mode == '#N': 55 | for tok in line: 56 | tok = tok[1:-1] 57 | self._names[tok] = len(self._names) 58 | elif mode == '#C': 59 | for tok in line: 60 | data.append(float(tok)) 61 | 62 | # reshape into numpy array 63 | nv = len(self._names) 64 | ns = len(data) // nv 65 | data = np.array(data, dtype=float) 66 | data = np.reshape(data, (ns, nv)) 67 | 68 | # store using internal numpy array 69 | self._data = data 70 | 71 | 72 | def nut_parse(nut_file, time='time'): 73 | data = decida.Data.Data() 74 | data.read_nutmeg(f'{nut_file}') 75 | return data_to_interp(data=data, time=time) 76 | 77 | 78 | def psf_parse(psf_file, time='time'): 79 | data = decida.Data.Data() 80 | data.read_psf(f'{psf_file}') 81 | return data_to_interp(data=data, time=time) 82 | 83 | 84 | def hspice_parse(tr0_file, time='time'): 85 | data = CSDFData() 86 | data.read(f'{tr0_file}') 87 | return data_to_interp(data=data, time=time) 88 | 89 | 90 | def data_to_interp(data, time, strip_vi=True): 91 | retval = {} 92 | 93 | # preprocess results as needed 94 | rdict = {} 95 | for name in data.names(): 96 | value = data.get(name) 97 | if strip_vi: 98 | if name.lower().startswith('v(') and name.lower().endswith(')'): 99 | name = name[2:-1] 100 | elif name.lower().startswith('i(') and name.lower().endswith(')'): 101 | name = name[2:-1] 102 | rdict[name] = value 103 | 104 | # prepare dictionary of interpolators 105 | time_vec = rdict[time] 106 | for name, value_vec in rdict.items(): 107 | # skip time variable -- no need to interpolate time to itself 108 | if name == time: 109 | continue 110 | 111 | # create interpolator 112 | result = SpiceResult(t=time_vec, v=value_vec) 113 | 114 | # add interpolator to dictionary 115 | retval[name] = result 116 | 117 | # return results 118 | return retval 119 | -------------------------------------------------------------------------------- /fault/select_path.py: -------------------------------------------------------------------------------- 1 | import fault 2 | from fault.verilog_utils import verilog_name, verilator_name 3 | 4 | 5 | class SelectPath: 6 | def __init__(self, init=None): 7 | self.path = [] if init is None else init 8 | 9 | @property 10 | def debug_name(self): 11 | assert hasattr(self.path[1], "debug_name"), type(self.path[-1]) 12 | return self.path[-1].debug_name 13 | 14 | def insert(self, index, value): 15 | self.path.insert(index, value) 16 | 17 | def __getitem__(self, index): 18 | return self.path[index] 19 | 20 | # TODO: Do we want to support mutability? 21 | def __setitem__(self, index, value): 22 | self.path[index] = value 23 | 24 | def make_path(self, separator, name_func=verilog_name): 25 | # Initialize empty path 26 | path = [] 27 | 28 | # Add the second through second-to-last entries to the path string. 29 | # Note that the first path entry is simply skipped. 30 | for x in self.path[1:-1]: 31 | if isinstance(x, str): 32 | path += [x] 33 | else: 34 | path += [x.instance.name] 35 | 36 | # Append the final path entry 37 | if isinstance(self.path[-1], fault.WrappedVerilogInternalPort): 38 | path += [self.path[-1].path] 39 | elif isinstance(self.path[-1], str): 40 | path += [self.path[-1]] 41 | else: 42 | path += [name_func(self.path[-1].name)] 43 | 44 | # Return the path string constructed with the provided separator. 45 | return separator.join(path) 46 | 47 | def system_verilog_path(self, disable_ndarray): 48 | def name_func(x): 49 | return verilog_name(x, disable_ndarray) 50 | return self.make_path(".", 51 | name_func=name_func) 52 | 53 | @property 54 | def spice_path(self): 55 | return self.make_path(".") 56 | 57 | @property 58 | def verilator_path(self): 59 | return self.make_path("->", name_func=verilator_name) 60 | 61 | @property 62 | def debug_name(self): 63 | return self.make_path('.') 64 | 65 | def __len__(self): 66 | return len(self.path) 67 | -------------------------------------------------------------------------------- /fault/sva.py: -------------------------------------------------------------------------------- 1 | class SVAProperty: 2 | def __init__(self, args): 3 | self.args = args 4 | 5 | 6 | def sva(*args): 7 | new_args = tuple() 8 | # Escape format chars 9 | for arg in args: 10 | if isinstance(arg, str): 11 | arg = arg.replace("{", "{{").replace("}", "}}") 12 | new_args += (arg,) 13 | return SVAProperty(new_args) 14 | -------------------------------------------------------------------------------- /fault/target.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Target(ABC): 5 | def __init__(self, circuit): 6 | self.circuit = circuit 7 | 8 | @abstractmethod 9 | def run(self, actions): 10 | pass 11 | -------------------------------------------------------------------------------- /fault/test_vector_generator.py: -------------------------------------------------------------------------------- 1 | import fault 2 | from fault.random import random_bv, random_bit 3 | import magma as m 4 | from hwtypes import BitVector, Bit 5 | from fault.common import get_renamed_port 6 | 7 | 8 | def get_random_arr(name, port): 9 | if issubclass(port, m.Bits) or issubclass(port.T, m.Digital): 10 | # TODO: Hack, check the name and don't twiddle config ports, we 11 | # should add a config type 12 | if "config_" in name: 13 | return BitVector[len(port)](0) 14 | else: 15 | return random_bv(len(port)) 16 | else: 17 | if issubclass(port.T, m.Array): 18 | return fault.array.Array([get_random_arr(name + f"_{i}", port.T) 19 | for i in range(len(port))], len(port)) 20 | raise NotImplementedError() # pragma: nocover 21 | 22 | 23 | def get_random_input(name, port): 24 | if issubclass(port, m.Array): 25 | return get_random_arr(name, port) 26 | elif issubclass(port, m.AsyncReset): 27 | return 0 28 | elif issubclass(port, m.Digital): 29 | # TODO: Hack, check the name and don't twiddle config ports, we 30 | # should add a config type 31 | if "config_" in name: 32 | return Bit(0) 33 | elif "reset" in name: 34 | return Bit(0) 35 | else: 36 | return random_bit() 37 | else: 38 | raise NotImplementedError(name, port, type(port)) # pragma: nocover 39 | 40 | 41 | def generate_random_test_vectors(circuit, functional_model, 42 | num_vectors=10, input_mapping=None): 43 | tester = fault.Tester(circuit) 44 | 45 | for i in range(num_vectors): 46 | inputs = [] 47 | for name, port in circuit.interface.items(): 48 | if port.is_input(): 49 | inputs.append(get_random_input(name, port)) 50 | tester.poke(getattr(circuit, name), inputs[-1]) 51 | tester.eval() 52 | # Used to handle differences between circuit's interface and 53 | # functional_model interface. For example, the simple_cb interface 54 | # is packed for the genesis version 55 | if input_mapping: 56 | inputs = input_mapping(*inputs) 57 | functional_model(*inputs) 58 | for name, port in circuit.interface.items(): 59 | if port.is_output(): 60 | # Handle renamed output ports 61 | fn_model_port = get_renamed_port(circuit, name) 62 | tester.expect(getattr(circuit, name), 63 | getattr(functional_model, fn_model_port)) 64 | return tester.test_vectors 65 | -------------------------------------------------------------------------------- /fault/tester/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import TesterBase 2 | from .staged_tester import Tester 3 | from .symbolic_tester import SymbolicTester 4 | from .interactive_tester import PythonTester 5 | from .synchronous import SynchronousTester 6 | -------------------------------------------------------------------------------- /fault/tester/control.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | from typing import List 3 | 4 | 5 | class LoopIndex: 6 | def __init__(self, name): 7 | self.name = name 8 | 9 | def __str__(self): 10 | return self.name 11 | 12 | 13 | class NoClockInit: 14 | """ 15 | Sub testers should not initialize the clock 16 | """ 17 | 18 | def init_clock(self): 19 | pass 20 | 21 | 22 | def add_control_structures(tester_class): 23 | """ 24 | Decorator to add control structures after Tester class definition so we can 25 | use the definition as a base class. 26 | 27 | This allows Loops, If, and Else testers to inherit the base class methods 28 | (e.g. for SynchronousTester they should inherit the advance_cycle method) 29 | """ 30 | class LoopTester(NoClockInit, tester_class): 31 | __unique_index_id = -1 32 | 33 | def __init__(self, circuit: m.Circuit, clock: m.Clock, monitors): 34 | super().__init__(circuit, clock, monitors=monitors) 35 | LoopTester.__unique_index_id += 1 36 | self.index = LoopIndex( 37 | f"__fault_loop_var_action_{LoopTester.__unique_index_id}") 38 | 39 | class ElseTester(NoClockInit, tester_class): 40 | def __init__(self, else_actions: List, circuit: m.Circuit, 41 | clock: m.Clock, monitors): 42 | super().__init__(circuit, clock, monitors=monitors) 43 | self.actions = else_actions 44 | 45 | class IfTester(NoClockInit, tester_class): 46 | def __init__(self, circuit: m.Circuit, clock: m.Clock, monitors): 47 | super().__init__(circuit, clock, monitors=monitors) 48 | self.else_actions = [] 49 | 50 | def _else(self): 51 | return ElseTester(self.else_actions, self._circuit, self.clock, 52 | self.monitors) 53 | 54 | class ForkTester(NoClockInit, tester_class): 55 | def __init__(self, name, circuit: m.Circuit, clock: m.Clock, monitors): 56 | super().__init__(circuit, clock, monitors=monitors) 57 | self.name = name 58 | 59 | tester_class.LoopTester = LoopTester 60 | tester_class.ElseTester = ElseTester 61 | tester_class.IfTester = IfTester 62 | tester_class.ForkTester = ForkTester 63 | return tester_class 64 | -------------------------------------------------------------------------------- /fault/tester/interactive_tester.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | from magma.simulator import PythonSimulator 3 | from magma.scope import Scope 4 | from .base import TesterBase 5 | from .utils import get_port_type 6 | from ..select_path import SelectPath 7 | from ..value_utils import make_value 8 | from hwtypes import BitVector, Bit 9 | from ..wrapper import PortWrapper 10 | from ..magma_utils import is_recursive_type 11 | 12 | 13 | class InteractiveTester(TesterBase): 14 | pass 15 | 16 | 17 | def check(got, port, expected): 18 | if isinstance(port, m.Array) and \ 19 | isinstance(port.T, m.Digital) and \ 20 | not isinstance(port, m.Bits) and \ 21 | isinstance(expected, BitVector): 22 | # If port is an Array(N, Bit) and **not** a Bits(N), then the 23 | # Python simulator will return a list of bools. So, if the user 24 | # provided a BitVector, we unpack it here so the equality check 25 | # works 26 | expected = expected.as_bool_list() 27 | if isinstance(port, m.Array): 28 | for i in range(port.N): 29 | check(got[i], port[i], expected[i]) 30 | return 31 | assert got == expected, f"Got {got}, expected {expected}" 32 | 33 | 34 | def _process_port(port): 35 | scope = Scope() 36 | if isinstance(port, PortWrapper): 37 | port = port.select_path 38 | if isinstance(port, SelectPath): 39 | for i in port[1:-1]: 40 | scope = Scope(parent=scope, instance=i.instance) 41 | port = port[-1] 42 | return port, scope 43 | 44 | 45 | class PythonTester(InteractiveTester): 46 | def __init__(self, *args, **kwargs): 47 | super().__init__(*args, **kwargs) 48 | self.simulator = PythonSimulator(self._circuit, self.clock) 49 | 50 | def eval(self): 51 | self.simulator.evaluate() 52 | 53 | def _set_value(self, port, value): 54 | port, scope = _process_port(port) 55 | self.simulator.set_value(port, value, scope) 56 | 57 | def _poke(self, port, value, delay=None): 58 | if delay is not None: 59 | raise NotImplementedError("delay is not support in Python " 60 | "simulator") 61 | type_ = get_port_type(port) 62 | self._set_value(port, value) 63 | 64 | def process_result(self, type_, result): 65 | if issubclass(type_, m.Digital): 66 | return Bit(result) 67 | if issubclass(type_, m.Bits): 68 | return BitVector[len(type_)](result) 69 | if is_recursive_type(type_): 70 | return BitVector[len(type_)](result) 71 | return result 72 | 73 | def _get_value(self, port): 74 | port, scope = _process_port(port) 75 | if isinstance(port, (int, BitVector, Bit, list)): 76 | return port 77 | result = self.simulator.get_value(port, scope) 78 | return self.process_result(type(port), result) 79 | 80 | def _expect(self, port, value, strict=None, caller=None, **kwargs): 81 | got = self._get_value(port) 82 | port, scope = _process_port(port) 83 | value = make_value(type(port), value) 84 | expected = self._get_value(value) 85 | check(got, port, expected) 86 | 87 | def peek(self, port): 88 | return self._get_value(port) 89 | 90 | def print(self, format_str, *args): 91 | got = [self._get_value(port) for port in args] 92 | values = () 93 | for value, port in zip(got, args): 94 | if (isinstance(port, m.Array) and 95 | issubclass(port.T, m.Digital)): 96 | value = BitVector[len(port)](value).as_uint() 97 | elif isinstance(port, m.Array): 98 | raise NotImplementedError("Printing complex nested " 99 | "arrays") 100 | values += (value, ) 101 | print(format_str % values, end="") 102 | 103 | def assert_(self, expr, msg=""): 104 | assert expr, msg 105 | 106 | def delay(self, time): 107 | raise NotImplementedError() 108 | 109 | def get_value(self, port): 110 | raise NotImplementedError() 111 | 112 | def step(self, steps=1): 113 | """ 114 | Step the clock `steps` times. 115 | """ 116 | self.eval() 117 | self.simulator.advance(steps) 118 | 119 | def wait_until_low(self, signal): 120 | while self.peek(signal): 121 | self.step() 122 | 123 | def wait_until_high(self, signal): 124 | while ~self.peek(signal): 125 | self.step() 126 | 127 | def __call__(self, *args, **kwargs): 128 | result = super().__call__(*args, **kwargs) 129 | if isinstance(result, tuple): 130 | return tuple(self.peek(r.select_path) for r in result) 131 | return self.peek(result.select_path) 132 | 133 | def advance_cycle(self): 134 | self.step(2) 135 | -------------------------------------------------------------------------------- /fault/tester/sequence_tester.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from fault.tester.staged_tester import Tester 4 | 5 | 6 | class SequenceTester(Tester): 7 | def __init__(self, circuit, driver, monitor, sequence, clock=None): 8 | super().__init__(circuit, clock) 9 | self.driver = driver 10 | self.driver.set_tester(self) 11 | self.monitor = monitor 12 | self.monitor.set_tester(self) 13 | self.sequence = sequence 14 | 15 | def _compile_sequences(self): 16 | for item in self.sequence: 17 | self.driver.lower(*item) 18 | if self.clock is None: 19 | self.eval() 20 | else: 21 | self.step(2) 22 | self.monitor.observe(*item) 23 | 24 | def _compile(self, target="verilator", **kwargs): 25 | self._compile_sequences() 26 | super()._compile(target, **kwargs) 27 | 28 | 29 | class SequenceTesterEntity(ABC): 30 | def set_tester(self, tester): 31 | self.tester = tester 32 | 33 | 34 | class Driver(SequenceTesterEntity): 35 | @abstractmethod 36 | def lower(self, *args): 37 | pass 38 | 39 | 40 | class Monitor(SequenceTesterEntity): 41 | @abstractmethod 42 | def observe(self, *args): 43 | pass 44 | -------------------------------------------------------------------------------- /fault/tester/symbolic_tester.py: -------------------------------------------------------------------------------- 1 | import fault 2 | from .staged_tester import Tester 3 | from fault.wrapper import Wrapper, PortWrapper, InstanceWrapper 4 | try: 5 | from fault.pono_target import PonoTarget 6 | except ImportError: 7 | # Optional dependency 8 | pass 9 | import fault.actions as actions 10 | from fault.random import ConstrainedRandomGenerator 11 | 12 | 13 | class SymbolicWrapper(Wrapper): 14 | def __init__(self, circuit, parent): 15 | super().__init__(circuit, parent) 16 | 17 | def __setattr__(self, attr, value): 18 | # Hack to stage this after __init__ has been run, should redefine this 19 | # method in a metaclass? Could also use a try/except pattern, so the 20 | # exceptions only occur during object instantiation 21 | if hasattr(self, "circuit") and hasattr(self, "instance_map"): 22 | if attr in self.circuit.interface.ports.keys(): 23 | if isinstance(self.parent, fault.Tester): 24 | self.parent.poke(self.circuit.interface.ports[attr], value) 25 | else: 26 | exit(1) 27 | else: 28 | object.__setattr__(self, attr, value) 29 | else: 30 | object.__setattr__(self, attr, value) 31 | 32 | def __getattr__(self, attr): 33 | # Hack to stage this after __init__ has been run, should redefine this 34 | # method in a metaclass? 35 | try: 36 | if attr in self.circuit.interface.ports.keys(): 37 | return SymbolicPortWrapper(self.circuit.interface.ports[attr], 38 | self) 39 | elif attr in self.instance_map: 40 | return SymbolicInstanceWrapper(self.instance_map[attr], self) 41 | else: 42 | object.__getattribute__(self, attr) 43 | except Exception as e: 44 | object.__getattribute__(self, attr) 45 | 46 | 47 | class SymbolicCircuitWrapper(SymbolicWrapper): 48 | pass 49 | 50 | 51 | class SymbolicPortWrapper(PortWrapper): 52 | def assume(self, pred): 53 | select_path = self.select_path 54 | select_path.tester.assume(select_path, pred) 55 | 56 | def guarantee(self, pred): 57 | select_path = self.select_path 58 | select_path.tester.guarantee(select_path, pred) 59 | 60 | 61 | class SymbolicInstanceWrapper(InstanceWrapper): 62 | pass 63 | 64 | 65 | class SymbolicTester(Tester): 66 | def __init__(self, circuit, clock=None, num_tests=100, 67 | random_strategy="rejection"): 68 | super().__init__(circuit, clock) 69 | self.num_tests = num_tests 70 | self.random_strategy = random_strategy 71 | 72 | def assume(self, port, constraint): 73 | """ 74 | Place a constraint on an input port by providing a symbolic expression 75 | as a Python lambda or function 76 | 77 | symbolic_tester_inst.assume(top.I, lambda x : x >= 0) 78 | """ 79 | action = actions.Assume(port, constraint) 80 | action.has_randvals = False 81 | if self.random_strategy == "smt": 82 | port = port[-1] 83 | v = {str(port.name): len(port)} 84 | gen = ConstrainedRandomGenerator() 85 | action.randvals = iter(gen(v, constraint, self.num_tests)) 86 | action.has_randvals = True 87 | self.actions.append(action) 88 | 89 | def guarantee(self, port, constraint): 90 | """ 91 | Assert a property about an output port by providing a symbolic 92 | expression as a Python lambda or function 93 | 94 | symbolic_tester_inst.assume(top.O, lambda x : x >= 0) 95 | """ 96 | self.actions.append(actions.Guarantee(port, constraint)) 97 | 98 | @property 99 | def circuit(self): 100 | return SymbolicCircuitWrapper(self._circuit, self) 101 | 102 | def run(self, target="verilator"): 103 | if target == "verilator": 104 | self.targets[target].run(self.actions, self.verilator_includes, 105 | self.num_tests, self._circuit) 106 | elif target == "pono": 107 | self.targets[target].run(self.actions) 108 | else: 109 | raise NotImplementedError() 110 | 111 | def make_target(self, target: str, **kwargs): 112 | if target == "pono": 113 | return PonoTarget(self._circuit, **kwargs) 114 | else: 115 | return super().make_target(target, **kwargs) 116 | -------------------------------------------------------------------------------- /fault/tester/synchronous.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | from .staged_tester import StagedTester 4 | from ..system_verilog_target import SynchronousSystemVerilogTarget 5 | from ..verilator_target import SynchronousVerilatorTarget 6 | 7 | from fault.tester.control import add_control_structures 8 | from fault.pysv import PysvMonitor 9 | import fault.actions as actions 10 | 11 | import magma as m 12 | 13 | 14 | @add_control_structures 15 | class SynchronousTester(StagedTester): 16 | def __init__(self, *args, **kwargs): 17 | super().__init__(*args, **kwargs) 18 | if self.clock is None: 19 | raise ValueError("SynchronousTester requires a clock") 20 | 21 | def eval(self): 22 | raise TypeError("Cannot eval with synchronous tester") 23 | 24 | def _flat_peek(self, value): 25 | if (isinstance(value, m.Tuple) or 26 | isinstance(value, m.Array) and 27 | not issubclass(value.T, m.Digital)): 28 | return sum((self._flat_peek(elem) for elem in value), []) 29 | return [self.peek(value)] 30 | 31 | def _call_monitors(self): 32 | for monitor in self.monitors: 33 | args = monitor.observe._orig_args_ 34 | assert args[0] == "self" 35 | args = sum((self._flat_peek(getattr(self._circuit, arg)) 36 | for arg in args[1:]), []) 37 | self.make_call_stmt(monitor.observe, *args) 38 | 39 | def advance_cycle(self): 40 | self.step(1) 41 | self._call_monitors() 42 | self.step(1) 43 | 44 | def make_target(self, target, **kwargs): 45 | if target == "system-verilog": 46 | return SynchronousSystemVerilogTarget(self._circuit, 47 | clock=self.clock, **kwargs) 48 | if target == "system-verilog": 49 | return SynchronousVerilatorTarget(self._circuit, clock=self.clock, 50 | **kwargs) 51 | return super().make_target(target, **kwargs) 52 | 53 | def attach_monitor(self, monitor: actions.Var): 54 | if (not isinstance(monitor, actions.Var) and 55 | not isinstance(monitor._type, PysvMonitor)): 56 | raise TypeError("Expected PysvMonitor variable") 57 | self.monitors.append(monitor) 58 | -------------------------------------------------------------------------------- /fault/tester/utils.py: -------------------------------------------------------------------------------- 1 | from ..select_path import SelectPath 2 | from ..wrapped_internal_port import WrappedVerilogInternalPort 3 | from ..actions import Var 4 | 5 | 6 | def get_port_type(port): 7 | if isinstance(port, SelectPath): 8 | port = port[-1] 9 | if isinstance(port, WrappedVerilogInternalPort): 10 | return port.type_ 11 | if isinstance(port, Var): 12 | return port._type 13 | return type(port) 14 | -------------------------------------------------------------------------------- /fault/tester_samples.py: -------------------------------------------------------------------------------- 1 | import fault 2 | from abc import ABCMeta, abstractmethod 3 | 4 | 5 | class GenericCellTester(fault.Tester, metaclass=ABCMeta): 6 | def __init__(self, circuit, *args, n_trials=100, supply0='vss', 7 | supply1='vdd', poke_delay_default=100e-9, **kwargs): 8 | # call super constructor 9 | super().__init__(circuit, *args, poke_delay_default=poke_delay_default, 10 | **kwargs) 11 | 12 | # save settings 13 | self.supply0 = supply0 14 | self.supply1 = supply1 15 | self.n_trials = n_trials 16 | 17 | # define the test 18 | self.define_test() 19 | 20 | def has_pin(self, name): 21 | return hasattr(self._circuit, name) 22 | 23 | def poke(self, name, *args, **kwargs): 24 | super().poke(getattr(self._circuit, name), *args, **kwargs) 25 | 26 | def poke_optional(self, name, *args, **kwargs): 27 | if self.has_pin(name): 28 | self.poke(name, *args, **kwargs) 29 | 30 | def expect(self, name, *args, **kwargs): 31 | super().expect(getattr(self._circuit, name), *args, **kwargs) 32 | 33 | def define_init(self): 34 | self.poke_optional(self.supply1, 1) 35 | self.poke_optional(self.supply0, 0) 36 | 37 | @abstractmethod 38 | def define_trial(self): 39 | pass 40 | 41 | def define_test(self): 42 | self.define_init() 43 | for _ in range(self.n_trials): 44 | self.define_trial() 45 | 46 | 47 | class SingleOutputTester(GenericCellTester): 48 | def __init__(self, circuit, *args, inputs=None, out='out', **kwargs): 49 | # save settings 50 | self.inputs = inputs if inputs is not None else [] 51 | self.out = out 52 | 53 | # call super constructor 54 | super().__init__(circuit, *args, **kwargs) 55 | 56 | @abstractmethod 57 | def model(self, *args): 58 | pass 59 | 60 | def define_trial(self): 61 | # poke random data 62 | data = [] 63 | for input_ in self.inputs: 64 | data += [fault.random_bit()] 65 | self.poke(input_, data[-1]) 66 | # expect a value based on the model 67 | self.expect(self.out, self.model(*data), strict=True) 68 | 69 | 70 | class UnaryOpTester(SingleOutputTester): 71 | def __init__(self, *args, in_='in_', out='out', **kwargs): 72 | inputs = [in_] 73 | super().__init__(*args, inputs=inputs, out=out, **kwargs) 74 | 75 | 76 | class BinaryOpTester(SingleOutputTester): 77 | def __init__(self, *args, a='a', b='b', out='out', **kwargs): 78 | inputs = [a, b] 79 | super().__init__(*args, inputs=inputs, out=out, **kwargs) 80 | 81 | 82 | class InvTester(UnaryOpTester): 83 | def model(self, in_): 84 | return not in_ 85 | 86 | 87 | class BufTester(UnaryOpTester): 88 | def model(self, in_): 89 | return in_ 90 | 91 | 92 | class NandTester(BinaryOpTester): 93 | def model(self, a, b): 94 | return not (a and b) 95 | 96 | 97 | class NorTester(BinaryOpTester): 98 | def model(self, a, b): 99 | return not (a or b) 100 | 101 | 102 | class SRAMTester(GenericCellTester): 103 | def __init__(self, circuit, *args, lbl='lbl', lblb='lblb', wl='wl', 104 | **kwargs): 105 | # build the pinmap 106 | self.lbl = lbl 107 | self.lblb = lblb 108 | self.wl = wl 109 | 110 | # call super constructor 111 | super().__init__(circuit, *args, **kwargs) 112 | 113 | def define_init(self): 114 | self.poke(self.wl, 0) 115 | self.poke(self.lbl, 0) 116 | self.poke(self.lblb, 0) 117 | super().define_init() 118 | 119 | def define_trial(self): 120 | # generate random input 121 | d = fault.random_bit() 122 | 123 | # write value 124 | self.poke(self.lbl, d) 125 | self.poke(self.lblb, not d) 126 | self.poke(self.wl, 1) 127 | self.poke(self.wl, 0) 128 | 129 | # read value 130 | self.poke(self.lbl, fault.HiZ) 131 | self.poke(self.lblb, fault.HiZ) 132 | self.poke(self.wl, 1) 133 | self.expect(self.lbl, d, strict=True) 134 | self.expect(self.lblb, not d, strict=True) 135 | self.poke(self.wl, 0) 136 | -------------------------------------------------------------------------------- /fault/user_cfg.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | import logging 4 | 5 | 6 | class FaultConfig: 7 | def __init__(self): 8 | # initialize 9 | self.opts = {} 10 | 11 | # read in config information 12 | self.read_cfg_files() 13 | 14 | def read_cfg_files(self): 15 | try: 16 | import yaml 17 | except ModuleNotFoundError: 18 | logging.warning('pyyaml not found, cannot parse config files.') 19 | logging.warning('Please run "pip install pyyaml" to fix this.') 20 | return 21 | 22 | locs = [Path.home() / '.faultrc', Path('.') / 'fault.yml'] 23 | for loc in locs: 24 | if loc.exists(): 25 | with open(loc, 'r') as f: 26 | try: 27 | new_opts = yaml.safe_load(f) 28 | self.opts.update(new_opts) 29 | except yaml.YAMLError as yaml_err: 30 | logging.warning(f'Skipping config file {loc} due to a parsing error. Error message:') # noqa 31 | logging.warning(f'{yaml_err}') 32 | 33 | def get_sim_env(self): 34 | env = os.environ.copy() 35 | 36 | if self.opts.get('remove_conda', False): 37 | self.remove_conda(env) 38 | 39 | for key, val in self.opts.get('add_env_vars', {}).items(): 40 | env[f'{key}'] = f'{val}' 41 | 42 | return env 43 | 44 | @staticmethod 45 | def remove_conda(env): 46 | '''Returns a copy of the current environment with conda directories 47 | removed from the path.''' 48 | 49 | # find the location of the conda installation 50 | # if the variable is not found, then just 51 | # return the original environment 52 | try: 53 | conda_prefix = env['CONDA_PREFIX'] 54 | except KeyError: 55 | return env 56 | 57 | # convert to Path object 58 | conda_prefix = os.path.realpath(os.path.expanduser(conda_prefix)) 59 | conda_prefix = Path(conda_prefix) 60 | 61 | # split up the path into individual entries, then filter out 62 | # those starting with the CONDA_PREFIX 63 | path_entries = env['PATH'].split(os.pathsep) 64 | path_entries = [Path(entry) for entry in path_entries] 65 | path_entries = [entry for entry in path_entries 66 | if conda_prefix not in entry.parents] 67 | 68 | # update the PATH variable 69 | env['PATH'] = os.pathsep.join(str(entry) for entry in path_entries) 70 | -------------------------------------------------------------------------------- /fault/util.py: -------------------------------------------------------------------------------- 1 | from math import ceil, log2 2 | 3 | 4 | def clog2(x): 5 | return int(ceil(log2(x))) 6 | 7 | 8 | def flatten(l): 9 | return [item for sublist in l for item in sublist] 10 | 11 | 12 | def has_kratos_runtime(): 13 | try: 14 | import kratos_runtime 15 | return True 16 | except ImportError: 17 | return False 18 | 19 | 20 | def is_valid_file_mode(file_mode): 21 | '''Return True if the given "file_mode" represents a valid file I/O mode''' 22 | return file_mode in {'r', 'w', 'rb', 'wb', 'a', 'ab', 'r+', 'rb+', 'w+', 23 | 'wb+', 'a+', 'ab+'} 24 | 25 | 26 | def file_mode_allows_reading(file_mode): 27 | '''Return True if the given "file_mode" allows reading''' 28 | return file_mode in {'r', 'rb', 'r+', 'rb+', 'w+', 'wb+', 'a+', 'ab+'} 29 | 30 | 31 | def file_mode_allows_writing(file_mode): 32 | '''Return True if the given "file_mode" allows writing''' 33 | return file_mode in {'w', 'wb', 'a', 'ab', 'r+', 'rb+', 'w+', 'wb+', 'a+', 34 | 'ab+'} 35 | -------------------------------------------------------------------------------- /fault/utils.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | import os 4 | 5 | 6 | # From https://gist.github.com/Xion/617c1496ff45f3673a5692c3b0e3f75a 7 | def get_short_lambda_body_text(lambda_func): 8 | """Return the source of a (short) lambda function. 9 | If it's impossible to obtain, returns None. 10 | """ 11 | try: 12 | source_lines, _ = inspect.getsourcelines(lambda_func) 13 | except (IOError, TypeError): 14 | return None 15 | 16 | # skip `def`-ed functions and long lambdas 17 | if len(source_lines) != 1: 18 | return None 19 | 20 | source_text = os.linesep.join(source_lines).strip() 21 | 22 | # find the AST node of a lambda definition 23 | # so we can locate it in the source code 24 | source_ast = ast.parse(source_text) 25 | lambda_node = next((node for node in ast.walk(source_ast) 26 | if isinstance(node, ast.Lambda)), None) 27 | if lambda_node is None: # could be a single line `def fn(x): ...` 28 | return None 29 | 30 | # HACK: Since we can (and most likely will) get source lines 31 | # where lambdas are just a part of bigger expressions, they will have 32 | # some trailing junk after their definition. 33 | # 34 | # Unfortunately, AST nodes only keep their _starting_ offsets 35 | # from the original source, so we have to determine the end ourselves. 36 | # We do that by gradually shaving extra junk from after the definition. 37 | lambda_text = source_text[lambda_node.col_offset:] 38 | lambda_body_text = source_text[lambda_node.body.col_offset:] 39 | min_length = len('lambda:_') # shortest possible lambda expression 40 | while len(lambda_text) > min_length: 41 | try: 42 | compile(lambda_body_text, '', 'eval') 43 | return lambda_body_text 44 | except SyntaxError: 45 | pass 46 | lambda_text = lambda_text[:-1] 47 | lambda_body_text = lambda_body_text[:-1] 48 | 49 | return None 50 | -------------------------------------------------------------------------------- /fault/value.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Value(Enum): 5 | Any = 0 6 | Unknown = 1 # X 7 | HiZ = 2 # Z 8 | 9 | 10 | AnyValue = Value.Any 11 | UnknownValue = Value.Unknown 12 | HiZ = Value.HiZ 13 | -------------------------------------------------------------------------------- /fault/value_utils.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import magma 3 | import magma as m 4 | from magma.protocol_type import MagmaProtocol 5 | from hwtypes import BitVector, Bit, FPVector 6 | from fault.value import AnyValue, UnknownValue, HiZ 7 | from fault.ms_types import RealType 8 | from fault.array import Array 9 | from fault.select_path import SelectPath 10 | from hwtypes.adt import Enum 11 | from hwtypes import BitVector 12 | 13 | 14 | def make_value(type_, value): 15 | if isinstance(type_, m.MagmaProtocolMeta): 16 | type_ = type_._to_magma_() 17 | if isinstance(value, MagmaProtocol): 18 | value = value._get_magma_value_() 19 | if not value.const(): 20 | raise TypeError("Expected const value when poking with instance of " 21 | "MagmaProtocol") 22 | if issubclass(type_, RealType): 23 | return make_real(value) 24 | if issubclass(type_, magma.Digital): 25 | return make_bit(value) 26 | if issubclass(type_, magma.Array): 27 | return make_array(type_.T, type_.N, value) 28 | if issubclass(type_, magma.Tuple): 29 | raise NotImplementedError() 30 | if issubclass(type_, BitVector): 31 | return value 32 | raise NotImplementedError(type_, value) 33 | 34 | 35 | def make_real(value): 36 | return value 37 | 38 | 39 | def make_bit(value): 40 | # TODO(rsetaluri): Use bit_vector.Bit when implemented. 41 | if isinstance(value, BitVector) and len(value) == 1: 42 | return value[0] 43 | if value == 0 or value == 1: 44 | return Bit(value) 45 | if value is AnyValue or value is UnknownValue or value is HiZ: 46 | return value 47 | raise NotImplementedError(value) 48 | 49 | 50 | def make_array(T, N, value): 51 | assert isinstance(N, int) 52 | if issubclass(T, magma.Digital): 53 | return make_bit_vector(N, value) 54 | if issubclass(T, magma.Array): 55 | if isinstance(value, list): 56 | return Array([make_array(T.T, T.N, value[i]) for i in range(N)], N) 57 | else: 58 | return Array([make_array(T.T, T.N, value) for _ in range(N)], N) 59 | raise NotImplementedError(T, N, value) 60 | 61 | 62 | def make_bit_vector(N, value): 63 | assert isinstance(N, int) 64 | if isinstance(value, FPVector): 65 | value = value.reinterpret_as_bv() 66 | if isinstance(value, BitVector[N]): 67 | return value 68 | if isinstance(value, BitVector) and len(value) < N or \ 69 | isinstance(value, Bit): 70 | return BitVector[N](value) 71 | if isinstance(value, int): 72 | return BitVector[N](value) 73 | if value is AnyValue or value is UnknownValue or value is HiZ: 74 | return value 75 | if isinstance(value, Enum): 76 | return BitVector[N](value.value) 77 | if isinstance(value, m.Bits): 78 | return BitVector[N](int(value)) 79 | raise NotImplementedError(N, value, type(value)) 80 | 81 | 82 | def is_any(value): 83 | if value is AnyValue: 84 | return True 85 | if isinstance(value, list): 86 | return all([is_any(v) for v in value]) 87 | return False 88 | -------------------------------------------------------------------------------- /fault/vector_builder.py: -------------------------------------------------------------------------------- 1 | from hwtypes import BitVector 2 | import magma 3 | import fault.actions as actions 4 | from fault.value_utils import make_value 5 | from fault.value import AnyValue 6 | 7 | 8 | class VectorBuilder: 9 | def __init__(self, circuit): 10 | self.circuit = circuit 11 | self.port_to_index = {} 12 | for i, port in enumerate(self.circuit.interface.ports.values()): 13 | self.port_to_index[port] = i 14 | self.vectors = [self.__empty_vector()] 15 | 16 | def __empty_vector(self): 17 | ports = self.circuit.interface.ports 18 | return [make_value(type(port), AnyValue) for port in ports.values()] 19 | 20 | def __indices(self, port): 21 | if port in self.port_to_index: 22 | return (self.port_to_index[port],) 23 | if isinstance(port.name, magma.ref.ArrayRef): 24 | indices = self.__indices(port.name.array) 25 | return (*indices, port.name.index) 26 | raise NotImplementedError(port, type(port)) 27 | 28 | def __get(self, indices): 29 | out = self.vectors[-1] 30 | for idx in indices: 31 | out = out[idx] 32 | return out 33 | 34 | def __set(self, indices, value): 35 | parent = self.vectors[-1] 36 | for idx in indices[:-1]: 37 | parent = parent[idx] 38 | parent[indices[-1]] = value 39 | 40 | def __eval(self): 41 | self.vectors.append(self.vectors[-1].copy()) 42 | for port in self.circuit.interface.ports.values(): 43 | if port.is_input(): 44 | index = self.port_to_index[port] 45 | self.vectors[-1][index] = make_value(type(port), AnyValue) 46 | 47 | def process(self, action): 48 | if isinstance(action, (actions.Poke, actions.Expect)): 49 | indices = self.__indices(action.port) 50 | self.__set(indices, action.value) 51 | elif isinstance(action, actions.Eval): 52 | self.__eval() 53 | elif isinstance(action, actions.Step): 54 | indices = self.__indices(action.clock) 55 | val = self.__get(indices) 56 | for step in range(action.steps): 57 | val ^= BitVector[1](1) 58 | self.__eval() 59 | self.__set(indices, val) 60 | elif isinstance(action, actions.Print): 61 | # Skip Print actions for test vectors 62 | return 63 | else: 64 | raise NotImplementedError(action) 65 | -------------------------------------------------------------------------------- /fault/verilator_utils.py: -------------------------------------------------------------------------------- 1 | from .subprocess_run import subprocess_run 2 | import os 3 | 4 | 5 | def verilator_version(disp_type='on_error'): 6 | # assemble the command 7 | cmd = ['verilator', '--version'] 8 | 9 | # run the command and parse out the version number 10 | result = subprocess_run(cmd, shell=True, disp_type=disp_type) 11 | version = float(result.stdout.split()[1]) 12 | 13 | # return version number 14 | return version 15 | 16 | 17 | def verilator_comp_cmd(top=None, verilog_filename=None, 18 | include_verilog_libraries=None, 19 | include_directories=None, 20 | driver_filename=None, verilator_flags=None, 21 | coverage=False, use_kratos=False, 22 | defines=None, parameters=None, 23 | use_pysv=None): 24 | # set defaults 25 | if include_verilog_libraries is None: 26 | include_verilog_libraries = [] 27 | if include_directories is None: 28 | include_directories = [] 29 | if verilator_flags is None: 30 | verilator_flags = [] 31 | if defines is None: 32 | defines = {} 33 | if parameters is None: 34 | parameters = {} 35 | 36 | # build up the command 37 | retval = [] 38 | retval += ['verilator'] 39 | retval += ['-Wall'] 40 | retval += ['-Wno-INCABSPATH'] 41 | retval += ['-Wno-DECLFILENAME'] 42 | retval += verilator_flags 43 | 44 | if verilog_filename is not None: 45 | retval += ['--cc', f'{verilog_filename}'] 46 | if use_kratos: 47 | from kratos_runtime import get_lib_path 48 | retval += [os.path.basename(get_lib_path())] 49 | if use_pysv: 50 | # this file name is the same cross all platforms 51 | retval += ["libpysv.so"] 52 | 53 | # -v arguments 54 | for file_ in include_verilog_libraries: 55 | retval += ['-v', f'{file_}'] 56 | 57 | # -I arguments 58 | for dir_ in include_directories: 59 | retval += [f'-I{dir_}'] 60 | 61 | # -D arguments 62 | for k, v in defines.items(): 63 | if v is not None: 64 | retval += [f'-D{k}={v}'] 65 | else: 66 | retval += [f'-D{k}'] 67 | 68 | # -G arguments 69 | for k, v in parameters.items(): 70 | retval += [f'-G{k}={v}'] 71 | 72 | if driver_filename is not None: 73 | retval += ['--exe', f'{driver_filename}'] 74 | if top is not None: 75 | retval += ['--top-module', f'{top}'] 76 | 77 | # vpi flag 78 | if use_kratos: 79 | retval += ["--vpi"] 80 | if coverage: 81 | retval += ["--coverage"] 82 | 83 | # return the command 84 | return retval 85 | 86 | 87 | def verilator_make_cmd(top): 88 | cmd = [] 89 | cmd += ['make'] 90 | cmd += ['-C', 'obj_dir'] 91 | cmd += ['-j'] 92 | cmd += ['-f', f'V{top}.mk'] 93 | cmd += [f'V{top}'] 94 | return cmd 95 | -------------------------------------------------------------------------------- /fault/verilog_utils.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | 3 | 4 | def is_nd_array(T, skip=True): 5 | if issubclass(T, m.Digital) and not skip: 6 | return True 7 | if issubclass(T, m.Array): 8 | return is_nd_array(T.T, False) 9 | return False 10 | 11 | 12 | def verilog_name(name, disable_ndarray=False): 13 | if isinstance(name, m.ref.DefnRef): 14 | return str(name) 15 | if isinstance(name, m.ref.ArrayRef): 16 | array_name = verilog_name(name.array.name, disable_ndarray) 17 | if (issubclass(name.array.T, m.Digital) or 18 | (is_nd_array(type(name.array)) and not disable_ndarray)): 19 | index = name.index 20 | if isinstance(index, slice): 21 | assert index.step in {None, 1}, "Unexpanded variable step slice" 22 | index = f"{index.stop - 1}:{index.start}" 23 | return f"{array_name}[{index}]" 24 | assert isinstance(name.index, int) 25 | return f"{array_name}_{name.index}" 26 | if isinstance(name, m.ref.TupleRef): 27 | tuple_name = verilog_name(name.tuple.name, disable_ndarray) 28 | index = name.index 29 | try: 30 | int(index) 31 | # python/coreir don't allow pure integer names 32 | index = f"_{index}" 33 | except ValueError: 34 | pass 35 | return f"{tuple_name}_{index}" 36 | raise NotImplementedError(name, type(name)) 37 | 38 | 39 | def verilator_name(name): 40 | if isinstance(name, m.ref.ArrayRef) and issubclass(name.array.T, m.Digital): 41 | # Setting a specific bit is done using bit twiddling, so we return the 42 | # full array see https://github.com/leonardt/fault/pull/194 for more 43 | # info 44 | name = verilog_name(name.array.name) 45 | else: 46 | name = verilog_name(name) 47 | # pg 21 of verilator 4.018 manual 48 | # To avoid conicts with Verilator's internal symbols, any double 49 | # underscore are replaced with ___05F (5F is the hex code of an underscore.) 50 | return name.replace("__", "___05F") 51 | -------------------------------------------------------------------------------- /fault/verilogams.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | from fault.codegen import CodeGenerator 3 | from fault import ElectIn, ElectOut, RealIn, RealOut 4 | 5 | 6 | class VAMSPort(): 7 | def __init__(self, dir_, type_, name): 8 | self.dir_ = dir_ 9 | self.type_ = type_ 10 | self.name = name 11 | 12 | def __str__(self): 13 | return f'{self.dir_} {self.type_} {self.name}' 14 | 15 | 16 | class VerilogAMSCode(CodeGenerator): 17 | def include(self, file): 18 | self.println(f'`include "{file}"') 19 | 20 | def start_module(self, name, *ports): 21 | self.println(f'module {name} (') 22 | self.println_comma_sep(*[f'{port}' for port in ports]) 23 | self.println(');') 24 | self.indent() 25 | 26 | def instantiate(self, mod_name, *wiring, inst_name=None): 27 | # set defaults 28 | if inst_name is None: 29 | inst_name = f'tmp{next(self.inst_count)}' 30 | 31 | self.println(f'{mod_name} {inst_name} (') 32 | self.println_comma_sep(*[ 33 | f'.{a}({b})' for a, b in wiring 34 | ]) 35 | self.println(');') 36 | 37 | def end_module(self): 38 | self.dedent() 39 | self.println('endmodule') 40 | 41 | 42 | def VAMSWrap(circ, wrap_name=None, inst_name=None, tab=' ', nl='\n'): 43 | # Set defaults 44 | wrap_name = (wrap_name if wrap_name is not None 45 | else f'{circ.name}_wrap') 46 | inst_name = (inst_name if inst_name is not None 47 | else f'{circ.name}_inst') 48 | 49 | # instantiate code generator 50 | gen = VerilogAMSCode(tab=tab, nl=nl) 51 | 52 | # include files 53 | gen.include('disciplines.vams') 54 | gen.emptyln() 55 | 56 | # module definition 57 | ports = [] 58 | for name, type_ in circ.IO.ports.items(): 59 | # determine port direction 60 | if type_.is_input(): 61 | vams_dir = 'input' 62 | elif type_.is_output(): 63 | vams_dir = 'output' 64 | elif type_.is_inout(): 65 | vams_dir = 'input' 66 | else: 67 | raise Exception(f'Unsupported port type: {type_}') 68 | 69 | # determine port type 70 | if type_ is ElectIn or type_ is ElectOut: 71 | vams_type = 'electrical' 72 | elif type_ is RealIn or type_ is RealOut: 73 | vams_type = 'wreal' 74 | elif len(type_) == 1: 75 | vams_type = 'wire' 76 | else: 77 | vams_type = f'wire [{len(type_) - 1}:0]' 78 | 79 | # add port to list of ports 80 | ports += [VAMSPort(dir_=vams_dir, type_=vams_type, name=f'{name}')] 81 | gen.start_module(wrap_name, *ports) 82 | gen.emptyln() 83 | 84 | # Generate wiring withing the module instantiation 85 | wiring = [] 86 | for name in circ.IO.ports.keys(): 87 | wiring += [(f'{name}', f'{name}')] 88 | gen.instantiate(f'{circ.name}', *wiring, inst_name=inst_name) 89 | gen.emptyln() 90 | 91 | # end the module 92 | gen.end_module() 93 | 94 | # Create magma circuit for wrapper 95 | io_kwargs = {} 96 | for name, type_ in circ.IO.ports.items(): 97 | io_kwargs[f'{name}'] = type_ 98 | 99 | # declare circuit that will be returned 100 | class wrap_cls(m.Circuit): 101 | name = f'{wrap_name}' 102 | io = m.IO(**io_kwargs) 103 | 104 | # Poke the VerilogAMS code into the magma circuit (a bit hacky...) 105 | wrap_cls.vams_code = gen.text 106 | wrap_cls.vams_inst_name = inst_name 107 | 108 | # Return the magma circuit 109 | return wrap_cls 110 | -------------------------------------------------------------------------------- /fault/wrapped_internal_port.py: -------------------------------------------------------------------------------- 1 | class WrappedVerilogInternalPort: 2 | def __init__(self, path: str, type_): 3 | """ 4 | path: . (can nest instances) 5 | 6 | type_: magma type of the signal (e.g. m.Bits(2)) 7 | """ 8 | self.path = path 9 | self.type_ = type_ 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pycodestyle] 2 | max-line-length = 80 3 | 4 | # E741: do not use variables named 'l', 'O', or 'I' 5 | # We ignore this because I and O is currently idiomatic magma for port names 6 | # W503: line break before binary operator 7 | # W504: line break before after operator 8 | # Ignore W503 and W504 since they are contradictory 9 | ignore = E741,W503,W504 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Setup file for fault 3 | """ 4 | 5 | from setuptools import setup 6 | 7 | with open("README.md", "r") as fh: 8 | LONG_DESCRIPTION = fh.read() 9 | 10 | DESCRIPTION = """\ 11 | A Python package for testing hardware (part of the magma ecosystem)\ 12 | """ 13 | 14 | setup( 15 | name='fault', 16 | version='4.0.1', 17 | description=DESCRIPTION, 18 | scripts=[], 19 | packages=[ 20 | "fault", 21 | "fault.tester", 22 | ], 23 | install_requires=[ 24 | "astor", 25 | "z3-solver", 26 | "hwtypes", 27 | "magma-lang>=3.0.0", 28 | "pyyaml", 29 | "scipy", 30 | "numpy", 31 | "DeCiDa", 32 | "pysv" 33 | ], 34 | license='BSD License', 35 | url='https://github.com/leonardt/fault', 36 | author='Leonard Truong', 37 | author_email='lenny@cs.stanford.edu', 38 | python_requires='>=3.6', 39 | long_description=LONG_DESCRIPTION, 40 | long_description_content_type="text/markdown" 41 | ) 42 | -------------------------------------------------------------------------------- /test.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leonardt/fault/093bc9a02efedb5aad1173fca84ce1f56ac996b7/test.pkl -------------------------------------------------------------------------------- /tests/SB_DFF.v: -------------------------------------------------------------------------------- 1 | sb_dff_sim.v -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leonardt/fault/093bc9a02efedb5aad1173fca84ce1f56ac996b7/tests/__init__.py -------------------------------------------------------------------------------- /tests/build/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/common.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import magma as m 3 | 4 | 5 | def pytest_sim_params(metafunc, *args, exclude=None): 6 | # set defaults 7 | if exclude is None: 8 | exclude = [] 9 | elif isinstance(exclude, str): 10 | exclude = [exclude] 11 | exclude = set(exclude) 12 | 13 | # simulators supported by each kind of target 14 | sims_by_arg = {'system-verilog': ['vcs', 'ncsim', 'iverilog', 'vivado'], 15 | 'verilog-ams': ['ncsim'], 16 | 'verilator': [None], 17 | 'spice': ['ngspice', 'spectre', 'hspice']} 18 | 19 | # only parameterize if we can actually specify the target type 20 | # and simulator to use for this particular test 21 | fixturenames = metafunc.fixturenames 22 | if 'target' in fixturenames and 'simulator' in fixturenames: 23 | targets = [] 24 | for arg in args: 25 | for simulator in sims_by_arg[arg]: 26 | if simulator in exclude: 27 | continue 28 | elif simulator is None or shutil.which(simulator): 29 | targets.append((arg, simulator)) 30 | 31 | metafunc.parametrize("target,simulator", targets) 32 | 33 | 34 | def outlines(capsys): 35 | captured = capsys.readouterr() 36 | lines = captured.out.splitlines() 37 | lines = [line.rstrip() for line in lines] 38 | return lines 39 | 40 | 41 | def define_simple_circuit(T, circ_name, has_clk=False): 42 | class _Circuit(m.Circuit): 43 | __test__ = False # Disable pytest discovery 44 | name = circ_name 45 | io = m.IO(I=m.In(T), O=m.Out(T)) 46 | if has_clk: 47 | io += m.ClockIO() 48 | 49 | m.wire(io.I, io.O) 50 | 51 | return _Circuit 52 | 53 | 54 | TestBasicCircuit = define_simple_circuit(m.Bit, "BasicCircuit") 55 | TestArrayCircuit = define_simple_circuit(m.Array[3, m.Bit], "ArrayCircuit") 56 | TestByteCircuit = define_simple_circuit(m.Bits[8], "ByteCircuit") 57 | TestUInt32Circuit = define_simple_circuit(m.UInt[32], "UInt32Circuit") 58 | TestUInt64Circuit = define_simple_circuit(m.UInt[64], "UInt64Circuit") 59 | TestUInt128Circuit = define_simple_circuit(m.UInt[128], "UInt128Circuit") 60 | TestSIntCircuit = define_simple_circuit(m.SInt[4], "SIntCircuit") 61 | TestNestedArraysCircuit = define_simple_circuit(m.Array[3, m.Bits[4]], 62 | "NestedArraysCircuit") 63 | TestDoubleNestedArraysCircuit = define_simple_circuit( 64 | m.Array[2, m.Array[3, m.Bits[4]]], "DoubleNestedArraysCircuit") 65 | TestNestedArrayTupleCircuit = define_simple_circuit( 66 | m.Array[2, m.Array[3, m.Product.from_fields("anon", dict(a=m.Bits[4], 67 | b=m.Bits[4]))]], 68 | "NestedArrayTupleCircuit") 69 | TestBasicClkCircuit = define_simple_circuit(m.Bit, "BasicClkCircuit", True) 70 | TestBasicClkCircuitCopy = define_simple_circuit(m.Bit, "BasicClkCircuitCopy", 71 | True) 72 | TestTupleCircuit = define_simple_circuit( 73 | m.Product.from_fields("anon", dict(a=m.Bits[4], b=m.Bits[4])), 74 | "TupleCircuit") 75 | TestNestedTupleCircuit = define_simple_circuit( 76 | m.Product.from_fields( 77 | "anon", 78 | dict(a=m.Product.from_fields("anon", dict(k=m.Bits[5], v=m.Bits[2])), 79 | b=m.Bits[4])), 80 | "NestedTupleCircuit") 81 | 82 | T = m.Bits[3] 83 | 84 | 85 | class TestPeekCircuit(m.Circuit): 86 | __test__ = False # Disable pytest discovery 87 | io = m.IO(I=m.In(T), O0=m.Out(T), O1=m.Out(T)) 88 | 89 | m.wire(io.I, io.O0) 90 | m.wire(io.I, io.O1) 91 | 92 | 93 | class ConfigReg(m.Circuit): 94 | io = m.IO(D=m.In(m.Bits[2]), Q=m.Out(m.Bits[2])) + \ 95 | m.ClockIO(has_enable=True) 96 | 97 | reg = m.Register(m.Bits[2], has_enable=True)(name="conf_reg") 98 | io.Q @= reg(io.D, CE=io.CE) 99 | 100 | 101 | class SimpleALU(m.Circuit): 102 | io = m.IO(a=m.In(m.UInt[16]), 103 | b=m.In(m.UInt[16]), 104 | c=m.Out(m.UInt[16]), 105 | config_data=m.In(m.Bits[2]), 106 | config_en=m.In(m.Enable), 107 | ) + m.ClockIO() 108 | 109 | opcode = ConfigReg(name="config_reg")(io.config_data, CE=io.config_en) 110 | io.c @= m.mux( 111 | # udiv not implemented 112 | # [io.a + io.b, io.a - io.b, io.a * io.b, io.a / io.b], opcode) 113 | # use arbitrary fourth op 114 | [io.a + io.b, io.a - io.b, io.a * io.b, io.b - io.a], opcode) 115 | 116 | 117 | class AndCircuit(m.Circuit): 118 | io = m.IO(I0=m.In(m.Bit), I1=m.In(m.Bit), O=m.Out(m.Bit)) 119 | 120 | io.O @= io.I0 & io.I1 121 | -------------------------------------------------------------------------------- /tests/sb_dff_sim.v: -------------------------------------------------------------------------------- 1 | // From https://raw.githubusercontent.com/YosysHQ/yosys/master/techlibs/ice40/cells_sim.v 2 | `define SB_DFF_REG reg Q = 0 3 | 4 | module SB_DFF (output `SB_DFF_REG, input C, D); 5 | always @(posedge C) 6 | Q <= D; 7 | endmodule 8 | -------------------------------------------------------------------------------- /tests/simple_alu.v: -------------------------------------------------------------------------------- 1 | module ConfigReg(input [1:0] D, output reg [1:0] Q /*verilator public*/, input CLK, input EN); 2 | always @(posedge CLK) begin 3 | if (EN) Q <= D; 4 | end 5 | endmodule 6 | 7 | module SimpleALU(input [15:0] a, input [15:0] b, output reg [15:0] c, input [1:0] config_data, input config_en, input CLK); 8 | 9 | wire [1:0] opcode/*verilator public*/; 10 | ConfigReg config_reg(config_data, opcode, CLK, config_en); 11 | 12 | 13 | always @(*) begin 14 | case (opcode) 15 | 0: c = a + b; 16 | 1: c = a - b; 17 | 2: c = a * b; 18 | 3: c = a / b; 19 | endcase 20 | end 21 | endmodule 22 | -------------------------------------------------------------------------------- /tests/spice/my_init_cond.sp: -------------------------------------------------------------------------------- 1 | * Initial conditions 2 | 3 | .subckt my_init_cond_2 vc 4 | * storage nodes 5 | Ca vc_x 0 1p 6 | * wiring 7 | Rb vc_x vc 1 8 | .ends 9 | 10 | .subckt my_init_cond va vb vc 11 | * storage nodes 12 | Cc va 0 1p 13 | Cd vb_x 0 1p 14 | Xe vc_x my_init_cond_2 15 | * wiring 16 | Rf vb_x vb 1 17 | Rg vc_x vc 1 18 | .ends 19 | -------------------------------------------------------------------------------- /tests/spice/myblend.sp: -------------------------------------------------------------------------------- 1 | * Circuit that produces a weighted average of "a" and "b" 2 | * Output is (1.2*b + 3.4*a)/(1.2 + 3.4) 3 | 4 | .subckt myblend a b c 5 | 6 | R0 a c 1.2e3 7 | R1 b c 3.4e3 8 | 9 | .ends 10 | -------------------------------------------------------------------------------- /tests/spice/mybuf.sp: -------------------------------------------------------------------------------- 1 | * NMOS/PMOS models from 2 | * https://people.rit.edu/lffeee/SPICE_Examples.pdf 3 | 4 | .model EENMOS NMOS (VTO=0.4 KP=432E-6 GAMMA=0.2 PHI=.88) 5 | .model EEPMOS PMOS (VTO=-0.4 KP=122E-6 GAMMA=0.2 PHI=.88) 6 | 7 | .subckt mybuf in_ out vdd vss 8 | 9 | * first inverter 10 | MP0 mid in_ vdd vdd EEPMOS w=0.7u l=0.1u 11 | MN0 mid in_ vss vss EENMOS w=0.4u l=0.1u 12 | 13 | * second inverter 14 | MP1 out mid vdd vdd EEPMOS w=0.7u l=0.1u 15 | MN1 out mid vss vss EENMOS w=0.4u l=0.1u 16 | 17 | .ends 18 | -------------------------------------------------------------------------------- /tests/spice/mybus.sp: -------------------------------------------------------------------------------- 1 | * NMOS/PMOS models from 2 | * https://people.rit.edu/lffeee/SPICE_Examples.pdf 3 | 4 | .model EENMOS NMOS (VTO=0.4 KP=432E-6 GAMMA=0.2 PHI=.88) 5 | .model EEPMOS PMOS (VTO=-0.4 KP=122E-6 GAMMA=0.2 PHI=.88) 6 | 7 | .subckt mybus a<1> b<2> a<0> b<1> b<0> vdd vss 8 | 9 | * inverter at bit #0 10 | MP0 b<0> a<0> vdd vdd EEPMOS w=0.7u l=0.1u 11 | MN0 b<0> a<0> vss vss EENMOS w=0.4u l=0.1u 12 | 13 | * buffer at bit #1 14 | R0 b<1> a<1> 0.1 15 | 16 | * constant at bit #2 17 | R1 b<2> vdd 0.1 18 | 19 | .ends 20 | -------------------------------------------------------------------------------- /tests/spice/myinv.sp: -------------------------------------------------------------------------------- 1 | * NMOS/PMOS models from 2 | * https://people.rit.edu/lffeee/SPICE_Examples.pdf 3 | 4 | .model EENMOS NMOS (VTO=0.4 KP=432E-6 GAMMA=0.2 PHI=.88) 5 | .model EEPMOS PMOS (VTO=-0.4 KP=122E-6 GAMMA=0.2 PHI=.88) 6 | 7 | .subckt myinv in_ out vdd vss 8 | 9 | MP0 out in_ vdd vdd EEPMOS w=0.7u l=0.1u 10 | MN0 out in_ vss vss EENMOS w=0.4u l=0.1u 11 | 12 | .ends 13 | -------------------------------------------------------------------------------- /tests/spice/mynand.sp: -------------------------------------------------------------------------------- 1 | * NMOS/PMOS models from 2 | * https://people.rit.edu/lffeee/SPICE_Examples.pdf 3 | 4 | .model EENMOS NMOS (VTO=0.4 KP=432E-6 GAMMA=0.2 PHI=.88) 5 | .model EEPMOS PMOS (VTO=-0.4 KP=122E-6 GAMMA=0.2 PHI=.88) 6 | 7 | .subckt mynand a b out vdd vss 8 | 9 | MP0 out a vdd vdd EEPMOS w=0.7u l=0.1u 10 | MP1 out b vdd vdd EEPMOS w=0.7u l=0.1u 11 | MN0 mid a vss vss EENMOS w=0.4u l=0.1u 12 | MN1 out b mid vss EENMOS w=0.4u l=0.1u 13 | 14 | .ends 15 | -------------------------------------------------------------------------------- /tests/spice/mynor.sp: -------------------------------------------------------------------------------- 1 | * NMOS/PMOS models from 2 | * https://people.rit.edu/lffeee/SPICE_Examples.pdf 3 | 4 | .model EENMOS NMOS (VTO=0.4 KP=432E-6 GAMMA=0.2 PHI=.88) 5 | .model EEPMOS PMOS (VTO=-0.4 KP=122E-6 GAMMA=0.2 PHI=.88) 6 | 7 | .subckt mynor a b out vdd vss 8 | 9 | MP0 mid a vdd vdd EEPMOS w=0.7u l=0.1u 10 | MP1 out b mid vdd EEPMOS w=0.7u l=0.1u 11 | MN0 out a vss vss EENMOS w=0.4u l=0.1u 12 | MN1 out b vss vss EENMOS w=0.4u l=0.1u 13 | 14 | .ends 15 | -------------------------------------------------------------------------------- /tests/spice/mysram.sp: -------------------------------------------------------------------------------- 1 | * NMOS/PMOS models from 2 | * https://people.rit.edu/lffeee/SPICE_Examples.pdf 3 | 4 | .model EENMOS NMOS (VTO=0.4 KP=432E-6 GAMMA=0.2 PHI=.88) 5 | .model EEPMOS PMOS (VTO=-0.4 KP=122E-6 GAMMA=0.2 PHI=.88) 6 | 7 | .subckt mysram lbl lblb vdd vss wl 8 | 9 | * inverter with lblb output 10 | MP0 lblb_x lbl_x vdd vdd EEPMOS w=0.7u l=0.1u 11 | MN0 lblb_x lbl_x vss vss EENMOS w=0.4u l=0.1u 12 | 13 | * inverter with lbl output 14 | MP1 lbl_x lblb_x vdd vdd EEPMOS w=0.7u l=0.1u 15 | MN1 lbl_x lblb_x vss vss EENMOS w=0.4u l=0.1u 16 | 17 | * access switches 18 | MN2 lbl wl lbl_x vss EENMOS w=1.2u l=0.1u 19 | MN3 lblb wl lblb_x vss EENMOS w=1.2u l=0.1u 20 | 21 | .ends 22 | -------------------------------------------------------------------------------- /tests/test_action.py: -------------------------------------------------------------------------------- 1 | from fault import Tester 2 | from fault.actions import Poke, Expect, Eval, Step, Print, Peek, FileOpen, \ 3 | FileRead, FileWrite, FileClose, Loop 4 | from fault.file import File 5 | from .common import TestBasicClkCircuit 6 | 7 | 8 | def test_action_strs(): 9 | circ = TestBasicClkCircuit 10 | assert str(Poke(circ.I, 1)) == 'Poke(BasicClkCircuit.I, 1)' 11 | assert str(Expect(circ.O, 1)) == 'Expect(BasicClkCircuit.O, 1)' 12 | assert str(Eval()) == 'Eval()' 13 | assert str(Step(circ.CLK, 1)) == 'Step(BasicClkCircuit.CLK, steps=1)' 14 | assert str(Print("%08x", circ.O)) == 'Print("%08x", BasicClkCircuit.O)' 15 | assert str(Peek(circ.O)) == 'Peek(BasicClkCircuit.O)' 16 | index = f"__fault_loop_var_action_0" 17 | loop_body = [Peek(circ.O), Poke(circ.I, 1)] 18 | assert str(Loop(12, index, loop_body)) == \ 19 | f'Loop(12, {index}, ' \ 20 | f'[Peek(BasicClkCircuit.O), Poke(BasicClkCircuit.I, 1)], up)' 21 | file = File("my_file", Tester(circ), "r", 1, "little") 22 | assert str(FileOpen(file)) == 'FileOpen(File<"my_file">)' 23 | assert str(FileRead(file)) == 'FileRead(File<"my_file">)' 24 | assert str(FileWrite(file, 3)) == 'FileWrite(File<"my_file">, 3)' 25 | assert str(FileClose(file)) == 'FileClose(File<"my_file">)' 26 | -------------------------------------------------------------------------------- /tests/test_bidir.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import magma as m 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | # Vivado doesn't support the "tran" keyword, so it must be excluded 9 | pytest_sim_params(metafunc, 'system-verilog', exclude='vivado') 10 | 11 | 12 | def test_bidir(target, simulator): 13 | # declare an external circuit that shorts together its two outputs 14 | class bidir(m.Circuit): 15 | io = m.IO( 16 | a=m.InOut(m.Bit), 17 | b=m.InOut(m.Bit) 18 | ) 19 | 20 | # instantiate the tester 21 | tester = fault.Tester(bidir, poke_delay_default=0) 22 | 23 | # define common pattern for running all cases 24 | def run_case(a_in, b_in, a_out, b_out): 25 | tester.poke(bidir.a, a_in) 26 | tester.poke(bidir.b, b_in) 27 | tester.delay(10e-9) 28 | tester.expect(bidir.a, a_out) 29 | tester.expect(bidir.b, b_out) 30 | 31 | # walk through all of the cases that produce a 0 or 1 output 32 | run_case(1, 1, 1, 1) 33 | run_case(0, 0, 0, 0) 34 | run_case(1, fault.HiZ, 1, 1) 35 | run_case(0, fault.HiZ, 0, 0) 36 | run_case(fault.HiZ, 1, 1, 1) 37 | run_case(fault.HiZ, 0, 0, 0) 38 | 39 | # run the test 40 | tester.compile_and_run( 41 | target=target, 42 | simulator=simulator, 43 | ext_libs=[Path('tests/verilog/bidir.v').resolve()], 44 | ext_model_file=True, 45 | tmp_dir=True 46 | ) 47 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import os 3 | import fault.config 4 | from .common import TestTupleCircuit 5 | 6 | 7 | def test_config_test_dir(): 8 | file_dir = os.path.dirname(__file__) 9 | harness_file = os.path.join(file_dir, "build/TupleCircuit_driver.cpp") 10 | # Remove harness if it exists to ensure that it's recreated properly 11 | if os.path.isfile(harness_file): 12 | os.remove(harness_file) 13 | fault.config.set_test_dir('callee_file_dir') 14 | circ = TestTupleCircuit 15 | tester = fault.Tester(circ) 16 | tester.circuit.I.a = 5 17 | tester.circuit.I.b = 11 18 | tester.eval() 19 | tester.circuit.O.a.expect(5) 20 | tester.circuit.O.b.expect(11) 21 | tester.compile_and_run("verilator", directory="build", 22 | flags=['-Wno-fatal']) 23 | fault.config.set_test_dir('normal') 24 | assert os.path.isfile(harness_file), \ 25 | "Verilator harness not created relative to current file" 26 | -------------------------------------------------------------------------------- /tests/test_constrained_random.py: -------------------------------------------------------------------------------- 1 | import random 2 | from fault.random import ConstrainedRandomGenerator 3 | 4 | N = 8 5 | WIDTH = 8 6 | 7 | 8 | def test_constrained_random(): 9 | random.seed(0) 10 | v = dict(x=WIDTH, y=WIDTH, z=WIDTH) 11 | 12 | def pred(x, y, z): 13 | return (((x + y) ^ z) >> 1) == WIDTH 14 | 15 | gen = ConstrainedRandomGenerator() 16 | 17 | models = gen(v, pred, N) 18 | assert len(models) >= N 19 | 20 | for m in models: 21 | assert pred(**m) 22 | -------------------------------------------------------------------------------- /tests/test_coverage.py: -------------------------------------------------------------------------------- 1 | from fault import Tester 2 | from .common import TestBasicCircuit 3 | import tempfile 4 | import os 5 | import pytest 6 | import shutil 7 | 8 | 9 | def test_verilator_coverage(): 10 | circ = TestBasicCircuit 11 | tester = Tester(circ) 12 | # dummy input and outputs to provide 13 | # input for the coverage 14 | tester.zero_inputs() 15 | tester.poke(circ.I, 1) 16 | tester.eval() 17 | tester.expect(circ.O, 1) 18 | 19 | with tempfile.TemporaryDirectory() as temp: 20 | tester.compile_and_run( 21 | target="verilator", 22 | coverage=True, 23 | directory=temp 24 | ) 25 | 26 | print(os.listdir(os.path.join(temp, "logs"))) 27 | # make sure it generates a non empty file 28 | cov_file = os.path.join(temp, "logs", "coverage.dat") 29 | assert os.path.isfile(cov_file) 30 | assert os.stat(cov_file).st_size > 0 31 | 32 | 33 | def test_ncsim_coverage(): 34 | irun = shutil.which("irun") 35 | if irun is None: 36 | pytest.skip("ncsim not found") 37 | circ = TestBasicCircuit 38 | tester = Tester(circ) 39 | # dummy input and outputs to provide 40 | # input for the coverage 41 | tester.zero_inputs() 42 | tester.poke(circ.I, 1) 43 | tester.eval() 44 | tester.expect(circ.O, 1) 45 | 46 | with tempfile.TemporaryDirectory() as temp: 47 | tester.compile_and_run( 48 | target="system-verilog", 49 | simulator="ncsim", 50 | coverage=True, 51 | directory=temp 52 | ) 53 | 54 | # make sure it generates a non empty file 55 | cov_dir = os.path.join(temp, "cov_work") 56 | assert os.path.isdir(cov_dir) 57 | assert len(os.listdir(cov_dir)) > 0 58 | 59 | 60 | if __name__ == "__main__": 61 | test_ncsim_coverage() 62 | -------------------------------------------------------------------------------- /tests/test_def_vlog.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import magma as m 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog', 'verilator') 9 | 10 | 11 | def test_def_vlog(target, simulator, n_bits=8, b_val=42): 12 | # declare circuit 13 | class defadd(m.Circuit): 14 | io = m.IO( 15 | a_val=m.In(m.Bits[n_bits]), 16 | c_val=m.Out(m.Bits[n_bits]) 17 | ) 18 | 19 | # instantiate tester 20 | tester = fault.Tester(defadd) 21 | 22 | # define test 23 | tester.poke(defadd.a_val, 12) 24 | tester.eval() 25 | tester.expect(defadd.c_val, 54) 26 | tester.poke(defadd.a_val, 34) 27 | tester.eval() 28 | tester.expect(defadd.c_val, 76) 29 | 30 | # define target-specific kwargs 31 | kwargs = {} 32 | module_file = Path('tests/verilog/defadd.sv').resolve() 33 | if target == 'verilator': 34 | kwargs['ext_model_file'] = module_file 35 | elif target == 'system-verilog': 36 | kwargs['simulator'] = simulator 37 | kwargs['include_verilog_libraries'] = [module_file] 38 | kwargs['ext_model_file'] = True 39 | 40 | # run simulation 41 | tester.compile_and_run( 42 | target=target, 43 | defines={'N_BITS': n_bits, 'B_VAL': b_val}, 44 | tmp_dir=True, 45 | **kwargs 46 | ) 47 | -------------------------------------------------------------------------------- /tests/test_env_mod.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import magma as m 3 | from .common import pytest_sim_params 4 | 5 | 6 | def pytest_generate_tests(metafunc): 7 | pytest_sim_params(metafunc, 'system-verilog') 8 | 9 | 10 | def test_env_mod(target, simulator): 11 | class myinv(m.Circuit): 12 | io = m.IO( 13 | a=m.In(m.Bit), 14 | y=m.Out(m.Bit) 15 | ) 16 | io.y @= ~io.a 17 | 18 | tester = fault.InvTester(myinv, in_='a', out='y') 19 | tester.compile_and_run( 20 | target=target, 21 | simulator=simulator, 22 | tmp_dir=True 23 | ) 24 | -------------------------------------------------------------------------------- /tests/test_error_handling.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import pytest 3 | import magma as m 4 | from pathlib import Path 5 | from .common import pytest_sim_params 6 | 7 | 8 | def pytest_generate_tests(metafunc): 9 | pytest_sim_params(metafunc, 'system-verilog') 10 | 11 | 12 | @pytest.mark.xfail(strict=True) 13 | def test_error_task(target, simulator): 14 | class error_task(m.Circuit): 15 | io = m.IO() 16 | 17 | tester = fault.Tester(error_task) 18 | tester.compile_and_run( 19 | target=target, 20 | simulator=simulator, 21 | ext_srcs=[Path('tests/verilog/error_task.sv').resolve()], 22 | ext_test_bench=True, 23 | tmp_dir=True 24 | ) 25 | 26 | 27 | @pytest.mark.xfail(strict=True) 28 | def test_fatal_task(target, simulator): 29 | class fatal_task(m.Circuit): 30 | io = m.IO() 31 | 32 | tester = fault.Tester(fatal_task) 33 | tester.compile_and_run( 34 | target=target, 35 | simulator=simulator, 36 | ext_srcs=[Path('tests/verilog/fatal_task.sv').resolve()], 37 | ext_test_bench=True, 38 | tmp_dir=True 39 | ) 40 | -------------------------------------------------------------------------------- /tests/test_ext_tb.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import magma as m 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog') 9 | 10 | 11 | def test_ext_tb(target, simulator): 12 | class mytb(m.Circuit): 13 | io = m.IO() 14 | 15 | tester = fault.Tester(mytb) 16 | tester.compile_and_run( 17 | target=target, 18 | simulator=simulator, 19 | ext_srcs=[Path('tests/verilog/mytb.sv').resolve()], 20 | ext_test_bench=True, 21 | tmp_dir=True 22 | ) 23 | -------------------------------------------------------------------------------- /tests/test_ext_vlog.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import fault 3 | import magma as m 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog') 9 | 10 | 11 | def test_ext_vlog(target, simulator): 12 | # declare circuit 13 | class myinv(m.Circuit): 14 | io = m.IO( 15 | in_=m.In(m.Bit), 16 | out=m.Out(m.Bit) 17 | ) 18 | 19 | # define test 20 | tester = fault.InvTester(myinv) 21 | 22 | # run the test 23 | tester.compile_and_run( 24 | target=target, 25 | simulator=simulator, 26 | ext_libs=[Path('tests/verilog/myinv.v').resolve()], 27 | ext_model_file=True, 28 | tmp_dir=True 29 | ) 30 | -------------------------------------------------------------------------------- /tests/test_fork.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import fault 3 | import magma as m 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog') 9 | 10 | 11 | def test_fork_join(target, simulator): 12 | class dut(m.Circuit): 13 | # add dummy clock 14 | io = m.IO(I=m.In(m.Bits[4]), O=m.Out(m.Bits[4]), clk=m.In(m.Clock)) 15 | io.O @= io.I 16 | 17 | tester = fault.Tester(dut) 18 | proc1 = tester.fork("proc1") 19 | proc2 = tester.fork("proc2") 20 | 21 | for i, proc in enumerate([proc1, proc2]): 22 | proc.poke(tester.circuit.I[1 - i], 1) 23 | proc.eval() 24 | proc.wait_until_high(tester.circuit.I[i]) 25 | proc.poke(tester.circuit.I[i + 2], 1) 26 | 27 | tester.join(proc1, proc2) 28 | tester.eval() 29 | tester.expect(tester.circuit.O, 0xF) 30 | 31 | tester.compile_and_run( 32 | target=target, 33 | simulator=simulator, 34 | tmp_dir=False 35 | ) 36 | -------------------------------------------------------------------------------- /tests/test_functional_tester.py: -------------------------------------------------------------------------------- 1 | import fault 2 | from hwtypes import BitVector 3 | from fault.functional_tester import FunctionalTester 4 | import magma as m 5 | import tempfile 6 | import pytest 7 | 8 | 9 | @pytest.mark.skip("Blocked by https://github.com/rdaly525/coreir/issues/627") 10 | def test_configuration(): 11 | class ConfigurationTester(FunctionalTester): 12 | def configure(self, addr, data): 13 | self.poke(self.clock, 0) 14 | self.poke(self.circuit.config_addr, addr) 15 | self.poke(self.circuit.config_data, data) 16 | self.poke(self.circuit.config_en, 1) 17 | self.step() 18 | self.functional_model.configure(addr, data) 19 | self.eval() 20 | 21 | class Configurable(m.Circuit): 22 | io = m.IO(config_addr=m.In(m.Bits[32]), config_data=m.In(m.Bits[32]), 23 | config_en=m.In(m.Enable), O=m.Out(m.Bits[32]) 24 | ) + m.ClockIO() 25 | 26 | reg = m.Register(m.Bits[32], has_enable=True)() 27 | 28 | reg(io.config_data, 29 | CE=(io.config_addr == m.bits(1, 32)) & m.bit(io.config_en)) 30 | m.wire(reg.O, io.O) 31 | 32 | class ConfigurableModel: 33 | def __init__(self): 34 | self.O = fault.UnknownValue 35 | 36 | def configure(self, addr, data): 37 | if addr == BitVector(1, 32): 38 | self.O = data 39 | 40 | model = ConfigurableModel() 41 | tester = ConfigurationTester(Configurable, Configurable.CLK, 42 | model) 43 | model.O = fault.AnyValue 44 | tester.configure(0, 12) 45 | tester.configure(1, 32) 46 | tester.configure(0, 23) 47 | tester.configure(1, 41) 48 | with tempfile.TemporaryDirectory() as tmp_dir: 49 | m.compile(f"{tmp_dir}/Configurable", Configurable, 50 | output="coreir-verilog") 51 | tester.compile_and_run(directory=tmp_dir, target="verilator", 52 | flags=["-Wno-fatal"], skip_compile=True, 53 | circuit_name="Configurable") 54 | -------------------------------------------------------------------------------- /tests/test_get_value_analog.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'verilog-ams', 'spice', 'system-verilog') 9 | 10 | 11 | def test_get_value_analog(target, simulator): 12 | # declare circuit 13 | class myblend(m.Circuit): 14 | io = m.IO( 15 | a=fault.RealIn, 16 | b=fault.RealIn, 17 | c=fault.RealOut 18 | ) 19 | 20 | # wrap if needed 21 | if target == 'verilog-ams': 22 | dut = fault.VAMSWrap(myblend) 23 | else: 24 | dut = myblend 25 | 26 | # create the tester 27 | tester = fault.Tester(dut) 28 | 29 | # define the test 30 | stim = [(5.6, 7.8), (-6.5, -8.7)] 31 | output = [] 32 | for a, b in stim: 33 | tester.poke(dut.a, a) 34 | tester.poke(dut.b, b) 35 | tester.delay(1e-6) 36 | output.append(tester.get_value(dut.c)) 37 | 38 | # set options 39 | kwargs = dict( 40 | target=target, 41 | simulator=simulator, 42 | tmp_dir=True 43 | ) 44 | verilog_model = Path('tests/verilog/myblend.sv').resolve() 45 | spice_model = Path('tests/spice/myblend.sp').resolve() 46 | if target == 'verilog-ams': 47 | kwargs['model_paths'] = [spice_model] 48 | kwargs['use_spice'] = ['myblend'] 49 | elif target == 'spice': 50 | kwargs['model_paths'] = [spice_model] 51 | elif target == 'system-verilog': 52 | kwargs['ext_libs'] = [verilog_model] 53 | kwargs['ext_model_file'] = True 54 | 55 | # run the simulation 56 | tester.compile_and_run(**kwargs) 57 | 58 | # check the results using Python assertions 59 | def model(a, b): 60 | return (1.2 * b + 3.4 * a) / (1.2 + 3.4) 61 | for (a, b), c in zip(stim, output): 62 | lb = model(a, b) - 0.01 63 | ub = model(a, b) + 0.01 64 | assert lb <= c.value <= ub 65 | -------------------------------------------------------------------------------- /tests/test_get_value_digital.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import fault 3 | import magma as m 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog', 'verilator') 9 | 10 | 11 | class MyAdder(m.Circuit): 12 | io = m.IO(a=m.In(m.UInt[4]), 13 | b=m.Out(m.UInt[4])) 14 | 15 | io.b @= io.a + 1 16 | 17 | 18 | def test_get_value_digital(target, simulator): 19 | # define test 20 | tester = fault.Tester(MyAdder) 21 | 22 | # provide stimulus 23 | stim = list(range(16)) 24 | output = [] 25 | for a in stim: 26 | tester.poke(MyAdder.a, a) 27 | tester.eval() 28 | output.append(tester.get_value(MyAdder.b)) 29 | 30 | # run the test 31 | kwargs = dict( 32 | target=target, 33 | tmp_dir=True 34 | ) 35 | if target == 'system-verilog': 36 | kwargs['simulator'] = simulator 37 | elif target == 'verilator': 38 | kwargs['flags'] = ['-Wno-fatal'] 39 | tester.compile_and_run(**kwargs) 40 | 41 | # check the results 42 | def model(a): 43 | return (a + 1) % 16 44 | for a, b_meas in zip(stim, output): 45 | b_expct = model(a) 46 | assert b_meas.value == b_expct 47 | -------------------------------------------------------------------------------- /tests/test_hi_z.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import magma as m 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | # Vivado doesn't support the "tran" keyword, so it must be excluded 9 | pytest_sim_params(metafunc, 'system-verilog', exclude='vivado') 10 | 11 | 12 | def test_hi_z(target, simulator): 13 | # declare circuit 14 | class hizmod(m.Circuit): 15 | io = m.IO( 16 | a=m.In(m.Bit), 17 | b=m.In(m.Bit), 18 | c=m.Out(m.Bit) 19 | ) 20 | 21 | # instantiate the tester 22 | tester = fault.Tester(hizmod, poke_delay_default=0) 23 | 24 | # define common pattern for running all cases 25 | def run_case(a, b, c): 26 | tester.poke(hizmod.a, a) 27 | tester.poke(hizmod.b, b) 28 | tester.delay(10e-9) 29 | tester.expect(hizmod.c, c) 30 | 31 | # walk through all of the cases that produce a 0 or 1 output 32 | run_case(1, 1, 1) 33 | run_case(0, 0, 0) 34 | run_case(1, fault.HiZ, 1) 35 | run_case(0, fault.HiZ, 0) 36 | run_case(fault.HiZ, 1, 1) 37 | run_case(fault.HiZ, 0, 0) 38 | 39 | # run the test 40 | tester.compile_and_run( 41 | target=target, 42 | simulator=simulator, 43 | ext_libs=[Path('tests/verilog/hizmod.v').resolve()], 44 | ext_model_file=True, 45 | tmp_dir=True 46 | ) 47 | -------------------------------------------------------------------------------- /tests/test_inc_dir.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import magma as m 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog') 9 | 10 | 11 | def test_inc_dir(target, simulator): 12 | # declare circuit 13 | class mybuf_inc_test(m.Circuit): 14 | io = m.IO( 15 | in_=m.In(m.Bit), 16 | out=m.Out(m.Bit) 17 | ) 18 | 19 | # define the test 20 | tester = fault.BufTester(mybuf_inc_test) 21 | 22 | # run the test 23 | tester.compile_and_run( 24 | target=target, 25 | simulator=simulator, 26 | ext_libs=[Path('tests/verilog/mybuf_inc_test.v').resolve()], 27 | inc_dirs=[Path('tests/verilog').resolve()], 28 | ext_model_file=True, 29 | tmp_dir=True 30 | ) 31 | -------------------------------------------------------------------------------- /tests/test_include_verilog.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import tempfile 3 | import fault 4 | import magma as m 5 | import os 6 | import shutil 7 | 8 | 9 | def pytest_generate_tests(metafunc): 10 | if "target" in metafunc.fixturenames: 11 | targets = [("verilator", None)] 12 | if shutil.which("irun"): 13 | targets.append(("system-verilog", "ncsim")) 14 | if shutil.which("vcs"): 15 | targets.append(("system-verilog", "vcs")) 16 | metafunc.parametrize("target,simulator", targets) 17 | 18 | 19 | def test_include_verilog(target, simulator): 20 | # define flip-flop (external implementation) 21 | class SB_DFF(m.Circuit): 22 | io = m.IO( 23 | D=m.In(m.Bit), 24 | Q=m.Out(m.Bit), 25 | C=m.In(m.Clock) 26 | ) 27 | 28 | # define main circuit that instantiates external flip-flop 29 | class main(m.Circuit): 30 | io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit)) + m.ClockIO() 31 | ff = SB_DFF() 32 | ff.D @= io.I 33 | io.O @= ff.Q 34 | 35 | # define the test 36 | tester = fault.Tester(main, main.CLK) 37 | tester.poke(main.CLK, 0) 38 | tester.poke(main.I, 1) 39 | tester.eval() 40 | tester.expect(main.O, 0) 41 | tester.step(2) 42 | tester.expect(main.O, 1) 43 | 44 | # define location of flip-flop implementation 45 | sb_dff_filename = pathlib.Path("tests/sb_dff_sim.v").resolve() 46 | 47 | kwargs = {} 48 | if simulator is not None: 49 | kwargs["simulator"] = simulator 50 | 51 | with tempfile.TemporaryDirectory(dir=".") as tmp_dir: 52 | tester.compile_and_run(target=target, directory=tmp_dir, 53 | include_verilog_libraries=[sb_dff_filename], 54 | **kwargs) 55 | 56 | if target in ["verilator"]: 57 | # Should work by including the tests/ directory which contains the 58 | # verilog file SB_DFF.v 59 | dir_path = os.path.dirname(os.path.realpath(__file__)) 60 | with tempfile.TemporaryDirectory(dir=".") as tmp_dir: 61 | tester.compile_and_run(target=target, directory=tmp_dir, 62 | include_directories=[dir_path], **kwargs) 63 | -------------------------------------------------------------------------------- /tests/test_init_cond.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'verilog-ams', 'spice') 9 | 10 | 11 | def test_init_cond(target, simulator, va=1.234, vb=2.345, vc=3.456, 12 | abs_tol=1e-3): 13 | # declare circuit 14 | class mycirc(m.Circuit): 15 | name = 'my_init_cond' 16 | io = m.IO( 17 | va=fault.RealOut, 18 | vb=fault.RealOut, 19 | vc=fault.RealOut 20 | ) 21 | 22 | # wrap if needed 23 | if target == 'verilog-ams': 24 | dut = fault.VAMSWrap(mycirc) 25 | else: 26 | dut = mycirc 27 | 28 | # define the test 29 | tester = fault.Tester(dut) 30 | tester.delay(10e-9) 31 | tester.expect(dut.va, va, abs_tol=abs_tol) 32 | tester.expect(dut.vb, vb, abs_tol=abs_tol) 33 | tester.expect(dut.vc, vc, abs_tol=abs_tol) 34 | 35 | # set options 36 | ic = { 37 | dut.va: va, 38 | tester.internal('vb_x'): vb, 39 | tester.internal('Xe', 'vc_x'): vc 40 | } 41 | kwargs = dict( 42 | target=target, 43 | simulator=simulator, 44 | model_paths=[Path('tests/spice/my_init_cond.sp').resolve()], 45 | ic=ic, 46 | tmp_dir=True 47 | ) 48 | if target == 'verilog-ams': 49 | kwargs['use_spice'] = ['my_init_cond'] 50 | 51 | # run the simulation 52 | tester.compile_and_run(**kwargs) 53 | -------------------------------------------------------------------------------- /tests/test_inv_tf.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'verilog-ams', 'spice') 9 | 10 | 11 | def test_inv_tf( 12 | target, simulator, n_steps=100, vsup=1.5, vil_rel=0.4, vih_rel=0.6, 13 | vol_rel=0.1, voh_rel=0.9 14 | ): 15 | # declare circuit 16 | class myinv(m.Circuit): 17 | io = m.IO( 18 | in_=fault.RealIn, 19 | out=fault.RealOut, 20 | vdd=fault.RealIn, 21 | vss=fault.RealIn 22 | ) 23 | 24 | # wrap if needed 25 | if target == 'verilog-ams': 26 | dut = fault.VAMSWrap(myinv) 27 | else: 28 | dut = myinv 29 | 30 | # define the test 31 | tester = fault.Tester(dut) 32 | tester.poke(dut.vdd, vsup) 33 | tester.poke(dut.vss, 0) 34 | for k in range(n_steps): 35 | in_ = k * vsup / (n_steps - 1) 36 | tester.poke(dut.in_, in_) 37 | tester.eval() 38 | if in_ <= vil_rel * vsup: 39 | tester.expect(dut.out, vsup, above=voh_rel * vsup) 40 | elif in_ >= vih_rel * vsup: 41 | tester.expect(dut.out, 0, below=vol_rel * vsup) 42 | 43 | # set options 44 | kwargs = dict( 45 | target=target, 46 | simulator=simulator, 47 | model_paths=[Path('tests/spice/myinv.sp').resolve()], 48 | vsup=vsup, 49 | tmp_dir=True 50 | ) 51 | if target == 'verilog-ams': 52 | kwargs['use_spice'] = ['myinv'] 53 | 54 | # run the simulation 55 | tester.compile_and_run(**kwargs) 56 | -------------------------------------------------------------------------------- /tests/test_kratos_debug.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import pytest 3 | import kratos 4 | import multiprocessing 5 | import time 6 | import tempfile 7 | import magma as m 8 | import shutil 9 | import operator 10 | import hwtypes 11 | 12 | 13 | pytest.skip("kratos_runtime archived", allow_module_level=True) 14 | 15 | has_runtime = fault.util.has_kratos_runtime() 16 | 17 | 18 | def mock_debugger(fn): 19 | # run it in a separate process to fake a debugger-simulator interaction 20 | p = multiprocessing.Process(target=fn) 21 | p.start() 22 | # send an CONTINUE request to the runtime to check if it's working 23 | from kratos_runtime import DebuggerMock 24 | mock = DebuggerMock() 25 | time.sleep(1) 26 | mock.connect() 27 | mock.continue_() 28 | mock.wait_till_finish() 29 | p.join() 30 | 31 | 32 | @pytest.mark.skipif((not shutil.which("irun")) and not has_runtime, 33 | reason="irun not available") 34 | def test_load_runtime(): 35 | # define an empty circuit 36 | mod = kratos.Generator("mod") 37 | with tempfile.TemporaryDirectory() as temp: 38 | 39 | def run_test(): 40 | # -g without the db dump 41 | circuit = kratos.util.to_magma(mod, insert_debug_info=True) 42 | tester = fault.Tester(circuit) 43 | tester.compile_and_run(target="system-verilog", 44 | simulator="ncsim", 45 | directory=temp, 46 | magma_output="verilog", 47 | use_kratos=True) 48 | mock_debugger(run_test) 49 | 50 | 51 | @pytest.mark.skipif(not has_runtime, reason="runtime not available") 52 | def test_veriltor_load(): 53 | # define an empty circuit 54 | mod = kratos.Generator("mod") 55 | with tempfile.TemporaryDirectory() as temp: 56 | 57 | def run_test(): 58 | # -g without the db dump 59 | circuit = kratos.util.to_magma(mod, insert_debug_info=True) 60 | tester = fault.Tester(circuit) 61 | tester.compile_and_run(target="verilator", 62 | directory=temp, 63 | magma_output="verilog", 64 | use_kratos=True) 65 | mock_debugger(run_test) 66 | 67 | 68 | # TODO: move the function in magma as a normal library call 69 | def build_kratos_debug_info(circuit, is_top): 70 | inst_to_defn_map = {} 71 | for instance in circuit.instances: 72 | instance_inst_to_defn_map = \ 73 | build_kratos_debug_info(type(instance), is_top=False) 74 | for k, v in instance_inst_to_defn_map.values(): 75 | key = instance.name + "." + k 76 | if is_top: 77 | key = circuit.name + "." + key 78 | inst_to_defn_map[key] = v 79 | inst_name = instance.name 80 | if is_top: 81 | inst_name = circuit.name + "." + instance.name 82 | if instance.kratos is not None: 83 | inst_to_defn_map[inst_name] = instance.kratos 84 | return inst_to_defn_map 85 | 86 | 87 | @pytest.mark.skipif(not has_runtime, reason="runtime not available") 88 | @pytest.mark.parametrize("target", ["verilator", "system-verilog"]) 89 | def test_magma_debug(target): 90 | if not shutil.which("irun"): 91 | pytest.skip("irun not available") 92 | 93 | @m.circuit.combinational_to_verilog(debug=True) 94 | def execute_alu(a: m.UInt[16], b: m.UInt[16], config_: m.Bits[2]) -> \ 95 | m.UInt[16]: 96 | if config_ == m.bits(0, 2): 97 | c = a + b 98 | elif config_ == m.bits(1, 2): 99 | c = a - b 100 | elif config_ == m.bits(2, 2): 101 | c = a * b 102 | else: 103 | c = m.bits(0, 16) 104 | return c 105 | 106 | class SimpleALU(m.Circuit): 107 | io = m.IO(a=m.In(m.UInt[16]), b=m.In(m.UInt[16]), 108 | c=m.Out(m.UInt[16]), config_=m.In(m.Bits[2])) 109 | 110 | io.c <= execute_alu(io.a, io.b, io.config_) 111 | 112 | inst_to_defn_map = build_kratos_debug_info(SimpleALU, is_top=True) 113 | assert "SimpleALU.execute_alu_inst0" in inst_to_defn_map 114 | generators = [] 115 | for instance_name, mod in inst_to_defn_map.items(): 116 | mod.instance_name = instance_name 117 | generators.append(mod) 118 | 119 | tester = fault.Tester(SimpleALU) 120 | ops = [operator.add, operator.sub, operator.mul, operator.floordiv] 121 | for i, op in enumerate(ops): 122 | tester.circuit.config_ = i 123 | tester.circuit.a = a = hwtypes.BitVector.random(16) 124 | tester.circuit.b = b = hwtypes.BitVector.random(16) 125 | tester.eval() 126 | if op == operator.floordiv: 127 | tester.circuit.c.expect(0) 128 | else: 129 | tester.circuit.c.expect(op(a, b)) 130 | 131 | with tempfile.TemporaryDirectory() as temp: 132 | 133 | def run_test(): 134 | kwargs = {"target": target, "directory": temp, 135 | "magma_output": "verilog", 136 | "use_kratos": True} 137 | if target == "system-verilog": 138 | kwargs["simulator"] = "ncsim" 139 | tester.compile_and_run(**kwargs) 140 | mock_debugger(run_test) 141 | -------------------------------------------------------------------------------- /tests/test_logging.py: -------------------------------------------------------------------------------- 1 | import fault.logging 2 | 3 | 4 | def test_logging_smoke(): 5 | fault.logging.info("some info msg") 6 | fault.logging.debug("some debug msg") 7 | fault.logging.warning("some warning msg") 8 | fault.logging.error("some error msg") 9 | -------------------------------------------------------------------------------- /tests/test_logic.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import magma as m 3 | import fault 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog', 'verilog-ams', 'spice') 9 | 10 | 11 | def test_inv(target, simulator): 12 | circuit_name = 'myinv' 13 | ports = dict(in_=m.BitIn, out=m.BitOut) 14 | run_generic(circuit_name=circuit_name, ports=ports, 15 | tester_cls=fault.InvTester, target=target, 16 | simulator=simulator) 17 | 18 | 19 | def test_nand(target, simulator): 20 | circuit_name = 'mynand' 21 | ports = dict(a=m.BitIn, b=m.BitIn, out=m.BitOut) 22 | run_generic(circuit_name=circuit_name, ports=ports, 23 | tester_cls=fault.NandTester, target=target, 24 | simulator=simulator) 25 | 26 | 27 | def test_sram(target, simulator): 28 | circuit_name = 'mysram' 29 | ports = dict(wl=m.BitIn, lbl=m.BitInOut, lblb=m.BitInOut) 30 | run_generic(circuit_name=circuit_name, ports=ports, 31 | tester_cls=fault.SRAMTester, target=target, 32 | simulator=simulator) 33 | 34 | 35 | def run_generic(circuit_name, ports, tester_cls, target, simulator, vsup=1.5): 36 | # add supply pins if needed 37 | if target in ['verilog-ams', 'spice']: 38 | ports = ports.copy() 39 | ports['vdd'] = m.BitIn 40 | ports['vss'] = m.BitIn 41 | 42 | # declare circuit 43 | class dut(m.Circuit): 44 | name = circuit_name 45 | io = m.IO(**ports) 46 | 47 | # define the test content 48 | tester = tester_cls(dut) 49 | 50 | # define run options 51 | kwargs = dict( 52 | target=target, 53 | simulator=simulator, 54 | tmp_dir=True 55 | ) 56 | if target in ['verilog-ams', 'system-verilog']: 57 | kwargs['ext_model_file'] = True 58 | if target in ['verilog-ams', 'spice']: 59 | kwargs['model_paths'] = [Path(f'tests/spice/{circuit_name}.sp').resolve()] # noqa 60 | kwargs['vsup'] = vsup 61 | if target == 'verilog-ams': 62 | kwargs['use_spice'] = [circuit_name] 63 | if target == 'system-verilog': 64 | kwargs['ext_libs'] = [Path(f'tests/verilog/{circuit_name}.v').resolve()] # noqa 65 | 66 | # compile and run 67 | tester.compile_and_run(**kwargs) 68 | -------------------------------------------------------------------------------- /tests/test_magma_opts.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import magma as m 3 | import fault as f 4 | 5 | 6 | def test_verilog_prefix(): 7 | class Foo(m.Circuit): 8 | io = m.IO(I=m.In(m.Bits[4]), O=m.Out(m.Bits[4])) 9 | io.O @= io.I 10 | 11 | tester = f.Tester(Foo) 12 | tester.circuit.I = 4 13 | tester.eval() 14 | tester.circuit.O.expect(4) 15 | 16 | with tempfile.TemporaryDirectory(dir=".") as tempdir: 17 | tester.compile_and_run("verilator", 18 | magma_opts={"verilog_prefix": "bar_"}, 19 | directory=tempdir) 20 | -------------------------------------------------------------------------------- /tests/test_magma_protocol.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | from typing import Optional 3 | import magma as m 4 | import fault 5 | from hwtypes import BitVector 6 | 7 | 8 | def test_foo_type_magma_protocol(): 9 | class FooMeta(m.MagmaProtocolMeta): 10 | def _to_magma_(cls): 11 | return cls.T 12 | 13 | def _qualify_magma_(cls, direction: m.Direction): 14 | return cls[cls.T.qualify(direction)] 15 | 16 | def _flip_magma_(cls): 17 | return cls[cls.T.flip()] 18 | 19 | def _from_magma_value_(cls, val: m.Type): 20 | return cls(val) 21 | 22 | def __getitem__(cls, T): 23 | return type(cls)(f"Foo{T}", (cls, ), {"T": T}) 24 | 25 | class Foo(m.MagmaProtocol, metaclass=FooMeta): 26 | def __init__(self, val: Optional[m.Type] = None): 27 | if val is None: 28 | val = self.T() 29 | self._val = val 30 | 31 | def _get_magma_value_(self): 32 | return self._val 33 | 34 | def non_standard_operation(self): 35 | v0 = self._val << 2 36 | v1 = m.bits(self._val[0], len(self.T)) << 1 37 | return Foo(v0 | v1 | m.bits(self._val[0], len(self.T))) 38 | 39 | class Bar(m.Circuit): 40 | io = m.IO(foo=m.In(Foo[m.Bits[8]]), O=m.Out(m.Bits[8])) 41 | m.wire(io.foo.non_standard_operation(), io.O) 42 | 43 | tester = fault.Tester(Bar) 44 | tester.circuit.foo = Foo(m.Bits[8](0xDE)) 45 | tester.eval() 46 | tester.circuit.O.expect(BitVector[8](0xDE << 2) | 47 | (BitVector[8](0xDE) << 1)[0] | 48 | BitVector[8](0xDE)[0]) 49 | 50 | with tempfile.TemporaryDirectory(dir=".") as _dir: 51 | tester.compile_and_run("verilator", directory=_dir) 52 | -------------------------------------------------------------------------------- /tests/test_magma_simulator_target.py: -------------------------------------------------------------------------------- 1 | from hwtypes import BitVector 2 | from fault import Tester 3 | from fault.actions import Poke, Expect, Eval, Step, Print, Peek 4 | from fault.magma_simulator_target import MagmaSimulatorTarget 5 | from fault.random import random_bv 6 | from .common import (TestBasicCircuit, TestNestedArraysCircuit, 7 | TestBasicClkCircuit, TestPeekCircuit) 8 | 9 | 10 | # NOTE(rsetaluri): The python simulator backend is not tested since it is not 11 | # being actively supported currently. If it is updated, we should add it the 12 | # test fixtures. 13 | def pytest_generate_tests(metafunc): 14 | if 'backend' in metafunc.fixturenames: 15 | metafunc.parametrize("backend", ["coreir"]) 16 | 17 | 18 | def run(circ, actions, clock, backend): 19 | target = MagmaSimulatorTarget(circ, clock, backend=backend) 20 | target.run(actions) 21 | 22 | 23 | def test_magma_simulator_target_basic(backend): 24 | """ 25 | Test basic python simulator workflow with a simple circuit. 26 | """ 27 | circ = TestBasicCircuit 28 | actions = [ 29 | Poke(circ.I, BitVector[1](0)), 30 | Expect(circ.O, BitVector[1](0)), 31 | Eval(), 32 | Poke(circ.I, BitVector[1](1)), 33 | Expect(circ.O, BitVector[1](0)), 34 | Eval(), 35 | Poke(circ.I, BitVector[1](0)), 36 | Expect(circ.O, BitVector[1](1)), 37 | Eval(), 38 | Expect(circ.O, BitVector[1](0)), 39 | ] 40 | run(circ, actions, None, backend) 41 | 42 | 43 | def test_magma_simulator_target_nested_arrays_by_element(backend): 44 | circ = TestNestedArraysCircuit 45 | expected = [random_bv(4) for i in range(3)] 46 | actions = [] 47 | for i, val in enumerate(expected): 48 | actions.append(Poke(circ.I[i], val)) 49 | actions.append(Eval()) 50 | for i, val in enumerate(expected): 51 | actions.append(Expect(circ.O[i], val)) 52 | run(circ, actions, None, backend) 53 | 54 | 55 | def test_magma_simulator_target_nested_arrays_bulk(backend): 56 | circ = TestNestedArraysCircuit 57 | expected = [random_bv(4) for i in range(3)] 58 | actions = [] 59 | actions.append(Poke(circ.I, expected)) 60 | actions.append(Eval()) 61 | actions.append(Expect(circ.O, expected)) 62 | run(circ, actions, None, backend) 63 | 64 | 65 | def test_magma_simulator_target_clock(backend, capfd): 66 | circ = TestBasicClkCircuit 67 | actions = [ 68 | Poke(circ.I, BitVector[1](0)), 69 | Print("%d\n", circ.I), 70 | Expect(circ.O, BitVector[1](0)), 71 | # TODO(rsetaluri): Figure out how to set clock value directly with the 72 | # coreir simulator. Currently it does not allow this. 73 | # Poke(circ.CLK, BitVector[1](0)), 74 | Step(circ.CLK, 1), 75 | Poke(circ.I, BitVector[1](1)), 76 | Eval(), 77 | Print("%d\n", circ.O), 78 | ] 79 | run(circ, actions, circ.CLK, backend) 80 | out, err = capfd.readouterr() 81 | lines = out.splitlines() 82 | print(lines) 83 | 84 | assert lines[-2] == "0", "Print output incorrect" 85 | assert lines[-1] == "1", "Print output incorrect" 86 | 87 | 88 | def test_magma_simulator_target_peek(backend): 89 | circ = TestPeekCircuit 90 | actions = [] 91 | for i in range(3): 92 | x = random_bv(3) 93 | actions.append(Poke(circ.I, x)) 94 | actions.append(Eval()) 95 | actions.append(Expect(circ.O0, Peek(circ.O1))) 96 | run(circ, actions, None, backend) 97 | 98 | 99 | if __name__ == "__main__": 100 | test_magma_simulator_target_basic("coreir") 101 | -------------------------------------------------------------------------------- /tests/test_no_coreir.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | 3 | import hwtypes as ht 4 | import fault as f 5 | import magma as m 6 | 7 | 8 | def test_fault_no_coreir(): 9 | class Foo(m.Circuit): 10 | io = m.IO(I=m.In(m.Bits[16]), O=m.Out(m.Bits[16])) 11 | io.O @= io.I & 0xFF 12 | 13 | tester = f.Tester(Foo) 14 | tester.circuit.I = I = ht.BitVector.random(16) 15 | tester.eval() 16 | tester.circuit.O.expect(I & 0xFF) 17 | 18 | with tempfile.TemporaryDirectory(dir=".") as tempdir: 19 | tester.compile_and_run("verilator", magma_output="mlir-verilog", 20 | directory=tempdir) 21 | -------------------------------------------------------------------------------- /tests/test_param_vlog.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import magma as m 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog', 'verilator') 9 | 10 | 11 | def test_def_vlog(target, simulator, n_bits=8, b_val=76): 12 | # declare circuit 13 | class paramadd(m.Circuit): 14 | io = m.IO( 15 | a_val=m.In(m.Bits[n_bits]), 16 | c_val=m.Out(m.Bits[n_bits]) 17 | ) 18 | 19 | # instantiate tester 20 | tester = fault.Tester(paramadd) 21 | 22 | # define test 23 | tester.poke(paramadd.a_val, 98) 24 | tester.eval() 25 | tester.expect(paramadd.c_val, 174) 26 | tester.poke(paramadd.a_val, 54) 27 | tester.eval() 28 | tester.expect(paramadd.c_val, 130) 29 | 30 | # define target-specific kwargs 31 | kwargs = {} 32 | module_file = Path('tests/verilog/paramadd.sv').resolve() 33 | if target == 'verilator': 34 | kwargs['ext_model_file'] = module_file 35 | elif target == 'system-verilog': 36 | kwargs['simulator'] = simulator 37 | kwargs['include_verilog_libraries'] = [module_file] 38 | kwargs['ext_model_file'] = True 39 | 40 | # run simulation 41 | tester.compile_and_run( 42 | target=target, 43 | parameters={'n_bits': n_bits, 'b_val': b_val}, 44 | tmp_dir=True, 45 | **kwargs 46 | ) 47 | -------------------------------------------------------------------------------- /tests/test_power_domains.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | from hwtypes import BitVector 4 | import pytest 5 | import shutil 6 | 7 | 8 | @pytest.mark.skipif(not shutil.which("irun"), reason="irun not available") 9 | def test_simple_alu_pd(): 10 | type_map = {"CLK": m.In(m.Clock)} 11 | circ = m.define_from_verilog_file("tests/verilog/simple_alu_pd.sv", 12 | type_map=type_map)[0] 13 | tester = fault.PowerTester(circ, circ.CLK) 14 | tester.add_power(circ.VDD_HIGH) 15 | tester.add_ground(circ.VSS) 16 | tester.add_tri(circ.VDD_HIGH_TOP_VIRTUAL) 17 | 18 | tester.circuit.CLK = 0 19 | 20 | # Enable the power switch 21 | tester.circuit.config_addr = 0x00080000 22 | tester.circuit.config_data = 0xFFFFFFF0 23 | tester.circuit.config_en = 1 24 | tester.step(2) 25 | tester.circuit.config_en = 0 26 | 27 | # rest of test... 28 | a, b = BitVector.random(16), BitVector.random(16) 29 | tester.circuit.a = a 30 | tester.circuit.b = b 31 | tester.circuit.c.expect(a + b) 32 | 33 | # Disable the power switch 34 | tester.circuit.config_addr = 0x00080000 35 | tester.circuit.config_data = 0xFFFFFFF0 36 | tester.circuit.config_en = 1 37 | tester.step(2) 38 | tester.circuit.config_en = 0 39 | # Stall global signal should be on when tile is off 40 | tester.circuit.stall_out.expect(1) 41 | # reset signal should be on when tile is off 42 | tester.circuit.reset.expect(1) 43 | 44 | # Enable the power switch 45 | tester.circuit.config_addr = 0x00080000 46 | tester.circuit.config_data = 0xFFFFFFF0 47 | tester.circuit.config_en = 1 48 | tester.step(2) 49 | tester.circuit.config_en = 0 50 | 51 | # rest of test... 52 | a, b = BitVector.random(16), BitVector.random(16) 53 | tester.circuit.a = a 54 | tester.circuit.b = b 55 | tester.circuit.c.expect(a + b) 56 | 57 | try: 58 | tester.compile_and_run(target="system-verilog", simulator="ncsim", 59 | directory="tests/build", skip_compile=True) 60 | except AssertionError: 61 | # Won't run because we don't have concrete DUT or ncsim, but we check 62 | # that the output has the right types for the special ports 63 | with open("tests/build/simple_alu_pd_tb.sv", "r") as f: 64 | for line in f.read().splitlines(): 65 | if "VDD_HIGH_TOP_VIRTUAL;" in line: 66 | assert line.lstrip().rstrip() == \ 67 | "tri VDD_HIGH_TOP_VIRTUAL;" 68 | elif "VDD_HIGH;" in line: 69 | assert line.lstrip().rstrip() == "supply1 VDD_HIGH;" 70 | elif "VSS;" in line: 71 | assert line.lstrip().rstrip() == "supply0 VSS;" 72 | -------------------------------------------------------------------------------- /tests/test_protocol.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | 3 | import magma as m 4 | import fault 5 | 6 | 7 | class MWrapperMeta(m.MagmaProtocolMeta): 8 | def __getitem__(cls, T): 9 | assert cls is MWrapper 10 | return type(cls)(f'MWrapper[{T}]', (cls,), {'_T_': T}) 11 | 12 | def _to_magma_(cls): 13 | return cls._T_ 14 | 15 | def _qualify_magma_(cls, d): 16 | return MWrapper[cls._T_.qualify(d)] 17 | 18 | def _flip_magma_(cls): 19 | return MWrapper[cls._T_.flip()] 20 | 21 | def _from_magma_value_(cls, value): 22 | return cls(value) 23 | 24 | 25 | class MWrapper(m.MagmaProtocol, metaclass=MWrapperMeta): 26 | def __init__(self, val): 27 | if not isinstance(val, type(self)._T_): 28 | raise TypeError() 29 | self._value_ = val 30 | 31 | def _get_magma_value_(self): 32 | return self._value_ 33 | 34 | def apply(self, f): 35 | return f(self._value_) 36 | 37 | 38 | WrappedBits8 = MWrapper[m.UInt[8]] 39 | 40 | 41 | @m.sequential2() 42 | class Foo: 43 | def __call__(self, val: WrappedBits8) -> m.UInt[8]: 44 | return val.apply(lambda x: x + 1) 45 | 46 | 47 | def test_proto(): 48 | tester = fault.Tester(Foo) 49 | tester.circuit.val = 1 50 | tester.eval() 51 | tester.circuit.O.expect(2) 52 | with tempfile.TemporaryDirectory(dir=".") as tempdir: 53 | tester.compile_and_run("verilator", flags=['-Wno-unused'], 54 | directory=tempdir) 55 | -------------------------------------------------------------------------------- /tests/test_pwl_gen.py: -------------------------------------------------------------------------------- 1 | from fault.pwl import pwc_to_pwl 2 | from math import isclose 3 | 4 | 5 | def check_pwl_result(meas, expct): 6 | for k, (a, b) in enumerate(zip(meas, expct)): 7 | assert isclose(a[0], b[0]), f'Time mismatch at index {k}: {a[0]} vs {b[0]}' # noqa 8 | assert isclose(a[1], b[1]), f'Value mismatch at index {k}: {a[1]} vs {b[1]}' # noqa 9 | 10 | 11 | def test_spice_target_pwl(t_tr=0.2e-9, t_stop=20e-9): 12 | def run_test(stim, expct): 13 | meas = pwc_to_pwl(stim, t_stop, t_tr=t_tr) 14 | check_pwl_result(meas=meas, expct=expct) 15 | 16 | run_test([(1e-9, 1.2), (10e-9, 3.4), (15e-9, 5.6)], 17 | [(0, 0), (1e-9, 0), (1.2e-9, 1.2), (10e-9, 1.2), (10.2e-9, 3.4), (15e-9, 3.4), (15.2e-9, 5.6), (20e-9, 5.6)]) # noqa 18 | 19 | run_test([(0, 1.2), (10e-9, 3.4), (15e-9, 5.6)], 20 | [(0, 1.2), (10e-9, 1.2), (10.2e-9, 3.4), (15e-9, 3.4), (15.2e-9, 5.6), (20e-9, 5.6)]) # noqa 21 | -------------------------------------------------------------------------------- /tests/test_ready_valid.py: -------------------------------------------------------------------------------- 1 | from hwtypes import BitVector 2 | import magma as m 3 | import fault as f 4 | from fault.verilator_utils import verilator_version 5 | import pytest 6 | 7 | 8 | class Main(m.Circuit): 9 | io = m.IO(I=m.Consumer(m.ReadyValid[m.UInt[8]]), 10 | O=m.Producer(m.ReadyValid[m.UInt[8]])) + m.ClockIO() 11 | count = m.Register(m.UInt[2])() 12 | count.I @= count.O + 1 13 | enable = io.I.valid & (count.O == 3) & io.O.ready 14 | io.I.ready @= enable 15 | io.O.data @= m.Register(m.UInt[8], has_enable=True)()(io.I.data + 1, 16 | CE=enable) 17 | io.O.valid @= enable 18 | 19 | 20 | def test_basic_ready_valid_sequence(): 21 | if verilator_version() < 4.0: 22 | pytest.skip("Untested with earlier verilator versions") 23 | I = [BitVector.random(8) for _ in range(8)] + [0] 24 | O = [0] + [i + 1 for i in I[:-1]] 25 | f.run_ready_valid_test( 26 | Main, 27 | {"I": I, "O": O}, 28 | "verilator", 29 | compile_and_run_kwargs={'magma_output': 'mlir-verilog', 30 | 'tmp_dir': True, 31 | 'magma_opts': {'flatten_all_tuples': True}} 32 | ) 33 | 34 | 35 | def test_basic_ready_valid_sequence_fail(): 36 | if verilator_version() < 4.0: 37 | pytest.skip("Untested with earlier verilator versions") 38 | I = [BitVector.random(8) for _ in range(8)] + [0] 39 | O = [0] + [i - 1 for i in I[:-1]] 40 | with pytest.raises(AssertionError): 41 | f.run_ready_valid_test( 42 | Main, 43 | {"I": I, "O": O}, 44 | "verilator", 45 | compile_and_run_kwargs={'magma_output': 'mlir-verilog', 46 | 'tmp_dir': True, 47 | 'magma_opts': {'flatten_all_tuples': True}} 48 | ) 49 | 50 | 51 | class Main2(m.Circuit): 52 | io = m.IO(I=m.Consumer(m.ReadyValid[m.UInt[8]]), 53 | O=m.Producer(m.ReadyValid[m.UInt[8]]), 54 | inc=m.In(m.UInt[8]), 55 | ) + m.ClockIO() 56 | count = m.Register(m.UInt[2])() 57 | count.I @= count.O + 1 58 | enable = io.I.valid & (count.O == 3) & io.O.ready 59 | io.I.ready @= enable 60 | io.O.data @= m.Register(m.UInt[8], has_enable=True)()(io.I.data + io.inc, 61 | CE=enable) 62 | io.O.valid @= enable 63 | 64 | 65 | def test_lifted_ready_valid_sequence_simple(): 66 | if verilator_version() < 4.0: 67 | pytest.skip("Untested with earlier verilator versions") 68 | I = [BitVector.random(8) for _ in range(8)] + [0] 69 | O = [0] + [i + 2 for i in I[:-1]] 70 | tester = f.ReadyValidTester(Main2, {"I": I, "O": O}) 71 | tester.circuit.inc = 2 72 | tester.finish_sequences() 73 | tester.compile_and_run("verilator", disp_type="realtime", 74 | flags=['-Wno-UNUSED'], 75 | magma_output="mlir-verilog", 76 | magma_opts={"flatten_all_tuples": True}, 77 | tmp_dir=True) 78 | 79 | 80 | def test_lifted_ready_valid_sequence_simple_fail(): 81 | if verilator_version() < 4.0: 82 | pytest.skip("Untested with earlier verilator versions") 83 | I = [BitVector.random(8) for _ in range(8)] + [0] 84 | O = [0] + [i + 2 for i in I[:-1]] 85 | tester = f.ReadyValidTester(Main2, {"I": I, "O": O}) 86 | tester.circuit.inc = 2 87 | # Should work for a few cycles 88 | for i in range(9): 89 | tester.advance_cycle() 90 | # Bad inc should fail 91 | tester.circuit.inc = 3 92 | tester.finish_sequences() 93 | with pytest.raises(AssertionError): 94 | tester.compile_and_run("verilator", disp_type="realtime", 95 | magma_output="mlir-verilog", 96 | magma_opts={"flatten_all_tuples": True}, 97 | tmp_dir=True) 98 | 99 | 100 | def test_lifted_ready_valid_sequence_changing_inc(): 101 | if verilator_version() < 4.0: 102 | pytest.skip("Untested with earlier verilator versions") 103 | I = [BitVector.random(8) for _ in range(8)] + [0] 104 | O = [0] + [I[i] + ((i + 1) % 2) for i in range(8)] 105 | tester = f.ReadyValidTester(Main2, {"I": I, "O": O}) 106 | # Sequence expects inc to change over time 107 | for i in range(8): 108 | tester.circuit.inc = i % 2 109 | tester.advance_cycle() 110 | tester.wait_until_high(tester.circuit.O.ready & tester.circuit.O.valid) 111 | # Advance one cycle to finish last handshake 112 | tester.advance_cycle() 113 | tester.expect_sequences_finished() 114 | tester.compile_and_run("verilator", disp_type="realtime", 115 | flags=['-Wno-UNUSED'], 116 | magma_output="mlir-verilog", 117 | magma_opts={"flatten_all_tuples": True}, 118 | tmp_dir=True) 119 | -------------------------------------------------------------------------------- /tests/test_real_val.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import magma as m 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog', 'verilator') 9 | 10 | 11 | def test_real_val(target, simulator): 12 | # define the circuit 13 | class realadd(m.Circuit): 14 | io = m.IO( 15 | a_val=fault.RealIn, 16 | b_val=fault.RealIn, 17 | c_val=fault.RealOut 18 | ) 19 | 20 | # define test content 21 | tester = fault.Tester(realadd) 22 | tester.poke(realadd.a_val, 1.125) 23 | tester.poke(realadd.b_val, 2.5) 24 | tester.eval() 25 | tester.expect(realadd.c_val, 3.625, abs_tol=1e-4) 26 | 27 | # define target-specific kwargs 28 | kwargs = {} 29 | module_file = Path('tests/verilog/realadd.sv').resolve() 30 | if target == 'verilator': 31 | kwargs['ext_model_file'] = module_file 32 | elif target == 'system-verilog': 33 | kwargs['simulator'] = simulator 34 | kwargs['include_verilog_libraries'] = [module_file] 35 | kwargs['ext_model_file'] = True 36 | if simulator == 'iverilog': 37 | kwargs['defines'] = {'__IVERILOG__': None} 38 | 39 | # run the test 40 | tester.compile_and_run( 41 | target=target, 42 | tmp_dir=True, 43 | **kwargs 44 | ) 45 | -------------------------------------------------------------------------------- /tests/test_spice_bus.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | from pathlib import Path 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'spice') 9 | 10 | 11 | def test_spice_bus(target, simulator, vsup=1.5): 12 | # declare circuit 13 | class dut(m.Circuit): 14 | name = 'mybus' 15 | io = m.IO( 16 | a=m.In(m.Bits[2]), 17 | b=m.Out(m.Bits[3]), 18 | vdd=m.BitIn, 19 | vss=m.BitIn 20 | ) 21 | 22 | # define the test 23 | tester = fault.Tester(dut) 24 | tester.poke(dut.vdd, 1) 25 | tester.poke(dut.vss, 0) 26 | 27 | # step through all possible inputs 28 | tester.poke(dut.a, 0b000) 29 | tester.expect(dut.b, 0b101) 30 | tester.poke(dut.a, 0b001) 31 | tester.expect(dut.b, 0b100) 32 | tester.poke(dut.a, 0b010) 33 | tester.expect(dut.b, 0b111) 34 | tester.poke(dut.a, 0b011) 35 | tester.expect(dut.b, 0b110) 36 | 37 | # set options 38 | kwargs = dict( 39 | target=target, 40 | simulator=simulator, 41 | model_paths=[Path('tests/spice/mybus.sp').resolve()], 42 | vsup=vsup, 43 | tmp_dir=True 44 | ) 45 | 46 | # run the simulation 47 | tester.compile_and_run(**kwargs) 48 | -------------------------------------------------------------------------------- /tests/test_spice_port_order.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | from fault.spice_target import SpiceTarget 4 | 5 | 6 | def test_spice_port_order(): 7 | # declare a circuit with 8 | class circ(m.Circuit): 9 | name = 's' 10 | io = m.IO( 11 | p=fault.ElectIn, 12 | i=m.BitIn, 13 | c=m.Out(m.Bits[3]), 14 | e=fault.RealOut 15 | ) 16 | 17 | target = SpiceTarget(circ, conn_order='alpha') 18 | 19 | ports = target.get_ordered_ports() 20 | 21 | assert ports == ['c<2>', 'c<1>', 'c<0>', 'e', 'i', 'p'] 22 | -------------------------------------------------------------------------------- /tests/test_test_vectors.py: -------------------------------------------------------------------------------- 1 | from itertools import product 2 | import pytest 3 | from hwtypes import Bit 4 | import magma as m 5 | from fault.test_vectors import (generate_function_test_vectors, 6 | generate_simulator_test_vectors) 7 | from fault.value import AnyValue 8 | from .common import TestBasicCircuit, TestArrayCircuit, TestSIntCircuit 9 | 10 | 11 | @pytest.mark.parametrize("Circuit", [TestBasicCircuit, TestArrayCircuit, 12 | TestSIntCircuit]) 13 | def test_circuit(Circuit): 14 | def fn(I): 15 | return I 16 | function_test_vectors = generate_function_test_vectors(Circuit, fn) 17 | simulator_test_vectors = generate_simulator_test_vectors(Circuit) 18 | assert function_test_vectors == simulator_test_vectors 19 | 20 | 21 | def test_combinational_circuit(): 22 | def f(a, b, c): 23 | return (a & b) ^ c 24 | 25 | class main(m.Circuit): 26 | io = m.IO(a=m.In(m.Bit), 27 | b=m.In(m.Bit), 28 | c=m.In(m.Bit), 29 | d=m.Out(m.Bit)) 30 | 31 | m.wire(f(io.a, io.b, io.c), io.d) 32 | 33 | test_vectors = generate_function_test_vectors(main, f) 34 | assert len(test_vectors) == 2 ** 3 + 1 35 | 36 | # Check that vectors are as expected. The general pattern that we expect is 37 | # that the outputs of the ith vector match f() evaluated on the inputs in 38 | # the (i - 1)th vector. Also the order of the inputs matches the canonical 39 | # order of the cartesian product. 40 | for i, inputs in enumerate(product((0, 1), (0, 1), (0, 1))): 41 | vec = test_vectors[i].test_vector 42 | expected = [Bit(x) for x in inputs] 43 | assert vec[:3] == expected 44 | if i == 0: 45 | assert vec[3] == AnyValue 46 | continue 47 | prev_inputs = test_vectors[i - 1].test_vector[:3] 48 | expected = Bit(f(*prev_inputs)) 49 | assert vec[3] == expected 50 | # Checking the pattern above for the last vector. 51 | vec = test_vectors[-1].test_vector 52 | prev_inputs = test_vectors[-2].test_vector[:3] 53 | expected = Bit(f(*prev_inputs)) 54 | assert vec[3] == expected 55 | -------------------------------------------------------------------------------- /tests/test_tester/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leonardt/fault/093bc9a02efedb5aad1173fca84ce1f56ac996b7/tests/test_tester/__init__.py -------------------------------------------------------------------------------- /tests/test_tester/test_call.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | from ..common import AndCircuit, pytest_sim_params, SimpleALU 3 | import fault 4 | import operator 5 | from hwtypes import BitVector 6 | 7 | 8 | def pytest_generate_tests(metafunc): 9 | pytest_sim_params(metafunc, 'verilator', 'system-verilog') 10 | 11 | 12 | def test_call_interface_basic(target, simulator): 13 | tester = fault.Tester(AndCircuit) 14 | for i, j in zip(range(2), range(2)): 15 | tester(i, j).expect(i & j) 16 | with tempfile.TemporaryDirectory(dir=".") as _dir: 17 | if target == "verilator": 18 | tester.compile_and_run(target, directory=_dir) 19 | else: 20 | tester.compile_and_run(target, directory=_dir, 21 | simulator=simulator, 22 | magma_opts={"sv": True}) 23 | 24 | 25 | def test_call_interface_kwargs(target, simulator): 26 | tester = fault.Tester(AndCircuit) 27 | for i, j in zip(range(2), range(2)): 28 | tester(i, j, I0=0).expect(0) 29 | with tempfile.TemporaryDirectory(dir=".") as _dir: 30 | if target == "verilator": 31 | tester.compile_and_run(target, directory=_dir) 32 | else: 33 | tester.compile_and_run(target, directory=_dir, 34 | simulator=simulator, 35 | magma_opts={"sv": True}) 36 | 37 | 38 | def test_call_interface_kwargs(target, simulator): 39 | tester = fault.Tester(AndCircuit) 40 | for i, j in zip(range(2), range(2)): 41 | tester(i, j, I0=0).expect(0) 42 | with tempfile.TemporaryDirectory(dir=".") as _dir: 43 | if target == "verilator": 44 | tester.compile_and_run(target, directory=_dir) 45 | else: 46 | tester.compile_and_run(target, directory=_dir, 47 | simulator=simulator, 48 | magma_opts={"sv": True}) 49 | 50 | 51 | def test_call_interface_clock(target, simulator, caplog): 52 | ops = [operator.add, operator.sub, operator.mul, lambda x, y: y - x] 53 | tester = fault.Tester(SimpleALU, SimpleALU.CLK) 54 | tester.circuit.CLK = 0 55 | tester.circuit.config_en = 1 56 | 57 | for i in range(0, 4): 58 | tester.circuit.config_data = i 59 | tester.step(2) 60 | tester(3, 2).expect(ops[i](BitVector[16](3), BitVector[16](2))) 61 | with tempfile.TemporaryDirectory(dir=".") as _dir: 62 | if target == "verilator": 63 | tester.compile_and_run(target, directory=_dir, 64 | flags=["-Wno-unused"]) 65 | else: 66 | tester.compile_and_run(target, directory=_dir, 67 | simulator=simulator, 68 | magma_opts={"sv": True}) 69 | warning = "Number of arguments to __call__ did not match number of " \ 70 | "circuit inputs" 71 | assert warning in caplog.messages 72 | -------------------------------------------------------------------------------- /tests/test_tester/test_control.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import magma as m 4 | import fault 5 | 6 | 7 | @pytest.mark.parametrize('TesterClass', [fault.Tester, 8 | fault.SynchronousTester]) 9 | def test_inherit_methods(TesterClass): 10 | class DUT(m.Circuit): 11 | io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit)) + m.ClockIO() 12 | 13 | tester = TesterClass(DUT, DUT.CLK) 14 | assert isinstance(tester._if(tester.circuit.O), TesterClass) 15 | assert isinstance(tester._while(tester.circuit.O), TesterClass) 16 | -------------------------------------------------------------------------------- /tests/test_tester/test_interactive.py: -------------------------------------------------------------------------------- 1 | from fault import PythonTester 2 | from ..common import AndCircuit, SimpleALU, TestTupleCircuit, \ 3 | TestNestedArraysCircuit, TestNestedArrayTupleCircuit 4 | from hwtypes import BitVector 5 | import magma as m 6 | 7 | 8 | def test_interactive_basic(capsys): 9 | tester = PythonTester(AndCircuit) 10 | tester.poke(AndCircuit.I0, 0) 11 | tester.poke(AndCircuit.I1, 1) 12 | tester.eval() 13 | tester.expect(AndCircuit.O, 0) 14 | tester.poke(AndCircuit.I0, 1) 15 | tester.eval() 16 | tester.assert_(tester.peek(AndCircuit.O) == 1) 17 | tester.print("Hello %d\n", AndCircuit.O) 18 | assert capsys.readouterr()[0] == "Hello 1\n" 19 | 20 | 21 | def test_interactive_setattr(): 22 | tester = PythonTester(AndCircuit) 23 | tester.circuit.I0 = 1 24 | tester.circuit.I1 = 1 25 | tester.eval() 26 | tester.circuit.O.expect(1) 27 | 28 | 29 | def test_interactive_clock(): 30 | tester = PythonTester(SimpleALU, SimpleALU.CLK) 31 | tester.circuit.a = 0xDEAD 32 | tester.circuit.b = 0xBEEF 33 | tester.circuit.CLK = 0 34 | tester.circuit.config_data = 1 35 | tester.circuit.config_en = 1 36 | tester.advance_cycle() 37 | tester.circuit.c.expect(BitVector[16](0xDEAD) - BitVector[16](0xBEEF)) 38 | 39 | 40 | def test_counter(): 41 | Counter4 = m.mantle.Counter(2**4) 42 | tester = PythonTester(Counter4, Counter4.CLK) 43 | tester.CLK = 0 44 | tester.wait_until_high(Counter4.O[3]) 45 | tester.circuit.O.expect(1 << 3) 46 | tester.wait_until_low(Counter4.O[3]) 47 | tester.circuit.O.expect(0) 48 | 49 | 50 | def test_tuple(): 51 | tester = PythonTester(TestTupleCircuit) 52 | tester.circuit.I = (4, 2) 53 | tester.eval() 54 | tester.circuit.O.expect((4, 2)) 55 | tester.circuit.I = {"a": 4, "b": 2} 56 | tester.eval() 57 | tester.circuit.O.expect({"a": 4, "b": 2}) 58 | 59 | 60 | def test_nested_arrays(): 61 | tester = PythonTester(TestTupleCircuit) 62 | tester.circuit.I = (4, 2) 63 | tester.eval() 64 | tester.circuit.O.expect((4, 2)) 65 | tester.circuit.I = {"a": 4, "b": 2} 66 | tester.eval() 67 | tester.circuit.O.expect({"a": 4, "b": 2}) 68 | 69 | 70 | def test_tester_nested_arrays_bulk(): 71 | tester = PythonTester(TestNestedArraysCircuit) 72 | expected = [] 73 | val = [BitVector.random(4) for _ in range(3)] 74 | tester.poke(TestNestedArraysCircuit.I, val) 75 | tester.eval() 76 | tester.expect(TestNestedArraysCircuit.O, val) 77 | 78 | 79 | def test_tester_nested_array_tuple(): 80 | tester = PythonTester(TestNestedArrayTupleCircuit) 81 | expected = [] 82 | val = (BitVector.random(4), BitVector.random(4)) 83 | tester.poke(TestNestedArrayTupleCircuit.I, val) 84 | tester.eval() 85 | tester.expect(TestNestedArrayTupleCircuit.O, val) 86 | -------------------------------------------------------------------------------- /tests/test_tester/test_magma_usernamespace.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import fault as f 3 | from ..common import SimpleALU, pytest_sim_params 4 | 5 | 6 | def pytest_generate_tests(metafunc): 7 | pytest_sim_params(metafunc, 'verilator', 'system-verilog') 8 | 9 | 10 | def test_user_namespace(target, simulator): 11 | t = f.SynchronousTester(SimpleALU, SimpleALU.CLK) 12 | t.circuit.config_en = 1 13 | t.circuit.config_data = 0 14 | t.advance_cycle() 15 | t.circuit.a = 1 16 | t.circuit.b = 2 17 | t.advance_cycle() 18 | t.circuit.c.expect(3) 19 | with tempfile.TemporaryDirectory(dir=".") as _dir: 20 | if target == "verilator": 21 | t.compile_and_run(target, directory=_dir, flags=["-Wno-fatal"], 22 | magma_opts={"user_namespace": "my_namespace", 23 | "sv": True}) 24 | else: 25 | t.compile_and_run(target, directory=_dir, simulator=simulator, 26 | magma_opts={"user_namespace": "my_namespace", 27 | "sv": True}) 28 | -------------------------------------------------------------------------------- /tests/test_tester/test_sequence_tester.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import pytest 3 | 4 | import magma as m 5 | from hwtypes import BitVector 6 | 7 | from fault.tester.sequence_tester import Monitor, Driver, SequenceTester 8 | 9 | 10 | class ALUCore(m.Circuit): 11 | io = m.IO(a=m.In(m.UInt[16]), 12 | b=m.In(m.UInt[16]), 13 | opcode=m.In(m.UInt[2]), 14 | c=m.Out(m.UInt[16])) 15 | io.c @= m.mux([io.a + io.b, io.a - io.b, io.a * io.b, io.a / io.b], 16 | io.opcode) 17 | 18 | 19 | class ALUTile(m.Circuit): 20 | io = m.IO(a=m.In(m.UInt[16]), 21 | b=m.In(m.UInt[16]), 22 | config_data=m.In(m.UInt[2]), 23 | config_en=m.In(m.Enable), 24 | c=m.Out(m.UInt[16])) + m.ClockIO() 25 | config_reg = m.Register(m.Bits[2], has_enable=True)() 26 | config_reg.CE @= io.config_en 27 | config_reg.I @= io.config_data 28 | alu = ALUCore() 29 | io.c @= alu(io.a, io.b, config_reg.O) 30 | 31 | 32 | class CoreDriver(Driver): 33 | def lower(self, a, b, opcode): 34 | self.tester.circuit.a = a 35 | self.tester.circuit.b = b 36 | self.tester.circuit.opcode = opcode 37 | 38 | 39 | class TileDriver(Driver): 40 | def lower(self, a, b, opcode): 41 | self.tester.circuit.config_en = 1 42 | self.tester.circuit.config_data = opcode 43 | self.tester.step(2) 44 | # Make sure enable logic works 45 | self.tester.circuit.config_en = 0 46 | self.tester.circuit.config_data = BitVector.random(2) 47 | self.tester.circuit.a = a 48 | self.tester.circuit.b = b 49 | 50 | 51 | class SharedMonitor(Monitor): 52 | def __init__(self): 53 | self.ops = [ 54 | lambda x, y: x + y, 55 | lambda x, y: x - y, 56 | lambda x, y: x * y, 57 | lambda x, y: x // y 58 | ] 59 | 60 | def observe(self, a, b, opcode): 61 | expected = self.ops[int(opcode)](a, b) 62 | self.tester.circuit.c.expect(expected) 63 | 64 | 65 | @pytest.mark.parametrize('circuit, driver, monitor, clock', [ 66 | (ALUCore, CoreDriver(), SharedMonitor(), None), 67 | (ALUTile, TileDriver(), SharedMonitor(), ALUTile.CLK), 68 | ]) 69 | def test_simple_alu_sequence(circuit, driver, monitor, clock): 70 | """ 71 | Reuse the same input/output sequence for core and tile 72 | """ 73 | sequence = [ 74 | (BitVector.random(16), BitVector.random(16), BitVector.random(2)) 75 | for _ in range(5) 76 | ] 77 | 78 | tester = SequenceTester(circuit, driver, monitor, sequence, clock=clock) 79 | 80 | with tempfile.TemporaryDirectory(dir=".") as tempdir: 81 | tester.compile_and_run("verilator", flags=['-Wno-UNUSED'], 82 | directory=tempdir) 83 | -------------------------------------------------------------------------------- /tests/test_tester/test_symbolic_tester.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | 3 | import pytest 4 | 5 | from hwtypes import BitVector 6 | 7 | import magma as m 8 | 9 | from fault import SymbolicTester 10 | 11 | from ..common import ConfigReg 12 | 13 | 14 | def pytest_generate_tests(metafunc): 15 | if 'target' in metafunc.fixturenames: 16 | metafunc.parametrize("target", ["verilator", "pono"]) 17 | 18 | 19 | class SimpleALU(m.Circuit): 20 | io = m.IO(a=m.In(m.UInt[16]), 21 | b=m.In(m.UInt[16]), 22 | c=m.Out(m.UInt[16]), 23 | config_data=m.In(m.Bits[2]), 24 | config_en=m.In(m.Enable), 25 | ) + m.ClockIO() 26 | 27 | opcode = ConfigReg(name="config_reg")(io.config_data, CE=io.config_en) 28 | io.c @= m.mux( 29 | [io.a - io.b, io.a + io.b, io.a * io.b, io.b / io.a], opcode) 30 | 31 | 32 | def test_tester_magma_internal_signals_verilator(target): 33 | if target == "pono": 34 | try: 35 | from smt_switch.primops import BVUge, And, BVUlt 36 | import pono 37 | # Use symbols to avoid unused symbols lint warning 38 | pono 39 | except ImportError: 40 | pytest.skip("Could not import pono or smt_switch") 41 | 42 | # TODO: Fix test 43 | # https://github.com/leonardt/fault/runs/2347548425 44 | # maybe it's using an old API? 45 | pytest.skip("Could not import pono or smt_switch") 46 | circ = SimpleALU 47 | 48 | tester = SymbolicTester(circ, circ.CLK, num_tests=100) 49 | tester.circuit.CLK = 0 50 | tester.circuit.config_en = 1 51 | tester.circuit.config_data = 1 # add is opcode 2 52 | tester.step(2) 53 | tester.circuit.config_en = 0 54 | tester.step(2) 55 | tester.circuit.config_en = 0 56 | tester.step(2) 57 | if target == "verilator": 58 | # TODO: We could turn this expect into a property 59 | tester.circuit.config_reg.Q.expect(1) 60 | tester.circuit.a.assume(lambda a: a < BitVector[16](32768)) 61 | tester.circuit.b.assume(lambda b: b < BitVector[16](32768)) 62 | tester.circuit.c.guarantee(lambda a, b, c: (c >= a) and (c >= b)) 63 | else: 64 | tester.circuit.a.assume( 65 | lambda solver, a, sort: solver.make_term(BVUlt, a, 66 | solver.make_term(32768, 67 | sort)) 68 | ) 69 | tester.circuit.b.assume( 70 | lambda solver, b, sort: solver.make_term(BVUlt, b, 71 | solver.make_term(32768, 72 | sort)) 73 | ) 74 | tester.circuit.c.guarantee( 75 | lambda solver, ports: 76 | solver.make_term( 77 | And, 78 | solver.make_term(BVUge, ports['c'], ports['a']), 79 | solver.make_term(BVUge, ports['c'], ports['b']), 80 | ) 81 | ) 82 | 83 | with tempfile.TemporaryDirectory() as _dir: 84 | kwargs = {} 85 | if target == "verilator": 86 | kwargs["magma_opts"] = {"verilator_debug": True, 87 | "verilator_compat": True} 88 | kwargs["flags"] = ["-Wno-unused"] 89 | tester.compile_and_run(target, directory=_dir, **kwargs) 90 | -------------------------------------------------------------------------------- /tests/test_tester/test_synchronous.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import pytest 3 | 4 | from fault import SynchronousTester 5 | import magma as m 6 | from hwtypes import BitVector 7 | from ..common import SimpleALU, pytest_sim_params 8 | 9 | 10 | def pytest_generate_tests(metafunc): 11 | pytest_sim_params(metafunc, 'verilator', 'system-verilog') 12 | 13 | 14 | def test_synchronous_basic(target, simulator): 15 | ops = [ 16 | lambda x, y: x + y, 17 | lambda x, y: x - y, 18 | lambda x, y: x * y, 19 | lambda x, y: y - x 20 | ] 21 | tester = SynchronousTester(SimpleALU, SimpleALU.CLK) 22 | for i in range(4): 23 | tester.circuit.a = a = BitVector.random(16) 24 | tester.circuit.b = b = BitVector.random(16) 25 | tester.circuit.config_data = i 26 | tester.circuit.config_en = 1 27 | tester.advance_cycle() 28 | # Make sure enable low works 29 | tester.circuit.config_data = BitVector.random(2) 30 | tester.circuit.config_en = 0 31 | tester.circuit.c.expect(ops[i](a, b)) 32 | tester.advance_cycle() 33 | 34 | if target == "verilator": 35 | with tempfile.TemporaryDirectory(dir=".") as tempdir: 36 | tester.compile_and_run("verilator", directory=tempdir, 37 | magma_output="mlir-verilog") 38 | else: 39 | tester.compile_and_run(target, simulator=simulator, 40 | magma_opts={"sv": True}) 41 | 42 | 43 | def test_find_default_clock(): 44 | tester = SynchronousTester(SimpleALU) 45 | assert tester.clock == SimpleALU.CLK 46 | 47 | 48 | def test_find_default_clock_nested(): 49 | class Foo(m.Circuit): 50 | io = m.IO( 51 | I=m.In(m.Tuple[m.Clock, m.Bit]) 52 | ) 53 | tester = SynchronousTester(Foo) 54 | assert tester.clock == Foo.I[0] 55 | 56 | 57 | def test_find_default_clock_multiple(): 58 | class Foo(m.Circuit): 59 | io = m.IO( 60 | clock0=m.In(m.Clock), 61 | clock1=m.In(m.Clock) 62 | ) 63 | with pytest.raises(ValueError) as e: 64 | SynchronousTester(Foo) 65 | assert str(e.value) == "SynchronousTester requires a clock" 66 | 67 | 68 | def test_find_default_clock_nested_multiple(): 69 | class Foo(m.Circuit): 70 | io = m.IO( 71 | I=m.In(m.Tuple[m.Clock, m.Clock]) 72 | ) 73 | with pytest.raises(ValueError) as e: 74 | SynchronousTester(Foo) 75 | assert str(e.value) == "SynchronousTester requires a clock" 76 | -------------------------------------------------------------------------------- /tests/test_tester/test_timing.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import os 3 | import shutil 4 | 5 | from vcdvcd import VCDVCD 6 | 7 | import fault as f 8 | from ..common import SimpleALU 9 | 10 | 11 | def test_init_clock(): 12 | tester = f.Tester(SimpleALU, SimpleALU.CLK) 13 | tester.circuit.a = 1 14 | tester.circuit.b = 2 15 | tester.circuit.config_data = 0 16 | tester.circuit.config_en = 0 17 | tester.step(2) 18 | tester.circuit.c.expect(3) 19 | with tempfile.TemporaryDirectory() as tempdir: 20 | tester.compile_and_run("verilator", flags=["--trace", "-Wno-fatal"], 21 | directory=tempdir) 22 | vcd_file = os.path.join(tempdir, "logs", "SimpleALU.vcd") 23 | vcd = VCDVCD(vcd_file, signals=["TOP.CLK"]) 24 | # One elem dict, we can grab the first element 25 | tvs = vcd["TOP.CLK"].tv 26 | assert tvs == [(0, '0'), (5, '1'), (10, '0')] 27 | 28 | if shutil.which("iverilog"): 29 | simulator = "iverilog" 30 | elif shutil.which("irun"): 31 | simulator = "ncsim" 32 | else: 33 | return 34 | tester.compile_and_run("system-verilog", simulator=simulator, 35 | directory=tempdir, magma_opts={"sv": True}, 36 | dump_waveforms=True) 37 | vcd_file = os.path.join(tempdir, "waveforms.vcd") 38 | vcd = VCDVCD(vcd_file, signals=["SimpleALU_tb.dut.CLK"]) 39 | tvs = vcd["SimpleALU_tb.dut.CLK"].tv 40 | assert tvs == [(0, '0'), (5, '1'), (10, '0')], tvs 41 | -------------------------------------------------------------------------------- /tests/test_top_module.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import fault 3 | import magma as m 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog') 9 | 10 | 11 | def test_ext_vlog(target, simulator): 12 | # declare circuit 13 | class myinv(m.Circuit): 14 | io = m.IO( 15 | in_=m.In(m.Bit), 16 | out=m.Out(m.Bit) 17 | ) 18 | 19 | # define test 20 | tester = fault.InvTester(myinv) 21 | 22 | # run the test 23 | tester.compile_and_run( 24 | target=target, 25 | simulator=simulator, 26 | ext_srcs=[Path('tests/verilog/myinv_extra_module.v').resolve()], 27 | ext_model_file=True, 28 | tmp_dir=True 29 | ) 30 | -------------------------------------------------------------------------------- /tests/test_value_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import magma as m 3 | from hwtypes import BitVector 4 | from fault.array import Array 5 | from fault.value_utils import make_value 6 | from fault.value import AnyValue, UnknownValue 7 | 8 | 9 | class Foo(m.Circuit): 10 | io = m.IO(a=m.In(m.Bit), 11 | b=m.In(m.Bits[8]), 12 | c=m.In(m.Array[12, m.Bit]), 13 | d=m.In(m.Array[16, m.Array[20, m.Bit]])) 14 | 15 | 16 | def test_all(): 17 | # Bit type. 18 | assert make_value(type(Foo.a), BitVector[1](0)) == BitVector[1](0) 19 | assert make_value(type(Foo.a), 0) == BitVector[1](0) 20 | assert make_value(type(Foo.a), 1) == BitVector[1](1) 21 | assert make_value(type(Foo.a), AnyValue) == AnyValue 22 | assert make_value(type(Foo.a), UnknownValue) == UnknownValue 23 | with pytest.raises(NotImplementedError) as pytest_e: 24 | make_value(type(Foo.a), 2) 25 | assert False 26 | assert pytest_e.type == NotImplementedError 27 | 28 | # NOTE(rsetaluri): For the following 3 tests we use arbitray values as input 29 | # into the bit-vectors. The tests should pass for any number. 30 | 31 | # Bits type. 32 | assert make_value(type(Foo.b), BitVector[8](5)) == BitVector[8](5) 33 | assert make_value(type(Foo.b), 17) == BitVector[8](17) 34 | assert make_value(type(Foo.b), AnyValue) == AnyValue 35 | assert make_value(type(Foo.b), UnknownValue) == UnknownValue 36 | 37 | # Array(Bit) type. Should be the same as above. 38 | assert make_value(type(Foo.c), BitVector[12](83)) == BitVector[12](83) 39 | assert make_value(type(Foo.c), 23) == BitVector[12](23) 40 | assert make_value(type(Foo.c), AnyValue) == AnyValue 41 | assert make_value(type(Foo.c), UnknownValue) == UnknownValue 42 | 43 | # Array(Array(Bit)) type. 44 | assert make_value(type(Foo.d), 894) == Array([BitVector[20](894)] * 16, 16) 45 | assert make_value(type(Foo.d), AnyValue) == Array([AnyValue] * 16, 16) 46 | assert make_value(type(Foo.d), UnknownValue) == \ 47 | Array([UnknownValue] * 16, 16) 48 | -------------------------------------------------------------------------------- /tests/test_vams_wrap.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | from fault.verilogams import VAMSWrap, RealIn, RealOut, ElectIn, ElectOut 3 | 4 | 5 | def test_vams_wrap(): 6 | # declare the circuit 7 | class myblk(m.Circuit): 8 | io = m.IO( 9 | a=RealIn, 10 | b=RealOut, 11 | c=m.In(m.Bit), 12 | d=m.Out(m.Bits[2]), 13 | e=ElectIn, 14 | f=ElectOut 15 | ) 16 | wrap_circ = VAMSWrap(myblk) 17 | 18 | # check magma representation of wrapped circuit 19 | assert wrap_circ.IO.ports['a'] is RealIn 20 | assert wrap_circ.IO.ports['b'] is RealOut 21 | assert wrap_circ.IO.ports['c'] is m.In(m.Bit) 22 | assert wrap_circ.IO.ports['d'] is m.Out(m.Bits[2]) 23 | assert wrap_circ.IO.ports['e'] is ElectIn 24 | assert wrap_circ.IO.ports['f'] is ElectOut 25 | 26 | # check Verilog-AMS code itself 27 | assert wrap_circ.vams_code == '''\ 28 | `include "disciplines.vams" 29 | 30 | module myblk_wrap ( 31 | input wreal a, 32 | output wreal b, 33 | input wire c, 34 | output wire [1:0] d, 35 | input electrical e, 36 | output electrical f 37 | ); 38 | 39 | myblk myblk_inst ( 40 | .a(a), 41 | .b(b), 42 | .c(c), 43 | .d(d), 44 | .e(e), 45 | .f(f) 46 | ); 47 | 48 | endmodule 49 | ''' 50 | -------------------------------------------------------------------------------- /tests/test_vector_builder.py: -------------------------------------------------------------------------------- 1 | import random 2 | from hwtypes import BitVector 3 | import fault 4 | from fault.actions import Poke, Expect, Eval, Step, Print 5 | from fault.array import Array 6 | from fault.vector_builder import VectorBuilder 7 | from .common import (TestBasicCircuit, TestBasicClkCircuit, 8 | TestNestedArraysCircuit) 9 | 10 | 11 | def test_tester_basic(): 12 | circ = TestBasicCircuit 13 | builder = VectorBuilder(circ) 14 | builder.process(Poke(circ.I, BitVector[1](0))) 15 | builder.process(Expect(circ.O, BitVector[1](0))) 16 | assert builder.vectors == [[BitVector[1](0), BitVector[1](0)]] 17 | builder.process(Eval()) 18 | assert builder.vectors == [[BitVector[1](0), BitVector[1](0)], 19 | [BitVector[1](0), fault.AnyValue]] 20 | 21 | 22 | def test_tester_clock(): 23 | circ = TestBasicClkCircuit 24 | builder = VectorBuilder(circ) 25 | builder.process(Poke(circ.I, BitVector[1](0))) 26 | builder.process(Print("%x", circ.O)) 27 | builder.process(Expect(circ.O, BitVector[1](0))) 28 | assert builder.vectors == [ 29 | [BitVector[1](0), BitVector[1](0), fault.AnyValue] 30 | ] 31 | builder.process(Poke(circ.CLK, BitVector[1](0))) 32 | assert builder.vectors == [ 33 | [BitVector[1](0), BitVector[1](0), BitVector[1](0)] 34 | ] 35 | builder.process(Step(circ.CLK, 1)) 36 | assert builder.vectors == [ 37 | [BitVector[1](0), BitVector[1](0), BitVector[1](0)], 38 | [BitVector[1](0), fault.AnyValue, BitVector[1](1)] 39 | ] 40 | 41 | 42 | def test_tester_nested_arrays(): 43 | circ = TestNestedArraysCircuit 44 | builder = VectorBuilder(circ) 45 | expected = [] 46 | for i in range(3): 47 | val = random.randint(0, (1 << 4) - 1) 48 | builder.process(Poke(circ.I[i], BitVector[4](val))) 49 | builder.process(Expect(circ.O[i], BitVector[4](val))) 50 | expected.append(val) 51 | assert builder.vectors == [[Array(expected, 3), Array(expected, 3)]] 52 | -------------------------------------------------------------------------------- /tests/test_verilator_target.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import magma as m 3 | import fault 4 | from hwtypes import BitVector 5 | from fault.actions import Poke, Expect, Eval, Step, Print, Peek 6 | from fault.tester import Tester 7 | import os.path 8 | from .common import TestBasicCircuit, TestBasicClkCircuit 9 | 10 | 11 | def test_verilator_peeks(): 12 | circ = TestBasicCircuit 13 | actions = [ 14 | Poke(circ.I, 1), 15 | Expect(circ.O, Peek(circ.O)) 16 | ] 17 | flags = ["-Wno-lint"] 18 | with tempfile.TemporaryDirectory(dir=".") as tempdir: 19 | m.compile(f"{tempdir}/{circ.name}", circ, output="coreir-verilog") 20 | target = fault.verilator_target.VerilatorTarget( 21 | circ, directory=f"{tempdir}/", 22 | flags=flags, skip_compile=True) 23 | target.run(actions) 24 | 25 | 26 | def test_verilator_skip_build(): 27 | circ = TestBasicCircuit 28 | flags = ["-Wno-lint"] 29 | tester = Tester(circ) 30 | with tempfile.TemporaryDirectory(dir=".") as tempdir: 31 | tester.compile_and_run(target="verilator", 32 | directory=tempdir, 33 | flags=flags) 34 | # get the timestamp on generated verilator obj files 35 | obj_filename = os.path.join(tempdir, "obj_dir", "VBasicCircuit__ALL.a") 36 | mtime = os.path.getmtime(obj_filename) 37 | 38 | # run without building the verilator 39 | tester.compile_and_run(target="verilator", 40 | directory=tempdir, 41 | skip_verilator=True, 42 | flags=flags) 43 | new_mtime = os.path.getmtime(obj_filename) 44 | assert mtime == new_mtime 45 | 46 | 47 | def test_verilator_trace(): 48 | circ = TestBasicClkCircuit 49 | actions = [ 50 | Poke(circ.I, 0), 51 | Print("%x", circ.I), 52 | Expect(circ.O, 0), 53 | Poke(circ.CLK, 0), 54 | Print("%x", circ.O), 55 | Step(circ.CLK, 1), 56 | Poke(circ.I, BitVector[1](1)), 57 | Eval(), 58 | Print("%x", circ.O), 59 | ] 60 | flags = ["-Wno-lint", "--trace"] 61 | 62 | with tempfile.TemporaryDirectory(dir=".") as tempdir: 63 | assert not os.path.isfile(f"{tempdir}/logs/BasicClkCircuit.vcd"), \ 64 | "Expected logs to be empty" 65 | m.compile(f"{tempdir}/{circ.name}", circ, 66 | output="coreir-verilog") 67 | target = fault.verilator_target.VerilatorTarget( 68 | circ, directory=f"{tempdir}/", 69 | flags=flags, skip_compile=True) 70 | target.run(actions) 71 | assert os.path.isfile(f"{tempdir}/logs/BasicClkCircuit.vcd"), \ 72 | "Expected VCD to exist" 73 | -------------------------------------------------------------------------------- /tests/test_while_loop.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import fault 3 | import magma as m 4 | from .common import pytest_sim_params 5 | 6 | 7 | def pytest_generate_tests(metafunc): 8 | pytest_sim_params(metafunc, 'system-verilog') 9 | 10 | 11 | def debug_print(tester, dut): 12 | to_display = {'dut.n_done': dut.n_done, 'dut.count': dut.count} 13 | 14 | args = [] 15 | fmt = [] 16 | for key, val in to_display.items(): 17 | args.append(val) 18 | fmt.append(f'{key}: %0d') 19 | 20 | tester.print(', '.join(fmt) + '\n', *args) 21 | 22 | 23 | def test_while_loop(target, simulator, n_cyc=3, n_bits=8): 24 | class dut(m.Circuit): 25 | name = 'clkdelay' 26 | io = m.IO( 27 | clk=m.In(m.Clock), 28 | rst=m.In(m.Reset), 29 | count=m.Out(m.Bits[n_bits]), 30 | n_done=m.Out(m.Bit) 31 | ) 32 | 33 | # instantiate the tester 34 | tester = fault.Tester(dut, clock=dut.clk, reset=dut.rst) 35 | tester.circuit.clk = 0 36 | 37 | # reset 38 | tester.sync_reset() 39 | 40 | # check initial state 41 | tester.expect(dut.n_done, 1) 42 | tester.expect(dut.count, 0) 43 | 44 | # wait for the loop to complete 45 | tester.poke(dut.rst, 0) 46 | loop = tester._while(tester.peek(dut.n_done)) 47 | debug_print(loop, dut) 48 | loop.step() 49 | loop.step() 50 | 51 | debug_print(tester, dut) 52 | 53 | # check final state 54 | tester.expect(dut.count, n_cyc - 1) 55 | tester.expect(dut.n_done, 0) 56 | 57 | # run the test 58 | tester.compile_and_run( 59 | target=target, 60 | simulator=simulator, 61 | tmp_dir=True, 62 | ext_libs=[Path('tests/verilog/clkdelay.sv').resolve()], 63 | ext_model_file=True, 64 | defines={'N_CYC': n_cyc, 'N_BITS': n_bits} 65 | ) 66 | -------------------------------------------------------------------------------- /tests/verilog/bidir.v: -------------------------------------------------------------------------------- 1 | module bidir( 2 | inout a, 3 | inout b 4 | ); 5 | 6 | tran tran_i(a, b); 7 | 8 | endmodule 9 | -------------------------------------------------------------------------------- /tests/verilog/clkdelay.sv: -------------------------------------------------------------------------------- 1 | module clkdelay #( 2 | parameter integer n_cyc=`N_CYC, 3 | parameter integer n_bits=`N_BITS 4 | ) ( 5 | input wire logic clk, 6 | input wire logic rst, 7 | output reg [n_bits-1:0] count, 8 | output reg n_done 9 | ); 10 | 11 | always @(posedge clk) begin 12 | if (rst == 1'b1) begin 13 | count <= 0; 14 | n_done <= 1; 15 | end else if (count >= n_cyc-1) begin 16 | count <= count; 17 | n_done <= 0; 18 | end else begin 19 | count <= count + 1; 20 | n_done <= 1; 21 | end 22 | end 23 | 24 | endmodule 25 | -------------------------------------------------------------------------------- /tests/verilog/defadd.sv: -------------------------------------------------------------------------------- 1 | module defadd #( 2 | parameter integer n_bits=`N_BITS, 3 | parameter integer b_val=`B_VAL 4 | ) ( 5 | input wire logic [n_bits-1:0] a_val, 6 | output wire logic [n_bits-1:0] c_val 7 | ); 8 | 9 | /* verilator lint_off WIDTH */ 10 | assign c_val = a_val + b_val; 11 | /* verilator lint_on WIDTH */ 12 | 13 | endmodule 14 | -------------------------------------------------------------------------------- /tests/verilog/error_task.sv: -------------------------------------------------------------------------------- 1 | module error_task; 2 | initial begin 3 | $error; 4 | $finish; 5 | end 6 | endmodule 7 | -------------------------------------------------------------------------------- /tests/verilog/fatal_task.sv: -------------------------------------------------------------------------------- 1 | module fatal_task; 2 | initial begin 3 | $fatal; 4 | end 5 | endmodule 6 | -------------------------------------------------------------------------------- /tests/verilog/hizmod.v: -------------------------------------------------------------------------------- 1 | module hizmod( 2 | input a, 3 | input b, 4 | output c 5 | ); 6 | 7 | tran ta(a, c); 8 | tran tb(b, c); 9 | 10 | endmodule 11 | -------------------------------------------------------------------------------- /tests/verilog/myblend.sv: -------------------------------------------------------------------------------- 1 | module myblend( 2 | input real a, 3 | input real b, 4 | output real c 5 | ); 6 | 7 | assign c = (1.2 * b + 3.4 * a) / (1.2 + 3.4); 8 | 9 | endmodule 10 | -------------------------------------------------------------------------------- /tests/verilog/mybuf.v: -------------------------------------------------------------------------------- 1 | module mybuf( 2 | input in_, 3 | output out 4 | ); 5 | 6 | assign out = in_; 7 | 8 | endmodule 9 | -------------------------------------------------------------------------------- /tests/verilog/mybuf_inc_test.v: -------------------------------------------------------------------------------- 1 | `include "myinv.v" 2 | 3 | module mybuf_inc_test( 4 | input in_, 5 | output out 6 | ); 7 | 8 | wire out_n; 9 | 10 | myinv myinv_0 (.in_(in_), .out(out_n)); 11 | myinv myinv_1 (.in_(out_n), .out(out)); 12 | 13 | endmodule 14 | -------------------------------------------------------------------------------- /tests/verilog/myinv.v: -------------------------------------------------------------------------------- 1 | module myinv( 2 | input in_, 3 | output out 4 | ); 5 | 6 | assign out = ~in_; 7 | 8 | endmodule 9 | -------------------------------------------------------------------------------- /tests/verilog/myinv_extra_module.v: -------------------------------------------------------------------------------- 1 | module extra_module #( 2 | parameter file_name="file.mem" 3 | ) ( 4 | input [1:0] addr, 5 | output [2:0] data 6 | ); 7 | // read into rom 8 | reg [2:0] rom [0:3]; 9 | initial begin 10 | $readmemb(file_name, rom); 11 | end 12 | // assign to output 13 | assign data=rom[addr]; 14 | endmodule 15 | 16 | module myinv( 17 | input in_, 18 | output out 19 | ); 20 | assign out = ~in_; 21 | endmodule 22 | -------------------------------------------------------------------------------- /tests/verilog/mynand.v: -------------------------------------------------------------------------------- 1 | module mynand( 2 | input a, 3 | input b, 4 | output out 5 | ); 6 | 7 | assign out = ~(a & b); 8 | 9 | endmodule 10 | -------------------------------------------------------------------------------- /tests/verilog/mynor.v: -------------------------------------------------------------------------------- 1 | module mynor( 2 | input a, 3 | input b, 4 | output out 5 | ); 6 | 7 | assign out = ~(a | b); 8 | 9 | endmodule 10 | -------------------------------------------------------------------------------- /tests/verilog/mysram.v: -------------------------------------------------------------------------------- 1 | module mysram( 2 | input wl, 3 | inout lbl, 4 | inout lblb 5 | ); 6 | 7 | // internal capacitive nodes 8 | reg lbl_x, lblb_x; 9 | bufif1 (weak1, weak0) ba (lbl, lbl_x, wl); 10 | bufif1 (weak1, weak0) bb (lblb, lblb_x, wl); 11 | 12 | // writing internal nodes 13 | always @(wl or lbl or lblb) begin 14 | #0; 15 | if (wl == 1'b1) begin 16 | lbl_x = lbl; 17 | lblb_x = lblb; 18 | end 19 | end 20 | 21 | endmodule 22 | -------------------------------------------------------------------------------- /tests/verilog/mytb.sv: -------------------------------------------------------------------------------- 1 | module mytb; 2 | reg in_; 3 | wire out; 4 | 5 | assign out = ~in_; 6 | 7 | task check(input stim, input expct); begin 8 | in_ = stim; 9 | #1; 10 | if (out !== expct) begin 11 | $error("Expected %0b, got %0b", expct, out); 12 | end 13 | end endtask 14 | 15 | initial begin 16 | check(1'b0, 1'b1); 17 | check(1'b1, 1'b0); 18 | $finish; 19 | end 20 | endmodule 21 | -------------------------------------------------------------------------------- /tests/verilog/paramadd.sv: -------------------------------------------------------------------------------- 1 | module paramadd #( 2 | parameter integer n_bits=1, 3 | parameter integer b_val=0 4 | ) ( 5 | input wire logic [n_bits-1:0] a_val, 6 | output wire logic [n_bits-1:0] c_val 7 | ); 8 | 9 | /* verilator lint_off WIDTH */ 10 | assign c_val = a_val + b_val; 11 | /* verilator lint_on WIDTH */ 12 | 13 | endmodule 14 | -------------------------------------------------------------------------------- /tests/verilog/realadd.sv: -------------------------------------------------------------------------------- 1 | `ifdef __IVERILOG__ 2 | `define real_t wire real 3 | `endif 4 | 5 | `ifndef real_t 6 | `define real_t real 7 | `endif 8 | 9 | module realadd ( 10 | input `real_t a_val, 11 | input `real_t b_val, 12 | output `real_t c_val 13 | ); 14 | 15 | assign c_val = a_val + b_val; 16 | 17 | endmodule 18 | -------------------------------------------------------------------------------- /tests/verilog/simple_alu_pd.sv: -------------------------------------------------------------------------------- 1 | module simple_alu_pd(input [15:0] a, input [15:0] b, output [15:0] c, 2 | input [15:0] config_addr, 3 | input [15:0] config_data, input [15:0] config_en, 4 | input CLK, input VDD_HIGH, input VSS, 5 | input VDD_HIGH_TOP_VIRTUAL, output stall_out, 6 | output reset); 7 | 8 | // stub to test support for reading in modules with supply1, 9 | // supply0, and tri types 10 | endmodule 11 | -------------------------------------------------------------------------------- /tutorial/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /tutorial/exercise_1.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | 3 | 4 | class ConfigReg(m.Circuit): 5 | io = m.IO(D=m.In(m.Bits[2]), Q=m.Out(m.Bits[2])) + \ 6 | m.ClockIO(has_ce=True) 7 | 8 | reg = m.Register(m.Bits[2], has_enable=True)(name="config_reg") 9 | io.Q <= reg(io.D, CE=io.CE) 10 | 11 | 12 | class SimpleALU(m.Circuit): 13 | io = m.IO(a=m.In(m.UInt[16]), 14 | b=m.In(m.UInt[16]), 15 | c=m.Out(m.UInt[16]), 16 | config_data=m.In(m.Bits[2]), 17 | config_en=m.In(m.Enable), 18 | ) + m.ClockIO() 19 | 20 | opcode = ConfigReg(name="opcode_reg")(io.config_data, CE=io.config_en) 21 | io.c <= m.mux( 22 | [io.a + io.b, io.a - io.b, io.a * io.b, io.b - io.a], opcode) 23 | -------------------------------------------------------------------------------- /tutorial/exercise_2.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | from reset_tester import ResetTester 4 | 5 | 6 | data_width = 16 7 | addr_width = 4 8 | 9 | # Randomize initial contents of memory 10 | init = [fault.random.random_bv(data_width) for _ in range(1 << addr_width)] 11 | 12 | 13 | class ROM(m.Circuit): 14 | io = m.IO( 15 | RADDR=m.In(m.Bits[addr_width]), 16 | RDATA=m.Out(m.Bits[data_width]), 17 | CLK=m.In(m.Clock) 18 | ) 19 | 20 | regs = [m.Register(m.Bits[data_width], init=int(init[i]))() 21 | for i in range(1 << addr_width)] 22 | for reg in regs: 23 | reg.I <= reg.O 24 | io.RDATA <= m.mux([reg.O for reg in regs], io.RADDR) 25 | 26 | 27 | class RAM(m.Circuit): 28 | io = m.IO( 29 | RADDR=m.In(m.Bits[addr_width]), 30 | RDATA=m.Out(m.Bits[data_width]), 31 | WADDR=m.In(m.Bits[addr_width]), 32 | WDATA=m.In(m.Bits[data_width]), 33 | WE=m.In(m.Bit), 34 | CLK=m.In(m.Clock), 35 | RESET=m.In(m.Reset) 36 | ) 37 | 38 | regs = [m.Register(m.Bits[data_width], init=int(init[i]), has_enable=True, 39 | reset_type=m.Reset) 40 | for i in range(1 << addr_width)] 41 | for i, reg in enumerate(regs): 42 | reg.I <= io.WDATA 43 | reg.CE <= (io.WADDR == m.bits(i, addr_width)) & io.WE 44 | io.RDATA <= m.mux([reg.O for reg in regs], io.RADDR) 45 | -------------------------------------------------------------------------------- /tutorial/passthrough.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | 4 | 5 | class Passthrough(m.Circuit): 6 | io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit)) 7 | 8 | io.O <= io.I 9 | 10 | 11 | passthrough_tester = fault.Tester(Passthrough) 12 | passthrough_tester.circuit.I = 1 13 | passthrough_tester.eval() 14 | passthrough_tester.circuit.O.expect(1) 15 | passthrough_tester.compile_and_run("verilator") 16 | -------------------------------------------------------------------------------- /tutorial/reset_tester.py: -------------------------------------------------------------------------------- 1 | import fault 2 | 3 | 4 | class ResetTester(fault.Tester): 5 | def __init__(self, circuit, clock, reset_port): 6 | super().__init__(circuit, clock) 7 | self.reset_port = reset_port 8 | 9 | def reset(self): 10 | self.poke(self.reset_port, 1) 11 | self.eval() 12 | self.poke(self.reset_port, 0) 13 | self.eval() 14 | -------------------------------------------------------------------------------- /tutorial/test.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import fault 3 | 4 | 5 | class Passthrough(m.Circuit): 6 | io = m.IO(I=m.In(m.Bit), O=m.Out(m.Bit)) 7 | 8 | io.O <= io.I 9 | 10 | 11 | passthrough_tester = fault.Tester(Passthrough) 12 | passthrough_tester.circuit.I = 1 13 | passthrough_tester.eval() 14 | passthrough_tester.circuit.O.expect(1) 15 | passthrough_tester.compile_and_run("verilator") 16 | -------------------------------------------------------------------------------- /tutorial/tff.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | # import mantle 3 | import fault 4 | 5 | 6 | class TFF(m.Circuit): 7 | io = m.IO(O=m.Out(m.Bit), CLK=m.In(m.Clock)) 8 | 9 | reg = mantle.Register(None, name="tff_reg") 10 | reg.CLK <= io.CLK 11 | reg.I <= ~reg.O 12 | io.O <= reg.O 13 | 14 | 15 | tff_tester = fault.Tester(TFF, clock=TFF.CLK) 16 | for i in range(8): 17 | tff_tester.circuit.O.expect(i % 2) 18 | tff_tester.step(2) 19 | tff_tester.compile_and_run("verilator") 20 | --------------------------------------------------------------------------------