├── example ├── files.f ├── ffd.sv └── ffd_testbench.sv ├── svutRun ├── svutCreate ├── readme.jpg ├── MANIFEST.in ├── svut ├── svut.core ├── template.cpp ├── template.sv ├── svut_h.sv ├── svutCreate.py └── svutRun.py ├── test ├── testsuite_examples.sh ├── testsuite_create.sh ├── testsuite_run.sh ├── lint.sh ├── testsuite_run_args.sh ├── Adder.v ├── regression.sh ├── Adder_KO_testsuite.sv └── Adder_OK_testsuite.sv ├── .github └── workflows │ ├── ci_macos.yaml │ └── ci_ubuntu.yaml ├── pypi.md ├── AUTHORS ├── pyproject.toml ├── LICENSE ├── setup.py ├── .gitignore └── README.md /example/files.f: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svutRun: -------------------------------------------------------------------------------- 1 | ./svut/svutRun.py -------------------------------------------------------------------------------- /svutCreate: -------------------------------------------------------------------------------- 1 | ./svut/svutCreate.py -------------------------------------------------------------------------------- /readme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dpretet/svut/HEAD/readme.jpg -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include LICENSE 3 | include README.md 4 | recursive-include svut/ * 5 | -------------------------------------------------------------------------------- /svut/svut.core: -------------------------------------------------------------------------------- 1 | CAPI=2: 2 | 3 | name : ::svut:1.4.2-r1 4 | 5 | filesets: 6 | include: 7 | files: [svut_h.sv : {file_type : systemVerilogSource, is_include_file : true}] 8 | 9 | targets: 10 | default: {filesets: [include]} 11 | -------------------------------------------------------------------------------- /test/testsuite_examples.sh: -------------------------------------------------------------------------------- 1 | test_run_ffd_example_icarus() { #@test 2 | 3 | run bash -c "cd ../example && ../svutRun -test ffd_testbench.sv -sim icarus" 4 | [ "$status" -eq 0 ] 5 | } 6 | 7 | test_run_ffd_example_verilator_failure() { #@test 8 | 9 | run bash -c "cd ../example && ../svutRun -test ffd_testbench.sv -sim verilator" 10 | [ "$status" -eq 1 ] 11 | } 12 | -------------------------------------------------------------------------------- /test/testsuite_create.sh: -------------------------------------------------------------------------------- 1 | test_create_adder_testbench() { #@test 2 | 3 | run "$DIR/../svutCreate" "$DIR/Adder.v" 4 | 5 | [ "$status" -eq 0 ] 6 | [ -e "Adder_testbench.sv" ] 7 | [ -e "sim_main.cpp" ] 8 | [ -e "files.f" ] 9 | 10 | } 11 | 12 | 13 | test_create_no_path() { #@test 14 | run "$DIR/../svutCreate" 15 | [ "$status" -eq 1 ] 16 | } 17 | 18 | 19 | test_create_bad_input_path() { #@test 20 | run "$DIR/../svutCreate" "$DIR/Add__er.v" 21 | [ "$status" -eq 1 ] 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/ci_macos.yaml: -------------------------------------------------------------------------------- 1 | name: macOS 2 | on: [push, pull_request] 3 | jobs: 4 | lint-code: 5 | runs-on: macos-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - run: brew install pylint 9 | - run: cd test; ./lint.sh 10 | ci-tests: 11 | runs-on: macos-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - run: brew install icarus-verilog 15 | - run: iverilog -V 16 | - run: brew install verilator 17 | - run: verilator -V 18 | - run: cd test; ./regression.sh 19 | -------------------------------------------------------------------------------- /.github/workflows/ci_ubuntu.yaml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 2 | on: [push, pull_request] 3 | jobs: 4 | lint-code: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - run: sudo apt install pylint 9 | - run: cd test; ./lint.sh 10 | ci-tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - run: sudo apt install iverilog 15 | - run: iverilog -V 16 | - run: sudo apt install verilator 17 | - run: verilator -V 18 | - run: cd test; ./regression.sh 19 | -------------------------------------------------------------------------------- /pypi.md: -------------------------------------------------------------------------------- 1 | # How To Release & Publish on Pypi 2 | 3 | 4 | - Update ./setup.py and ./pyproject.toml with correct release number 5 | - Update contributors in ./pyproject.toml and ./AUTHORS 6 | - Prepare the tag and push the commits tagged 7 | - Follow next intructions to build and publish the package 8 | 9 | ```bash 10 | 11 | # Install the necessary tools 12 | # For linux 13 | python3 -m pip install --upgrade build 14 | # For Macos with brew 15 | brew install python-build 16 | 17 | # Build the package 18 | python3.10 -m build 19 | 20 | # Check the package 21 | twine check dist/* 22 | 23 | # Upload to Pypi once registred 24 | twine upload -r pypi dist/* 25 | ``` 26 | 27 | https://realpython.com/pypi-publish-python-package/#publish-your-package-to-pypi 28 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # SVUT has been originally created in 2017 to provide an easy way to create 2 | # testbenchs for Verilog and SystemVerilog development. It's licensed 3 | # under MIT licence, a very permissive license for reuse and compatibility 4 | # with other licenses. 5 | # 6 | # This does not necessarily list everyone who has contributed code, since in 7 | # some cases, their employer may be the copyright holder. To see the full list 8 | # of contributors, see the revision history in source control. 9 | 10 | Damien Pretet 11 | Sarah Clark 12 | Sebastian Schaetz 13 | Olof Kindgren 14 | Evan Mays <@evanon0ping> 15 | De hekkende krekker 16 | leibnewton 17 | ping-ee 18 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "svut" 7 | version = "1.10.0" 8 | authors = [ 9 | { name="Damien Pretet", email="damien.pretet@me.com" }, 10 | { name="Sarah Clark", email="sarahclark@google.com" }, 11 | { name="Sebastian Schaetz", email="seb.schaetz@gmail.com" }, 12 | ] 13 | description = "SystemVerilog Unit Test (SVUT)" 14 | readme = "README.md" 15 | license = { file = "LICENSE" } 16 | requires-python = ">=3.7" 17 | keywords = ["verilog", "systemverilog", "testing"] 18 | classifiers = [ 19 | "Programming Language :: Python :: 3", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | ] 23 | 24 | [project.urls] 25 | "Homepage" = "https://github.com/dpretet/svut" 26 | "Bug Tracker" = "https://github.com/dpretet/svut/issues" 27 | -------------------------------------------------------------------------------- /test/testsuite_run.sh: -------------------------------------------------------------------------------- 1 | test_run_ok_testsuite() { #@test 2 | 3 | run ../svutRun -test "$DIR/Adder_OK_testsuite.sv" -define "MYDEF1=5;MYDEF2" 4 | [ "$status" -eq 0 ] 5 | } 6 | 7 | test_run_ok_testsuite_failure() { #@test 8 | 9 | run ../svutRun -test "$DIR/Adder_OK_testsuite.sv" 10 | [ "$status" -eq 1 ] 11 | } 12 | 13 | 14 | test_run_ko_testsuite() { #@test 15 | 16 | run ../svutRun -test "$DIR/Adder_KO_testsuite.sv" 17 | [ "$status" -eq 0 ] 18 | } 19 | 20 | test_run_ko_testsuite_error_count() { #@test 21 | 22 | run exe_ko_to_log 23 | error_num=8 24 | [ $(grep -c "ERROR:" log) -eq "$error_num" ] 25 | } 26 | 27 | test_run_ko_testsuite_error_count_failure() { #@test 28 | 29 | run exe_ko_to_log 30 | error_num=0 31 | [ $(grep -c "ERROR:" log) -ne "$error_num" ] 32 | } 33 | 34 | function exe_ko_to_log() { 35 | ../svutRun -test "$DIR/Adder_KO_testsuite.sv" | tee log 36 | } 37 | -------------------------------------------------------------------------------- /test/lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # pipe fails if first command fails. Else is always successful 4 | set -o pipefail 5 | rm -f lint.txt; touch lint.txt 6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 7 | 8 | echo "Lint svutRun.py" 9 | pylint -d C0301 -d C0103 $DIR/../svut/svutRun.py | tee -a lint.txt 10 | ret=$? 11 | 12 | if [[ $ret != 0 ]]; then 13 | echo "Linting failed" 14 | else 15 | echo "svutRun.py finished successfully" 16 | fi 17 | 18 | echo "Lint svutCreate.py" 19 | pylint -d C0301 -d C0103 $DIR/../svut/svutCreate.py | tee -a lint.txt 20 | ret=$? 21 | 22 | if [[ $ret != 0 ]]; then 23 | echo "Linting failed" 24 | else 25 | echo "svutCreate.py finished successfully" 26 | fi 27 | 28 | grep -n "^E:" lint.txt 29 | ret=$? 30 | 31 | if [[ $ret == 0 ]]; then 32 | echo "Linting failed" 33 | exit 1 34 | else 35 | echo "Linting finished successfully" 36 | exit 0 37 | fi 38 | -------------------------------------------------------------------------------- /svut/template.cpp: -------------------------------------------------------------------------------- 1 | #include "build/V${name}_testbench.h" 2 | #include "verilated.h" 3 | // Uncomment if use FST format 4 | // #include "verilated_fst_c.h" 5 | 6 | int main(int argc, char** argv, char** env) { 7 | 8 | // Uncomment if use FST format 9 | // VerilatedFstC* tfp = new VerilatedFstC; 10 | 11 | Verilated::commandArgs(argc, argv); 12 | V${name}_testbench* top = new V${name}_testbench; 13 | 14 | // Uncomment if use FST format 15 | // top->trace(tfp, 99); // Depth of 99 levels 16 | // tfp->open("waveform.fst"); // Open FST file 17 | 18 | // Simulate until $$finish() 19 | while (!Verilated::gotFinish()) { 20 | 21 | // Evaluate model; 22 | top->eval(); 23 | } 24 | 25 | // Final model cleanup 26 | top->final(); 27 | // Uncomment if use FST format 28 | // tfp->close(); // Close the FST file 29 | 30 | // Destroy model 31 | delete top; 32 | 33 | // Return good completion status 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2024 The SVUT Authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial 12 | portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 18 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /test/testsuite_run_args.sh: -------------------------------------------------------------------------------- 1 | test_run_wrong_run_only_compile_only() { #@test 2 | 3 | run "$DIR/../svut/svutRun.py" "-run-only" "-compile-only" 4 | [ "$status" -eq 1 ] 5 | } 6 | 7 | test_run_all_with_run_only() { #@test 8 | 9 | run "$DIR/../svut/svutRun.py" "-run-only" 10 | [ "$status" -eq 0 ] 11 | } 12 | 13 | test_run_all_with_compile_only() { #@test 14 | 15 | run "$DIR/../svut/svutRun.py" "-compile-only" 16 | [ "$status" -eq 2 ] 17 | } 18 | 19 | test_run_wrong_simulator() { #@test 20 | 21 | run "$DIR/../svut/svutRun.py" "-sim" "xxx" 22 | [ "$status" -eq 1 ] 23 | } 24 | 25 | test_run_version() { #@test 26 | 27 | run "$DIR/../svut/svutRun.py" "-version" 28 | [ "$status" -eq 0 ] 29 | } 30 | 31 | test_run_no_tb_present_to_scan() { #@test 32 | 33 | run "$DIR/../svut/svutRun.py" "-test" "../" 34 | [ "$status" -eq 1 ] 35 | } 36 | 37 | test_run_not_a_tb_path() { #@test 38 | 39 | run "$DIR/../svut/svutRun.py" "-test" "../example/" 40 | [ "$status" -eq 1 ] 41 | } 42 | 43 | test_run_no_tests() { #@test 44 | 45 | run "cd" "-" "&&" "./svutRun" 46 | [ "$status" -eq 1 ] 47 | } 48 | 49 | 50 | test_run_dry_run() { #@test 51 | 52 | run "$DIR/../svut/svutRun.py" "-test" "../example/ffd_testbench.sv" "-dry-run" 53 | [ "$status" -eq 0 ] 54 | } 55 | -------------------------------------------------------------------------------- /test/Adder.v: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | //----------------------------------------------------------------------- 14 | 15 | `timescale 1 ns / 1 ps 16 | `default_nettype none 17 | 18 | module Adder 19 | 20 | #( 21 | parameter WIDTH = 8 22 | )( 23 | input wire aclk, 24 | input wire arstn, 25 | input wire clr, 26 | input wire inc, 27 | output reg [WIDTH-1:0] out 28 | ); 29 | 30 | always @ (posedge aclk or negedge arstn) begin 31 | 32 | if (arstn == 1'b0) begin 33 | out <= {WIDTH{1'b0}}; 34 | end 35 | else begin 36 | if (clr == 1'b1) 37 | out <= {WIDTH{1'b0}}; 38 | else if (inc == 1'b1) 39 | out <= out + 1'b1; 40 | end 41 | end 42 | 43 | endmodule 44 | 45 | `resetall 46 | 47 | -------------------------------------------------------------------------------- /example/ffd.sv: -------------------------------------------------------------------------------- 1 | /// Copyright 2024 The SVUT Authors 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to 5 | /// deal in the Software without restriction, including without limitation the 6 | /// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | /// sell copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | /// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | /// IN THE SOFTWARE. 20 | 21 | `timescale 1 ns / 1 ps 22 | 23 | module ffd 24 | 25 | ( 26 | input wire aclk, 27 | input wire arstn, 28 | input wire d, 29 | output reg q 30 | ); 31 | 32 | always @ (posedge aclk or negedge arstn) begin 33 | if (arstn == 1'b0) q <= 1'b0; 34 | else q <= d; 35 | end 36 | 37 | endmodule 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Setup module for SVUT. 2 | """ 3 | 4 | from setuptools import setup, find_namespace_packages 5 | import pathlib 6 | 7 | here = pathlib.Path(__file__).parent.resolve() 8 | 9 | long_description = (here / "README.md").read_text(encoding="utf-8") 10 | 11 | 12 | setup( 13 | name="svut", 14 | version="1.10.0", 15 | description="SystemVerilog Unit Test (SVUT)", 16 | long_description=long_description, 17 | long_description_content_type="text/markdown", 18 | url="https://github.com/dpretet/svut", 19 | author="Damien Pretet", 20 | author_email="damien.pretet@me.com", 21 | classifiers=[ 22 | "Development Status :: 3 - Production/Stable", 23 | "Intended Audience :: Developers", 24 | "Topic :: Software Development :: Code Generators", 25 | "Topic :: System :: Hardware", 26 | "Topic :: Software Development :: Testing", 27 | "License :: OSI Approved :: MIT License", 28 | "Programming Language :: Python :: 3", 29 | "Programming Language :: Python :: 3.7", 30 | "Programming Language :: Python :: 3.8", 31 | "Programming Language :: Python :: 3.9", 32 | "Programming Language :: Python :: 3.10", 33 | "Programming Language :: Python :: 3.11", 34 | "Programming Language :: Python :: 3.12", 35 | ], 36 | keywords="Verilog Testbench Digital-Design", 37 | packages=find_namespace_packages(), 38 | include_package_data=True, 39 | python_requires=">=3.7, <4", 40 | entry_points={ 41 | "console_scripts": [ 42 | "svutRun=svut.svutRun:main", 43 | "svutCreate=svut.svutCreate:main", 44 | ], 45 | }, 46 | project_urls={ 47 | "Bug Reports": "https://github.com/dpretet/svut/issues", 48 | "Say Thanks!": "http://saythanks.io/to/example", 49 | "Source": "https://github.com/dpretet/svut/", 50 | }, 51 | ) 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | # Editing 92 | .vscode 93 | 94 | # Ctags 95 | tags 96 | .tags 97 | 98 | # Testing artifacts 99 | a.out 100 | dump.vcd 101 | test/regression.txt 102 | log 103 | .ccls-cache/ 104 | 105 | example/svut_h.sv 106 | example/*.out 107 | example/*.vcd 108 | example/*.fst 109 | example/build/ 110 | example/log 111 | test/svut_h.sv 112 | test/*.out 113 | test/*.vcd 114 | test/*.fst 115 | test/build/ 116 | test/log 117 | debug 118 | 119 | # Byte-compiled / optimized / DLL files 120 | __pycache__/ 121 | *.py[cod] 122 | *$py.class 123 | 124 | # C extensions 125 | *.so 126 | 127 | # Distribution / packaging 128 | .Python 129 | build/ 130 | develop-eggs/ 131 | dist/ 132 | downloads/ 133 | eggs/ 134 | .eggs/ 135 | lib/ 136 | lib64/ 137 | parts/ 138 | sdist/ 139 | var/ 140 | wheels/ 141 | share/python-wheels/ 142 | *.egg-info/ 143 | .installed.cfg 144 | *.egg 145 | MANIFEST 146 | test/bats 147 | -------------------------------------------------------------------------------- /svut/template.sv: -------------------------------------------------------------------------------- 1 | // Mandatory file to be able to launch SVUT flow 2 | `include "svut_h.sv" 3 | // Specify the module to load or on files.f 4 | `include "${name}.sv" 5 | `timescale 1 ns / 100 ps 6 | 7 | module ${name}_testbench(); 8 | 9 | `SVUT_SETUP 10 | 11 | ${module_inst} 12 | 13 | // To create a clock: 14 | // initial aclk = 0; 15 | // always #2 aclk = !aclk; 16 | 17 | // To dump data for visualization: 18 | // initial begin 19 | // Default wavefile name with VCD format 20 | // $$dumpfile("${name}_testbench.vcd"); 21 | // Or use FST format with -fst argument 22 | // $$dumpfile("${name}_testbench.fst"); 23 | // Dump all the signals of the design 24 | // $$dumpvars(0, ${name}_testbench); 25 | // end 26 | 27 | // Setup time format when printing with $$realtime() 28 | initial $$timeformat(-9, 1, "ns", 8); 29 | 30 | task setup(msg=""); 31 | begin 32 | // setup() runs when a test begins 33 | end 34 | endtask 35 | 36 | task teardown(msg=""); 37 | begin 38 | // teardown() runs when a test ends 39 | end 40 | endtask 41 | 42 | `TEST_SUITE("TESTSUITE_NAME") 43 | 44 | // Available macros:" 45 | // 46 | // - `MSG("message"): Print a raw white message 47 | // - `INFO("message"): Print a blue message with INFO: prefix 48 | // - `SUCCESS("message"): Print a green message if SUCCESS: prefix 49 | // - `WARNING("message"): Print an orange message with WARNING: prefix and increment warning counter 50 | // - `CRITICAL("message"): Print a purple message with CRITICAL: prefix and increment critical counter 51 | // - `FAILURE("message"): Print a red message with FAILURE: prefix and do **not** increment error counter 52 | // - `ERROR("message"): Print a red message with ERROR: prefix and increment error counter 53 | // 54 | // - `FAIL_IF(aSignal): Increment error counter if evaluaton is true 55 | // - `FAIL_IF_NOT(aSignal): Increment error coutner if evaluation is false 56 | // - `FAIL_IF_EQUAL(aSignal, 23): Increment error counter if evaluation is equal 57 | // - `FAIL_IF_NOT_EQUAL(aSignal, 45): Increment error counter if evaluation is not equal 58 | // - `ASSERT(aSignal): Increment error counter if evaluation is not true 59 | // - `ASSERT(aSignal == 0): Increment error counter if evaluation is not true 60 | // 61 | // Available flag: 62 | // 63 | // - `LAST_STATUS: tied to 1 if last macro did experience a failure, else tied to 0 64 | 65 | `UNIT_TEST("TESTCASE_NAME") 66 | 67 | // Describe here the testcase scenario 68 | // 69 | // Because SVUT uses long nested macros, it's possible 70 | // some local variable declaration leads to compilation issue. 71 | // You should declare your variables after the IOs declaration to avoid that. 72 | 73 | `UNIT_TEST_END 74 | 75 | `TEST_SUITE_END 76 | 77 | endmodule 78 | -------------------------------------------------------------------------------- /test/regression.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Global status, the global return code 4 | status=0 5 | 6 | #------------------------------------------------------------------------------ 7 | # Bash compliant way to check a program is in PATH 8 | # https://stackoverflow.com/a/53798785 9 | #------------------------------------------------------------------------------ 10 | function is_bin_in_path { 11 | 12 | builtin type -P "$1" &> /dev/null 13 | } 14 | 15 | 16 | #------------------------------------------------------------------------------ 17 | # Prepare Bats, if not present in PATH, clone it locally and setup in PATH. 18 | # If not present and bats folder is already there, just setup PATH. Else just 19 | # exit gently 20 | #------------------------------------------------------------------------------ 21 | setup_bats() { 22 | 23 | if is_bin_in_path bats ; then 24 | echo 'INFO: Bats in PATH' 25 | return 0 26 | else 27 | echo 'INFO: Bats not in PATH' 28 | [ ! -d "$DIR/bats" ] && git clone https://github.com/bats-core/bats-core.git "$DIR/bats" 29 | export PATH=$DIR/bats/bin:$PATH 30 | fi 31 | 32 | if is_bin_in_path bats ; then 33 | echo "INFO: Finished Bats setup" 34 | return 0 35 | else 36 | echo "ERROR: Failed to clone and setup Bats" 37 | return 1 38 | fi 39 | 40 | echo "" 41 | } 42 | 43 | 44 | #------------------------------------------------------------------------------ 45 | # Clean files possibly still there after a previous execution 46 | #------------------------------------------------------------------------------ 47 | clean() { 48 | 49 | rm -f ./Adder_testbench.sv 50 | rm -f files.f 51 | rm -f sim_main.cpp 52 | } 53 | 54 | #------------------------------------------------------------------------------ 55 | # Bats testsuite runner 56 | #------------------------------------------------------------------------------ 57 | run_bats() { 58 | 59 | echo "INFO: Execute $1" 60 | echo "" 61 | 62 | bats "$DIR/$1.sh" 63 | ret=$? 64 | status=$((status + ret)) 65 | [ "$ret" -ne 0 ] && echo -e "ERROR: $1 failed\n" 66 | return 67 | } 68 | 69 | #------------------------------------------------------------------------------ 70 | # Main function setting up the flow and launch Bats 71 | #------------------------------------------------------------------------------ 72 | main () { 73 | 74 | echo "" 75 | echo "INFO: Start SVUT Regression" 76 | echo "" 77 | 78 | 79 | # Get script's location 80 | export DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 81 | export PATH="$DIR/..":$PATH 82 | 83 | # Remove dirties 84 | clean 85 | 86 | # Check Bats is in PATH, else clone it locally and push it in PATH 87 | setup_bats 88 | 89 | echo "" 90 | echo "INFO: Start Bats execution" 91 | echo "" 92 | 93 | # Execute all the testsuites 94 | run_bats "testsuite_create" 95 | run_bats "testsuite_run" 96 | run_bats "testsuite_run_args" 97 | run_bats "testsuite_examples" 98 | 99 | if [ $status -eq 0 ]; then 100 | echo "INFO: Regression finished successfully. SVUT sounds alive ^^" 101 | exit 0 102 | else 103 | echo "ERROR: Regression failed" 104 | exit 1 105 | fi 106 | } 107 | 108 | main "$@" 109 | -------------------------------------------------------------------------------- /test/Adder_KO_testsuite.sv: -------------------------------------------------------------------------------- 1 | // Mandatory file to be able to launch SVUT flow 2 | `include "svut_h.sv" 3 | 4 | // Specify here the module to load or setup the path in files.f 5 | `include "Adder.v" 6 | 7 | `timescale 1 ns / 1 ps 8 | 9 | module Adder_unit_test_KO; 10 | 11 | `SVUT_SETUP 12 | 13 | parameter WIDTH = 8; 14 | 15 | reg aclk; 16 | reg arstn; 17 | reg clr; 18 | reg inc; 19 | reg [WIDTH-1:0] out; 20 | 21 | Adder 22 | #( 23 | WIDTH 24 | ) 25 | dut 26 | ( 27 | aclk, 28 | arstn, 29 | clr, 30 | inc, 31 | out 32 | ); 33 | 34 | // An example to create a clock 35 | initial aclk = 0; 36 | always #2 aclk <= ~aclk; 37 | 38 | // An example to dump data for visualization 39 | initial $dumpvars(0, Adder_unit_test_KO); 40 | 41 | task setup(msg="Here is the setup function"); 42 | begin 43 | // setup() runs when a test begins 44 | inc = 1'b0; 45 | clr = 1'b0; 46 | arstn = 1'b0; 47 | #100; 48 | arstn = 1'b1; 49 | end 50 | endtask 51 | 52 | task teardown(msg="Here is the teardown function"); 53 | begin 54 | // teardown() runs when a test ends 55 | end 56 | endtask 57 | 58 | `TEST_SUITE("This is my KO Testsuite") 59 | 60 | /* Available macros: 61 | 62 | - `INFO("message"); Print a grey message 63 | - `SUCCESS("message"); Print a green message 64 | - `WARNING("message"); Print an orange message and increment warning counter 65 | - `CRITICAL("message"); Print an pink message and increment critical counter 66 | - `ERROR("message"); Print a red message and increment error counter 67 | - `FAIL_IF(aSignal); Increment error counter if evaluaton is positive 68 | - `FAIL_IF_NOT(aSignal); Increment error coutner if evaluation is false 69 | - `FAIL_IF_EQUAL(aSignal, 23); Increment error counter if evaluation is equal 70 | - `FAIL_IF_NOT_EQUAL(aSignal, 45); Increment error counter if evaluation is not equal 71 | */ 72 | 73 | /* Available flag: 74 | 75 | - `LAST_STATUS: tied to 1 if last macros has been asserted, else tied to 0 76 | */ 77 | 78 | `UNIT_TEST("Macro test") 79 | 80 | `MSG("I print a message for myself\nThis message can span several lines\nif I insert new line"); 81 | 82 | // Basic tests of the main functions. All results are expected KO 83 | `CRITICAL("All tests are expected KO"); 84 | `INFO("Test FAIL_IF_NOT"); 85 | `FAIL_IF_NOT(inc); 86 | @(posedge aclk); 87 | inc = 1'b1; 88 | `INFO("Test FAIL_IF"); 89 | `FAIL_IF(inc); 90 | @(posedge aclk); 91 | inc = 1'b0; 92 | `INFO("Test FAIL_IF_NOT"); 93 | `FAIL_IF_NOT(inc); 94 | `INFO("Test FAIL_IF_EQUAL"); 95 | `FAIL_IF_NOT_EQUAL(out, 8'd0); 96 | `INFO("Test FAIL_IF_NOT_EQUAL"); 97 | `FAIL_IF_EQUAL(out, 8'd1); 98 | `INFO("Test ASSERT"); 99 | `ASSERT(inc === 1'b1); 100 | 101 | `ERROR("Test finished"); 102 | 103 | `UNIT_TEST_END 104 | 105 | `UNIT_TEST("Define check") 106 | 107 | `ifndef MYNODEF 108 | `ERROR("No define found!"); 109 | `endif 110 | 111 | `UNIT_TEST_END 112 | 113 | `TEST_SUITE_END 114 | 115 | endmodule 116 | 117 | -------------------------------------------------------------------------------- /test/Adder_OK_testsuite.sv: -------------------------------------------------------------------------------- 1 | // Mandatory file to be able to launch SVUT flow 2 | `include "svut_h.sv" 3 | 4 | // Specify here the module to load or setup the path in files.f 5 | `include "Adder.v" 6 | 7 | `timescale 1 ns / 1 ps 8 | 9 | module Adder_unit_test_OK; 10 | 11 | `SVUT_SETUP 12 | 13 | parameter WIDTH = 8; 14 | 15 | reg aclk; 16 | reg arstn; 17 | reg clr; 18 | reg inc; 19 | reg [WIDTH-1:0] out; 20 | 21 | Adder 22 | #( 23 | WIDTH 24 | ) 25 | dut 26 | ( 27 | aclk, 28 | arstn, 29 | clr, 30 | inc, 31 | out 32 | ); 33 | 34 | // An example to create a clock 35 | initial aclk = 0; 36 | always #2 aclk <= ~aclk; 37 | 38 | // An example to dump data for visualization 39 | initial $dumpvars(0, Adder_unit_test_OK); 40 | 41 | task setup(msg="Here is the setup function"); 42 | begin 43 | // setup() runs when a test begins 44 | inc = 1'b0; 45 | clr = 1'b0; 46 | arstn = 1'b0; 47 | #100; 48 | arstn = 1'b1; 49 | end 50 | endtask 51 | 52 | task teardown(msg="Here is the teardown function"); 53 | begin 54 | // teardown() runs when a test ends 55 | end 56 | endtask 57 | 58 | `TEST_SUITE("This is my OK Testsuite") 59 | 60 | /* Available macros: 61 | 62 | - `INFO("message"); Print a grey message 63 | - `SUCCESS("message"); Print a green message 64 | - `WARNING("message"); Print an orange message and increment warning counter 65 | - `CRITICAL("message"); Print an pink message and increment critical counter 66 | - `ERROR("message"); Print a red message and increment error counter 67 | - `FAIL_IF(aSignal); Increment error counter if evaluaton is positive 68 | - `FAIL_IF_NOT(aSignal); Increment error coutner if evaluation is false 69 | - `FAIL_IF_EQUAL(aSignal, 23); Increment error counter if evaluation is equal 70 | - `FAIL_IF_NOT_EQUAL(aSignal, 45); Increment error counter if evaluation is not equal 71 | */ 72 | 73 | /* Available flag: 74 | 75 | - `LAST_STATUS: tied to 1 if last macros has been asserted, else tied to 0 76 | */ 77 | 78 | `UNIT_TEST("Macro test") 79 | 80 | `MSG("I print a message for myself"); 81 | `SUCCESS("All tests are expected OK!"); 82 | // Basic tests of the main functions. All results are expected OK 83 | `INFO("Test FAIL_IF"); 84 | `FAIL_IF(inc); 85 | `INFO("Test FAIL_IF_EQUAL"); 86 | `FAIL_IF_EQUAL(out, 8'd18); 87 | `INFO("Test FAIL_IF_NOT_EQUAL"); 88 | `FAIL_IF_NOT_EQUAL(out, 8'd0); 89 | @(posedge aclk); 90 | inc = 1'b1; 91 | `INFO("Test FAIL_IF_NOT"); 92 | `FAIL_IF_NOT(inc); 93 | @(posedge aclk); 94 | inc = 1'b0; 95 | `INFO("Test FAIL_IF"); 96 | `FAIL_IF(inc); 97 | `INFO("Test FAIL_IF_EQUAL"); 98 | `FAIL_IF_EQUAL(out, 8'd0); 99 | `INFO("Test FAIL_IF_NOT_EQUAL"); 100 | `FAIL_IF_NOT_EQUAL(out, 8'd1); 101 | `INFO("Test ASSERT"); 102 | `ASSERT(inc === 1'b0); 103 | 104 | `SUCCESS("Test finished"); 105 | 106 | `UNIT_TEST_END 107 | 108 | `UNIT_TEST("Define check") 109 | 110 | `ifndef MYDEF1 111 | `ERROR("No define 1 found!"); 112 | `endif 113 | 114 | if (`MYDEF1!=5) 115 | `ERROR("MYDEF1 is not equal to 5"); 116 | 117 | `ifndef MYDEF2 118 | `ERROR("No define 2 found!"); 119 | `endif 120 | 121 | `UNIT_TEST_END 122 | 123 | `TEST_SUITE_END 124 | 125 | endmodule 126 | 127 | -------------------------------------------------------------------------------- /example/ffd_testbench.sv: -------------------------------------------------------------------------------- 1 | /// Copyright 2024 The SVUT Authors 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to 5 | /// deal in the Software without restriction, including without limitation the 6 | /// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | /// sell copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | /// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | /// IN THE SOFTWARE. 20 | 21 | // Mandatory file to be able to launch SVUT flow 22 | `include "svut_h.sv" 23 | // Specify the module to load or on files.f 24 | `include "ffd.sv" 25 | 26 | `timescale 1 ns / 1 ps 27 | 28 | module ffd_testbench(); 29 | 30 | `SVUT_SETUP 31 | 32 | logic aclk; 33 | logic arstn; 34 | logic d; 35 | logic q; 36 | 37 | ffd 38 | dut 39 | ( 40 | .aclk (aclk), 41 | .arstn (arstn), 42 | .d (d), 43 | .q (q) 44 | ); 45 | 46 | // An example to create a clock for icarus: 47 | initial aclk = 0; 48 | always #2 aclk <= ~aclk; 49 | 50 | // An example to dump data for visualization 51 | initial begin 52 | $dumpfile("ffd_testbench.vcd"); 53 | $dumpvars(0, ffd_testbench); 54 | end 55 | 56 | task setup(msg=""); 57 | begin 58 | // setup() runs when a test begins 59 | arstn = 1'b0; 60 | d = 1'b0; 61 | #100; 62 | arstn = 1'b1; 63 | end 64 | endtask 65 | 66 | task teardown(msg=""); 67 | begin 68 | // teardown() runs when a test ends 69 | end 70 | endtask 71 | 72 | `TEST_SUITE("FFD Testsuite") 73 | 74 | // Available macros:" 75 | // 76 | // - `INFO("message"): Print a grey message 77 | // - `SUCCESS("message"): Print a green message 78 | // - `WARNING("message"): Print an orange message and increment warning counter 79 | // - `CRITICAL("message"): Print an pink message and increment critical counter 80 | // - `ERROR("message"): Print a red message and increment error counter 81 | // 82 | // - `FAIL_IF(aSignal): Increment error counter if evaluaton is true 83 | // - `FAIL_IF_NOT(aSignal): Increment error coutner if evaluation is false 84 | // - `FAIL_IF_EQUAL(aSignal, 23): Increment error counter if evaluation is equal 85 | // - `FAIL_IF_NOT_EQUAL(aSignal, 45): Increment error counter if evaluation is not equal 86 | // - `ASSERT(aSignal): Increment error counter if evaluation is not true 87 | // - `ASSERT((aSignal == 0)): Increment error counter if evaluation is not true 88 | // 89 | // Available flag: 90 | // 91 | // - `LAST_STATUS: tied to 1 if last macro did experience a failure, else tied to 0 92 | 93 | `UNIT_TEST("Check reset is applied") 94 | 95 | `MSG("I will test if Q output is 0 after reset"); 96 | # 10; 97 | `FAIL_IF(q, "this flip-flop should be zeroed after reset"); 98 | `ASSERT(q===0, "this flip-flop should be zeroed after reset"); 99 | 100 | `UNIT_TEST_END 101 | 102 | `UNIT_TEST("Drive the FFD") 103 | 104 | `MSG("I will test if Q output is 1 after D assertion"); 105 | # 10; 106 | d = 1'b1; 107 | @ (posedge aclk); 108 | @ (posedge aclk); 109 | `FAIL_IF_NOT(q, "this flip-flop should be enabled after reset"); 110 | `ASSERT(q===1, "this flip-flop should be enabled after reset"); 111 | # 10; 112 | 113 | `UNIT_TEST_END 114 | 115 | `TEST_SUITE_END 116 | 117 | endmodule 118 | -------------------------------------------------------------------------------- /svut/svut_h.sv: -------------------------------------------------------------------------------- 1 | /// Copyright 2024 The SVUT Authors 2 | /// 3 | /// Permission is hereby granted, free of charge, to any person obtaining a copy 4 | /// of this software and associated documentation files (the "Software"), to 5 | /// deal in the Software without restriction, including without limitation the 6 | /// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | /// sell copies of the Software, and to permit persons to whom the Software is 8 | /// furnished to do so, subject to the following conditions: 9 | /// 10 | /// The above copyright notice and this permission notice shall be included in 11 | /// all copies or substantial portions of the Software. 12 | /// 13 | /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | /// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | /// IN THE SOFTWARE. 20 | 21 | 22 | `ifndef SVUT_DEFINES 23 | `define SVUT_DEFINES 24 | 25 | /// Define colors for $display 26 | 27 | /// 1 set bold 28 | /// 2 set half-bright (simulated with color on a color display) 29 | /// 4 set underscore (simulated with color on a color display) 30 | /// 5 set blink 31 | /// 7 set reverse video 32 | 33 | `define BLACK "\033[1;30m" 34 | `define RED "\033[1;31m" 35 | `define GREEN "\033[1;32m" 36 | `define BROWN "\033[1;33m" 37 | `define BLUE "\033[1;34m" 38 | `define PINK "\033[1;35m" 39 | `define CYAN "\033[1;36m" 40 | `define WHITE "\033[1;37m" 41 | 42 | `define BG_BLACK "\033[1;40m" 43 | `define BG_RED "\033[1;41m" 44 | `define BG_GREEN "\033[1;42m" 45 | `define BG_BROWN "\033[1;43m" 46 | `define BG_BLUE "\033[1;44m" 47 | `define BG_PINK "\033[1;45m" 48 | `define BG_CYAN "\033[1;46m" 49 | `define BG_WHITE "\033[1;47m" 50 | 51 | `define NC "\033[0m" 52 | 53 | /// Follows a set of ready to use function to print status 54 | /// and information with an appropriate color. 55 | 56 | `define MSG(msg) \ 57 | $display("\033[0;37m%s (@ %0t) (%s:%0d)\033[0m", msg, $realtime, `__FILE__, `__LINE__) 58 | 59 | `define INFO(msg) \ 60 | $display("\033[0;34mINFO: %s (@ %0t) (%s:%0d)\033[0m", msg, $realtime, `__FILE__, `__LINE__) 61 | 62 | `define SUCCESS(msg) \ 63 | $display("\033[0;32mSUCCESS: %s (@ %0t) (%s:%0d)\033[0m", msg, $realtime, `__FILE__, `__LINE__) 64 | 65 | `define WARNING(msg) \ 66 | begin\ 67 | $display("\033[1;33mWARNING: %s (@ %0t) (%s:%0d)\033[0m", msg, $realtime, `__FILE__, `__LINE__);\ 68 | svut_warning += 1;\ 69 | end 70 | 71 | `define CRITICAL(msg) \ 72 | begin\ 73 | $display("\033[1;35mCRITICAL: %s (@ %0t) (%s:%0d)\033[0m", msg, $realtime, `__FILE__, `__LINE__);\ 74 | svut_critical += 1;\ 75 | end 76 | 77 | /// This macro will not increment the error counter while displaying a failure 78 | /// message. 79 | `define FAILURE(msg) \ 80 | begin\ 81 | $display("\033[1;31mFAILURE: %s (@ %0t) (%s:%0d)\033[0m", msg, $realtime, `__FILE__, `__LINE__);\ 82 | end 83 | 84 | `define ERROR(msg)\ 85 | begin\ 86 | $display("\033[1;31mERROR: %s (@ %0t) (%s:%0d)\033[0m", msg, $realtime, `__FILE__, `__LINE__);\ 87 | svut_error += 1;\ 88 | end 89 | 90 | /// SVUT_SETUP is the code portion initializing all the needed 91 | /// variables. To call once before or after the module instance 92 | `define SVUT_SETUP \ 93 | integer svut_test_number = 0; \ 94 | string testnum; \ 95 | integer svut_status = 0; \ 96 | integer svut_warning = 0; \ 97 | integer svut_critical = 0; \ 98 | integer svut_error = 0; \ 99 | integer svut_error_total = 0; \ 100 | integer svut_nb_test = 0; \ 101 | integer svut_nb_test_success = 0; \ 102 | string svut_test_name = ""; \ 103 | string svut_suite_name = ""; \ 104 | string svut_msg = ""; \ 105 | string svut_fail_list = "Failling test(s):"; 106 | 107 | /// LAST_STATUS is a flag asserted if check the last 108 | /// check function failed 109 | `define LAST_STATUS svut_status 110 | 111 | /// This function is shared between assertions to format messages 112 | function automatic string create_msg(input string assertion, message); 113 | if (message != "") begin 114 | create_msg = {message, " (", assertion, ")"}; 115 | end else begin 116 | create_msg = assertion; 117 | end 118 | endfunction 119 | 120 | /// Follows a set of macros to check an expression 121 | /// or a signal. All use the same syntax: 122 | /// - a signal or an expression to evaluate 123 | /// - an optional message to print if case the 124 | /// evaluation fails. 125 | 126 | /// This check fails if expression is not = 0 127 | `define FAIL_IF(exp, message="") \ 128 | svut_status = 0; \ 129 | svut_msg = create_msg("FAIL_IF", message); \ 130 | if (exp) begin \ 131 | `ERROR(svut_msg); \ 132 | svut_status = 1; \ 133 | end 134 | 135 | /// This check fails if expression is not > 0 136 | `define FAIL_IF_NOT(exp, message="") \ 137 | svut_status = 0; \ 138 | svut_msg = create_msg("FAIL_IF_NOT", message); \ 139 | if (!(exp)) begin \ 140 | `ERROR(svut_msg); \ 141 | svut_status = 1; \ 142 | end 143 | 144 | /// This check fails if both input are equal 145 | `define FAIL_IF_EQUAL(a,b, message="") \ 146 | svut_status = 0; \ 147 | svut_msg = create_msg("FAIL_IF_EQUAL", message); \ 148 | if (a === b) begin \ 149 | `ERROR(svut_msg); \ 150 | svut_status = 1; \ 151 | end 152 | 153 | /// This check fails if both input are not equal 154 | `define FAIL_IF_NOT_EQUAL(a,b, message="") \ 155 | svut_status = 0; \ 156 | svut_msg = create_msg("FAIL_IF_NOT_EQUAL", message); \ 157 | if (a !== b) begin \ 158 | `ERROR(svut_msg); \ 159 | svut_status = 1; \ 160 | end 161 | 162 | /// This check fails if expression is not = 0 163 | `define ASSERT(exp, message="") \ 164 | svut_status = 0; \ 165 | svut_msg = create_msg("ASSERT", message); \ 166 | if (!(exp)) begin \ 167 | `ERROR(svut_msg); \ 168 | svut_status = 1; \ 169 | end 170 | 171 | /// This header must be placed to start a test suite execution 172 | `define TEST_SUITE(name="") \ 173 | task run(msg=""); \ 174 | begin \ 175 | svut_suite_name = name; \ 176 | $display("");\ 177 | svut_msg = {"Start testsuite << ", name, " >>"}; \ 178 | `INFO(svut_msg); 179 | 180 | /// This header must be placed to start a test execution 181 | `define UNIT_TEST(name="") \ 182 | begin \ 183 | $display("");\ 184 | $sformat(testnum, "%0d", svut_test_number); \ 185 | svut_msg = {"Starting << ", "Test ", testnum, ": ", name, " >>"}; \ 186 | `INFO(svut_msg); \ 187 | setup(); \ 188 | svut_test_name = name; \ 189 | svut_error = 0; \ 190 | svut_nb_test = svut_nb_test + 1; 191 | 192 | /// This footer must be placed to close a test 193 | `define UNIT_TEST_END \ 194 | teardown(); \ 195 | if (svut_error == 0) begin \ 196 | svut_nb_test_success = svut_nb_test_success + 1; \ 197 | svut_msg = {"<< ", "Test ", testnum, ": ", svut_test_name, " >>", " pass"}; \ 198 | `SUCCESS(svut_msg); \ 199 | end else begin \ 200 | svut_msg = {"<< ", "Test ", testnum, ": ", svut_test_name, " >>", " fail"}; \ 201 | `FAILURE(svut_msg); \ 202 | svut_fail_list = {svut_fail_list, " '", svut_test_name, "'"}; \ 203 | svut_error_total += svut_error; \ 204 | end \ 205 | svut_test_number = svut_test_number + 1; \ 206 | end 207 | 208 | /// This footer must be placed to close a test suite 209 | `define TEST_SUITE_END \ 210 | end \ 211 | endtask \ 212 | initial begin\ 213 | run(); \ 214 | $display("");\ 215 | svut_msg = {"Stop testsuite '", svut_suite_name, "'"}; \ 216 | `INFO(svut_msg); \ 217 | if (svut_error_total > 0) begin \ 218 | $display("\033[1;31m"); \ 219 | $display(svut_fail_list); \ 220 | $display(""); \ 221 | end \ 222 | $display(" \033[1;33m- Warning number: %0d\033[0m", svut_warning); \ 223 | $display(" \033[1;35m- Critical number: %0d\033[0m", svut_critical); \ 224 | $display(" \033[1;31m- Error number: %0d\033[0m", svut_error_total); \ 225 | if (svut_nb_test_success != svut_nb_test) begin \ 226 | $display(" \033[1;31m- STATUS: %0d/%0d test(s) passed\033[0m\n", svut_nb_test_success, svut_nb_test); \ 227 | end else begin \ 228 | $display(" \033[0;32m- STATUS: %0d/%0d test(s) passed\033[0m\n", svut_nb_test_success, svut_nb_test); \ 229 | end \ 230 | $finish(); \ 231 | end 232 | 233 | `endif 234 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SystemVerilog Unit Test (SVUT) 2 | 3 | [![GitHub license](https://img.shields.io/github/license/dpretet/svut)](https://github.com/dpretet/svut/blob/master/LICENSE) 4 | ![Github Actions](https://github.com/dpretet/svut/actions/workflows/ci_ubuntu.yaml/badge.svg) 5 | ![Github Actions](https://github.com/dpretet/svut/actions/workflows/ci_macos.yaml/badge.svg) 6 | [![GitHub issues](https://img.shields.io/github/issues/dpretet/svut)](https://github.com/dpretet/svut/issues) 7 | [![GitHub stars](https://img.shields.io/github/stars/dpretet/svut)](https://github.com/dpretet/svut/stargazers) 8 | [![GitHub forks](https://img.shields.io/github/forks/dpretet/svut)](https://github.com/dpretet/svut/network) 9 | 10 | 11 | ## Introduction 12 | 13 | SVUT is a very simple flow to create a Verilog/SystemVerilog unit test. It is 14 | widely inspired by [SVUnit](http://agilesoc.com/open-source-projects/svunit/), 15 | but it's written in python and run with [Icarus 16 | Verilog](http://iverilog.icarus.com/) or 17 | [Verilator](https://www.veripool.org/verilator/). SVUT follows KISS principle: [Keep It 18 | Simple, Stupid](https://en.wikipedia.org/wiki/KISS_principle). 19 | 20 | Hope it can help you! 21 | 22 | ### How to Install 23 | 24 | #### Pypi 25 | 26 | SVUT is available on [Pypi](https://pypi.org/project/svut/) and can be installed as following: 27 | 28 | ```bash 29 | pip3 install svut 30 | ``` 31 | 32 | #### Git 33 | 34 | Git clone the repository in a path. Set up the SVUT environment variable 35 | and add SVUT to `$PATH`: 36 | 37 | ```bash 38 | export SVUT=$HOME/.svut 39 | git clone https://github.com/dpretet/svut.git $SVUT 40 | export PATH=$SVUT:$PATH 41 | ``` 42 | 43 | SVUT relies on [Icarus Verilog](http://iverilog.icarus.com/) as simulation 44 | back-end. Please install it with your favourite package manager and be sure to 45 | use a version greater or equal to v10.2. SVUT is tested with `v10.2` and cannot 46 | work with lower version `<= v9.x`. 47 | 48 | SVUT can also use [Verilator](https://github.com/verilator/verilator) with a limited support 49 | for the moment. A future release will improve it, with example & tutorial. SVUT is tested with 50 | version `>= v4`. 51 | 52 | 53 | ### How to use it 54 | 55 | To create a unit test of a verilog module, call the command: 56 | 57 | ```bash 58 | svutCreate your_file.v 59 | ``` 60 | 61 | No argument is required. SVUT will create "your_file_testbench.sv" which contains your module 62 | instanciated and a place to write your testcase(s). Some codes are also commented to describe the 63 | different macros and how to create a clock or dump a VCD for 64 | [GTKWave](https://gtkwave.sourceforge.net) or 65 | [Surfer](https://gitlab.com/surfer-project/surfer). A C++ file being the verilator 66 | top level is also generated (`sim_main.cpp`). It can be ignored if you don't use Verilator. 67 | An example to understand how to use can be found [here](https://github.com/dpretet/friscv/tree/master/test/common) 68 | 69 | To run a test, call the command: 70 | 71 | ```bash 72 | svutRun -test your_file_testbench.sv 73 | ``` 74 | 75 | or simply `svutRun` to execute all testbenchs in the current folder. 76 | 77 | ```bash 78 | svutRun 79 | ``` 80 | 81 | SVUT will scan your current folder, search for the files with `_testbench.sv` 82 | suffix and run all tests available. Multiple suffix patterns are 83 | [possible](https://github.com/dpretet/svut/blob/master/svut/svutRun.py#L46). 84 | 85 | svutRun proposes several arguments, most optional: 86 | 87 | - `-test`: specify the testsuite file path or a folder containing tests 88 | - `-f`: pass the fileset description, default is `files.f` 89 | - `-sim`: specify the simulator, `icarus` or `verilator` 90 | - `-main`: specify the C++ main file when using verilator, default is `sim_main.cpp` 91 | - `-define`: pass verilog defines to the tool, like `-define "DEF1=2;DEF2;DEF3=3"` 92 | - `-vpi`: specify a compiled VPI, for instance `-vpi "-M. -mMyVPI"` 93 | - `-dry-run`: print the commands but don't execute them 94 | - `-include`: to pass include path, several can be passed like `-include folder1 folder2` 95 | - `-no-splash`: don't print SVUT splash banner, printed by default 96 | - `-compile-only`: just compile the testbench, don't execute it 97 | - `-run-only`: just execute the testbench, if no executable found, also build it 98 | - `-fst`: dump waveform with FST format. If not specified use VCD format 99 | 100 | All these arguments are common for both the simulators. 101 | 102 | # Tutorial 103 | 104 | Copy/paste this basic FFD model in a file named ffd.v into a new folder: 105 | 106 | ```verilog 107 | `timescale 1 ns / 1 ps 108 | 109 | module ffd 110 | ( 111 | input wire aclk, 112 | input wire arstn, 113 | input wire d, 114 | output reg q 115 | ); 116 | 117 | always @ (posedge aclk or negedge arstn) begin 118 | if (arstn == 1'b0) q <= 1'b0; 119 | else q <= d; 120 | end 121 | 122 | endmodule 123 | ``` 124 | 125 | Then run: 126 | 127 | ```bash 128 | svutCreate ffd.v 129 | ``` 130 | 131 | ffd\_testbench.v has been dropped in the folder from you called svutCreate. It 132 | contains all you need to start populating your testcases. In the header, you 133 | can include directly your DUT file (uncomment): 134 | 135 | ```verilog 136 | `include "ffd.v" 137 | ``` 138 | 139 | or you can store the path to your file into a `files.f` file, automatically 140 | recognized by SVUT. Populate it with the files describing your IP. You can 141 | also specify include folder in this way: 142 | 143 | ```bash 144 | +incdir+$HOME/path/to/include/ 145 | ``` 146 | 147 | Right after the module instance, you can use the example to generate a clock 148 | (to uncomment): 149 | 150 | ```verilog 151 | initial aclk = 0; 152 | always #2 aclk = !aclk; 153 | ``` 154 | 155 | Next line explains how to dump your signals values into a VCD file to open a 156 | waveform in GTKWave (uncomment): 157 | 158 | ```verilog 159 | initial $dumpvars(0, ffd_unit_test); 160 | initial $dumpfile("ffd_testbench.vcd"); 161 | ``` 162 | 163 | Two functions follow, `setup()` and `teardown()`. Use them to configure the 164 | environment of the testcases: 165 | - `setup()` is called before each testcase execution 166 | - `teardown()` is called after each testcase execution 167 | 168 | A testcase is enclosed between two specific defines: 169 | 170 | ```verilog 171 | `UNIT_TEST("TESTNAME") 172 | ... 173 | `UNIT_TEST_END 174 | ``` 175 | 176 | `TESTNAME` is a string which will be displayed when test execution 177 | will start. Then you can use the macros provided to display information, 178 | warning, error and check some signals values. Each error encountered by a 179 | macro increments a globla error counter which determine a testsuite status. 180 | If the error counter is bigger than `0`, the test is considered as failed. 181 | 182 | A testsuite, comprising several `UNIT_TEST`, is declared with another define: 183 | 184 | ```verilog 185 | `TEST_SUITE("SUITENAME") 186 | ... 187 | `TEST_SUITE_END 188 | ``` 189 | 190 | To test the FFD, add the next line into `setup()` to drive the reset and init the 191 | FFD input: 192 | 193 | ```verilog 194 | arstn = 1'b0; 195 | d = 1'b0; 196 | #100; 197 | arstn = 1'b1; 198 | ``` 199 | 200 | and into the testcase: 201 | 202 | ```verilog 203 | `FAIL_IF(q); 204 | ``` 205 | 206 | Here is a basic unit test checking if the FFD output is `0` after reset. Once 207 | called `svutRun` in your shell, you should see something similar: 208 | 209 |

210 | 211 | 212 |

213 | 214 | SVUT relies (optionally) on files.f to declare the fileset and define. Follows an example: 215 | 216 | ``` 217 | ... 218 | +define+MY_DEFINE_SIM1 219 | +define+MY_DEFINE_SIM2=723 220 | ./ffd.sv 221 | +incdir+$HOME/work/mylib 222 | ... 223 | ``` 224 | 225 | The user can also choose to pass define in the command line, common for both the simulator: 226 | 227 | ```bash 228 | svutRun -test my_testbench.sv -define "DEF1=1;DEF2;DEF3=3" 229 | ``` 230 | 231 | SVUT doesn't check possible collision between define passed in command line 232 | and the others defined in `files.f`. Double check that point if unexpected 233 | behavior occurs during testbench. 234 | 235 | Finally, SVUT supports VPI for Icarus. Follow an example to compile and set up 236 | the flow of an hypothetic UART, compiled with iverilog and using a define "PORT": 237 | 238 | ```bash 239 | iverilog-vpi uart.c 240 | svutRun -vpi "-M. -muart" -define "PORT=3333" -t ./my_testbench.sv & 241 | ``` 242 | 243 | Now you know the basics of SVUT. The generated testbench provides prototypes of 244 | available macros. Try them and play around to test SVUT. You can find these 245 | files into the example folder. 246 | 247 | Enjoy! 248 | 249 | 250 | ## License 251 | 252 | Copyright 2024 The SVUT Authors 253 | 254 | Permission is hereby granted, free of charge, to any person obtaining a copy of 255 | this software and associated documentation files (the "Software"), to deal in 256 | the Software without restriction, including without limitation the rights to 257 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 258 | of the Software, and to permit persons to whom the Software is furnished to do 259 | so, subject to the following conditions: 260 | 261 | The above copyright notice and this permission notice shall be included in all 262 | copies or substantial portions of the Software. 263 | 264 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 265 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 266 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 267 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 268 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 269 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 270 | SOFTWARE. imitations under the License. 271 | -------------------------------------------------------------------------------- /svut/svutCreate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2024 The SVUT Authors 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | """ 25 | 26 | # pylint: disable=C0103 27 | 28 | import os 29 | import sys 30 | import re 31 | from string import Template 32 | from pathlib import Path 33 | 34 | SCRIPTDIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) 35 | 36 | def parse_verilog(verilog): 37 | 38 | """ 39 | The next section will parse the user file and extract the parameters 40 | list, the IOs name and width and the module name. Expect Verilog 2005 41 | style ONLY for IOs! 42 | This FSM is a very basic parser and handle simple construction. 43 | TODO: replace it with serious implementation like pyverilog 44 | """ 45 | 46 | print("INFO: Extract information from module to test") 47 | 48 | # List of flags activated during parsing steps. 49 | intoComment = "No" 50 | inlineComment = "Yes" 51 | moduleFound = "No" 52 | parameterFound = "No" 53 | ioFound = "No" 54 | 55 | instance = {"name": "", "io": [], "parameter": []} 56 | 57 | # One by one the loop will detect the different part of the 58 | # module declaration. Steps are: 59 | # 1. Detect block comments in file header 60 | # 2. Detect module name 61 | # 3. Detect parameters 62 | # 4. Detect input/output of the module 63 | # Once detected, store the result for testsuite writing 64 | 65 | for line in verilog: 66 | 67 | # Remove space at beginning and end of line 68 | line = line.strip() 69 | 70 | # Detect comment block in header and avoid to parse them 71 | # A block comment like a license is thus ignored 72 | if line[0:2] == "/*": 73 | intoComment = "Yes" 74 | inlineComment = "No" 75 | elif line[0:2] == "//": 76 | intoComment = "Yes" 77 | inlineComment = "Yes" 78 | elif line[0:2] == "*/" or line[-2:] == "*/": 79 | intoComment = "No" 80 | inlineComment = "No" 81 | 82 | if intoComment == "Yes": 83 | if inlineComment == "Yes": 84 | intoComment = "No" 85 | continue 86 | 87 | # Search for the module name if `module` found, split line with " " and 88 | # get the last part, the name. Expect `module module_name` on the line 89 | if moduleFound == "No": 90 | if "module" in line: 91 | moduleFound = "Yes" 92 | info = line.split(" ") 93 | instance["name"] = info[1] 94 | if instance["name"][-1] == ";": 95 | instance["name"] = instance["name"][:-1] 96 | 97 | # Search for the parameter if present search a line with `parameter`, 98 | # remove comment at the end of line, replace comma with semicolon and 99 | # store the line, ready to be written as a parameter declaration in 100 | # testsuite file 101 | if parameterFound == "No": 102 | if line[0:9] == "parameter": 103 | _line = line.split("//")[0].strip() 104 | _line = _line.replace("\t", " ") 105 | _line = _line.replace(",", "") 106 | if _line[-1] != ";": 107 | _line = _line + ";" 108 | instance["parameter"].append(_line) 109 | 110 | # Search for input or ouput, change comma to semicolon, signed|wire to 111 | # reg and remove IO mode. Remove comment at the end of line 112 | # Ready to be written into testsuitefile. 113 | if ioFound == "No": 114 | if line[0:5] == "input" or line[0:6] == "output": 115 | _line = line.split("//")[0].strip() 116 | if line[0:10] == "input var ": 117 | _line = re.sub("input var", " ", _line) 118 | else: 119 | _line = re.sub("input", " ", _line) 120 | _line = re.sub("output", " ", _line) 121 | _line = re.sub("signed", "logic ", _line) 122 | _line = re.sub("wire", "logic ", _line) 123 | _line = re.sub(r"\sreg\s", "logic ", _line) 124 | _line = re.sub(",", "", _line) 125 | _line = _line + ";" 126 | instance["io"].append(_line.strip()) 127 | 128 | return instance 129 | 130 | 131 | def get_instance(instance): 132 | """ 133 | This functions creates the wires declaration and the module instance 134 | """ 135 | 136 | print("INFO: Prepare the testbench") 137 | 138 | mod_inst = "" 139 | 140 | # Print parameter declaration if present 141 | if instance["parameter"]: 142 | for param in instance["parameter"]: 143 | mod_inst += """ """ + param + "\n" 144 | mod_inst += """\n""" 145 | 146 | # Print input/output declaration if present 147 | if instance["io"]: 148 | for io in instance["io"]: 149 | mod_inst += """ """ + io + "\n" 150 | mod_inst += """\n""" 151 | 152 | # Write the instance 153 | mod_inst += """ """ + instance["name"] + " \n" 154 | 155 | # Print parameter instance if present 156 | if instance["parameter"]: 157 | 158 | mod_inst += """ #(\n""" 159 | 160 | # First get the longest name 161 | maxlen = 0 162 | for _, param in enumerate(instance["parameter"]): 163 | 164 | _param = param.split(" ") 165 | _name = _param[-3] 166 | 167 | if len(_name) > maxlen: 168 | maxlen = len(_name) 169 | 170 | for ix, param in enumerate(instance["parameter"]): 171 | # get left and right side around the equal sign 172 | _param = param.split("=") 173 | # split over space of the let side ('parameter param_name') 174 | _param = _param[-2].split(" ") 175 | # remove empty element in the list 176 | _param = list(filter(None, _param)) 177 | # grab parameter name, always the last element in the list 178 | _name = _param[-1] 179 | 180 | _text = " ." + _name + \ 181 | " " * (maxlen - len(_name)) + \ 182 | " (" + _name + ")" 183 | 184 | mod_inst += _text 185 | if ix == len(instance["parameter"]) - 1: 186 | mod_inst += "\n" 187 | else: 188 | mod_inst += ",\n" 189 | 190 | mod_inst += " )\n" 191 | 192 | mod_inst += """ dut \n (\n""" 193 | 194 | # Print input/output instance if present 195 | if instance["io"]: 196 | 197 | # First get the longest name 198 | maxlen = 0 199 | for _, ios in enumerate(instance["io"]): 200 | _io = ios.split(" ") 201 | _name = _io[-1][:-1] 202 | if len(_name) > maxlen: 203 | maxlen = len(_name) 204 | 205 | for ix, io in enumerate(instance["io"]): 206 | 207 | _io = io.split(" ") 208 | # write until the semicolumn 209 | _name = _io[-1][:-1] 210 | 211 | _text = " ." + _name + \ 212 | " " * (maxlen - len(_name)) + \ 213 | " (" + _name + ")" 214 | 215 | mod_inst += _text 216 | if ix == len(instance["io"]) - 1: 217 | mod_inst += "\n" 218 | else: 219 | mod_inst += ",\n" 220 | 221 | mod_inst += """ );\n""" 222 | 223 | return mod_inst 224 | 225 | def dump_template(file_name, tmpl): 226 | """ 227 | Store the template transformated after substitution 228 | """ 229 | try: 230 | # Store the testbench 231 | with open(file_name, "w", encoding="utf-8") as ofile: 232 | ofile.write(tmpl) 233 | ofile.close() 234 | except OSError: 235 | print("Can't store template") 236 | sys.exit(1) 237 | 238 | return 0 239 | 240 | 241 | def print_recommandation(name): 242 | """ 243 | After the file is written and ready to use, print some 244 | recommandation to setup and call SVUT flow. 245 | """ 246 | 247 | print("") 248 | print(f"INFO: Testbench for {name} has been generated in: {name}_testbench.sv") 249 | print("") 250 | print(" To launch SVUT, don't forget to setup its environment variable. For instance:") 251 | print("") 252 | print(" export SVUT=\"$HOME/.svut\"") 253 | print(" export PATH=$SVUT:$PATH") 254 | print("") 255 | print(" The testbench needs to be tuned to generate clock, reset and test scenarios") 256 | print("") 257 | print(" You can setup your fileset in files.f") 258 | print("") 259 | print(" Then call SVUT with:") 260 | print(" svutRun -test my_testbench.sv -define \"DEF1=1;DEF2;DEF3=3\"") 261 | print("") 262 | print(" Further information can be found in Github:") 263 | print(" https://github.com/dpretet/svut#tutorial") 264 | print("") 265 | 266 | 267 | def main(): 268 | # Handle the input arguments. A file must be passed 269 | # and exists in file system 270 | FILE_NAME = "" 271 | 272 | if len(sys.argv) > 1: 273 | FILE_NAME = sys.argv[1] 274 | else: 275 | print("ERROR: please specify a file to parse") 276 | sys.exit(1) 277 | 278 | print("INFO: Start to generate the testbench") 279 | 280 | # First extract information from the module to test 281 | try: 282 | with open(FILE_NAME, "r", encoding="utf-8") as verilog_module: 283 | verilog_info = parse_verilog(verilog_module) 284 | except OSError: 285 | print(f"ERROR: Can't find file {FILE_NAME} to load...") 286 | sys.exit(1) 287 | 288 | # Put in shape the module instance and the wire declarations 289 | module_inst = get_instance(verilog_info) 290 | # Setup the data to substitute in the template 291 | tmpl_data = dict(name=verilog_info["name"], module_inst=module_inst) 292 | 293 | # Load the system verilog template and substitute 294 | tmpl = Path(SCRIPTDIR+"/template.sv").read_text() 295 | tmpl = Template(tmpl).substitute(tmpl_data) 296 | dump_template(verilog_info["name"] + "_testbench.sv", tmpl) 297 | 298 | # Load the cpp template and substitute 299 | tmpl = Path(SCRIPTDIR+"/template.cpp").read_text() 300 | tmpl = Template(tmpl).substitute(tmpl_data) 301 | dump_template("sim_main.cpp", tmpl) 302 | 303 | # Print recommandation to users before exiting 304 | print_recommandation(verilog_info["name"]) 305 | 306 | # Create a files.f if doesn't exist, else preserve the existing 307 | os.system("touch files.f") 308 | 309 | sys.exit(0) 310 | 311 | if __name__ == '__main__': 312 | main() 313 | 314 | -------------------------------------------------------------------------------- /svut/svutRun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright 2024 The SVUT Authors 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | """ 25 | 26 | # pylint: disable=W0621 27 | 28 | import os 29 | import sys 30 | import argparse 31 | import filecmp 32 | import subprocess 33 | import shutil 34 | import datetime 35 | from timeit import default_timer as timer 36 | from datetime import timedelta 37 | from pathlib import Path, PosixPath 38 | 39 | SCRIPTDIR = os.path.abspath(os.path.dirname(__file__)) 40 | 41 | def check_arguments(args): 42 | """ 43 | Verify the arguments are correctly setup 44 | """ 45 | 46 | if "iverilog" in args.simulator or "icarus" in args.simulator: 47 | print_event("Run with Icarus Verilog") 48 | elif "verilator" in args.simulator: 49 | print_event("Run with Verilator") 50 | else: 51 | print_event("ERROR: Simulator not supported") 52 | sys.exit(1) 53 | 54 | if not args.test: 55 | print_event("ERROR: No testcase or path to testcases passed") 56 | sys.exit(1) 57 | 58 | for path in args.test: 59 | error = False 60 | if not path.exists(): 61 | error = True 62 | print_event("ERROR: %s does not exist" % path) 63 | 64 | if error: 65 | sys.exit() 66 | 67 | if args.compile_only and args.run_only: 68 | print("ERROR: Both compile-only and run-only are used") 69 | sys.exit(1) 70 | 71 | if (args.compile_only or args.run_only) and args.test=="all": 72 | print_event("ERROR: compile-only or run-only can't be used with multiple testbenchs") 73 | sys.exit(1) 74 | 75 | return 0 76 | 77 | 78 | def check_tb_extension(test : PosixPath): 79 | """ 80 | Check the extension to be sure it can be run 81 | """ 82 | if test.suffix not in [".v",".sv"]: 83 | print("ERROR: Failed to find supported extension. Must use either *.v or *.sv") 84 | sys.exit(1) 85 | 86 | 87 | def copy_svut_h(): 88 | """ 89 | First copy svut_h.sv macro in the user folder if not present or different 90 | """ 91 | 92 | # Resolve first the real place h file is located 93 | # The place is different if the python is directly call or if 94 | # using the symlink 95 | org_hfile = SCRIPTDIR + "/svut/svut_h.sv" 96 | 97 | if not os.path.isfile(org_hfile): 98 | org_hfile = SCRIPTDIR + "/svut_h.sv" 99 | 100 | curr_hfile = os.getcwd() + "/svut_h.sv" 101 | 102 | if (not os.path.isfile(curr_hfile)) or\ 103 | (not filecmp.cmp(curr_hfile, org_hfile)): 104 | print("INFO: Copy up-to-date version of svut_h.sv") 105 | os.system("cp " + org_hfile + " " + os.getcwd()) 106 | 107 | return 0 108 | 109 | 110 | def find_unit_tests(test_dir : PosixPath): 111 | """ 112 | Parse all unit test files of the current folder 113 | and return a list of available tests 114 | """ 115 | 116 | supported_prefix = ["tb_", "ts_", "testbench_", "testsuite_", "unit_test_"] 117 | supported_suffix = ["_unit_test", "_testbench","_testsuite", "_tb", "_ts"] 118 | files = [] 119 | 120 | # Parse the current folder 121 | for path in Path(test_dir).iterdir(): 122 | # Check only the files 123 | if not path.is_file(): 124 | continue 125 | 126 | # Files not ending with .sv or .v are skipped 127 | if path.suffix not in [".sv",".v"]: 128 | continue 129 | 130 | if path.stem.startswith(tuple(supported_prefix)): 131 | files.append(path) 132 | 133 | if path.stem.endswith(tuple(supported_suffix)): 134 | files.append(path) 135 | 136 | # Remove duplicated file if contains both prefix and suffix 137 | files = list(set(files)) 138 | 139 | if not files: 140 | print("ERROR: Can't find tests to run") 141 | sys.exit(1) 142 | 143 | return files 144 | 145 | 146 | def print_banner(tag): 147 | """ 148 | A banner printed when the flow starts 149 | """ 150 | print() 151 | print(""" ______ ____ ________""") 152 | print(""" / ___/ | / / / / /_ __/""") 153 | print(""" \\__ \\| | / / / / / / / """) 154 | print(""" ___/ /| |/ / /_/ / / / """) 155 | print(""" /____/ |___/\\____/ /_/""") 156 | print() 157 | print(f" {tag}") 158 | print() 159 | 160 | return 0 161 | 162 | def helper(tag): 163 | """ 164 | Help menu 165 | """ 166 | 167 | print_banner(tag) 168 | print(" https://github.com/dpretet/svut") 169 | print() 170 | 171 | return 0 172 | 173 | 174 | def get_defines(defines): 175 | """ 176 | Return a string with the list of defines ready to drop in icarus 177 | """ 178 | simdefs = "" 179 | 180 | if not defines: 181 | return simdefs 182 | 183 | defs = defines.split(';') 184 | 185 | for _def in defs: 186 | if _def: 187 | simdefs += "-D" + _def + " " 188 | 189 | return simdefs 190 | 191 | 192 | def create_iverilog(args, test): 193 | """ 194 | Create the Icarus Verilog command to launch the simulation 195 | """ 196 | 197 | cmds = [] 198 | 199 | testname = os.path.basename(test).split(".")[0] 200 | 201 | if not os.path.isfile("svut.out"): 202 | print_event("Testbench executable not found. Will build it") 203 | args.run_only = False 204 | 205 | # Build testbench executable 206 | if not args.run_only: 207 | 208 | cmd = "iverilog -g2012 -Wall -o svut.out " 209 | 210 | if args.define: 211 | cmd += get_defines(args.define) 212 | 213 | if args.dotfile: 214 | 215 | dotfiles = "" 216 | 217 | for dot in args.dotfile: 218 | if os.path.isfile(dot): 219 | dotfiles += dot + " " 220 | 221 | if dotfiles: 222 | cmd += "-f " + dotfiles + " " 223 | 224 | if args.include: 225 | incs = " ".join(args.include) 226 | cmd += "-I " + incs + " " 227 | 228 | cmd += test + " " 229 | cmds.append(cmd) 230 | 231 | # Execute testbench 232 | if not args.compile_only: 233 | 234 | cmd = "vvp " 235 | if args.vpi: 236 | cmd += args.vpi + " " 237 | 238 | cmd += "svut.out " 239 | 240 | if args.fst: 241 | cmd += "-fst " 242 | 243 | cmds.append(cmd) 244 | 245 | return cmds 246 | 247 | 248 | def create_verilator(args, test): 249 | """ 250 | Create the Verilator command to launch the simulation 251 | """ 252 | 253 | testname = os.path.basename(test).split(".")[0] 254 | 255 | cmds = [] 256 | 257 | if not os.path.isfile("build/V" + testname + ".mk"): 258 | print_event("Testbench executable not found. Will build it") 259 | args.run_only = False 260 | 261 | 262 | # Build testbench executable 263 | if not args.run_only: 264 | 265 | cmd = """verilator -Wall --Mdir build +1800-2012ext+sv """ 266 | cmd += """+1800-2005ext+v -Wno-STMTDLY -Wno-UNUSED -Wno-UNDRIVEN -Wno-PINCONNECTEMPTY """ 267 | cmd += """-Wpedantic -Wno-VARHIDDEN -Wno-lint """ 268 | 269 | if args.define: 270 | cmd += get_defines(args.define) 271 | 272 | if args.fst: 273 | cmd += "--trace-fst " 274 | else: 275 | cmd += "--trace " 276 | 277 | if args.dotfile: 278 | 279 | dotfiles = "" 280 | 281 | for dot in args.dotfile: 282 | if os.path.isfile(dot): 283 | dotfiles += dot + " " 284 | 285 | if dotfiles: 286 | cmd += "-f " + dotfiles + " " 287 | 288 | if args.include: 289 | for inc in args.include: 290 | cmd += "+incdir+" + inc + " " 291 | 292 | cmd += "-cc --exe --build -j --top-module " + testname + " " 293 | cmd += test + " " + args.main 294 | cmds.append(cmd) 295 | 296 | # Execution command 297 | if not args.compile_only: 298 | cmd = "build/V" + testname 299 | cmds.append(cmd) 300 | 301 | return cmds 302 | 303 | 304 | def print_event(event): 305 | """ 306 | Print an event during SVUT execution 307 | TODO: manage severity/verbosity level 308 | """ 309 | 310 | time = datetime.datetime.now().time().strftime('%H:%M:%S') 311 | 312 | print("SVUT (@ " + time + ") " + event, flush=True) 313 | print("") 314 | 315 | return 0 316 | 317 | 318 | def get_git_tag(): 319 | """ 320 | Return current SVUT version 321 | """ 322 | 323 | # Handles an environment without Git installed 324 | git_path = shutil.which("git") 325 | if git_path is None: 326 | return "" 327 | 328 | curr_path = os.getcwd() 329 | os.chdir(SCRIPTDIR) 330 | 331 | try: 332 | git_tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"]) 333 | git_tag = git_tag.strip().decode('ascii') 334 | except subprocess.CalledProcessError as err: 335 | print("WARNING: Can't get last git tag. Will return v0.0.0") 336 | git_tag = "v0.0.0" 337 | 338 | os.chdir(curr_path) 339 | return git_tag 340 | 341 | def get_test_dir(input: list) -> PosixPath: 342 | if len(input) == 1 and input[0].is_dir(): 343 | return input[0] 344 | 345 | return None 346 | 347 | 348 | def main(): 349 | """ 350 | Main function 351 | """ 352 | 353 | parser = argparse.ArgumentParser(description='SystemVerilog Unit Test Flow') 354 | 355 | # SVUT options 356 | 357 | parser.add_argument('-sim', dest='simulator', type=str, default="icarus", 358 | help='The simulator to use, icarus or verilator.') 359 | 360 | parser.add_argument('-test', dest='test', type=str, default=[os.getcwd()], nargs="*", 361 | help='Unit test to run. A file,list of files or path to test files') 362 | 363 | parser.add_argument('-no-splash', dest='splash', default=False, action='store_true', 364 | help='Don\'t print the banner when executing') 365 | 366 | parser.add_argument('-version', dest='version', action='store_true', 367 | default="", help='Print version menu') 368 | 369 | # Simulator options 370 | 371 | parser.add_argument('-f', dest='dotfile', type=str, default=["files.f"], nargs="*", 372 | help="A dot file (*.f) with incdir, define and file path") 373 | 374 | parser.add_argument('-include', dest='include', type=str, nargs="*", 375 | default="", help='Specify an include folder; can be used along a dotfile') 376 | 377 | parser.add_argument('-main', dest='main', type=str, default="sim_main.cpp", 378 | help='Verilator main cpp file, like sim_main.cpp') 379 | 380 | parser.add_argument('-define', dest='define', type=str, default="", 381 | help='''A list of define separated by ; \ 382 | ex: -define "DEF1=2;DEF2;DEF3=3"''') 383 | 384 | parser.add_argument('-vpi', dest='vpi', type=str, default="", 385 | help='''A string of arguments passed as is to Icarus (only), separated \ 386 | by a space ex: -vpi "-M. -mMyVPI"''') 387 | 388 | parser.add_argument('-fst', dest='fst', default=False, action='store_true', 389 | help="Choose FST format for waveform. If not used, select VCD") 390 | 391 | # SVUT Execution options 392 | 393 | parser.add_argument('-run-only', dest='run_only', default=False, action='store_true', 394 | help='Only run existing executable but build it if not present') 395 | 396 | parser.add_argument('-compile-only', dest='compile_only', default=False, action='store_true', 397 | help='Only prepare the testbench executable') 398 | 399 | parser.add_argument('-dry-run', dest='dry', default=False, action='store_true', 400 | help='Just print the command, don\'t execute') 401 | 402 | 403 | args = parser.parse_args() 404 | args.test = [Path(x) for x in args.test] 405 | 406 | git_tag = get_git_tag() 407 | 408 | if args.version: 409 | helper(git_tag) 410 | sys.exit(0) 411 | 412 | if not args.splash: 413 | print_banner(git_tag) 414 | 415 | # Lower the simulator name to ease checking 416 | args.simulator = args.simulator.lower() 417 | 418 | # Check arguments consistency 419 | check_arguments(args) 420 | 421 | # If the user specifies a directory to look for files, test_dir will point to this directory 422 | # If the user does not specify any test files or directy at all, test_dir will point to the CWD 423 | # If the user specifies one or more test files, test_dir is none and no searching will take place 424 | test_dir = get_test_dir(args.test) 425 | if test_dir: 426 | args.test = find_unit_tests(test_dir) 427 | 428 | # Copy svut_h.sv if not present or not up-to-date 429 | copy_svut_h() 430 | 431 | cmdret = 0 432 | 433 | start = timer() 434 | 435 | test : PosixPath 436 | for test in args.test: 437 | 438 | check_tb_extension(test) 439 | 440 | if "iverilog" in args.simulator or "icarus" in args.simulator: 441 | cmds = create_iverilog(args, str(test)) 442 | 443 | elif "verilator" in args.simulator: 444 | cmds = create_verilator(args, str(test)) 445 | 446 | print_event("Start " + test.name) 447 | 448 | # Execute commands one by one 449 | for cmd in cmds: 450 | 451 | print_event(cmd) 452 | 453 | if not args.dry: 454 | if os.system(cmd): 455 | cmdret += 1 456 | print("ERROR: Command failed: " + cmd) 457 | break 458 | 459 | print_event("Stop " + test.name) 460 | 461 | end = timer() 462 | print_event("Elapsed time: " + str(timedelta(seconds=end-start))) 463 | print() 464 | 465 | sys.exit(cmdret) 466 | 467 | if __name__ == '__main__': 468 | main() 469 | --------------------------------------------------------------------------------