├── .editorconfig ├── .github ├── .submodules_generated ├── docker │ ├── Dockerfile │ ├── docker-compose.yml │ └── packages.txt ├── restore_submodules.sh ├── save_submodules.sh └── workflows │ ├── build.yml │ ├── license.yml │ └── lint.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json └── tasks.json ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── deps └── Makefile ├── include └── svase │ ├── design.h │ ├── diag.h │ ├── postproc.h │ ├── preproc.h │ ├── rewrite.h │ └── util.h ├── scripts ├── gen_version.sh ├── run_test.sh └── svase.env ├── src ├── design.cpp ├── diag.cpp ├── driver.cpp └── rewrite.cpp └── test ├── Makefile ├── README.md ├── assign └── assign.sv ├── cheshire.mk ├── cva6.mk ├── idma.mk ├── param ├── param-in-gen.sv ├── param-instance.sv └── port-to-module.sv └── snitch.mk /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor config file: https://editorconfig.org/ 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.{h,cpp}] 12 | indent_size = 2 13 | max_line_length = 80 14 | 15 | [{Makefile,*.mk}] 16 | indent_style = tab 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.github/.submodules_generated: -------------------------------------------------------------------------------- 1 | [submodule "deps/fmt"] 2 | path = deps/fmt 3 | hash = d2c47c0df23d95e3d7c5def06e5f67f32eb7ffc7 4 | url = https://github.com/fmtlib/fmt.git 5 | [submodule "deps/cxxopts"] 6 | path = deps/cxxopts 7 | hash = 2e3c6991d33811878ebcc0839d3815850d129b3a 8 | url = https://github.com/jarro2783/cxxopts 9 | [submodule "deps/slang"] 10 | path = deps/slang 11 | hash = fd8849cc8d37cf3240ce8aa0d8bbf6d69c8da840 12 | url = https://github.com/MikePopoloski/slang.git 13 | -------------------------------------------------------------------------------- /.github/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | # Authors: 6 | # - Philippe Sauter 7 | # 8 | # syntax=docker/dockerfile:1 9 | # slang requires very recent libboost and clang versions so doing it on alpine is fast 10 | # also we want to link against musl, see https://build-your-own.org/blog/20221229_alpine/ 11 | ARG BASE_IMG=alpine:3.19 12 | 13 | FROM ${BASE_IMG} AS builder 14 | 15 | # install packages 16 | COPY packages.txt /packages.txt 17 | RUN apk add --no-cache $(cat /packages.txt) 18 | 19 | RUN ln -sf /usr/bin/clang++-16 /usr/bin/c++ \ 20 | && c++ --version 21 | 22 | # should not be necessary for github-workflows build but is for local 23 | RUN git config --global --add safe.directory /svase 24 | 25 | WORKDIR /svase 26 | -------------------------------------------------------------------------------- /.github/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Philippe Sauter 6 | 7 | version: '3' 8 | services: 9 | svase-builder: 10 | image: svase-builder 11 | command: sh -c 'make release -j' 12 | user: "${UID}:${GID}" 13 | volumes: 14 | - "${REPO_PATH}:/svase" 15 | -------------------------------------------------------------------------------- /.github/docker/packages.txt: -------------------------------------------------------------------------------- 1 | coreutils 2 | git 3 | make 4 | cmake 5 | clang16 6 | clang16-dev 7 | boost-dev=1.82.0-r3 8 | -------------------------------------------------------------------------------- /.github/restore_submodules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 3 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # Author: Philippe Sauter 7 | 8 | # This exists to make Githubs zip-download actually somewhat usable 9 | # It restores the submodules in a fresh git-tree 10 | 11 | input=".github/.submodules_generated" 12 | 13 | path="" 14 | hash="" 15 | url="" 16 | 17 | cat $input | while IFS= read -r line; do 18 | if [[ "$line" =~ ^\[submodule\ (.*)\] ]]; then 19 | # new submodule section, clear info from previous 20 | submodule_name="${BASH_REMATCH[1]}" 21 | path="" 22 | hash="" 23 | url="" 24 | elif [[ "$line" =~ "path ="\ (.*) ]]; then 25 | path="${BASH_REMATCH[1]}" 26 | elif [[ "$line" =~ "hash ="\ (.*) ]]; then 27 | hash="${BASH_REMATCH[1]}" 28 | elif [[ "$line" =~ "url ="\ (.*) ]]; then 29 | url="${BASH_REMATCH[1]}" 30 | fi 31 | 32 | # Do we have all details? 33 | if [[ -n "$path" && -n "$hash" && -n "$url" ]]; then 34 | echo "Restoring submodule $submodule_name" 35 | rm -rf "$path" 36 | git submodule add "$url" "$path" 37 | git -C "$path" checkout "$hash" 38 | path="" 39 | hash="" 40 | url="" 41 | fi 42 | done 43 | -------------------------------------------------------------------------------- /.github/save_submodules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 3 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # Author: Philippe Sauter 7 | 8 | # This exists to make Githubs zip-download actually somewhat usable 9 | # The hashes of the used submodule commits are not stored in .gitmodule 10 | # but rather in the git-tree itself. 11 | # This appends the commit to each submodule in .gitmodule 12 | 13 | target=".github/.submodules_generated" 14 | cat /dev/null > $target 15 | 16 | regexp="[[:space:]]*path[[:space:]]*=[[:space:]]*(.*)" 17 | 18 | # Read .gitmodules lines 19 | cat .gitmodules | while IFS= read -r line; do 20 | echo "$line" >> $target 21 | # search for the 'path' keys (each submodule must have one) 22 | if [[ $line =~ $regexp ]]; then 23 | submodule_path=${BASH_REMATCH[1]} 24 | submodule_hash=$(git ls-tree HEAD "$submodule_path" | awk '{print $3}') 25 | # Update the submodule section with the commit hash 26 | echo -e "\thash = $submodule_hash" >> $target 27 | fi 28 | done 29 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Philippe Sauter 6 | 7 | name: Build 8 | 9 | on: 10 | push: 11 | workflow_dispatch: 12 | 13 | jobs: 14 | save-submodule-hashes: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Check if .gitmodules exists 21 | run: | 22 | if [ -e .gitmodules ]; then 23 | echo "Submodules found..." 24 | else 25 | echo "No submodules, this should not be run" 26 | exit 1 27 | fi 28 | 29 | - name: Generate File 30 | run: | 31 | .github/save_submodules.sh 32 | 33 | # bot details: https://github.com/orgs/community/discussions/26560#discussioncomment-3531273 34 | # main is protected so we can't push there 35 | - name: Commit and Push Changes 36 | if: success() 37 | run: | 38 | current_branch=$(git rev-parse --abbrev-ref HEAD) 39 | if [ "$current_branch" != "main" ]; then 40 | if git diff --exit-code .github/.submodules_generated; then 41 | echo "No differences for submodules found. Exiting gracefully." 42 | exit 0 43 | else 44 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 45 | git config user.name "github-actions[bot]" 46 | git add .github/.submodules_generated 47 | git commit -m "ci: update submodule commit hashes" 48 | git push 49 | fi 50 | fi 51 | 52 | build: 53 | runs-on: ubuntu-latest 54 | env: 55 | BUILD_TYPE: Release 56 | needs: save-submodule-hashes 57 | strategy: 58 | matrix: 59 | os: ['alpine:3.19'] 60 | 61 | steps: 62 | - name: Checkout code 63 | uses: actions/checkout@v4 64 | with: 65 | submodules: 'recursive' 66 | 67 | - name: Restore dependencies from cache 68 | uses: actions/cache@v4 69 | with: 70 | path: | 71 | deps 72 | key: | 73 | ${{matrix.os}}-deps-all-${{ hashFiles('.github/.submodules_generated') }} 74 | restore-keys: | 75 | ${{matrix.os}}-deps-all- 76 | 77 | - name: Build OS 78 | shell: bash 79 | run: | 80 | export BASE_IMG=${{matrix.os}} 81 | docker build .github/docker -t svase-builder 82 | 83 | - name: Build SVase 84 | shell: bash 85 | run: | 86 | export GID=$(id -g) 87 | export REPO_PATH=${{github.workspace}}/ 88 | export CMAKE_BUILD_PORTABLE=1 89 | docker compose -f .github/docker/docker-compose.yml up 90 | 91 | - name: Cache dependencies 92 | uses: actions/cache@v4 93 | with: 94 | path: | 95 | deps 96 | key: | 97 | ${{matrix.os}}-deps-all-${{ hashFiles('.github/.submodules_generated') }} 98 | 99 | - name: Upload 100 | uses: actions/upload-artifact@v4 101 | with: 102 | name: svase-linux_${{github.run_number}} 103 | path: ${{github.workspace}}/build/svase 104 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Jannis Schönleber 6 | 7 | name: lint-license 8 | 9 | on: [push, pull_request, workflow_dispatch] 10 | 11 | jobs: 12 | lint-license: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: lint license 16 | uses: pulp-platform/pulp-actions/lint-license@v2 17 | with: 18 | license: | 19 | Copyright \(c\) (\d{4}(-\d{4})?\s)?(ETH Zurich and University of Bologna|lowRISC contributors). 20 | (Solderpad Hardware License, Version 0.51|Licensed under the Apache License, Version 2.0), see LICENSE for details. 21 | SPDX-License-Identifier: (SHL-0.51|Apache-2.0) 22 | exclude_paths: | 23 | deps 24 | include/version.h 25 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Jannis Schönleber 6 | 7 | name: lint 8 | 9 | on: [ push, pull_request, workflow_dispatch ] 10 | 11 | jobs: 12 | lint-cxx: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Run Clang-format 18 | uses: DoozyX/clang-format-lint-action@v0.14 19 | with: 20 | extensions: "c,h,cpp" 21 | clangFormatVersion: 12 22 | style: LLVM 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.idea 2 | build/ 3 | deps/**/build 4 | deps/install 5 | splitted_output/ 6 | test/debug 7 | include/version.h -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/fmt"] 2 | path = deps/fmt 3 | url = https://github.com/fmtlib/fmt.git 4 | [submodule "deps/cxxopts"] 5 | path = deps/cxxopts 6 | url = https://github.com/jarro2783/cxxopts 7 | [submodule "deps/slang"] 8 | path = deps/slang 9 | url = https://github.com/MikePopoloski/slang.git 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "ParameterRewriter - port to module", 6 | "type": "cppdbg", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/build/svase", 9 | "cwd": "${workspaceFolder}/test/param", 10 | "args": [ 11 | "test", 12 | "../debug/port-to-module_out.sv", 13 | "port-to-module.sv" 14 | ], 15 | "preLaunchTask": "createDebugDirectory" 16 | }, 17 | { 18 | "name": "ParameterRewriter - param instance", 19 | "type": "cppdbg", 20 | "request": "launch", 21 | "program": "${workspaceFolder}/build/svase", 22 | "cwd": "${workspaceFolder}/test/param", 23 | "args": [ 24 | "test", 25 | "../debug/param-instance_out.sv", 26 | "param-instance.sv" 27 | ], 28 | "preLaunchTask": "createDebugDirectory" 29 | }, 30 | { 31 | "name": "ParameterRewriter - generate-blocks", 32 | "type": "cppdbg", 33 | "request": "launch", 34 | "program": "${workspaceFolder}/build/svase", 35 | "cwd": "${workspaceFolder}/test/param", 36 | "args": [ 37 | "test", 38 | "../debug/param-in-gen_out.sv", 39 | "param-in-gen.sv" 40 | ], 41 | "preLaunchTask": "createDebugDirectory" 42 | }, 43 | { 44 | "name": "AssignmentRewriter - assignments", 45 | "type": "cppdbg", 46 | "request": "launch", 47 | "program": "${workspaceFolder}/build/svase", 48 | "cwd": "${workspaceFolder}/test/assign", 49 | "args": [ 50 | "test", 51 | "../debug/assign_out.sv", 52 | "assign.sv" 53 | ], 54 | "preLaunchTask": "createDebugDirectory" 55 | } 56 | ], 57 | "cppdbg.setupCommands": [ 58 | { 59 | "description": "Enable pretty-printing for gdb", 60 | "text": "-enable-pretty-printing", 61 | "ignoreFailures": true 62 | }, 63 | { 64 | "description": "Set Disassembly Flavor to Intel", 65 | "text": "-gdb-set disassembly-flavor intel", 66 | "ignoreFailures": true 67 | } 68 | ] 69 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "createDebugDirectory", 6 | "type": "shell", 7 | "command": "mkdir", 8 | "args": [ 9 | "-p", 10 | "${workspaceFolder}/test/debug" 11 | ] 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(svase) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | option(CMAKE_BUILD_SHARED_LIBS "Build shared libraries" OFF) 7 | option(CMAKE_BUILD_PORTABLE "Build fully portable (no shared libc), not recommended with glibc" OFF) 8 | # used on alpine builder, see: https://build-your-own.org/blog/20221229_alpine/ 9 | 10 | # Handle local prefix (TODO: parameterize) 11 | set(DEP_INSTALL_DIR deps/install) 12 | set(CMAKE_PREFIX_PATH ${DEP_INSTALL_DIR}) 13 | link_directories(${DEP_INSTALL_DIR}/lib64) 14 | link_directories(${DEP_INSTALL_DIR}/lib) 15 | include_directories(${DEP_INSTALL_DIR}/include) 16 | 17 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror") 18 | set(CMAKE_BUILD_TYPE Debug) 19 | 20 | # Dependencies 21 | find_package(slang REQUIRED) 22 | find_package(fmt REQUIRED) 23 | find_package(cxxopts REQUIRED) 24 | 25 | # Build and link 26 | add_executable(svase 27 | src/design.cpp 28 | src/rewrite.cpp 29 | src/driver.cpp 30 | src/diag.cpp) 31 | target_include_directories(svase PRIVATE include) 32 | 33 | if (CMAKE_BUILD_SHARED_LIBS) 34 | target_link_libraries(svase PRIVATE svlang fmt) 35 | else() 36 | if (CMAKE_BUILD_PORTABLE) 37 | target_link_libraries(svase PRIVATE svlang fmt -static) 38 | else() 39 | target_link_libraries(svase PRIVATE svlang fmt -static-libgcc -static-libstdc++) 40 | endif() 41 | endif() 42 | 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Philippe Sauter 6 | 7 | BUILD_DIR ?= build 8 | BUILD_TYPE ?= Debug 9 | DEPS := deps/fmt/build deps/slang/build deps/cxxopts/build 10 | 11 | ## build svase in debug mode (default) 12 | all: debug 13 | 14 | ## calls build in release mode 15 | release: $(DEPS) 16 | @$(MAKE) build BUILD_TYPE=Release 17 | 18 | ## calls build with debug mode 19 | debug: $(DEPS) 20 | @$(MAKE) build BUILD_TYPE=Debug 21 | 22 | build: $(DEPS) 23 | scripts/gen_version.sh > include/version.h 24 | @rm -rf $(BUILD_DIR) 25 | @mkdir -p $(BUILD_DIR) 26 | cmake -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) 27 | cmake --build $(BUILD_DIR) 28 | 29 | ## install each dependency 30 | deps/%/build: .git 31 | git submodule update --init deps/$* 32 | @echo "Installing $*..." 33 | @$(MAKE) -C deps/ install_$* 34 | 35 | # if downloaded as zip, the submodules need to be restored 36 | .git: 37 | @if [ ! -d ".git" ]; then \ 38 | git init; \ 39 | .github/restore_submodules.sh; \ 40 | fi 41 | 42 | ## format code to match linter 43 | format: 44 | clang-format -style=LLVM -i src/*.cpp include/svase/*.h 45 | 46 | ## remove svase-build and dependencies 47 | clean: 48 | @rm -rf build 49 | @rm -rf $(DEPS) 50 | #rm -rf deps/install 51 | 52 | 53 | .PHONY: all release debug build format clean help 54 | help: Makefile 55 | @printf "Available targets:\n\n" 56 | @awk '/^[a-zA-Z\-_0-9]+:/ { \ 57 | helpMessage = match(lastLine, /^## (.*)/); \ 58 | if (helpMessage) { \ 59 | helpCommand = substr($$1, 0, index($$1, ":")-1); \ 60 | helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ 61 | printf "%-15s %s\n", helpCommand, helpMessage; \ 62 | } \ 63 | } \ 64 | { lastLine = $$0 }' $(MAKEFILE_LIST) 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SVase 2 | 3 | _SVase_ is a source-to-source pre-elaborator for SystemVerilog (IEEE 1800-2017) single-unit designs. 4 | 5 | It is developed as part of the PULP project, a joint effort between ETH Zurich and the University of Bologna. 6 | 7 | It leverages the [best-in-class](https://chipsalliance.github.io/sv-tests-results/history) FOSS tool [Slang](https://github.com/MikePopoloski/slang) to parse and compile a design and then rewrite constructs unsupported by fundamental open EDA tools using the provided compile-time information. Most notably, _SVase_ 8 | 9 | * Creates unique module variants for each parameter combination and then hardcodes their parameters. 10 | * Unrolls generate constructs and replaces instance types to use unique modules. 11 | 12 | This completely uncouples instances and modules from each other, fully qualifying hierarchical parameters and types: any resulting unique module can be processed and synthesized out of context. Unlike Slang itself, _SVase_ outputs a fully-compliant 1800-2017 source that may be fed into any next tool with limited SV support as-is. 13 | 14 | ## Disclaimer 15 | This project is still considered to be in early development; some parts may not yet be functional, and existing interfaces and conventions may be broken without prior notice. We target a formal release in the near future. 16 | 17 | 18 | 19 | ## Using _SVase_ 20 | 21 | Currently there is no way to run select passes or give multiple input files. 22 | The only way to use _SVase_ is shown below: 23 | 24 | ```bash 25 | # svase top_module output.sv input.sv 26 | svase test out.sv test/assign/assign.sv 27 | ``` 28 | 29 | ## 30 | 31 | ### Example: SystemVerilog to Verilog flow 32 | 33 | 34 | A popular tool to convert from SystemVerilog to Verilog is [sv2v](https://github.com/zachjs/sv2v) but it does not support all SystemVerilog constructs as can be seen on the [sv-tests website](https://chipsalliance.github.io/sv-tests-results/). 35 | 36 | By adding _SVase_ into the flow, it is possible to get a wider coverage of SystemVerilog constructs. 37 | 38 | ```sh 39 | svase top_module intermediate.sv input.sv 40 | sv2v --verbose --write output.sv intermediate.sv 41 | ``` 42 | 43 | 44 | 45 | ## Getting _SVase_ 46 | 47 | ### Releases 48 | 49 | You can get _SVase_ binaries from the Releases page on Github. 50 | It should work properly on most Linux distros. If it does not, please open an Issue. 51 | 52 | ### Build it yourself 53 | 54 | #### Requirements 55 | 56 | The build requirements for _SVase_ are mostly dicated by the [Slang build requirements](https://sv-lang.com/building.html#build-requirements). 57 | The following requirements should be sufficient: 58 | 59 | - CMake 3.15 60 | - C++20 compatible compiler (GCC 10, Clang 16, XCode 14.3) 61 | - Python 3 62 | 63 | The tested configurations are: 64 | - GCC 11.2, CMake 3.20 and Python 3.6. 65 | - GCC 13.1, CMake 3.30 and Python 3.8. 66 | Depending on where/how you installed the compiler, CMake may not find the correct one. In this case you can manually specify a compiler, `scripts/svase.env` provides and example for this. 67 | 68 | #### Build 69 | 70 | One-liner: 71 | 72 | ```bash 73 | make build 74 | ``` 75 | 76 | Step-by-step: 77 | ```bash 78 | # edit and source svase.env if necessary 79 | # source scripts/svase.env 80 | 81 | # build dependencies 82 | cd deps 83 | make 84 | cd .. 85 | 86 | # build SVase 87 | mkdir build && cd build 88 | cmake .. 89 | make 90 | ``` 91 | 92 | If everything went well, you can find _SVase_ at `build/svase`. 93 | 94 | #### Running Tests 95 | 96 | ```bash 97 | make run-tests 98 | ``` 99 | 100 | ## 101 | 102 | ## Development 103 | 104 | ### Format to match linter 105 | 106 | ```bash 107 | make format 108 | ``` 109 | 110 | 111 | 112 | ### V1.0.0 ToDo list 113 | 114 | #### User Interface 115 | 116 | * [ ] Help message 117 | * [ ] Multiple input files (ie from json) 118 | * [ ] (Optional) Run select passes only 119 | 120 | #### Preprocessing 121 | 122 | * [ ] Clean up endmodule-trailing 123 | 124 | #### Rewrite support 125 | 126 | * [ ] Uniquify and replace interfaces 127 | * [ ] Rewrite assignment pattern expressions using `default:` 128 | * [ ] Rewrite starred port expressions 129 | * [ ] Handle instance arrays 130 | * [ ] (Optional) Add optional SV attributes on rewritten syntax 131 | * [ ] (Optional) Preserve/remove comments in a systematic way 132 | 133 | #### Postprocessing 134 | 135 | * [ ] Add support for library output 136 | * [ ] (Optional) Formatting and cleanup using verible if installed 137 | * [ ] (Optional) output validation using slang and verible if installed 138 | 139 | #### Cleanup 140 | 141 | * [ ] Switch to available Slang release 142 | * [ ] Use better command line library (`cxxopts` is buggy and lacking) 143 | * [ ] Revise cloning and use `std::move` where appropriate 144 | 145 | #### Hardening 146 | 147 | * [x] Set up CI with linting and compiling 148 | * [ ] Add feature-oriented test cases (FOSS equivalence checking?) 149 | * [ ] Integrate Cheshire as test 150 | * [ ] (Optional) Integrate Snitch Cluster as test 151 | * [ ] (Optional) Investigate Occamy Top as test 152 | 153 | 154 | 155 | ## License 156 | 157 | Unless specified otherwise in the respective file headers, all code checked into this repository is made available under a permissive license. All software sources are licensed under Apache 2.0. 158 | -------------------------------------------------------------------------------- /deps/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Paul Scheffler 6 | 7 | all: install_fmt 8 | all: install_slang 9 | all: install_cxxopts 10 | 11 | 12 | install: 13 | mkdir -p $@ 14 | 15 | install_fmt: | install 16 | git submodule update --init --recursive fmt 17 | cd fmt && cmake -B build 18 | cmake --build fmt/build -j$(shell nproc) 19 | cmake --install fmt/build --prefix install 20 | 21 | install_slang: | install 22 | git submodule update --init --recursive slang 23 | cd slang && cmake -B build 24 | cmake -S slang -B slang/build -DSLANG_INCLUDE_TOOLS=OFF -DSLANG_INCLUDE_TESTS=OFF 25 | cmake --build slang/build -j$(shell nproc) 26 | cmake --install slang/build --prefix install 27 | 28 | install_cxxopts: | install 29 | git submodule update --init --recursive cxxopts 30 | cd cxxopts && cmake -B build 31 | cmake --build cxxopts/build -j$(shell nproc) 32 | cmake --install cxxopts/build --prefix install 33 | -------------------------------------------------------------------------------- /include/svase/design.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Author: Paul Scheffler 6 | // Gist: A collection of classes to manage a compiled design with a single 7 | // root. 8 | 9 | #pragma once 10 | 11 | #include "slang/ast/Symbol.h" 12 | #include "slang/ast/symbols/InstanceSymbols.h" 13 | #include 14 | 15 | namespace svase { 16 | 17 | using namespace slang; 18 | using namespace slang::ast; 19 | 20 | /// Represents a hierarchical instance in a design, storing it's symbol and 21 | /// providing useful methods 22 | struct DesignInstance { 23 | public: 24 | const InstanceSymbol *const symbol; 25 | 26 | DesignInstance() = delete; 27 | 28 | DesignInstance(const InstanceSymbol *const symbol) : symbol(symbol) {} 29 | }; 30 | 31 | /// Collects DesignInstances that share a same unique module-parameterization 32 | /// pair. 33 | class DesignUniqueModule { 34 | private: 35 | std::vector instances; 36 | 37 | public: 38 | const size_t id; 39 | 40 | DesignUniqueModule() = delete; 41 | 42 | DesignUniqueModule(const InstanceSymbol *const symbol, size_t id) 43 | : instances(), id(id) { 44 | addInstance(symbol); 45 | } 46 | 47 | /// Add more instances to this unique module; used by an DesignCollection 48 | /// tracking this. Returns the new index. 49 | size_t addInstance(const InstanceSymbol *const symbol); 50 | 51 | /// Get modifiable view of the instances. 52 | std::vector &getInstances(); 53 | 54 | /// Get the module name without parameter uniquification, i.e. its name in the 55 | /// original design. 56 | std::string_view getGenericName() const; 57 | 58 | /// Get the uniquified name for this module, i.e. suffixed with its unique 59 | /// parameter ID. 60 | std::string getUniqueName(bool isRecompiled) const; 61 | }; 62 | 63 | /// Describes and stores a compiled design hierarchy, grouping instances by 64 | /// unique module parameterizations. 65 | class Design { 66 | private: 67 | std::unordered_map> 69 | uniqMods; 70 | std::unordered_map> 71 | uniqModByName; 72 | std::unordered_map> 73 | uniqModByParamStr; 74 | 75 | bool _isRecompiled; 76 | 77 | /// Insert an instance into the collection, returning the hierarchical key to 78 | /// the reference. Instances of the unique module are ordered by insertion, so 79 | /// the created instance is the last entry. 80 | void insertInstance(const InstanceSymbol *const sym, 81 | const size_t collisions = 0); 82 | 83 | /// Walk a compiled hierarchy, inserting all instances we find into our 84 | /// design. 85 | void walkModuleInstances(const Symbol *sym); 86 | 87 | public: 88 | Design(const InstanceSymbol *root, bool isRecompiled = false) 89 | : uniqMods(), uniqModByName(), uniqModByParamStr(), 90 | _isRecompiled(isRecompiled) { 91 | walkModuleInstances(root); 92 | } 93 | 94 | bool isRecompiled() { return _isRecompiled; }; 95 | 96 | /// Get a pointer to a generic module if it exists or null. 97 | const std::unordered_map * 98 | getGenericModule(std::string_view genericName) const; 99 | 100 | /// Get a uniquified module by its generic name and ID if it exists or null. 101 | DesignUniqueModule *getUniqueModule(std::string_view genericName, 102 | size_t id) const; 103 | 104 | /// Get an uniquified module by its unique name if it exists or null. 105 | DesignUniqueModule *getUniqueModule(std::string uniqueName) const; 106 | 107 | /// Get an uniquified module by its unique name if it exists or null. 108 | DesignUniqueModule *getUniqueModule(std::string_view uniqueName) const; 109 | 110 | /// Get a uniquified module by its generic name and full parameter string. 111 | DesignUniqueModule *getUniqueModule(std::string_view genericName, 112 | std::string paramString) const; 113 | 114 | /// Get a uniquified module by one of its instances' symbols. 115 | DesignUniqueModule *getUniqueModule(const InstanceSymbol &instSym) const; 116 | }; 117 | 118 | } // namespace svase 119 | -------------------------------------------------------------------------------- /include/svase/diag.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Author: Paul Scheffler 6 | // Gist: Utilities for diagnostic output and logging 7 | 8 | #pragma once 9 | 10 | #include "svase/util.h" 11 | 12 | #include "slang/ast/Symbol.h" 13 | #include "slang/diagnostics/TextDiagnosticClient.h" 14 | #include "slang/syntax/SyntaxNode.h" 15 | #include "slang/util/OS.h" 16 | #include 17 | #include 18 | #include 19 | 20 | namespace svase { 21 | 22 | using namespace slang; 23 | using namespace slang::ast; 24 | using namespace slang::syntax; 25 | 26 | using DiagSev = DiagnosticSeverity; 27 | 28 | typedef std::tuple UniqMsg; 29 | 30 | /// An extended `TextDiagnosticClient` with additional methods for simple 31 | /// logging and reporting. 32 | class Diag : TextDiagnosticClient { 33 | private: 34 | std::unique_ptr eng; 35 | std::clock_t startTime; 36 | DiagSev verbosity; 37 | // We index by byte location first, then differentiate strings, level 2 38 | // collisions are unlikely. 39 | std::unordered_map> uniqMsgs; 40 | 41 | /// Reuse meta diagnostics code 0 for our purposes. 42 | static inline DiagCode getDiagCode() { 43 | return DiagCode(DiagSubsystem::Meta, 0); 44 | } 45 | 46 | /// Insert a unique message into our map and return whether it was present. 47 | bool testOrInsertUniqMsg(DiagSev sev, std::string_view msg, 48 | size_t locHash = -1, bool showCode = false); 49 | 50 | /// Emit a log message with a location. 51 | void logLocation(DiagSev sev, std::string_view msg, Diagnostic od, 52 | bool keepUnique, bool showCode); 53 | 54 | public: 55 | Diag() 56 | : TextDiagnosticClient(), eng(nullptr), startTime(std::clock()), 57 | verbosity(DiagSev::Ignored) { 58 | showColors(OS::tryEnableColors()); 59 | } 60 | 61 | /// Set the verbosity; any severity smaller will be ignored 62 | void setVerbosity(DiagSev sev); 63 | 64 | /// Get the time elapsed since the diagnostic engine was constructed; may be 65 | /// used for timekeeping. 66 | clock_t timeElapsed(); 67 | 68 | /// Get a string representing the elapsed time. 69 | const std::string formatTimeElapsed(); 70 | 71 | /// Register a diagnostic engine by providing a source manager needed to 72 | /// report source locations. 73 | void registerEngine(const SourceManager *sm); 74 | 75 | /// Log basic message without source location; does not need engine. 76 | void log(DiagSev sev, std::string_view msg, bool keepUnique = false); 77 | 78 | /// Log diagnostic using a source location only. 79 | void log(DiagSev sev, std::string_view msg, const SourceLocation &loc, 80 | bool keepUnique = false, bool showCode = true); 81 | 82 | /// Log diagnostic using a symbol to determine source location. 83 | void log(DiagSev sev, std::string_view msg, const Symbol &sym, 84 | bool keepUnique = false, bool showCode = true); 85 | 86 | /// Log diagnostic using a syntax node to determine source location. 87 | void log(DiagSev sev, std::string_view msg, const SyntaxNode &syn, 88 | bool keepUnique = false, bool showCode = true); 89 | 90 | /// Log the start of a new stage in a highlighted fashion 91 | void logStage(std::string_view name); 92 | }; 93 | 94 | } // namespace svase 95 | -------------------------------------------------------------------------------- /include/svase/postproc.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Author: Paul Scheffler 6 | // Gist: Postprocess rewritten syntax tree to yield desired output buffers. 7 | 8 | #pragma once 9 | 10 | // TODO: appropriate headers 11 | 12 | namespace svase { 13 | 14 | class Postprocessor { 15 | private: 16 | s TypedBumpAllocator &postBuffers; 17 | Diag &diag; 18 | 19 | public: 20 | Postprocessor(std::vector &buffers, 21 | TypedBumpAllocator &postBuffers, Diag &diag) 22 | : buffers(buffers), postBuffers(postBuffers), diag(diag) {} 23 | 24 | void preprocess() { 25 | for (auto &buf : buffers) { 26 | auto strBuf = newBuffers.emplace(buf.data); 27 | // Add future per-buffer transforms here 28 | filterPragmaTranslate(*strBuf, buf.id); 29 | buf.data = std::string_view(strBuf->c_str()); 30 | } 31 | } 32 | 33 | void filterPragmaTranslate(std::string &strBuf, BufferID &id) { 34 | std::regex re( 35 | R"(//\s*pragma\s+translate[_ ]off(.|\n)+?//\s*pragma\s+translate[_ ]on)"); 36 | std::smatch match; 37 | // Overwrite these matches with whitespace of equal length 38 | auto start = strBuf.cbegin(); 39 | while (std::regex_search(start, strBuf.cend(), match, re)) { 40 | auto begin = (char *)start.base() + match.position(0); 41 | auto end = begin + match.length(0); 42 | diag.log(DiagSev::Note, 43 | "Filtering out `pragma_translate off` region before parsing", 44 | SourceLocation(id, begin - strBuf.cbegin().base()), false, 45 | false); 46 | std::fill(begin, end, ' '); 47 | start = match.suffix().first; 48 | } 49 | } 50 | }; 51 | 52 | } // namespace svase -------------------------------------------------------------------------------- /include/svase/preproc.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Author: Paul Scheffler 6 | // Gist: Preprocess syntax prior to parsing and compilation, *preserving* 7 | // syntax offsets. 8 | 9 | #pragma once 10 | 11 | // TODO: appropriate headers 12 | 13 | namespace svase { 14 | 15 | class Preprocessor { 16 | private: 17 | std::vector &buffers; 18 | TypedBumpAllocator &preBuffers; 19 | Diag &diag; 20 | 21 | public: 22 | Preprocessor(std::vector &buffers, 23 | TypedBumpAllocator &preBuffers, Diag &diag) 24 | : buffers(buffers), preBuffers(preBuffers), diag(diag) {} 25 | 26 | void preprocess() { 27 | for (auto &buf : buffers) { 28 | auto strBuf = preBuffers.emplace(buf.data); 29 | // Add future per-buffer transforms here 30 | filterPragmaTranslate(*strBuf, buf.id); 31 | buf.data = std::string_view(strBuf->c_str()); 32 | } 33 | } 34 | 35 | // TODO: glibcxx has known overflow bugs... use an alternative? 36 | void filterPragmaTranslate(std::string &strBuf, BufferID &id) { 37 | // match '// pragma translate on/off' and '// synthesis translate on/off' 38 | std::regex reOff(R"(\/\/\s*(?:pragma|synthesis)\s+translate[_ ]off)"); 39 | std::regex reOn(R"(\/\/\s*(?:pragma|synthesis)\s+translate[_ ]on)"); 40 | std::smatch match; 41 | // Overwrite these matches with whitespace of equal length 42 | auto start = strBuf.cbegin(); 43 | while (std::regex_search(start, strBuf.cend(), match, reOff)) { 44 | auto begin = (char *)start.base() + match.position(0); 45 | start = match.suffix().first; 46 | std::regex_search(start, strBuf.cend(), match, reOn); 47 | auto end = (char *)match.suffix().first.base(); 48 | diag.log(DiagSev::Note, 49 | "Filtering out `pragma_translate off` region before parsing", 50 | SourceLocation(id, begin - strBuf.cbegin().base()), false, 51 | false); 52 | std::fill(begin, end, ' '); 53 | } 54 | } 55 | }; 56 | 57 | } // namespace svase 58 | -------------------------------------------------------------------------------- /include/svase/rewrite.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Author: Paul Scheffler 6 | // Gist: A collection of syntax rewriters leveraging design information. 7 | 8 | #pragma once 9 | 10 | #include "svase/design.h" 11 | #include "svase/diag.h" 12 | 13 | #include "slang/ast/symbols/BlockSymbols.h" 14 | #include "slang/ast/types/TypePrinter.h" 15 | #include "slang/syntax/SyntaxPrinter.h" 16 | #include "slang/syntax/SyntaxVisitor.h" 17 | 18 | namespace svase { 19 | 20 | using namespace slang; 21 | using namespace slang::ast; 22 | using namespace slang::syntax; 23 | using namespace slang::parsing; 24 | 25 | /// A `SyntaxRewriter` variant leveraging a `DesignCollection` to rewrite code 26 | /// using elaboration info. 27 | template 28 | class DesignRewriter : public SyntaxRewriter { 29 | protected: 30 | Design &design; 31 | BumpAllocator &alloc; 32 | TypedBumpAllocator &strAlloc; 33 | Diag &diag; 34 | size_t uniqCounter; // Can be used to uniquify names during rewrote 35 | 36 | public: 37 | DesignRewriter(Design &coll, BumpAllocator &alloc, 38 | TypedBumpAllocator &strAlloc, Diag &diag) 39 | : design(coll), alloc(alloc), strAlloc(strAlloc), diag(diag), 40 | uniqCounter(0) {} 41 | }; 42 | 43 | /// Uniquify modules by creating one instantiation per unique parameter set and 44 | /// using its unique name. Original and unused module declarations are 45 | /// discarded. 46 | class UniqueModuleRewriter : public DesignRewriter { 47 | public: 48 | using DesignRewriter::DesignRewriter; 49 | 50 | /// Duplicate module declarations to uniquify them, removing all other 51 | /// declarations 52 | void handle(const ModuleDeclarationSyntax &modSyn); 53 | }; 54 | 55 | /// Replace parameters and type parameters, both in port maps and module bodies, 56 | /// with their elaborated values. This requires uniquified modules as generated 57 | /// by `UniqueModuleWriter`. 58 | class ParameterRewriter : public DesignRewriter { 59 | private: 60 | /// Make a equals token for a parameter initializer 61 | Token makeEquals(); 62 | 63 | /// Get the unique module containing a given parameter declaration syntax or 64 | /// null. 65 | DesignUniqueModule * 66 | getUniqueModule(const ParameterDeclarationBaseSyntax &pd) const; 67 | 68 | // Obtain Scope containing the Symbols corresponding to the given SyntaxNode 69 | const Scope *getContainingScope(const SyntaxNode &synNode) const; 70 | /// Get the symbol for a parameter declaration syntax or leave it unchanged 71 | /// and return null. 72 | 73 | template 74 | const Symbol *getParamSymOrBail(const T *pd, 75 | std::vector &declStrs, 76 | const Scope *scope) const; 77 | 78 | /// Replace a parameter declaration syntax with a new syntax in string form if 79 | /// it can be parsed as such. 80 | template 81 | void replaceParamDeclOrBail(std::string declStr, const T &pd); 82 | 83 | /// Walk a type syntax and mangle enumerated type value labels (inaccessible 84 | /// anyways) to prevent name collisions. 85 | DataTypeSyntax *mangleEnumTypes(DataTypeSyntax &typeSyn); 86 | 87 | public: 88 | using DesignRewriter::DesignRewriter; 89 | 90 | void handle(const TypeParameterDeclarationSyntax &pd); 91 | 92 | void handle(const ParameterDeclarationSyntax &pd); 93 | }; 94 | 95 | // Unroll generate constructs inside uniquified modules as needed by their 96 | // parameter set. This does *not* create new instance symbols, keeping them 97 | // intentionally coupled for later replacement.! 98 | class GenerateRewriter : public DesignRewriter { 99 | private: 100 | /// Make an empty member as a substitute for uninstantiated blocks 101 | EmptyMemberSyntax *makeEmptyMember(); 102 | 103 | /// Make a colon token for labels 104 | Token makeColon(); 105 | 106 | /// Make a trivial ifGenerate to wrap a `GenerateBlock` in, since standalone 107 | /// blocks are not LRM-legal. 108 | IfGenerateSyntax *makeDummyIfGen(std::string_view label = ""sv); 109 | 110 | /// Wrap an existing block in a trivial ifGenerate 111 | IfGenerateSyntax *wrapBlockInIfGen(GenerateBlockSyntax &blockSyn, 112 | MemberSyntax *genvar = nullptr); 113 | 114 | /// Wrap multiple members in a trivial ifGenerate with a block to make it a 115 | /// standalone member. 116 | IfGenerateSyntax *wrapMemberListInIfGen(SyntaxList &members, 117 | std::string_view label = ""sv); 118 | 119 | /// Wrap one member in a trivial ifGenerate with a block to make it a 120 | /// standalone member. 121 | IfGenerateSyntax *wrapMemberInIfGen(MemberSyntax &membSyn, 122 | std::string_view label = ""sv, 123 | MemberSyntax *genvar = nullptr); 124 | 125 | /// Wrap a Pseudomember in a trivial ifGenerate, possibly with a block, to 126 | /// make it a standalone member. 127 | MemberSyntax *wrapInIfGen(MemberSyntax &membSyn, 128 | MemberSyntax *genvar = nullptr); 129 | 130 | /// Get a `beginName` label from whatever may be the block's canonical name 131 | /// iff it exists or null. 132 | NamedBlockClauseSyntax *getBeginName(const GenerateBlockSyntax &blockSyn); 133 | 134 | /// Generate a block's `beginName` syntax from a string; is null iff `name` is 135 | /// empty. 136 | NamedBlockClauseSyntax *makeBlockBeginName(std::string_view name); 137 | 138 | /// Unroll a generic `MemberSyntax`, leaving those that are not generate 139 | /// constructs or instances as they are. 140 | MemberSyntax *unrollGenSyntax(MemberSyntax &membSyn, const Scope &scope, 141 | NamedBlockClauseSyntax *beginName = nullptr, 142 | const GenerateBlockSymbol *blockSym = nullptr, 143 | const Scope *globalScope = nullptr); 144 | 145 | /// Unroll a `GenerateBlockSyntax`, looking up its symbol in the compilation. 146 | MemberSyntax * 147 | unrollGenBlockSyntax(const GenerateBlockSyntax &blockSyn, 148 | const GenerateBlockSymbol &blockSym, 149 | NamedBlockClauseSyntax *beginName = nullptr); 150 | 151 | /// Unroll an `IfGenerateSyntax`, looking up its symbol in the compilation. 152 | MemberSyntax *unrollGenSyntax(const IfGenerateSyntax &ifSyn, 153 | const Scope &scope, 154 | NamedBlockClauseSyntax *beginName = nullptr); 155 | 156 | /// Unroll a `CaseGenerateSyntax`, looking up its symbol in the compilation. 157 | MemberSyntax *unrollGenSyntax(const CaseGenerateSyntax &caseSyn, 158 | const Scope &scope, 159 | NamedBlockClauseSyntax *beginName = nullptr); 160 | 161 | /// Unroll a `LoopGenerateSyntax`, looking up its symbol in the compilation. 162 | MemberSyntax *unrollGenSyntax(const LoopGenerateSyntax &loopSyn, 163 | const Scope &scope); 164 | 165 | /// Unroll a `GenerateRegionSyntax`; these have no real effect on the design, 166 | /// but must be walked. 167 | MemberSyntax *unrollGenSyntax(const GenerateRegionSyntax ®Syn, 168 | const Scope &scope, 169 | NamedBlockClauseSyntax *beginName = nullptr); 170 | 171 | /// "Unroll" an instance by giving it a unique type; best done here as we 172 | /// already resolved the Symbol hierarchy. 173 | /// TODO: what about instance arrays? are we even properly tracking those in 174 | /// `Design`? 175 | MemberSyntax *unrollGenSyntax(const HierarchyInstantiationSyntax &instSyn, 176 | const Scope &scope, 177 | const Scope *globalScope = nullptr); 178 | 179 | /// Handle a generic generate construct iff it is inside a module. 180 | template void handleGenerate(const T &syn); 181 | 182 | public: 183 | using DesignRewriter::DesignRewriter; 184 | 185 | void handle(const IfGenerateSyntax &syn); 186 | void handle(const LoopGenerateSyntax &syn); 187 | void handle(const CaseGenerateSyntax &syn); 188 | void handle(const GenerateRegionSyntax &syn); 189 | 190 | void handle(const HierarchyInstantiationSyntax &syn); 191 | }; 192 | 193 | class TypedefDeclarationRewriter 194 | : public DesignRewriter { 195 | private: 196 | const Symbol *getTypeNameSymOrBail(const TypedefDeclarationSyntax *pd, 197 | DesignUniqueModule *uniqMod) const; 198 | 199 | template 200 | void replaceTypeDeclOrBail(std::string declStr, const T &pd); 201 | 202 | DesignUniqueModule *getUniqueModule(const SyntaxNode &pd) const; 203 | 204 | public: 205 | using DesignRewriter::DesignRewriter; 206 | 207 | // void handle(const ScopedNameSyntax &pd); 208 | void handle(const TypedefDeclarationSyntax &pd); 209 | }; 210 | 211 | class AssignmentRewriter : public DesignRewriter { 212 | private: 213 | const ContinuousAssignSymbol * 214 | getLHSNameSymOrBail(const ContinuousAssignSyntax *pd, 215 | DesignUniqueModule *uniqMod); 216 | // template 217 | // void replaceAssignmentOrBail(std::string declStr, const T &pd); 218 | 219 | DesignUniqueModule *getUniqueModule(const SyntaxNode &pd) const; 220 | 221 | public: 222 | using DesignRewriter::DesignRewriter; 223 | 224 | void handle(const ContinuousAssignSyntax &pd); 225 | }; 226 | 227 | } // namespace svase 228 | -------------------------------------------------------------------------------- /include/svase/util.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Author: Paul Scheffler 6 | // Gist: Header for utility types and inline functions. 7 | 8 | #pragma once 9 | 10 | #include "fmt/format.h" 11 | #include "slang/ast/symbols/BlockSymbols.h" 12 | #include "slang/ast/symbols/InstanceSymbols.h" 13 | #include "slang/ast/symbols/MemberSymbols.h" 14 | #include "slang/ast/symbols/ParameterSymbols.h" 15 | #include "slang/ast/types/Type.h" 16 | #include "slang/ast/types/TypePrinter.h" 17 | #include "slang/syntax/AllSyntax.h" 18 | #include "slang/syntax/SyntaxNode.h" 19 | #include "slang/util/BumpAllocator.h" 20 | #include 21 | #include 22 | #include 23 | 24 | namespace svase { 25 | 26 | using namespace slang; 27 | using namespace slang::ast; 28 | using namespace slang::syntax; 29 | 30 | /// Generates a string uniquely identifying the external parameterization of an 31 | /// instance. 32 | static inline std::string genParamString(const InstanceSymbol *const sym) { 33 | std::string hashString; 34 | auto typePrinter = TypePrinter(); 35 | typePrinter.options.skipScopedTypeNames = true; 36 | typePrinter.options.fullEnumType = true; 37 | // Find all non-local Type and non-type parameters and uniquely stringify them 38 | // for hashing 39 | for (auto ¶m : sym->body.membersOfType()) 40 | if (!param.isLocalParam()) { 41 | typePrinter.append( 42 | param.getTypeAlias().getDeclaredType()->getType().getCanonicalType()); 43 | hashString += fmt::format("{}:{},", param.name, typePrinter.toString()); 44 | typePrinter.clear(); 45 | } 46 | for (auto ¶m : sym->body.membersOfType()) 47 | if (!param.isLocalParam()) 48 | hashString += 49 | fmt::format("{}:{},", param.name, 50 | param.getValue().toString(SVInt::MAX_BITS, true)); 51 | return hashString; 52 | } 53 | 54 | /// Generates a reproducible (non-collision-free) hash representing only module 55 | /// parameterization. 56 | static inline size_t genParamHash(const InstanceSymbol *const inst) { 57 | return std::hash()(genParamString(inst)); 58 | } 59 | 60 | /// Check whether the module parameterization of two instances is exactly 61 | /// identical. 62 | static inline bool areParamEqual(const InstanceSymbol *const a, 63 | const InstanceSymbol *const b) { 64 | return genParamString(a) == genParamString(b); 65 | } 66 | 67 | /// Get the member of a scope by name if it exists or null. 68 | static inline const Symbol *getScopeMember(const Scope &scope, 69 | std::string_view name) { 70 | auto find = scope.getNameMap().find(name); 71 | if (find == scope.getNameMap().end()) 72 | return nullptr; 73 | else 74 | return find->second; 75 | } 76 | 77 | /// Get a fixed-sized array on a heap; useful whenever `SmallVector`s become too 78 | /// large for the stack. 79 | template 80 | static inline std::span allocArray(const size_t size, BumpAllocator &alloc) { 81 | auto base = (T *)(void *)alloc.allocate(size * sizeof(T), sizeof(T)); 82 | return std::span(base, size); 83 | } 84 | 85 | /// Get a comparable index (without collisions) for unique source locations 86 | /// across buffers. We avoid collisions by reserving half a `size_t` for the 87 | /// buffer ID and offset, respectively. 88 | static inline size_t getSourceLocIdx(const SourceLocation &loc) { 89 | constexpr size_t size_half_bits = sizeof(size_t) * 4; 90 | constexpr size_t size_hash_sup = size_t(1) << size_half_bits; 91 | size_t bufId = loc.buffer().getId(); 92 | size_t bufOffs = loc.offset(); 93 | assert(bufId < size_hash_sup); 94 | assert(bufOffs < size_hash_sup); 95 | return (bufId << size_half_bits) | bufOffs; 96 | } 97 | 98 | /// Get an index uniquely identifying byte offset of a syntax node. 99 | static inline size_t getSynSourceLocIdx(const SyntaxNode &syn) { 100 | return getSourceLocIdx(syn.sourceRange().start()); 101 | } 102 | 103 | /// Get an index uniquely identifying the byte offset of the source syntax 104 | /// associated with a symbol. May be used to identify the symbol iff original 105 | /// syntax locations are preserved. 106 | static inline size_t getSymSourceLocIdx(const Symbol &sym) { 107 | return getSourceLocIdx(sym.location); 108 | } 109 | 110 | /// Conditionally Resolve symbol of a specific type from its syntax and scope or 111 | /// return null. 112 | template 113 | static inline const TSym *synToSym(const SyntaxNode &syn, const Scope &scope) { 114 | for (auto &memSym : scope.membersOfType()) { 115 | size_t memIdx = getSymSourceLocIdx(memSym); 116 | if constexpr (std::is_same_v || 117 | std::is_same_v || 118 | std::is_same_v) { 119 | // We account here for a difference that appears 120 | // The SyntaxNode start with "" 121 | // The Symbol location is at the "begin : "" so we need to skip the 122 | // "begin :" part. We do that by converting it to a SyntaxNode 123 | auto memSyn = memSym.getSyntax(); 124 | // can be called for a loopGenerate and for that we do not want to move 125 | // the matching 126 | if (!memSyn) { 127 | printf("Warning: Symbol %s has no syntax.\n", memSym.name.data()); 128 | continue; 129 | } 130 | 131 | if constexpr (std::is_same_v) { 132 | // assign a=b; gives AssignmentExpression from getSyntax with 133 | // ContinuousAssignment as its parent 134 | memSyn = memSyn->parent; 135 | } 136 | 137 | if (memSyn->kind == syn.kind) { 138 | memIdx = getSynSourceLocIdx(*memSyn); 139 | } else { 140 | if (memIdx != getSynSourceLocIdx(syn) && 141 | memSyn->kind == SyntaxKind::LoopGenerate) { 142 | std::regex reBegin(R"(begin\s*:\s*)"); 143 | std::smatch match; 144 | std::string symbolStr = syn.toString(); 145 | auto start = symbolStr.cbegin(); 146 | size_t beginLength = 0; 147 | while (std::regex_search(start, symbolStr.cend(), match, reBegin)) { 148 | if (beginLength == 0) { 149 | beginLength = match.length(); 150 | break; 151 | } 152 | } 153 | if (memIdx - beginLength == getSynSourceLocIdx(syn)) { 154 | return &memSym.template as(); 155 | } 156 | } 157 | } 158 | } 159 | 160 | if (memIdx == getSynSourceLocIdx(syn)) 161 | return &memSym.template as(); 162 | } 163 | 164 | // None of the source locations matched, so the scope does not contain this 165 | // syntax 166 | return nullptr; 167 | } 168 | 169 | /// If the passed member syntax is a `GenerateBlockSyntax` matching a 170 | /// `GenerateBlockSymbol` in the passed scope, return it. We do this even if the 171 | /// block is not instantiated. 172 | static inline const GenerateBlockSymbol * 173 | matchInstGenBlock(SyntaxNode *condBlkSyn, const Scope &scope) { 174 | if (condBlkSyn) 175 | return synToSym(*condBlkSyn, scope); 176 | else 177 | return nullptr; 178 | } 179 | 180 | } // namespace svase 181 | -------------------------------------------------------------------------------- /scripts/gen_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2024 ETH Zurich and University of Bologna. 3 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # Author: 7 | # - Philippe Sauter 8 | 9 | # latest tag 10 | TAG=$(git describe --tags --abbrev=0) 11 | 12 | # current commit hash 13 | COMMIT=$(git rev-parse --short HEAD) 14 | 15 | printf "#pragma once\n#define VERSION \"SVase: ${TAG} - ${COMMIT}\"\n" 16 | -------------------------------------------------------------------------------- /scripts/run_test.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Jannis Schönleber 6 | 7 | cd build && make && cd .. 8 | svase iguana_padframe_fixture iguana_svase.sv iguana_padframe_fixture.pickle.sv --split 9 | slang iguana_svase.sv -Wrange-oob --allow-use-before-declare -Wrange-width-oob -error-limit=4419 -top iguana_padframe__15538910711671852192 10 | -------------------------------------------------------------------------------- /scripts/svase.env: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Jannis Schönleber 6 | 7 | root_dir="$(realpath $(dirname "${BASH_SOURCE[0]}"))" 8 | 9 | export CC=gcc-11.2.0-af 10 | export CXX=g++-11.2.0-af 11 | 12 | # during development you want to use the current versions 13 | export PATH="$root_dir/build:$PATH" 14 | export PATH="$root_dir/deps/slang/build/bin:$PATH" -------------------------------------------------------------------------------- /src/design.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Author: Paul Scheffler 6 | // Gist: A collection of classes to manage a compiled design with a single 7 | // root. 8 | 9 | #include "svase/design.h" 10 | #include "svase/util.h" 11 | 12 | #include "fmt/format.h" 13 | #include "slang/ast/Definition.h" 14 | #include "slang/ast/symbols/BlockSymbols.h" 15 | #include 16 | 17 | namespace svase { 18 | 19 | using namespace slang; 20 | using namespace slang::ast; 21 | 22 | size_t DesignUniqueModule::addInstance(const InstanceSymbol *const symbol) { 23 | instances.emplace_back(symbol); 24 | return instances.size(); 25 | } 26 | 27 | std::vector &DesignUniqueModule::getInstances() { 28 | return instances; 29 | } 30 | 31 | std::string_view DesignUniqueModule::getGenericName() const { 32 | return instances.back().symbol->getDefinition().name; 33 | } 34 | 35 | std::string DesignUniqueModule::getUniqueName(bool isRecompiled = false) const { 36 | if (isRecompiled) { 37 | if (getGenericName().find(fmt::format("__{}", id)) != std::string::npos) { 38 | // Already uniquified 39 | return fmt::format("{}", getGenericName()); 40 | } 41 | std::smatch sm; 42 | std::string name = fmt::format("{}", getGenericName()); 43 | std::regex_search(name, sm, std::regex("__\\d{15,}")); 44 | if (sm.size() > 0) { 45 | // Already uniquified 46 | return fmt::format("{}", getGenericName()); 47 | } 48 | } else { 49 | std::smatch sm; 50 | std::string name = fmt::format("{}", getGenericName()); 51 | std::regex_search(name, sm, std::regex("__\\d{15,}")); 52 | if (sm.size() > 0) { 53 | fmt::print( 54 | "Warning: Module name {} contains a 15+ digit number, which is " 55 | "likely " 56 | "a hash. This may cause collisions in the uniquification process.\n", 57 | getGenericName()); 58 | } 59 | } 60 | return fmt::format("{}__{}", getGenericName(), id); 61 | } 62 | 63 | void Design::insertInstance(const InstanceSymbol *const sym, 64 | const size_t collisions) { 65 | // Generate ID and avoid collisions if necessary 66 | size_t id = genParamHash(sym) + collisions; 67 | // Construct our DesignUniqueModule *inside* the map *iff* the key is unused 68 | std::string_view genericName = sym->getDefinition().name; 69 | auto emplRet = uniqMods[genericName].emplace( 70 | std::piecewise_construct, std::make_tuple(id), std::make_tuple(sym, id)); 71 | // Handle insertion as necessary 72 | auto containedMod = &emplRet.first->second; 73 | if (!emplRet.second) { 74 | // Insertion has failed due to collision 75 | if (!areParamEqual(containedMod->getInstances().back().symbol, sym)) 76 | // Existing ID has different parameters: modify ID and retry insertion 77 | return this->insertInstance(sym, collisions + 1); 78 | else 79 | // Existing ID has same parameters: add instance to existing UniqueModule 80 | containedMod->addInstance(sym); 81 | } else { 82 | // Module Insertion successful: enter unique module into its maps 83 | uniqModByName.emplace( 84 | std::piecewise_construct, 85 | std::make_tuple(containedMod->getUniqueName(isRecompiled())), 86 | std::make_tuple(genericName, id)); 87 | uniqModByParamStr[genericName].emplace(std::piecewise_construct, 88 | std::make_tuple(genParamString(sym)), 89 | std::make_tuple(id)); 90 | } 91 | } 92 | 93 | void Design::walkModuleInstances(const Symbol *sym) { 94 | switch (sym->kind) { 95 | // Walk and add instances and add modules only. 96 | case SymbolKind::Instance: { 97 | auto instSymb = static_cast(sym); 98 | if (instSymb->isModule()) 99 | insertInstance(instSymb); 100 | for (auto &member : instSymb->body.members()) 101 | walkModuleInstances(&member); 102 | break; 103 | } 104 | // Walk past structures that may occur between instance bodies and 105 | // instantiations. 106 | case SymbolKind::GenerateBlock: { 107 | auto genBlock = static_cast(sym); 108 | if (!genBlock->isUninstantiated) 109 | for (auto &member : genBlock->members()) 110 | walkModuleInstances(&member); 111 | break; 112 | } 113 | case SymbolKind::GenerateBlockArray: { 114 | auto genBlockArray = static_cast(sym); 115 | if (!genBlockArray->isUninstantiated()) 116 | for (auto &member : genBlockArray->members()) 117 | walkModuleInstances(&member); 118 | break; 119 | } 120 | case SymbolKind::InstanceArray: { 121 | auto instArray = static_cast(sym); 122 | if (!instArray->isUninstantiated()) 123 | for (auto &member : instArray->members()) 124 | walkModuleInstances(&member); 125 | break; 126 | } 127 | default:; 128 | } 129 | } 130 | 131 | const std::unordered_map * 132 | Design::getGenericModule(std::string_view genericName) const { 133 | if (auto find = uniqMods.find(genericName); find == uniqMods.end()) 134 | return nullptr; 135 | else 136 | return &find->second; 137 | } 138 | 139 | // Get a uniquified module by its generic name and ID if it exists or null. 140 | DesignUniqueModule *Design::getUniqueModule(std::string_view genericName, 141 | size_t id) const { 142 | auto module = getGenericModule(genericName); 143 | if (!module) 144 | return nullptr; 145 | auto find = module->find(id); 146 | if (find == module->end()) 147 | return nullptr; 148 | else 149 | return const_cast(&find->second); 150 | } 151 | 152 | DesignUniqueModule *Design::getUniqueModule(std::string uniqueName) const { 153 | if (auto find = uniqModByName.find(uniqueName); find == uniqModByName.end()) 154 | return nullptr; 155 | else 156 | return getUniqueModule(find->second.first, find->second.second); 157 | } 158 | 159 | DesignUniqueModule *Design::getUniqueModule(std::string_view uniqueName) const { 160 | return getUniqueModule(std::string(uniqueName)); 161 | } 162 | 163 | DesignUniqueModule *Design::getUniqueModule(std::string_view genericName, 164 | std::string paramString) const { 165 | if (auto find = uniqModByParamStr.find(genericName); 166 | find == uniqModByParamStr.end()) 167 | return nullptr; 168 | else if (auto inFind = find->second.find(paramString); 169 | inFind == find->second.end()) 170 | return nullptr; 171 | else 172 | return getUniqueModule(genericName, inFind->second); 173 | } 174 | 175 | DesignUniqueModule * 176 | Design::getUniqueModule(const InstanceSymbol &instSym) const { 177 | return getUniqueModule(instSym.getDefinition().name, 178 | genParamString(&instSym)); 179 | } 180 | 181 | } // namespace svase 182 | -------------------------------------------------------------------------------- /src/diag.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Author: Paul Scheffler 6 | // Gist: Utilities for diagnostic output and logging 7 | 8 | #include "svase/diag.h" 9 | 10 | #include "fmt/color.h" 11 | #include "fmt/format.h" 12 | 13 | namespace svase { 14 | 15 | using namespace slang; 16 | using namespace slang::ast; 17 | using namespace slang::syntax; 18 | 19 | bool Diag::testOrInsertUniqMsg(DiagSev sev, std::string_view msg, 20 | size_t locHash, bool showCode) { 21 | auto insStr = fmt::format("{:0x}{:0x}|{}", showCode, int(sev), msg); 22 | // Check in two stages whether message was already logged. 23 | if (uniqMsgs.count(locHash) && uniqMsgs[locHash].count(insStr)) 24 | return true; 25 | // If it was not, insert into map and instruct caller to log message. 26 | uniqMsgs[locHash].insert(insStr); 27 | return false; 28 | } 29 | 30 | void Diag::logLocation(DiagSev sev, std::string_view msg, Diagnostic od, 31 | bool keepUnique, bool showCode) { 32 | // Do not log messages below the desired 33 | if (sev < verbosity) 34 | return; 35 | if (keepUnique && 36 | testOrInsertUniqMsg(sev, msg, getSourceLocIdx(od.location), showCode)) 37 | return; 38 | if (!engine) 39 | throw new std::runtime_error( 40 | "Cannot log a source location without engine."); 41 | ReportedDiagnostic rd(od); 42 | rd.formattedMessage = msg; 43 | rd.severity = sev; 44 | rd.shouldShowIncludeStack = false; 45 | rd.location = od.location; 46 | showSourceLine(showCode); 47 | report(rd); 48 | OS::printE(getString()); 49 | clear(); 50 | // TODO: throw something more appropriate 51 | if (sev >= DiagSev::Fatal) 52 | ASSUME_UNREACHABLE; 53 | } 54 | 55 | void Diag::setVerbosity(DiagSev sev) { verbosity = sev; } 56 | 57 | clock_t Diag::timeElapsed() { return std::clock() - startTime; } 58 | 59 | const std::string Diag::formatTimeElapsed() { 60 | auto t = timeElapsed() * 1000 / CLOCKS_PER_SEC; 61 | auto ms = t % 1000; 62 | auto s = (t /= 1000) % 60; 63 | auto m = (t /= 60); 64 | return fmt::format("{:3}m {:2}.{:03}s", m, s, ms); 65 | } 66 | 67 | void Diag::registerEngine(const SourceManager *sm) { 68 | sourceManager = sm; 69 | eng = std::make_unique(*sm); 70 | engine = eng.get(); 71 | } 72 | 73 | void Diag::log(DiagSev sev, std::string_view msg, bool keepUnique) { 74 | if (sev < verbosity) 75 | return; 76 | if (keepUnique && testOrInsertUniqMsg(sev, msg)) 77 | return; 78 | OS::printE(fg(getSeverityColor(sev)), 79 | fmt::format("{}: ", getSeverityString(sev))); 80 | if (sev != DiagSev::Note) 81 | OS::printE(fmt::text_style(fmt::emphasis::bold), fmt::format("{}\n", msg)); 82 | else 83 | OS::printE(fmt::format("{}\n", msg)); 84 | // TODO: throw something more appropriate 85 | if (sev >= DiagSev::Fatal) 86 | ASSUME_UNREACHABLE; 87 | } 88 | 89 | void Diag::log(DiagSev sev, std::string_view msg, const SourceLocation &loc, 90 | bool keepUnique, bool showCode) { 91 | Diagnostic od(getDiagCode(), loc); 92 | logLocation(sev, msg, od, keepUnique, showCode); 93 | } 94 | 95 | void Diag::log(DiagSev sev, std::string_view msg, const Symbol &sym, 96 | bool keepUnique, bool showCode) { 97 | Diagnostic od(sym, getDiagCode(), sym.location); 98 | logLocation(sev, msg, od, keepUnique, showCode); 99 | } 100 | 101 | void Diag::log(DiagSev sev, std::string_view msg, const SyntaxNode &syn, 102 | bool keepUnique, bool showCode) { 103 | Diagnostic od(getDiagCode(), syn.sourceRange().start()); 104 | logLocation(sev, msg, od, keepUnique, showCode); 105 | } 106 | 107 | void Diag::logStage(std::string_view name) { 108 | OS::printE(fg(highlightColor), 109 | fmt::format("[{}] {}\n", formatTimeElapsed(), name)); 110 | } 111 | 112 | } // namespace svase 113 | -------------------------------------------------------------------------------- /src/driver.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Author: Paul Scheffler 6 | // Gist: Main command line driver for SVase. 7 | 8 | #include "cxxopts.hpp" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "svase/design.h" 16 | #include "svase/diag.h" 17 | #include "svase/preproc.h" 18 | #include "svase/rewrite.h" 19 | #include "version.h" 20 | 21 | #include "slang/ast/symbols/CompilationUnitSymbols.h" 22 | #include "slang/driver/Driver.h" 23 | #include "slang/util/TimeTrace.h" 24 | 25 | namespace svase { 26 | 27 | cxxopts::Options genCmdOpts() { 28 | cxxopts::Options ret("svase", 29 | "SVase: a source-to-source SystemVerilog elaborator"); 30 | // TODO: make these mandatory somehow and make sure they show in help 31 | // TODO: get a better parser library... 32 | ret.parse_positional({"top", "out", "files"}); 33 | ret.add_options() 34 | // Mandatory positionals 35 | ("top", "Top module of design to elaborate", 36 | cxxopts::value())( 37 | "out", "The output file (- for stdout) or library", 38 | cxxopts::value())( 39 | "files", "The source files to process", 40 | cxxopts::value>()) 41 | // Optional arguments 42 | ("intermediate", "Output intermediate steps (ie after recompiling)", 43 | cxxopts::value()->implicit_value("false"))( 44 | "l,lib", "Output library of individual modules", 45 | cxxopts::value()->implicit_value("false")) // TODO 46 | ("split", 47 | "write all files in split files", // TODO add option to pass a path 48 | cxxopts::value()->implicit_value("false"))( 49 | "z,compress", "Compress output file or library using Gzip", 50 | cxxopts::value()->implicit_value("false")) // TODO 51 | ("s,slang-argfile", "Argument file overriding Slang default options", 52 | cxxopts::value())( 53 | "S,slang-args", "Argument string overriding Slang default options", 54 | cxxopts::value())( 55 | "r,timetrace", "Time each stage and write chrome event trace to JSON", 56 | cxxopts::value()) // TODO 57 | ("V,verbosity", 58 | "Verbosity of stderr diagnostics: 3(errors), 2(warnings), 1(notes)", 59 | cxxopts::value()->default_value("2")) 60 | // Informational arguments (cause early exit) 61 | ("h,help", "Print usage")("v,version", "Print version information"); 62 | 63 | return ret; 64 | } 65 | 66 | template 67 | void writeToFile(Stream &os, std::string_view fileName, String contents) { 68 | os.write(contents.data(), contents.size()); 69 | os.flush(); 70 | if (!os) 71 | throw std::runtime_error( 72 | fmt::format("Unable to write AST to '{}'", fileName)); 73 | } 74 | 75 | void writeToFile(std::string_view fileName, std::string_view contents) { 76 | if (fileName == "-") { 77 | writeToFile(std::cout, "stdout", contents); 78 | } else { 79 | std::ofstream file{std::string(fileName)}; 80 | writeToFile(file, fileName, contents); 81 | } 82 | } 83 | 84 | // TODO: find a nicer way to wrap the timing/exception scopes 85 | int driverMain(int argc, char **argv) { 86 | Diag diag; 87 | DiagSev verbosity; 88 | bool ok; 89 | 90 | // Parse and handle global args 91 | auto cmdOpts = genCmdOpts(); 92 | cxxopts::ParseResult cmdOptsRes; 93 | try { 94 | cmdOptsRes = cmdOpts.parse(argc, argv); 95 | } catch (const cxxopts::exceptions::parsing &e) { 96 | diag.log(DiagSev::Fatal, e.what()); 97 | return 1; 98 | } 99 | if (cmdOptsRes.count("help")) { 100 | const auto &helpOptions = cmdOpts.help(); 101 | fmt::print("{}\n", helpOptions); 102 | return 0; 103 | } 104 | if (cmdOptsRes.count("version")) { 105 | fmt::print("{}\n", VERSION); 106 | return 0; 107 | } 108 | if (!cmdOptsRes.count("top") || !cmdOptsRes.count("out") || 109 | !cmdOptsRes.count("files")) { 110 | const auto &helpOptions = cmdOpts.help(); 111 | fmt::print("Error: Missing required arguments: svase TOP_MODULE OUTPUT " 112 | "FILES\n\n{}\n", 113 | helpOptions); 114 | return 1; 115 | } 116 | if (cmdOptsRes.count("timetrace")) 117 | TimeTrace::initialize(); 118 | verbosity = (DiagSev)cmdOptsRes["verbosity"].as(); 119 | diag.setVerbosity(verbosity); 120 | 121 | // Parse and handle Slang args using its driver 122 | slang::driver::Driver slangDriver; 123 | slangDriver.diagEngine.setIgnoreAllNotes(verbosity > DiagSev::Note); 124 | slangDriver.diagEngine.setIgnoreAllWarnings(verbosity > DiagSev::Warning); 125 | slangDriver.addStandardArgs(); 126 | ok = true; 127 | auto builtinFlags = 128 | "--ignore-unknown-modules --allow-use-before-declare --single-unit -Wrange-width-oob -Wrange-oob"sv; 129 | if (cmdOptsRes.count("fslang")) 130 | ok &= slangDriver.processCommandFile( 131 | cmdOptsRes["slang-argfile"].as(), true); 132 | std::string slangArgs = cmdOptsRes.count("slang-args") 133 | ? cmdOptsRes["slang-args"].as() 134 | : ""; 135 | auto filesStr = fmt::to_string( 136 | fmt::join(cmdOptsRes["files"].as>(), " ")); 137 | auto slangCmd = 138 | fmt::format("{} {} {} --top {} {}", argv[0], slangArgs, builtinFlags, 139 | cmdOptsRes["top"].as(), filesStr); 140 | ok &= slangDriver.parseCommandLine(slangCmd); 141 | ok &= slangDriver.processOptions(); 142 | diag.registerEngine(&slangDriver.sourceManager); 143 | if (!ok) 144 | return 2; 145 | 146 | // Preprocess buffers 147 | TypedBumpAllocator preBuffers; 148 | try { 149 | TimeTraceScope timeScope("preproc"sv, ""sv); 150 | diag.logStage("PREPROCESS"); 151 | Preprocessor preproc(slangDriver.buffers, preBuffers, diag); 152 | preproc.preprocess(); 153 | } catch (const std::exception &e) { 154 | diag.log(DiagSev::Fatal, e.what()); 155 | ok = false; 156 | } 157 | if (!ok) 158 | return 3; 159 | 160 | // Parse using Slang 161 | try { 162 | diag.logStage("PARSE"); 163 | TimeTraceScope timeScope("parse"sv, ""sv); 164 | ok = slangDriver.parseAllSources(); 165 | } catch (const std::exception &e) { 166 | diag.log(DiagSev::Fatal, e.what()); 167 | ok = false; 168 | } 169 | if (!ok) 170 | return 4; 171 | 172 | // Compile using Slang 173 | std::unique_ptr compilation; 174 | try { 175 | diag.logStage("COMPILE"); 176 | TimeTraceScope timeScope("compile"sv, ""sv); 177 | compilation = slangDriver.createCompilation(); 178 | ok = slangDriver.reportCompilation(*compilation, true); 179 | 180 | } catch (const std::exception &e) { 181 | diag.log(DiagSev::Fatal, e.what()); 182 | ok = false; 183 | } 184 | if (!ok) 185 | return 5; 186 | 187 | // Rewrite sources using our passes 188 | // TODO: put rewriter-referenced resources in a shared class for convenience 189 | std::unique_ptr design; 190 | std::shared_ptr synTree; 191 | BumpAllocator alloc; 192 | TypedBumpAllocator strAlloc; 193 | // try { 194 | diag.logStage("REWRITE"); 195 | // TimeTraceScope timeScope("rewrite"sv, ""sv); 196 | design = 197 | std::make_unique(*compilation->getRoot().topInstances.begin()); 198 | synTree = compilation->getSyntaxTrees().back(); 199 | // Run our passes (TODO: somehow handle boolean return?) 200 | 201 | // turn each parametrization into a unique module 202 | synTree = 203 | UniqueModuleRewriter(*design, alloc, strAlloc, diag).transform(synTree); 204 | // propagate port-params from instances to new modules (as defaults) 205 | synTree = 206 | ParameterRewriter(*design, alloc, strAlloc, diag).transform(synTree); 207 | // unroll all generate blocks and loops 208 | synTree = GenerateRewriter(*design, alloc, strAlloc, diag).transform(synTree); 209 | // propagate components of typedefs (ie other types from pkgs in a struct) 210 | // to the modules 211 | synTree = TypedefDeclarationRewriter(*design, alloc, strAlloc, diag) 212 | .transform(synTree); 213 | 214 | // recompile to make unrolled structure explicit/real 215 | // (each genblock has a unique location in the source-code) 216 | diag.logStage("REWRITE [after recompilation]"); 217 | compilation = std::make_unique(compilation->getOptions()); 218 | std::vector> intermediateBuffers; 219 | intermediateBuffers.emplace_back(cmdOptsRes["top"].as(), 220 | synTree->root().toString()); 221 | if (cmdOptsRes["intermediate"].as()) { 222 | writeToFile("recompiled.sv", intermediateBuffers.back().second); 223 | } 224 | 225 | Diag newDiag; 226 | newDiag.setVerbosity(DiagSev::Error); // avoid reprinting warnings 227 | SourceManager newSourceManager; 228 | synTree = slang::syntax::SyntaxTree::fromFileInMemory( 229 | std::string_view(intermediateBuffers.back().second), newSourceManager, 230 | "after_gen_unfold"); 231 | compilation->addSyntaxTree(synTree); 232 | 233 | design = std::make_unique( 234 | *compilation->getRoot().topInstances.begin(), true); 235 | synTree = compilation->getSyntaxTrees().back(); 236 | newDiag.registerEngine(&newSourceManager); 237 | 238 | // Run passes after unrolling the structure 239 | // Todo: Is this necessary? 240 | synTree = UniqueModuleRewriter(*design, alloc, strAlloc, newDiag) 241 | .transform(synTree); 242 | // Propagate parameters inside each module (and the unrolled generate blocks) 243 | synTree = 244 | ParameterRewriter(*design, alloc, strAlloc, newDiag).transform(synTree); 245 | 246 | // resolve constant continous assignments (assign a = bla;) 247 | synTree = 248 | AssignmentRewriter(*design, alloc, strAlloc, newDiag).transform(synTree); 249 | // } catch (const std::exception e) {diag.log(DiagSev::Fatal, e.what()); ok = 250 | // false;} 251 | // if (!ok) return 6; 252 | 253 | // TODO: Postprocess syntax tree into writable buffers, one per root unit 254 | // member 255 | std::vector> postBuffers; 256 | std::vector> postBuffersFiles; 257 | try { 258 | diag.logStage("POSTPROCESS"); 259 | TimeTraceScope timeScope("postproc"sv, ""sv); 260 | // TODO: proper lib handling 261 | postBuffers.emplace_back(cmdOptsRes["top"].as(), 262 | synTree->root().toString()); 263 | int moduleAmount = 0, packageAmount = 0, interfaceAmount = 0, 264 | classAmount = 0, unknownAmount = 0; 265 | for (size_t i = 0; i < synTree->root().childNode(0)->getChildCount(); i++) { 266 | auto child = synTree->root().childNode(0)->childNode(i); 267 | if (child) { 268 | if (child->kind == SyntaxKind::ModuleDeclaration) { 269 | postBuffersFiles.emplace_back( 270 | fmt::format("{}_{}", "module", moduleAmount++), 271 | child->toString()); 272 | } else if (child->kind == SyntaxKind::PackageDeclaration) { 273 | postBuffersFiles.emplace_back( 274 | fmt::format("{}_{}", "package", packageAmount++), 275 | child->toString()); 276 | } else if (child->kind == SyntaxKind::InterfaceDeclaration) { 277 | postBuffersFiles.emplace_back( 278 | fmt::format("{}_{}", "interface", interfaceAmount++), 279 | child->toString()); 280 | } else if (child->kind == SyntaxKind::ClassDeclaration) { 281 | postBuffersFiles.emplace_back( 282 | fmt::format("{}_{}", "class", classAmount++), child->toString()); 283 | } else { 284 | postBuffersFiles.emplace_back( 285 | fmt::format("{}_{}", "unknown", unknownAmount++), 286 | child->toString()); 287 | } 288 | } 289 | } 290 | } catch (const std::exception &e) { 291 | diag.log(DiagSev::Fatal, e.what()); 292 | ok = false; 293 | } 294 | if (!ok) 295 | return 7; 296 | 297 | // try { 298 | diag.logStage("WRITEOUT"); 299 | TimeTraceScope timeScope("writeout"sv, ""sv); 300 | // TODO: library handling and such; guard that out exists, is legal 301 | // beforehand! 302 | writeToFile(cmdOptsRes["out"].as(), postBuffers.back().second); 303 | std::string defaultPath = "splitted_output"; 304 | if (cmdOptsRes.count("split")) { 305 | if (!std::filesystem::is_directory(defaultPath) || 306 | !std::filesystem::exists(defaultPath)) { 307 | std::filesystem::create_directory(defaultPath); 308 | } 309 | for (auto buffer : postBuffersFiles) { 310 | writeToFile(fmt::format("{}/{}.sv", defaultPath, buffer.first), 311 | buffer.second); 312 | } 313 | } 314 | 315 | //} catch (const std::exception e) {diag.log(DiagSev::Fatal, e.what()); ok = 316 | // false;} if (!ok) return 8; 317 | 318 | diag.logStage("DONE"); 319 | 320 | // TODO: report on top, unique modules, and blackboxes here. 321 | 322 | // TODO: Timescope implementation! 323 | 324 | return 0; 325 | } 326 | 327 | } // namespace svase 328 | 329 | int main(int argc, char **argv) { return svase::driverMain(argc, argv); } 330 | -------------------------------------------------------------------------------- /src/rewrite.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Author: Paul Scheffler 6 | // Gist: A collection of syntax rewriters leveraging design information. 7 | 8 | #include 9 | #include 10 | 11 | #include "svase/rewrite.h" 12 | #include "svase/util.h" 13 | 14 | #include "fmt/format.h" 15 | #include "slang/ast/Expression.h" 16 | #include "slang/ast/expressions/AssignmentExpressions.h" 17 | #include "slang/ast/symbols/MemberSymbols.h" 18 | #include "slang/ast/symbols/ParameterSymbols.h" 19 | #include "slang/ast/symbols/VariableSymbols.h" 20 | #include "slang/ast/types/TypePrinter.h" 21 | 22 | namespace svase { 23 | 24 | using namespace slang; 25 | using namespace slang::ast; 26 | using namespace slang::syntax; 27 | using namespace slang::parsing; 28 | 29 | void UniqueModuleRewriter::handle(const ModuleDeclarationSyntax &modSyn) { 30 | // Look up the module to check if it needs to be uniquified 31 | auto hdrSyn = modSyn.header.get(); 32 | auto genName = hdrSyn->name.rawText(); 33 | auto genMod = design.getGenericModule(genName); 34 | // This module is instantiated, hence in use 35 | if (genMod) { 36 | for (auto uniqPair : *genMod) { 37 | auto uniqMod = uniqPair.second; 38 | // Change module name to unique version 39 | auto name = 40 | strAlloc.emplace(uniqMod.getUniqueName(design.isRecompiled())); 41 | auto nameToken = hdrSyn->name.withRawText(alloc, *name); 42 | // Assemble header 43 | auto uniqHdrSyn = 44 | ModuleHeaderSyntax(SyntaxKind::ModuleHeader, hdrSyn->moduleKeyword, 45 | hdrSyn->lifetime, nameToken, hdrSyn->imports, 46 | hdrSyn->parameters, hdrSyn->ports, hdrSyn->semi); 47 | // Assemble module (deepclone to decouple) 48 | auto uniqModSyn = 49 | deepClone(ModuleDeclarationSyntax( 50 | SyntaxKind::ModuleDeclaration, modSyn.attributes, 51 | uniqHdrSyn, modSyn.members, modSyn.endmodule, nullptr), 52 | alloc); 53 | // Insert after generic module 54 | insertAfter(modSyn, *uniqModSyn); 55 | } 56 | } 57 | // Remove generic module iff it is a module and not a package or interface 58 | if (modSyn.kind == SyntaxKind::ModuleDeclaration) 59 | remove(modSyn); 60 | } 61 | 62 | Token ParameterRewriter::makeEquals() { 63 | return makeToken(TokenKind::Equals, *strAlloc.emplace("=")); 64 | } 65 | 66 | DesignUniqueModule *ParameterRewriter::getUniqueModule( 67 | const ParameterDeclarationBaseSyntax &pd) const { 68 | // Obtain module instance from either port list or root (skip if in package or 69 | // in some other config) 70 | auto modNode = pd.parent; 71 | if (modNode->kind == SyntaxKind::ParameterPortList) 72 | modNode = modNode->parent->parent; 73 | else if (modNode->kind == SyntaxKind::ParameterDeclarationStatement) 74 | modNode = modNode->parent; 75 | if (modNode->kind == SyntaxKind::PackageDeclaration || 76 | modNode->kind == SyntaxKind::InterfaceDeclaration || 77 | modNode->kind == SyntaxKind::GenerateBlock) 78 | return nullptr; 79 | else if (modNode->kind != SyntaxKind::ModuleDeclaration) { 80 | diag.log(DiagSev::Warning, 81 | fmt::format("parameter declaration with unhandled parent kind " 82 | "`{}`; left unchanged", 83 | toString(modNode->kind)), 84 | pd, true); 85 | return nullptr; 86 | } 87 | // Obtain corresponding unique module 88 | auto &modSyn = modNode->as(); 89 | return design.getUniqueModule(modSyn.header->name.rawText()); 90 | } 91 | 92 | const Scope * 93 | ParameterRewriter::getContainingScope(const SyntaxNode &synNode) const { 94 | // Obtain Scope containing the Symbols corresponding to the given SyntaxNode 95 | static std::unordered_map cache; 96 | 97 | const SyntaxNode *node = &synNode; 98 | const Scope *scope = nullptr; 99 | 100 | const SyntaxNode *topNode = nullptr; 101 | // go to the top-most Node still in the same scope 102 | while (topNode == nullptr && node->parent != nullptr && 103 | node->parent->kind != SyntaxKind::Unknown && 104 | node->parent->kind != SyntaxKind::CompilationUnit) { 105 | // these Nodes create the current scope, we can't go further 106 | if (node->parent->kind == SyntaxKind::GenerateBlock || 107 | node->parent->kind == SyntaxKind::LoopGenerate || 108 | node->parent->kind == SyntaxKind::ModuleDeclaration) { 109 | topNode = node; 110 | } else { 111 | node = node->parent; 112 | } 113 | } 114 | 115 | if (topNode == nullptr) { 116 | return nullptr; 117 | } 118 | 119 | // Use source-code location as SyntaxNode identifier to match Symbol 120 | SourceLocation topNodeSourceStart = topNode->sourceRange().start(); 121 | SourceLocation topNodeSourceEnd = topNode->sourceRange().end(); 122 | 123 | // go upwards through the SyntaxNodes (through parent Scopes) 124 | // find the next known scope (from cache or UniqueModule) 125 | while (scope == nullptr && node->parent) { 126 | if (cache.count(node) > 0) { 127 | scope = cache[node]; 128 | } else { 129 | node = node->parent; 130 | 131 | if (node->kind == SyntaxKind::ModuleDeclaration) { 132 | auto uniqMod = design.getUniqueModule( 133 | node->as().header->name.rawText()); 134 | scope = &(uniqMod->getInstances().begin()->symbol->body.as()); 135 | } 136 | } 137 | } 138 | 139 | // if we don't have a scope this is not in a uniqueModule 140 | if (!scope) { 141 | return nullptr; 142 | } 143 | // here we have a scope in which the corresponding symbol should be somewhere 144 | 145 | // scopes encountered while going downwards, need to be searched for Symbol 146 | std::queue remainingScopes; 147 | remainingScopes.push(scope); 148 | 149 | while (!remainingScopes.empty()) { 150 | auto localScope = remainingScopes.front(); 151 | remainingScopes.pop(); 152 | 153 | for (auto &child : localScope->members()) { 154 | // if the syntax of the child-symbol is our syntax 155 | // then we currently are in its containing scope 156 | auto childLocation = child.location; 157 | if (topNodeSourceStart <= childLocation && 158 | childLocation <= topNodeSourceEnd) { 159 | cache[child.getSyntax()] = localScope; 160 | return localScope; 161 | } 162 | 163 | if (child.isScope()) { 164 | remainingScopes.push(&(child.as())); 165 | } 166 | } 167 | } 168 | 169 | return nullptr; 170 | } 171 | 172 | template 173 | const Symbol *ParameterRewriter::getParamSymOrBail( 174 | const T *pd, std::vector &declStrs, const Scope *scope) const { 175 | auto memberSym = getScopeMember(*scope, pd->name.rawText()); 176 | if (memberSym == nullptr) { 177 | diag.log(DiagSev::Note, 178 | "parameter declaration not found in compilation; left unchanged", 179 | *pd, true); 180 | declStrs.emplace_back(pd->toString()); 181 | } 182 | return memberSym; 183 | } 184 | 185 | template 186 | void ParameterRewriter::replaceParamDeclOrBail(std::string declStr, 187 | const T &pd) { 188 | auto &newPdSyn = parse(declStr); 189 | // param decl without keyword are parsed as AssignmentExpression 190 | if (newPdSyn.kind == SyntaxKind::AssignmentExpression) { 191 | return; 192 | } else if (newPdSyn.kind != SyntaxKind::ParameterDeclarationStatement) { 193 | diag.log(DiagSev::Error, 194 | fmt::format( 195 | "misparsed parameter declaration as kind `{}`; left unchanged", 196 | toString(newPdSyn.kind)), 197 | pd, true); 198 | return; 199 | } 200 | auto newPdStatement = 201 | clone(newPdSyn.as(), alloc); 202 | if (newPdStatement) { 203 | auto newPd = clone(newPdStatement->parameter.get()->as(), alloc); 204 | replace(pd, *newPd); 205 | } 206 | } 207 | 208 | DataTypeSyntax *ParameterRewriter::mangleEnumTypes(DataTypeSyntax &typeSyn) { 209 | switch (typeSyn.kind) { 210 | case SyntaxKind::EnumType: { 211 | auto &enumSyn = typeSyn.as(); 212 | size_t enumIdx = ++uniqCounter; 213 | auto newMembArr = 214 | allocArray(2 * enumSyn.members.size() - 1, alloc); 215 | size_t i = 0; 216 | bool firstIter = true; 217 | for (auto member : enumSyn.members) { 218 | if (!firstIter) { 219 | newMembArr[i++] = *alloc.emplace(makeComma()); 220 | } 221 | firstIter = false; 222 | auto newNameStr = std::string_view(*strAlloc.emplace( 223 | fmt::format("{}_svase{}", member->name.rawText(), enumIdx))); 224 | Token newName(alloc, parsing::TokenKind::Identifier, 225 | member->name.trivia(), newNameStr, member->name.location()); 226 | newMembArr[i++] = *alloc.emplace(clone( 227 | DeclaratorSyntax(newName, member->dimensions, member->initializer), 228 | alloc)); 229 | } 230 | auto newMembers = SeparatedSyntaxList(newMembArr); 231 | return clone(EnumTypeSyntax(enumSyn.keyword, enumSyn.baseType, 232 | enumSyn.openBrace, newMembers, 233 | enumSyn.closeBrace, enumSyn.dimensions), 234 | alloc); 235 | } 236 | case SyntaxKind::UnionType: 237 | case SyntaxKind::StructType: { 238 | // Resolve Syntax 239 | auto &ustrSyn = typeSyn.as(); 240 | auto newMembArr = 241 | allocArray(ustrSyn.members.size(), alloc); 242 | size_t i = 0; 243 | for (auto member : ustrSyn.members) { 244 | auto newTypeSyn = mangleEnumTypes(*member->type); 245 | newMembArr[i++] = 246 | clone(StructUnionMemberSyntax(member->attributes, 247 | member->randomQualifier, *newTypeSyn, 248 | member->declarators, member->semi), 249 | alloc); 250 | } 251 | return clone(StructUnionTypeSyntax(ustrSyn.kind, ustrSyn.keyword, 252 | ustrSyn.tagged, ustrSyn.packed, 253 | ustrSyn.signing, ustrSyn.openBrace, 254 | SyntaxList(newMembArr), 255 | ustrSyn.closeBrace, ustrSyn.dimensions), 256 | alloc); 257 | } 258 | case SyntaxKind::VirtualInterfaceType: { 259 | diag.log(DiagSev::Warning, 260 | "Virtual interface type elaboration is not supported yet", typeSyn, 261 | true); 262 | } 263 | default:; 264 | } 265 | return &typeSyn; 266 | } 267 | 268 | void ParameterRewriter::handle(const TypeParameterDeclarationSyntax &pd) { 269 | auto uniqMod = getUniqueModule(pd); 270 | if (!uniqMod) 271 | return; 272 | std::vector newDeclStrs; 273 | auto typePrinter = TypePrinter(); 274 | typePrinter.options.skipScopedTypeNames = true; 275 | typePrinter.options.fullEnumType = true; 276 | for (auto decl : pd.declarators) { 277 | // Find parameter in compilation; if not found, leave as-is 278 | auto memberSym = getParamSymOrBail( 279 | decl, newDeclStrs, 280 | &uniqMod->getInstances().begin()->symbol->body.as()); 281 | if (!memberSym) 282 | continue; 283 | auto ¶mSym = memberSym->as(); 284 | // Generate parseable string view of our type, then parse it as a 285 | // declaration 286 | auto &type = paramSym.getDeclaredType()->getType().getCanonicalType(); 287 | typePrinter.append(type); 288 | auto &declSyn = this->parse(typePrinter.toString()) 289 | .template as(); 290 | typePrinter.clear(); 291 | // Rebuild assignment 292 | auto newEquals = EqualsTypeClauseSyntax(decl->assignment->equals, 293 | *mangleEnumTypes(*declSyn.type)); 294 | auto assignSyn = TypeAssignmentSyntax(decl->name, &newEquals); 295 | newDeclStrs.emplace_back(assignSyn.toString()); 296 | } 297 | auto newDeclStr = fmt::to_string(fmt::join(newDeclStrs, ", ")); 298 | auto declStr = fmt::format("{}{}{}", pd.keyword.toString(), 299 | pd.typeKeyword.toString(), newDeclStr); 300 | if (pd.keyword.toString().empty()) { 301 | diag.log( 302 | DiagSev::Warning, 303 | fmt::format( 304 | "parameter declaration without keyword (parameter or localparam)"), 305 | pd, true); 306 | } 307 | replaceParamDeclOrBail(declStr, pd); 308 | } 309 | 310 | void ParameterRewriter::handle(const ParameterDeclarationSyntax &pd) { 311 | const Scope *scope = nullptr; 312 | if (design.isRecompiled()) { 313 | scope = getContainingScope(pd); 314 | } else { 315 | auto uniqMod = getUniqueModule(pd); 316 | if (!uniqMod) 317 | return; 318 | scope = &uniqMod->getInstances().begin()->symbol->body.as(); 319 | } 320 | if (!scope) 321 | return; 322 | // Todo: Use ParameterBuilder instead? 323 | std::vector newDeclStrs; 324 | for (auto decl : pd.declarators) { 325 | // Find parameter in compilation; if not found, leave as-is 326 | auto memberSym = 327 | getParamSymOrBail(decl, newDeclStrs, scope); 328 | if (!memberSym) 329 | continue; 330 | auto ¶mSym = memberSym->as(); 331 | // Parse parameter value as expression (wrap in cast if enum) 332 | auto exprStr = paramSym.getValue().toString(SVInt::MAX_BITS, true, true); 333 | auto ¶mType = memberSym->getDeclaredType()->getType(); 334 | if (paramType.isEnum()) 335 | exprStr = fmt::format("{}'({})", pd.type.get()->toString(), exprStr); 336 | auto &exprSyn = this->parse(exprStr).template as(); 337 | 338 | // Rebuild assignment 339 | // create new equals token as params w\o defaults do not have a '=' 340 | // (and creating a new one is likely faster than checking first) 341 | auto newEquals = EqualsValueClauseSyntax(makeEquals(), exprSyn); 342 | auto declSyn = DeclaratorSyntax(decl->name, decl->dimensions, &newEquals); 343 | newDeclStrs.emplace_back(declSyn.toString()); 344 | } 345 | // Todo: figure out keyword, if the last keyword in the parameter port list 346 | // was a localparam, it is localparam, otherwise it is parameter 347 | auto newDeclStr = fmt::to_string(fmt::join(newDeclStrs, ", ")); 348 | auto declStr = fmt::format("{}{}{}", pd.keyword.toString(), 349 | pd.type->toString(), newDeclStr); 350 | if (pd.keyword.toString().empty()) { 351 | diag.log( 352 | DiagSev::Warning, 353 | fmt::format( 354 | "parameter declaration without keyword (parameter or localparam)"), 355 | pd, true); 356 | } 357 | replaceParamDeclOrBail(declStr, pd); 358 | } 359 | 360 | EmptyMemberSyntax *GenerateRewriter::makeEmptyMember() { 361 | return clone(parse("\n;\n").as(), alloc); 362 | } 363 | 364 | Token GenerateRewriter::makeColon() { 365 | return makeToken(TokenKind::Colon, *strAlloc.emplace(":")); 366 | } 367 | 368 | IfGenerateSyntax *GenerateRewriter::makeDummyIfGen(std::string_view label) { 369 | std::string labelSuffix = label.empty() ? "" : fmt::format(" : {}", label); 370 | std::string retStr = fmt::format( 371 | "generate\n\nif (1) begin{}\nwire dummy;\nend\n\nendgenerate\n", 372 | labelSuffix); 373 | return clone((*parse(retStr).as().members.begin()) 374 | ->as(), 375 | alloc); 376 | } 377 | 378 | IfGenerateSyntax * 379 | GenerateRewriter::wrapBlockInIfGen(GenerateBlockSyntax &blockSyn, 380 | MemberSyntax *genvar) { 381 | auto ifSyn = makeDummyIfGen(); 382 | auto newBlkSyn = &blockSyn; 383 | if (genvar) { 384 | // Create new span with genvar member at beginning 385 | auto newMembers = 386 | allocArray(blockSyn.members.size() + 1, alloc); 387 | newMembers[0] = genvar; 388 | std::copy(std::begin(blockSyn.members), std::end(blockSyn.members), 389 | std::begin(newMembers) + 1); 390 | newBlkSyn = 391 | clone(GenerateBlockSyntax(blockSyn.attributes, blockSyn.label, 392 | blockSyn.begin, blockSyn.beginName, 393 | newMembers, blockSyn.end, blockSyn.endName), 394 | alloc); 395 | } 396 | return clone(IfGenerateSyntax(ifSyn->attributes, ifSyn->keyword, 397 | ifSyn->openParen, *ifSyn->condition, 398 | ifSyn->closeParen, *newBlkSyn, 399 | ifSyn->elseClause), 400 | alloc); 401 | } 402 | 403 | IfGenerateSyntax * 404 | GenerateRewriter::wrapMemberListInIfGen(SyntaxList &members, 405 | std::string_view label) { 406 | auto ifSyn = makeDummyIfGen(label); 407 | auto &blkSyn = ifSyn->block->as(); 408 | auto newBlkSyn = 409 | clone(GenerateBlockSyntax(blkSyn.attributes, blkSyn.label, blkSyn.begin, 410 | blkSyn.beginName, members, blkSyn.end, 411 | blkSyn.endName), 412 | alloc); 413 | return clone(IfGenerateSyntax(ifSyn->attributes, ifSyn->keyword, 414 | ifSyn->openParen, *ifSyn->condition, 415 | ifSyn->closeParen, *newBlkSyn, 416 | ifSyn->elseClause), 417 | alloc); 418 | } 419 | 420 | IfGenerateSyntax *GenerateRewriter::wrapMemberInIfGen(MemberSyntax &membSyn, 421 | std::string_view label, 422 | MemberSyntax *genvar) { 423 | auto newMembSpan = allocArray(1 + bool(genvar), alloc); 424 | if (genvar) 425 | newMembSpan[0] = genvar; 426 | newMembSpan[bool(genvar)] = &membSyn; 427 | auto newMembers = clone(SyntaxList(newMembSpan), alloc); 428 | return wrapMemberListInIfGen(*newMembers, label); 429 | } 430 | 431 | MemberSyntax *GenerateRewriter::wrapInIfGen(MemberSyntax &membSyn, 432 | MemberSyntax *genvar) { 433 | // Do not wrap empty members to avoid unnecessary block spawning 434 | if (membSyn.kind == SyntaxKind::EmptyMember) 435 | return &membSyn; 436 | else if (membSyn.kind == SyntaxKind::GenerateBlock) 437 | return wrapBlockInIfGen(membSyn.as(), genvar); 438 | else 439 | return wrapMemberInIfGen(membSyn, ""sv, genvar); 440 | } 441 | 442 | NamedBlockClauseSyntax * 443 | GenerateRewriter::getBeginName(const GenerateBlockSyntax &blockSyn) { 444 | NamedBlockClauseSyntax *ret = blockSyn.beginName; 445 | if (!ret && blockSyn.label) 446 | ret = 447 | clone(NamedBlockClauseSyntax(makeColon(), blockSyn.label->name), alloc); 448 | return ret; 449 | } 450 | 451 | NamedBlockClauseSyntax * 452 | GenerateRewriter::makeBlockBeginName(std::string_view name) { 453 | auto ifSyn = makeDummyIfGen(name); 454 | return ifSyn->block->as().beginName; 455 | } 456 | 457 | MemberSyntax * 458 | GenerateRewriter::unrollGenSyntax(MemberSyntax &membSyn, const Scope &scope, 459 | NamedBlockClauseSyntax *beginName, 460 | const GenerateBlockSymbol *blockSym, 461 | const Scope *globalScope) { 462 | // After generate, skip into its possible block scope unless an actual block 463 | // syntax exists. 464 | auto &genScope = blockSym ? *blockSym : scope; 465 | if (blockSym && blockSym->isUninstantiated) 466 | return makeEmptyMember(); 467 | switch (membSyn.kind) { 468 | case SyntaxKind::IfGenerate: 469 | return unrollGenSyntax(membSyn.as(), genScope, beginName); 470 | case SyntaxKind::CaseGenerate: 471 | return unrollGenSyntax(membSyn.as(), genScope, 472 | beginName); 473 | case SyntaxKind::LoopGenerate: 474 | return unrollGenSyntax(membSyn.as(), genScope); 475 | case SyntaxKind::GenerateRegion: 476 | return unrollGenSyntax(membSyn.as(), genScope, 477 | beginName); 478 | case SyntaxKind::HierarchyInstantiation: 479 | return unrollGenSyntax(membSyn.as(), genScope, 480 | globalScope); 481 | case SyntaxKind::GenerateBlock: { 482 | // If the child is a block, the parent *should* set the block symbol 483 | if (!blockSym) 484 | diag.log(DiagSev::Fatal, 485 | "Could not find compilation symbol for generate block", membSyn); 486 | return unrollGenBlockSyntax(membSyn.as(), *blockSym, 487 | beginName); 488 | } 489 | default: 490 | return &membSyn; 491 | } 492 | } 493 | 494 | MemberSyntax * 495 | GenerateRewriter::unrollGenBlockSyntax(const GenerateBlockSyntax &blockSyn, 496 | const GenerateBlockSymbol &blockSym, 497 | NamedBlockClauseSyntax *beginName) { 498 | // Allocate and unroll new members; do not propagate our begin label past this 499 | // stage. 500 | auto newMembers = allocArray(blockSyn.members.size(), alloc); 501 | size_t i = 0; 502 | for (auto memb : blockSyn.members) 503 | newMembers[i++] = unrollGenSyntax(*memb, blockSym); 504 | // Construct new block (Name is either passed down or taken from beginName or 505 | // label) 506 | if (!beginName) 507 | beginName = getBeginName(blockSyn); 508 | auto newMembersList = clone(SyntaxList(newMembers), alloc); 509 | return clone(GenerateBlockSyntax(blockSyn.attributes, nullptr, blockSyn.begin, 510 | beginName, *newMembersList, blockSyn.end, 511 | nullptr), 512 | alloc); 513 | } 514 | 515 | MemberSyntax * 516 | GenerateRewriter::unrollGenSyntax(const IfGenerateSyntax &ifSyn, 517 | const Scope &scope, 518 | NamedBlockClauseSyntax *beginName) { 519 | auto newSyn = unrollGenSyntax(*ifSyn.block, scope, beginName, 520 | matchInstGenBlock(ifSyn.block, scope)); 521 | if (newSyn->kind == SyntaxKind::EmptyMember && ifSyn.elseClause) { 522 | auto &elseSyn = ifSyn.elseClause->clause->as(); 523 | newSyn = unrollGenSyntax(elseSyn, scope, beginName, 524 | matchInstGenBlock(&elseSyn, scope)); 525 | } 526 | return wrapInIfGen(*newSyn); 527 | } 528 | 529 | MemberSyntax * 530 | GenerateRewriter::unrollGenSyntax(const CaseGenerateSyntax &caseSyn, 531 | const Scope &scope, 532 | NamedBlockClauseSyntax *beginName) { 533 | // Iterate over case items and break once one contains an instantiated block 534 | for (auto &item : caseSyn.items) { 535 | SyntaxNode *caseNode; 536 | switch (item->kind) { 537 | case SyntaxKind::StandardCaseItem: { 538 | caseNode = item->as().clause; 539 | break; 540 | } 541 | case SyntaxKind::DefaultCaseItem: { 542 | caseNode = item->as().clause; 543 | break; 544 | } 545 | default: 546 | diag.log(DiagSev::Fatal, "Encountered unexpected generate case item type", 547 | item); 548 | exit(1); 549 | } 550 | auto &itemSyn = caseNode->as(); 551 | auto newSyn = unrollGenSyntax(itemSyn, scope, beginName, 552 | matchInstGenBlock(&itemSyn, scope)); 553 | // We found the one instantiated (matching) case --> wrap and return 554 | // unrolled block or member 555 | if (newSyn->kind != SyntaxKind::EmptyMember) 556 | return wrapInIfGen(*newSyn); 557 | } 558 | // None of the cases are instantiated (i.e. matched) --> return *unwrapped* 559 | // empty member 560 | return makeEmptyMember(); 561 | } 562 | 563 | MemberSyntax * 564 | GenerateRewriter::unrollGenSyntax(const LoopGenerateSyntax &loopSyn, 565 | const Scope &scope) { 566 | // Get syntax and symbol 567 | auto topMembSyn = loopSyn.block; 568 | auto topArraySym = synToSym(*topMembSyn, scope); 569 | if (!topArraySym) 570 | diag.log(DiagSev::Fatal, 571 | "Could not find symbol for loop generate construct", loopSyn); 572 | 573 | // Allocate member array. We will later wrap them in an if-generate block 574 | auto newMembers = 575 | allocArray(topArraySym->entries.size(), alloc); 576 | // If direct child is block, give top block its name and give it an index 577 | // name. Otherwise, give top block a generic name and leave subblocks 578 | // unchanged (guaranteed unique) 579 | bool renameSubs = (topMembSyn->kind == SyntaxKind::GenerateBlock); 580 | // Iterate over instantiated blocks and collect unrolled clones with defparams 581 | size_t i = 0; 582 | for (auto &entry : topArraySym->entries) { 583 | // Skip uninstantiated members 584 | if (entry->isUninstantiated) { 585 | newMembers[i++] = makeEmptyMember(); 586 | continue; 587 | } 588 | // Emit defparam assignment before member 589 | auto arrayIdxStr = entry->arrayIndex->toString(); 590 | auto loopGenvarDeclStr = fmt::format( 591 | "\nlocalparam {} = {};\n", loopSyn.identifier.toString(), arrayIdxStr); 592 | auto loopGenvarSyn = 593 | clone(parse(loopGenvarDeclStr).as(), alloc); 594 | // Emit member, with overriden name if requested. 595 | // All children should consume the block used to store the genvar. 596 | NamedBlockClauseSyntax *subBlockName = nullptr; 597 | if (renameSubs) 598 | subBlockName = makeBlockBeginName(fmt::format("__{}", arrayIdxStr)); 599 | newMembers[i++] = 600 | wrapInIfGen(*unrollGenSyntax(*topMembSyn, *topArraySym, subBlockName, 601 | entry, topArraySym->getParentScope()), 602 | loopGenvarSyn); 603 | } 604 | // Determine top block name 605 | std::string topBlockName = fmt::format("genfor{}", ++uniqCounter); 606 | if (renameSubs) { 607 | auto topBeginName = getBeginName(topMembSyn->as()); 608 | if (topBeginName) 609 | topBlockName = topBeginName->name.toString(); 610 | } 611 | // Return clones of top member in new generate-block pair 612 | auto newMembersList = clone(SyntaxList(newMembers), alloc); 613 | return wrapMemberListInIfGen(*newMembersList, topBlockName); 614 | } 615 | 616 | MemberSyntax * 617 | GenerateRewriter::unrollGenSyntax(const GenerateRegionSyntax ®Syn, 618 | const Scope &scope, 619 | NamedBlockClauseSyntax *beginName) { 620 | // Allocate and unroll members 621 | auto newMembers = allocArray(regSyn.members.size(), alloc); 622 | size_t i = 0; 623 | for (auto memb : regSyn.members) 624 | newMembers[i++] = unrollGenSyntax(*memb, scope, beginName); 625 | // Construct new block (Name is either passed down or taken from beginName or 626 | // label) 627 | auto newMembersList = clone(SyntaxList(newMembers), alloc); 628 | return clone(GenerateRegionSyntax(regSyn.attributes, regSyn.keyword, 629 | *newMembersList, regSyn.endgenerate), 630 | alloc); 631 | }; 632 | 633 | MemberSyntax * 634 | GenerateRewriter::unrollGenSyntax(const HierarchyInstantiationSyntax &instSyn, 635 | const Scope &scope, 636 | const Scope *globalScope) { 637 | // We use the first instance symbol as a representative to get the unique 638 | // module 639 | auto instSymGeneric = 640 | getScopeMember(scope, (*instSyn.instances.begin())->decl->name.rawText()); 641 | if (!instSymGeneric && globalScope) 642 | instSymGeneric = getScopeMember( 643 | *globalScope, (*instSyn.instances.begin())->decl->name.rawText()); 644 | if (!instSymGeneric) 645 | diag.log(DiagSev::Fatal, "Could not find compilation symbol for instance", 646 | instSyn); 647 | // Check if this is a black box or non-module; if so, leave it be. 648 | // Uninstantiated modules are also left be, are pad or macros 649 | if (instSymGeneric->kind == SymbolKind::Unknown || 650 | instSymGeneric->kind == SymbolKind::UninstantiatedDef) { 651 | return clone(instSyn, alloc); 652 | } 653 | 654 | // instance arrays (interface arrays etc) do not inherit from InstanceSymbol 655 | // they inherit from Symbol directy, hence they have no isModule() 656 | // we catch failing casts and return default 657 | try { 658 | auto &instSym = instSymGeneric->as(); 659 | if (!instSym.isModule()) 660 | return clone(instSyn, alloc); 661 | } catch (const slang::assert::AssertionException &e) { 662 | return clone(instSyn, alloc); 663 | } 664 | // Identify unique module 665 | auto uniqMod = design.getUniqueModule(instSymGeneric->as()); 666 | if (!uniqMod) 667 | diag.log(DiagSev::Fatal, "Could not find unique module for instance", 668 | instSyn); 669 | // Create a new type token 670 | auto uniqNameStr = 671 | strAlloc.emplace(uniqMod->getUniqueName(design.isRecompiled()) + " "); 672 | auto uniqTypeTok = 673 | instSyn.type.withRawText(alloc, std::string_view(*uniqNameStr)); 674 | // Return reconstructed instantiation 675 | return clone(HierarchyInstantiationSyntax(instSyn.attributes, uniqTypeTok, 676 | nullptr, instSyn.instances, 677 | instSyn.semi), 678 | alloc); 679 | } 680 | 681 | template void GenerateRewriter::handleGenerate(const T &syn) { 682 | // The handler should tackle the uppermost block automatically --> handle only 683 | // if our parent is a module 684 | if (syn.parent->kind == SyntaxKind::ModuleDeclaration) { 685 | auto uniqMod = design.getUniqueModule( 686 | syn.parent->template as() 687 | .header->name.rawText()); 688 | auto &scope = 689 | uniqMod->getInstances().begin()->symbol->body.template as(); 690 | auto newSyn = unrollGenSyntax(syn, scope); 691 | replace(syn, *newSyn); 692 | } 693 | } 694 | 695 | void GenerateRewriter::handle(const IfGenerateSyntax &syn) { 696 | handleGenerate(syn); 697 | } 698 | 699 | void GenerateRewriter::handle(const LoopGenerateSyntax &syn) { 700 | handleGenerate(syn); 701 | } 702 | 703 | void GenerateRewriter::handle(const CaseGenerateSyntax &syn) { 704 | handleGenerate(syn); 705 | } 706 | 707 | void GenerateRewriter::handle(const GenerateRegionSyntax &syn) { 708 | handleGenerate(syn); 709 | } 710 | 711 | void GenerateRewriter::handle(const HierarchyInstantiationSyntax &syn) { 712 | handleGenerate(syn); 713 | } 714 | 715 | DesignUniqueModule * 716 | TypedefDeclarationRewriter::getUniqueModule(const SyntaxNode &pd) const { 717 | // Obtain module instance from either port list or root (skip if in package or 718 | // in some other config) 719 | if (!pd.parent) 720 | return nullptr; 721 | auto modNode = pd.parent; 722 | while (modNode->kind != SyntaxKind::ModuleDeclaration && modNode->parent && 723 | modNode->kind != SyntaxKind::Unknown) { 724 | modNode = modNode->parent; 725 | } 726 | if (modNode->kind == SyntaxKind::CompilationUnit) 727 | return nullptr; 728 | if (modNode->kind != SyntaxKind::ModuleDeclaration) { 729 | diag.log(DiagSev::Warning, 730 | fmt::format( 731 | "type rewrite with no parent found with unhandled parent kind " 732 | "`{}`; left unchanged", 733 | toString(modNode->kind)), 734 | pd, true); 735 | return nullptr; 736 | } 737 | // Obtain corresponding unique module 738 | auto &modSyn = modNode->as(); 739 | return design.getUniqueModule(modSyn.header->name.rawText()); 740 | } 741 | 742 | const Symbol *TypedefDeclarationRewriter::getTypeNameSymOrBail( 743 | const TypedefDeclarationSyntax *pd, DesignUniqueModule *uniqMod) const { 744 | auto memberSym = 745 | getScopeMember(uniqMod->getInstances().begin()->symbol->body.as(), 746 | pd->name.rawText()); 747 | if (memberSym == nullptr) { 748 | diag.log(DiagSev::Note, 749 | "getTypeNameSymOrBail not found in compilation; left unchanged", 750 | *pd, true); 751 | } 752 | return memberSym; 753 | } 754 | 755 | template 756 | void TypedefDeclarationRewriter::replaceTypeDeclOrBail(std::string declStr, 757 | const T &pd) { 758 | auto &newPdSyn = parse(declStr); 759 | if (newPdSyn.kind != SyntaxKind::TypedefDeclaration) { 760 | diag.log(DiagSev::Error, 761 | fmt::format( 762 | "misparsed parameter declaration as kind `{}`; left unchanged", 763 | toString(newPdSyn.kind)), 764 | pd, true); 765 | return; 766 | } 767 | auto newPdStatement = clone(newPdSyn.as(), alloc); 768 | if (newPdStatement) { 769 | replace(pd, *newPdStatement); 770 | } 771 | } 772 | 773 | void TypedefDeclarationRewriter::handle(const TypedefDeclarationSyntax &pd) { 774 | auto uniqMod = getUniqueModule(pd); 775 | if (!uniqMod) 776 | return; 777 | auto memberSym = getTypeNameSymOrBail(&pd, uniqMod); 778 | if (!memberSym) 779 | return; 780 | 781 | auto typePrinter = TypePrinter(); 782 | typePrinter.options.skipScopedTypeNames = true; 783 | typePrinter.options.fullEnumType = true; 784 | auto ¶mSym = memberSym->as(); 785 | auto &type = paramSym.getDeclaredType()->getType().getCanonicalType(); 786 | typePrinter.append(type); 787 | std::string typeStr = typePrinter.toString(); 788 | std::string declStr; 789 | 790 | // unpacked types come in the format "type$[dim]" 791 | // where the '$' is a placeholder for the name 792 | if (type.isUnpackedArray()) { 793 | // searching for $[ instead of $ since it might be possible to 794 | // have $ appear before the actual placeholder (ie in the type) 795 | std::size_t pos = typeStr.find("$["); 796 | std::string packedStr = typeStr.substr(0, pos); 797 | std::string unpackedStr = typeStr.substr(pos + 1, typeStr.length()); 798 | declStr = fmt::format("{} {}{} {};", pd.typedefKeyword.toString(), 799 | packedStr, pd.name.toString(), unpackedStr); 800 | } else { 801 | declStr = fmt::format("{} {} {};", pd.typedefKeyword.toString(), typeStr, 802 | pd.name.toString()); 803 | } 804 | typePrinter.clear(); 805 | replaceTypeDeclOrBail(declStr, pd); 806 | } 807 | 808 | DesignUniqueModule * 809 | AssignmentRewriter::getUniqueModule(const SyntaxNode &pd) const { 810 | // Obtain module instance from either port list or root (skip if in package or 811 | // in some other config) 812 | auto *modNode = &pd; 813 | while (modNode->kind != SyntaxKind::ModuleDeclaration && 814 | modNode->kind != SyntaxKind::Unknown && 815 | modNode->kind != SyntaxKind::CompilationUnit && 816 | modNode->kind != SyntaxKind::PackageDeclaration && 817 | modNode->parent != nullptr) { 818 | modNode = modNode->parent; 819 | } 820 | if (modNode->kind != SyntaxKind::ModuleDeclaration) { 821 | diag.log(DiagSev::Warning, 822 | fmt::format( 823 | "type rewrite with no parent found with unhandled parent kind " 824 | "`{}`; left unchanged", 825 | toString(modNode->kind)), 826 | pd, true); 827 | return nullptr; 828 | } 829 | // Obtain corresponding unique module 830 | auto &modSyn = modNode->as(); 831 | return design.getUniqueModule(modSyn.header->name.rawText()); 832 | } 833 | 834 | const ContinuousAssignSymbol * 835 | AssignmentRewriter::getLHSNameSymOrBail(const ContinuousAssignSyntax *pd, 836 | DesignUniqueModule *uniqMod) { 837 | if (pd->assignments.size() != 1) { 838 | diag.log(DiagSev::Warning, 839 | fmt::format( 840 | "type rewrite with no parent found with unhandled parent kind " 841 | "`{}`; left unchanged", 842 | toString(pd->kind)), 843 | *pd, true); 844 | return nullptr; 845 | } 846 | 847 | auto &scope = 848 | uniqMod->getInstances().begin()->symbol->body.template as(); 849 | const ContinuousAssignSymbol &memberSym = 850 | *(synToSym(*pd, scope)); 851 | return &memberSym; 852 | } 853 | 854 | void AssignmentRewriter::handle(const ContinuousAssignSyntax &pd) { 855 | auto uniqMod = getUniqueModule(pd); 856 | if (!uniqMod) 857 | return; 858 | auto memberSym = getLHSNameSymOrBail(&pd, uniqMod); 859 | if (!memberSym) 860 | return; 861 | 862 | auto &scope = 863 | uniqMod->getInstances().begin()->symbol->body.template as(); 864 | auto &assignExpr = memberSym->getAssignment(); 865 | if (assignExpr.kind == ExpressionKind::Invalid) { 866 | return; 867 | } 868 | auto &assign = 869 | memberSym->getAssignment().as(); 870 | auto &right = assign.right(); 871 | EvalContext ctx(scope.getCompilation(), EvalFlags::CacheResults); 872 | ConstantValue constant = right.eval(ctx); 873 | if (!constant.bad()) { 874 | auto exprStr = constant.toString(SVInt::MAX_BITS, true, true); 875 | auto &left = assign.left(); 876 | auto lhsStr = left.syntax->toString(); 877 | auto newAssignStr = fmt::format("\nassign {}= {};", lhsStr, exprStr); 878 | 879 | auto &newContAssign = parse(newAssignStr); 880 | if (newContAssign.kind != SyntaxKind::ContinuousAssign) { 881 | diag.log( 882 | DiagSev::Error, 883 | fmt::format("misparsed ContinuousAssign as kind `{}`; left unchanged", 884 | toString(newContAssign.kind)), 885 | pd, true); 886 | return; 887 | } 888 | replace(pd, newContAssign); 889 | } 890 | } 891 | 892 | } // namespace svase -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Paul Scheffler 6 | 7 | build_dir ?= build 8 | 9 | all: pickles 10 | 11 | pickles: test_deps 12 | 13 | test_deps: 14 | cargo install bender morty 15 | pip install mako hjson jsonref jsonschema 16 | 17 | $(build_dir): 18 | mkdir -p $@ 19 | 20 | # include snitch.mk 21 | # include idma.mk 22 | # include cva6.mk 23 | include cheshire.mk -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | ## Possible Approach 4 | Include the unprocessed source `test.sv` and the reference-output `test_ref.sv`. 5 | 6 | 1. run `svase test test_out.sv test.sv` 7 | 2. either `diff` the `*.sv` files or `diff` their ast-json representation 8 | 9 | For ast-json: 10 | ```sh 11 | slang test_ref.sv --ast-json test_ref.json 12 | jq 'walk(if type == "object" then del(.["addr", "symbol"]) else . end)' test_ref.json > test_ref2.json 13 | 14 | slang test_out.sv --ast-json test_out.json 15 | jq 'walk(if type == "object" then del(.["addr", "symbol"]) else . end)' test_out.json > test_out2.json 16 | 17 | diff test_ref.json test_out.json 18 | ``` -------------------------------------------------------------------------------- /test/assign/assign.sv: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Author: Philippe Sauter 6 | // 7 | // Test the following ... 8 | 9 | module test #( ) ( ); 10 | logic [31:0] bit5_hot; 11 | logic [31:0] bit12_hot; 12 | logic [31:0] bits; 13 | 14 | assign bits = 32'h0000aaee; 15 | assign bit5_hot = nth_bit(5); 16 | assign bit12_hot = nth_bit(12); 17 | 18 | function automatic logic[31:0] nth_bit(input int unsigned bit_pos); 19 | logic[31:0] shifted; 20 | shifted = (32'd01 << bit_pos); 21 | 22 | return shifted; 23 | endfunction 24 | endmodule -------------------------------------------------------------------------------- /test/cheshire.mk: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Jannis Schönleber 6 | 7 | cheshire_build_dir = $(build_dir)/cheshire 8 | cheshire_rev = 9b6c99a749cbba857aabe41fec10830ef0c07b56 9 | 10 | $(cheshire_build_dir): | $(build_dir) 11 | rm -rf $@ 12 | git clone https://github.com/pulp-platform/cheshire.git $@ 13 | cd $@ && git checkout $(cheshire_rev) 14 | cd $@ && git submodule update --init --recursive 15 | cd $@ && make all 16 | cd $@ && make slang-check-cheshire 17 | 18 | $(build_dir)/cheshire_top.pickle.sv: | $(cheshire_build_dir) 19 | cp $(cheshire_build_dir)/build/cheshire_top.pickle.sv $@ 20 | 21 | pickles: $(build_dir)/cheshire_top.pickle.sv -------------------------------------------------------------------------------- /test/cva6.mk: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Jannis Schönleber 6 | 7 | 8 | cva6_build_dir = $(build_dir)/cva6 9 | cva6_rev = fbfc417b9285ab9ec40470ce5269802b1dcc96a6 10 | 11 | $(cva6_build_dir): | $(build_dir) 12 | rm -rf $@ 13 | git clone https://github.com/pulp-platform/cva6.git $@ 14 | cd $@ && git checkout $(cva6_rev) 15 | cd $@ && git submodule update --init --recursive 16 | 17 | $(build_dir)/cva6.pickle.sv: | $(cva6_build_dir) 18 | bender sources -f -d $(cva6_build_dir) -t cv64a6_imafdc_sv39 -t synthesis | morty -f /dev/stdin -D VERILATOR=1 -o $@ 19 | 20 | $(build_dir)/cva6_top.pickle.sv: | $(cva6_build_dir) 21 | bender sources -f -d $(cva6_build_dir) -t cv64a6_imafdc_sv39 -t synthesis | morty -f /dev/stdin -D VERILATOR=1 -o $@ --top cva6 22 | 23 | pickles: $(build_dir)/cva6.pickle.sv $(build_dir)/cva6_top.pickle.sv 24 | -------------------------------------------------------------------------------- /test/idma.mk: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Paul Scheffler 6 | 7 | 8 | # Repository 9 | 10 | idma_build_dir = $(build_dir)/idma 11 | idma_rev = 591f9fcf5ec9b5d8bcf1137c44b9f704e059050e 12 | 13 | $(idma_build_dir): | $(build_dir) 14 | rm -rf $@ 15 | git clone https://github.com/pulp-platform/iDMA.git $@ 16 | cd $@ && git checkout $(idma_rev) 17 | 18 | # iDMA synthesis wrapper 19 | 20 | $(build_dir)/idma_backend_synth.pickle.sv: | $(idma_build_dir) 21 | bender sources -t synthesis -f -d $(idma_build_dir) | morty -f /dev/stdin -o $@ 22 | 23 | pickles: $(build_dir)/idma_backend_synth.pickle.sv 24 | 25 | $(build_dir)/idma_backend_synth.top.pickle.sv: | $(idma_build_dir) 26 | bender sources -t synthesis -f -d $(idma_build_dir) | morty -f /dev/stdin --top idma_backend_synth -o $@ 27 | 28 | bla: $(build_dir)/idma_backend_synth.top.pickle.sv 29 | 30 | # iDMA nD synthesis wrapper 31 | 32 | $(build_dir)/idma_nd_backend_synth.pickle.sv: | $(idma_build_dir) 33 | bender sources -t synthesis -f -d $(idma_build_dir) | morty -f /dev/stdin -o $@ 34 | 35 | pickles: $(build_dir)/idma_nd_backend_synth.pickle.sv 36 | -------------------------------------------------------------------------------- /test/param/param-in-gen.sv: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Author: Philippe Sauter 6 | // 7 | // Test the following parameter propagation scenarios: 8 | // - into generate-if scope 9 | // - into iterations of generate-loop 10 | 11 | module test #( ) ( ); 12 | localparam int unsigned IfElseParam = 2; 13 | localparam int unsigned ModuleParam = 32'd5; 14 | 15 | if (IfElseParam == unsigned'(1)) begin 16 | localparam int unsigned GenParam = unsigned'($clog2(ModuleParam)); 17 | end else begin 18 | localparam int unsigned GenParam = unsigned'(ModuleParam); 19 | 20 | for (genvar k = 0; k < GenParam; k++) begin : gen_for_outer 21 | localparam int unsigned OuterForParam = 2**k; 22 | 23 | for (genvar l = 0; l < 2**k; l++) begin : gen_for_inner 24 | localparam int unsigned InnerForParam = 2**k+l; 25 | end 26 | end 27 | end 28 | endmodule -------------------------------------------------------------------------------- /test/param/param-instance.sv: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Author: Philippe Sauter 6 | // 7 | // Test the following parameter propagation scenarios: 8 | // - to port of instantiated module 9 | // - to param without default (has no initializer) 10 | 11 | module test2 #( 12 | parameter int unsigned PortParam = 0, 13 | parameter int unsigned NoDefaultParam, 14 | parameter type PortTypeParam = logic 15 | ) ( ); 16 | localparam int unsigned Module2Param = PortParam; 17 | PortTypeParam used_type; 18 | endmodule 19 | 20 | 21 | module test #( ) ( ); 22 | localparam int unsigned TopParam = 32'd5; 23 | localparam type TypeParam = logic[1:0][31:0]; 24 | 25 | test2 #( 26 | .PortParam(TopParam), 27 | .NoDefaultParam(TopParam), 28 | .PortTypeParam(TypeParam) 29 | ) i_test2 ( ); 30 | endmodule 31 | -------------------------------------------------------------------------------- /test/param/port-to-module.sv: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Author: Philippe Sauter 6 | // 7 | // Test the following parameter propagation scenarios: 8 | // - from port to module scope 9 | // - from module to derived 10 | 11 | module test #( 12 | parameter int unsigned PortParam1 = 32'd5, 13 | parameter int unsigned PortParam2 = 32'd8 14 | ) ( ); 15 | localparam int unsigned TopParam1 = unsigned'($clog2(PortParam1)); 16 | localparam int unsigned TopParam2 = unsigned'($clog2(PortParam2)); 17 | 18 | localparam int unsigned DerivParam = 2**TopParam1; 19 | endmodule -------------------------------------------------------------------------------- /test/snitch.mk: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Author: Paul Scheffler 6 | 7 | # Repository 8 | 9 | snitch_build_dir = $(build_dir)/snitch 10 | # snitch_rev = 18b0d683b03a308c1942ed764eeb1523c622379d 11 | snitch_rev = 1f2a396b70584b0dd9f4ee595501ec5db5d60024 12 | # fatal: reference is not a tree: 18b0d683b03a308c1942ed764eeb1523c622379d 13 | 14 | $(snitch_build_dir): | $(build_dir) 15 | rm -rf $@ 16 | git clone https://github.com/pulp-platform/snitch.git $@ 17 | cd $@ && git checkout $(snitch_rev) 18 | 19 | # Snitch cluster 20 | 21 | snitch_cluster_dir = $(shell realpath -q $(snitch_build_dir)/hw/system/snitch_cluster) 22 | 23 | $(build_dir)/snitch_cluster_wrapper.pickle.sv: | $(snitch_build_dir) 24 | make -BC $(snitch_cluster_dir) $(snitch_cluster_dir)/generated/snitch_cluster_wrapper.sv 25 | bender sources -f -d $(snitch_cluster_dir) | morty -f /dev/stdin -o $@ 26 | 27 | $(build_dir)/snitch_cluster_wrapper_top.pickle.sv: | $(snitch_build_dir) 28 | make -BC $(snitch_cluster_dir) $(snitch_cluster_dir)/generated/snitch_cluster_wrapper.sv 29 | bender sources -f -d $(snitch_cluster_dir) | morty -f /dev/stdin --top snitch_cluster_wrapper -o $@ 30 | 31 | bla: $(build_dir)/snitch_cluster_wrapper_top.pickle.sv 32 | 33 | 34 | pickles: $(build_dir)/snitch_cluster_wrapper.pickle.sv $(build_dir)/snitch_cluster_wrapper_top.pickle.sv 35 | 36 | # Occamy 37 | 38 | $(build_dir)/occamy_top.pickle.sv: | $(snitch_build_dir) 39 | bender sources -f -d $(snitch_build_dir)/hw/system/occamy) | morty -f /dev/stdin -o $@ 40 | 41 | # TODO @paulsc: makes morty stack-overflow 42 | #pickles: $(build_dir)/occamy_top.pickle.sv 43 | --------------------------------------------------------------------------------