├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── general-q-a.md └── workflows │ └── release.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE ├── Makefile ├── Makefile.toml ├── README.md ├── instrumentation ├── README.md └── src │ ├── difuzz │ ├── ControlRegisterCoverage.scala │ ├── GraphLedger.scala │ ├── InstrAssert.scala │ ├── InstrCov.scala │ ├── InstrReset.scala │ └── README.md │ ├── rfuzz │ ├── MetaReset.scala │ ├── NoDedup.scala │ ├── ProfilingTransform.scala │ ├── README.md │ ├── ReplaceMems.scala │ ├── SparseMem.scala │ ├── SplitMuxConditions.scala │ ├── TomlGenerator.scala │ └── package.scala │ ├── sic │ ├── Builder.scala │ ├── Coverage.scala │ ├── CoverageInfoEmitter.scala │ ├── CoverageShellOptions.scala │ ├── CoverageStatisticsPass.scala │ ├── FsmCoverage.scala │ ├── FsmCoveragePass.scala │ ├── FsmInfoPass.scala │ ├── LineCoverage.scala │ ├── LineCoveragePass.scala │ ├── README.md │ ├── ReadyValidCoverage.scala │ ├── ReadyValidCoveragePass.scala │ ├── RemoveCoverPointsPass.scala │ ├── ToggleCoverage.scala │ ├── ToggleCoveragePass.scala │ └── passes │ │ ├── AddResetAssumptionPass.scala │ │ ├── AliasAnalysis.scala │ │ ├── ChangeMainPass.scala │ │ ├── KeepClockAndResetPass.scala │ │ ├── RandomStateInit.scala │ │ └── RegisterResetAnnotationPass.scala │ └── xfuzz │ ├── ControlRegisterCoverTransform.scala │ ├── CoverPointTransform.scala │ ├── DontTouchClockAndResetTransform.scala │ └── MuxCoverTransform.scala ├── scripts ├── mem_stats.py ├── separator └── visualizer ├── src ├── coverage.rs ├── fuzzer.rs ├── harness.rs ├── lib.rs └── monitor.rs └── xfuzz /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: poemonsense 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: poemonsense 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general-q-a.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General Q&A 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: question 6 | assignees: poemonsense 7 | 8 | --- 9 | 10 | **Describe the question** 11 | 12 | **What you expect us to do for you** 13 | 14 | **Additional context** 15 | Add any other context about the problem here. 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Release 2 | 3 | on: 4 | schedule: 5 | # Runs at 07:30 Asia/Shanghai (UTC+8) => 23:30 UTC (previous day) 6 | - cron: '30 23 * * *' 7 | push: 8 | branches: [ release ] 9 | 10 | jobs: 11 | # 1. Build artifacts in parallel 12 | build: 13 | strategy: 14 | matrix: 15 | os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04] 16 | runs-on: ubuntu-latest 17 | container: ghcr.io/openxiangshan/${{ matrix.os }}:latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: recursive 23 | 24 | - name: Build libfuzzer.a on ${{ matrix.os }} 25 | run: | 26 | # install Rust 27 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 28 | source $HOME/.cargo/env 29 | cargo install cargo-make 30 | # build libfuzzer.a 31 | make build 32 | # rename it before upload 33 | # downloaded artifacts will have original names (different from the artifact name) 34 | mv target/release/libfuzzer.a libfuzzer-${{ matrix.os }}.a 35 | 36 | - name: Upload artifact for ${{ matrix.os }} 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: libfuzzer-${{ matrix.os }}.a 40 | path: libfuzzer-${{ matrix.os }}.a 41 | 42 | # 2. Create release if there are new commits since last tag 43 | release: 44 | needs: build 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v4 48 | 49 | - uses: actions/download-artifact@v4 50 | with: 51 | merge-multiple: true 52 | 53 | - name: Fetch all tags 54 | run: git fetch --tags 55 | 56 | - name: Check for commits since last release 57 | id: check_changes 58 | run: | 59 | LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1) || echo "") 60 | if [ -z "$LATEST_TAG" ]; then 61 | echo "::set-output name=changed::true" 62 | else 63 | COUNT=$(git rev-list ${LATEST_TAG}..HEAD --count) 64 | if [ "$COUNT" -gt 0 ]; then 65 | echo "::set-output name=changed::true" 66 | else 67 | echo "::set-output name=changed::false" 68 | fi 69 | fi 70 | shell: bash 71 | 72 | - name: Set release date (UTC+8) 73 | id: vars 74 | run: echo "RELEASE_DATE=$(date -u -d '8 hours' +'%Y-%m-%d')" >> $GITHUB_ENV 75 | 76 | - name: Create Git tag 77 | if: steps.check_changes.outputs.changed == 'true' 78 | run: | 79 | git config user.name "github-actions[bot]" 80 | git config user.email "github-actions[bot]@users.noreply.github.com" 81 | git tag -a "${{ env.RELEASE_DATE }}" -m "Release ${{ env.RELEASE_DATE }}" 82 | git push origin "${{ env.RELEASE_DATE }}" 83 | 84 | - name: Create Release 85 | if: steps.check_changes.outputs.changed == 'true' 86 | uses: softprops/action-gh-release@v2 87 | with: 88 | tag_name: ${{ env.RELEASE_DATE }} 89 | name: ${{ env.RELEASE_DATE }} 90 | make_latest: 'true' 91 | fail_on_unmatched_files: true 92 | preserve_order: true 93 | files: | 94 | libfuzzer-ubuntu-20.04.a 95 | libfuzzer-ubuntu-22.04.a 96 | libfuzzer-ubuntu-24.04.a 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is from https://github.com/github/gitignore/blob/main/Rust.gitignore 2 | 3 | # Generated by Cargo 4 | # will have compiled files and executables 5 | debug/ 6 | target/ 7 | 8 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 9 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 10 | Cargo.lock 11 | 12 | # These are backup files generated by rustfmt 13 | **/*.rs.bk 14 | 15 | # MSVC Windows builds of rustc generate these, which store debugging information 16 | *.pdb 17 | 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "LibAFL"] 2 | path = LibAFL 3 | url = https://github.com/AFLplusplus/LibAFL.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xfuzz" 3 | version = "0.1.0" 4 | edition = "2024" 5 | description = "Fuzzing General-Purpose Hardware Designs with Software Fuzzers" 6 | 7 | [features] 8 | default = ["std"] 9 | std = [] 10 | 11 | [dependencies] 12 | libafl = { path = "./LibAFL/libafl/", features = ["prelude"] } 13 | libafl_bolts = { path = "./LibAFL/libafl_bolts/" } 14 | clap = { version = "4.5.20", features = ["derive"] } 15 | libc = "0.2" 16 | md5 = "0.7" 17 | rand = "0.8.5" 18 | 19 | [lib] 20 | name = "fuzzer" 21 | crate-type = ["staticlib"] 22 | 23 | [profile.release] 24 | lto = true 25 | codegen-units = 1 26 | opt-level = 3 27 | debug = true 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 木兰宽松许可证, 第2版 2 | 3 | 2020年1月 http://license.coscl.org.cn/MulanPSL2 4 | 5 | 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 6 | 7 | 0. 定义 8 | 9 | “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 10 | 11 | “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 12 | 13 | “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 14 | 15 | “法人实体”是指提交贡献的机构及其“关联实体”。 16 | 17 | “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 18 | 19 | 1. 授予版权许可 20 | 21 | 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 22 | 23 | 2. 授予专利许可 24 | 25 | 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 26 | 27 | 3. 无商标许可 28 | 29 | “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 30 | 31 | 4. 分发限制 32 | 33 | 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 34 | 35 | 5. 免责声明与责任限制 36 | 37 | “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 38 | 39 | 6. 语言 40 | 41 | “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 42 | 43 | 条款结束 44 | 45 | 如何将木兰宽松许可证,第2版,应用到您的软件 46 | 47 | 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 48 | 49 | 1,请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 50 | 51 | 2,请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 52 | 53 | 3,请将如下声明文本放入每个源文件的头部注释中。 54 | 55 | Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. 56 | 57 | January 2020 http://license.coscl.org.cn/MulanPSL2 58 | 59 | Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 60 | 61 | 0. Definition 62 | 63 | Software means the program and related documents which are licensed under this License and comprise all Contribution(s). 64 | 65 | Contribution means the copyrightable work licensed by a particular Contributor under this License. 66 | 67 | Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. 68 | 69 | Legal Entity means the entity making a Contribution and all its Affiliates. 70 | 71 | Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 72 | 73 | 1. Grant of Copyright License 74 | 75 | Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 76 | 77 | 2. Grant of Patent License 78 | 79 | Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 80 | 81 | 3. No Trademark License 82 | 83 | No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 84 | 85 | 4. Distribution Restriction 86 | 87 | You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 88 | 89 | 5. Disclaimer of Warranty and Limitation of Liability 90 | 91 | THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 92 | 93 | 6. Language 94 | 95 | THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. 96 | 97 | END OF THE TERMS AND CONDITIONS 98 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | 3 | build: 4 | @cargo make build-all 5 | 6 | init: 7 | git submodule update --init 8 | 9 | rebuild: 10 | @cargo make --quiet rebuild 11 | 12 | %: 13 | @cargo make $@ 14 | 15 | Makefile: ; 16 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.clean] 2 | command = "cargo" 3 | args = ["clean"] 4 | 5 | [tasks.build-all] 6 | command = "cargo" 7 | args = ["build", "--release"] 8 | 9 | [tasks.rebuild] 10 | dependencies = ["clean", "build-all"] 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xfuzz 2 | 3 | This project aims at fuzzing general-purpose hardware designs with software fuzzers. 4 | For now, we are using the [LibAFL](https://github.com/AFLplusplus/LibAFL) as the underlying fuzzing framework. 5 | 6 | ## Usage 7 | 8 | This is a Rust project. To install Rust, please see [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install). After the installation of Rust, please run `cargo install cargo-make` to install the cargo-make library. 9 | 10 | The [Makefile](Makefile) provides some simple commands to build the target `libfuzzer.a`. 11 | 12 | - `make init` to initialize the project 13 | - `make clean` to clean up the project 14 | - `make build` to build the dependencies 15 | - `make rebuild` to clean up and build the project 16 | 17 | ## Example 18 | 19 | We have a real example using the rocket-chip (DUT) and Spike (REF) [here](https://github.com/OpenXiangShan/difftest/blob/master/.github/workflows/nightly.yml#L9). 20 | This `test-difftest-fuzzing` CI test builds the fuzzer and runs it for 10000 runs (testcases). 21 | 22 | Run `fuzzer --help` for a full list of runtime arguments. 23 | 24 | An example build flow with rocket-chip as the design-under-test is listed as follows: 25 | 26 | ```bash 27 | git clone https://github.com/OpenXiangShan/xfuzz.git 28 | cd xfuzz && make init && make build && cd .. 29 | 30 | git clone https://github.com/OpenXiangShan/riscv-arch-test.git 31 | cd riscv-arch-test/riscv-test-suite 32 | make build_I -j2 33 | rm build/*.elf build/*.txt 34 | cd ../.. 35 | 36 | git clone https://github.com/OpenXiangShan/riscv-isa-sim.git 37 | # Replace ROCKET_CHIP with other CPU models (NUTSHELL, XIANGSHAN) if necessary 38 | # SANCOV=1 lets the C++ compile instrument coverage definitions into the elf 39 | # If you are not using the LLVM instrumented coverage, remove SANCOV=1 and rebuild it after `make clean` 40 | make -C riscv-isa-sim/difftest CPU=ROCKET_CHIP SANCOV=1 -j16 41 | 42 | # Some shortcuts. They are not required if you understand why we set them 43 | export SPIKE_HOME=$(pwd)/riscv-isa-sim 44 | export XFUZZ_HOME=$(pwd)/xfuzz 45 | export NOOP_HOME=$(pwd)/rocket-chip 46 | export CORPUS=$(pwd)/riscv-arch-test/riscv-test-suite/build 47 | 48 | # Here we build the rocket-chip 49 | # For other CPU models, please refer to their README for help 50 | git clone -b dev-difftest --single-branch https://github.com/OpenXiangShan/rocket-chip.git 51 | cd rocket-chip && make init && make bootrom 52 | make emu XFUZZ=1 REF=$SPIKE_HOME/difftest/build/riscv64-spike-so LLVM_COVER=1 -j16 53 | 54 | # Run the fuzzer for 100 inputs 55 | ./build/fuzzer -f --max-runs 100 --corpus-input $CORPUS -- --max-cycles 10000 56 | ``` 57 | 58 | If you are using [NEMU](https://github.com/OpenXiangShan/NEMU) as the REF with LLVM branch coverage instrumented, you may build it 59 | with the following commands: `make staticlib CC="clang -fsanitize-coverage=trace-pc-guard -fsanitize-coverage=pc-table -g" CXX="clang++ -fsanitize-coverage=trace-pc-guard -fsanitize-coverage=pc-table -g"`. 60 | If you are not using LLVM branch coverage, you can simply follow the NEMU docs to build the general-purpose dynamic library `.so` file. 61 | 62 | If you are fuzzing the [XiangShan](https://github.com/OpenXiangShan/XiangShan) RISC-V processor, for example, with LLVM branch coverage instrumented NEMU, 63 | you can use the following command *after* you build the static NEMU library: `make emu XFUZZ=1 REF=$NEMU_HOME/build/libriscv64-nemu-interpreter.a LLVM_COVER=1 EMU_THREADS=16 WITH_CHISELDB=0 WITH_CONSTANTIN=0 EMU_TRACE=1 CONFIG=FuzzConfig`. 64 | If you are not using LLVM branch coverage, please read the following sections carefully and try the flow by yourself. 65 | 66 | ## Integrating Hardware Designs 67 | 68 | This repository is not a self-running repository. 69 | The created static library `libfuzzer.a` is expected be linked into a simulation runner. 70 | For example, it could be passed to [Verilator](https://github.com/verilator/verilator) as an external library for linking. 71 | The required interfaces between Rust and C/C++ modules are listed exclusively at [the harness file](src/harness.rs). 72 | 73 | We have upgraded the [DiffTest](https://github.com/OpenXiangShan/difftest) environment to support these interfaces. 74 | Please refer to the [DiffTest README.md](https://github.com/OpenXiangShan/difftest/blob/master/README.md) for connecting your CPU design with the fuzzer. 75 | 76 | Specifically, for the coverage guidance for fuzzing, currently both C++ branch coverage via LLVM sanitizer and FIRRTL-instrumented coverage are supported. 77 | For C++ branch coverage, both [Spike](https://github.com/OpenXiangShan/riscv-isa-sim) and [NEMU](https://github.com/OpenXiangShan/NEMU) are supported. 78 | For FIRRTL-instrumented coverage, please refer to the [Coverage Instrumentation for Chisel Designs section](#coverage-instrumentation-for-chisel-designs) of this README. 79 | Each of the supported coverage metrics can exist independently and is configurable in the building steps. 80 | The build commands are self-explained and we assume the users can understand them easily. 81 | 82 | Once you build the simulation executable, [xfuzz](xfuzz) provides some Python scripts to run the fuzzer and parse the outputs. 83 | 84 | ## Coverage Instrumentation for Chisel Designs 85 | 86 | To instrument Chisel coverage metrics into your Chisel designs and use them as the coverage feedback for fuzzing, we provide some useful FIRRTL transforms in the `instrumentation` directory. 87 | These transforms are mostly migrated from some other projects, including [ekiwi/rfuzz](https://github.com/ekiwi/rfuzz), [compsec-snu/difuzz-rtl](https://github.com/compsec-snu/difuzz-rtl), and [ekiwi/simulator-independent-coverage](https://github.com/ekiwi/simulator-independent-coverage). 88 | 89 | It's worth noting current instrumentation transforms support only Chisel<=3.6.0 designs due to FIRRTL constains. 90 | For a reference design, please refer to the [rocket-chip](https://github.com/OpenXiangShan/rocket-chip/tree/dev-difftest) project. 91 | Generally, the requirements include: 1) adding the submodule and the Scala compilation recipe to your Chisel design (example at [here](https://github.com/OpenXiangShan/rocket-chip/blob/dev-difftest/build.sc#L87-L111)), and 2) enabling FIRRTL transforms when necessary (example at [here](https://github.com/OpenXiangShan/rocket-chip/blob/dev-difftest/generator/chisel3/ccover.patch)). 92 | 93 | Once you have successfully instrumented your Chisel design, tell the DiffTest to include FIRRTL coverage metrics using `FIRRTL_COVER=your_choice_1,your_choice_2`. 94 | Then, the generated coverage files (`build/generated-src/firrtl-cover.*`) will be detected and captured by DiffTest. 95 | The Chisel/FIRRTL coverage metrics will have names as `firrtl.your_choice1` and `firrtl.your_choice2`. 96 | Lastly, replace the default `llvm.branch` coverage feedback for fuzzing with the new ones when calling the fuzzer. 97 | 98 | All supported Chisel/FIRRTL coverage metrics are listed in the `CoverPoint.getTransforms` function [here](instrumentation/src/xfuzz/CoverPointTransform.scala). 99 | 100 | We sincerely thank the original authors for these FIRRTL transforms. 101 | The rights of the source code are reserved by the original repositories and authors. 102 | If you don't like this project integrating your code, please feel free to tell us through GitHub issues to allow us remove them. 103 | 104 | An example to build rocket-chip with FIRRTL coverage instrumentation is as follows. 105 | It is required to build the spike-so (without `SANCOV=1`) before `make emu`. 106 | If you are building the spike-so with `SANCOV=1`, please add `LLVM_COVER=1` as well. 107 | 108 | ```bash 109 | git clone -b dev-difftest --single-branch https://github.com/OpenXiangShan/rocket-chip.git 110 | cd rocket-chip && make init && make bootrom 111 | 112 | # Apply the patch to include command line arguments and coverage transforms 113 | git apply generator/chisel3/ccover.patch 114 | 115 | # Add the ccover directory. You may instead add it as a git submodule if you want to track the changes 116 | git clone https://github.com/OpenXiangShan/xfuzz.git ccover 117 | 118 | # Now build the fuzzer with FIRRTL_COVER instead of LLVM_COVER 119 | # Please make sure you have successfully built the spike-so at SPIKE_HOME without SANCOV=1 120 | # To see the full list of supported FIRRTL coverage metrics, please read this README again 121 | make emu REF=$SPIKE_HOME/difftest/build/riscv64-spike-so XFUZZ=1 FIRRTL_COVER=mux,control,line,toggle,ready_valid -j16 122 | ``` 123 | 124 | You may refer to the [FuzzingNutShell](https://github.com/poemonsense/FuzzingNutShell) repo for a demo. 125 | 126 | ## License 127 | 128 | This project is licensed under [the Mulan Permissive Software License, Version 2](LICENSE) except explicit specified. 129 | All files in the `instrumentation` directory are licensed under their original licenses with rights reserved by the original repositories and authors. 130 | 131 | This project has been adopted by researchers from academia and industry. 132 | We appreciate their contributions for the open-source community. 133 | Your valuable Issues and Pull Requests are always welcomed. 134 | 135 | - PathFuzz: Broadening Fuzzing Horizons with Footprint Memory for CPUs. DAC'23. [DOI (ACM, Open Access)](https://doi.org/10.1145/3649329.3655911), [Presentation (ChinaSys)](https://drive.google.com/file/d/1SfXSfWkwMqqVNuTauUpFvfP5Q5JV2oMw/view?usp=sharing). 136 | -------------------------------------------------------------------------------- /instrumentation/README.md: -------------------------------------------------------------------------------- 1 | # Chisel Coverage Instrumentatio Transforms 2 | 3 | This directory contains some FIRRTL transforms for coverage instrumentation of Chisel designs. 4 | The code is migrated from the corresponding GitHub repositories. 5 | The license is reserved by the original repositories and authors. 6 | 7 | ## Example Usage 8 | 9 | Refer to the [rocket-chip](https://github.com/OpenXiangShan/rocket-chip/tree/dev-difftest) project. 10 | 11 | - In `build.sc`: 12 | 13 | ```scala 14 | trait CcoverModule extends SbtModule 15 | with HasChisel 16 | with Cross.Module[String] { 17 | 18 | def scalaVersion: T[String] = T(v.scala) 19 | 20 | def sourceRoot = T.sources { T.workspace / "ccover" / "instrumentation" / "src" } 21 | 22 | private def getSources(p: PathRef) = if (os.exists(p.path)) os.walk(p.path) else Seq() 23 | 24 | def allSources = T { sourceRoot().flatMap(getSources).map(PathRef(_)) } 25 | 26 | def chiselModule: Option[ScalaModule] = None 27 | 28 | def chiselPluginJar: T[Option[PathRef]] = None 29 | 30 | def chiselIvy = Some(v.chiselCrossVersions(crossValue)._1) 31 | 32 | def chiselPluginIvy = Some(v.chiselCrossVersions(crossValue)._2) 33 | 34 | def ivyDeps = super.ivyDeps() ++ Agg(ivy"edu.berkeley.cs::chiseltest:0.6.2") 35 | 36 | } 37 | 38 | object ccover extends Cross[CcoverModule](v.chiselCrossVersions.keys.toSeq) 39 | ``` 40 | 41 | - In your top-level Chisel generator class: 42 | 43 | ```diff 44 | diff --git a/generator/chisel3/src/main/scala/FuzzTop.scala b/generator/chisel3/src/main/scala/FuzzTop.scala 45 | index 983345472..2d94cb533 100644 46 | --- a/generator/chisel3/src/main/scala/FuzzTop.scala 47 | +++ b/generator/chisel3/src/main/scala/FuzzTop.scala 48 | @@ -3,6 +3,7 @@ package freechips.rocketchip.system 49 | import chisel3.stage.{ChiselCli, ChiselGeneratorAnnotation, ChiselStage} 50 | import firrtl.options.Shell 51 | import firrtl.stage.FirrtlCli 52 | +import xfuzz.CoverPoint 53 | 54 | class FuzzStage extends ChiselStage { 55 | override val shell: Shell = new Shell("rocket-chip") 56 | @@ -16,6 +17,6 @@ object FuzzMain { 57 | ChiselGeneratorAnnotation(() => { 58 | freechips.rocketchip.diplomacy.DisableMonitors(p => new SimTop()(p))(new FuzzConfig) 59 | }) 60 | - )) 61 | + ) ++ CoverPoint.getTransforms(args)._2) 62 | } 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /instrumentation/src/difuzz/ControlRegisterCoverage.scala: -------------------------------------------------------------------------------- 1 | // Modified version, 2 | // Instrumentation for control coverage 3 | 4 | package difuzz 5 | 6 | import firrtl.Mappers._ 7 | import firrtl._ 8 | import firrtl.ir._ 9 | import firrtl.passes.{InferTypes, ResolveFlows, ResolveKinds, LowerTypes} 10 | import firrtl.stage.TransformManager.TransformDependency 11 | 12 | import java.io.{File, PrintWriter} 13 | import scala.collection.{Map, Set, mutable} 14 | 15 | object ModuleInfo { 16 | def apply(mod: DefModule, gLedger: GraphLedger): ModuleInfo = { 17 | val ctrlSrcs = gLedger.findMuxSrcs 18 | val muxSrcs = gLedger.getMuxSrcs 19 | val insts = gLedger.getInstances 20 | val regs = gLedger.findRegs 21 | val vecRegs = gLedger.findVecRegs 22 | val dirInRegs = gLedger.findDirInRegs 23 | 24 | new ModuleInfo(mod.name, ctrlSrcs, muxSrcs, insts, regs, vecRegs, dirInRegs) 25 | } 26 | } 27 | 28 | class ModuleInfo(val mName: String, 29 | val ctrlSrcs: Map[String, Set[Node]], 30 | val muxSrcs: Map[Mux, Set[String]], 31 | val insts: Set[WDefInstance], 32 | val regs: Set[DefRegister], 33 | val vecRegs: Set[Tuple3[Int, String, Set[String]]], 34 | val dirInRegs: Set[DefRegister]) { 35 | 36 | var covSize: Int = 0 37 | var regNum: Int = 0 38 | var ctrlRegNum: Int = 0 39 | var muxNum: Int = 0 40 | var muxCtrlNum: Int = 0 41 | var regBitWidth: Int = 0 42 | var ctrlRegBitWidth: Int = 0 43 | var ctrlBitWidth: Int = 0 44 | var assertReg: Option[DefRegister] = None 45 | 46 | def printInfo(): Unit = { 47 | print(s"${mName} Information\n") 48 | } 49 | 50 | def saveCovResult(instrCov: InstrCov): Unit = { 51 | covSize = instrCov.covMapSize 52 | regNum = regs.size 53 | ctrlRegNum = ctrlSrcs("DefRegister").size 54 | regBitWidth = regs.toSeq.map(reg => reg.tpe match { 55 | case utpe: UIntType => utpe.width.asInstanceOf[IntWidth].width.toInt 56 | case stpe: SIntType => stpe.width.asInstanceOf[IntWidth].width.toInt 57 | case _ => throw new Exception(s"${reg.name} does not have IntType\n") 58 | }).sum 59 | 60 | ctrlRegBitWidth = ctrlSrcs("DefRegister").toSeq.map(reg => { 61 | reg.node.asInstanceOf[DefRegister].tpe match { 62 | case utpe: UIntType => utpe.width.asInstanceOf[IntWidth].width.toInt 63 | case stpe: SIntType => stpe.width.asInstanceOf[IntWidth].width.toInt 64 | case _ => throw new Exception(s"${reg.name} does not have IntType\n") 65 | } 66 | }).sum 67 | 68 | ctrlBitWidth = instrCov.totBitWidth 69 | muxNum = muxSrcs.size 70 | muxCtrlNum = muxSrcs.map(_._1.cond.serialize).toSet.size 71 | } 72 | } 73 | 74 | class ControlRegisterCoverage extends Transform with DependencyAPIMigration { 75 | override def prerequisites: Seq[TransformDependency] = firrtl.stage.Forms.LowForm 76 | override def optionalPrerequisites: Seq[TransformDependency] = firrtl.stage.Forms.LowFormOptimized 77 | override def optionalPrerequisiteOf: Seq[TransformDependency] = firrtl.stage.Forms.LowEmitters 78 | override def invalidates(a: Transform) = a match { 79 | case InferTypes | ResolveKinds | ResolveFlows | LowerTypes => true 80 | case _ => false 81 | } 82 | 83 | val moduleInfos = mutable.Map[String, ModuleInfo]() 84 | var totNumOptRegs = 0 85 | 86 | def execute(state: CircuitState): CircuitState = { 87 | val circuit = state.circuit 88 | 89 | // print("==================== Finding Control Registers ====================\n") 90 | for (m <- circuit.modules) { 91 | val gLedger = new GraphLedger(m).parseModule 92 | moduleInfos(m.name) = ModuleInfo(m, gLedger) 93 | // gLedger.printLog() 94 | } 95 | 96 | // print("===================================================================\n") 97 | 98 | // print("====================== Instrumenting Coverage =====================\n") 99 | 100 | val extModules = circuit.modules.filter(_.isInstanceOf[ExtModule]).map(_.name) 101 | val instrCircuit = circuit map { m: DefModule => 102 | val instrCov = new InstrCov(m, moduleInfos(m.name), extModules) 103 | val mod = instrCov.instrument() 104 | totNumOptRegs = totNumOptRegs + instrCov.numOptRegs 105 | // instrCov.printLog() 106 | 107 | moduleInfos(m.name).saveCovResult(instrCov) 108 | mod 109 | } 110 | 111 | // print("===================================================================\n") 112 | 113 | // print("====================== Instrumenting metaAssert ===================\n") 114 | // val assertCircuit = instrCircuit map { m: DefModule => 115 | // val instrAssert = new InstrAssert(m, moduleInfos(m.name)) 116 | // val mod = instrAssert.instrument() 117 | // // convertStop.printLog() 118 | // mod 119 | // } 120 | 121 | // print("===================================================================\n") 122 | 123 | // print("====================== Instrumenting MetaReset ====================\n") 124 | 125 | // val metaResetCircuit = assertCircuit map { m: DefModule => 126 | // val instrReset = new InstrReset(m, moduleInfos(m.name)) 127 | // val mod = instrReset.instrument() 128 | // instrReset.printLog() 129 | // mod 130 | // } 131 | 132 | // print("===================================================================\n") 133 | 134 | print("\n====================== Instrumentation Summary ==================\n") 135 | printSummary(circuit.main) 136 | print("===================================================================\n") 137 | 138 | /* Dump hierarchy of the modules to instrument system tasks */ 139 | val hierWriter = new PrintWriter(new File(s"${instrCircuit.main}_hierarchy.txt")) 140 | for ((mName, mInfo) <- moduleInfos) { 141 | val insts = mInfo.insts 142 | hierWriter.write(s"$mName\t${insts.size}\t${mInfo.covSize}\n") 143 | insts.map(inst => hierWriter.write(s"\t${inst.module}\t${inst.name}\n")) 144 | } 145 | hierWriter.close() 146 | 147 | state.copy(instrCircuit) 148 | 149 | } 150 | 151 | def printSummary(topName: String) : Unit = { 152 | assert(moduleInfos.size > 0, "printSummary must be called after instrumentation\n") 153 | 154 | val moduleNums: Map[String, Int] = moduleInfos.map(tuple => { 155 | (tuple._1, findModules(topName, tuple._1)) 156 | }).toMap 157 | 158 | val totRegNum = moduleInfos.foldLeft(0)((totNum, tuple) => { 159 | totNum + (tuple._2.regNum * moduleNums(tuple._1)) 160 | }) 161 | 162 | val totCtrlRegNum = moduleInfos.foldLeft(0)((totNum, tuple) => { 163 | totNum + (tuple._2.ctrlRegNum * moduleNums(tuple._1)) 164 | }) 165 | 166 | val totMuxNum = moduleInfos.foldLeft(0)((totNum, tuple) => { 167 | totNum + (tuple._2.muxNum * moduleNums(tuple._1)) 168 | }) 169 | 170 | val totRegBitWidth = moduleInfos.foldLeft(0)((totBitWidth, tuple) => { 171 | totBitWidth + (tuple._2.regBitWidth * moduleNums(tuple._1)) 172 | }) 173 | 174 | val totCtrlRegBitWidth = moduleInfos.foldLeft(0)((totBitWidth, tuple) => { 175 | totBitWidth + (tuple._2.ctrlRegBitWidth * moduleNums(tuple._1)) 176 | }) 177 | 178 | val totCtrlBitWidth_optimized = moduleInfos.foldLeft(0)((totBitWidth, tuple) => { 179 | totBitWidth + (tuple._2.ctrlBitWidth * moduleNums(tuple._1)) 180 | }) 181 | 182 | val totMuxBitWidth = totMuxNum 183 | 184 | val totMuxCtrlBitWidth = moduleInfos.foldLeft(0)((totBitWidth, tuple) => { 185 | totBitWidth + (tuple._2.muxCtrlNum * moduleNums(tuple._1)) 186 | }) 187 | 188 | print(s"Total number of registers: ${totRegNum}\n" + 189 | s"Total number of control registers: ${totCtrlRegNum}\n" + 190 | s"Total number of muxes: ${totMuxNum}\n" + 191 | s"Total number of optimized registers: ${totNumOptRegs}\n" + 192 | s"Total bit width of registers: ${totRegBitWidth}\n" + 193 | s"Total bit width of control registers: ${totCtrlRegBitWidth}\n" + 194 | s"Optimized total bit width of control registers: ${totCtrlBitWidth_optimized}\n" + 195 | s"Total bit width of muxes: ${totMuxBitWidth}\n" + 196 | s"Total bit width of muxes: ${totMuxCtrlBitWidth}\n" 197 | ) 198 | } 199 | 200 | def findModules(topName: String, moduleName: String): Int = { 201 | if (topName == moduleName) 1 202 | else { 203 | moduleInfos(topName).insts.foldLeft(0)((num, inst) => { 204 | num + findModules(inst.module, moduleName) 205 | }) 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /instrumentation/src/difuzz/InstrAssert.scala: -------------------------------------------------------------------------------- 1 | package difuzz 2 | 3 | import firrtl.PrimOps.Or 4 | import firrtl.ir._ 5 | import firrtl._ 6 | 7 | import scala.collection.mutable.ListBuffer 8 | 9 | class InstrAssert(mod: DefModule, mInfo: ModuleInfo) { 10 | private val mName = mod.name 11 | private val insts = mInfo.insts 12 | 13 | def instrument(): DefModule = { 14 | mod match { 15 | case m: Module => { 16 | val block = m.body 17 | val stmts = block.asInstanceOf[Block].stmts 18 | val (clockName, resetName, hasClockReset) = hasClockAndReset(m) 19 | 20 | val assertPort = Port(NoInfo, "metaAssert", Output, UIntType(IntWidth(1))) 21 | 22 | val stops = ListBuffer[Stop]() 23 | findStop(stops)(block) 24 | 25 | val stopEns = stops.zipWithIndex.map(tup => DefNode(NoInfo, s"stopEn${tup._2}", tup._1.en)) 26 | val instAsserts = insts.map(inst => 27 | (inst, DefWire(NoInfo, s"${inst.name}_metaAssert_wire", UIntType(IntWidth(1))))) 28 | val instAssertCons = instAsserts.map(tup => 29 | Connect(NoInfo, WRef(tup._2), WSubField(WRef(tup._1), "metaAssert")) 30 | ) 31 | 32 | val (topOr, orStmts) = makeOr(stopEns.map(en => WRef(en)).toSeq ++ instAsserts.map(tup => WRef(tup._2)), 0) 33 | 34 | val conStmts = if (hasClockReset && topOr != None) { 35 | val assertReg = DefRegister(NoInfo, s"${mName}_metaAssert", UIntType(IntWidth(1)), 36 | WRef(clockName, ClockType, PortKind, SourceFlow), 37 | UIntLiteral(0, IntWidth(1)), 38 | WRef(s"${mName}_metaAssert", UIntType(IntWidth(1)), RegKind, UnknownFlow)) 39 | mInfo.assertReg = Some(assertReg) 40 | val or = DoPrim(Or, Seq(WRef(assertReg), topOr.get), Seq(), UIntType(IntWidth(1))) 41 | val assertRegCon = Connect(NoInfo, WRef(assertReg), or) 42 | val portCon = Connect(NoInfo, WRef(assertPort), WRef(assertReg)) 43 | Seq[Statement](assertReg, assertRegCon, portCon) 44 | } else if (topOr != None) { 45 | val portCon = Connect(NoInfo, WRef(assertPort), topOr.get) 46 | Seq[Statement](portCon) 47 | } else { 48 | val portCon = Connect(NoInfo, WRef(assertPort), UIntLiteral(0)) 49 | Seq[Statement](portCon) 50 | } 51 | 52 | val ports = (mod.ports :+ assertPort) 53 | val newStmts = stmts ++ stopEns ++ instAsserts.map(tup => tup._2) ++ instAssertCons ++ orStmts ++ conStmts 54 | val newBlock = Block(newStmts) 55 | Module(mod.info, mName, ports, newBlock) 56 | } 57 | case ext: ExtModule => ext 58 | case other => other 59 | } 60 | } 61 | 62 | def makeOr(stopEns: Seq[WRef], id: Int): (Option[WRef], Seq[Statement]) = { 63 | stopEns.length match { 64 | case 0 => (None, Seq[Statement]()) 65 | case 1 => { 66 | (Some(stopEns.head), Seq[Statement]()) 67 | } 68 | case 2 => { 69 | val or_wire = DefWire(NoInfo, mName + s"_or${id}", UIntType(IntWidth(1))) 70 | val or_op = DoPrim(Or, stopEns, Seq(), UIntType(IntWidth(1))) 71 | val or_connect = Connect(NoInfo, WRef(or_wire), or_op) 72 | (Some(WRef(or_wire)), Seq[Statement](or_wire, or_connect)) 73 | } 74 | case _ => { 75 | val (or1, stmts1) = makeOr(stopEns.splitAt(stopEns.length / 2)._1, 2 * id + 1) 76 | val (or2, stmts2) = makeOr(stopEns.splitAt(stopEns.length / 2)._2, 2 * id + 2) 77 | val or_wire = DefWire(NoInfo, mName + s"_or${id}", UIntType(IntWidth(1))) 78 | val or_op = DoPrim(Or, Seq(or1.get, or2.get), Seq(), UIntType(IntWidth(1))) 79 | val or_connect = Connect(NoInfo, WRef(or_wire), or_op) 80 | (Some(WRef(or_wire)), stmts1 ++ stmts2 :+ or_wire :+ or_connect) 81 | } 82 | } 83 | } 84 | 85 | def findStop(stops: ListBuffer[Stop])(stmt: Statement): Unit = stmt match { 86 | case stop: Stop => 87 | stops.append(stop) 88 | case s: Statement => 89 | s foreachStmt findStop(stops) 90 | } 91 | 92 | def hasClockAndReset(mod: Module): (String, String, Boolean) = { 93 | val ports = mod.ports 94 | val (clockName, resetName) = ports.foldLeft[(String, String)](("None", "None"))( 95 | (tuple, p) => { 96 | if (p.name == "clock") (p.name, tuple._2) 97 | else if (p.name contains "reset") (tuple._1, p.name) 98 | else tuple 99 | }) 100 | val hasClockAndReset = (clockName != "None") && (resetName != "None") 101 | 102 | (clockName, resetName, hasClockAndReset) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /instrumentation/src/difuzz/InstrReset.scala: -------------------------------------------------------------------------------- 1 | // Instrumentation for metaReset 2 | 3 | package difuzz 4 | 5 | import firrtl.PrimOps.Or 6 | import firrtl._ 7 | import firrtl.ir._ 8 | 9 | import scala.collection.mutable.ListBuffer 10 | 11 | class InstrReset(mod: DefModule, mInfo: ModuleInfo) { 12 | private val mName = mod.name 13 | private val insts = mInfo.insts 14 | private val regs = if (mInfo.assertReg != None) (mInfo.regs.toSeq :+ mInfo.assertReg.get).toSet else mInfo.regs 15 | private val regNames = regs.map(_.name) 16 | 17 | def printLog(): Unit = { 18 | print("=============================================\n") 19 | print(s"${mName}\n") 20 | print("---------------------------------------------\n") 21 | insts.foreach(inst => print(s"- [${inst.module}]: ${inst.name}\n")) 22 | print("=============================================\n") 23 | } 24 | 25 | def instrument(): DefModule = { 26 | mod match { 27 | case mod: Module => { 28 | val stmts = mod.body.asInstanceOf[Block].stmts 29 | val metaResetPort = Port(NoInfo, "metaReset", Input, UIntType(IntWidth(1))) 30 | val newStmts = stmts.map(addMetaReset(metaResetPort)) 31 | 32 | val resetCons = ListBuffer[Statement]() 33 | val instResetPorts = ListBuffer[Port]() 34 | for (inst <- insts) { 35 | val instResetPort = Port(NoInfo, inst.name + "_halt", Input, UIntType(IntWidth(1))) 36 | 37 | val instReset = DoPrim(Or, Seq(WRef(metaResetPort), WRef(instResetPort)), Seq(), UIntType(IntWidth(1))) 38 | val instResetCons = Connect(NoInfo, WSubField(WRef(inst), "metaReset"), instReset) 39 | 40 | resetCons.append(instResetCons) 41 | instResetPorts.append(instResetPort) 42 | } 43 | 44 | val ports = (mod.ports :+ metaResetPort) ++ instResetPorts 45 | val newBlock = Block(newStmts ++ resetCons) 46 | 47 | Module(mod.info, mName, ports, newBlock) 48 | } 49 | case ext: ExtModule => ext 50 | case other => other 51 | } 52 | } 53 | 54 | def addMetaReset(metaReset: Port)(s: Statement): Statement = { 55 | s match { 56 | // All components are connected exactly once 57 | case Connect(info, loc, expr) if regs.exists(r => r.name == loc.serialize) => 58 | val width = regs.find(r => r.name == loc.serialize).getOrElse( 59 | throw new Exception(s"${loc.serialize} is not in registers") 60 | ).tpe match { 61 | case utpe: UIntType => utpe.width.asInstanceOf[IntWidth].width 62 | case stpe: SIntType => stpe.width.asInstanceOf[IntWidth].width 63 | case _ => 64 | throw new Exception("Register type must be one of UInt/SInt") 65 | } 66 | Connect(info, loc, Mux(WRef(metaReset), UIntLiteral(0, IntWidth(width)), expr)) 67 | case other => 68 | other.mapStmt(addMetaReset(metaReset)) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /instrumentation/src/difuzz/README.md: -------------------------------------------------------------------------------- 1 | # DifuzzRTL: Differential Fuzz Testing to Find CPU Bugs 2 | 3 | ## Notice 4 | 5 | All files in this directory belong to [the difuzz-rtl project](https://github.com/compsec-snu/difuzz-rtl) under the BSD 3-Clause "New" or "Revised" License. 6 | 7 | ## Introduction 8 | 9 | DifuzzRTL is a differential fuzz testing approach for CPU verification. 10 | We introduce new coverage metric, *register-coverage*, which comprehensively captures the states of an RTL design and correctly guides the input generation. 11 | DifuzzRTL automatically instruments *register-coverage*, randomly generates and mutates instructions defined in ISA, then cross-check against an ISA simulator to 12 | detect bugs. 13 | DiFuzzRTL is accepted at IEEE S&P 2021 ([paper][paperlink]) 14 | 15 | [paperlink]: https://www.computer.org/csdl/proceedings-article/sp/2021/893400b778/1t0x9G4Q5MI 16 | 17 | ## Setup 18 | 19 | ### Prerequisite 20 | Please install the correct versions! 21 | 22 | 1. [sbt][sbtlink] for FIRRTL 23 | 24 | [sbtlink]: https://www.scala-sbt.org/ 25 | 26 | 2. [verilator][verilatorlink] for RTL simulation (v4.106) 27 | 28 | [verilatorlink]: https://github.com/verilator/verilator 29 | 30 | 3. [cocotb][cocotblink] for RTL simulation (1.5.2) 31 | 32 | [cocotblink]: https://docs.cocotb.org/en/stable/ 33 | 34 | 4. [riscv][riscvlink] for RISC-V instruction mutation (2021.04.23) 35 | 36 | [riscvlink]: https://github.com/riscv/riscv-gnu-toolchain.git 37 | 38 | ### Instructions 39 | 40 | - For RTL simulation using verilator 41 | 42 | ``` 43 | git clone https://github.com/compsec-snu/difuzz-rtl 44 | cd DifuzzRTL 45 | git checkout sim 46 | 47 | . ./setup.sh 48 | ``` 49 | 50 | ## Instrumentation 51 | 52 | ``` 53 | cd firrtl 54 | sbt compile; sbt assembly 55 | ./utils/bin/firrtl -td regress -i regress/ -fct coverage.regCoverage -X verilog -o 56 | ``` 57 | 58 | **target_fir**: Firrtl file to instrument 59 | **output_verilog**: Output verilog file 60 | 61 | ## Run 62 | 63 | ``` 64 | cd Fuzzer 65 | make SIM_BUILD= VFILE= TOPLEVEL= NUM_ITER= OUT= 66 | ``` 67 | 68 | **SIM_BUILD**: Directory for RTL simulation binary build by cocotb 69 | **VFILE**: Target RTL design in DifuzzRTL/Benchmarks/Verilog/ 70 | (e.g., RocketTile_state, SmallBoomTile_v_1.2_state, SmallBoomTile_v1.3_state) 71 | **TOPLEVEL**: Top-level module 72 | (e.g., RocketTile or BoomTile) 73 | **NUM_ITER**: Number of fuzzing iterations to run 74 | **OUT**: Output directory 75 | **RECORD**: Set 1 to record coverage log 76 | **DEBUG**: Set 1 to print debug messages 77 | 78 | -------------------------------------------------------------------------------- /instrumentation/src/rfuzz/MetaReset.scala: -------------------------------------------------------------------------------- 1 | 2 | package rfuzz 3 | 4 | import firrtl.Mappers._ 5 | import firrtl.Utils.throwInternalError 6 | import firrtl._ 7 | import firrtl.analyses.InstanceGraph 8 | import firrtl.ir._ 9 | import firrtl.passes.RemoveValidIf 10 | import firrtl.stage.TransformManager.TransformDependency 11 | 12 | // Add a meta-reset to all registers 13 | class AddMetaResetTransform extends Transform with DependencyAPIMigration { 14 | override def prerequisites = firrtl.stage.Forms.LowForm 15 | override def optionalPrerequisites = firrtl.stage.Forms.LowFormOptimized 16 | override def optionalPrerequisiteOf: Seq[TransformDependency] = firrtl.stage.Forms.LowEmitters 17 | override def invalidates(a: Transform): Boolean = false 18 | 19 | val metaResetPort = Port(NoInfo, "metaReset", Input, Utils.BoolType) 20 | val metaResetInput = WRef(metaResetPort.name, metaResetPort.tpe, PortKind, SourceFlow) 21 | def metaResetInstPort(inst: WDefInstance) = { 22 | val instRef = WRef(inst.name, inst.tpe, InstanceKind, DuplexFlow) 23 | WSubField(instRef, metaResetPort.name, metaResetPort.tpe, SourceFlow) 24 | } 25 | val metaResetSinkRef = metaResetInput.copy(flow = SinkFlow) 26 | 27 | // Make a firrtl util 28 | private def getZero(tpe: Type): Literal = tpe match { 29 | case gtpe: GroundType => 30 | RemoveValidIf.getGroundZero(gtpe).mapWidth { w: Width => gtpe.width }.asInstanceOf[Literal] 31 | case other => throwInternalError(s"Unexpected non-GroundType $other") 32 | } 33 | 34 | private def onStmt(modType: Map[String, Type])(stmt: Statement): Statement = 35 | stmt.mapStmt(onStmt(modType)) match { 36 | case reg: DefRegister => 37 | if (reg.reset != Utils.zero) { 38 | throwInternalError(s"Resets must have been removed! Got ${reg.serialize}") 39 | } 40 | val zero = getZero(reg.tpe) 41 | reg.copy(reset = metaResetInput, tpe = zero.tpe, init = zero) 42 | case inst: WDefInstance if (modType.contains(inst.module)) => 43 | val instx = inst.copy(tpe = modType(inst.module)) 44 | val con = Connect(NoInfo, metaResetInstPort(instx), metaResetInput) 45 | Block(Seq(instx, con)) 46 | case other => other 47 | } 48 | private def onMod(modType: Map[String, Type])(m: Module): Module = { 49 | val portsx = metaResetPort +: m.ports 50 | val bodyx = onStmt(modType)(m.body) 51 | m.copy(ports = portsx, body = bodyx) 52 | } 53 | def cleanup: Seq[Transform] = Seq(firrtl.transforms.RemoveReset) 54 | 55 | def execute(state: CircuitState): CircuitState = { 56 | val modSet = state.circuit.modules.collect({ case m: Module => m }).toSet 57 | val modsLeafToRoot = (new InstanceGraph(state.circuit)).moduleOrder.reverse 58 | val (modsUpdate, _) = 59 | modsLeafToRoot.foldLeft((Map.empty[String, DefModule], Map.empty[String, Type])) { 60 | case ((acc, types), m) => m match { 61 | case mod: Module => 62 | val modx = onMod(types)(mod) 63 | (acc + (modx.name -> modx), types + (modx.name -> Utils.module_type(modx))) 64 | case ext: ExtModule => (acc, types) 65 | } 66 | } 67 | // Maintain order 68 | val modsx = state.circuit.modules.map(m => modsUpdate.getOrElse(m.name, m)) 69 | val res = state.copy(circuit = state.circuit.copy(modules = modsx)) 70 | cleanup.foldLeft(res) { case (in, xform) => xform.runTransform(in) } 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /instrumentation/src/rfuzz/NoDedup.scala: -------------------------------------------------------------------------------- 1 | package rfuzz 2 | 3 | import firrtl.Mappers._ 4 | import firrtl._ 5 | import firrtl.annotations._ 6 | import firrtl.ir._ 7 | import firrtl.transforms.NoDedupAnnotation 8 | 9 | import scala.collection.mutable 10 | 11 | // Annotate every module with NoDedupAnnotation 12 | // Also check that there is only one instance of each module 13 | // We need this because the WiringTransform cannot handle deduplicated modules 14 | // Turning off dedup should be a feature of FIRRTL 15 | // Note that we can't use InstanceGraph because it only works on WIR (needs to be checked) 16 | class NoDedupTransform extends Transform with DependencyAPIMigration { 17 | 18 | // Returns a *relative* count of modules, ie. how many times the DefInstance shows up in the 19 | // Module graph 20 | def countInsts(circuit: Circuit): Map[String, Int] = { 21 | val counts = mutable.HashMap.empty[String, Int] 22 | counts(circuit.main) = 1 23 | def onStmt(stmt: Statement): Statement = stmt.mapStmt(onStmt) match { 24 | case inst @ DefInstance(_, _, mname, _) => 25 | counts(mname) = counts.getOrElse(mname, 0) + 1 26 | inst 27 | case (_: WDefInstance | _: WDefInstanceConnector) => 28 | throw new FirrtlUserException("Unexpected non-Chirrtl Instance!") 29 | case other => other 30 | } 31 | def onMod(mod: DefModule): DefModule = mod.mapStmt(onStmt) 32 | circuit.mapModule(onMod) 33 | counts.toMap 34 | } 35 | 36 | // NoDedupAnnotation 37 | def execute(state: CircuitState): CircuitState = { 38 | val counts = countInsts(state.circuit) 39 | // Check counts 40 | val duplicated = counts.filter(_._2 != 1).map(_._1).toSeq 41 | // require(counts.forall(_._2 == 1), s"There must be no deduplicated modules! " + 42 | // counts.filter(_._2 > 1) 43 | // ) 44 | // create annotations 45 | val circuitName = CircuitName(state.circuit.main) 46 | val modNames = state.circuit.modules.filterNot(m => duplicated.contains(m.name)).map(m => ModuleName(m.name, circuitName)) 47 | val annos = modNames.map(NoDedupAnnotation(_)) 48 | state.copy(annotations = state.annotations ++ annos) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /instrumentation/src/rfuzz/ProfilingTransform.scala: -------------------------------------------------------------------------------- 1 | 2 | package rfuzz 3 | 4 | import firrtl.Mappers._ 5 | import firrtl.Utils.{BoolType, get_info} 6 | import firrtl._ 7 | import firrtl.annotations._ 8 | import firrtl.ir._ 9 | import firrtl.options.Dependency 10 | import firrtl.passes.wiring.{SinkAnnotation, SourceAnnotation} 11 | import firrtl.stage.TransformManager.TransformDependency 12 | 13 | import scala.collection.mutable 14 | 15 | object ProfilingTransform { 16 | case class ProfileConfig(topPortName: String)( 17 | // Returns the optionally mutated input statement and any expressions 18 | // that should be extracted to the top for profiling 19 | val processStmt: PartialFunction[Statement, (Statement, Seq[Expression])] 20 | ) 21 | } 22 | 23 | case class DoNotProfileModule(target: ModuleName) extends SingleTargetAnnotation[ModuleName] { 24 | def duplicate(n: ModuleName) = this.copy(target = n) 25 | } 26 | 27 | /** Uses Firrtl's Wiring Transform to wire profiling signals in rocket-chip to the top 28 | * 29 | * Cover points are expected to be of the form: printf(..., "COVER:..." signal) 30 | * Assertions are expected to be of the form: printf(..., "Assertion...") 31 | */ 32 | class ProfilingTransform extends Transform with DependencyAPIMigration { 33 | override def prerequisites: Seq[TransformDependency] = Seq(Dependency[rfuzz.SplitMuxConditions]) 34 | override def optionalPrerequisiteOf: Seq[TransformDependency] = firrtl.stage.Forms.LowEmitters 35 | override def invalidates(a: Transform): Boolean = a match { 36 | case _ : firrtl.passes.wiring.WiringTransform => true 37 | case _ => false 38 | } 39 | 40 | import ProfilingTransform._ 41 | 42 | def containsMux(stmt: Statement): Boolean = { 43 | var muxFound = false 44 | def onExpr(expr: Expression): Expression = expr.mapExpr(onExpr) match { 45 | case m: Mux => 46 | muxFound = m.cond.serialize != "reset" 47 | m 48 | case other => other 49 | } 50 | stmt.mapExpr(onExpr) 51 | muxFound 52 | } 53 | def extractMuxConditions(stmt: Statement): Seq[Expression] = { 54 | val conds = mutable.ArrayBuffer.empty[Expression] 55 | def onExpr(expr: Expression): Expression = expr.mapExpr(onExpr) match { 56 | case mux @ Mux(cond, _,_,_) => 57 | conds += cond 58 | mux 59 | case other => other 60 | } 61 | stmt.mapExpr(onExpr) 62 | conds.toSeq 63 | } 64 | 65 | val coverageConfig = ProfileConfig("func_cover_out") { 66 | case Print(_, slit, Seq(pred), _, en) if slit.string.startsWith("COVER:") => 67 | (EmptyStmt, Seq(And(pred, en))) 68 | } 69 | val assertConfig = ProfileConfig("assert_out") { 70 | case Print(_, slit, _, _, en) if slit.string.startsWith("Assertion") => 71 | (EmptyStmt, Seq(en)) 72 | case _: Stop => (EmptyStmt, Seq.empty) // Remove stops 73 | } 74 | val autoCoverageConfig = ProfileConfig("auto_cover_out") { 75 | case stmt if containsMux(stmt) => (stmt, extractMuxConditions(stmt)) 76 | } 77 | val configs = Seq( 78 | coverageConfig, 79 | assertConfig, 80 | autoCoverageConfig 81 | ) 82 | 83 | // Hardwired constants 84 | val profilePinPrefix = "profilePin" 85 | 86 | // Helper functions that probably should be in Firrtl itself 87 | // Returns a statement because deeply-nested Expressions cause problems 88 | private def Cat(namespace: Namespace, exprs: Expression*): (Expression, Seq[DefNode]) = { 89 | def wref(n: DefNode) = WRef(n.name, n.value.tpe, NodeKind) 90 | require(exprs.size > 0) 91 | val first = DefNode(NoInfo, namespace.newTemp, exprs.head) 92 | val (ref, revnodes) = exprs.tail.foldLeft((wref(first), List(first))) { case ((prev, stmts), next) => 93 | val cat = DoPrim(PrimOps.Cat, List(prev, next), List.empty, CatTypes(prev.tpe, next.tpe)) 94 | val node = DefNode(NoInfo, namespace.newTemp, cat) 95 | (wref(node), node +: stmts) // prepend because List but need to reverse 96 | } 97 | (ref, revnodes.reverse) 98 | } 99 | private def CatTypes(tpe1: Type, tpe2: Type): Type = (tpe1, tpe2) match { 100 | case (UIntType(w1), UIntType(w2)) => UIntType(w1 + w2) 101 | case (SIntType(w1), SIntType(w2)) => UIntType(w1 + w2) 102 | case other => throw new Exception(s"Unexpected Types $other!") 103 | } 104 | private def And(exprs: Expression*) = { 105 | require(exprs.size > 0) 106 | exprs.tail.foldLeft(exprs.head) { case (next, acc) => 107 | require(acc.tpe == BoolType) 108 | DoPrim(PrimOps.And, List(next, acc), List.empty, BoolType) 109 | } 110 | } 111 | 112 | // This namespace is based on the ports that exist in the top 113 | private def onModule( 114 | mod: Module, 115 | top: String, 116 | namespace: Namespace 117 | ): (Module, Map[ProfileConfig, Seq[SourceAnnotation]]) = { 118 | // We record Annotations for each profiled signal for each profiling configuration 119 | val profiledSignals = Map(configs.map(c => c -> mutable.ArrayBuffer.empty[SourceAnnotation]): _*) 120 | val localNS = Namespace(mod) 121 | 122 | def onStmt(stmt: Statement): Statement = { 123 | val stmtx = stmt.mapStmt(onStmt) 124 | configs.flatMap(c => c.processStmt.lift(stmtx).map(c -> _)) match { 125 | // No profiling on this Statement, just return it 126 | case Seq() => stmtx 127 | case Seq((config, (retStmt, signals))) => 128 | val (nodes, annos) = (signals.map { expr => 129 | val node = DefNode(get_info(stmtx), localNS.newTemp, expr) 130 | val named = ComponentName(node.name, ModuleName(mod.name, CircuitName(top))) 131 | val pinName = namespace.newName(profilePinPrefix) 132 | assert(localNS.tryName(pinName), s"Name collision with $pinName in ${mod.name}!") 133 | val anno = SourceAnnotation(named, pinName) 134 | (node, anno) 135 | }).unzip 136 | profiledSignals(config) ++= annos 137 | Block(retStmt +: nodes) 138 | case multiple => 139 | // We don't let multiple configurations match on a statement because they could have 140 | // different behavior for what should happen to that Statement (eg. removed vs. not 141 | // removed) 142 | throw new Exception("Error! Multiple profiling configurations trying to " + 143 | "profile the same Statement!") 144 | } 145 | } 146 | 147 | val bodyx = onStmt(mod.body) 148 | (mod.copy(body = bodyx), profiledSignals.view.mapValues(_.toSeq).toMap) 149 | } 150 | 151 | // Create sinks for all the cover points in top and cat them to a bitvector output 152 | def onTop(mod: Module, 153 | profiledSignals: Map[ProfileConfig, Seq[Annotation]]): (Module, Seq[Annotation]) = { 154 | val modName = ModuleName(mod.name, CircuitName(mod.name)) 155 | val namespace = Namespace(mod) 156 | 157 | val (newPorts, stmts, annos) = profiledSignals.flatMap { case (config, sourceAnnos) => 158 | // Create wires and sink annos 159 | val (wires, sinkAnnos) = (sourceAnnos.map { case SourceAnnotation(_, pin) => 160 | val w = DefWire(NoInfo, namespace.newTemp, BoolType) 161 | (w, SinkAnnotation(ComponentName(w.name, modName), pin)) 162 | }).unzip 163 | assert(namespace.tryName(config.topPortName)) 164 | 165 | // Wire profiled points to top 166 | val numPoints = wires.size 167 | if (numPoints > 0) { 168 | val port = Port(NoInfo, config.topPortName, Output, UIntType(IntWidth(numPoints))) 169 | val (catref, catstmts) = Cat(namespace, wires.map(w => WRef(w.name, BoolType, NodeKind, SourceFlow)): _*) 170 | val connect = Connect(NoInfo, WRef(port.name, port.tpe, PortKind, SourceFlow), catref) 171 | Some((port, wires ++ catstmts :+ connect, sourceAnnos ++ sinkAnnos)) 172 | } else { 173 | None 174 | } 175 | }.unzip3 176 | 177 | val bodyx = Block(stmts.flatten.toSeq :+ mod.body) 178 | val portsx = mod.ports ++ newPorts 179 | val modx = mod.copy(body = bodyx, ports = portsx) 180 | (modx, annos.flatten.toSeq) 181 | } 182 | 183 | def execute(state: CircuitState): CircuitState = { 184 | 185 | val dontProfile = state.annotations 186 | .collect { case DoNotProfileModule(ModuleName(m, _)) => m } 187 | .toSet 188 | val top = state.circuit.modules.find(_.name == state.circuit.main).get 189 | val topNameS = Namespace(top) // used for pins naming 190 | 191 | val (modsx, profiledSignalMaps) = (state.circuit.modules.map { 192 | case mod: Module if !dontProfile(mod.name) => onModule(mod, top.name, topNameS) 193 | case other => (other, Map.empty[ProfileConfig, Seq[SourceAnnotation]]) 194 | }).unzip 195 | val profiledSignals = 196 | configs.map( 197 | c => c -> profiledSignalMaps 198 | .filterNot(_.isEmpty) 199 | .map(_.apply(c)) 200 | .reduce(_ ++ _)) 201 | .toMap 202 | 203 | val (Seq(topx: Module), otherMods) = modsx.partition(_.name == state.circuit.main) 204 | val (newTop, fullAnnos) = onTop(topx, profiledSignals) 205 | 206 | val circuitx = state.circuit.copy(modules = newTop +: otherMods) 207 | val annosx = state.annotations ++ fullAnnos 208 | 209 | TomlGenerator(circuitx, profiledSignals) 210 | 211 | val result = state.copy(circuit = circuitx, annotations = annosx) 212 | result 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /instrumentation/src/rfuzz/ReplaceMems.scala: -------------------------------------------------------------------------------- 1 | package rfuzz 2 | 3 | import chisel3.stage.ChiselStage 4 | import firrtl.Mappers._ 5 | import firrtl.Utils.throwInternalError 6 | import firrtl._ 7 | import firrtl.annotations._ 8 | import firrtl.ir._ 9 | import firrtl.passes.MemPortUtils.memType 10 | import firrtl.stage.TransformManager.TransformDependency 11 | 12 | import scala.collection.mutable 13 | 14 | object ReplaceMemsTransform { 15 | // TODO: increase for longer tests! 16 | val SparseMemSize = 32 17 | 18 | def getMem(tpe: Type, depth: BigInt, nR: Int, nW: Int, syncRead: Boolean): Module = { 19 | import chisel3._ 20 | import chisel3.util.log2Ceil 21 | val chirrtl = (new ChiselStage).emitChirrtl(new Module { 22 | override def desiredName = "IgnoreMe" 23 | def typeToData(tpe: Type): Data = tpe match { 24 | case UIntType(IntWidth(w)) => UInt(w.toInt.W) 25 | case SIntType(IntWidth(w)) => SInt(w.toInt.W) 26 | case VectorType(t, s) => Vec(s, typeToData(t)) 27 | case other => throwInternalError(s"Unsupported type ${other.serialize} for SparseMem") 28 | } 29 | val dataType = typeToData(tpe) 30 | val io = IO(new Bundle {}) 31 | // No need for dontTouch because we retop to SparseMem right away 32 | val size = math.min(SparseMemSize, depth.toInt) // Use actual mem depth if it's smaller 33 | val submod = Module(new SparseMem(dataType, size, log2Ceil(depth), nR, nW, syncRead)) 34 | }) 35 | val circuit = { 36 | val parsed = Parser.parse(chirrtl.split("\n").toIterator, Parser.IgnoreInfo) 37 | val mods = parsed.modules.filterNot(_.name == "IgnoreMe") 38 | assert(mods.size == 1) 39 | parsed.copy(modules = mods, main = mods.head.name) 40 | } 41 | val state = (new MiddleFirrtlCompiler).compile(CircuitState(circuit, ChirrtlForm), Seq.empty) 42 | state.circuit.modules match { 43 | case Seq(one: firrtl.ir.Module) => one 44 | case other => throwInternalError(s"Invalid resulting modules ${other.map(_.name)}") 45 | } 46 | } 47 | } 48 | 49 | /** Replaces Mems with a sparse mem 50 | * @note Assumes that clocks on all mem ports are the same (generally safe assumption) 51 | * @note Assumes that wrapping Module has a port named "reset" 52 | */ 53 | class ReplaceMemsTransform extends Transform with DependencyAPIMigration { 54 | override def prerequisites: Seq[TransformDependency] = firrtl.stage.Forms.MidForm 55 | override def optionalPrerequisiteOf: Seq[TransformDependency] = firrtl.stage.Forms.MidEmitters 56 | override def invalidates(a: Transform): Boolean = false 57 | 58 | import ReplaceMemsTransform._ 59 | 60 | def access(expr: Expression, index: Expression) = 61 | WSubAccess(expr, index, UnknownType, UnknownFlow) 62 | def subindex(expr: Expression, index: Int) = 63 | WSubIndex(expr, index, UnknownType, UnknownFlow) 64 | 65 | private def onStmt(ns: Namespace, 66 | topNS: Namespace, 67 | resetRef: Option[WRef], 68 | newMods: mutable.ArrayBuffer[Module]) 69 | (stmt: Statement): Statement = stmt.mapStmt(onStmt(ns, topNS, resetRef, newMods)) match { 70 | case mem: DefMemory => 71 | if (mem.writeLatency != 1 || (mem.readLatency != 0 && mem.readLatency != 1) || mem.readwriters.nonEmpty) { 72 | throwInternalError(s"Error! Can only handle Chisel mems, try running after CustomVerilogMemDelays!\n" + 73 | s"Got ${mem.serialize}") 74 | } 75 | val memMod = { 76 | val syncRead = mem.readLatency == 1 77 | val mod = getMem(mem.dataType, mem.depth, mem.readers.size, mem.writers.size, syncRead) 78 | mod.copy(name = topNS.newName(mod.name)) 79 | } 80 | newMods += memMod 81 | val instName = ns.newName(s"mem_sparse") // Helps debug 82 | val inst = WDefInstance(mem.info, instName, memMod.name, UnknownType) 83 | 84 | // Keep connections to the mem now go through the wire 85 | val wire = DefWire(mem.info, mem.name, memType(mem)) 86 | val rcons = { 87 | mem.readers.zipWithIndex.flatMap { case (r, i) => 88 | val pref = subindex(WSubField(WSubField(WRef(inst.name), "io"), "r"), i) 89 | val rref = WSubField(WRef(wire), r) 90 | // On LowForm, zero-width address will be removed 91 | val addrCon = if (mem.depth > 1) { 92 | Seq(Connect(NoInfo, WSubField(pref, "addr"), WSubField(rref, "addr"))) 93 | } else Seq() 94 | addrCon ++ Seq(Connect(NoInfo, WSubField(pref, "en"), WSubField(rref, "en")), 95 | Connect(NoInfo, WSubField(rref, "data"), WSubField(pref, "data"))) 96 | } 97 | } 98 | val wcons = { 99 | mem.writers.zipWithIndex.flatMap { case (w, i) => 100 | val pref = subindex(WSubField(WSubField(WRef(inst.name), "io"), "w"), i) 101 | val wref = WSubField(WRef(wire), w) 102 | val addrCond = if (mem.depth > 1) { 103 | Seq(Connect(NoInfo, WSubField(pref, "addr"), WSubField(wref, "addr"))) 104 | } else Seq() 105 | addrCond ++ Seq(Connect(NoInfo, WSubField(pref, "en"), WSubField(wref, "en")), 106 | Connect(NoInfo, WSubField(pref, "data"), WSubField(wref, "data")), 107 | Connect(NoInfo, WSubField(pref, "mask"), WSubField(wref, "mask"))) 108 | } 109 | } 110 | val clkCon = { 111 | val memPort = mem.readers.headOption.getOrElse(mem.writers.head) 112 | val lhs = WSubField(WRef(inst.name), "clock") 113 | val rhs = WSubField(WSubField(WRef(wire), memPort), "clk") 114 | Connect(NoInfo, lhs, rhs) 115 | } 116 | // TODO This heavily relies on metaRest reseting everything to zero 117 | val reset = resetRef.getOrElse(UIntLiteral(0, IntWidth(1))) 118 | val resetCon = Connect(NoInfo, WSubField(WRef(inst.name), "reset"), reset) 119 | val allCons = rcons ++ wcons ++ Seq(clkCon, resetCon) 120 | 121 | Block(Seq(inst, wire) ++ allCons) 122 | case other => other 123 | } 124 | 125 | // Returns the transformed module as well as any new modules created 126 | private def onModule(topNS: Namespace)(mod: Module): Seq[Module] = { 127 | val namespace = Namespace(mod) 128 | 129 | val resetRef = mod.ports.collectFirst { case Port(_,"reset",_,_) => WRef("reset") } 130 | 131 | val newMods = mutable.ArrayBuffer.empty[Module] 132 | val bodyx = onStmt(namespace, topNS, resetRef, newMods)(mod.body) 133 | Seq(mod.copy(body = bodyx)) ++ newMods 134 | } 135 | def execute(state: CircuitState): CircuitState = { 136 | val moduleNamespace = Namespace(state.circuit) 137 | val circuitName = CircuitName(state.circuit.main) 138 | val (modulesx: Seq[DefModule], annos: Seq[DoNotProfileModule]) = state.circuit.modules.map { 139 | case mod: Module => 140 | val mods = onModule(moduleNamespace)(mod) 141 | val newMods = mods.filter(_.name != mod.name) 142 | assert(newMods.size + 1 == mods.size) // Sanity check 143 | (mods, newMods.map(m => DoNotProfileModule(ModuleName(m.name, circuitName)))) 144 | case ext: ExtModule => (Seq(ext), Seq.empty[DoNotProfileModule]) 145 | }.unzip match { case (ms, as) => (ms.flatten, as.flatten) } 146 | 147 | val res = state.copy(circuit = state.circuit.copy(modules = modulesx), 148 | annotations = annos ++ state.annotations) 149 | (new ResolveAndCheck).execute(res) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /instrumentation/src/rfuzz/SparseMem.scala: -------------------------------------------------------------------------------- 1 | package rfuzz 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | 6 | object SparseMem { 7 | def getMaskWidth(data: Data): Int = data match { 8 | case agg: Aggregate => 9 | agg.getElements.map(getMaskWidth).sum 10 | case _: Element => 1 11 | } 12 | } 13 | 14 | class MemReaderIO[T <: Data](private val dataType: T, private val addrWidth: Int) extends Bundle { 15 | val addr = Input(UInt(addrWidth.W)) 16 | val en = Input(Bool()) 17 | val data = Output(dataType) 18 | def check(addr: UInt, expected: T): Unit = { 19 | en := true.B 20 | this.addr := addr 21 | assert(data.asUInt === expected.asUInt) 22 | } 23 | } 24 | class MemWriterIO[T <: Data](private val dataType: T, private val addrWidth: Int) extends Bundle { 25 | val addr = Input(UInt(addrWidth.W)) 26 | val en = Input(Bool()) 27 | // We have to emulate firrtl's memlib mask type with is Vector for aggregate or Bool for GroundType 28 | val mask: Data = dataType match { 29 | case _: Vec[_] => Input(Vec(SparseMem.getMaskWidth(dataType), Bool())) 30 | case _: Bits => Input(UInt(SparseMem.getMaskWidth(dataType).W)) 31 | } 32 | val data = Input(dataType) 33 | def write(addr: UInt, data: T): Unit = { 34 | en := true.B 35 | this.addr := addr 36 | this.data := data 37 | } 38 | } 39 | class SparseMem[T <: Data] 40 | (dataType: T, 41 | depth: Int, 42 | addrWidth: Int, 43 | nR: Int, 44 | nW: Int, 45 | syncRead: Boolean) 46 | (implicit maybe: MaybeVec[T]) extends Module with RequireSyncReset { 47 | val io = IO(new Bundle { 48 | val w = Vec(nW, new MemWriterIO(dataType, addrWidth)) 49 | val r = Vec(nR, new MemReaderIO(dataType, addrWidth)) 50 | }) 51 | 52 | val mem = Mem(depth, dataType) 53 | val addresses = RegInit(VecInit(Seq.fill(depth) { 54 | val w = Wire(Valid(UInt(addrWidth.W))) 55 | w.valid := false.B 56 | w.bits := DontCare 57 | w 58 | })) 59 | 60 | def addrMatch(addr: UInt): (Bool, UInt) = { 61 | val matches = VecInit(addresses.map(v => v.valid && v.bits === addr)).asUInt 62 | (matches =/= 0.U, OHToUInt(matches)) 63 | } 64 | def read(en: Bool, addr: UInt): Data = { 65 | val (valid, addrIdx) = addrMatch(addr) 66 | Mux(valid && en, mem(addrIdx), 0.U.asTypeOf(dataType)) 67 | } 68 | 69 | val readers = if (syncRead) { 70 | val readers = Reg(Output(chiselTypeOf(io.r))) 71 | readers := io.r 72 | } else io.r 73 | 74 | for (r <- io.r) { 75 | r.data := read(r.en, r.addr) 76 | } 77 | 78 | // Increase addr width by 1 so we can detect overflow 79 | val nextAddr = RegInit(0.U((log2Ceil(depth) + 1).W)) 80 | 81 | val nextAddrs = io.w.foldLeft(nextAddr) { 82 | case (na, w) => Mux(w.en, na +% 1.U, na) 83 | } 84 | 85 | val nextAddrUpdate = io.w.foldLeft(nextAddr) { case (naddr, w) => 86 | val (found, faddr) = addrMatch(w.addr) 87 | val allocate = w.en && !found 88 | when (w.en) { 89 | val addr = Mux(found, faddr, naddr) 90 | addresses(addr).valid := true.B 91 | addresses(addr).bits := w.addr 92 | maybe.evidence match { 93 | case Some(ev) => 94 | implicit val evidence = ev 95 | mem.write(addr, w.data, w.mask.asUInt.asBools) 96 | case None => 97 | mem.write(addr, w.data) 98 | } 99 | } 100 | Mux(allocate, naddr +% 1.U, naddr) 101 | } 102 | nextAddr := nextAddrUpdate 103 | 104 | // make sure that we never write beyond the allocated space 105 | assert(nextAddrUpdate <= depth.U, 106 | "SparseMem ran out of space with size %d, increase size in ReplaceMemsTransform!", depth.U) 107 | } 108 | -------------------------------------------------------------------------------- /instrumentation/src/rfuzz/SplitMuxConditions.scala: -------------------------------------------------------------------------------- 1 | package rfuzz 2 | 3 | import firrtl.Mappers._ 4 | import firrtl._ 5 | import firrtl.ir._ 6 | import firrtl.stage.TransformManager.TransformDependency 7 | 8 | import scala.collection.mutable 9 | 10 | // Removes compound expressions from mux conditions 11 | // Ensures they are a reference 12 | // Borrows from Firrtl's SplitExpressions 13 | class SplitMuxConditions extends Transform with DependencyAPIMigration { 14 | override def prerequisites: Seq[TransformDependency] = firrtl.stage.Forms.LowForm 15 | override def optionalPrerequisites = firrtl.stage.Forms.LowFormOptimized 16 | override def optionalPrerequisiteOf: Seq[TransformDependency] = firrtl.stage.Forms.LowEmitters 17 | override def invalidates(a: Transform): Boolean = false 18 | 19 | private def isRef(expr: Expression): Boolean = expr match { 20 | case ref @ (_: WRef | _: WSubField | _: WSubIndex) => true 21 | case _ => false 22 | } 23 | 24 | private def onModule(mod: Module): Module = { 25 | val namespace = Namespace(mod) 26 | def onStmt(s: Statement): Statement = { 27 | val stmts = mutable.ArrayBuffer.empty[Statement] 28 | def onExpr(e: Expression): Expression = e mapExpr onExpr match { 29 | case Mux(cond, tval, fval, mtpe) if !isRef(cond) => 30 | val n = DefNode(Utils.get_info(s), namespace.newTemp, cond) 31 | stmts.append(n) 32 | Mux(WRef(n.name, cond.tpe, NodeKind, SourceFlow), tval, fval, mtpe) 33 | case mux: Mux => 34 | mux 35 | case other => other 36 | } 37 | 38 | stmts += s.mapExpr(onExpr).mapStmt(onStmt) 39 | stmts.size match { 40 | case 1 => stmts.head 41 | case _ => Block(stmts.toSeq) 42 | } 43 | } 44 | mod.copy(body = onStmt(mod.body)) 45 | } 46 | def execute(state: CircuitState): CircuitState = { 47 | state.copy(circuit = state.circuit.copy(modules = state.circuit.modules map { 48 | case mod: Module => onModule(mod) 49 | case ext: ExtModule => ext 50 | })) 51 | } 52 | } -------------------------------------------------------------------------------- /instrumentation/src/rfuzz/TomlGenerator.scala: -------------------------------------------------------------------------------- 1 | 2 | package rfuzz 3 | 4 | import java.text.SimpleDateFormat 5 | import java.util.Date 6 | 7 | import firrtl.Mappers._ 8 | import firrtl.PrimOps._ 9 | import firrtl.Utils.get_info 10 | import firrtl._ 11 | import firrtl.annotations._ 12 | import firrtl.ir._ 13 | import firrtl.passes.wiring.SourceAnnotation 14 | import rfuzz.ProfilingTransform._ 15 | 16 | import scala.collection.mutable 17 | 18 | 19 | // TODO: this is realtively decoupled from the instrumentation pass 20 | // -> maybe move into separate file 21 | object TomlGenerator { 22 | case class TestInput(name: String, width: Int) 23 | case class DebugInfo(filename: String, line: Int, col: Int) 24 | def getWidth(port: Port) : Int = { 25 | port.tpe match { 26 | case gt: GroundType => { 27 | gt.width match { 28 | case iw: IntWidth => iw.width.intValue 29 | case _ => throw new Exception(s"Type of ${port.name} width is unexpected") 30 | } 31 | } 32 | case _ => throw new Exception(s"Type of ${port.name} is unexpected") 33 | } 34 | } 35 | def getFuzzingInputs(ports: Seq[Port]) : Seq[TestInput] = { 36 | val possible_inputs : Iterable[Option[TestInput]] = 37 | for(port <- ports) yield { 38 | port.direction match { 39 | case Input => { 40 | if(port.name != "clock" && port.name != "reset") { 41 | Some(TestInput(port.name, getWidth(port))) 42 | } else { None } 43 | } 44 | case _ => None 45 | } 46 | } 47 | // sort decending by width (TODO: is sortWith stable?) 48 | possible_inputs.flatten.toSeq.sortWith(_.width > _.width) 49 | } 50 | def parseFileInfo(info: FileInfo) : Option[DebugInfo] = { 51 | val pattern = raw":([^@:]+)@(\d+)\.(\d+)".r.unanchored 52 | // yosys: gates.v:5.16-5.21 53 | info.info.string match { 54 | case pattern(filename, line, col) => Some(DebugInfo(filename, line.toInt, col.toInt)) 55 | case _ => None 56 | } 57 | } 58 | def getDebugInfo(info: Info) : Seq[Option[DebugInfo]] = { 59 | info match { 60 | case fi: FileInfo => Seq(parseFileInfo(fi)) 61 | case mi: MultiInfo => mi.infos.flatMap(getDebugInfo) 62 | case _ => Seq() 63 | } 64 | } 65 | def getDebugInfo(stmt: Statement) : DebugInfo = { 66 | val infos = getDebugInfo(get_info(stmt)).flatten 67 | infos.headOption match { 68 | case Some(info) => info 69 | case None => DebugInfo("", -1, -1) 70 | } 71 | } 72 | def getDefinitions(mod: Module) : Map[String, IsDeclaration] = { 73 | val definitions = collection.mutable.HashMap[String, IsDeclaration]() 74 | def onStmt(stmt: Statement) : Statement = { 75 | stmt map onStmt 76 | stmt match { 77 | case d: DefNode => definitions(d.name) = d 78 | case d: DefWire => definitions(d.name) = d 79 | case d: DefRegister => definitions(d.name) = d 80 | case _ => None 81 | } 82 | stmt 83 | } 84 | for(port <- mod.ports) { 85 | definitions(port.name) = port 86 | } 87 | mod.body map onStmt 88 | definitions.toMap 89 | } 90 | def isGeneratedIdentifier(id: String) : Boolean = { 91 | // heuristic 92 | id.matches(raw"(_?(:?(:?T)|(:?GEN))_\d+(:?_\d+)?)") 93 | } 94 | def getHumanReadableExpression(definitions: Map[String, IsDeclaration], decl: IsDeclaration) : String = { 95 | decl match { 96 | case d: DefNode => getHumanReadableExpression(definitions, d.value) 97 | case d: DefWire => s"Wire(${d.name})" 98 | case d: DefRegister => s"Reg(${d.name})" 99 | case p: Port => { 100 | p.direction match { 101 | case Input => { s"In(${p.name})" } 102 | case Output => { s"Out(${p.name})" } 103 | case d => throw new Exception(s"Unexpected direction ${d}") 104 | } 105 | } 106 | } 107 | } 108 | def getHumanReadableExpression(definitions: Map[String, IsDeclaration], cond: Expression) : String = { 109 | // this is a *heuristic* 110 | // TODO: minimize expressions .... 111 | cond match { 112 | case ref: Reference => { 113 | if(isGeneratedIdentifier(ref.name)) { 114 | definitions.get(ref.name) match { 115 | case Some(d) => getHumanReadableExpression(definitions, d) 116 | case None => ref.name 117 | } 118 | } else { ref.name } 119 | } 120 | case ref: WRef => { 121 | if(isGeneratedIdentifier(ref.name)) { 122 | definitions.get(ref.name) match { 123 | case Some(d) => getHumanReadableExpression(definitions, d) 124 | case None => ref.name 125 | } 126 | } else { ref.name } 127 | } 128 | case lit: Literal => { s"${lit.value}" } 129 | case doprim: DoPrim => { 130 | // lot's of code similar to the Verilog Emitter .... hm 131 | def a0 = getHumanReadableExpression(definitions, doprim.args.head) 132 | def a1 = getHumanReadableExpression(definitions, doprim.args(1)) 133 | def c0 = doprim.consts.head.toInt 134 | def c1 = doprim.consts(1).toInt 135 | doprim.op match { 136 | case Add => s"(${a0} + ${a1})" 137 | case Addw => s"(${a0} + ${a1})" 138 | case Sub => s"(${a0} - ${a1})" 139 | case Subw => s"(${a0} - ${a1})" 140 | case Mul => s"(${a0} * ${a1})" 141 | case Div => s"(${a0} / ${a1})" 142 | case Rem => s"(${a0} % ${a1})" 143 | case Lt => s"(${a0} < ${a1})" 144 | case Leq => s"(${a0} <= ${a1})" 145 | case Gt => s"(${a0} > ${a1})" 146 | case Geq => s"(${a0} >= ${a1})" 147 | case Eq => s"(${a0} == ${a1})" 148 | case Neq => s"(${a0} != ${a1})" 149 | case Dshlw => s"(${a0} << ${a1})" 150 | case Dshl => s"(${a0} << ${a1})" 151 | case Dshr => s"(${a0} >> ${a1})" 152 | case Shl => s"(${a0} << ${c0})" 153 | case Shr => s"(${a0} >> ${c0})" 154 | case Neg => s"-${a0}" 155 | case Not => s"(not ${a0})" 156 | case And => s"(${a0} and ${a1})" 157 | case Or => s"(${a0} or ${a1})" 158 | case Xor => s"(${a0} xor ${a1})" 159 | case Bits => s"${a0}[${c0}:${c1}]" 160 | case other => s"Todo(${other})" 161 | } 162 | } 163 | case mux: Mux => { 164 | val cond = getHumanReadableExpression(definitions, mux.cond) 165 | val tval = getHumanReadableExpression(definitions, mux.tval) 166 | val fval = getHumanReadableExpression(definitions, mux.fval) 167 | s"(${cond}? ${tval} : ${fval})" 168 | } 169 | case wsf: WSubField => { 170 | val expr = getHumanReadableExpression(definitions, wsf.expr) 171 | s"(${expr}).${wsf.name}" 172 | } 173 | case other => { 174 | s"Todo(${other})" 175 | } 176 | } 177 | } 178 | def apply(circuit: Circuit, profiledSignals: Map[ProfileConfig, Seq[SourceAnnotation]]) = { 179 | //def apply(mod: Module, coverage: Seq[(WRef,Statement)]) = { 180 | val top = circuit.modules.find(_.name == circuit.main).get 181 | val moduleDefinitions = circuit.modules.collect { 182 | case m: Module => m.name -> getDefinitions(m) 183 | }.toMap 184 | 185 | val output = s"${top.name}.toml" 186 | val out = new java.io.PrintWriter(output) 187 | 188 | val instrumented = s"${top.name}.v" // TODO: fix heuristic 189 | val filename = try { 190 | getDebugInfo(top.info).flatten.map(_.filename).head 191 | } catch { case _ : Throwable => "???" } 192 | val timestamp = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(new Date()) 193 | val q = "\"" // Workaround for Vim Scala syntax highlighting 194 | out.println("# This file was generated by the AutoCoverage pass.") 195 | out.println("# It contains all information needed by the fuzzer and") 196 | out.println("# to generate the FPGA as well as the C++ test harness.") 197 | out.println(s"""[general]""") 198 | out.println(s"""filename = "${filename}$q""") 199 | out.println(s"""instrumented = "${instrumented}$q""") 200 | out.println(s"""top = "${top.name}$q""") 201 | out.println(s"""timestamp = ${timestamp}""") 202 | out.println() 203 | for(inp <- getFuzzingInputs(top.ports)) { 204 | out.println(s"""[[input]]""") 205 | out.println(s"""name = "${inp.name}$q""") 206 | out.println(s"""width = ${inp.width}""") 207 | out.println() 208 | } 209 | out.println() 210 | 211 | val muxConds = mutable.LinkedHashSet.empty[Expression] 212 | for ((config, annos) <- profiledSignals) { 213 | // this information helps us integrate the DUT as a BlackBox in the harness 214 | val portWidth = annos.size 215 | val portName = config.topPortName 216 | out.println(s"""[[port]]""") 217 | out.println(s"""name = "$portName$q""") 218 | out.println(s"""width = $portWidth""") 219 | out.println() 220 | 221 | for ((anno, index) <- annos.zipWithIndex) { 222 | val (mname, cond) = (anno.target: @unchecked) match { 223 | case ComponentName(cname, ModuleName(mname, _)) => (mname, AnnotationUtils.toExp(cname)) 224 | } 225 | 226 | val definitions = moduleDefinitions(mname) 227 | val name = cond.serialize 228 | val decl = definitions(name) 229 | 230 | // Very hacky way to get the original mux condition 231 | // (instead of the node pointing to it) 232 | // This will break soon, but (hopefully) not tonight! 233 | val origMuxCond = decl.asInstanceOf[DefNode].value 234 | 235 | // skip reset, as it is not fuzzed at the moment 236 | val skip = origMuxCond.serialize == "reset" || 237 | // also skip duplicate conditions 238 | (muxConds contains origMuxCond) 239 | 240 | if(!skip) { 241 | muxConds += origMuxCond 242 | val dbg = getDebugInfo(decl.asInstanceOf[Statement]) 243 | val human = getHumanReadableExpression(definitions, cond) 244 | out.println(s"""[[coverage]]""") 245 | out.println(s"""port = "${config.topPortName}$q""") 246 | out.println(s"""name = "$name$q""") 247 | out.println(s"""index = $index""") 248 | out.println(s"""filename = "${dbg.filename}$q""") 249 | out.println(s"""line = ${dbg.line}""") 250 | out.println(s"""column = ${dbg.col}""") 251 | out.println(s"""human = "$human$q""") 252 | out.println() 253 | } 254 | } 255 | } 256 | out.close 257 | } 258 | } 259 | 260 | -------------------------------------------------------------------------------- /instrumentation/src/rfuzz/package.scala: -------------------------------------------------------------------------------- 1 | 2 | import chisel3._ 3 | 4 | package object rfuzz { 5 | // Some magic to help SparseMem generically handle Mems of type Vec and not (for use of mask) 6 | case class MaybeVec[-T](evidence: Option[T <:< Vec[_]]) 7 | implicit def isVec[T](implicit ev: T <:< Vec[_]): MaybeVec[T] = MaybeVec(Some(ev)) 8 | implicit val notVec: MaybeVec[Any] = MaybeVec(None) 9 | } 10 | -------------------------------------------------------------------------------- /instrumentation/src/sic/Builder.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import firrtl._ 8 | import firrtl.annotations.{IsModule, ReferenceTarget} 9 | import logger.Logger 10 | 11 | import scala.collection.mutable 12 | 13 | /** Helps us construct well typed low-ish firrtl. 14 | * Some of these convenience functions could be moved to firrtl at some point. 15 | */ 16 | object Builder { 17 | def enableVerificationIR: Boolean = false 18 | 19 | /** Fails if there isn't exactly one Clock input */ 20 | def findClock(m: ir.Module): ir.RefLikeExpression = { 21 | val clocks = findClocks(m) 22 | assert(clocks.length == 1, s"[${m.name}] This transformation only works if there is exactly one clock.\n" + 23 | s"Found: ${clocks.map(_.serialize)}\n") 24 | clocks.head 25 | } 26 | 27 | def findClock(mod: ir.Module, logger: Logger): Option[ir.RefLikeExpression] = { 28 | val clocks = Builder.findClocks(mod) 29 | if(clocks.isEmpty) { 30 | logger.warn(s"WARN: [${mod.name}] found no clock input, skipping ...") 31 | } 32 | if(clocks.length > 1) { 33 | logger.warn(s"WARN: [${mod.name}] found more than one clock, picking the first one: " + clocks.map(_.serialize).mkString(", ")) 34 | } 35 | clocks.headOption 36 | } 37 | 38 | def findClocks(m: ir.Module): Seq[ir.RefLikeExpression] = { 39 | val ports = flattenedPorts(m.ports) 40 | val clockIO = ports.filter(_.tpe == ir.ClockType) 41 | val clockInputs = clockIO.filter(_.flow == SourceFlow) 42 | 43 | val isAsyncQueue = m.name == "AsyncQueue" || m.name.startsWith("AsyncQueue_") 44 | if(isAsyncQueue) { 45 | // The "clock" input of the AsyncQueue from rocketchip is unused 46 | // thus, even if both sides of the AsyncQueue are in the same clock domain (which is an assumption that we make) 47 | // using "clock" will lead to counters that never increment. 48 | // Using any of the other clocks is fine! 49 | clockInputs.filterNot(_.serialize == "clock") 50 | } else { 51 | clockInputs 52 | } 53 | } 54 | 55 | def refToTarget(module: IsModule, ref: ir.RefLikeExpression): ReferenceTarget = ref match { 56 | case ir.Reference(name, _, _, _) => module.ref(name) 57 | case ir.SubField(expr, name, _, _) => refToTarget(module, expr.asInstanceOf[ir.RefLikeExpression]).field(name) 58 | case ir.SubIndex(expr, value, _, _) => refToTarget(module, expr.asInstanceOf[ir.RefLikeExpression]).index(value) 59 | case other => throw new RuntimeException(s"Unsupported reference expression: $other") 60 | } 61 | 62 | private def flattenedPorts(ports: Seq[ir.Port]): Seq[ir.RefLikeExpression] = { 63 | ports.flatMap { p => expandRef(ir.Reference(p.name, p.tpe, PortKind, Utils.to_flow(p.direction))) } 64 | } 65 | 66 | private def expandRef(ref: ir.RefLikeExpression): Seq[ir.RefLikeExpression] = ref.tpe match { 67 | case ir.BundleType(fields) => 68 | Seq(ref) ++ fields.flatMap(f => expandRef(ir.SubField(ref, f.name, f.tpe, Utils.times(f.flip, ref.flow)))) 69 | case _ => Seq(ref) 70 | } 71 | 72 | def findResets(m: ir.Module): Seq[ir.RefLikeExpression] = { 73 | val ports = flattenedPorts(m.ports) 74 | val inputs = ports.filter(_.flow == SourceFlow) 75 | val ofResetType = inputs.filter(p => p.tpe == ir.AsyncResetType || p.tpe == ir.ResetType) 76 | val boolWithCorrectName = inputs.filter(p => p.tpe == ir.UIntType(ir.IntWidth(1)) && p.serialize.endsWith("reset")) 77 | val resetInputs = ofResetType ++ boolWithCorrectName 78 | resetInputs 79 | } 80 | 81 | def reduceAnd(e: ir.Expression): ir.Expression = ir.DoPrim(PrimOps.Andr, List(e), List(), Utils.BoolType) 82 | 83 | def add(a: ir.Expression, b: ir.Expression): ir.Expression = { 84 | val (aWidth, bWidth) = (getWidth(a.tpe), getWidth(b.tpe)) 85 | val resultWidth = Seq(aWidth, bWidth).max 86 | val (aPad, bPad) = (pad(a, resultWidth), pad(b, resultWidth)) 87 | val res = ir.DoPrim(PrimOps.Add, List(aPad, bPad), List(), withWidth(a.tpe, resultWidth + 1)) 88 | ir.DoPrim(PrimOps.Bits, List(res), List(resultWidth - 1, 0), withWidth(a.tpe, resultWidth)) 89 | } 90 | 91 | def pad(e: ir.Expression, to: BigInt): ir.Expression = { 92 | val from = getWidth(e.tpe) 93 | require(to >= from) 94 | if (to == from) { e } 95 | else { ir.DoPrim(PrimOps.Pad, List(e), List(to), withWidth(e.tpe, to)) } 96 | } 97 | 98 | def withWidth(tpe: ir.Type, width: BigInt): ir.Type = tpe match { 99 | case ir.UIntType(_) => ir.UIntType(ir.IntWidth(width)) 100 | case ir.SIntType(_) => ir.SIntType(ir.IntWidth(width)) 101 | case other => throw new RuntimeException(s"Cannot change the width of $other!") 102 | } 103 | 104 | def getWidth(tpe: ir.Type): BigInt = firrtl.bitWidth(tpe) 105 | 106 | def makeRegister( 107 | stmts: mutable.ListBuffer[ir.Statement], 108 | info: ir.Info, 109 | name: String, 110 | tpe: ir.Type, 111 | clock: ir.Expression, 112 | next: ir.Expression, 113 | reset: ir.Expression = Utils.False(), 114 | init: Option[ir.Expression] = None, 115 | ): ir.Reference = { 116 | if (isAsyncReset(reset)) { 117 | val initExpr = init.getOrElse(ir.Reference(name, tpe, RegKind)) 118 | val reg = ir.DefRegister(info, name, tpe, clock, reset, initExpr) 119 | stmts.append(reg) 120 | stmts.append(ir.Connect(info, ir.Reference(reg), next)) 121 | ir.Reference(reg) 122 | } else { 123 | val ref = ir.Reference(name, tpe, RegKind, UnknownFlow) 124 | stmts.append(ir.DefRegister(info, name, tpe, clock, Utils.False(), ref)) 125 | init match { 126 | case Some(value) => stmts.append(ir.Connect(info, ref, Utils.mux(reset, value, next))) 127 | case None => stmts.append(ir.Connect(info, ref, next)) 128 | } 129 | ref 130 | } 131 | } 132 | 133 | def isAsyncReset(reset: ir.Expression): Boolean = reset.tpe match { 134 | case ir.AsyncResetType => true 135 | case _ => false 136 | } 137 | 138 | def getKind(ref: ir.RefLikeExpression): firrtl.Kind = ref match { 139 | case ir.Reference(_, _, kind, _) => kind 140 | case ir.SubField(expr, _, _, _) => getKind(expr.asInstanceOf[ir.RefLikeExpression]) 141 | case ir.SubIndex(expr, _, _, _) => getKind(expr.asInstanceOf[ir.RefLikeExpression]) 142 | case ir.SubAccess(expr, _, _, _) => getKind(expr.asInstanceOf[ir.RefLikeExpression]) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /instrumentation/src/sic/Coverage.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import chiseltest.coverage._ 8 | import firrtl._ 9 | import firrtl.analyses.InstanceKeyGraph 10 | import firrtl.analyses.InstanceKeyGraph.InstanceKey 11 | import firrtl.options.Dependency 12 | import firrtl.passes.InlineInstances 13 | import firrtl.stage.TransformManager.TransformDependency 14 | import logger.LazyLogging 15 | import rfuzz.DoNotProfileModule 16 | 17 | import scala.collection.mutable 18 | import scala.util.matching.Regex 19 | 20 | /** rfuzz.DoNotProfileModule: Tags a module that should not have any coverage added. 21 | * This annotation should be respected by all automated coverage passes. 22 | */ 23 | 24 | object Coverage { 25 | val AllPasses: Seq[TransformDependency] = Seq( 26 | Dependency(LineCoveragePass), Dependency(ToggleCoveragePass), Dependency(FsmCoveragePass), 27 | ) 28 | 29 | def collectTestCoverage(annos: AnnotationSeq): List[(String, Long)] = { 30 | annos.collect { case TestCoverage(e) => e } match { 31 | case Seq(one) => one 32 | case other => throw new RuntimeException(s"Expected exactly one TestCoverage annotation, not: $other") 33 | } 34 | } 35 | 36 | def collectModuleInstances(annos: AnnotationSeq): List[(String, String)] = { 37 | annos.collect { case ModuleInstancesAnnotation(e) => e } match { 38 | case Seq(one) => one 39 | case other => throw new RuntimeException(s"Expected exactly one ModuleInstances annotation, not: $other") 40 | } 41 | } 42 | 43 | def moduleToInstances(annos: AnnotationSeq): Map[String, List[String]] = { 44 | collectModuleInstances(annos).groupBy(_._2).map{ case (k,v) => k -> v.map(_._1) } 45 | } 46 | 47 | def collectModulesToIgnore(state: CircuitState): Set[String] = { 48 | val main = state.circuit.main 49 | state.annotations.collect { case DoNotProfileModule(target) if target.circuit.name == main => target.name }.toSet 50 | } 51 | 52 | def path(prefix: String, suffix: String): String = { 53 | if (prefix.isEmpty) suffix else prefix + "." + suffix 54 | } 55 | 56 | type Lines = List[(String, List[Int])] 57 | private val chiselFileInfo: Regex = raw"\s*([^\.]+\.\w+) (\d+):(\d+)".r 58 | 59 | def parseFileInfo(i: ir.FileInfo): Seq[(String, Int)] = { 60 | chiselFileInfo.findAllIn(i.unescaped).map { 61 | case chiselFileInfo(filename, line, col) => (filename, line.toInt) 62 | }.toSeq 63 | } 64 | 65 | def infosToLines(infos: Seq[ir.Info]): Lines = { 66 | val parsed = findFileInfos(infos).flatMap(parseFileInfo) 67 | val byFile = parsed.groupBy(_._1).toList.sortBy(_._1) 68 | byFile.map { case (filename, e) => filename -> e.map(_._2).toSet.toList.sorted } 69 | } 70 | 71 | def findFileInfos(infos: Seq[ir.Info]): Seq[ir.FileInfo] = infos.flatMap(findFileInfos) 72 | def findFileInfos(info: ir.Info): Seq[ir.FileInfo] = info match { 73 | case ir.MultiInfo(infos) => findFileInfos(infos) 74 | case f: ir.FileInfo => List(f) 75 | case _ => List() 76 | } 77 | } 78 | 79 | /** Represents a Scala code base. */ 80 | class CodeBase(root: os.Path) extends LazyLogging { 81 | require(os.exists(root), s"Could not find root directory: $root") 82 | require(os.isDir(root), s"Is not a directory: $root") 83 | 84 | val index = CodeBase.index(root) 85 | private val duplicates = index.filter(_._2.size > 1) 86 | 87 | def warnAboutDuplicates(): Unit = { 88 | if (duplicates.nonEmpty) { 89 | val msgs = duplicates.flatMap { case (key, values) => 90 | Seq(s"Multiple files map to key: $key") ++ 91 | values.map(v => s" - $v") 92 | } 93 | 94 | val msg = Seq(s"In code base: $root") ++ msgs 95 | logger.warn(msg.mkString("\n")) 96 | } 97 | } 98 | 99 | val duplicateKeys: List[String] = duplicates.keys.toList 100 | def isDuplicate(key: String): Boolean = getDuplicate(key).isDefined 101 | def getDuplicate(key: String): Option[List[os.RelPath]] = duplicates.get(key) 102 | 103 | /** returns None if the key is not unique */ 104 | def getLine(key: String, line: Int): Option[String] = { 105 | require(line > 0) 106 | getSource(key).map(_(line - 1)) 107 | } 108 | 109 | private val sourceCache = mutable.HashMap[os.RelPath, IndexedSeq[String]]() 110 | def getSource(key: String): Option[IndexedSeq[String]] = getFilePath(key).map { rel => 111 | sourceCache.getOrElseUpdate(rel, os.read.lines(root / rel)) 112 | } 113 | 114 | /** returns None if the key is not unique */ 115 | private def getFilePath(key: String): Option[os.RelPath] = index.get(key) match { 116 | case Some(List(one)) => Some(one) 117 | case _ => None 118 | } 119 | 120 | } 121 | 122 | object CodeBase { 123 | 124 | /** finds all source files in the path and maps them by their filename */ 125 | private def index(root: os.Path, exts: Set[String] = Set("scala")): Map[String, List[os.RelPath]] = { 126 | val i = mutable.HashMap[String, List[os.RelPath]]() 127 | index(root, root, exts, i) 128 | i.toMap 129 | } 130 | 131 | private def index(root: os.Path, dir: os.Path, exts: Set[String], i: mutable.HashMap[String, List[os.RelPath]]): Unit = { 132 | val stream = os.walk.stream(dir) 133 | stream.foreach { f: os.Path => 134 | if (exts.contains(f.ext)) { 135 | val key = f.last 136 | val old = i.getOrElse(key, List()) 137 | val relative = f.relativeTo(root) 138 | i(key) = relative +: old 139 | } 140 | } 141 | } 142 | } 143 | 144 | /** this is a copy of the upstream version from chiseltest with some dependencies changed */ 145 | object ModuleInstancesPass extends Transform with DependencyAPIMigration { 146 | override def prerequisites: Seq[TransformDependency] = Seq() 147 | // we needs to run *after* any transform that changes the hierarchy 148 | override def optionalPrerequisites: Seq[TransformDependency] = Seq(Dependency[InlineInstances]) 149 | override def invalidates(a: Transform): Boolean = false 150 | 151 | override protected def execute(state: CircuitState): CircuitState = { 152 | val children = InstanceKeyGraph(state.circuit).getChildInstances.toMap 153 | val topInstance = InstanceKey("", state.circuit.main) 154 | val topChildren = children(topInstance.module) 155 | val instances = topInstance +: topChildren.flatMap(onInstance("", _, children)) 156 | val instanceToModule = instances.toList.map(i => i.name -> i.module) 157 | val anno = ModuleInstancesAnnotation(instanceToModule) 158 | state.copy(annotations = anno +: state.annotations) 159 | } 160 | 161 | /** expands the instance name to its complete path (relative to the main module) */ 162 | private def onInstance( 163 | prefix: String, 164 | inst: InstanceKey, 165 | children: Map[String, Seq[InstanceKey]] 166 | ): Seq[InstanceKey] = { 167 | val ii = InstanceKey(prefix + inst.name, inst.module) 168 | val cc = children(ii.module).flatMap(onInstance(ii.name + ".", _, children)) 169 | ii +: cc 170 | } 171 | } -------------------------------------------------------------------------------- /instrumentation/src/sic/CoverageInfoEmitter.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import chiseltest.coverage.{CoverageInfo, ModuleInstancesAnnotation, TestCoverage} 8 | import firrtl._ 9 | import firrtl.annotations.{JsonProtocol, NoTargetAnnotation} 10 | import firrtl.options.{CustomFileEmission, Dependency} 11 | import firrtl.options.Viewer.view 12 | import firrtl.stage.FirrtlOptions 13 | 14 | 15 | /** Serializes all relevant coverage annotations. */ 16 | object CoverageInfoEmitter extends Transform with DependencyAPIMigration { 17 | override def prerequisites = Seq() 18 | override def optionalPrerequisites = Coverage.AllPasses ++ Seq( 19 | Dependency(CoverageStatisticsPass), Dependency(ModuleInstancesPass) 20 | ) 21 | override def invalidates(a: Transform) = false 22 | 23 | override def execute(state: CircuitState): CircuitState = { 24 | val (covAnnos, otherAnnos) = state.annotations.partition { 25 | case _: CoverageInfo => true 26 | case _: TestCoverage => true 27 | case _: ModuleInstancesAnnotation => true 28 | case SkipLineCoverageAnnotation => true 29 | case SkipToggleCoverageAnnotation => true 30 | case SkipFsmCoverageAnnotation => true 31 | case _: FsmInfoAnnotation => true 32 | // case _: DoNotCoverAnnotation => true 33 | case WireToggleCoverage => true 34 | case MemoryToggleCoverage => true 35 | case RegisterToggleCoverage => true 36 | case PortToggleCoverage => true 37 | case _: RemoveCoverAnnotation => true 38 | case _: LoadCoverageAnnotation => true 39 | case _ => false 40 | } 41 | 42 | if(covAnnos.nonEmpty) { 43 | val str = JsonProtocol.serialize(covAnnos) 44 | val out = EmittedCoverageInfo(str, state.circuit.main) 45 | state.copy(annotations = out +: otherAnnos) 46 | } else { state } 47 | } 48 | } 49 | 50 | case class EmittedCoverageInfo(str: String, main: String) extends NoTargetAnnotation with CustomFileEmission { 51 | override protected def baseFileName(annotations: AnnotationSeq): String = { 52 | view[FirrtlOptions](annotations).outputFileName.getOrElse(main) 53 | } 54 | override protected def suffix = Some(".cover.json") 55 | override def getBytes = str.getBytes 56 | } 57 | -------------------------------------------------------------------------------- /instrumentation/src/sic/CoverageShellOptions.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import sic.passes.{AddResetAssumptionPass, ChangeMainPass, MakeMainAnnotation, RandomStateInit} 8 | import firrtl.annotations.{CircuitTarget, ModuleTarget} 9 | import firrtl.options._ 10 | import firrtl.stage.RunFirrtlTransformAnnotation 11 | import rfuzz.DoNotProfileModule 12 | 13 | final class CoverageShellOptions extends RegisteredLibrary { 14 | override def name = "Coverage" 15 | 16 | override def options = Seq( 17 | new ShellOption[Unit]( 18 | longOption = "line-coverage", 19 | toAnnotationSeq = _ => LineCoverage.annotations ++ Common, 20 | helpText = "enable line coverage instrumentation" 21 | ), 22 | new ShellOption[Unit]( 23 | longOption = "fsm-coverage", 24 | toAnnotationSeq = _ => FsmCoverage.annotations ++ Common, 25 | helpText = "enable finite state machine coverage instrumentation" 26 | ), 27 | new ShellOption[Unit]( 28 | longOption = "toggle-coverage", 29 | toAnnotationSeq = _ => ToggleCoverage.all ++ Common, 30 | helpText = "enable toggle coverage instrumentation for all signals" 31 | ), 32 | new ShellOption[Unit]( 33 | longOption = "toggle-coverage-ports", 34 | toAnnotationSeq = _ => ToggleCoverage.ports ++ Common, 35 | helpText = "enable toggle coverage instrumentation for all I/O ports" 36 | ), 37 | new ShellOption[Unit]( 38 | longOption = "toggle-coverage-registers", 39 | toAnnotationSeq = _ => ToggleCoverage.registers ++ Common, 40 | helpText = "enable toggle coverage instrumentation for all registers" 41 | ), 42 | new ShellOption[Unit]( 43 | longOption = "toggle-coverage-memories", 44 | toAnnotationSeq = _ => ToggleCoverage.memories ++ Common, 45 | helpText = "enable toggle coverage instrumentation for all memory ports" 46 | ), 47 | new ShellOption[Unit]( 48 | longOption = "toggle-coverage-wires", 49 | toAnnotationSeq = _ => ToggleCoverage.wires ++ Common, 50 | helpText = "enable toggle coverage instrumentation for all wires" 51 | ), 52 | new ShellOption[Unit]( 53 | longOption = "ready-valid-coverage", 54 | toAnnotationSeq = _ => ReadyValidCoverage.annotations ++ Common, 55 | helpText = "enable coverage of read valid interfaces on I/O ports" 56 | ), 57 | new ShellOption[String]( 58 | longOption = "do-not-cover", 59 | toAnnotationSeq = a => Seq(DoNotProfileModule(parseModuleTarget(a).toNamed)), 60 | helpText = "select module which should not be instrumented with coverage", 61 | helpValueName = Some("") 62 | ), 63 | new ShellOption[Unit]( 64 | longOption = "emit-cover-info", 65 | toAnnotationSeq = _ => Seq(RunFirrtlTransformAnnotation(Dependency(CoverageInfoEmitter))), 66 | helpText = "write coverage information to a .cover.json file" 67 | ), 68 | new ShellOption[Unit]( 69 | longOption = "random-state-init", 70 | toAnnotationSeq = _ => Seq(RunFirrtlTransformAnnotation(Dependency(RandomStateInit))), 71 | helpText = "initializes registers and memories with random (at compile time) values" 72 | ), 73 | new ShellOption[Unit]( 74 | longOption = "formal-cover", 75 | toAnnotationSeq = _ => Seq(RunFirrtlTransformAnnotation(Dependency(RandomStateInit)), 76 | RunFirrtlTransformAnnotation(Dependency(AddResetAssumptionPass))), 77 | helpText = "prepares the circuit for formal cover trace generation" 78 | ), 79 | new ShellOption[String]( 80 | longOption = "make-main", 81 | toAnnotationSeq = a => Seq(MakeMainAnnotation(parseModuleTarget(a)), RunFirrtlTransformAnnotation(Dependency(ChangeMainPass))), 82 | helpText = "selects a module to be the main module of the circuit", 83 | helpValueName = Some("") 84 | ), 85 | new ShellOption[Unit]( 86 | longOption = "add-reset-assumption", 87 | toAnnotationSeq = _ => Seq(RunFirrtlTransformAnnotation(Dependency(AddResetAssumptionPass))), 88 | helpText = "adds an assumption to the toplevel module that all resets are active in the first cycle" 89 | ), 90 | new ShellOption[String]( 91 | longOption = "remove-covered", 92 | toAnnotationSeq = a => Seq(LoadCoverageAnnotation(a), 93 | RunFirrtlTransformAnnotation(Dependency(RemoveCoverPointsPass)), 94 | RunFirrtlTransformAnnotation(Dependency(FindCoversToRemovePass))), 95 | helpText = "removes all points that have been covered at least N times", 96 | helpValueName = Some("<...cove.json>") 97 | ), 98 | ) 99 | 100 | private def parseModuleTarget(a: String): ModuleTarget = { 101 | val parts = a.trim.split(':').toSeq 102 | parts match { 103 | case Seq(circuit, module) => CircuitTarget(circuit.trim).module(module.trim) 104 | case _ => throw new RuntimeException(s"Expected format: , not: $a") 105 | } 106 | } 107 | 108 | private val Common = Seq(RunFirrtlTransformAnnotation(Dependency(CoverageStatisticsPass))) 109 | } 110 | -------------------------------------------------------------------------------- /instrumentation/src/sic/CoverageStatisticsPass.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import chiseltest.coverage.CoverageInfo 8 | import firrtl._ 9 | 10 | /** Display information about all coverage instrumentation. 11 | * This pass does not modify the circuit itself, it only prints out 12 | * some information. Make sure to set the log level at least to "info" 13 | * to see the output. 14 | * */ 15 | object CoverageStatisticsPass extends Transform with DependencyAPIMigration { 16 | override def prerequisites = Seq() 17 | override def optionalPrerequisites = Coverage.AllPasses 18 | override def optionalPrerequisiteOf = AllEmitters() 19 | override def invalidates(a: Transform) = false 20 | 21 | override def execute(state: CircuitState): CircuitState = { 22 | analysis.foreach(a => a(state)) 23 | state 24 | } 25 | 26 | private val analysis = Seq(generalAnalysis(_), analyzeLineCoverage(_), analyzeToggleCoverage(_), 27 | analyzeFsmCoverage(_), analyzeRemovedCoverage(_), analyzeReadyValidCoverage(_)) 28 | 29 | private def generalAnalysis(state: CircuitState): Unit = { 30 | val coverPoints = state.annotations.collect{ case a: CoverageInfo => a }.size 31 | logger.info("Coverage Statistics:") 32 | logger.info(s"- Total automatic cover points: $coverPoints") 33 | val ignored = Coverage.collectModulesToIgnore(state) 34 | if(ignored.nonEmpty) { 35 | logger.info(s"- Ignored modules: " + ignored.toSeq.sorted.mkString(", ")) 36 | } 37 | } 38 | 39 | private def analyzeLineCoverage(state: CircuitState): Unit = { 40 | val line = state.annotations.collect{ case a : LineCoverageAnnotation => a } 41 | if(line.nonEmpty) { 42 | logger.info("Line Coverage:") 43 | logger.info(s"- Line cover points: ${line.size}") 44 | } 45 | } 46 | 47 | private def analyzeToggleCoverage(state: CircuitState): Unit = { 48 | val annos = state.annotations 49 | val toggle = annos.collect{ case a : ToggleCoverageAnnotation => a } 50 | if(toggle.nonEmpty) { 51 | logger.info("Toggle Coverage:") 52 | logger.info(s"- Toggle cover points: ${toggle.size}") 53 | val allBits = toggle.flatMap(a => a.signals.map(_.toString() + "[" + a.bit + "]")) 54 | val allSignals = toggle.flatMap(_.signals.map(_.toString())).distinct 55 | logger.info(s"- Signals covered: ${allSignals.size}") 56 | logger.info(s"- Signal Bits covered: ${allBits.size}") 57 | val opts = Seq(PortToggleCoverage -> "ports", RegisterToggleCoverage -> "regs", 58 | MemoryToggleCoverage -> "mems", WireToggleCoverage -> "wires") 59 | val on = opts.map{ case (a, s) => if(annos.contains(a)) s + " ✅" else s + " ❌" }.mkString(" ") 60 | logger.info("- " + on) 61 | } 62 | } 63 | 64 | private def analyzeFsmCoverage(state: CircuitState): Unit = { 65 | val annos = state.annotations 66 | val fsms = annos.collect { case a: FsmCoverageAnnotation => a } 67 | if(fsms.nonEmpty) { 68 | logger.info("FSM Coverage:") 69 | fsms.foreach { case FsmCoverageAnnotation(stateReg, states, transitions) => 70 | logger.info(s"- ${stateReg}: ${states.length} states, ${transitions.length} transitions") 71 | } 72 | } 73 | } 74 | 75 | private def analyzeRemovedCoverage(state: CircuitState): Unit = { 76 | val counts = state.annotations.collect{ case a: RemoveCoverAnnotation => a.remove.length } 77 | if(counts.nonEmpty) { 78 | logger.info("Removed Cover Points:") 79 | logger.info(s"- ${counts.sum} coverage points removed because they were already sufficiently covered") 80 | } 81 | } 82 | 83 | private def analyzeReadyValidCoverage(state: CircuitState): Unit = { 84 | val fires = state.annotations.collect{ case a : ReadyValidCoverageAnnotation => a } 85 | if(fires.nonEmpty) { 86 | logger.info("Ready/Valid Coverage:") 87 | logger.info(s"- fire signals covered: ${fires.size}") 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /instrumentation/src/sic/FsmCoverage.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import firrtl._ 8 | import firrtl.options.Dependency 9 | import firrtl.stage.RunFirrtlTransformAnnotation 10 | 11 | object FsmCoverage { 12 | def annotations: AnnotationSeq = Seq( 13 | RunFirrtlTransformAnnotation(Dependency(FsmCoveragePass)), 14 | RunFirrtlTransformAnnotation(Dependency(ModuleInstancesPass)) 15 | ) 16 | 17 | def processCoverage(annos: AnnotationSeq): Seq[FsmCoverageData] = { 18 | val fsms = annos.collect{ case a : FsmCoverageAnnotation => a } 19 | val cov = Coverage.collectTestCoverage(annos).toMap 20 | val moduleToInst = Coverage.moduleToInstances(annos) 21 | 22 | fsms.flatMap { fsm => 23 | val top = fsm.stateReg.circuit + "." 24 | moduleToInst(fsm.stateReg.module).map { inst => 25 | val states = fsm.states 26 | .map(s => s._1 -> cov(Coverage.path(inst, s._2.ref))) 27 | .toList.sortBy(_._1) 28 | val transitions = fsm.transitions 29 | .map(t => t._1 -> cov(Coverage.path(inst, t._2.ref))) 30 | .toList.sortBy(_._1) 31 | FsmCoverageData(top + Coverage.path(inst, fsm.stateReg.ref), states, transitions) 32 | } 33 | } 34 | } 35 | 36 | def textReport(data: Seq[FsmCoverageData]): Iterable[String] = data.flatMap { fsm => 37 | Seq( 38 | fsm.name, 39 | fsm.states.map{ case (name, count) => s"$name ($count)" }.mkString(", ")) ++ 40 | fsm.transitions.map{ case ((from, to), count) => s"$from -> $to: $count" } 41 | } 42 | } 43 | 44 | case class FsmCoverageData(name: String, states: List[(String, Long)], transitions: List[((String, String), Long)]) -------------------------------------------------------------------------------- /instrumentation/src/sic/FsmCoveragePass.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import chiseltest.coverage.CoverageInfo 8 | import sic.passes.{RegisterResetAnnotation, RegisterResetAnnotationPass} 9 | import firrtl.annotations._ 10 | import firrtl._ 11 | import firrtl.options.Dependency 12 | import firrtl.stage.Forms 13 | import firrtl.stage.TransformManager.TransformDependency 14 | 15 | import scala.collection.mutable 16 | 17 | case class FsmCoverageAnnotation( 18 | stateReg: ReferenceTarget, 19 | states: Seq[(String, ReferenceTarget)], 20 | transitions: Seq[((String, String), ReferenceTarget)]) extends MultiTargetAnnotation with CoverageInfo { 21 | override def targets = Seq(Seq(stateReg)) ++ states.map(s => Seq(s._2)) ++ transitions.map(t => Seq(t._2)) 22 | 23 | override def duplicate(n: Seq[Seq[Target]]) = { 24 | assert(n.length == 1 + states.length + transitions.length) 25 | n.foreach(e => assert(e.length == 1, "Cover points and state registers should not be split up!")) 26 | val targets = n.map(_.head.asInstanceOf[ReferenceTarget]) 27 | val r = copy(stateReg = targets.head, 28 | states = states.map(_._1).zip(targets.slice(1, states.length + 1)), 29 | transitions = transitions.map(_._1).zip(targets.drop(1 + states.length)), 30 | ) 31 | r 32 | } 33 | } 34 | 35 | case object SkipFsmCoverageAnnotation extends NoTargetAnnotation 36 | 37 | object FsmCoveragePass extends Transform with DependencyAPIMigration { 38 | val Prefix = "f" 39 | 40 | override def prerequisites: Seq[TransformDependency] = Forms.LowForm ++ Seq(Dependency(FsmInfoPass), Dependency(RegisterResetAnnotationPass)) 41 | override def invalidates(a: Transform): Boolean = false 42 | 43 | override def execute(state: CircuitState): CircuitState = { 44 | if(state.annotations.contains(SkipFsmCoverageAnnotation)) { 45 | logger.info("[FsmCoverage] skipping due to SkipFsmCoverage annotation") 46 | return state 47 | } 48 | 49 | // collect FSMs in modules that are not ignored 50 | val ignoreMods = Coverage.collectModulesToIgnore(state) 51 | val infos = state.annotations.collect{ case a: FsmInfoAnnotation if !ignoreMods(a.target.module) => a } 52 | 53 | // if there are no FSMs there is nothing to do 54 | if(infos.isEmpty) return state 55 | 56 | // instrument FSMs 57 | val registerResets = state.annotations.collect { case a: RegisterResetAnnotation => a } 58 | val newAnnos = mutable.ListBuffer[Annotation]() 59 | val c = CircuitTarget(state.circuit.main) 60 | val circuit = state.circuit.mapModule(onModule(_, c, newAnnos, infos, registerResets)) 61 | state.copy(circuit = circuit, annotations = newAnnos.toList ++ state.annotations) 62 | } 63 | 64 | private def onModule(m: ir.DefModule, c: CircuitTarget, annos: mutable.ListBuffer[Annotation], 65 | infos: Seq[FsmInfoAnnotation], resets: Seq[RegisterResetAnnotation]): ir.DefModule = m match { 66 | case mod: ir.Module => 67 | val fsms = infos.filter(_.target.module == mod.name) 68 | if(fsms.isEmpty) { mod } else { 69 | val isFsm = fsms.map(_.target.ref).toSet 70 | val fsmRegs = findFsmRegs(mod.body, isFsm) 71 | val stmts = new mutable.ListBuffer[ir.Statement]() 72 | val ctx = ModuleCtx(c.module(mod.name), stmts, Namespace(mod)) 73 | val toReset = RegisterResetAnnotationPass.findResetsInModule(ctx.m, resets) 74 | val fsmAnnos = fsms.map { f => onFsm(f, fsmRegs.find(_.name == f.target.ref).get, ctx, toReset.get) } 75 | annos ++= fsmAnnos 76 | val newBody = ir.Block(mod.body +: stmts.toList) 77 | 78 | mod.copy(body = newBody) 79 | } 80 | case other => other 81 | } 82 | 83 | private case class ModuleCtx(m: ModuleTarget, stmts: mutable.ListBuffer[ir.Statement], namespace: Namespace) 84 | 85 | private def onFsm(fsm: FsmInfoAnnotation, reg: ir.DefRegister, ctx: ModuleCtx, toReset: String => Option[String]): Annotation = { 86 | val info = reg.info 87 | val clock = reg.clock 88 | val reset = toReset(reg.name).map(ir.Reference(_, Utils.BoolType, NodeKind, SourceFlow)).getOrElse(Utils.False()) 89 | val notReset = Utils.not(reset) 90 | val regRef = ir.Reference(reg) 91 | val regWidth = firrtl.bitWidth(reg.tpe) 92 | def inState(s: BigInt): ir.Expression = Utils.eq(regRef, ir.UIntLiteral(s, ir.IntWidth(regWidth))) 93 | 94 | // cover state when FSM is _not_ in reset 95 | val states = fsm.states.map { case (id, stateName) => 96 | val name = ctx.namespace.newName(reg.name + "_" + stateName) 97 | ctx.stmts.append(ir.Verification(ir.Formal.Cover, info, clock, inState(id), notReset, ir.StringLit(""), name)) 98 | stateName -> ctx.m.ref(name) 99 | } 100 | 101 | // create a register to hold the previous state 102 | val prevState = Builder.makeRegister(ctx.stmts, info, ctx.namespace.newName(reg.name + "_prev"), reg.tpe, clock, regRef) 103 | def inPrevState(s: BigInt): ir.Expression = Utils.eq(prevState, ir.UIntLiteral(s, ir.IntWidth(regWidth))) 104 | 105 | // create a register to track if the previous state is valid 106 | val prevValid = Builder.makeRegister(ctx.stmts, info, ctx.namespace.newName(reg.name + "_prev_valid"), Utils.BoolType, clock, notReset) 107 | 108 | // create a transition valid signal 109 | val transitionValid = ir.Reference(ctx.namespace.newName(reg.name + "_t_valid"), Utils.BoolType, NodeKind) 110 | ctx.stmts.append(ir.DefNode(info, transitionValid.name, Utils.and(notReset, prevValid))) 111 | 112 | val idToName = fsm.states.toMap 113 | val transitions = fsm.transitions.map { case (from, to) => 114 | val (fromName, toName) = (idToName(from), idToName(to)) 115 | val name = ctx.namespace.newName(reg.name + "_" + fromName + "_to_" + toName) 116 | ctx.stmts.append(ir.Verification(ir.Formal.Cover, info, clock, Utils.and(inPrevState(from), inState(to)), 117 | transitionValid, ir.StringLit(""), name)) 118 | (fromName, toName) -> ctx.m.ref(name) 119 | } 120 | 121 | FsmCoverageAnnotation(ctx.m.ref(reg.name), states, transitions) 122 | } 123 | 124 | private def printFsmInfo(fsm: FsmInfoAnnotation): Unit = { 125 | val toName = fsm.states.toMap 126 | println(s"[${fsm.target.module}.${fsm.target.name}] Found FSM") 127 | if (fsm.start.nonEmpty) { 128 | println(s"Start: ${toName(fsm.start.get)}") 129 | } 130 | println("Transitions:") 131 | fsm.transitions.foreach(t => println(s"${toName(t._1)} -> ${toName(t._2)}")) 132 | } 133 | 134 | private def findFsmRegs(s: ir.Statement, isFsm: String => Boolean): Seq[ir.DefRegister] = s match { 135 | case r : ir.DefRegister if isFsm(r.name) => List(r) 136 | case ir.Block(stmts) => stmts.flatMap(findFsmRegs(_, isFsm)) 137 | case _ : ir.Conditionally => throw new RuntimeException("Unexpected when statement! Expected LoFirrtl.") 138 | case _ => List() 139 | } 140 | } -------------------------------------------------------------------------------- /instrumentation/src/sic/FsmInfoPass.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import chisel3.experimental.EnumAnnotations.{EnumComponentAnnotation, EnumDefAnnotation} 8 | import firrtl.annotations._ 9 | import firrtl._ 10 | import firrtl.stage.Forms 11 | import firrtl.stage.TransformManager.TransformDependency 12 | 13 | import scala.collection.mutable 14 | 15 | case class FsmInfoAnnotation(target: ReferenceTarget, states: Seq[(BigInt, String)], transitions: Seq[(BigInt, BigInt)], start: Option[BigInt]) 16 | extends SingleTargetAnnotation[ReferenceTarget] { 17 | override def duplicate(n: ReferenceTarget) = copy(target=n) 18 | } 19 | 20 | /** Annotates FSMs in the design with information about all available states and transitions. */ 21 | object FsmInfoPass extends Transform with DependencyAPIMigration { 22 | val Prefix = "f" 23 | 24 | override def prerequisites: Seq[TransformDependency] = Forms.LowForm 25 | override def invalidates(a: Transform): Boolean = false 26 | 27 | override protected def execute(state: CircuitState): CircuitState = { 28 | val enums = state.annotations.collect { case a: EnumDefAnnotation => a.typeName -> a }.toMap 29 | val components = state.annotations.collect { case a : EnumComponentAnnotation => a } 30 | 31 | // if there are no enums, we won't be able to find any FSMs 32 | if(enums.isEmpty) return state 33 | 34 | val c = CircuitTarget(state.circuit.main) 35 | val infos = state.circuit.modules.flatMap(onModule(_, c, enums, components)) 36 | 37 | state.copy(annotations = infos ++: state.annotations) 38 | } 39 | 40 | private def onModule(m: ir.DefModule, c: CircuitTarget, enums: Map[String, EnumDefAnnotation], components: Seq[EnumComponentAnnotation]): List[Annotation] = m match { 41 | case mod: ir.Module => 42 | val localComponents = components 43 | .filter(c => toReferenceTarget(c.target).module == mod.name) 44 | .map(c => toReferenceTarget(c.target).ref -> c).toMap 45 | if (localComponents.isEmpty) { 46 | List() 47 | } else { 48 | // sometime wires/nodes get annotated instead of registers, we want to filter those out 49 | val isReg = findRegNames(mod.body) 50 | val realStateRegs = localComponents.filter(c => isReg(c._1)) 51 | // extract net info from module 52 | val regNames = realStateRegs.keySet 53 | val netData = new ModuleNetAnalyzer(regNames).run(mod) 54 | // sometimes wires/nodes instead of registers 55 | realStateRegs.map { case (name, anno) => 56 | analyzeFSM(c.module(mod.name), name, netData, enums(anno.enumTypeName).definition) 57 | }.toList 58 | } 59 | case other => List() 60 | } 61 | 62 | private def analyzeFSM(module: ModuleTarget, regName: String, netData: ModuleNetData, states: Map[String, BigInt]): FsmInfoAnnotation = { 63 | val nextExpr = netData.next(regName) 64 | val (resetState, next) = destructReset(nextExpr) 65 | 66 | // analyze next state expression for each start state 67 | val allStates = states.values.toSeq 68 | val transitions = states.toSeq.sortBy(_._2).flatMap { case (name, from) => 69 | val res = new FsmAnalyzer(netData.con, regName, from, allStates).analyzeNext(next) 70 | res.map(from -> _) 71 | } 72 | 73 | FsmInfoAnnotation(module.ref(regName), 74 | states = states.toSeq.sorted.map{ case (n, i) => i -> n }, 75 | transitions = transitions, 76 | start = resetState 77 | ) 78 | } 79 | 80 | // tries to extract the reset value, this assumes synchronous resets! 81 | private def destructReset(e: ir.Expression): (Option[BigInt], ir.Expression) = e match { 82 | case ir.Mux(ir.Reference("reset", _, _, _), rval: ir.UIntLiteral, oval, _) => (Some(rval.value), oval) 83 | case ir.Mux(ir.DoPrim(PrimOps.Not, Seq(ir.Reference("reset", _, _, _)), _, _), oval, rval: ir.UIntLiteral, _) => (Some(rval.value), oval) 84 | case _ => (None, e) 85 | } 86 | 87 | private def toReferenceTarget(n: Named): ReferenceTarget = n match { 88 | case ComponentName(name, module) => module.toTarget.ref(name) 89 | case other => throw new NotImplementedError(s"Unexpected $other") 90 | } 91 | } 92 | 93 | 94 | private class FsmAnalyzer(con: Map[String, ConnectionInfo], stateRegName: String, stateValue: BigInt, allStates: Seq[BigInt]) { 95 | def analyzeNext(e: ir.Expression): Seq[BigInt] = { 96 | val simplified = simplify(followAll = true)(e) 97 | simplified match { 98 | case ir.UIntLiteral(value, _) => Seq(value) // the state will be `value` without any condition 99 | case ir.Mux(condExpr, tvalExpr, fvalExpr, _) => 100 | // try to simplify the predicate (but only if it references the state register!) 101 | val simplePred = simplify(followAll = false)(condExpr) 102 | 103 | // implement branch 104 | simplePred match { 105 | case ir.UIntLiteral(value, _) if value == 1 => analyzeNext(tvalExpr) 106 | case ir.UIntLiteral(value, _) if value == 0 => analyzeNext(fvalExpr) 107 | case _ => // both branches are feasible 108 | analyzeNext(tvalExpr) ++ analyzeNext(fvalExpr) 109 | } 110 | case other => 111 | // over approximate 112 | allStates 113 | } 114 | } 115 | def simplify(followAll: Boolean)(e: ir.Expression): ir.Expression = { 116 | // we simplify bottom up! 117 | e.mapExpr(simplify(followAll)) match { 118 | // replace references to the state register with the state value 119 | case ir.Reference(name, ir.UIntType(width), _, _) if name == stateRegName => 120 | ir.UIntLiteral(stateValue, width) 121 | // follow (i.e., inline) some references 122 | case r@ir.Reference(name, _, _, _) => 123 | con.get(name) match { 124 | case None => r // nothing to do, cannot follow 125 | case Some(info) => 126 | val dependsOnStateValue = info.registerDependencies.contains(stateRegName) 127 | if (dependsOnStateValue || followAll) { 128 | simplify(followAll)(info.expr) 129 | } else { 130 | r 131 | } 132 | } 133 | case other => 134 | // try to propagate any constants 135 | val constPropped = propConst(other) 136 | constPropped 137 | } 138 | } 139 | } 140 | 141 | private object findRegNames { 142 | def apply(s: ir.Statement): Set[String] = s match { 143 | case ir.DefRegister(_, name, _, _, _, _) => Set(name) 144 | case ir.Block(stmts) => stmts.map(apply).reduce(_ | _) 145 | case ir.Conditionally(_, _, conseq, alt) => Seq(conseq, alt).map(apply).reduce(_ | _) 146 | case _ => Set() 147 | } 148 | } 149 | 150 | private object propConst { 151 | private def toUInt(cond: Boolean): ir.Expression = if(cond) { Utils.True() } else { Utils.False() } 152 | // performs a single level of constant propagation (does not recurse into expression!) 153 | def apply(e: ir.Expression): ir.Expression = e match { 154 | case ir.Mux(ir.UIntLiteral(value, _), tval, fval, _) => if(value == 1) { tval } else { fval } 155 | case ir.DoPrim(PrimOps.Eq, Seq(ir.UIntLiteral(a, _), ir.UIntLiteral(b, _)), _, _) => toUInt(a == b) 156 | case ir.DoPrim(PrimOps.AsUInt, Seq(lit : ir.UIntLiteral), _, _) => lit 157 | case other => other 158 | } 159 | } 160 | 161 | /** Contains the right-hand-side expression and transitive register dependency for a single node or register next expression. */ 162 | private case class ConnectionInfo(expr: ir.Expression, registerDependencies: Set[String]) 163 | /** Contains information about all nodes and register next connections in the circuit */ 164 | private case class ModuleNetData(con: Map[String, ConnectionInfo], next: Map[String, ir.Expression]) 165 | private class ModuleNetAnalyzer(registers: Set[String]) { 166 | private val con = mutable.HashMap[String, ConnectionInfo]() 167 | private val next = mutable.HashMap[String, ir.Expression]() 168 | def run(mod: ir.Module): ModuleNetData = { 169 | mod.foreachStmt(onStmt) 170 | ModuleNetData(con.toMap, next.toMap) 171 | } 172 | def onStmt(s: ir.Statement): Unit = s match { 173 | case ir.Connect(_, ir.Reference(name, _, kind, _), expr) if kind == RegKind => 174 | next(name) = expr 175 | case ir.Connect(_, loc, expr) => 176 | con(loc.serialize) = ConnectionInfo(expr, findDeps(expr)) 177 | case ir.DefNode(_, name, expr) => 178 | con(name) = ConnectionInfo(expr, findDeps(expr)) 179 | case other => other.foreachStmt(onStmt) 180 | } 181 | def findDeps(e: ir.Expression): Set[String] = e match { 182 | case ir.Reference(name, _, _, _) => 183 | if(registers.contains(name)) { Set(name) } else { 184 | con.get(name).map(_.registerDependencies).getOrElse(Set()) 185 | } 186 | case other => getChildren(other).map(findDeps) match { 187 | case Seq() => Set() 188 | case children => children.reduce(_ | _) 189 | } 190 | } 191 | } 192 | 193 | private object getChildren { 194 | def apply(e: ir.Expression): Seq[ir.Expression] = e match { 195 | case _ : ir.UIntLiteral | _ : ir.SIntLiteral | _: ir.Reference => Seq() 196 | case prim: ir.DoPrim => prim.args 197 | case ir.Mux(cond, tval, fval, _) => Seq(cond, tval, fval) 198 | case ir.ValidIf(cond, value, _) => Seq(cond, value) 199 | case ir.SubField(expr, _, _, _) => Seq(expr) 200 | } 201 | } -------------------------------------------------------------------------------- /instrumentation/src/sic/LineCoverage.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import firrtl._ 8 | import firrtl.options.Dependency 9 | import firrtl.stage.RunFirrtlTransformAnnotation 10 | 11 | object LineCoverage { 12 | def annotations: AnnotationSeq = Seq( 13 | RunFirrtlTransformAnnotation(Dependency(LineCoveragePass)), 14 | RunFirrtlTransformAnnotation(Dependency(ModuleInstancesPass)) 15 | ) 16 | 17 | def processCoverage(annos: AnnotationSeq): LineCoverageData = { 18 | val cov = Coverage.collectTestCoverage(annos).toMap 19 | val moduleToInst = Coverage.moduleToInstances(annos) 20 | val infos = annos.collect { case a: LineCoverageAnnotation => a } 21 | 22 | val counts = infos.flatMap { case LineCoverageAnnotation(target, lines) => 23 | val insts = moduleToInst(target.module) 24 | val counts = insts.map { i => 25 | val path = Coverage.path(i, target.ref) 26 | cov(path) 27 | } 28 | val total = counts.sum 29 | 30 | lines.flatMap { case (filename, ll) => 31 | ll.map { line => 32 | (filename, line) -> total 33 | } 34 | } 35 | } 36 | 37 | val files = counts 38 | .groupBy(_._1._1) 39 | .map { case (filename, entries) => 40 | val lines = entries.map(e => (e._1._2, e._2)).sortBy(_._1).toList 41 | LineCoverageInFile(filename, lines) 42 | } 43 | .toList 44 | .sortBy(_.name) 45 | 46 | LineCoverageData(files) 47 | } 48 | 49 | private val Count = "Cnt" 50 | private val LineNr = "Line" 51 | def textReport(code: CodeBase, file: LineCoverageInFile): Iterable[String] = { 52 | val sourceLines = code.getSource(file.name).getOrElse { 53 | throw new RuntimeException(s"Unable to find file ${file.name} in ${code}") 54 | } 55 | val counts: Map[Int, Long] = file.lines.toMap 56 | 57 | // we output a table with Line, Exec, Source 58 | val lineNrWidth = (file.lines.map(_._1.toString.length) :+ LineNr.length).max 59 | val countWidth = (file.lines.map(_._2.toString.length) :+ Count.length).max 60 | val countBlank = " " * countWidth 61 | val srcWidth = sourceLines.map(_.length).max 62 | 63 | val header = pad(LineNr, lineNrWidth) + " | " + pad(Count, countWidth) + " | " + "Source" 64 | val headerLine = "-" * (lineNrWidth + 3 + countWidth + 3 + srcWidth) 65 | 66 | val body = sourceLines.zipWithIndex.map { case (line, ii) => 67 | val lineNo = ii + 1 // lines are 1-indexed 68 | val lineNoStr = pad(lineNo.toString, lineNrWidth) 69 | val countStr = counts.get(lineNo).map(c => pad(c.toString, countWidth)).getOrElse(countBlank) 70 | lineNoStr + " | " + countStr + " | " + line 71 | } 72 | Seq(header, headerLine) ++ body 73 | } 74 | 75 | private def pad(str: String, to: Int): String = { 76 | assert(str.length <= to) 77 | str.reverse.padTo(to, ' ').reverse 78 | } 79 | } 80 | 81 | case class LineCoverageData(files: List[LineCoverageInFile]) 82 | case class LineCoverageInFile(name: String, lines: List[(Int, Long)]) -------------------------------------------------------------------------------- /instrumentation/src/sic/LineCoveragePass.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import chiseltest.coverage.CoverageInfo 8 | import firrtl._ 9 | import firrtl.annotations._ 10 | import firrtl.ir.{DefNode, MultiInfo} 11 | import firrtl.options.Dependency 12 | import firrtl.passes.{ExpandWhens, ExpandWhensAndCheck} 13 | import firrtl.stage.Forms 14 | import firrtl.stage.TransformManager.TransformDependency 15 | import firrtl.transforms.DedupModules 16 | import xfuzz.{CoverInfo, CoverPointAnnotation} 17 | 18 | import scala.collection.mutable 19 | 20 | case class LineCoverageAnnotation(target: ReferenceTarget, lines: Coverage.Lines) 21 | extends SingleTargetAnnotation[ReferenceTarget] 22 | with CoverageInfo { 23 | override def duplicate(n: ReferenceTarget) = copy(target = n) 24 | } 25 | 26 | case object SkipLineCoverageAnnotation extends NoTargetAnnotation 27 | 28 | object LineCoveragePass extends Transform with DependencyAPIMigration { 29 | val Prefix = "l" 30 | 31 | override def prerequisites: Seq[TransformDependency] = Forms.Checks 32 | // we can run after deduplication which should make things faster 33 | override def optionalPrerequisites: Seq[TransformDependency] = Seq(Dependency[DedupModules]) 34 | // line coverage does not work anymore after whens have been expanded 35 | override def optionalPrerequisiteOf: Seq[TransformDependency] = 36 | Seq(Dependency[ExpandWhensAndCheck], Dependency(ExpandWhens)) 37 | override def invalidates(a: Transform): Boolean = false 38 | 39 | override protected def execute(state: CircuitState): CircuitState = { 40 | if(state.annotations.contains(SkipLineCoverageAnnotation)) { 41 | logger.info("[LineCoverage] skipping due to SkipLineCoverage annotation") 42 | return state 43 | } 44 | val newAnnos = mutable.ListBuffer[Annotation]() 45 | val c = CircuitTarget(state.circuit.main) 46 | val ignoreMods = Coverage.collectModulesToIgnore(state) 47 | val circuit = state.circuit.mapModule(onModule(_, c, newAnnos, ignoreMods)) 48 | val annos = newAnnos.toList ++ state.annotations 49 | CircuitState(circuit, annos) 50 | } 51 | 52 | private case class ModuleCtx( 53 | annos: mutable.ListBuffer[Annotation], 54 | namespace: Namespace, 55 | m: ModuleTarget, 56 | clk: ir.Expression) 57 | 58 | private def onModule(m: ir.DefModule, c: CircuitTarget, annos: mutable.ListBuffer[Annotation], ignore: Set[String]): ir.DefModule = 59 | m match { 60 | case mod: ir.Module if !ignore(mod.name) => 61 | Builder.findClock(mod, logger) match { 62 | case Some(clock) => 63 | val namespace = Namespace(mod) 64 | namespace.newName(Prefix) 65 | val ctx = ModuleCtx(annos, namespace, c.module(mod.name), clock) 66 | // we always cover the body, even if the module only contains nodes and cover statements 67 | val bodyInfo = onStmt(mod.body, ctx).copy(_2 = true) 68 | val body = addCover(bodyInfo, ctx, None) 69 | mod.copy(body = body) 70 | case None => 71 | mod 72 | } 73 | case other => other 74 | } 75 | 76 | private def onStmt(s: ir.Statement, ctx: ModuleCtx): (ir.Statement, Boolean, Seq[ir.Info]) = s match { 77 | case c @ ir.Conditionally(_, cond, conseq, alt) => 78 | val truInfo = onStmt(conseq, ctx) 79 | val falsInfo = onStmt(alt, ctx) 80 | val doCover = truInfo._2 || falsInfo._2 81 | val stmt = c.copy(conseq = addCover(truInfo, ctx, Some((true, cond))), alt = addCover(falsInfo, ctx, Some((false, cond)))) 82 | (stmt, doCover, List(c.info)) 83 | case ir.Block(stmts) => 84 | val s = stmts.map(onStmt(_, ctx)) 85 | val block = ir.Block(s.map(_._1)) 86 | val doCover = s.map(_._2).foldLeft(false)(_ || _) 87 | val infos = s.flatMap(_._3) 88 | (block, doCover, infos) 89 | case ir.EmptyStmt => (ir.EmptyStmt, false, List()) 90 | // if there is only a cover statement, we do not explicitly try to cover that line 91 | case v @ ir.Verification(ir.Formal.Cover, _, _, _, _, _) => (v, false, List(v.info)) 92 | // nodes are always side-effect free, so they should not be covered unless there is another operation in the block 93 | case n : ir.DefNode => (n, false, List(n.info)) 94 | case other: ir.HasInfo => (other, true, List(other.info)) 95 | case other => (other, false, List()) 96 | } 97 | 98 | private def addCover(info: (ir.Statement, Boolean, Seq[ir.Info]), ctx: ModuleCtx, cond: Option[(Boolean, ir.Expression)]): ir.Statement = { 99 | val (stmt, doCover, infos) = info 100 | if (!doCover) { stmt } 101 | else { 102 | val name = ctx.namespace.newName(Prefix) 103 | val lines = Coverage.infosToLines(infos) 104 | ctx.annos.prepend(LineCoverageAnnotation(ctx.m.ref(name), lines)) 105 | val cover = if (Builder.enableVerificationIR) { 106 | Some(ir.Verification(ir.Formal.Cover, ir.NoInfo, ctx.clk, Utils.True(), Utils.True(), ir.StringLit(""), name)) 107 | } else None 108 | if (cond.isDefined) { 109 | val node = DefNode(MultiInfo(infos), ctx.namespace.newTemp, cond.get._2) 110 | val named = ComponentName(node.name, ctx.m.toNamed) 111 | ctx.annos.prepend(CoverPointAnnotation(named, CoverInfo("line"))) 112 | if (cover.isDefined) ir.Block(node, cover.get, stmt) else ir.Block(node, stmt) 113 | } else { 114 | if (cover.isDefined) ir.Block(cover.get, stmt) else stmt 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /instrumentation/src/sic/ReadyValidCoverage.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import firrtl._ 8 | import firrtl.options.Dependency 9 | import firrtl.stage.RunFirrtlTransformAnnotation 10 | 11 | object ReadyValidCoverage { 12 | def annotations: AnnotationSeq = Seq( 13 | RunFirrtlTransformAnnotation(Dependency(ReadyValidCoveragePass)), 14 | RunFirrtlTransformAnnotation(Dependency(ModuleInstancesPass)) 15 | ) 16 | 17 | def processCoverage(annos: AnnotationSeq): Seq[ReadyValidCoverageData] = { 18 | val fires = annos.collect{ case a : ReadyValidCoverageAnnotation => a } 19 | val cov = Coverage.collectTestCoverage(annos).toMap 20 | val moduleToInst = Coverage.moduleToInstances(annos) 21 | 22 | fires.flatMap { f => 23 | val top = f.target.circuit + "." 24 | moduleToInst(f.target.module).map { inst => 25 | val count = cov(Coverage.path(inst, f.target.ref)) 26 | ReadyValidCoverageData(top + Coverage.path(inst, f.bundle), count) 27 | } 28 | } 29 | } 30 | 31 | def textReport(data: Seq[ReadyValidCoverageData]): Iterable[String] = { 32 | data.map(d => s"${d.name}: ${d.count}") 33 | } 34 | } 35 | 36 | case class ReadyValidCoverageData(name: String, count: Long) -------------------------------------------------------------------------------- /instrumentation/src/sic/ReadyValidCoveragePass.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import chiseltest.coverage.CoverageInfo 8 | import firrtl._ 9 | import firrtl.annotations._ 10 | import firrtl.ir.{Block, DefNode, NoInfo} 11 | import firrtl.options.Dependency 12 | import firrtl.stage.TransformManager.TransformDependency 13 | import firrtl.transforms.DedupModules 14 | import xfuzz.{CoverInfo, CoverPointAnnotation} 15 | 16 | import scala.collection.mutable 17 | 18 | 19 | case class ReadyValidCoverageAnnotation(target: ReferenceTarget, bundle: String) 20 | extends SingleTargetAnnotation[ReferenceTarget] 21 | with CoverageInfo { 22 | override def duplicate(n: ReferenceTarget) = copy(target = n) 23 | } 24 | 25 | case object SkipReadyValidCoverageAnnotation extends NoTargetAnnotation 26 | 27 | object ReadyValidCoveragePass extends Transform with DependencyAPIMigration { 28 | val Prefix = "r" 29 | 30 | override def prerequisites: Seq[TransformDependency] = Seq(Dependency[DedupModules]) 31 | 32 | // we need to run before types are lowered in order to detect the read/valid bundles 33 | override def optionalPrerequisiteOf: Seq[TransformDependency] = Seq(Dependency(firrtl.passes.LowerTypes)) 34 | 35 | override def invalidates(a: Transform): Boolean = false 36 | 37 | override protected def execute(state: CircuitState): CircuitState = { 38 | if(state.annotations.contains(SkipReadyValidCoverageAnnotation)) { 39 | logger.info("[ReadyValidCoverage] skipping due to SkipReadyValidCoverageAnnotation annotation") 40 | return state 41 | } 42 | val newAnnos = mutable.ListBuffer[Annotation]() 43 | val c = CircuitTarget(state.circuit.main) 44 | val ignoreMods = Coverage.collectModulesToIgnore(state) 45 | val circuit = state.circuit.mapModule(onModule(_, c, newAnnos, ignoreMods)) 46 | val annos = newAnnos.toList ++ state.annotations 47 | CircuitState(circuit, annos) 48 | } 49 | 50 | private case class ModuleCtx( 51 | annos: mutable.ListBuffer[Annotation], 52 | namespace: Namespace, 53 | m: ModuleTarget, 54 | clk: ir.Expression) 55 | 56 | private def onModule(m: ir.DefModule, c: CircuitTarget, annos: mutable.ListBuffer[Annotation], ignore: Set[String]): ir.DefModule = m match { 57 | case mod: ir.Module if !ignore(mod.name) => 58 | Builder.findClock(mod, logger) match { 59 | case Some(clock) => 60 | val fires = analyzePorts(mod.ports) 61 | if(fires.isEmpty) { mod } else { 62 | val namespace = Namespace(mod) 63 | namespace.newName(Prefix) 64 | val ctx = ModuleCtx(annos, namespace, c.module(mod.name), clock) 65 | val covers = fires.map{ case (parent, fire) => addCover(parent.serialize, fire, ctx) } 66 | annos ++= covers.map(_._2) 67 | val newBody = ir.Block(mod.body +: covers.map(_._1)) 68 | mod.copy(body=newBody) 69 | } 70 | case None => 71 | mod 72 | } 73 | 74 | case other => other 75 | } 76 | 77 | private def addCover(bundle: String, fire: ir.Expression, ctx: ModuleCtx): (ir.Statement, ReadyValidCoverageAnnotation) = { 78 | val node = DefNode(NoInfo, ctx.namespace.newTemp, fire) 79 | ctx.annos.prepend(CoverPointAnnotation(ComponentName(node.name, ctx.m.toNamed), CoverInfo("ready_valid"))) 80 | val name = ctx.namespace.newName(Prefix) 81 | val anno = ReadyValidCoverageAnnotation(ctx.m.ref(name), bundle) 82 | if (Builder.enableVerificationIR) { 83 | val cover = ir.Verification(ir.Formal.Cover, ir.NoInfo, ctx.clk, fire, Utils.True(), ir.StringLit(""), name) 84 | (Block(cover, node), anno) 85 | } 86 | else { 87 | (node, anno) 88 | } 89 | } 90 | 91 | /** finds any ready/valid bundles that are part of the IO and returns their reference + the fire expression */ 92 | private def analyzePorts(ports: Seq[ir.Port]): Seq[(ir.RefLikeExpression, ir.Expression)] = ports.flatMap { port => 93 | analyzePorts(ir.Reference(port)) 94 | } 95 | 96 | private def analyzePorts(parent: ir.RefLikeExpression): Seq[(ir.RefLikeExpression, ir.Expression)] = parent.tpe match { 97 | case ir.BundleType(fields) => 98 | fields.map(_.name).sorted match { 99 | case Seq("bits", "ready", "valid") => 100 | val fire = Utils.and(ir.SubField(parent, "ready", Utils.BoolType), ir.SubField(parent, "valid", Utils.BoolType)) 101 | List((parent, fire)) 102 | case _ => 103 | fields.flatMap(f => analyzePorts(ir.SubField(parent, f.name, f.tpe))) 104 | } 105 | case ir.VectorType(tpe, _) => List() // ignoring vector types for now 106 | case _ => List() 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /instrumentation/src/sic/RemoveCoverPointsPass.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | package sic 5 | 6 | import chiseltest.coverage.{ModuleInstancesAnnotation, TestCoverage} 7 | import firrtl._ 8 | import firrtl.annotations._ 9 | import firrtl.options.Dependency 10 | 11 | import scala.collection.mutable 12 | 13 | 14 | /** specifies a list of cover points (refered to by their hierarchical path name) that can be removed */ 15 | case class RemoveCoverAnnotation(remove: List[String]) extends NoTargetAnnotation 16 | 17 | /** Removes cover statements that have been covered sufficiently often */ 18 | object RemoveCoverPointsPass extends Transform with DependencyAPIMigration { 19 | // run on lowered firrtl 20 | override def prerequisites = Seq( 21 | Dependency(firrtl.passes.ExpandWhens), Dependency(firrtl.passes.LowerTypes), 22 | Dependency(firrtl.transforms.RemoveReset), Dependency(ModuleInstancesPass)) 23 | 24 | override def invalidates(a: Transform) = false 25 | 26 | // we need to run after the cover points are added 27 | override def optionalPrerequisites = Coverage.AllPasses 28 | 29 | // we want to run before the actual Verilog is emitted 30 | override def optionalPrerequisiteOf = AllEmitters() ++ 31 | Seq(Dependency(CoverageStatisticsPass)) 32 | 33 | override def execute(state: CircuitState): CircuitState = { 34 | val removePaths = state.annotations.collect { case RemoveCoverAnnotation(remove) => remove }.flatten 35 | if (removePaths.isEmpty) return state 36 | 37 | val instToMod = state.annotations.collectFirst { case ModuleInstancesAnnotation(instanceToModule) => instanceToModule }.get 38 | val modNames = state.circuit.modules.map(_.name) 39 | val remove = findStatementsToRemove(modNames, removePaths, instToMod) 40 | 41 | val modules = state.circuit.modules.map { 42 | case mod: ir.Module if remove(mod.name).nonEmpty => onModule(mod, remove(mod.name)) 43 | case other => other 44 | } 45 | 46 | // TODO: maybe remove annotations? 47 | val circuit = state.circuit.copy(modules=modules) 48 | state.copy(circuit = circuit) 49 | } 50 | 51 | private def onModule(m: ir.Module, remove: List[String]): ir.DefModule = { 52 | m.mapStmt(onStmt(_, remove.toSet)) 53 | } 54 | 55 | private def onStmt(s: ir.Statement, remove: String => Boolean): ir.Statement = s match { 56 | case v @ ir.Verification(ir.Formal.Cover, _, _, _, _, _) if remove(v.name) => ir.EmptyStmt 57 | case other => other.mapStmt(onStmt(_, remove)) 58 | } 59 | 60 | private def findStatementsToRemove(modules: Seq[String], removePaths: Seq[String], instanceToModule: List[(String, String)]): Map[String, List[String]] = { 61 | // count in how many instances the cover point is requested to be removed 62 | val instToMod = instanceToModule.toMap 63 | val moduleCoverCounts = modules.map(m => m -> mutable.HashMap[String, Int]()).toMap 64 | removePaths.foreach { path => 65 | val parts = path.split('.') 66 | val name = parts.last 67 | val instance = parts.dropRight(1).mkString(".") 68 | instToMod.get(instance) match { 69 | case Some(mod) => 70 | moduleCoverCounts(mod)(name) = moduleCoverCounts(mod).getOrElse(name, 0) + 1 71 | case None => 72 | logger.warn(s"[WARN] unknown instance: $instance") 73 | } 74 | } 75 | 76 | // for every module, remove the cover points that are requested to be removed in all instances 77 | modules.map { m => 78 | val instanceCount = instanceToModule.count(_._2 == m) 79 | val covers = moduleCoverCounts(m).toList 80 | val remove = covers.filter(_._2 == instanceCount).map(_._1) 81 | m -> remove 82 | }.toMap 83 | } 84 | } 85 | 86 | 87 | case class LoadCoverageAnnotation(filename: String) extends NoTargetAnnotation 88 | 89 | object LoadTestCoveragePass extends Transform with DependencyAPIMigration { 90 | override def prerequisites = Seq() 91 | override def invalidates(a: Transform) = false 92 | 93 | override def execute(state: CircuitState): CircuitState = { 94 | val files = state.annotations.collect { case LoadCoverageAnnotation(filename) => filename } 95 | if(files.isEmpty) { state } else { 96 | val annos = loadFiles(files) 97 | state.copy(annotations = annos ++: state.annotations) 98 | } 99 | } 100 | 101 | private def loadFiles(files: Seq[String]): Seq[TestCoverage] = files.flatMap { file => 102 | JsonProtocol.deserialize(FileUtils.getText(file)).collect { case t: TestCoverage => t } 103 | } 104 | } 105 | 106 | /** reads in one or several JSON files containing one or several [[TestCoverage]] annotations 107 | * and generates a [[RemoveCoverAnnotation]] for all cover points that were covered at least once. 108 | * */ 109 | object FindCoversToRemovePass extends Transform with DependencyAPIMigration { 110 | override def prerequisites = Seq(Dependency(LoadTestCoveragePass)) 111 | override def invalidates(a: Transform) = false 112 | override def optionalPrerequisiteOf = Seq(Dependency(RemoveCoverPointsPass)) 113 | 114 | val Threshold: Long = 10 // at 10 covered once 115 | 116 | override def execute(state: CircuitState): CircuitState = { 117 | val annos = state.annotations.collect { case a: TestCoverage => a } 118 | if(annos.isEmpty) return state 119 | 120 | val covers = merge(annos) 121 | val coveredEnough = covers.filter(_._2 >= Threshold) 122 | if(coveredEnough.isEmpty) return state 123 | 124 | val remove = RemoveCoverAnnotation(coveredEnough.map(_._1)) 125 | logger.info(s"[FindCoversToRemovePass] found ${coveredEnough.length} cover points that were already covered $Threshold+ times.") 126 | 127 | state.copy(annotations = remove +: state.annotations) 128 | } 129 | 130 | 131 | private def merge(annos: Seq[TestCoverage]): List[(String, Long)] = { 132 | if(annos.isEmpty) return List() 133 | if(annos.length == 1) return annos.head.counts 134 | throw new NotImplementedError("TODO: implement coverage merging") 135 | } 136 | } -------------------------------------------------------------------------------- /instrumentation/src/sic/ToggleCoverage.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic 6 | 7 | import sic.passes.RemoveKeepClockAndResetAnnotations 8 | import firrtl.annotations.NoTargetAnnotation 9 | import firrtl._ 10 | import firrtl.options.Dependency 11 | import firrtl.stage.RunFirrtlTransformAnnotation 12 | import firrtl.stage.TransformManager.TransformDependency 13 | 14 | import scala.collection.mutable 15 | 16 | object ToggleCoverage { 17 | def passes: Seq[TransformDependency] = Seq(Dependency(ToggleCoveragePass), Dependency(ModuleInstancesPass), 18 | Dependency(RemoveKeepClockAndResetAnnotations)) 19 | // TODO: re-enable MemoryToggleCoverage (currently broken b/c we are not allowed to read input ports!) 20 | def all: AnnotationSeq = Seq(PortToggleCoverage, RegisterToggleCoverage, WireToggleCoverage) ++ passAnnos 21 | def ports: AnnotationSeq = Seq(PortToggleCoverage) ++ passAnnos 22 | def registers: AnnotationSeq = Seq(RegisterToggleCoverage) ++ passAnnos 23 | def memories: AnnotationSeq = Seq(MemoryToggleCoverage) ++ passAnnos 24 | def wires: AnnotationSeq = Seq(WireToggleCoverage) ++ passAnnos 25 | private def passAnnos = passes.map(p => RunFirrtlTransformAnnotation(p)) 26 | 27 | def processCoverage(annos: AnnotationSeq): ToggleCoverageData = { 28 | val infos = annos.collect { case a: ToggleCoverageAnnotation => a } 29 | if(infos.isEmpty) return ToggleCoverageData(List()) 30 | val cov = Coverage.collectTestCoverage(annos).toMap 31 | val moduleToInst = Coverage.moduleToInstances(annos) 32 | 33 | val counts = infos.flatMap { info => 34 | moduleToInst(info.target.module).flatMap { inst => 35 | val count = cov(Coverage.path(inst, info.target.ref)) 36 | info.signals.map { signal => 37 | val instance = signal.path.map(_._1.value).mkString(".") 38 | val module = signal.leafModule 39 | val ref = signal.ref 40 | val r = ((instance, module), ref, (info.bit, count)) 41 | println(signal) 42 | println(r) 43 | r 44 | } 45 | } 46 | } 47 | 48 | val byModule = counts.groupBy(_._1).toSeq.sortBy(_._1) 49 | val bySignal = byModule.map { case (k, vs) => 50 | k -> vs 51 | .map{ case (_, signal, count) => signal -> count } 52 | .groupBy(_._1).toSeq.sortBy(_._1) 53 | .map{ case (signal, counts) => signal -> counts.map(_._2) } 54 | } 55 | ToggleCoverageData(bySignal) 56 | } 57 | } 58 | 59 | // instance, module, signal, bit, count 60 | case class ToggleCoverageData(inst: Seq[((String, String), Seq[(String, Seq[(Int, Long)])])]) 61 | 62 | /** enables coverage of all I/O ports in the design */ 63 | case object PortToggleCoverage extends NoTargetAnnotation 64 | /** enables coverage of all register signals in the design */ 65 | case object RegisterToggleCoverage extends NoTargetAnnotation 66 | /** enables coverage of all memory port signals in the design */ 67 | case object MemoryToggleCoverage extends NoTargetAnnotation 68 | /** enables coverage of all wires in the design */ 69 | case object WireToggleCoverage extends NoTargetAnnotation -------------------------------------------------------------------------------- /instrumentation/src/sic/passes/AddResetAssumptionPass.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic.passes 6 | 7 | 8 | import sic.{AllEmitters, Coverage, Builder} 9 | import firrtl._ 10 | import firrtl.annotations._ 11 | import firrtl.options.Dependency 12 | import firrtl.transforms._ 13 | 14 | import scala.collection.mutable 15 | 16 | /** adds an assumption to the toplevel module that all resets are active in the first cycle */ 17 | object AddResetAssumptionPass extends Transform with DependencyAPIMigration { 18 | // run on lowered firrtl 19 | override def prerequisites = Seq( 20 | Dependency(firrtl.passes.ExpandWhens), Dependency(firrtl.passes.LowerTypes), 21 | Dependency(firrtl.transforms.RemoveReset), 22 | // try to work around dead code elimination removing our registers 23 | Dependency[firrtl.transforms.DeadCodeElimination] 24 | ) 25 | override def invalidates(a: Transform) = false 26 | // since we generate PresetRegAnnotations, we need to run after preset propagation 27 | override def optionalPrerequisites = Seq(Dependency[PropagatePresetAnnotations]) ++ Coverage.AllPasses 28 | // we want to run before the actual Verilog is emitted 29 | override def optionalPrerequisiteOf = AllEmitters() 30 | 31 | override def execute(state: CircuitState): CircuitState = { 32 | val annos = mutable.ListBuffer[Annotation]() 33 | val c = CircuitTarget(state.circuit.main) 34 | val modules = state.circuit.modules.map { 35 | case mod: ir.Module => onModule(c, mod, annos) 36 | case other => other 37 | } 38 | val circuit = state.circuit.copy(modules = modules) 39 | state.copy(circuit = circuit, annotations = annos.toList ++: state.annotations) 40 | } 41 | 42 | private def onModule(c: CircuitTarget, m: ir.Module, annos: mutable.ListBuffer[Annotation]): ir.Module = { 43 | val clock = Builder.findClock(m ,logger) 44 | if(clock.isEmpty) return m 45 | 46 | // create a register to know when we are in the "init" cycle 47 | val namespace = Namespace(m) 48 | val reg = ir.DefRegister(ir.NoInfo, namespace.newName("isInitCycle"), 49 | Utils.BoolType, clock.get, reset = Utils.False(), init = Utils.True()) 50 | val regRef = ir.Reference(reg) 51 | val next = ir.Connect(ir.NoInfo, regRef, Utils.False()) 52 | val notInit = ir.DefNode(ir.NoInfo, namespace.newName("enCover"), Utils.not(regRef)) 53 | annos.append(PresetRegAnnotation(c.module(m.name).ref(reg.name))) 54 | 55 | val isMain = c.circuit == m.name 56 | val resetAssumptions = if(isMain) { 57 | val resets = Builder.findResets(m) 58 | // assume that isInitCycle => reset 59 | resets.map { r => 60 | ir.Verification(ir.Formal.Assume, ir.NoInfo, clock.get, r, regRef, ir.StringLit(""), namespace.newName(r.serialize + "_active")) 61 | } 62 | } else { List() } 63 | 64 | // make sure the we do not cover anything in the first cycle 65 | val guardedCovers = onStmt(m.body, ir.Reference(notInit)) 66 | val body = ir.Block(Seq(reg, next, notInit) ++ resetAssumptions ++ Seq(guardedCovers)) 67 | 68 | m.copy(body = body) 69 | } 70 | 71 | private def onStmt(s: ir.Statement, en: ir.Expression): ir.Statement = s match { 72 | case v: ir.Verification if v.op == ir.Formal.Cover => v.withEn(Utils.and(en, v.en)) 73 | case other => other.mapStmt(onStmt(_, en)) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /instrumentation/src/sic/passes/AliasAnalysis.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic.passes 6 | 7 | import firrtl._ 8 | import firrtl.analyses.InstanceKeyGraph 9 | import firrtl.analyses.InstanceKeyGraph.InstanceKey 10 | 11 | import scala.collection.mutable 12 | 13 | /** Analyses which signals in a module always have the same value (are aliases of each other). 14 | * @note will only work on low firrtl! 15 | * @note right now this isn't an actual firrtl pass, but an analysis called into from a firrtl pass. 16 | */ 17 | object AliasAnalysis { 18 | type Aliases = Seq[List[String]] 19 | type Result = Map[String, Aliases] 20 | 21 | /** @return map from module name to signals in the module that alias */ 22 | def findAliases(c: ir.Circuit, iGraph: InstanceKeyGraph): Result = { 23 | // analyze each module in isolation 24 | val local = c.modules.map(m => m.name -> findAliases(m)).toMap 25 | 26 | // compute global results 27 | val moduleOrderBottomUp = iGraph.moduleOrder.reverseIterator 28 | val childInstances = iGraph.getChildInstances.toMap 29 | val portAliases = mutable.HashMap[String, PortAliases]() 30 | 31 | val aliases = moduleOrderBottomUp.map { 32 | case m: ir.Module => 33 | val groups = resolveAliases(m, local(m.name), portAliases, childInstances(m.name)) 34 | val isPort = m.ports.map(_.name).toSet 35 | portAliases(m.name) = computePortAliases(groups, isPort) 36 | m.name -> groups 37 | case other => 38 | portAliases(other.name) = List() 39 | other.name -> List() 40 | } 41 | 42 | aliases.toMap 43 | } 44 | 45 | private type PortAliases = List[(String, String)] 46 | 47 | // Incorporate the alias information from all sub modules. 48 | // This matters if the submodule has an input and an output that aliases. 49 | private def resolveAliases(m: ir.Module, local: LocalInfo, portAliases: String => PortAliases, 50 | instances: Seq[InstanceKey]): Seq[List[String]] = { 51 | // compute any port aliases for all child modules 52 | val instancePortAliases = instances.flatMap { case InstanceKey(name, module) => 53 | portAliases(module).map { case (a,b) => 54 | (name + "." + a) -> (name + "." + b) 55 | } 56 | }.toMap 57 | 58 | // if there are no port aliases in the children, nothing is going to change 59 | if(instancePortAliases.isEmpty) return local.groups 60 | 61 | // we need to create a new group for signals that are not aliased when just looking at the local module, 62 | // but are aliased through a connection in a submodule 63 | val isAliasedPort = instancePortAliases.flatMap{ case (a,b) => List(a,b) }.toSet 64 | val isGroupedSignal = local.groups.flatten.toSet 65 | val singleSignalGroups = (isAliasedPort -- isGroupedSignal).toList.sorted.map(List(_)) 66 | val localGroups = local.groups ++ singleSignalGroups 67 | 68 | // build a map from (aliasing) instance port to group id 69 | val localGroupsWithIds = localGroups.zipWithIndex 70 | val instPortToGroupId = localGroupsWithIds.flatMap { case (g, ii) => 71 | val ips = g.filter(isAliasedPort(_)) 72 | ips.map(i => i -> ii) 73 | }.toMap 74 | 75 | // check to see if there are any groups that need to be merged 76 | val merges = findMerges(instancePortAliases, instPortToGroupId) 77 | val updatedGroups = mergeGroups(localGroups, merges) 78 | 79 | updatedGroups 80 | } 81 | 82 | private def computePortAliases(groups: Seq[List[String]], isPort: String => Boolean): PortAliases = { 83 | groups.flatMap { g => 84 | val ports = g.filter(isPort) 85 | assert(ports.length < 32, s"Unexpected exponential blowup! Redesign the data-structure! $ports") 86 | ports.flatMap { a => 87 | ports.flatMap { b => 88 | if(a == b) None else Some(a -> b) 89 | } 90 | } 91 | }.toList 92 | } 93 | 94 | private def findMerges(aliases: Iterable[(String, String)], signalToGroupId: Map[String, Int]): List[Set[Int]] = { 95 | // check to see if there are any groups that need to be merged 96 | var merges = List[Set[Int]]() 97 | aliases.foreach { case (a,b) => 98 | val (aId, bId) = (signalToGroupId(a), signalToGroupId(b)) 99 | if(aId != bId) { 100 | val merge = Set(aId, bId) 101 | // update merges 102 | val bothNew = !merges.exists(s => (s & merge).nonEmpty) 103 | if(bothNew) { 104 | merges = merge +: merges 105 | } else { 106 | merges = merges.map { old => 107 | if((old & merge).nonEmpty) { old | merge } else { old } 108 | }.distinct 109 | } 110 | } 111 | } 112 | merges 113 | } 114 | 115 | private def mergeGroups(groups: Seq[List[String]], merges: List[Set[Int]]): Seq[List[String]] = { 116 | if(merges.isEmpty) { groups } else { 117 | val merged = merges.map { m => 118 | m.toList.sorted.flatMap(i => groups(i)) 119 | } 120 | val wasMerged = merges.flatten.toSet 121 | val unmerged = groups.indices.filterNot(wasMerged).map(i => groups(i)) 122 | merged ++ unmerged 123 | } 124 | } 125 | 126 | 127 | private def findAliases(m: ir.DefModule): LocalInfo = m match { 128 | case mod: ir.Module => findAliasesM(mod) 129 | case _ => LocalInfo(List()) 130 | } 131 | 132 | private type Connects = mutable.HashMap[String, String] 133 | private def findAliasesM(m: ir.Module): LocalInfo = { 134 | // find all signals inside the module that alias 135 | val cons = new Connects() 136 | m.foreachStmt(onStmt(_, cons)) 137 | val groups = groupSignals(cons) 138 | //groups.foreach(g => println(g.mkString(" <-> "))) 139 | LocalInfo(groups) 140 | } 141 | private def groupSignals(cons: Connects): Seq[List[String]] = { 142 | val signalToGroup = mutable.HashMap[String, Int]() 143 | val groups = mutable.ArrayBuffer[List[String]]() 144 | val signals = (cons.keys.toSet | cons.values.toSet).toSeq 145 | signals.foreach { sig => 146 | signalToGroup.get(sig) match { 147 | case Some(groupId) => 148 | // we have seen this signal before, so all alias info is up to date and we just need to add it to the group! 149 | groups(groupId) = sig +: groups(groupId) 150 | case None => 151 | // check to see if any group exists under any alias name 152 | val aliases = getAliases(sig, cons) 153 | val groupId = aliases.find(a => signalToGroup.contains(a)) match { 154 | case Some(key) => signalToGroup(key) 155 | case None => groups.append(List()) ; groups.length - 1 156 | } 157 | groups(groupId) = sig +: groups(groupId) 158 | aliases.foreach(a => signalToGroup(a) = groupId) 159 | } 160 | } 161 | groups.toSeq 162 | } 163 | private def getAliases(name: String, cons: Connects): List[String] = cons.get(name) match { 164 | case None => List(name) 165 | case Some(a) => name +: getAliases(a, cons) 166 | } 167 | 168 | import sic.Builder.getKind 169 | private def onStmt(s: ir.Statement, cons: Connects): Unit = s match { 170 | case ir.DefNode(_, lhs, rhs: ir.RefLikeExpression) => 171 | cons(lhs) = rhs.serialize 172 | case ir.Connect(_, lhs: ir.RefLikeExpression, rhs: ir.RefLikeExpression) if getKind(lhs) != RegKind => 173 | cons(lhs.serialize) = rhs.serialize 174 | case other => other.foreachStmt(onStmt(_, cons)) 175 | } 176 | private case class LocalInfo(groups: Seq[List[String]]) 177 | } 178 | -------------------------------------------------------------------------------- /instrumentation/src/sic/passes/ChangeMainPass.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic.passes 6 | 7 | 8 | import firrtl._ 9 | import firrtl.analyses.InstanceKeyGraph 10 | import firrtl.analyses.InstanceKeyGraph.InstanceKey 11 | import firrtl.annotations._ 12 | import firrtl.options.Dependency 13 | import firrtl.renamemap.MutableRenameMap 14 | import firrtl.stage.Forms 15 | 16 | 17 | case class MakeMainAnnotation(target: ModuleTarget) extends SingleTargetAnnotation[ModuleTarget] { 18 | override def duplicate(n: ModuleTarget) = copy(target=n) 19 | } 20 | 21 | object ChangeMainPass extends Transform with DependencyAPIMigration { 22 | // try to run early 23 | override def prerequisites = Forms.Checks 24 | override def invalidates(a: Transform) = false 25 | // run early 26 | override def optionalPrerequisiteOf = Seq( 27 | Dependency[firrtl.transforms.DedupModules], 28 | Dependency(passes.PullMuxes) 29 | ) 30 | 31 | override def execute(state: CircuitState): CircuitState = { 32 | state.annotations.collect{ case MakeMainAnnotation(m) => m } match { 33 | case Seq() => 34 | logger.info(s"[ChangeMainPass] no MakeMainAnnotation found, ${state.circuit.main} remains main.") 35 | state 36 | case Seq(main) => 37 | assert(main.circuit == state.circuit.main) 38 | if(main.module == state.circuit.main) { 39 | logger.info(s"[ChangeMainPass] nothing to do, ${state.circuit.main} remains main.") 40 | state 41 | } else { 42 | // change main of circuit 43 | val circuit = state.circuit.copy(main = main.module) 44 | 45 | // remove all modules that are now unreachable 46 | val iGraph = InstanceKeyGraph(circuit) 47 | val reachable = findReachable(circuit.main, iGraph.getChildInstances.toMap) 48 | val isUnreachable = iGraph.moduleMap.keySet -- reachable 49 | if(isUnreachable.nonEmpty) { 50 | logger.info(s"[ChangeMainPass] removing unreachable modules:\n${isUnreachable}") 51 | } 52 | val reducedCircuit = circuit.copy(modules = circuit.modules.filterNot(m => isUnreachable(m.name))) 53 | 54 | // rename all annotations 55 | val rename = MutableRenameMap() 56 | rename.rename(CircuitTarget(state.circuit.main), CircuitTarget(main.module)) 57 | state.copy(circuit = reducedCircuit, renames = Some(rename)) 58 | } 59 | case other => 60 | throw new RuntimeException(s"[ChangeMainPass] only a single MakeMainAnnotation may be supplied: $other") 61 | } 62 | } 63 | 64 | private def findReachable(module: String, childInstances: Map[String, Seq[InstanceKey]]): Set[String] = { 65 | childInstances(module).flatMap(k => findReachable(k.module, childInstances)).toSet | Set(module) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /instrumentation/src/sic/passes/KeepClockAndResetPass.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic.passes 6 | 7 | import firrtl._ 8 | import firrtl.annotations._ 9 | import firrtl.options.Dependency 10 | import firrtl.stage.Forms 11 | import firrtl.transforms._ 12 | import sic.Builder 13 | 14 | case class KeepClockAndResetAnnotation(target: ReferenceTarget) extends 15 | SingleTargetAnnotation[ReferenceTarget] with HasDontTouches { 16 | override def duplicate(n: ReferenceTarget) = copy(target=n) 17 | override def dontTouches = List(target) 18 | } 19 | 20 | /** Marks all `clock` and `reset` signals as DontTouch so that they are not removed by 21 | * Dead Code Elimination. This makes adding coverage that relies on those pins being 22 | * available easier. 23 | */ 24 | object KeepClockAndResetPass extends Transform with DependencyAPIMigration { 25 | // try to run early 26 | override def prerequisites = Forms.Checks 27 | override def invalidates(a: Transform) = false 28 | // need to run before DCE 29 | override def optionalPrerequisiteOf = Seq(Dependency[DeadCodeElimination]) 30 | 31 | 32 | override def execute(state: CircuitState): CircuitState = { 33 | val c = CircuitTarget(state.circuit.main) 34 | val annos = state.circuit.modules.flatMap(onModule(_, c)) 35 | state.copy(annotations = annos ++ state.annotations) 36 | } 37 | 38 | private def onModule(m: ir.DefModule, c: CircuitTarget): List[KeepClockAndResetAnnotation] = m match { 39 | case mod: ir.Module => 40 | val clock = Builder.findClocks(mod) 41 | val reset = Builder.findResets(mod) 42 | val mRef = c.module(mod.name) 43 | (clock).map(e => KeepClockAndResetAnnotation(Builder.refToTarget(mRef, e))).toList 44 | case _ => List() 45 | } 46 | } 47 | 48 | object RemoveKeepClockAndResetAnnotations extends Transform with DependencyAPIMigration { 49 | override def prerequisites = Seq(Dependency(KeepClockAndResetPass)) 50 | override def invalidates(a: Transform) = a == KeepClockAndResetPass 51 | override def execute(state: CircuitState): CircuitState = { 52 | val filtered = state.annotations.filterNot(_.isInstanceOf[KeepClockAndResetAnnotation]) 53 | state.copy(annotations = filtered) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /instrumentation/src/sic/passes/RandomStateInit.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic.passes 6 | 7 | 8 | import sic.AllEmitters 9 | import firrtl._ 10 | import firrtl.annotations._ 11 | import firrtl.options.Dependency 12 | import firrtl.transforms._ 13 | 14 | import scala.collection.mutable 15 | import scala.util.Random 16 | 17 | /** Initializes every register with a (random at compile time) value. 18 | * This can be useful when doing formal cover trace generation because 19 | * it prevents the solver from choosing specific register initialization 20 | * value to achieve coverage instead of choosing proper inputs. 21 | * 22 | * */ 23 | object RandomStateInit extends Transform with DependencyAPIMigration { 24 | // run on lowered firrtl 25 | override def prerequisites = Seq( 26 | Dependency(firrtl.passes.ExpandWhens), Dependency(firrtl.passes.LowerTypes), 27 | Dependency(firrtl.transforms.RemoveReset)) 28 | override def invalidates(a: Transform) = false 29 | // since we generate PresetRegAnnotations, we need to run after preset propagation 30 | override def optionalPrerequisites = Seq(Dependency[PropagatePresetAnnotations]) 31 | // we want to run before the actual Verilog is emitted 32 | override def optionalPrerequisiteOf = AllEmitters() 33 | 34 | override def execute(state: CircuitState): CircuitState = { 35 | val c = CircuitTarget(state.circuit.main) 36 | val initd = findInitializedMems(state.annotations) 37 | val modsAndAnnos = state.circuit.modules.map(onModule(_, c, initd)) 38 | // TODO MemorySynthInit annotation does not work with MemoryScalarInitAnnotation :( 39 | val annos = modsAndAnnos.flatMap(_._2) ++: state.annotations 40 | val circuit = state.circuit.copy(modules = modsAndAnnos.map(_._1)) 41 | state.copy(circuit = circuit, annotations = annos) 42 | } 43 | 44 | private def findInitializedMems(annos: AnnotationSeq): Seq[ReferenceTarget] = { 45 | annos.collect { 46 | case MemoryScalarInitAnnotation(target, _) => target 47 | case MemoryArrayInitAnnotation(target, _) => target 48 | case MemoryFileInlineAnnotation(target, _, _) => target 49 | case LoadMemoryAnnotation(target, _, _, _) => target.toTarget 50 | } 51 | } 52 | 53 | private def onModule(m: ir.DefModule, c: CircuitTarget, initialized: Seq[ReferenceTarget]): (ir.DefModule, Seq[Annotation]) = m match { 54 | case mod: ir.Module => 55 | val isInitd = initialized.filter(_.module == mod.name).map(_.ref).toSet 56 | val rand = new Random(mod.name.hashCode) 57 | val annos = mutable.ListBuffer[Annotation]() 58 | val m = c.module(mod.name) 59 | val newMod = mod.mapStmt(onStmt(_, annos, m, rand, isInitd)) 60 | (newMod, annos.toList) 61 | case other => (other, List()) 62 | } 63 | 64 | private def bitsToSigned(unsigned: BigInt, width: Int): BigInt = { 65 | val isNeg = ((unsigned >> (width - 1)) & 1) == 1 66 | if(isNeg) { 67 | val mask = (BigInt(1) << width) - 1 68 | -1 * (((~unsigned) & mask) + 1) 69 | } else { unsigned } 70 | } 71 | 72 | private def onStmt(s: ir.Statement, annos: mutable.ListBuffer[Annotation], m: ModuleTarget, rand: Random, isInitd: String => Boolean): ir.Statement = s match { 73 | case r: ir.DefRegister => 74 | if(r.reset == Utils.False()) { 75 | val bits = firrtl.bitWidth(r.tpe).toInt 76 | 77 | val init = r.tpe match { 78 | case _ : ir.SIntType => 79 | val initValue = bitsToSigned(BigInt(bits, rand), bits) 80 | ir.SIntLiteral(initValue, ir.IntWidth(bits)) 81 | case _ => ir.UIntLiteral(BigInt(bits, rand), ir.IntWidth(bits)) 82 | } 83 | annos.append(PresetRegAnnotation(m.ref(r.name))) 84 | r.copy(init = init) 85 | } else { 86 | logger.warn(s"[${m.module}] Cannot initialize register ${r.name} with reset: ${r.reset.serialize}") 87 | r 88 | } 89 | case mem: ir.DefMemory if !isInitd(mem.name) => 90 | val value = BigInt(firrtl.bitWidth(mem.dataType).toInt, rand) 91 | annos.append(MemoryScalarInitAnnotation(m.ref(mem.name), value)) 92 | mem 93 | case other => other.mapStmt(onStmt(_, annos, m, rand, isInitd)) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /instrumentation/src/sic/passes/RegisterResetAnnotationPass.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Regents of the University of California 2 | // released under BSD 3-Clause License 3 | // author: Kevin Laeufer 4 | 5 | package sic.passes 6 | 7 | import firrtl._ 8 | import firrtl.annotations._ 9 | import firrtl.options.Dependency 10 | import firrtl.transforms.{HasDontTouches, RemoveReset} 11 | 12 | import scala.collection.mutable 13 | 14 | 15 | case class RegisterResetAnnotation(registers: Seq[ReferenceTarget], reset: ReferenceTarget) extends MultiTargetAnnotation 16 | with HasDontTouches { 17 | override def targets = Seq(registers, Seq(reset)) 18 | override def dontTouches = List(reset) 19 | 20 | override def duplicate(n: Seq[Seq[Target]]) = n match { 21 | case Seq(t: Seq[_], Seq(r: ReferenceTarget)) => copy(registers=t.map(_.asInstanceOf[ReferenceTarget]), reset=r) 22 | case other => throw new RuntimeException(s"Unexpected argument to duplicate: $other") 23 | } 24 | } 25 | 26 | /** Ensures that all register resets are named signals that will be annotated and not removed. */ 27 | object RegisterResetAnnotationPass extends Transform with DependencyAPIMigration { 28 | // run on lowered firrtl 29 | override def prerequisites = Seq(Dependency(passes.ExpandWhens), Dependency(passes.LowerTypes)) 30 | override def invalidates(a: Transform) = false 31 | // need to run before synchronous resets are removed 32 | override def optionalPrerequisiteOf = Seq(Dependency(RemoveReset)) 33 | 34 | def findResetsInModule(m: ModuleTarget, annos: Seq[RegisterResetAnnotation]): Map[String, String] = { 35 | annos.filter(_.reset.module == m.module).flatMap { a => 36 | a.registers.map(r => r.ref -> a.reset.ref) 37 | }.toMap 38 | } 39 | 40 | override def execute(state: CircuitState): CircuitState = { 41 | val newAnnos = mutable.ListBuffer[RegisterResetAnnotation]() 42 | val c = CircuitTarget(state.circuit.main) 43 | val circuit = state.circuit.mapModule(onModule(_, c, newAnnos)) 44 | state.copy(circuit = circuit, annotations = newAnnos.toList ++ state.annotations) 45 | } 46 | 47 | private def onModule(m: ir.DefModule, c: CircuitTarget, annos: mutable.ListBuffer[RegisterResetAnnotation]): ir.DefModule = m match { 48 | case mod: ir.Module => 49 | val resets = mutable.ListBuffer[(String, String)]() 50 | val signalNames = mutable.HashMap[String, String]() 51 | val namespace = Namespace(mod) 52 | val newMod = mod.mapStmt(onStmt(_, resets, signalNames, namespace)) 53 | 54 | // create reset annotations 55 | val m = c.module(mod.name) 56 | annos ++= resets.groupBy(_._1).map { case (reset, regs) => 57 | RegisterResetAnnotation(regs.toList.map(r => m.ref(r._2)), m.ref(reset)) 58 | } 59 | 60 | newMod 61 | case other => other 62 | } 63 | 64 | private def onStmt(s: ir.Statement, resets: mutable.ListBuffer[(String, String)], 65 | signalNames: mutable.HashMap[String, String], namespace: Namespace): ir.Statement = s match { 66 | case reg : ir.DefRegister => 67 | reg.reset match { 68 | case r: ir.Reference => 69 | resets.append(r.name -> reg.name) 70 | reg 71 | case other => 72 | signalNames.get(other.serialize) match { 73 | case Some(name) => 74 | resets.append(name -> reg.name) 75 | reg.copy(reset = ir.Reference(name, Utils.BoolType, NodeKind, SourceFlow)) 76 | case None => 77 | val node = ir.DefNode(reg.info, namespace.newName("reset"), other) 78 | resets.append(node.name -> reg.name) 79 | ir.Block(node, 80 | reg.copy(reset = ir.Reference(node.name, Utils.BoolType, NodeKind, SourceFlow)) 81 | ) 82 | } 83 | } 84 | case other => other.mapStmt(onStmt(_, resets, signalNames, namespace)) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /instrumentation/src/xfuzz/ControlRegisterCoverTransform.scala: -------------------------------------------------------------------------------- 1 | /*************************************************************************************** 2 | * Copyright (c) 2020-2024 Institute of Computing Technology, Chinese Academy of Sciences 3 | * 4 | * XiangShan is licensed under Mulan PSL v2. 5 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | * You may obtain a copy of Mulan PSL v2 at: 7 | * http://license.coscl.org.cn/MulanPSL2 8 | * 9 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 10 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 11 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 12 | * 13 | * See the Mulan PSL v2 for more details. 14 | ***************************************************************************************/ 15 | 16 | package xfuzz 17 | 18 | import difuzz.{GraphLedger, InstrCov, ModuleInfo} 19 | import firrtl._ 20 | import firrtl.annotations.ModuleName 21 | import firrtl.ir.ExtModule 22 | import firrtl.options.Dependency 23 | import firrtl.stage.TransformManager.TransformDependency 24 | import rfuzz.DoNotProfileModule 25 | 26 | class ControlRegisterCoverTransform extends Transform with DependencyAPIMigration { 27 | override def prerequisites: Seq[TransformDependency] = firrtl.stage.Forms.LowForm 28 | override def optionalPrerequisites: Seq[TransformDependency] = firrtl.stage.Forms.LowFormOptimized 29 | override def optionalPrerequisiteOf: Seq[TransformDependency] = Seq(Dependency[CoverPointTransform]) 30 | override def invalidates(a: Transform): Boolean = false 31 | 32 | override def execute(state: CircuitState): CircuitState = { 33 | val dontProfile = state.annotations 34 | .collect { case DoNotProfileModule(ModuleName(m, _)) => m } 35 | .toSet 36 | val extModules = state.circuit.modules.filter(_.isInstanceOf[ExtModule]).map(_.name) 37 | val (mods, annos) = state.circuit.modules.map { 38 | case m: firrtl.ir.Module if !dontProfile(m.name) => 39 | val ledger = new GraphLedger(m).parseModule 40 | val cov = new InstrCov(m, ModuleInfo(m, ledger), extModules) 41 | val (mod, annos) = cov.annotate(state.circuit.main) 42 | (mod, annos) 43 | case mod => (mod, Seq()) 44 | }.unzip 45 | val circuitx = state.circuit.copy(modules = mods) 46 | val annosx = state.annotations ++ annos.flatten 47 | state.copy(circuit = circuitx, annotations = annosx) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /instrumentation/src/xfuzz/DontTouchClockAndResetTransform.scala: -------------------------------------------------------------------------------- 1 | /*************************************************************************************** 2 | * Copyright (c) 2020-2024 Institute of Computing Technology, Chinese Academy of Sciences 3 | * 4 | * XiangShan is licensed under Mulan PSL v2. 5 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | * You may obtain a copy of Mulan PSL v2 at: 7 | * http://license.coscl.org.cn/MulanPSL2 8 | * 9 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 10 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 11 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 12 | * 13 | * See the Mulan PSL v2 for more details. 14 | ***************************************************************************************/ 15 | 16 | package xfuzz 17 | 18 | import firrtl._ 19 | import firrtl.annotations._ 20 | import firrtl.ir._ 21 | import firrtl.transforms.DontTouchAnnotation 22 | import rfuzz.NoDedupTransform 23 | 24 | import scala.collection.mutable.ListBuffer 25 | 26 | // mark clock and reset as dontTouch 27 | class DontTouchClockAndResetTransform extends Transform with DependencyAPIMigration { 28 | override def invalidates(a: Transform): Boolean = a match { 29 | case _: NoDedupTransform => false 30 | case _ => true 31 | } 32 | 33 | val dontTouchAnnos = ListBuffer.empty[Annotation] 34 | private def dontTouchPorts(cond: Seq[Port => Boolean], circuit: String)(mod: DefModule): Unit = { 35 | for (port <- mod.ports.filter(p => cond.exists(_(p)))) { 36 | val target = ReferenceTarget(circuit, mod.name, Seq(), port.name, Seq()) 37 | dontTouchAnnos += DontTouchAnnotation(target) 38 | } 39 | } 40 | 41 | private val isClock = (port: Port) => port.tpe == ClockType 42 | private val isReset = (port: Port) => port.tpe == ResetType 43 | def execute(state: CircuitState): CircuitState = { 44 | val clockAndReset = Seq(isClock, isReset) 45 | state.circuit.foreachModule(dontTouchPorts(clockAndReset, state.circuit.main)) 46 | state.copy(annotations = state.annotations ++ dontTouchAnnos) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /instrumentation/src/xfuzz/MuxCoverTransform.scala: -------------------------------------------------------------------------------- 1 | // This file is mostly adapted from rfuzz.ProfilingTransform.scala. 2 | 3 | package xfuzz 4 | 5 | import firrtl.Utils.get_info 6 | import firrtl._ 7 | import firrtl.annotations._ 8 | import firrtl.ir._ 9 | import firrtl.options.Dependency 10 | import firrtl.stage.TransformManager.TransformDependency 11 | import rfuzz.DoNotProfileModule 12 | 13 | import scala.collection.mutable.{ArrayBuffer, ListBuffer} 14 | 15 | case class ProfileConfig(topPortName: String)( 16 | // Returns the optionally mutated input statement and any expressions 17 | // that should be extracted to the top for profiling 18 | val processStmt: PartialFunction[(Statement, ListBuffer[String]), (Statement, Seq[Expression])] 19 | ) 20 | 21 | /** 22 | * Insert CoverPointAnnotation into the MUX cover points. 23 | * Let CoverPointTransform to do the wiring or connection to blackboxes. 24 | */ 25 | class MuxCoverTransform extends Transform with DependencyAPIMigration { 26 | override def prerequisites: Seq[TransformDependency] = Seq(Dependency[rfuzz.SplitMuxConditions]) 27 | override def optionalPrerequisiteOf: Seq[TransformDependency] = Seq(Dependency[CoverPointTransform]) 28 | override def invalidates(a: Transform): Boolean = false 29 | 30 | def containsMux(c: ListBuffer[String])(stmt: Statement): Boolean = { 31 | var muxFound = false 32 | def onExpr(expr: Expression): Expression = expr.mapExpr(onExpr) match { 33 | case m: Mux => 34 | muxFound = m.cond.serialize != "reset" && !c.contains(m.cond.serialize) 35 | m 36 | case other => other 37 | } 38 | stmt.mapExpr(onExpr) 39 | muxFound 40 | } 41 | def extractMuxConditions(c: ListBuffer[String])(stmt: Statement): Seq[Expression] = { 42 | val conds = ArrayBuffer.empty[Expression] 43 | def onExpr(expr: Expression): Expression = expr.mapExpr(onExpr) match { 44 | case mux @ Mux(cond, _,_,_) => 45 | conds += cond 46 | c += cond.serialize 47 | mux 48 | case other => other 49 | } 50 | stmt.mapExpr(onExpr) 51 | conds.toSeq 52 | } 53 | 54 | val autoCoverageConfig = ProfileConfig("auto_cover_out") { 55 | case (stmt, c) if containsMux(c)(stmt) => (stmt, extractMuxConditions(c)(stmt)) 56 | } 57 | val configs = Seq( 58 | autoCoverageConfig 59 | ) 60 | 61 | // Hardwired constants 62 | val profilePinPrefix = "profilePin" 63 | 64 | private def CatTypes(tpe1: Type, tpe2: Type): Type = (tpe1, tpe2) match { 65 | case (UIntType(w1), UIntType(w2)) => UIntType(w1 + w2) 66 | case (SIntType(w1), SIntType(w2)) => UIntType(w1 + w2) 67 | case other => throw new Exception(s"Unexpected Types $other!") 68 | } 69 | 70 | // This namespace is based on the ports that exist in the top 71 | private def onModule( 72 | mod: Module, 73 | top: String, 74 | namespace: Namespace 75 | ): (Module, Map[ProfileConfig, Seq[CoverPointAnnotation]]) = { 76 | // We record Annotations for each profiled signal for each profiling configuration 77 | val profiledSignals = Map(configs.map(c => c -> ArrayBuffer.empty[CoverPointAnnotation]): _*) 78 | val localNS = Namespace(mod) 79 | 80 | def onStmt(refNames: ListBuffer[String])(stmt: Statement): Statement = { 81 | val stmtx = stmt.mapStmt(onStmt(refNames)) 82 | configs.flatMap(c => c.processStmt.lift((stmtx, refNames)).map(c -> _)) match { 83 | // No profiling on this Statement, just return it 84 | case Seq() => stmtx 85 | case Seq((config, (retStmt, signals))) => 86 | val (nodes, annos) = signals.map { expr => 87 | val node = DefNode(get_info(stmtx), localNS.newTemp, expr) 88 | val named = ComponentName(node.name, ModuleName(mod.name, CircuitName(top))) 89 | val pinName = namespace.newName(profilePinPrefix) 90 | assert(localNS.tryName(pinName), s"Name collision with $pinName in ${mod.name}!") 91 | val anno = Seq(CoverPointAnnotation(named, CoverInfo("mux"))) 92 | (node, anno) 93 | }.unzip 94 | profiledSignals(config) ++= annos.flatten 95 | Block(retStmt +: nodes) 96 | case _ => 97 | // We don't let multiple configurations match on a statement because they could have 98 | // different behavior for what should happen to that Statement (eg. removed vs. not 99 | // removed) 100 | throw new Exception("Error! Multiple profiling configurations trying to " + 101 | "profile the same Statement!") 102 | } 103 | } 104 | 105 | val bodyx = onStmt(ListBuffer.empty[String])(mod.body) 106 | (mod.copy(body = bodyx), profiledSignals.view.mapValues(_.toSeq).toMap) 107 | } 108 | 109 | def execute(state: CircuitState): CircuitState = { 110 | 111 | val dontProfile = state.annotations 112 | .collect { case DoNotProfileModule(ModuleName(m, _)) => m } 113 | .toSet 114 | val top = state.circuit.modules.find(_.name == state.circuit.main).get 115 | val topNameS = Namespace(top) // used for pins naming 116 | 117 | val (modsx, profiledSignalMaps) = state.circuit.modules.map { 118 | case mod: Module if !dontProfile(mod.name) => onModule(mod, top.name, topNameS) 119 | case other => (other, Map.empty[ProfileConfig, Seq[CoverPointAnnotation]]) 120 | }.unzip 121 | val profiledSignals = 122 | configs.map( 123 | c => c -> profiledSignalMaps 124 | .filterNot(_.isEmpty) 125 | .map(_.apply(c)) 126 | .reduce(_ ++ _)) 127 | .toMap 128 | 129 | val circuitx = state.circuit.copy(modules = modsx) 130 | val annosx = state.annotations ++ profiledSignals.flatMap(_._2) 131 | 132 | val result = state.copy(circuit = circuitx, annotations = annosx) 133 | result 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /scripts/mem_stats.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import os 3 | import sys 4 | 5 | import xlsxwriter 6 | 7 | 8 | def read_csv(filename): 9 | data = [] 10 | with open(filename, 'r') as csvfile: 11 | reader = csv.reader(csvfile) 12 | for row in reader: 13 | data.append(row) 14 | return data 15 | 16 | def calculate_metrics(data): 17 | img_sizes = [int(row[0]) for row in data] 18 | mem_reqs = [int(row[1]) for row in data] 19 | mem_reqs_in_range = [int(row[2]) for row in data] 20 | 21 | img_fractions = [(in_range / img_size) if img_size != 0 else 1 for img_size, in_range in zip(img_sizes, mem_reqs_in_range)] 22 | out_of_range_accesses = [mem_req - in_range for mem_req, in_range in zip(mem_reqs, mem_reqs_in_range)] 23 | 24 | return img_fractions, out_of_range_accesses 25 | 26 | def calculate_intervals(metrics, num_intervals, interval_index): 27 | intervals = dict() 28 | for i in range(num_intervals): 29 | lower, upper = interval_index(i), interval_index(i + 1) 30 | uppers_s = f"{upper:.2f}" if i < num_intervals - 1 else "inf" 31 | interval = f"[{lower:.2f}, {uppers_s})" 32 | intervals[interval] = 0 33 | for value in metrics: 34 | if lower <= value and (value < upper or i == num_intervals - 1): 35 | intervals[interval] += 1 36 | return intervals 37 | 38 | def average_metrics(metrics_list): 39 | num_files = len(metrics_list) 40 | avg_metrics = [sum(metrics) / num_files for metrics in zip(*metrics_list)] 41 | return avg_metrics 42 | 43 | def main(): 44 | data = dict() 45 | aver_img_frac, aver_oor = 0, 0 46 | input_files = sys.argv[1:] 47 | prefix = len(os.path.commonprefix(input_files)) 48 | suffix = len(os.path.commonprefix([f[::-1] for f in input_files])) 49 | 50 | for file in input_files: 51 | img_fractions, out_of_range_accesses = calculate_metrics(read_csv(file)) 52 | 53 | print(f"File: {file}") 54 | 55 | average_img_fraction = sum(img_fractions) / len(img_fractions) 56 | print(f"Image Fractions: {average_img_fraction}") 57 | aver_img_frac += average_img_fraction 58 | 59 | average_oor = sum(out_of_range_accesses) / len(out_of_range_accesses) 60 | print(f"Out-of-Range Accesses: {average_oor}") 61 | aver_oor += average_oor 62 | 63 | img_intervals = calculate_intervals(img_fractions, num_intervals=20, interval_index=lambda i: i * 0.05) 64 | out_of_range_intervals = calculate_intervals(out_of_range_accesses, num_intervals=9, interval_index=lambda i: 2 ** i) 65 | data[file[prefix:-suffix]] = {"accessed": img_intervals, "out-of-range": out_of_range_intervals} 66 | 67 | print() 68 | 69 | print(f"Average Image Fractions: {aver_img_frac / len(input_files)}") 70 | print(f"Average Out-of-Range Accesses: {aver_oor / len(input_files)}") 71 | 72 | columns = sorted(set(sum([list(d.keys()) for d in data.values()], []))) 73 | def sort_interval(s): 74 | return float(s.strip('[)').split(', ')[0]) 75 | rows = [sorted(set(sum([list(d[c].keys()) for d in data.values()], [])), key=sort_interval) for c in columns] 76 | rows = dict(zip(columns, rows)) 77 | 78 | workbook = xlsxwriter.Workbook('data.xlsx') 79 | for c in columns: 80 | worksheet = workbook.add_worksheet(c) 81 | for i, f in enumerate(data.keys()): 82 | worksheet.write(0, 1 + i, f) 83 | for j, r in enumerate(rows[c]): 84 | worksheet.write(1 + j, 0, r) 85 | worksheet.write(1 + j, 1 + i, data[f][c][r]) 86 | workbook.close() 87 | 88 | if __name__ == "__main__": 89 | main() 90 | -------------------------------------------------------------------------------- /scripts/separator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2023 Institute of Computing Technology, Chinese Academy of Sciences 3 | # xfuzz is licensed under Mulan PSL v2. 4 | # You can use this software according to the terms and conditions of the Mulan PSL v2. 5 | # You may obtain a copy of Mulan PSL v2 at: 6 | # http://license.coscl.org.cn/MulanPSL2 7 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 8 | # EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 9 | # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 10 | # See the Mulan PSL v2 for more details. 11 | 12 | import argparse 13 | import multiprocessing 14 | import re 15 | import string 16 | 17 | def logfile_separator(filename, result_queue): 18 | fuzzer_filename = filename + ".fuzzer" 19 | fuzzer_f = open(fuzzer_filename, "w") 20 | dut_filename = filename + ".dut" 21 | dut_f = open(dut_filename, "w") 22 | ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') 23 | fuzzer_re = re.compile(r'^(.*)(\[\w+ #\d+\].*)$') 24 | pending_line = "" 25 | with open(filename, encoding="ISO-8859-1") as f: 26 | for line in f: 27 | line = ansi_escape.sub('', line) 28 | line = "".join(filter(lambda x: x in string.printable, line)).lstrip() 29 | fuzzer_match = fuzzer_re.match(line) 30 | if fuzzer_match: 31 | pending_line += fuzzer_match.group(1) 32 | fuzzer_f.write(fuzzer_match.group(2) + "\n") 33 | else: 34 | dut_f.writelines(pending_line + line) 35 | pending_line = "" 36 | fuzzer_f.close() 37 | dut_f.close() 38 | result_queue.put(filename) 39 | 40 | 41 | if __name__ == "__main__": 42 | parser = argparse.ArgumentParser( 43 | description='fuzzing log separator for bugfinder') 44 | parser.add_argument('log_files', metavar='log_files', type=str, nargs='*', 45 | default=None, help='fuzzing log files') 46 | parser.add_argument('--jobs', '-j', type=int, default=1) 47 | 48 | args = parser.parse_args() 49 | 50 | processes, results_queue, results = [], multiprocessing.Queue(), [] 51 | def wait_for_one(): 52 | finished = results_queue.get() 53 | results.append(finished) 54 | print(f"({len(results)} / {len(args.log_files)}) finish {finished}") 55 | for filename in args.log_files: 56 | proc = multiprocessing.Process(target=logfile_separator, args=(filename, results_queue)) 57 | if len(processes) - len(results) >= args.jobs: 58 | wait_for_one() 59 | proc.start() 60 | processes.append(proc) 61 | while len(processes) != len(results): 62 | wait_for_one() 63 | for proc in processes: 64 | proc.join() 65 | -------------------------------------------------------------------------------- /src/coverage.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Mutex, OnceLock}; 2 | 3 | /** 4 | * Copyright (c) 2023 Institute of Computing Technology, Chinese Academy of Sciences 5 | * xfuzz is licensed under Mulan PSL v2. 6 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 7 | * You may obtain a copy of Mulan PSL v2 at: 8 | * http://license.coscl.org.cn/MulanPSL2 9 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 10 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 11 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 12 | * See the Mulan PSL v2 for more details. 13 | */ 14 | use crate::harness::get_cover_number; 15 | 16 | struct Coverage { 17 | cover_points: Vec, 18 | accumulated: Vec, 19 | } 20 | 21 | impl Coverage { 22 | pub fn new(n_cover: usize) -> Self { 23 | Self { 24 | cover_points: vec![0; n_cover], 25 | accumulated: vec![0; n_cover], 26 | } 27 | } 28 | 29 | pub fn len(&self) -> usize { 30 | self.cover_points.capacity() 31 | } 32 | 33 | pub fn as_mut_ptr(&self) -> *mut i8 { 34 | self.cover_points.as_ptr().cast_mut() 35 | } 36 | 37 | pub fn accumulate(&mut self) { 38 | for (i, covered) in self.cover_points.iter().enumerate() { 39 | if *covered != 0 as i8 { 40 | self.accumulated[i] = 1; 41 | } 42 | } 43 | } 44 | 45 | pub fn get_accumulative_coverage(&self) -> f64 { 46 | let mut covered_num: usize = 0; 47 | for covered in self.accumulated.iter() { 48 | if *covered != 0 as i8 { 49 | covered_num += 1; 50 | } 51 | } 52 | 100.0 * covered_num as f64 / self.len() as f64 53 | } 54 | 55 | pub fn display(&self) { 56 | // println!("Total Covered Points: {:?}", self.accumulated); 57 | println!( 58 | "Total Coverage: {:.3}%", 59 | self.get_accumulative_coverage() 60 | ); 61 | } 62 | } 63 | 64 | static ICOVERAGE: OnceLock> = OnceLock::new(); 65 | 66 | /// Call this once, right after your C test‑bench has told you how many 67 | /// counters are present. 68 | pub(crate) fn cover_init() { 69 | let cover = Coverage::new(unsafe { get_cover_number() as usize }); 70 | // `set` returns Err if it was already initialised; handle that however 71 | // you prefer (here we just ignore the second call). 72 | let _ = ICOVERAGE.set(Mutex::new(cover)); 73 | } 74 | 75 | fn cov() -> std::sync::MutexGuard<'static, Coverage> { 76 | ICOVERAGE 77 | .get() 78 | .expect("cover_init() not called") 79 | .lock() 80 | .expect("poisoned mutex") 81 | } 82 | 83 | pub(crate) fn cover_len() -> usize { 84 | cov().len() 85 | } 86 | 87 | pub(crate) fn cover_as_mut_ptr() -> *mut i8 { 88 | let guard = cov(); 89 | guard.as_mut_ptr().cast::() 90 | } 91 | 92 | pub(crate) fn cover_accumulate() { 93 | cov().accumulate() 94 | } 95 | 96 | pub(crate) fn cover_display() { 97 | cov().display() 98 | } 99 | -------------------------------------------------------------------------------- /src/fuzzer.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZero; 2 | /** 3 | * Copyright (c) 2023 Institute of Computing Technology, Chinese Academy of Sciences 4 | * xfuzz is licensed under Mulan PSL v2. 5 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | * You may obtain a copy of Mulan PSL v2 at: 7 | * http://license.coscl.org.cn/MulanPSL2 8 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | * See the Mulan PSL v2 for more details. 12 | */ 13 | use std::path::PathBuf; 14 | use std::time::Duration; 15 | 16 | use crate::coverage::*; 17 | use crate::harness; 18 | use crate::monitor; 19 | 20 | use libafl::StdFuzzer; 21 | use libafl::prelude::*; 22 | use libafl::schedulers::QueueScheduler; 23 | use libafl::stages::StdMutationalStage; 24 | use libafl::state::StdState; 25 | use libafl_bolts::{current_nanos, rands::StdRand, tuples::tuple_list}; 26 | 27 | pub(crate) fn run_fuzzer( 28 | random_input: bool, 29 | max_iters: Option, 30 | max_run_timeout: Option, 31 | corpus_input: Option, 32 | corpus_output: Option, 33 | continue_on_errors: bool, 34 | save_errors: bool, 35 | ) { 36 | // Scheduler, Feedback, Objective 37 | let scheduler = QueueScheduler::new(); 38 | let observer = 39 | unsafe { StdMapObserver::from_mut_ptr("signals", cover_as_mut_ptr(), cover_len()) }; 40 | let mut feedback = MaxMapFeedback::new(&observer); 41 | let mut objective = CrashFeedback::new(); 42 | 43 | // State, Manager 44 | let mut state = StdState::new( 45 | StdRand::with_seed(current_nanos()), 46 | InMemoryCorpus::new(), 47 | OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(), 48 | &mut feedback, 49 | &mut objective, 50 | ) 51 | .unwrap(); 52 | let monitor = SimpleMonitor::new(|s| { 53 | println!("{}", s); 54 | }); 55 | let mut mgr = SimpleEventManager::new(monitor); 56 | 57 | // Fuzzer, Executor 58 | let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); 59 | let mut binding = harness::fuzz_harness; 60 | let mut executor = InProcessExecutor::with_timeout( 61 | &mut binding, 62 | // tuple_list!(edges_observer, time_observer), 63 | tuple_list!(observer), 64 | &mut fuzzer, 65 | &mut state, 66 | &mut mgr, 67 | Duration::from_secs(max_run_timeout.unwrap_or(10)), 68 | ) 69 | .unwrap(); 70 | 71 | if continue_on_errors { 72 | unsafe { harness::CONTINUE_ON_ERRORS = true }; 73 | } 74 | 75 | if save_errors { 76 | unsafe { harness::SAVE_ERRORS = true }; 77 | } 78 | 79 | // Corpus 80 | if state.corpus().count() < 1 { 81 | if corpus_input.is_some() { 82 | let corpus_dirs = vec![PathBuf::from(corpus_input.unwrap())]; 83 | state 84 | .load_initial_inputs_forced(&mut fuzzer, &mut executor, &mut mgr, &corpus_dirs) 85 | .unwrap_or_else(|err| { 86 | panic!( 87 | "Failed to load initial corpus at {:?}: {:?}", 88 | &corpus_dirs, err 89 | ) 90 | }); 91 | } else { 92 | let mut generator = RandBytesGenerator::new(NonZero::new(16384).unwrap()); 93 | state 94 | .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 32) 95 | .expect("Failed to generate the initial corpus"); 96 | } 97 | println!("We imported {} inputs from disk.", state.corpus().count()); 98 | } 99 | 100 | if random_input { 101 | println!("We are using random input bytes"); 102 | unsafe { harness::USE_RANDOM_INPUT = true }; 103 | } 104 | 105 | // Mutator 106 | let mutator = StdScheduledMutator::new(havoc_mutations()); 107 | let mut stages = tuple_list!(StdMutationalStage::new(mutator)); 108 | 109 | // Fuzzing Loop 110 | if max_iters.is_some() { 111 | println!("Running the Fuzzer for {} iterations.", max_iters.unwrap()); 112 | fuzzer 113 | .fuzz_loop_for( 114 | &mut stages, 115 | &mut executor, 116 | &mut state, 117 | &mut mgr, 118 | max_iters.unwrap(), 119 | ) 120 | .expect("Fuzzer should not run into errors."); 121 | } else { 122 | println!("Running the Fuzzer for unlimited iterations."); 123 | fuzzer 124 | .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) 125 | .expect("Error in the fuzzing loop"); 126 | } 127 | 128 | if corpus_output.is_some() { 129 | monitor::store_testcases(&mut state, corpus_output.unwrap()); 130 | }; 131 | } 132 | -------------------------------------------------------------------------------- /src/harness.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Mutex, OnceLock}; 2 | 3 | /** 4 | * Copyright (c) 2023 Institute of Computing Technology, Chinese Academy of Sciences 5 | * xfuzz is licensed under Mulan PSL v2. 6 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 7 | * You may obtain a copy of Mulan PSL v2 at: 8 | * http://license.coscl.org.cn/MulanPSL2 9 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 10 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 11 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 12 | * See the Mulan PSL v2 for more details. 13 | */ 14 | extern crate libc; 15 | extern crate rand; 16 | 17 | use std::ffi::CString; 18 | use std::io::{self, Write}; 19 | 20 | use crate::coverage::*; 21 | use crate::monitor::store_testcase; 22 | 23 | use libafl::prelude::*; 24 | use libc::*; 25 | 26 | unsafe extern "C" { 27 | pub fn sim_main(argc: c_int, argv: *const *const c_char) -> c_int; 28 | 29 | pub fn get_cover_number() -> c_uint; 30 | 31 | pub fn update_stats(bitmap: *mut c_char); 32 | 33 | pub fn display_uncovered_points(); 34 | 35 | pub fn set_cover_feedback(name: *const c_char); 36 | 37 | pub fn enable_sim_verbose(); 38 | 39 | pub fn disable_sim_verbose(); 40 | } 41 | 42 | static SIM_ARGS: OnceLock>> = OnceLock::new(); 43 | 44 | fn sim_run(workload: &String) -> i32 { 45 | // prepare the simulation arguments in Vec format 46 | let mut sim_args: Vec = vec!["emu".to_string(), "-i".to_string(), workload.to_string()] 47 | .iter() 48 | .map(|s| s.to_string()) 49 | .collect(); 50 | let guard = SIM_ARGS 51 | .get() 52 | .expect("SIM_ARGS not initialized") 53 | .lock() 54 | .unwrap(); 55 | sim_args.extend(guard.iter().cloned()); 56 | 57 | // convert the simulation arguments into c_char** 58 | let sim_args: Vec<_> = sim_args 59 | .iter() 60 | .map(|s| CString::new(s.as_bytes()).unwrap()) 61 | .collect(); 62 | let mut p_argv: Vec<_> = sim_args.iter().map(|arg| arg.as_ptr()).collect(); 63 | p_argv.push(std::ptr::null()); 64 | 65 | // send simulation arguments to sim_main and get the return code 66 | let ret = unsafe { sim_main(sim_args.len() as i32, p_argv.as_ptr()) }; 67 | unsafe { update_stats(cover_as_mut_ptr()) } 68 | cover_accumulate(); 69 | 70 | ret 71 | } 72 | 73 | fn sim_run_from_memory(input: &BytesInput) -> i32 { 74 | // create a workload-in-memory name for the input bytes 75 | let wim_bytes = input.mutator_bytes(); 76 | let wim_addr = wim_bytes.as_ptr(); 77 | let wim_size = wim_bytes.len() as u64; 78 | let wim_name = format!("wim@{wim_addr:p}+0x{wim_size:x}"); 79 | // pass the in-memory workload to sim_run 80 | sim_run(&wim_name) 81 | } 82 | 83 | pub(crate) fn sim_run_multiple(workloads: &Vec, auto_exit: bool) -> i32 { 84 | let mut ret = 0; 85 | for workload in workloads.iter() { 86 | ret = sim_run(workload); 87 | if ret != 0 { 88 | println!("{} exits abnormally with return code: {}", workload, ret); 89 | if auto_exit { 90 | break; 91 | } 92 | } 93 | } 94 | return ret; 95 | } 96 | 97 | pub static mut USE_RANDOM_INPUT: bool = false; 98 | pub static mut CONTINUE_ON_ERRORS: bool = false; 99 | pub static mut SAVE_ERRORS: bool = false; 100 | pub static mut NUM_RUNS: u64 = 0; 101 | pub static mut MAX_RUNS: u64 = u64::MAX; 102 | 103 | pub(crate) fn fuzz_harness(input: &BytesInput) -> ExitKind { 104 | let ret = if unsafe { USE_RANDOM_INPUT } { 105 | let random_bytes: Vec = (0..1024).map(|_| rand::random::()).collect(); 106 | let b = BytesInput::new(random_bytes); 107 | sim_run_from_memory(&b) 108 | } else { 109 | sim_run_from_memory(input) 110 | }; 111 | 112 | // get coverage 113 | cover_display(); 114 | io::stdout().flush().unwrap(); 115 | 116 | // panic if return code is non-zero (this is for fuzzers to catch crashes) 117 | let do_panic = unsafe { !CONTINUE_ON_ERRORS && ret != 0 }; 118 | if do_panic { 119 | unsafe { display_uncovered_points() } 120 | panic!("<<<<<< Bug triggered >>>>>>"); 121 | } 122 | 123 | // save the target testcase into disk 124 | let do_save = unsafe { SAVE_ERRORS && ret != 0 }; 125 | if do_save { 126 | store_testcase(input, &"errors".to_string(), None); 127 | } 128 | 129 | // panic to exit the fuzzer if max_runs is reached 130 | unsafe { NUM_RUNS += 1 }; 131 | let do_exit = unsafe { NUM_RUNS >= MAX_RUNS }; 132 | if do_exit { 133 | println!("Exit due to max_runs == 0"); 134 | unsafe { display_uncovered_points() } 135 | panic!("Exit due to max_runs == 0"); 136 | } 137 | 138 | ExitKind::Ok 139 | } 140 | 141 | pub(crate) fn set_sim_env( 142 | coverage: String, 143 | verbose: bool, 144 | max_runs: Option, 145 | emu_args: Vec, 146 | ) { 147 | let cover_name = CString::new(coverage.as_bytes()).unwrap(); 148 | unsafe { set_cover_feedback(cover_name.as_ptr()) } 149 | 150 | if verbose { 151 | unsafe { enable_sim_verbose() } 152 | } else { 153 | unsafe { disable_sim_verbose() } 154 | } 155 | 156 | if max_runs.is_some() { 157 | unsafe { MAX_RUNS = max_runs.unwrap() }; 158 | } 159 | 160 | let _ = SIM_ARGS.set(Mutex::new(emu_args)); 161 | 162 | cover_init(); 163 | } 164 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Institute of Computing Technology, Chinese Academy of Sciences 3 | * xfuzz is licensed under Mulan PSL v2. 4 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 5 | * You may obtain a copy of Mulan PSL v2 at: 6 | * http://license.coscl.org.cn/MulanPSL2 7 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 8 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 9 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 10 | * See the Mulan PSL v2 for more details. 11 | */ 12 | mod coverage; 13 | mod fuzzer; 14 | mod harness; 15 | mod monitor; 16 | 17 | use clap::Parser; 18 | 19 | #[derive(Parser, Default, Debug)] 20 | struct Arguments { 21 | // Fuzzer options 22 | #[clap(default_value_t = false, short, long)] 23 | fuzzing: bool, 24 | #[clap(default_value_t = String::from("llvm.branch"), short, long)] 25 | coverage: String, 26 | #[clap(default_value_t = false, short, long)] 27 | verbose: bool, 28 | #[clap(long)] 29 | max_iters: Option, 30 | #[clap(long)] 31 | max_runs: Option, 32 | #[clap(long)] 33 | max_run_timeout: Option, 34 | #[clap(default_value_t = false, long)] 35 | random_input: bool, 36 | #[clap(default_value_t = String::from("./corpus"), long)] 37 | corpus_input: String, 38 | #[clap(long)] 39 | corpus_output: Option, 40 | #[clap(default_value_t = false, long)] 41 | continue_on_errors: bool, 42 | #[clap(default_value_t = false, long)] 43 | save_errors: bool, 44 | // Run options 45 | #[clap(default_value_t = 1, long)] 46 | repeat: usize, 47 | #[clap(default_value_t = false, long)] 48 | auto_exit: bool, 49 | extra_args: Vec, 50 | } 51 | 52 | #[unsafe(no_mangle)] 53 | fn main() -> i32 { 54 | let args = Arguments::parse(); 55 | 56 | let mut workloads: Vec = Vec::new(); 57 | let mut emu_args: Vec = Vec::new(); 58 | 59 | let mut is_emu = false; 60 | for arg in args.extra_args { 61 | if arg.starts_with("-") { 62 | is_emu = true; 63 | } 64 | 65 | if is_emu { 66 | emu_args.push(arg); 67 | } else { 68 | workloads.push(arg); 69 | } 70 | } 71 | 72 | harness::set_sim_env(args.coverage, args.verbose, args.max_runs, emu_args); 73 | 74 | let mut has_failed = 0; 75 | if workloads.len() > 0 { 76 | for _ in 0..args.repeat { 77 | let ret = harness::sim_run_multiple(&workloads, args.auto_exit); 78 | if ret != 0 { 79 | has_failed = 1; 80 | if args.auto_exit { 81 | return ret; 82 | } 83 | } 84 | } 85 | coverage::cover_display(); 86 | } 87 | 88 | if args.fuzzing { 89 | let corpus_input = if args.corpus_input == "random" { 90 | None 91 | } else { 92 | Some(args.corpus_input) 93 | }; 94 | fuzzer::run_fuzzer( 95 | args.random_input, 96 | args.max_iters, 97 | args.max_run_timeout, 98 | corpus_input, 99 | args.corpus_output, 100 | args.continue_on_errors, 101 | args.save_errors, 102 | ); 103 | } 104 | 105 | return has_failed; 106 | } 107 | -------------------------------------------------------------------------------- /src/monitor.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Institute of Computing Technology, Chinese Academy of Sciences 3 | * xfuzz is licensed under Mulan PSL v2. 4 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 5 | * You may obtain a copy of Mulan PSL v2 at: 6 | * http://license.coscl.org.cn/MulanPSL2 7 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 8 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 9 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 10 | * See the Mulan PSL v2 for more details. 11 | */ 12 | extern crate md5; 13 | 14 | use std::fs; 15 | use std::path::PathBuf; 16 | 17 | use libafl::inputs::{HasMutatorBytes, ValueInput}; 18 | use libafl::prelude::{BytesInput, Corpus, InMemoryCorpus, Input, OnDiskCorpus}; 19 | use libafl::state::{HasCorpus, StdState}; 20 | use libafl_bolts::rands::RomuDuoJrRand; 21 | 22 | pub fn store_testcases( 23 | state: &mut StdState< 24 | InMemoryCorpus>>, 25 | ValueInput>, 26 | RomuDuoJrRand, 27 | OnDiskCorpus>>, 28 | >, 29 | output_dir: String, 30 | ) { 31 | let corpus = state.corpus(); 32 | 33 | let count = corpus.count(); 34 | println!("Total corpus count: {count}"); 35 | 36 | for id in corpus.ids() { 37 | let testcase: std::cell::RefMut> = 38 | corpus.get(id).unwrap().borrow_mut(); 39 | let exec_time = testcase.exec_time().map(|s| s.as_secs()).unwrap_or(0); 40 | let scheduled_count = testcase.scheduled_count(); 41 | let parent_id = if testcase.parent_id().is_some() { 42 | usize::from(testcase.parent_id().unwrap()) as i32 43 | } else { 44 | -1 45 | }; 46 | println!( 47 | "Corpus {id}: exec_time {exec_time}, scheduled_count {scheduled_count}, parent_id {parent_id}" 48 | ); 49 | let x = testcase.input().as_ref().unwrap(); 50 | store_testcase(x, &output_dir, Some(id.to_string())); 51 | } 52 | } 53 | 54 | pub fn store_testcase(input: &BytesInput, output_dir: &String, name: Option) { 55 | fs::create_dir_all(&output_dir).expect("Unable to create the output directory"); 56 | 57 | let filename = if name.is_some() { 58 | name.unwrap() 59 | } else { 60 | let mut context = md5::Context::new(); 61 | context.consume(input.mutator_bytes()); 62 | format!("{:x}", context.compute()) 63 | }; 64 | 65 | input 66 | .to_file(PathBuf::from(format!("{output_dir}/{filename}")).as_path()) 67 | .expect(format!("written {filename} failed").as_str()); 68 | } 69 | -------------------------------------------------------------------------------- /xfuzz: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2023 Institute of Computing Technology, Chinese Academy of Sciences 3 | # xfuzz is licensed under Mulan PSL v2. 4 | # You can use this software according to the terms and conditions of the Mulan PSL v2. 5 | # You may obtain a copy of Mulan PSL v2 at: 6 | # http://license.coscl.org.cn/MulanPSL2 7 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 8 | # EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 9 | # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 10 | # See the Mulan PSL v2 for more details. 11 | 12 | import argparse 13 | import os 14 | import shutil 15 | import signal 16 | import subprocess 17 | import time 18 | 19 | 20 | def prepare_optional_args(args): 21 | cmd = [None if x[1] is None else f"--{x[0]}={x[1]}" for x in args] 22 | return list(filter(lambda _: _ is not None, cmd)) 23 | 24 | 25 | def prepare_binary_args(args): 26 | cmd = [f"--{x[0]}" if x[1] else None for x in args] 27 | return list(filter(lambda _: _ is not None, cmd)) 28 | 29 | class ProcControl(object): 30 | def __init__(self, n_procs): 31 | self.n_procs = n_procs if n_procs is not None else 200 32 | self.running = [] 33 | 34 | def exec_cmd(self, cmd, env=None, stdout=None, stderr=None): 35 | self.wait_on_full() 36 | use_shell = isinstance(cmd, str) 37 | p = subprocess.Popen(cmd, stdout=stdout, stderr=stderr, env=env, preexec_fn=os.setsid, shell=use_shell) 38 | self.running.append(p) 39 | return p 40 | 41 | def wait_on_full(self): 42 | while len(self.running) >= self.n_procs: 43 | self.running = [p for p in self.running if p.poll() is None] 44 | time.sleep(1) 45 | 46 | class Xfuzz(object): 47 | def __init__(self, ctrl, elf_path, coverage, max_iters, max_runs, verbose, no_pipe, corpus_input, corpus_output): 48 | self.proc_ctrl = ctrl 49 | self.elf_path = os.path.realpath(elf_path) 50 | self.coverage = coverage 51 | self.max_iters = max_iters 52 | self.max_runs = max_runs 53 | self.random_input = args.random_input 54 | self.continue_on_errors = args.continue_on_errors 55 | self.save_errors = args.save_errors 56 | self.verbose = verbose 57 | self.no_pipe = no_pipe 58 | self.corpus_input = corpus_input 59 | self.corpus_output = corpus_output 60 | 61 | def cmd(self) -> list: 62 | base_cmd = [self.elf_path] 63 | optional_cmd = prepare_optional_args([ 64 | ('corpus-input', self.corpus_input), 65 | ("coverage", self.coverage), 66 | ("max-iters", self.max_iters), 67 | ("max-runs", self.max_runs) 68 | ]) 69 | binary_cmd = prepare_binary_args([ 70 | ("random-input", self.random_input), 71 | ("continue-on-errors", self.continue_on_errors), 72 | ("save-errors", self.save_errors), 73 | ("verbose", self.verbose) 74 | ]) 75 | return base_cmd + optional_cmd + binary_cmd 76 | 77 | def run(self, extra_args, worker_path, run_name="0"): 78 | base_path = os.path.dirname(os.path.realpath(__file__)) 79 | env = os.environ.copy() 80 | env["CARGO_MANIFEST_DIR"] = base_path 81 | # if --no-pipe is set, we do not pipe the stdout, stderr to files 82 | if self.no_pipe: 83 | run_cmd = self.cmd() + extra_args 84 | return self.proc_ctrl.exec_cmd(run_cmd, env) 85 | # create worker directory 86 | if not os.path.exists(worker_path): 87 | os.makedirs(worker_path, exist_ok=True) 88 | run_cmd = self.cmd() + extra_args 89 | result_path = os.path.join(worker_path, run_name) 90 | if not os.path.exists(result_path): 91 | os.makedirs(result_path, exist_ok=True) 92 | stdout_file = os.path.join(result_path, "stdout.txt") 93 | stderr_file = os.path.join(result_path, "stderr.txt") 94 | if self.corpus_output: 95 | run_cmd += ['--corpus-output', f'{result_path}/testcases'] 96 | with open(stdout_file, "w") as stdout, open(stderr_file, "w") as stderr: 97 | print(f"run {run_name}:", " ".join(run_cmd), ">", stdout_file, "2>", stderr_file) 98 | proc = self.proc_ctrl.exec_cmd(run_cmd, env, stdout=stdout, stderr=stderr) 99 | return proc 100 | 101 | def report(self, worker_path, run_name="0", merge=False): 102 | if self.no_pipe: 103 | raise Exception("--no-pipe is set. Don't know what to report.") 104 | if merge: 105 | merge_cmd = ["./scripts/visualizer", worker_path, "--merge"] 106 | print(f"merge reports in {worker_path}:", merge_cmd) 107 | return self.proc_ctrl.exec_cmd(merge_cmd) 108 | result_path = os.path.join(worker_path, run_name) 109 | stdout_file = os.path.join(result_path, "stdout.txt") 110 | rpt_file = os.path.join(result_path, f"rpt-{run_name}") 111 | report_cmd = [ 112 | ["./scripts/separator", stdout_file], 113 | ["./scripts/visualizer", result_path, "-o", rpt_file] 114 | ] 115 | report_cmd = ";".join([" ".join(cmd) for cmd in report_cmd]) 116 | print(f"report {run_name}:", report_cmd) 117 | return self.proc_ctrl.exec_cmd(report_cmd) 118 | 119 | 120 | class XfuzzServer(object): 121 | STEP_RUN = "run" 122 | STEP_REPORT = "report" 123 | STEP_MERGE = "merge" 124 | STEPS = [STEP_RUN, STEP_REPORT, STEP_MERGE] 125 | 126 | def __init__(self, args, ctrl) -> None: 127 | self.xfuzz = [] 128 | for e in args.elf.split(","): 129 | for c in args.coverage.split(","): 130 | f = Xfuzz(ctrl, e, c, args.max_iters, args.max_runs, args.verbose, args.no_pipe, 131 | args.corpus_input, args.corpus_output) 132 | self.xfuzz.append(f) 133 | self.worker_path = args.output 134 | self.steps = args.steps.split(",") if args.steps else self.STEPS 135 | self.no_pipe = args.no_pipe 136 | if self.no_pipe: 137 | if self.STEP_REPORT in self.steps: 138 | self.steps.remove(self.STEP_REPORT) 139 | if self.STEP_MERGE in self.steps: 140 | self.steps.remove(self.STEP_MERGE) 141 | self.fuzzing = args.fuzzing 142 | if self.fuzzing: 143 | self.max_iters = args.max_iters 144 | self.jobs = args.jobs 145 | else: 146 | self.auto_exit = args.auto_exit 147 | self.repeat = args.repeat 148 | self.workloads = [] 149 | self.extra_args = [] 150 | is_workload = True 151 | for arg in args.extra_args: 152 | if is_workload and arg.startswith("-"): 153 | is_workload = False 154 | if is_workload: 155 | self.workloads.append(arg) 156 | else: 157 | self.extra_args.append(arg) 158 | if len(self.extra_args) > 0: 159 | self.extra_args = ['--'] + self.extra_args 160 | self.procs = [] 161 | self.proc_ctrl = ctrl 162 | 163 | def launch(self): 164 | if "run" in self.steps: 165 | self.run() 166 | if "report" in self.steps: 167 | self.wait_finish() 168 | self.report() 169 | if "merge" in self.steps: 170 | self.wait_finish() 171 | self.merge_reports() 172 | 173 | def run(self): 174 | if self.fuzzing: 175 | for x in self.xfuzz: 176 | for i in range(self.jobs): 177 | self.procs.append(x.run(["-f"] + self.extra_args, self.worker_path, run_name=f"{x.coverage}/{i}")) 178 | else: 179 | args = self.workloads 180 | args += prepare_optional_args([ 181 | ("repeat", self.repeat) 182 | ]) 183 | args += prepare_binary_args([ 184 | ("auto-exit", self.auto_exit) 185 | ]) 186 | for x in self.xfuzz: 187 | self.procs.append(x.run(args + self.extra_args, self.worker_path)) 188 | 189 | def report(self): 190 | if self.fuzzing: 191 | for x in self.xfuzz: 192 | for i in range(self.jobs): 193 | self.procs.append(x.report(self.worker_path, run_name=f"{i}")) 194 | else: 195 | for x in self.xfuzz: 196 | self.procs.append(x.report(self.worker_path)) 197 | 198 | def merge_reports(self): 199 | if self.no_pipe: 200 | raise Exception("--no-pipe is set. Don't know what to report.") 201 | merge_cmd = ["./scripts/visualizer", self.worker_path, "--merge"] 202 | print(f"merge reports in {self.worker_path}:", merge_cmd) 203 | self.procs.append(self.proc_ctrl.exec_cmd(merge_cmd)) 204 | 205 | def load_workloads(self, workloads): 206 | def recursive(p): 207 | if os.path.isdir(p): 208 | result = [] 209 | for filename in os.listdir(p): 210 | if filename.startswith("."): 211 | continue 212 | result += recursive(os.path.join(p, filename)) 213 | return result 214 | return [p] 215 | 216 | results = [] 217 | for workload in workloads: 218 | if not os.path.exists(workload): 219 | raise FileNotFoundError(f"{workload}") 220 | results += recursive(os.path.realpath(workload)) 221 | return results 222 | 223 | def is_finished(self) -> bool: 224 | for proc in self.procs: 225 | if proc.poll() is None: 226 | return False 227 | return True 228 | 229 | def wait_finish(self): 230 | while not self.is_finished(): 231 | time.sleep(1) 232 | 233 | 234 | if __name__ == "__main__": 235 | parser = argparse.ArgumentParser(description='xfuzz: a fuzzer wrapper') 236 | # server options 237 | parser.add_argument('--no-pipe', action='store_true') 238 | parser.add_argument('--steps', type=str, help='options: run, report, merge') 239 | parser.add_argument('--output', '-o', type=str, default="fuzz_results") 240 | # harness options 241 | parser.add_argument('--coverage', '-c', type=str) 242 | parser.add_argument('--max-runs', type=int, help='maximum number of sim runs') 243 | parser.add_argument('--verbose', action='store_true') 244 | # fuzz options 245 | parser.add_argument('--fuzzing', '-f', action='store_true') 246 | parser.add_argument('--max-iters', type=int) 247 | parser.add_argument('--jobs', '-j', type=int, default=1) 248 | parser.add_argument('--corpus-input') 249 | parser.add_argument('--random-input', action='store_true') 250 | parser.add_argument('--continue-on-errors', action='store_true') 251 | parser.add_argument('--save-errors', action='store_true') 252 | parser.add_argument('--corpus-output', action='store_true') 253 | # run options 254 | parser.add_argument('--elf', help='fuzz elf path') 255 | parser.add_argument('--auto-exit', action='store_true') 256 | parser.add_argument('--repeat', type=int) 257 | parser.add_argument('extra_args', type=str, nargs='*') 258 | # process control 259 | parser.add_argument('--parallel', type=int) 260 | 261 | args = parser.parse_args() 262 | 263 | if os.path.isdir(args.elf): 264 | args.elf = [os.path.join(args.elf, e) for e in os.listdir(args.elf)] 265 | args.elf = [e for e in args.elf if os.path.isfile(e)] 266 | args.elf = ",".join(args.elf) 267 | 268 | ctrl = ProcControl(args.parallel) 269 | server = XfuzzServer(args, ctrl) 270 | try: 271 | server.launch() 272 | server.wait_finish() 273 | except KeyboardInterrupt: 274 | print("Interrupted. Exiting all processes ...") 275 | for proc in server.procs: 276 | try: 277 | os.killpg(os.getpgid(proc.pid), signal.SIGINT) 278 | except ProcessLookupError: 279 | pass 280 | --------------------------------------------------------------------------------