diff --git a/.github/actions/artifact-cache/action.yml b/.github/actions/artifact-cache/action.yml new file mode 100644 index 000000000..0fcc4448b --- /dev/null +++ b/.github/actions/artifact-cache/action.yml @@ -0,0 +1,165 @@ +--- +# DESCRIPTION: Github actions composite action +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +name: Artifact cache +description: >- + Cache a directory across workflow runs using a GitHub Actions artifact instead + of actions/cache (useful where artifacts are preferable, e.g. no 10 GB limit + on public repos). The base 'key' is scoped automatically: per pull request + (-pr-) or per branch (-branch-), and on a PR restore falls + back to the target branch's cache. Call once with mode=restore before the work + and once with mode=save after, passing the same 'key' and 'path'. + + Branch-scoped restores are provenance-checked (the artifact must come from a + same-repo push to that branch) - artifact names are a global, unauthenticated + namespace, so otherwise a fork PR could forge one. + + mode=restore calls the GitHub API via 'gh', so the caller's job must grant + 'actions: read'. + +inputs: + mode: + description: "'restore' or 'save'" + required: true + path: + description: "Directory to cache (archived on save, extracted into on restore)" + required: true + key: + description: "Base artifact name; scoped per PR/branch automatically" + required: true + retention-days: + description: "Artifact retention in days (mode=save)" + required: false + default: '3' + token: + description: "Token for the GitHub API (mode=restore); defaults to the job's GITHUB_TOKEN" + required: false + default: '' + +outputs: + key: + description: "Echoes the input 'key', so a later save can reuse it without repeating the expression" + value: ${{ inputs.key }} + cache-hit: + description: "'true' if an artifact was restored (mode=restore)" + value: ${{ steps.restore.outputs.cache-hit }} + +runs: + using: composite + steps: + - name: Check mode + shell: bash + env: + MODE: ${{ inputs.mode }} + run: | + case "$MODE" in + restore|save) ;; + *) echo "::error::artifact-cache: 'mode' must be 'restore' or 'save' (got '$MODE')"; exit 1 ;; + esac + + # Scope the base key: -pr- for pull requests (with the target branch + # as fallback), else -branch-. Refs come via env, not ${{ }} + # interpolation, so a branch name with shell metacharacters can't inject. + - name: Compute artifact name + id: name + shell: bash + env: + BASE_KEY: ${{ inputs.key }} + EVENT_NAME: ${{ github.event_name }} + PR_NUMBER: ${{ github.event.pull_request.number }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + sanitize() { printf '%s' "$1" | tr '/' '-'; } # artifact names cannot contain '/' + if [ "$EVENT_NAME" = pull_request ]; then + { + echo "primary=${BASE_KEY}-pr-${PR_NUMBER}" + echo "primary-branch=" + echo "fallback=${BASE_KEY}-branch-$(sanitize "$BASE_REF")" + echo "fallback-branch=$BASE_REF" + } >> "$GITHUB_OUTPUT" + else + { + echo "primary=${BASE_KEY}-branch-$(sanitize "$REF_NAME")" + echo "primary-branch=$REF_NAME" + echo "fallback=" + echo "fallback-branch=" + } >> "$GITHUB_OUTPUT" + fi + + - name: Restore artifact cache + id: restore + if: ${{ inputs.mode == 'restore' }} + continue-on-error: true # a cold start is fine; a restore failure must not fail the caller + shell: bash + env: + GH_TOKEN: ${{ inputs.token || github.token }} + CACHE_PATH: ${{ inputs.path }} + PRIMARY: ${{ steps.name.outputs.primary }} + PRIMARY_BRANCH: ${{ steps.name.outputs.primary-branch }} + FALLBACK: ${{ steps.name.outputs.fallback }} + FALLBACK_BRANCH: ${{ steps.name.outputs.fallback-branch }} + run: | + set -euo pipefail + mkdir -p "$CACHE_PATH" + # Newest non-expired artifact named $1 (empty if none). With a non-empty + # $2 (branch), require provenance: a same-repo run (head_repository_id == + # repository_id) whose head branch is $2, so a fork cannot forge it. + newest_run_id() { + want="$1" br="$2" gh api \ + "/repos/$GITHUB_REPOSITORY/actions/artifacts?name=$1&per_page=100" \ + --jq '.artifacts[] + | select(.expired == false and .name == env.want) + | select(env.br == "" + or (.workflow_run.head_branch == env.br + and .workflow_run.head_repository_id == .workflow_run.repository_id)) + | [.created_at, (.workflow_run.id | tostring)] | @tsv' \ + | sort | tail -n1 | cut -f2 + } + restore() { # $1=name $2=branch(empty ok); returns 0 on a successful restore + local name="$1" br="$2" rid tarball + [ -n "$name" ] || return 1 + rid="$(newest_run_id "$name" "$br")" || return 1 + [ -n "$rid" ] || return 1 + echo "Restoring '$CACHE_PATH' from artifact '$name' (run $rid)" + gh run download "$rid" -R "$GITHUB_REPOSITORY" --name "$name" --dir "$RUNNER_TEMP/artifact-cache-dl" || return 1 + # Each artifact holds a single tarball; match by extension so the inner + # filename is not part of the contract. + tarball="$(find "$RUNNER_TEMP/artifact-cache-dl" -name '*.tar.zst' -print -quit)" + [ -n "$tarball" ] || return 1 + tar -I zstd -x -f "$tarball" -C "$CACHE_PATH" + } + if restore "$PRIMARY" "$PRIMARY_BRANCH"; then + echo "Restored from primary key" + echo "cache-hit=true" >> "$GITHUB_OUTPUT" + elif restore "$FALLBACK" "$FALLBACK_BRANCH"; then + echo "Restored from fallback key" + echo "cache-hit=true" >> "$GITHUB_OUTPUT" + else + echo "No matching artifact found; starting cold" + echo "cache-hit=false" >> "$GITHUB_OUTPUT" + fi + + - name: Pack artifact cache + id: pack + if: ${{ inputs.mode == 'save' }} + shell: bash + env: + CACHE_PATH: ${{ inputs.path }} + run: | + set -euo pipefail + packdir="$(mktemp -d)" # unique dir so concurrent saves cannot collide + tar -I 'zstd -T0' -cf "$packdir/cache.tar.zst" -C "$CACHE_PATH" . + echo "tarball=$packdir/cache.tar.zst" >> "$GITHUB_OUTPUT" + + - name: Upload artifact cache + if: ${{ inputs.mode == 'save' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: ${{ steps.name.outputs.primary }} + path: ${{ steps.pack.outputs.tarball }} + retention-days: ${{ inputs.retention-days }} + overwrite: true + compression-level: 0 # tarball is already zstd-compressed diff --git a/.github/actions/setup-venv/action.yml b/.github/actions/setup-venv/action.yml new file mode 100644 index 000000000..3feb9c5be --- /dev/null +++ b/.github/actions/setup-venv/action.yml @@ -0,0 +1,56 @@ +--- +# DESCRIPTION: Github actions composite action +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +name: Set up Python venv +description: >- + Restore, create, and optionally save the make-managed Python venv in a GitHub + Actions cache. The cache key combines the venv path (venvs are not + relocatable), the host OS and version, the Python major.minor version, and + the python-dev-requirements.txt hash, so any of those changing invalidates + it. Requires the already-configured Verilator tree to be present in 'repo'. + +inputs: + repo: + description: "Verilator checkout holding the Makefile and .venv" + required: false + default: repo + save: + description: "Save the cache on a miss (set true only on trusted branches)" + required: false + default: 'false' + +runs: + using: composite + steps: + - name: Compute Python venv cache key + id: cachekey + shell: bash + working-directory: ${{ inputs.repo }} + run: | + source ci/ci-common.bash + host="${DISTRO_ID:-$HOST_OS}${DISTRO_VERSION:+-$DISTRO_VERSION}" + pyver="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')" + reqs="$(sha256sum python-dev-requirements.txt | cut -d' ' -f1)" + venv="${PWD#/}/.venv" # absolute venv path (leading '/' trimmed) + echo "key=venv-${host}-py${pyver}-${reqs}-${venv//\//-}" >> "$GITHUB_OUTPUT" + + - name: Restore Python venv + id: venv + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 + with: + path: ${{ inputs.repo }}/.venv + key: ${{ steps.cachekey.outputs.key }} + + - name: Create Python venv + if: ${{ steps.venv.outputs.cache-hit != 'true' }} + shell: bash + working-directory: ${{ inputs.repo }} + run: make venv + + - name: Save Python venv + if: ${{ inputs.save == 'true' && steps.venv.outputs.cache-hit != 'true' }} + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 + with: + path: ${{ inputs.repo }}/.venv + key: ${{ steps.venv.outputs.cache-primary-key }} diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 772fc1c95..163669117 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -16,6 +16,7 @@ on: permissions: contents: read + actions: read defaults: run: @@ -28,259 +29,143 @@ concurrency: jobs: - build-2404-gcc: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + build-2604-gcc: + name: Build | 26.04 | gcc uses: ./.github/workflows/reusable-build.yml with: + cc: gcc + runs-on: ubuntu-26.04 + sha: ${{ github.sha }} + + build-2604-clang: + name: Build | 26.04 | clang + uses: ./.github/workflows/reusable-build.yml + with: + cc: clang + dev-asan: true # Build (and run) with address sanitizer + runs-on: ubuntu-26.04 + sha: ${{ github.sha }} + + build-2404-gcc: + name: Build | 24.04 | gcc + uses: ./.github/workflows/reusable-build.yml + with: + cc: gcc + runs-on: ubuntu-24.04 sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-24.04, cc: gcc, asan: 0} build-2404-clang: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + name: Build | 24.04 | clang uses: ./.github/workflows/reusable-build.yml with: + cc: clang + runs-on: ubuntu-24.04 sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-24.04, cc: clang, asan: 1} build-2204-gcc: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + name: Build | 22.04 | gcc uses: ./.github/workflows/reusable-build.yml with: + cc: gcc + runs-on: ubuntu-22.04 sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-22.04, cc: gcc, asan: 0} - build-2204-clang: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + build-macos-15-clang: + name: Build | macos-15 | clang uses: ./.github/workflows/reusable-build.yml with: + cc: clang + runs-on: macos-15 sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-22.04, cc: clang, asan: 0} - - build-osx-gcc: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} - uses: ./.github/workflows/reusable-build.yml - with: - sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: osx - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: macos-15, cc: gcc, asan: 0} - - build-osx-clang: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} - uses: ./.github/workflows/reusable-build.yml - with: - sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: osx - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: macos-15, cc: clang, asan: 0} build-windows: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - {os: windows-2025-vs2026, cc: msvc} - env: - CI_OS_NAME: win - CCACHE_COMPRESS: 1 - CCACHE_DIR: ${{ github.workspace }}/.ccache - CCACHE_LIMIT_MULTIPLE: 0.95 + name: Build | windows-2025-vs2026 | msvc + runs-on: windows-2025-vs2026 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo - - name: Cache $CCACHE_DIR - uses: actions/cache@v5 + - name: Cache win_flex_bison + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 with: - path: ${{ env.CCACHE_DIR }} - key: msbuild-msvc-cmake + path: ${{ github.workspace }}/win_flex_bison + key: win_flex_bison - name: compile - env: - WIN_FLEX_BISON: ${{ github.workspace }}/.ccache run: ./ci/ci-win-compile.ps1 - name: test build run: ./ci/ci-win-test.ps1 - - name: Zip up repository - run: Compress-Archive -LiteralPath install -DestinationPath verilator.zip - - name: Upload zip archive - uses: actions/upload-artifact@v7 - with: - path: ${{ github.workspace }}/repo/verilator.zip - name: verilator-win.zip + + test-2604-gcc: + name: Test | 26.04 | gcc | ${{ matrix.suite }} + needs: build-2604-gcc + uses: ./.github/workflows/reusable-test.yml + with: + archive: ${{ needs.build-2604-gcc.outputs.archive }} + cc: gcc + runs-on: ubuntu-26.04 + suite: ${{ matrix.suite }} + strategy: + fail-fast: false + matrix: + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] + + test-2604-clang: + name: Test | 26.04 | clang | ${{ matrix.suite }} + needs: build-2604-clang + uses: ./.github/workflows/reusable-test.yml + with: + archive: ${{ needs.build-2604-clang.outputs.archive }} + cc: clang + runs-on: ubuntu-26.04 + suite: ${{ matrix.suite }} + strategy: + fail-fast: false + matrix: + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] test-2404-gcc: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + name: Test | 24.04 | gcc | ${{ matrix.suite }} needs: build-2404-gcc uses: ./.github/workflows/reusable-test.yml with: archive: ${{ needs.build-2404-gcc.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} + cc: gcc + runs-on: ubuntu-24.04 suite: ${{ matrix.suite }} - dev-gcov: 0 strategy: fail-fast: false matrix: - include: - # Ubuntu 24.04 gcc - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-0} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-1} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-2} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-3} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-0} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-1} - - {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-2} + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] test-2404-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + name: Test | 24.04 | clang | ${{ matrix.suite }} needs: build-2404-clang uses: ./.github/workflows/reusable-test.yml with: archive: ${{ needs.build-2404-clang.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} + cc: clang + reloc: true # Test with relocated installation + runs-on: ubuntu-24.04 suite: ${{ matrix.suite }} - dev-gcov: 0 strategy: fail-fast: false matrix: - include: - # Ubuntu 24.04 clang - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-0} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-1} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-2} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-3} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-0} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-1} - - {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-2} + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] test-2204-gcc: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + name: Test | 22.04 | gcc | ${{ matrix.suite }} needs: build-2204-gcc uses: ./.github/workflows/reusable-test.yml with: archive: ${{ needs.build-2204-gcc.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} + cc: gcc + runs-on: ubuntu-22.04 suite: ${{ matrix.suite }} - dev-gcov: 0 strategy: fail-fast: false matrix: - include: - # Ubuntu 22.04 gcc - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-0} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-1} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-2} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-3} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-0} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-1} - - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-2} - - test-2204-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} - needs: build-2204-clang - uses: ./.github/workflows/reusable-test.yml - with: - archive: ${{ needs.build-2204-clang.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} - suite: ${{ matrix.suite }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - # Ubuntu 22.04 clang, also test relocation - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-0} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-1} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-2} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-3} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-0} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-1} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-2} + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] lint-py: name: Lint Python uses: ./.github/workflows/reusable-lint-py.yml - - passed: - name: Test suite passed - if: always() - needs: - - build-2404-gcc - - build-2404-clang - - build-2204-gcc - - build-2204-clang - - build-osx-gcc - - build-osx-clang - - build-windows - - test-2404-gcc - - test-2404-clang - - test-2204-gcc - - test-2204-clang - - lint-py - - runs-on: ubuntu-24.04 - - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/contributor.yml b/.github/workflows/contributor.yml index 02549bf76..4fef0fd66 100644 --- a/.github/workflows/contributor.yml +++ b/.github/workflows/contributor.yml @@ -16,5 +16,5 @@ jobs: name: "'docs/CONTRIBUTORS' was signed" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - run: test_regress/t/t_dist_contributors.py diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 439143c85..5ff39b99b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -13,6 +13,7 @@ on: permissions: contents: read + actions: read defaults: run: @@ -38,15 +39,13 @@ jobs: (github.event_name == 'workflow_dispatch') uses: ./.github/workflows/reusable-build.yml with: + cc: gcc + dev-gcov: true + runs-on: ubuntu-24.04 # For pull requests, build the head of the pull request branch, not the # merge commit, otherwise patch coverage would include the changes # between the root of the pull request and the target branch sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - os: ubuntu-24.04 - os-name: linux - cc: gcc - dev-asan: 0 - dev-gcov: 1 test: name: Test | ${{ matrix.test }}${{ matrix.num }} @@ -54,11 +53,10 @@ jobs: uses: ./.github/workflows/reusable-test.yml with: archive: ${{ needs.build.outputs.archive }} - os: ubuntu-24.04 cc: gcc - reloc: 0 + dev-gcov: true + runs-on: ubuntu-24.04 suite: ${{ matrix.test }}${{ matrix.num }} - dev-gcov: 1 strategy: fail-fast: false matrix: @@ -74,10 +72,10 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Download code coverage data - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: code-coverage-* path: obj_coverage @@ -90,7 +88,7 @@ jobs: find obj_coverage -type f | paste -sd, | sed "s/^/files=/" >> "$GITHUB_OUTPUT" - name: Upload to codecov.io - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7 with: disable_file_fixes: true disable_search: true @@ -114,18 +112,18 @@ jobs: sudo apt install lcov - name: Download repository archive - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ needs.build.outputs.archive }} path: ${{ github.workspace }} - name: Unpack repository archive run: | - tar -x -z -f ${{ needs.build.outputs.archive }} + tar --zstd -x -f ${{ needs.build.outputs.archive }} ls -lsha - name: Download code coverage data - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: code-coverage-* path: repo/obj_coverage @@ -172,14 +170,14 @@ jobs: fi - name: Upload report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: repo/obj_coverage name: coverage-report - name: Upload notification if: ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: repo/notification name: pr-notification @@ -195,9 +193,9 @@ jobs: # Creating issues requires elevated privilege - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 278a8aeb9..a059439e2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Extract context variables run: | @@ -54,7 +54,7 @@ jobs: - name: Docker meta id: docker_meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 with: images: | ${{ vars.DOCKER_HUB_NAMESPACE }}/${{ env.image_name }} @@ -64,21 +64,21 @@ jobs: type=raw,value=latest,enable=${{ inputs.add_latest_tag == true }} - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 with: buildkitd-flags: --debug - name: Login to Docker Hub - uses: docker/login-action@v4 + uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4 with: username: ${{ secrets.DOCKER_HUB_USER }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Build and Push to Docker - uses: docker/build-push-action@v7 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' with: context: ${{ env.build_context }} diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index eefc544a1..78fd4130a 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -11,35 +11,44 @@ on: permissions: contents: write +defaults: + run: + working-directory: repo + jobs: format: runs-on: ubuntu-24.04 name: Ubuntu 24.04 | format - env: - CI_OS_NAME: linux - CI_RUNS_ON: ubuntu-24.04 - CI_COMMIT: ${{ github.sha }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: + path: repo token: ${{ secrets.GITHUB_TOKEN }} + - name: Install packages for build - env: - CI_BUILD_STAGE_NAME: build run: | sudo apt install clang-format-18 || \ sudo apt install clang-format-18 git config --global user.email "action@example.com" git config --global user.name "github action" - - name: Format code + + - name: Configure run: | autoconf ./configure - make venv + + - name: Set up Python venv + uses: ./repo/.github/actions/setup-venv + with: + save: ${{ github.ref == 'refs/heads/master' }} + + - name: Format code + run: | source .venv/bin/activate make -j 4 format CLANGFORMAT=clang-format-18 git status + - name: Push run: |- if [ -n "$(git status --porcelain)" ]; then diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 4d1523ac2..e1f258832 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -7,7 +7,7 @@ name: Pages on: push: branches: [master] - paths: ["ci/**", ".github/workflows"] + paths: ["ci/**", ".github/workflows/**"] workflow_dispatch: workflow_run: workflows: ["Code coverage", "RTLMeter"] @@ -22,8 +22,11 @@ permissions: # Allow only one concurrent deployment, skipping runs queued between the run # in-progress and latest queued. However, do NOT cancel in-progress runs as we # want to allow these deployments to complete. +# A skipped-upstream run does no work (see the build job's if:), so put it in its +# own throwaway group: otherwise, as the newest queued run, it would cancel a +# pending real run and then deploy nothing. concurrency: - group: "pages" + group: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'skipped') && format('pages-skip-{0}', github.run_id) || 'pages' }} cancel-in-progress: false defaults: @@ -34,11 +37,15 @@ jobs: build: name: Build content runs-on: ubuntu-24.04 + # A skipped upstream run (e.g. Code coverage / RTLMeter on a branch push) + # still fires workflow_run; don't rebuild and redeploy for it. deploy and + # notify need this job, so they cascade-skip too. + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion != 'skipped' }} outputs: pr-run-ids: ${{ steps.build.outputs.pr-run-ids }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Build pages id: build env: @@ -48,7 +55,7 @@ jobs: ls -lsha tree -L 3 pages - name: Upload pages artifact - uses: actions/upload-pages-artifact@v5 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 with: path: pages @@ -58,10 +65,17 @@ jobs: runs-on: ubuntu-24.04 environment: name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + url: ${{ steps.deploy-2.outputs.page_url || steps.deploy-1.outputs.page_url }} steps: + # GitHub's Pages backend intermittently fails a deployment mid-sync, retry - name: Deploy to GitHub Pages - uses: actions/deploy-pages@v5 + id: deploy-1 + continue-on-error: true + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 + - name: Deploy to GitHub Pages (retry) + id: deploy-2 + if: steps.deploy-1.outcome == 'failure' + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 notify: name: Notify @@ -70,13 +84,13 @@ jobs: if: ${{ github.repository == 'verilator/verilator' }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 # Use the Verilator CI app to post the comment - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} permission-actions: write permission-pull-requests: write diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 7138a7cce..884d1a1f9 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -7,37 +7,47 @@ name: reusable-build on: workflow_call: inputs: + cc: + description: "Compiler to use: 'gcc' or 'clang'" + required: true + type: string + ccwarn: + description: "Build Verilator with warnings treated as errors (--enable-ccwarn)" + required: false + default: true + type: boolean + dev-asan: + description: "Build Verilator with the address sanitizer (--enable-dev-asan)" + required: false + default: false + type: boolean + dev-gcov: + description: "Build Verilator with gcov instrumentation (--enable-dev-gcov)" + required: false + default: false + type: boolean + install: + description: "Archive the Verilator installation, not the repo tree" + required: false + default: false + type: boolean + runs-on: + description: "Runner to build on, e.g. ubuntu-24.04" + required: true + type: string sha: description: "Commit SHA to build" required: true type: string - os: # e.g. ubuntu-24.04 - required: true - type: string - cc: # 'clang' or 'gcc' - required: true - type: string - os-name: # 'linux' or 'osx' - required: true - type: string - dev-asan: - required: true - type: number - dev-gcov: - required: true - type: number outputs: archive: - description: "Name of the built repository archive artifact" + description: "Name of the built archive artifact" value: ${{ jobs.build.outputs.archive }} env: - CI_OS_NAME: ${{ inputs.os-name }} - CCACHE_COMPRESS: 1 + CACHE_BASE_KEY: build-${{ inputs.runs-on }}-${{ inputs.cc }}${{ inputs.ccwarn && '-ccwarn' || '' }}${{ inputs.dev-asan && '-asan' || '' }}${{ inputs.dev-gcov && '-gcov' || '' }} + CCACHE_COMPILERCHECK: content CCACHE_DIR: ${{ github.workspace }}/.ccache - CCACHE_LIMIT_MULTIPLE: 0.95 - INSTALL_DIR: ${{ github.workspace }}/install - RELOC_DIR: ${{ github.workspace }}/relloc defaults: run: @@ -48,52 +58,72 @@ jobs: build: name: Build - runs-on: ${{ inputs.os }} + runs-on: ${{ inputs.runs-on }} outputs: archive: ${{ steps.create-archive.outputs.archive }} - env: - CI_BUILD_STAGE_NAME: build - CI_DEV_ASAN: ${{ inputs.dev-asan }} - CI_DEV_GCOV: ${{ inputs.dev-gcov }} - CI_RUNS_ON: ${{ inputs.os }} - CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} - CACHE_BASE_KEY: build-${{ inputs.os }}-${{ inputs.cc }} - CCACHE_MAXSIZE: 1000M # Per build matrix entry (* 5 = 5000M in total) steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo ref: ${{ inputs.sha }} - fetch-depth: ${{ inputs.dev-gcov && '0' || '1' }} # Coverage flow needs full history + # Coverage needs full history; install needs it for the 'git describe' + # behind 'verilator --version' + fetch-depth: ${{ (inputs.install || inputs.dev-gcov) && '0' || '1' }} + + - name: Configure ccache + run: | + # ccache is unreliable on macOS, and does nothing with 'gcc --coverage' + if [ "${{ startsWith(inputs.runs-on, 'macos') }}" = true ] || [ "${{ inputs.dev-gcov }}" = true ]; then + echo "CCACHE_DISABLE=1" >> "$GITHUB_ENV" + fi - name: Cache $CCACHE_DIR - uses: actions/cache@v5 + if: ${{ env.CCACHE_DISABLE != '1' }} + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache with: path: ${{ env.CCACHE_DIR }} key: ${{ env.CACHE_KEY }}-${{ inputs.sha }} restore-keys: | - ${{ env.CACHE_KEY }}- + ${{ env.CACHE_KEY }} - name: Install packages for build - run: ./ci/ci-install.bash + run: ./ci/ci-install.bash build - name: Build - run: ./ci/ci-script.bash + run: | + ./ci/ci-build.bash \ + --prefix ${{ github.workspace }}/install \ + --compiler ${{ inputs.cc }} \ + ${{ inputs.ccwarn && '--ccwarn' || '' }} \ + ${{ inputs.dev-asan && '--asan' || '' }} \ + ${{ inputs.dev-gcov && '--gcov' || '' }} - - name: Create repository archive + - name: Install + if: ${{ inputs.install }} + run: make install + + - name: Create archive id: create-archive working-directory: ${{ github.workspace }} run: | - # Name of the archive must be unique based on the build parameters - ARCHIVE=verilator-${{ inputs.sha }}-${{ inputs.os }}-${{ inputs.cc }}-${{ inputs.dev-asan }}-${{ inputs.dev-gcov }}.tar.gz - tar --posix -c -z -f $ARCHIVE repo + # Archive name, unique per build; flavour tags mark non-default builds + ARCHIVE="verilator-${{ inputs.sha }}-${{ inputs.runs-on }}-${{ inputs.cc }}" + ARCHIVE="$ARCHIVE${{ inputs.ccwarn && '-ccwarn' || '' }}" + ARCHIVE="$ARCHIVE${{ inputs.dev-asan && '-asan' || '' }}" + ARCHIVE="$ARCHIVE${{ inputs.dev-gcov && '-gcov' || '' }}" + ARCHIVE="$ARCHIVE.tar.zst" + # zstd compresses faster than gzip and decompresses far faster in the many downstream jobs + ZSTD_NBTHREADS=0 tar --posix --zstd -c -f "$ARCHIVE" ${{ inputs.install && 'install' || 'repo' }} echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT" - - name: Upload repository archive - uses: actions/upload-artifact@v7 + - name: Upload archive + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ github.workspace }}/${{ steps.create-archive.outputs.archive }} name: ${{ steps.create-archive.outputs.archive }} + overwrite: true + # Archive is already zstd-compressed; skip upload-artifact's zip pass + compression-level: 0 diff --git a/.github/workflows/reusable-lint-py.yml b/.github/workflows/reusable-lint-py.yml index f4ad9b6e8..e3bea6fe7 100644 --- a/.github/workflows/reusable-lint-py.yml +++ b/.github/workflows/reusable-lint-py.yml @@ -7,14 +7,6 @@ name: reusable-lint-py on: workflow_call: -env: - CI_OS_NAME: linux - CI_BUILD_STAGE_NAME: build - CI_RUNS_ON: ubuntu-22.04 - CCACHE_COMPRESS: 1 - CCACHE_DIR: ${{ github.workspace }}/.ccache - CCACHE_LIMIT_MULTIPLE: 0.95 - defaults: run: shell: bash @@ -27,23 +19,22 @@ jobs: name: Sub-lint | Python steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo - - name: Install packages for build - run: ./ci/ci-install.bash + - name: Install dependencies + run: ./ci/ci-install.bash lint-py - name: Configure run: | autoconf ./configure --enable-longtests --enable-ccwarn - - name: Install python dependencies - run: | - sudo apt install python3-clang || \ - sudo apt install python3-clang - make venv + - name: Set up Python venv + uses: ./repo/.github/actions/setup-venv + with: + save: ${{ github.ref == 'refs/heads/master' }} - name: Lint run: |- diff --git a/.github/workflows/reusable-rtlmeter-build.yml b/.github/workflows/reusable-rtlmeter-build.yml deleted file mode 100644 index 321033a72..000000000 --- a/.github/workflows/reusable-rtlmeter-build.yml +++ /dev/null @@ -1,96 +0,0 @@ ---- -# DESCRIPTION: Github actions config -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -name: reusable-rtlmeter-build - -on: - workflow_call: - inputs: - runs-on: - description: "Runner to use, e.g.: ubuntu-24.04" - type: string - required: true - cc: - description: "Compiler to use: 'gcc' or 'clang'" - type: string - required: true - sha: - description: "Git SHA to build" - type: string - required: true - outputs: - archive: - description: "Name of the built installation archive artifact" - value: ${{ jobs.build.outputs.archive }} - -defaults: - run: - shell: bash - -env: - CCACHE_DIR: ${{ github.workspace }}/ccache - CCACHE_MAXSIZE: 512M - -jobs: - build: - name: Build - runs-on: ${{ inputs.runs-on }} - outputs: - archive: ${{ steps.create-archive.outputs.archive }} - steps: - - name: Install dependencies - run: | - echo "path-exclude /usr/share/doc/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc - echo "path-exclude /usr/share/man/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc - echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc - sudo apt update || \ - sudo apt update - sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev || \ - sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev - - - name: Use saved ccache - uses: actions/cache@v5 - with: - path: ccache - key: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: | - rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }} - rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }} - rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }} - - - name: Checkout - uses: actions/checkout@v6 - with: - path: repo - ref: ${{ inputs.sha }} - fetch-depth: 0 # Required for 'git describe' used for 'verilator --version' - - - name: Configure - working-directory: repo - run: | - autoconf - ./configure --prefix=${{ github.workspace }}/install CXX=${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} - - - name: Make - working-directory: repo - run: make -j $(nproc) - - - name: Install - working-directory: repo - run: make install - - - name: Tar up installation - id: create-archive - run: | - SHA=$(git -C repo rev-parse HEAD) - ARCHIVE=verilator-$SHA-rtlmeter-${{ inputs.runs-on }}-${{ inputs.cc }}.tar.gz - tar --posix -c -z -f $ARCHIVE install - echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT - - - name: Upload Verilator installation archive - uses: actions/upload-artifact@v7 - with: - path: ${{ steps.create-archive.outputs.archive }} - name: ${{ steps.create-archive.outputs.archive }} - overwrite: true diff --git a/.github/workflows/reusable-rtlmeter-run.yml b/.github/workflows/reusable-rtlmeter-run.yml index ebbaffef8..4d8fbd9b1 100644 --- a/.github/workflows/reusable-rtlmeter-run.yml +++ b/.github/workflows/reusable-rtlmeter-run.yml @@ -20,11 +20,11 @@ on: type: string required: true verilator-archive-new: - description: "Name of the installation archive artifact from reusable-rtlmeter-build, new version" + description: "Name of the installation archive artifact from reusable-build, new version" type: string required: true verilator-archive-old: - description: "Name of the installation archive artifact from reusable-rtlmeter-build, old version" + description: "Name of the installation archive artifact from reusable-build, old version" type: string required: false default: "" @@ -53,8 +53,8 @@ defaults: shell: bash env: - CCACHE_DIR: ${{ github.workspace }}/ccache - CCACHE_MAXSIZE: 512M + # RTLMeter measures compile/execute times, so caching must stay off to keep + # timings representative; ccache is still on PATH but acts as a pass-through CCACHE_DISABLE: 1 jobs: @@ -73,7 +73,7 @@ jobs: sudo apt install ccache mold libfl-dev libjemalloc-dev libsystemc-dev - name: Checkout RTLMeter - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -82,26 +82,18 @@ jobs: working-directory: rtlmeter run: make venv - - name: Use saved ccache - if: ${{ env.CCACHE_DISABLE == 0 }} - uses: actions/cache@v5 - with: - path: ${{ env.CCACHE_DIR }} - key: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }} - ######################################################################## # Run with new Verilator ######################################################################## - name: Download Verilator installation archive - new - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.verilator-archive-new }} - name: Unpack Verilator installation archive - new run: | - tar -x -z -f ${{ inputs.verilator-archive-new }} + tar --zstd -x -f ${{ inputs.verilator-archive-new }} mv install verilator-new - name: Compile cases - new @@ -135,7 +127,7 @@ jobs: ./rtlmeter report --steps '*' --metrics '*' ../results-${{ steps.results.outputs.hash }}.json - name: Upload results - new - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: results-${{ steps.results.outputs.hash }}.json name: rtlmeter-${{ inputs.tag }}-results-${{ steps.results.outputs.hash }} @@ -157,14 +149,14 @@ jobs: - name: Download Verilator installation archive - old if: ${{ inputs.verilator-archive-old != '' }} - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.verilator-archive-old }} - name: Unpack Verilator installation archive - old if: ${{ inputs.verilator-archive-old != '' }} run: | - tar -x -z -f ${{ inputs.verilator-archive-old }} + tar --zstd -x -f ${{ inputs.verilator-archive-old }} mv install verilator-old - name: Compile cases - old @@ -198,7 +190,7 @@ jobs: - name: Upload results - old if: ${{ inputs.verilator-archive-old != '' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: reference-${{ steps.results.outputs.hash }}.json name: rtlmeter-${{ inputs.tag }}-reference-${{ steps.results.outputs.hash }} diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index 7f363f2d9..bd5a76c7c 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -11,29 +11,37 @@ on: description: "Name of the repository archive artifact from reusable-build" required: true type: string - os: # e.g. ubuntu-24.04 - required: true - type: string - cc: # gcc or clang - required: true - type: string - reloc: # 0 or 1 - required: true - type: number - suite: # e.g. dist-vlt-0 + cc: + description: "Compiler to use: 'gcc' or 'clang'" required: true type: string dev-gcov: + description: "Collect gcov coverage data from the test run" + required: false + default: false + type: boolean + reloc: + description: "Relocate the installation before testing" + required: false + default: false + type: boolean + runs-on: + description: "Runner to test on, e.g. ubuntu-24.04" required: true - type: number + type: string + suite: + description: "Test suite to run, e.g. dist-vlt-0" + required: true + type: string + +permissions: + contents: read + actions: read env: - CI_OS_NAME: linux - CCACHE_COMPRESS: 1 + CCACHE_COMPILERCHECK: content CCACHE_DIR: ${{ github.workspace }}/.ccache - CCACHE_LIMIT_MULTIPLE: 0.95 - INSTALL_DIR: ${{ github.workspace }}/install - RELOC_DIR: ${{ github.workspace }}/relloc + CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} defaults: run: @@ -43,19 +51,12 @@ defaults: jobs: test: - runs-on: ${{ inputs.os }} + runs-on: ${{ inputs.runs-on }} name: Test - env: - CI_BUILD_STAGE_NAME: test - CI_RUNS_ON: ${{ inputs.os }} - CI_RELOC: ${{inputs.reloc }} - CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }} - CACHE_BASE_KEY: test-${{ inputs.os }}-${{ inputs.cc }}-${{inputs.reloc }}-${{ inputs.suite }} - CCACHE_MAXSIZE: 100M # Per build per suite (* 5 * 5 = 2500M in total) steps: - name: Download repository archive - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.archive }} path: ${{ github.workspace }} @@ -63,32 +64,43 @@ jobs: - name: Unpack repository archive working-directory: ${{ github.workspace }} run: | - tar -x -z -f ${{ inputs.archive }} + tar --zstd -x -f ${{ inputs.archive }} ls -lsha - - name: Cache $CCACHE_DIR - uses: actions/cache@v5 - env: - CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache2 + - name: Configure ccache + run: | + # ccache is unreliable on macOS, and does nothing with 'gcc --coverage' + if [ "${{ startsWith(inputs.runs-on, 'macos') }}" = true ] || [ "${{ inputs.dev-gcov }}" = true ]; then + echo "CCACHE_DISABLE=1" >> "$GITHUB_ENV" + fi + + # Test-job ccache is stored as an artifact, not actions/cache + - name: Restore ccache + id: ccache + if: ${{ env.CCACHE_DISABLE != '1' }} + uses: ./repo/.github/actions/artifact-cache with: + mode: restore path: ${{ env.CCACHE_DIR }} - key: ${{ env.CACHE_KEY }}-${{ github.sha }} - restore-keys: | - ${{ env.CACHE_KEY }}- + key: ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.suite }} - name: Install test dependencies run: | - ./ci/ci-install.bash - make venv + ./ci/ci-install.bash test + + - name: Set up Python venv + uses: ./repo/.github/actions/setup-venv + with: + save: ${{ github.ref == 'refs/heads/master' }} - name: Test id: run-test continue-on-error: true - env: - TESTS: ${{ inputs.suite }} run: | source .venv/bin/activate - ./ci/ci-script.bash + ./ci/ci-test.bash \ + --suite ${{ inputs.suite }} \ + ${{ inputs.reloc && format('--reloc {0}/reloc', github.workspace) || '' }} - name: Combine code coverage data if: ${{ inputs.dev-gcov }} @@ -99,11 +111,21 @@ jobs: - name: Upload code coverage data if: ${{ inputs.dev-gcov }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ github.workspace }}/repo/obj_coverage/verilator-${{ inputs.suite }}.info name: code-coverage-${{ inputs.suite }} + - name: Save ccache + if: ${{ env.CCACHE_DISABLE != '1' && !cancelled() }} + uses: ./repo/.github/actions/artifact-cache + with: + mode: save + path: ${{ env.CCACHE_DIR }} + key: ${{ steps.ccache.outputs.key }} + # Keep master's cache around longer; PR/branch caches churn faster + retention-days: ${{ github.ref == 'refs/heads/master' && 7 || 3 }} + - name: Fail job if a test failed if: ${{ steps.run-test.outcome == 'failure' && !cancelled() }} run: |- diff --git a/.github/workflows/rtlmeter.yml b/.github/workflows/rtlmeter.yml index d2880b476..c0f7506cd 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -45,7 +45,7 @@ jobs: cases: ${{ steps.cases.outputs.cases }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Startup id: start @@ -65,39 +65,47 @@ jobs: build-gcc-new: name: Build New Verilator - GCC needs: start - uses: ./.github/workflows/reusable-rtlmeter-build.yml + uses: ./.github/workflows/reusable-build.yml with: - runs-on: ubuntu-24.04 cc: gcc + ccwarn: false + install: true + runs-on: ubuntu-24.04 sha: ${{ github.sha }} build-clang-new: name: Build New Verilator - Clang needs: start - uses: ./.github/workflows/reusable-rtlmeter-build.yml + uses: ./.github/workflows/reusable-build.yml with: - runs-on: ubuntu-24.04 cc: clang + ccwarn: false + install: true + runs-on: ubuntu-24.04 sha: ${{ github.sha }} build-gcc-old: name: Build Old Verilator - GCC needs: start if: ${{ needs.start.outputs.old-sha != '' }} - uses: ./.github/workflows/reusable-rtlmeter-build.yml + uses: ./.github/workflows/reusable-build.yml with: - runs-on: ubuntu-24.04 cc: gcc + ccwarn: false + install: true + runs-on: ubuntu-24.04 sha: ${{ needs.start.outputs.old-sha }} build-clang-old: name: Build Old Verilator - Clang needs: start if: ${{ needs.start.outputs.old-sha != '' }} - uses: ./.github/workflows/reusable-rtlmeter-build.yml + uses: ./.github/workflows/reusable-build.yml with: - runs-on: ubuntu-24.04 cc: clang + ccwarn: false + install: true + runs-on: ubuntu-24.04 sha: ${{ needs.start.outputs.old-sha }} run-gcc: @@ -208,7 +216,7 @@ jobs: run: echo "tags=$(jq -r 'keys | map(sub("^run-"; "")) | join(" ")' <<< '${{ toJSON(needs) }}')" >> "$GITHUB_OUTPUT" - name: Checkout RTLMeter - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -232,7 +240,7 @@ jobs: ./rtlmeter collate ../all-results-$tag/*.json > ../all-results-$tag.json done - name: Upload combined results - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: all-results-*.json name: all-results @@ -259,7 +267,7 @@ jobs: done - name: Upload reference results if: ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: all-reference-*.json name: all-reference @@ -278,27 +286,27 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Download combined results - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: all-results path: results - name: Upload published results - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: results/*.json name: published-results # Pushing to verilator/verilator-rtlmeter-results requires elevated permissions - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator-rtlmeter-results permission-contents: write - name: Checkout verilator-rtlmeter-results - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/verilator-rtlmeter-results" token: ${{ steps.generate-token.outputs.token }} @@ -331,7 +339,7 @@ jobs: actions: read steps: - name: Checkout RTLMeter - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -341,7 +349,7 @@ jobs: run: make venv - name: Checkout Verilator - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: verilator @@ -367,13 +375,13 @@ jobs: echo ${{ github.event.number }} > ../notification-artifact/pr-number.txt - name: Upload report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: report-artifact name: rtlmeter-report - name: Upload notification - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: notification-artifact name: pr-notification @@ -392,9 +400,9 @@ jobs: # Creating issues requires elevated privilege - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..2f5f06eda --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,142 @@ + + +# Verilator Guidelines for AI Coding Agents + +These files are the general layer an agent loads first -- nearest file wins, so +you read this repository-root file plus the one for the directory you are +editing. They stay deliberately high-level: where to start, how the tree is laid +out, and the conventions reviewers otherwise enforce by hand. They are an index, +not the architecture reference -- for depth (how a pass works internally, the +algorithms, node lifetime) they point you to `docs/internals.rst`. When the +guidance here is not enough, that is where to look next. + +This file has two parts. **Orientation** gets you productive in the codebase from +a cold start. **Before you open a PR** is the checklist of conventions reviewers +otherwise have to enforce by hand -- read it before submitting any change. + +Then read the directory guide for the area you are editing: + +- [src/AGENTS.md](src/AGENTS.md) -- compiler C++ sources: AST, visitors, passes, parser, style +- [include/AGENTS.md](include/AGENTS.md) -- runtime library (`verilated*`): C++14, MT-safety, fixed-width types +- [test_regress/AGENTS.md](test_regress/AGENTS.md) -- regression tests: harness, drivers, golden files +- [docs/AGENTS.md](docs/AGENTS.md) -- documentation (`*.rst`) + +______________________________________________________________________ + +# Orientation + +## What Verilator is + +Verilator is a *compiler*, not an interpreter. It translates synthesizable (and +much behavioral) SystemVerilog into a cycle-accurate C++ model that you then +compile and run. Almost every decision is made at compile ("verilation") time; +the generated C++ just advances state each evaluation. Optimize for verilation- +time work over runtime work. + +## The pipeline is the spine + +A run is an ordered sequence of passes over one shared AST (abstract syntax +tree). In source order: + +| Stage | What it does | Key files | +|---|---|---| +| Preprocess + parse | Lex and parse text into a raw AST -- builds nodes only, no semantic checks | `verilog.l`, `verilog.y` | +| Link / elaborate | Resolve names, scopes, parameters; instantiate the hierarchy | `V3LinkParse`, `V3LinkDot`, `V3Param` | +| Width / type | Assign and check data types and bit widths | `V3Width` | +| Transform / optimize / schedule | Constant fold, lower language features, schedule events | `V3Const`, `V3Randomize`, `V3Assert*`, `V3Sched`, `V3Timing`, `V3Dfg` | +| Emit | Lower the final AST to generated C++ | `V3EmitC*` | +| Runtime | Library the generated model links against | `include/verilated*` | + +This table is the map; `docs/internals.rst` has the detail behind each stage. + +## Where to make a change + +Map the symptom to the pass that owns it, then start by reading that pass's +top-of-file comment. + +| Symptom or feature area | Start in | +|---|---| +| Type/width error, "what type is this", implicit conversion | `V3Width` | +| Name/scope/parameter resolution ("Can't find...", hierarchy) | `V3LinkDot`, `V3Param` | +| `randomize` / `constraint` / `rand` / `randc` | `V3Randomize` | +| `assert` / `property` / `sequence` / `cover` | `V3Assert`, `V3AssertPre`, `V3AssertNfa` | +| `fork` / timing / `#delay` / NBA / event scheduling | `V3Sched`, `V3Timing`, `V3Fork` | +| Syntax wrongly accepted or rejected | `verilog.y`, `verilog.l` | +| Wrong generated C++ | `V3EmitC*` | +| Runtime model behavior | `include/verilated*` | + +## Build and run a test + +- Build in the source tree: `autoconf && ./configure && make -j8`. Configure with + `--enable-ccwarn` so a new compiler warning stops the build. +- Run one test from the repository root: `test_regress/t/t_.py`. +- Run the full regression with `make test`. The complete suite requires + configuring with `--enable-longtests` (works on every OS, including macOS). + +______________________________________________________________________ + +# Before you open a PR + +## Scope and process + +- [ ] Searched open PRs and issues -- duplicating in-flight work wastes review time. +- [ ] Fixed the general root cause, not just the reported case -- if it also + affects other modules/classes/interfaces, cover them or expect rejection. +- [ ] PR is single-purpose. Refactors, drive-by fixes found along the way, and new + features each go in separate PRs; land standalone cleanups first. +- [ ] Every bug fix has a test that fails *without* the fix; include the issue's + own reproducer when possible. +- [ ] New code aims for 100% line coverage; branch coverage far below line coverage + signals guards callers never violate -- justify or remove them. +- [ ] Ran `make format` (clang-format), `make cppcheck`, and `make lint-py`; + self-reviewed the diff for leftover debug code, stale comments, and + copy-paste errors. +- [ ] Ran the full regression on at least one OS before submitting. Partial runs + are fine during development, but the submitted PR is expected to pass every + test. +- [ ] Did not edit `docs/CONTRIBUTORS` (humans only) or `Changes` (maintainer + updates it near release). + +## Pick the right diagnostic (and its required test) + +The API you choose determines which test must accompany the change. + +| API | Output | Meaning | Required test | +|---|---|---|---| +| `v3error("...")` | `%Error:` | User wrote invalid SystemVerilog | `t_*_bad*.v` + `.out` golden | +| `v3error("Unsupported: ...")` | `%Error-UNSUPPORTED:` | Legal SV that Verilator does not yet support | `t_*_unsup*.v` + `.out` golden | +| `v3warn(CODE, "...")` | `%Warning-CODE:` | Legal but suspicious code | warning test + `.out` golden | +| `v3fatalSrc("...")` | `%Error: Internal Error` | Should-never-happen internal assertion | none -- not user-triggerable | + +- Every `v3error`/`v3warn` needs a test in `test_regress/t/` -- enforced by the + warn-coverage distribution test. `v3fatalSrc` is exempt. +- Reserve "Unsupported:" for not-yet-implemented features, never for user mistakes. +- When an error enforces a spec-defined restriction, cite the clause + (`IEEE 1800-2023 11.4.7`) so it is verifiable. Update `docs/guide/warnings.rst` + when adding or changing a warning. +- On error paths, clean up or replace invalid AST (e.g. `AstConst::BitFalse`) so + later passes do not crash after the error. + +## Cross-cutting code rules + +- [ ] No non-ASCII characters in C++ sources or headers: write `--` (two ASCII + hyphens) rather than a Unicode em-dash, and a plain `'` rather than a smart + quote. At write time, not when CI complains. +- [ ] Lists stay sorted: lexer/parser tokens, option declarations, enum values, + configure feature lists, documented option lists. +- [ ] `bin/` scripts are Python (distributed cross-platform); `nodist/` may use + bash and platform-specific code (developer-only, not packaged). +- [ ] Runtime code in `include/` targets C++14 (`--no-timing` builds must work); + C++20 only in timing code paths. +- [ ] In `include/` public headers, prefix public classes with `Verilated`/`Vl` + and document the API with `///` comments. +- [ ] A new code pattern is applied globally or not at all -- no one-off + convention in a single file. + +## Commits + +- Subject line is short and imperative and conventionally ends with the PR number: + `Support property case (#7721)`. A body is optional and common for non-trivial + changes. diff --git a/CMakeLists.txt b/CMakeLists.txt index eb5682e7b..6fa5e2ffc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ cmake_minimum_required(VERSION 3.15) cmake_policy(SET CMP0091 NEW) # Use MSVC_RUNTIME_LIBRARY to select the runtime project( Verilator - VERSION 5.049 + VERSION 5.051 HOMEPAGE_URL https://verilator.org LANGUAGES CXX ) diff --git a/Changes b/Changes index fcb769eae..9ae1a40ee 100644 --- a/Changes +++ b/Changes @@ -10,12 +10,26 @@ The changes in each Verilator version are described below. The contributors that suggested or implemented a given issue are shown in []. Thanks! -Verilator 5.049 devel +Verilator 5.051 devel +========================== + +**Other:** + +* Add comments as a branch description in coverage .info files (#7843). [Eryk Szpotanski] +* Optimize random initialization. [Geza Lore, Testorrent USA, Inc.] +* Fix DFG misoptimizing bound checks (#7755). [Jakub Michalski] +* Fix unique0 case side effects (#7787). [Pawel Klopotek] +* Fix cleaning purity cache after assertions. [Geza Lore, Testorrent USA, Inc.] +* Fix clang++ ambiguous overload of '==' operator (#7863). [Pawel Kojma, Antmicro Ltd.] +* Fix heap-use-after-free in `VlRNG::VlRNG()` (#7865). [Dragon-Git] + + +Verilator 5.050 2026-07-01 ========================== **Important:** -* Support covergroups, coverpoints, and bins (#784) (#7117). [Matthew Ballance] +* Support covergroups, coverpoints, and bins (#784) (#7117) (#7728). [Matthew Ballance] * Support new FST writer API (#6871) (#6992). [Yu-Sheng Lin] Use of FST may requiring installing liblz4 and/or liblz4-dev packages, see docs/install.rst. @@ -27,9 +41,12 @@ Verilator 5.049 devel * Add `--coverage-per-instance` (#7636). [Yogish Sekhar] * Add NOTREDOP error on reduction and negation operators (#7417) (#7623) (#7624). * Add hierarchy-aware reporting to `verilator_coverage` (#7657). [Yogish Sekhar] +* Add FINALDLY error (#7754). [Igor Zaworski, Antmicro Ltd.] +* Deprecate isolate_assignments attribute (#7774) (#7144). [Geza Lore, Testorrent USA, Inc.] * Improve `--coverage-fsm` (#7490) (#7529) (#7561) (#7573) (#7619). [Yogish Sekhar] * Change `+verilator+seed` to default to 1, and 0 to randomly select (#7325) (#7516). [Miguel] * Change JSON to include parameter constant mnemonics for FSM Coverage (#7531). [Yogish Sekhar] +* Support assert property `default disable iff` (#4848) (#7723). [Artur Bieniek, Antmicro Ltd.] * Support printing enum names for %p and %s (#5523) (#7338 repair) (#7521) (#7527). [Nick Brereton] * Support weak `until` / `until_with` property operators (#7290) (#7548) (#7685). [Yilou Wang] * Support `s_eventually` (#7291) (#7508). [Bartłomiej Chmiel, Antmicro Ltd.] @@ -50,6 +67,7 @@ Verilator 5.049 devel * Support procedural concurrent assertions with inferred clock (#7581). [Yilou Wang] * Support calling interface functions without parens (#7584). [Krzysztof Bieganski, Antmicro Ltd.] * Support streaming on queues (#7597). [Benjamin Collier, Secturion Systems, Inc.] +* Support clocking event on a sequence declaration body (#7598) (#7793). [Yilou Wang] * Support generic interface arrays (#7604). [Krzysztof Bieganski, Antmicro Ltd.] * Support FSM detection in primitive wrappers (#7607). [Yogish Sekhar] * Support busses with mix of pullup/pulldown (#7632). [Lucas Amaral] @@ -59,6 +77,24 @@ Verilator 5.049 devel * Support if/if-else in properties (#7692). [Artur Bieniek, Antmicro Ltd.] * Support process::self().srand() (#7695). [Igor Zaworski, Antmicro Ltd.] * Support MacOS lldb (#7697). [Tracy Narine] +* Support assoc array methods with wide value types (#7680). [pawelktk] +* Support property case (#7682) (#7721). [Artur Bieniek, Antmicro Ltd.] +* Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.] +* Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance] +* Support hierarchical reference cross members (#7749) (#7820). [Matthew Ballance] +* Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.] +* Support NBAs in initial blocks (#7754). [Igor Zaworski, Antmicro Ltd.] +* Support assertion control system tasks in classes and interfaces (#7761). [Yilou Wang] +* Support cover sequence statement (#7764). [Yilou Wang] +* Support unpacked struct stream (#7767). [Nick Brereton] +* Support $assertcontrol control_type from lock to kill (#7788). [Yilou Wang] +* Support unbounded always [m:$] and strong s_always liveness (#7798). [Yilou Wang] +* Support method calls on a sub-interface via a virtual interface (#7800). [Yilou Wang] +* Support global $assertcontrol (#7807). [Artur Bieniek, Antmicro Ltd.] +* Support VPI access to unpacked struct members (#7823). [Nick Brereton] +* Support variable-length intersect in SVA sequences (#7835). [Yilou Wang] +* Support dynamic loading of VPI extensions (#7727). [Matthew Ballance] +* Optimize DFG cycle breaking to do less work (#7210). [Geza Lore, Testorrent USA, Inc.] * Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.] * Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.] * Optimize forced signal handling (#7554 partial) (#7572) (#7594) (#7596). [Krzysztof Bieganski, Artur Bieniek, Antmicro Ltd.] @@ -71,9 +107,27 @@ Verilator 5.049 devel * Optimize runtime assertOn() checks (#7707). [Geza Lore, Testorrent USA, Inc.] * Optimize $countones and $onehot in DFG. [Geza Lore, Testorrent USA, Inc.] * Optimize procedural loop unrolling. [Geza Lore, Testorrent USA, Inc.] +* Optimize V3Gate inlining heuristic (#7716). [Geza Lore, Testorrent USA, Inc.] +* Optimize reset in DFG (#7737). [Geza Lore, Testorrent USA, Inc.] +* Optimize DFG with relaxed live variable analysis (#7739). [Geza Lore, Testorrent USA, Inc.] +* Optimize conditional patterns sharing common MBSs/LSBs in DfgPeephole (#7760). [Geza Lore, Testorrent USA, Inc.] +* Optimize bit select removal earlier in DFG (#7762). [Geza Lore, Testorrent USA, Inc.] +* Optimize away proven redundant case statement assertions (#7771). [Geza Lore, Testorrent USA, Inc.] +* Optimize table lookups in DFG (#7772). [Geza Lore, Testorrent USA, Inc.] +* Optimize input combinational logic by change detection (#7784). [Geza Lore, Testorrent USA, Inc.] +* Optimize decoder case statements into lookup tables (#7795). [Geza Lore, Testorrent USA, Inc.] +* Optimize wide decoder case statements into decoder expressions (#7804). [Geza Lore, Testorrent USA, Inc.] +* Optimize statically known oversize shifts (#7806). [Geza Lore, Testorrent USA, Inc.] +* Optimize generated function inlining (#7811). [Geza Lore, Testorrent USA, Inc.] +* Optimize bit-scan loops into most-set-bit or $countones (#7822). [Thomas Santerre] +* Optimize additional expression patterns (#7824). [Geza Lore, Testorrent USA, Inc.] +* Optimize module inlining heuristic (#7837). [Geza Lore, Testorrent USA, Inc.] +* Fix `$bits` on unpacked structs (#4521) (#7796). [Nick Brereton] * Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal] * Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.] +* Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli] * Fix segmentation fault when using --trace with --lib-create (#7299) (#7518). [anonkey] +* Fix cross-hierarchy tristate drivers of interface nets (#7339) (#7801). [Yilou Wang] * Fix destructive event state before dynamic waits (#7340). [Nick Brereton] * Fix ALWCOMBORDER on variable ordering (#7350) (#7608). [Cookie] * Fix false MULTIDRIVEN warning on always_ff variables (#7351) (#7621) (#7672). @@ -118,6 +172,7 @@ Verilator 5.049 devel * Fix reference counting for modport task references (#7628). [Nick Brereton] * Fix internal error when handling typedefs containing parameterized class type members (#7635) (#7661). [em2machine] * Fix forceable signal with a procedural continuous assign (#7638) (#7639). [Zubin Jain] +* Fix scheduling of virtual interface method writes (#7641). [Artur Bieniek, Antmicro Ltd.] * Fix implicit conversions of VlWide (#7642). [Geza Lore, Testorrent USA, Inc.] * Fix CASEINCOMPLETE to not warn on `unique0 case` (#7647). * Fix hierarchical coverage counts for duplicate no-inline module instances (#7649). [Yogish Sekhar] @@ -128,13 +183,45 @@ Verilator 5.049 devel * Fix loss of events due to bit shift (#7670). [Artur Bieniek, Antmicro Ltd.] * Fix parameter read through locally-declared interface instance (#7679). [Nick Brereton] * Fix skipping nulls in $sscanf (#7689). +* Fix bounds checks in expressions with read/write references (#7694). [Ryszard Rozak, Antmicro Ltd.] * Fix (const) ref default task argument handling (#7698). [Nick Brereton] * Fix `ref` argument type check for packed arrays with differing range directions (#7700). [Nick Brereton] * Fix ignoring not-found modules with encoded names (#7706). [Igor Zaworski, Antmicro Ltd.] * Fix MULTIDRIVEN in generates (#7709). [Todd Strader] +* Fix parameter pollution when using class parameters (#7711) (#7763). [em2machine] * Fix Makefile action to not write to ${srcdir} (#7715). [Larry Doolittle] * Fix splitting functions containing fork logic (#7717). [Mateusz Gancarz, Antmicro Ltd.] * Fix optimizations of assignments with timing controls (#7718). [Ryszard Rozak, Antmicro Ltd.] +* Fix s_eventually on interface (#7731) (#7733). [Marco Bartoli] +* Fix parameter values in coverage bins widths (#7732) (#7734). [Marco Bartoli] +* Fix configure fall back on dynamic malloc libraries (#7736). [Geza Lore, Testorrent USA, Inc.] +* Fix crash on overlapping priority case. [Geza Lore, Testorrent USA, Inc.] +* Fix s_eventually in parameterized interfaces (#7741). [Nick Brereton] +* Fix dpi export pointers (#7742) (#7751). [Yilin Li] +* Fix force on unpacked bit select (#7744) (#7745). [Nikolai Kumar] +* Fix `$` as unsupported coverpoint-bin range bounds (#7750) (#7825). [Matthew Ballance] +* Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.] +* Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang] +* Fix `case (_) inside` with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.] +* Fix not failing assertion when RHS of a range window rejects once (#7773). [Artur Bieniek, Antmicro Ltd.] +* Fix $fflush and autoflush with --threads (#7782). +* Fix out-of-bounds read value for 2-state types (#7785). [Jakub Michalski] +* Fix `cover property` of an implication counting vacuous matches (#7789). [Yilou Wang] +* Fix randomization of dynamic arrays of objects (#7790). [Ryszard Rozak, Antmicro Ltd.] +* Fix randomize() with skipping derived pre/post_randomize (#7799). [Yilou Wang] +* Fix skewed dist operator for arrays (#7802). [Jakub Wasilewski, Antmicro Ltd.] +* Fix assertion when loop unrolling failed (#7810). [Geza Lore, Testorrent USA, Inc.] +* Fix CASEINCOMPLETE for all uncovered enum items (#7815) (#7817). [Saksham] +* Fix split optimization nested-class crash (#7826). [Igor Zaworski, Antmicro Ltd.] +* Fix class/var named identically to an enclosing-scope type (#7827) (#7828). [Tom Jackson] +* Fix performance on large package-scoped structs (#7830). [Wolfgang Mayerwieser] +* Fix unclocked concurrent assertion misreported as unsupported (#7831). [Yilou Wang] +* Fix insertion of expression coverage statement (#7832). [Ryszard Rozak, Antmicro Ltd.] +* Fix lifetime of expression coverage variable (#7834). [Ryszard Rozak, Antmicro Ltd.] +* Fix disable iff ignored when its condition is held continuously true (#7841). [Yilou Wang] +* Fix constant pool cache after dead scope removal (#7845). [Nick Brereton] +* Fix covergroups without --coverage (#7848) (#7849). [Joshua Leahy] +* Fix class scope '::' reference through an inherited type parameter (#7844). [Sergey Chusov] Verilator 5.048 2026-04-26 @@ -471,7 +558,7 @@ Verilator 5.044 2026-01-01 * Support clocking output delay `1step` (#6681). [Ondrej Ille] * Support parsing of dotted `bins_expression` (#6683). [Pawel Kojma, Antmicro Ltd.] * Support constant expression cycle delays in sequences (#6691). [Ryszard Rozak, Antmicro Ltd.] -* Support general global constraints (#6709) (#6711). [Yilou Wang] +* Support general global constraints (#6709) (#6711) (#7833) (#7838). [Yilou Wang] * Support complex std::randomize patterns (#6736) (#6737). [Yilou Wang] * Support `rand_mode` in global constraint gathering (#6740) (#6752). [Yilou Wang] * Support reduction or in constraints (#6840). [Pawel Kojma, Antmicro Ltd.] @@ -5665,7 +5752,7 @@ Verilator 3.104 2003-04-30 **Major:** * Indicate direction of ports with VL_IN and VL_OUT. -* Allow $c32, etc, to specify width of the $c statement for VCS. +* Allow $c32, etc, to specify width of the $c statement. * Numerous performance improvements, worth about 25% **Minor:** diff --git a/Makefile.in b/Makefile.in index c2b60d004..80db572be 100644 --- a/Makefile.in +++ b/Makefile.in @@ -495,6 +495,11 @@ MAKE_FILES = \ src/Makefile*.in \ test_regress/Makefile* \ +# Markdown +MD_FILES = \ + *.md \ + */*.md \ + # Perl programs PERL_PROGRAMS = \ bin/redirect \ @@ -541,6 +546,13 @@ PY_FILES = \ # Python files, test_regress tests PY_TEST_FILES = test_regress/t/*.py +# reStructuredText Sphinx files +RST_FILES = \ + *.rst \ + */*.rst \ + ci/docker/*/*.rst \ + docs/guide/*.rst \ + # YAML files YAML_FILES = \ .*.yaml \ @@ -553,7 +565,7 @@ YAML_FILES = \ # Format format: - $(MAKE) -j 5 format-c format-cmake format-exec format-py format-yaml + $(MAKE) -j 5 format-c format-cmake format-exec format-md format-py format-rst format-yaml BEAUTYSH = beautysh BEAUTYSH_FLAGS = --indent-size 2 @@ -590,6 +602,13 @@ format-make mbake: $(MBAKE) --version $(MBAKE) $(MBAKE_FLAGS) $(MAKE_FILES) +MDFORMAT = mdformat +MDFORMAT_FLAGS = + +format-md: + $(MDFORMAT) --version + $(MDFORMAT) $(MDFORMAT_FLAGS) $(MD_FILES) + YAPF = yapf YAPF_FLAGS = -i --parallel @@ -597,6 +616,13 @@ format-py yapf: $(YAPF) --version $(YAPF) $(YAPF_FLAGS) $(PY_FILES) +DOCSTRFMT = docstrfmt +DOCSTRFMT_FLAGS = --line-length 75 --indent-width 3 --keep-blanks --ordered-marker "\#" --preserve-adornments --no-center-section-titles + +format-rst: + $(DOCSTRFMT) --version + $(DOCSTRFMT) $(DOCSTRFMT_FLAGS) $(RST_FILES) + YAMLFIX = YAMLFIX_WHITELINES=1 YAMLFIX_LINE_LENGTH=200 YAMLFIX_preserve_quotes=true yamlfix YAMLFIX_FLAGS = diff --git a/README.rst b/README.rst index bec2182ad..d51837df2 100644 --- a/README.rst +++ b/README.rst @@ -1,58 +1,63 @@ -.. Github doesn't render images unless absolute URL -.. Do not know of a conditional tag, "only: github" nor "github display" works -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + Github doesn't render images unless absolute URL + Do not know of a conditional tag, "only: github" nor "github display" works + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 |badge1| |badge2| |badge3| |badge4| |badge5| |badge7| |badge8| .. |badge1| image:: https://img.shields.io/badge/Website-Verilator.org-181717.svg :target: https://verilator.org + .. |badge2| image:: https://img.shields.io/badge/License-LGPL%20v3-blue.svg :target: https://www.gnu.org/licenses/lgpl-3.0 + .. |badge3| image:: https://img.shields.io/badge/License-Artistic%202.0-0298c3.svg :target: https://opensource.org/licenses/Artistic-2.0 + .. |badge4| image:: https://repology.org/badge/tiny-repos/verilator.svg?header=distro%20packages :target: https://repology.org/project/verilator/versions + .. |badge5| image:: https://img.shields.io/docker/pulls/verilator/verilator :target: https://hub.docker.com/r/verilator/verilator + .. |badge7| image:: https://github.com/verilator/verilator/workflows/build/badge.svg :target: https://github.com/verilator/verilator/actions?query=workflow%3Abuild + .. |badge8| image:: https://img.shields.io/github/actions/workflow/status/verilator/verilator/rtlmeter.yml?branch=master&event=schedule&label=benchmarks :target: https://verilator.github.io/verilator-rtlmeter-results - Welcome to Verilator ==================== .. list-table:: - * - **Welcome to Verilator, the fastest Verilog/SystemVerilog simulator.** - * Accepts Verilog or SystemVerilog - * Performs lint code-quality checks - * Compiles into multithreaded C++, or SystemC - * Creates JSON to front-end your own tools + - - **Welcome to Verilator, the fastest Verilog/SystemVerilog simulator.** + - Accepts Verilog or SystemVerilog + - Performs lint code-quality checks + - Compiles into multithreaded C++, or SystemC + - Creates JSON to front-end your own tools - |Logo| - * - |verilator multithreaded performance| + - - |verilator multithreaded performance| - **Fast** - * Outperforms many closed-source commercial simulators - * Single- and multithreaded output models - * - **Widely Used** - * Wide industry and academic deployment - * Out-of-the-box support from Arm and RISC-V vendor IP - * Over 700 contributors + - Outperforms many closed-source commercial simulators + - Single- and multithreaded output models + - - **Widely Used** + - Wide industry and academic deployment + - Out-of-the-box support from Arm and RISC-V vendor IP + - Over 700 contributors - |verilator usage| - * - |verilator community| + - - |verilator community| - **Community Driven & Openly Licensed** - * Guided by the `CHIPS Alliance`_ and `Linux Foundation`_ - * Open, and free as in both speech and beer - * More simulation for your verification budget - * - **Commercial Support Available** - * Commercial support contracts - * Design support contracts - * Enhancement contracts + - Guided by the `CHIPS Alliance`_ and `Linux Foundation`_ + - Open, and free as in both speech and beer + - More simulation for your verification budget + - - **Commercial Support Available** + - Commercial support contracts + - Design support contracts + - Enhancement contracts - |verilator support| - What Verilator Does =================== @@ -77,7 +82,6 @@ SDF annotation, or mixed-signal simulation. However, if you are looking for a path to migrate SystemVerilog to C++/SystemC, or want high-speed simulation, Verilator is the tool for you. - Performance =========== @@ -96,7 +100,6 @@ Mentor ModelSim/Questa, Synopsys VCS, VTOC, and Pragmatic CVer/CVC). But, Verilator is open-sourced, so you can spend on computes rather than licenses. Thus, Verilator gives you the best simulation cycles/dollar. - Installation & Documentation ============================ @@ -115,7 +118,6 @@ For more information: - `Verilator issues `_ - Support ======= @@ -132,7 +134,6 @@ Verilator also supports and encourages commercial support models and organizations; please see `Verilator Commercial Support `_. - Related Projects ================ @@ -149,7 +150,6 @@ Related Projects - `Surfer `_ - Web or offline waveform viewer for Verilator traces. - Open License ============ @@ -161,10 +161,17 @@ the terms of either the GNU Lesser General Public License Version 3 or the Perl Artistic License Version 2.0. See the documentation for more details. .. _chips alliance: https://chipsalliance.org + .. _icarus verilog: https://steveicarus.github.io/iverilog + .. _linux foundation: https://www.linuxfoundation.org + .. |Logo| image:: https://www.veripool.org/img/verilator_256_200_min.png + .. |verilator multithreaded performance| image:: https://www.veripool.org/img/verilator_multithreaded_performance_bg-min.png + .. |verilator usage| image:: https://www.veripool.org/img/verilator_usage_400x200-min.png + .. |verilator community| image:: https://www.veripool.org/img/verilator_community_400x125-min.png + .. |verilator support| image:: https://www.veripool.org/img/verilator_support_400x125-min.png diff --git a/bin/verilator b/bin/verilator index 10176f45c..cb866e243 100755 --- a/bin/verilator +++ b/bin/verilator @@ -438,6 +438,7 @@ detailed descriptions of these arguments. --diagnostics-sarif-output Set SARIF diagnostics output file --dpi-hdr-only Only produce the DPI header file --dump- Enable dumping everything in source file + --dump-ast-patterns Enable dumping Ast pattern statistics --dump-defines Show preprocessor defines with -E --dump-dfg Enable dumping DfgGraphs to .dot files --dump-dfg-patterns Enable dumping Dfg pattern statistics @@ -652,6 +653,7 @@ description of these arguments. +verilator+solver+file+ Set random solver log filename +verilator+V Show verbose version and config +verilator+version Show version and exit + +verilator+vpi+[:] Load VPI shared library +verilator+wno+unsatconstr+ Disable constraint warnings diff --git a/ci/ci-build.bash b/ci/ci-build.bash new file mode 100755 index 000000000..64f037b33 --- /dev/null +++ b/ci/ci-build.bash @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# DESCRIPTION: Verilator: CI build job script +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +################################################################################ +# Executed in the 'build' stage. +################################################################################ + +# Destructive to the checkout (reconfigures and rebuilds); never run locally +if [ "$GITHUB_ACTIONS" != "true" ]; then + echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2 + exit 1 +fi + +set -e +set -x + +source "$(dirname "$0")/ci-common.bash" + +################################################################################ +# Parse arguments + +OPT_ASAN=0 +OPT_CCWARN=0 +OPT_GCOV=0 +OPT_COMPILER= +OPT_PREFIX= +while [ $# -gt 0 ]; do + case "$1" in + --asan) OPT_ASAN=1 ;; + --ccwarn) OPT_CCWARN=1 ;; + --compiler) + [ $# -ge 2 ] || fatal "--compiler requires an argument" + OPT_COMPILER="$2" + shift + ;; + --gcov) OPT_GCOV=1 ;; + --prefix) + [ $# -ge 2 ] || fatal "--prefix requires an argument" + OPT_PREFIX="$2" + shift + ;; + *) fatal "Unknown option: '$1'" ;; + esac + shift +done + +[ -n "$OPT_COMPILER" ] || fatal "--compiler is required" +[ -n "$OPT_PREFIX" ] || fatal "--prefix is required" + +# Map the compiler name to the executable name configure CXX expects +case "$OPT_COMPILER" in + clang) CXX=clang++ ;; + gcc) CXX=g++ ;; + *) fatal "Unknown compiler: '$OPT_COMPILER'" ;; +esac + +################################################################################ +# Configure + +CONFIGURE_ARGS="--prefix=$OPT_PREFIX --enable-longtests --enable-light-debug" +if [ "$OPT_CCWARN" = 1 ]; then + CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-ccwarn" +fi +if [ "$OPT_ASAN" = 1 ]; then + CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-asan" + CXX="$CXX -DVL_LEAK_CHECKS" +fi +if [ "$OPT_GCOV" = 1 ]; then + CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-gcov" +fi +autoconf +./configure $CONFIGURE_ARGS CXX="$CXX" + +################################################################################ +# Build + +ccache -z +BUILD_START=$SECONDS +"$MAKE" -j "$NPROC" -k +ccache -svv +ccache --evict-older-than "$((SECONDS - BUILD_START + 60))s" +ccache -svv diff --git a/ci/ci-common.bash b/ci/ci-common.bash new file mode 100644 index 000000000..1bc8826a3 --- /dev/null +++ b/ci/ci-common.bash @@ -0,0 +1,35 @@ +# DESCRIPTION: Verilator: CI common definitions, sourced by the stage scripts +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +################################################################################ +# Common definitions sourced by the CI stage scripts; not executed directly. +################################################################################ + +fatal() { + echo "ERROR: $(basename "$0"): $1" >&2; exit 1; +} + +case "$(uname -s)" in + Linux) + HOST_OS=linux + MAKE=make + NPROC=$(nproc) + # Distro id/version; subshell keeps os-release out of our namespace + if [ -r /etc/os-release ]; then + DISTRO_ID=$(. /etc/os-release; echo "$ID") + DISTRO_VERSION=$(. /etc/os-release; echo "$VERSION_ID") + fi + ;; + Darwin) + HOST_OS=macOS + MAKE=make + NPROC=$(sysctl -n hw.logicalcpu) + ;; + *) + fatal "Unknown host OS: '$(uname -s)'" + ;; +esac + +export MAKE diff --git a/ci/ci-install.bash b/ci/ci-install.bash index b3ae2e06d..fa14be495 100755 --- a/ci/ci-install.bash +++ b/ci/ci-install.bash @@ -1,7 +1,7 @@ #!/usr/bin/env bash # DESCRIPTION: Verilator: CI dependency install script # -# SPDX-FileCopyrightText: 2020 Geza Lore +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ################################################################################ @@ -10,141 +10,168 @@ # required by the particular build stage. ################################################################################ +# Installs system packages with sudo; never run locally +if [ "$GITHUB_ACTIONS" != "true" ]; then + echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2 + exit 1 +fi + set -e set -x cd $(dirname "$0")/.. +source "$(dirname "$0")/ci-common.bash" + +################################################################################ +# Parse arguments + +# Which stage to install dependencies for: 'build' or 'test' +STAGE="$1" + +################################################################################ +# Install dependencies + # Avoid occasional cpan failures "Issued certificate has expired." export PERL_LWP_SSL_VERIFY_HOSTNAME=0 echo "check_certificate = off" >> ~/.wgetrc -fatal() { - echo "ERROR: $(basename "$0"): $1" >&2; exit 1; -} - -if [ "$CI_OS_NAME" = "linux" ]; then - MAKE=make -elif [ "$CI_OS_NAME" = "osx" ]; then - MAKE=make -elif [ "$CI_OS_NAME" = "freebsd" ]; then - MAKE=gmake -else - fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'" -fi - -if [ "$CI_OS_NAME" = "linux" ]; then +if [ "$HOST_OS" = "linux" ]; then # Avoid slow "processing triggers for man db" echo "path-exclude /usr/share/doc/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc echo "path-exclude /usr/share/man/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc +elif [ "$HOST_OS" = "macOS" ]; then + # The macos runner image ships an untrusted third-party tap we don't use; + # untap it so brew stops emitting a tap-trust warning. Force + '|| true' since + # untap fails if a formula was installed from it, which is harmless here. + brew untap --force aws/tap || true fi install-wavediff() { source ci/docker/buildenv/wavetools.conf local _base_url="https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}" local _platform - if [ "$CI_OS_NAME" = "linux" ]; then + if [ "$HOST_OS" = "linux" ]; then _platform="linux-x86_64" - elif [ "$CI_OS_NAME" = "osx" ]; then + elif [ "$HOST_OS" = "macOS" ]; then _platform="macos-arm64" - elif [ "$CI_OS_NAME" = "windows" ]; then - _platform="windows-x86_64" else - echo "WARNING: No wavetools binary available for CI_OS_NAME=$CI_OS_NAME, skipping" + echo "WARNING: No wavetools binary available for HOST_OS=$HOST_OS, skipping" return 0 fi local _tmpdir _tmpdir=$(mktemp -d) local _archive="wavetools-${WAVETOOLS_VERSION}-${_platform}" - if [ "$CI_OS_NAME" = "windows" ]; then - wget -q -O "${_tmpdir}/${_archive}.zip" "${_base_url}/${_archive}.zip" - unzip -o "${_tmpdir}/${_archive}.zip" -d "${_tmpdir}" - else - wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz" - tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}" - fi + wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz" + tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}" sudo cp "${_tmpdir}/${_archive}/wavediff" /usr/local/bin/wavediff rm -rf "${_tmpdir}" } -if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then +if [ "$STAGE" = "build" ]; then ############################################################################## # Dependencies of jobs in the 'build' stage, i.e.: packages required to # build Verilator - if [ "$CI_OS_NAME" = "linux" ]; then - sudo apt-get update || - sudo apt-get update - sudo apt-get install --yes ccache help2man libfl-dev || - sudo apt-get install --yes ccache help2man libfl-dev - if [[ ! "$CI_RUNS_ON" =~ "ubuntu-22.04" ]]; then - # Some conflict of libunwind verison on 22.04, can live without it for now - sudo apt-get install --yes libjemalloc-dev || - sudo apt-get install --yes libjemalloc-dev - fi - if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then - if [[ ! "$CI_RUNS_ON" =~ "-riscv" ]]; then - sudo apt-get install --yes libsystemc libsystemc-dev || - sudo apt-get install --yes libsystemc libsystemc-dev + if [ "$HOST_OS" = "linux" ]; then + if [ "$DISTRO_ID" = "ubuntu" ]; then + PACKAGES=( + bear + ccache + help2man + libfl-dev + libsystemc-dev + mold + ) + # libunwind conflict on 22.04, can live without libjemalloc there + if [ "$DISTRO_VERSION" != "22.04" ]; then + PACKAGES+=(libjemalloc-dev) fi + sudo apt-get update || + sudo apt-get update + sudo apt-get install --yes "${PACKAGES[@]}" || + sudo apt-get install --yes "${PACKAGES[@]}" fi - if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then - sudo apt-get install --yes bear mold || - sudo apt-get install --yes bear mold - fi - elif [ "$CI_OS_NAME" = "osx" ]; then + elif [ "$HOST_OS" = "macOS" ]; then + PACKAGES=( + autoconf + bison + ccache + flex + gperftools + help2man + perl + ) brew update || brew update - brew install ccache perl gperftools autoconf bison flex help2man || - brew install ccache perl gperftools autoconf bison flex help2man - elif [ "$CI_OS_NAME" = "freebsd" ]; then - sudo pkg install -y autoconf bison ccache gmake perl5 + brew install "${PACKAGES[@]}" || + brew install "${PACKAGES[@]}" else - fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'" + fatal "Unknown HOST_OS: '$HOST_OS'" fi - - if [ -n "$CCACHE_DIR" ]; then - mkdir -p "$CCACHE_DIR" - fi -elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then +elif [ "$STAGE" = "test" ]; then ############################################################################## # Dependencies of jobs in the 'test' stage, i.e.: packages required to # run the tests - if [ "$CI_OS_NAME" = "linux" ]; then - sudo apt-get update || - sudo apt-get update - # libfl-dev needed for internal coverage's test runs - sudo apt-get install --yes gdb gtkwave lcov libfl-dev ccache jq z3 || - sudo apt-get install --yes gdb gtkwave lcov libfl-dev ccache jq z3 - # Required for test_regress/t/t_dist_attributes.py - if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then - sudo apt-get install --yes python3-clang mold || - sudo apt-get install --yes python3-clang mold + if [ "$HOST_OS" = "linux" ]; then + if [ "$DISTRO_ID" = "ubuntu" ]; then + PACKAGES=( + ccache + gdb + jq + lcov + libfl-dev + libsystemc-dev + mold + python3-clang + z3 + ) + sudo apt-get update || + sudo apt-get update + sudo apt-get install --yes "${PACKAGES[@]}" || + sudo apt-get install --yes "${PACKAGES[@]}" fi - if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then - if [[ ! "$CI_RUNS_ON" =~ "-riscv" ]]; then - sudo apt-get install --yes libsystemc libsystemc-dev || - sudo apt-get install --yes libsystemc libsystemc-dev - fi - fi - elif [ "$CI_OS_NAME" = "osx" ]; then + elif [ "$HOST_OS" = "macOS" ]; then + PACKAGES=( + ccache + jq + perl + z3 + ) + brew update || brew update - # brew cask install gtkwave # fst2vcd hangs at launch, so don't bother - brew install ccache perl jq z3 - elif [ "$CI_OS_NAME" = "freebsd" ]; then - # fst2vcd fails with "Could not open '', exiting." - sudo pkg install -y ccache gmake perl5 python3 jq z3 + brew install "${PACKAGES[@]}" || + brew install "${PACKAGES[@]}" else - fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'" + fatal "Unknown HOST_OS: '$HOST_OS'" fi # Common installs install-wavediff # Workaround -fsanitize=address crash sudo sysctl -w vm.mmap_rnd_bits=28 +elif [ "$STAGE" = "lint-py" ]; then + # nodist/clang_check_attributes. + if [ "$HOST_OS" = "linux" ] && [ "$DISTRO_ID" = "ubuntu" ]; then + PACKAGES=( + python3-clang # Not run, but importers are linted + ) + sudo apt-get update || + sudo apt-get update + sudo apt-get install --yes "${PACKAGES[@]}" || + sudo apt-get install --yes "${PACKAGES[@]}" + fi else ############################################################################## # Unknown build stage - fatal "Unknown CI_BUILD_STAGE_NAME: '$CI_BUILD_STAGE_NAME'" + fatal "Unknown stage '$STAGE' (expected 'build', 'test' or 'lint-py')" fi + +# Report where the tools we may have installed live (ok if some are missing) +set +x +echo "Tools:" +for bin in autoconf bear bison ccache flex gdb help2man jq lcov mold perl wavediff z3; do + echo -n " $bin: " + which "$bin" || echo "Not found" +done diff --git a/ci/ci-pages-notify.bash b/ci/ci-pages-notify.bash index e5dd599b6..67fa85dda 100755 --- a/ci/ci-pages-notify.bash +++ b/ci/ci-pages-notify.bash @@ -34,9 +34,14 @@ for RUN_ID in ${PR_RUN_IDS//,/ }; do cat ${ARTIFACTS_DIR}/body.txt gh pr comment $(cat ${ARTIFACTS_DIR}/pr-number.txt) --body-file ${ARTIFACTS_DIR}/body.txt - # Get the artifact ID - ARTIFACT_ID=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id') + # Get the artifact IDs. Note there can be more than one artifact named + # 'pr-notification' for a single run, as the artifacts endpoint lists + # artifacts across all run attempts, and a re-run uploads a new one while + # keeping the previous attempt's artifact. + ARTIFACT_IDS=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id') - # Delete it, so we only notify once - gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}" + # Delete them all, so we only notify once + for ARTIFACT_ID in ${ARTIFACT_IDS}; do + gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}" + done done diff --git a/ci/ci-script.bash b/ci/ci-script.bash deleted file mode 100755 index aa7a5987a..000000000 --- a/ci/ci-script.bash +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env bash -# DESCRIPTION: Verilator: CI main job script -# -# SPDX-FileCopyrightText: 2020 Geza Lore -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -################################################################################ -# This is the main script executed in the 'script' phase by all jobs. We use a -# single script to keep the CI setting simple. We pass job parameters via -# environment variables using 'env' keys. -################################################################################ - -set -e -set -x - -fatal() { - echo "ERROR: $(basename "$0"): $1" >&2; exit 1; -} - -if [ "$CI_OS_NAME" = "linux" ]; then - export MAKE=make - NPROC=$(nproc) -elif [ "$CI_OS_NAME" = "osx" ]; then - export MAKE=make - NPROC=$(sysctl -n hw.logicalcpu) - # Disable ccache, doesn't always work in GitHub Actions - export OBJCACHE= -elif [ "$CI_OS_NAME" = "freebsd" ]; then - export MAKE=gmake - NPROC=$(sysctl -n hw.ncpu) -else - fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'" -fi -NPROC=$(expr $NPROC '+' 1) - -if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then - ############################################################################## - # Build verilator - - autoconf - CONFIGURE_ARGS="--enable-longtests --enable-ccwarn" - if [ "$CI_DEV_ASAN" = 1 ]; then - CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-asan" - CXX="$CXX -DVL_LEAK_CHECKS" - fi - if [ "$CI_DEV_GCOV" = 1 ]; then - CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-gcov" - fi - ./configure $CONFIGURE_ARGS --prefix="$INSTALL_DIR" - ccache -z - "$MAKE" -j "$NPROC" -k - # 22.04: ccache -s -v - ccache -s - if [ "$CI_OS_NAME" = "osx" ]; then - file bin/verilator_bin - file bin/verilator_bin_dbg - md5 bin/verilator_bin - md5 bin/verilator_bin_dbg - stat bin/verilator_bin - stat bin/verilator_bin_dbg - fi -elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then - ############################################################################## - # Run tests - - export VERILATOR_TEST_NO_CONTRIBUTORS=1 # Separate workflow check - export VERILATOR_TEST_NO_LINT_PY=1 # Separate workflow check - - if [ "$CI_OS_NAME" = "osx" ]; then - export VERILATOR_TEST_NO_GDB=1 # Pain to get GDB to work on OS X - # TODO below may no longer be required as configure checks for -pg - export VERILATOR_TEST_NO_GPROF=1 # Apple Clang has no -pg - # export PATH="/Applications/gtkwave.app/Contents/Resources/bin:$PATH" # fst2vcd - file bin/verilator_bin - file bin/verilator_bin_dbg - md5 bin/verilator_bin - md5 bin/verilator_bin_dbg - stat bin/verilator_bin - stat bin/verilator_bin_dbg - # For some reason, the dbg exe is corrupted by this point ('file' reports - # it as data rather than a Mach-O). Unclear if this is an OS X issue or - # CI's. Remove the file and re-link... - rm bin/verilator_bin_dbg - "$MAKE" -j "$NPROC" -k - elif [ "$CI_OS_NAME" = "freebsd" ]; then - export VERILATOR_TEST_NO_GDB=1 # Disable for now, ideally should run - # TODO below may no longer be required as configure checks for -pg - export VERILATOR_TEST_NO_GPROF=1 # gprof is a bit different on FreeBSD, disable - fi - - TEST_REGRESS=test_regress - if [ "$CI_RELOC" == 1 ]; then - # Testing that the installation is relocatable. - "$MAKE" install - mkdir -p "$RELOC_DIR" - mv "$INSTALL_DIR" "$RELOC_DIR/relocated-install" - export VERILATOR_ROOT="$RELOC_DIR/relocated-install/share/verilator" - TEST_REGRESS="$RELOC_DIR/test_regress" - mv test_regress "$TEST_REGRESS" - NODIST="$RELOC_DIR/nodist" - mv nodist "$NODIST" - # Feeling brave? - find . -delete - ls -la . - fi - - # Run the specified test - ccache -z - case $TESTS in - dist-vlt-0) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=0/4 - ;; - dist-vlt-1) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=1/4 - ;; - dist-vlt-2) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=2/4 - ;; - dist-vlt-3) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=3/4 - ;; - vltmt-0) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=0/3 - ;; - vltmt-1) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=1/3 - ;; - vltmt-2) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=2/3 - ;; - coverage-dist) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist" - ;; - coverage-vlt-0) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=0/10 - ;; - coverage-vlt-1) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=1/10 - ;; - coverage-vlt-2) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=2/10 - ;; - coverage-vlt-3) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=3/10 - ;; - coverage-vlt-4) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=4/10 - ;; - coverage-vlt-5) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=5/10 - ;; - coverage-vlt-6) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=6/10 - ;; - coverage-vlt-7) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=7/10 - ;; - coverage-vlt-8) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=8/10 - ;; - coverage-vlt-9) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=9/10 - ;; - coverage-vltmt-0) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=0/10 - ;; - coverage-vltmt-1) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=1/10 - ;; - coverage-vltmt-2) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=2/10 - ;; - coverage-vltmt-3) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=3/10 - ;; - coverage-vltmt-4) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=4/10 - ;; - coverage-vltmt-5) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=5/10 - ;; - coverage-vltmt-6) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=6/10 - ;; - coverage-vltmt-7) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=7/10 - ;; - coverage-vltmt-8) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=8/10 - ;; - coverage-vltmt-9) - "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=9/10 - ;; - *) - fatal "Unknown TESTS: $TESTS" - ;; - esac - - # To see load average (1 minute, 5 minute, 15 minute) - uptime - # 22.04: ccache -s -v - ccache -s - -else - ############################################################################## - # Unknown build stage - fatal "Unknown CI_BUILD_STAGE_NAME: '$CI_BUILD_STAGE_NAME'" -fi diff --git a/ci/ci-test.bash b/ci/ci-test.bash new file mode 100755 index 000000000..9d0b69517 --- /dev/null +++ b/ci/ci-test.bash @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +# DESCRIPTION: Verilator: CI test job script +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +################################################################################ +# Executed in the 'test' stage. +################################################################################ + +# Destructive to the checkout (wipes most of it); never run locally +if [ "$GITHUB_ACTIONS" != "true" ]; then + echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2 + exit 1 +fi + +set -e +set -x + +source "$(dirname "$0")/ci-common.bash" + +################################################################################ +# Parse arguments + +OPT_RELOC= +OPT_SUITE= +while [ $# -gt 0 ]; do + case "$1" in + --reloc) + [ $# -ge 2 ] || fatal "--reloc requires an argument" + OPT_RELOC="$2" + shift + ;; + --suite) + [ $# -ge 2 ] || fatal "--suite requires an argument" + OPT_SUITE="$2" + shift + ;; + *) fatal "Unknown option: '$1'" ;; + esac + shift +done + +[ -n "$OPT_SUITE" ] || fatal "--suite is required" + +################################################################################ +# Run tests + +export VERILATOR_TEST_NO_CONTRIBUTORS=1 # Separate workflow check +export VERILATOR_TEST_NO_LINT_PY=1 # Separate workflow check + +if [ "$HOST_OS" = "macOS" ]; then + export VERILATOR_TEST_NO_GDB=1 # No working GDB on macOS +fi + +TEST_REGRESS=test_regress +if [ -n "$OPT_RELOC" ]; then + # Testing that the installation is relocatable. + "$MAKE" install + # Install prefix, as configured into the Makefile at build time + INSTALL_DIR=$(sed -n 's/^prefix = //p' Makefile) + mkdir -p "$OPT_RELOC" + mv "$INSTALL_DIR" "$OPT_RELOC/relocated-install" + export VERILATOR_ROOT="$OPT_RELOC/relocated-install/share/verilator" + TEST_REGRESS="$OPT_RELOC/test_regress" + mv test_regress "$TEST_REGRESS" + NODIST="$OPT_RELOC/nodist" + mv nodist "$NODIST" + # Delete everything else, but keep the CI infrastructure + find . -mindepth 1 -maxdepth 1 ! -name .github ! -name ci -exec rm -rf {} + + ls -la . +fi + +# Run the specified suite +ccache -z +TEST_START=$SECONDS +case $OPT_SUITE in + dist-vlt-0) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=0/4 + ;; + dist-vlt-1) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=1/4 + ;; + dist-vlt-2) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=2/4 + ;; + dist-vlt-3) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=3/4 + ;; + vltmt-0) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=0/3 + ;; + vltmt-1) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=1/3 + ;; + vltmt-2) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=2/3 + ;; + coverage-dist) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist" + ;; + coverage-vlt-0) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=0/10 + ;; + coverage-vlt-1) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=1/10 + ;; + coverage-vlt-2) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=2/10 + ;; + coverage-vlt-3) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=3/10 + ;; + coverage-vlt-4) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=4/10 + ;; + coverage-vlt-5) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=5/10 + ;; + coverage-vlt-6) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=6/10 + ;; + coverage-vlt-7) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=7/10 + ;; + coverage-vlt-8) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=8/10 + ;; + coverage-vlt-9) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=9/10 + ;; + coverage-vltmt-0) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=0/10 + ;; + coverage-vltmt-1) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=1/10 + ;; + coverage-vltmt-2) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=2/10 + ;; + coverage-vltmt-3) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=3/10 + ;; + coverage-vltmt-4) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=4/10 + ;; + coverage-vltmt-5) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=5/10 + ;; + coverage-vltmt-6) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=6/10 + ;; + coverage-vltmt-7) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=7/10 + ;; + coverage-vltmt-8) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=8/10 + ;; + coverage-vltmt-9) + "$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=9/10 + ;; + *) + fatal "Unknown suite: $OPT_SUITE" + ;; +esac +ccache -svv +ccache --evict-older-than "$((SECONDS - TEST_START + 60))s" +ccache -svv +uptime # To see load average diff --git a/ci/ci-win-compile.ps1 b/ci/ci-win-compile.ps1 index 2722d8362..8f019b5e5 100644 --- a/ci/ci-win-compile.ps1 +++ b/ci/ci-win-compile.ps1 @@ -6,19 +6,33 @@ Set-PSDebug -Trace 1 -if (-Not (Test-Path $PWD/../.ccache/win_bison.exe)) { +$NPROC = $env:NUMBER_OF_PROCESSORS + +# Absolute path for the win_flex_bison install; CMake reads this from the environment +$env:WIN_FLEX_BISON = "$(Resolve-Path ..)/win_flex_bison" + +if (-Not (Test-Path $env:WIN_FLEX_BISON/win_bison.exe)) { git clone --depth 1 https://github.com/lexxmark/winflexbison cd winflexbison mkdir build cd build - cmake .. --install-prefix $PWD/../../../.ccache - cmake --build . --config Release -j 3 - cmake --install . --prefix $PWD/../../../.ccache + cmake .. --install-prefix $env:WIN_FLEX_BISON + cmake --build . --config Release -j $NPROC + cmake --install . --prefix $env:WIN_FLEX_BISON cd ../.. } +# Enter the MSVC developer shell so cl/link are on PATH for the Ninja generator +$VsPath = & "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" -latest -products * -property installationPath +Import-Module "$VsPath/Common7/Tools/Microsoft.VisualStudio.DevShell.dll" +Enter-VsDevShell -VsInstallPath $VsPath -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64" + mkdir build cd build -cmake .. --install-prefix $PWD/../install -cmake --build . --config Release -j 3 +# Ninja saturates all cores; the MSBuild generator only parallelizes across +# projects and Verilator is a single target, so it compiles serially. /Od skips +# optimization: this job only checks that Verilator builds and can verilate an +# example, so an optimized binary is not needed and the codegen time dominates. +cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release --install-prefix $PWD/../install "-DCMAKE_CXX_FLAGS_RELEASE=/Od /DNDEBUG" +cmake --build . cmake --install . --prefix $PWD/../install diff --git a/ci/ci-win-test.ps1 b/ci/ci-win-test.ps1 index 23697fe09..288bcbe37 100644 --- a/ci/ci-win-test.ps1 +++ b/ci/ci-win-test.ps1 @@ -7,13 +7,17 @@ Set-PSDebug -Trace 1 +$NPROC = $env:NUMBER_OF_PROCESSORS + cd install $Env:VERILATOR_ROOT=$PWD cd examples/cmake_tracing_c mkdir build cd build -cmake .. -cmake --build . --config Release -j 3 +# /Od skips optimization; this only checks the example verilates and builds, so +# an optimized binary is not needed (see ci-win-compile.ps1). +cmake .. "-DCMAKE_CXX_FLAGS_RELEASE=/Od /DNDEBUG" +cmake --build . --config Release -j $NPROC # TODO put this back in, see issue# 5163 # Release/example.exe diff --git a/ci/docker/buildenv/README.rst b/ci/docker/buildenv/README.rst index 146725466..e1648420d 100644 --- a/ci/docker/buildenv/README.rst +++ b/ci/docker/buildenv/README.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _verilator build docker container: diff --git a/ci/docker/run/README.rst b/ci/docker/run/README.rst index 1ef7f9fb4..4feaf0804 100644 --- a/ci/docker/run/README.rst +++ b/ci/docker/run/README.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Verilator Executable Docker Container ===================================== diff --git a/configure.ac b/configure.ac index 6917f0c4b..893bf91d0 100644 --- a/configure.ac +++ b/configure.ac @@ -12,7 +12,7 @@ # Then 'make maintainer-dist' #AC_INIT([Verilator],[#.### YYYY-MM-DD]) #AC_INIT([Verilator],[#.### devel]) -AC_INIT([Verilator],[5.049 devel], +AC_INIT([Verilator],[5.051 devel], [https://verilator.org], [verilator],[https://verilator.org]) @@ -129,6 +129,21 @@ AC_ARG_ENABLE([dev-gcov], AC_SUBST(CFG_WITH_DEV_GCOV) AC_MSG_RESULT($CFG_WITH_DEV_GCOV) +# Flag to reduce the debug info in the debug executable to minimize its size +AC_MSG_CHECKING(whether to reduce debug info in the debug executable) +AC_ARG_ENABLE([light-debug], + [AS_HELP_STRING([--enable-light-debug], + [Reduce the amount of debug information in the debug + Verilator executable to minimize its size. Enable + slight optimization. Enough for backtraces only.])], + [case "${enableval}" in + yes) CFG_WITH_LIGHT_DEBUG=yes ;; + no) CFG_WITH_LIGHT_DEBUG=no ;; + *) AC_MSG_ERROR([bad value '${enableval}' for --enable-light-debug]) ;; + esac], + CFG_WITH_LIGHT_DEBUG=no) +AC_MSG_RESULT($CFG_WITH_LIGHT_DEBUG) + # Special Substitutions - CFG_WITH_DEFENV AC_MSG_CHECKING(whether to use hardcoded paths) AC_ARG_ENABLE([defenv], @@ -518,15 +533,27 @@ _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_PARSER,-Wno-unused) AC_SUBST(CFG_CXXFLAGS_PARSER) # Flags for compiling the debug version of Verilator (in addition to above CFG_CXXFLAGS_SRC) -if test "$CFG_WITH_DEV_GCOV" = "no"; then # Do not optimize for the coverage build -_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-Og) +if test "$CFG_WITH_LIGHT_DEBUG" = "yes"; then + # Slight optimization and minimal compressed debug info. This is enough for + # --gdb/--gdbbt backtraces, but omits the bulk of the debug info, which + # significantly reduces object file sizes. For CI or release builds. + if test "$CFG_WITH_DEV_GCOV" = "no"; then # Do not optimize for the coverage build + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-Og) + fi + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb1) + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-fdebug-info-for-profiling) # For fully-qualified names with clang + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-gz) +else + # Full debug: no optimization, full debug info, for development. + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-O0) + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb) fi -_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb) -_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-gz) AC_SUBST(CFG_CXXFLAGS_DBG) # Flags for linking the debug version of Verilator (in addition to above CFG_LDFLAGS_SRC) -_MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_DBG,-gz) +if test "$CFG_WITH_LIGHT_DEBUG" = "yes"; then + _MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_DBG,-gz) +fi AC_SUBST(CFG_LDFLAGS_DBG) # Flags for compiling the optimized version of Verilator (in addition to above CFG_CXXFLAGS_SRC) @@ -589,6 +616,34 @@ m4_foreach([ldflag], [ AC_SUBST(CFG_LDLIBS_THREADS) AC_SUBST(CFG_LDFLAGS_THREADS_CMAKE) +# Find link flags for runtime VPI library loading (+verilator+vpi+). +# The model executable must export its VPI symbols so the dlopen'd library can +# resolve them: -rdynamic (GNU ld) or -Wl,-export_dynamic (Darwin); the first the +# linker accepts wins. -ldl provides dlopen/dlsym where it is a separate library. +_MY_LDLIBS_CHECK_SET(CFG_LDFLAGS_DYNAMIC, -rdynamic) +# -Wl,-export_dynamic contains a comma, so probe it directly rather than through +# the _MY_LDLIBS_CHECK_* macros (which re-expand their flag argument unquoted). +if test "$CFG_LDFLAGS_DYNAMIC" = ""; then + ACO_SAVE_LIBS="$LIBS" + LIBS="$LIBS -Wl,-export_dynamic" + AC_MSG_CHECKING([whether $CXX linker accepts -Wl,-export_dynamic]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[]])], + [_my_result=yes + if test -s conftest.err; then + if grep -e "-export_dynamic" conftest.err >/dev/null; then + _my_result=no + fi + fi], + [_my_result=no]) + AC_MSG_RESULT($_my_result) + LIBS="$ACO_SAVE_LIBS" + if test "$_my_result" = "yes"; then CFG_LDFLAGS_DYNAMIC="-Wl,-export_dynamic"; fi +fi +AC_SUBST(CFG_LDFLAGS_DYNAMIC) +_MY_LDLIBS_CHECK_OPT(CFG_LDLIBS_DYNAMIC, -ldl) +AC_SUBST(CFG_LDLIBS_DYNAMIC) + # If 'mold' is installed, use it to link for faster buildtimes _MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_SRC, -fuse-ld=mold) _MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_VERILATED, -fuse-ld=mold) diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 000000000..1c0524c78 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,38 @@ + + +# docs/ Guidelines -- Verilator documentation (\*.rst) + +When to check: before editing anything under `docs/`. +Read the repository-root [AGENTS.md](../AGENTS.md) first for process and cross-cutting rules. + +## Writing rules + +- Rewrap paragraphs after editing -- keep consistent line length in `*.rst` files. + +- Document only stable, implemented features -- never planned or in-development capabilities; prevents user confusion and support burden. + +- Explain WHAT and WHEN, not HOW -- conceptual purpose and practical use cases over implementation mechanics; describe behavior ("optimized as pure", not "treated as pure") and clarify ambiguous referents ("in the internals of Verilator"). + +- Keep terminology consistent -- one term per concept; update docs when renaming code constructs; spell out full terms, avoiding abbreviations like "sim"/"sims". + +- Use "how many" for countable nouns (threads, tasks, workers) and "how much" for uncountable quantities. + +- Mark internal or experimental features "for internal use only" -- prevents user dependence and forced deprecation cycles later. + +- Use specific IEEE references: `IEEE {number}-{year}` plus the section (e.g. `Annex I`) -- a vague "IEEE spec requires" is unverifiable. + +- Document all flags with descriptions, not just syntax. + +## reStructuredText mechanics + +- Use the `:vlopt:` role for Verilator option references -- makes cross-references clickable and consistent. + +- Escape angle brackets (`\<`, `\>`) in link targets -- prevents broken links to command-line options. + +- Generate documentation examples with `test.extract` from `test_regress` test files -- examples stay synced with actually tested behavior. + +## Hard constraint + +- Never edit `docs/CONTRIBUTORS` -- only humans may, after reading and agreeing to its statement; remind the user instead. diff --git a/docs/CONTRIBUTING.rst b/docs/CONTRIBUTING.rst index 6567d22b4..bec0684c6 100644 --- a/docs/CONTRIBUTING.rst +++ b/docs/CONTRIBUTING.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Contributing to Verilator ========================= diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index ef3746a76..24e3ced87 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -9,6 +9,7 @@ Please see the Verilator manual for 200+ additional contributors. Thanks to all. 404allen404 Adam Bagley +Adam Kostrzewski Adrian Sampson Adrien Le Masle أحمد المحمودي (Ahmed El-Mahmoudy) @@ -60,6 +61,7 @@ David Turner Dercury Diego Roux Dominick Grochowina +Dragon-Git Don Williamson Drew Ranck Drew Taussig @@ -68,6 +70,7 @@ Edgar E. Iglesias Eric Mejdrich Eric Müller Eric Rippey +Eryk Szpotański Eunseo Song Ethan Sifferman Eyck Jentzsch @@ -145,6 +148,7 @@ Jose Loyola Josep Sans Joseph Nwabueze Josh Redford +Joshua Leahy Julian Carrier Julian Daube Julie Schwartz @@ -158,6 +162,7 @@ Kefa Chen Keith Colbert Kevin Kiningham Kevin Nygaard +Kornel Uriasz Kritik Bhimani Krzysztof Bieganski Krzysztof Boronski @@ -250,10 +255,12 @@ Rowan Goemans Rupert Swarbrick Ryan Ziegler Ryszard Rozak +Saksham Gupta Samuel Riedel Sean Cross Sebastien Van Cauwenberghe Secturion +Sergey Chusov Sergey Fedorov Sergi Granell Seth Pellegrino @@ -270,12 +277,14 @@ Teng Huang Thomas Aldrian Thomas Brown Thomas Dybdahl Ahle +Thomas Santerre Tim Hutt Tim Snyder Tobias Jensen Tobias Rosenkranz Tobias Wölfel Todd Strader +Tom Jackson Tom Manner Tomasz Gorochowik Topa Topino @@ -297,6 +306,7 @@ Vito Gamberini Wei-Lun Chiu William D. Jones Wilson Snyder +Wolfgang Mayerwieser Xi Zhang Yan Xu Yangyu Chen @@ -326,3 +336,4 @@ Yogish Sekhar Zubin Jain Muzaffer Kal Yilin Li +Shashvat Prabhu diff --git a/docs/README.rst b/docs/README.rst index 5ace9194a..706619968 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Verilator Documentation ======================= diff --git a/docs/gen/ex_FINALDLY_faulty.rst b/docs/gen/ex_FINALDLY_faulty.rst new file mode 100644 index 000000000..78c456ad4 --- /dev/null +++ b/docs/gen/ex_FINALDLY_faulty.rst @@ -0,0 +1,7 @@ +.. comment: generated by t_finaldly_bad +.. code-block:: sv + :linenos: + :emphasize-lines: 2 + + bit foo; + final foo <= 1; // <--- Error diff --git a/docs/gen/ex_FINALDLY_msg.rst b/docs/gen/ex_FINALDLY_msg.rst new file mode 100644 index 000000000..4178e2669 --- /dev/null +++ b/docs/gen/ex_FINALDLY_msg.rst @@ -0,0 +1,6 @@ +.. comment: generated by t_finaldly_bad +.. code-block:: + + %Error-FINALDLY: example.v:1:13 Non-blocking assignment '<=' in final block + 9 | final foo <= 1; + | ^~ diff --git a/docs/guide/changes.rst b/docs/guide/changes.rst index 09264212b..743fc77b9 100644 --- a/docs/guide/changes.rst +++ b/docs/guide/changes.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 **************** Revision History diff --git a/docs/guide/connecting.rst b/docs/guide/connecting.rst index 65640aee9..35ec9742b 100644 --- a/docs/guide/connecting.rst +++ b/docs/guide/connecting.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _connecting: @@ -28,13 +29,13 @@ The generated model class file manages all internal state required by the model, and exposes the following interface that allows interaction with the model: -* Top level IO ports are exposed as references to the appropriate internal +- Top level IO ports are exposed as references to the appropriate internal equivalents. -* Public top level module instances are exposed as pointers to allow access +- Public top level module instances are exposed as pointers to allow access to ``/* verilator public */`` items. -* The root of the design hierarchy (as in SystemVerilog ``$root``) is +- The root of the design hierarchy (as in SystemVerilog ``$root``) is exposed via the ``rootp`` member pointer to allow access to model internals, including ``/* verilator public_flat */`` items. @@ -58,22 +59,22 @@ This means that user code that accesses internal signals in the model (likely including ``/* verilator public_flat */`` signals, as they are often inlined into the root scope) will need to be updated as follows: -* No change required for accessing top level IO signals. These are directly +- No change required for accessing top level IO signals. These are directly accessible in the model class via references. -* No change required for accessing ``/* verilator public */`` items. These +- No change required for accessing ``/* verilator public */`` items. These are directly accessible via sub-module pointers in the model class. -* Accessing any other internal members, including - ``/* verilator public_flat */`` items requires the following changes: +- Accessing any other internal members, including ``/* verilator + public_flat */`` items requires the following changes: - * Additionally include :file:`{prefix}___024root.h`. This header defines + - Additionally include :file:`{prefix}___024root.h`. This header defines type of the ``rootp`` pointer within the model class. Note the ``__024`` substring is the Verilator escape sequence for the ``$`` character, i.e.: ``rootp`` points to the Verilated SystemVerilog ``$root`` scope. - * Replace ``modelp->internal->member`` references with + - Replace ``modelp->internal->member`` references with ``modelp->rootp->internal->member`` references, which contain one additional indirection via the ``rootp`` pointer. @@ -210,9 +211,9 @@ Verilator extends the DPI format to allow using the same scheme to efficiently add system functions. Use a dollar-sign prefixed system function name for the import, but note it must be escaped. -.. code-block:: sv +.. code-block:: - export "DPI-C" function integer \$myRand; + import "DPI-C" function integer \$myRand; initial $display("myRand=%d", $myRand()); @@ -502,9 +503,9 @@ described above is just a wrapper which calls these two functions. 3. If using delays and :vlopt:`--timing`, there are two additional methods the user should call: - * ``designp->eventsPending()``, which returns ``true`` if there are any + - ``designp->eventsPending()``, which returns ``true`` if there are any delayed events pending, - * ``designp->nextTimeSlot()``, which returns the simulation time of the + - ``designp->nextTimeSlot()``, which returns the simulation time of the next delayed event. This method can only be called if ``designp->eventsPending()`` returned ``true``. diff --git a/docs/guide/contributing.rst b/docs/guide/contributing.rst index 9975c7512..8f834d5a9 100644 --- a/docs/guide/contributing.rst +++ b/docs/guide/contributing.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ******************************* Contributing and Reporting Bugs @@ -86,7 +87,8 @@ Please refer to `sv-bugpoint README `_ for more information on how to use `sv-bugpoint`. -.. Contributing -.. ============ +.. + Contributing + ============ .. include:: ../CONTRIBUTING.rst diff --git a/docs/guide/contributors.rst b/docs/guide/contributors.rst index 782a79865..37d7ef9f1 100644 --- a/docs/guide/contributors.rst +++ b/docs/guide/contributors.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ************************ Contributors and Origins @@ -29,10 +30,10 @@ Alliance `_, and `Antmicro Ltd Previous major corporate sponsors of Verilator, by providing significant contributions of time or funds include: Antmicro Ltd., Atmel Corporation, Compaq Corporation, Digital Equipment Corporation, Embecosm Ltd., Fractile -Ltd., Hicamp Systems, Intel Corporation, Marvell Inc., Mindspeed Technologies -Inc., MicroTune Inc., picoChip Designs Ltd., Sun Microsystems Inc., Nauticus -Networks Inc., SiCortex Inc, Shunyao CAD, Tenstorrent USA, Inc. and Western -Digital Inc. +Ltd., Hicamp Systems, Intel Corporation, Marvell Inc., Mindspeed +Technologies Inc., MicroTune Inc., picoChip Designs Ltd., Sun Microsystems +Inc., Nauticus Networks Inc., SiCortex Inc, Shunyao CAD, Tenstorrent USA, +Inc. and Western Digital Inc. The contributors of major functionality are: Jeremy Bennett, Krzysztof Bieganski, Byron Bradley, Lane Brooks, John Coiner, Duane Galbi, Arkadiusz @@ -142,18 +143,18 @@ Krzysztof Obłonczek, Danny Oler, Andreas Olofsson, Baltazar Ortiz, Aleksander Osman, Don Owen, Tim Paine, Deepa Palaniappan, James Pallister, Vassilis Papaefstathiou, Sanggyu Park, Brad Parker, Risto Pejašinović, Seth Pellegrino, Joel Peltonen, Morten Borup Petersen, Dan Petrisko, Thanh Tung -Pham, Wesley Piard, Maciej Piechotka, David Pierce, Cody Piersall, -T. Platz, Michael Platzer, Dominic Plunkett, Nolan Poe, Tuomas Poikela, -George Polack, David Poole, Michael Popoloski, Roman Popov, Aylon Chaim -Porat, Oron Port, Rich Porter, Rick Porter, Stefan Post, Niranjan Prabhu, -Damien Pretet, Harald Pretl, Bill Pringlemeir, Usha Priyadharshini, Mark -Jackson Pulver, Prateek Puri, Nikolay Puzanov, Han Qi, Jiacheng Qian, -Marshal Qiao, Raynard Qiao, Yujia Qiao, Jasen Qin, Frank Qiu, Nandu Raj, -Kamil Rakoczy, Danilo Ramos, Drew Ranck, Chris Randall, Anton Rapp, Josh -Redford, Odd Magne Reitan, Frédéric Requin, Wajahat Riaz, Dustin Richmond, -Samuel Riedel, Alberto Del Rio, Eric Rippey, Narcis Rodas, Oleg Rodionov, -Ludwig Rogiers, Paul Rolfe, Michail Rontionov, Arjen Roodselaar, Arthur -Rosa, Tobias Rosenkranz, Yernagula Roshit, Diego Roux, Ryszard Rozak, Dan +Pham, Wesley Piard, Maciej Piechotka, David Pierce, Cody Piersall, T. +Platz, Michael Platzer, Dominic Plunkett, Nolan Poe, Tuomas Poikela, George +Polack, David Poole, Michael Popoloski, Roman Popov, Aylon Chaim Porat, +Oron Port, Rich Porter, Rick Porter, Stefan Post, Niranjan Prabhu, Damien +Pretet, Harald Pretl, Bill Pringlemeir, Usha Priyadharshini, Mark Jackson +Pulver, Prateek Puri, Nikolay Puzanov, Han Qi, Jiacheng Qian, Marshal Qiao, +Raynard Qiao, Yujia Qiao, Jasen Qin, Frank Qiu, Nandu Raj, Kamil Rakoczy, +Danilo Ramos, Drew Ranck, Chris Randall, Anton Rapp, Josh Redford, Odd +Magne Reitan, Frédéric Requin, Wajahat Riaz, Dustin Richmond, Samuel +Riedel, Alberto Del Rio, Eric Rippey, Narcis Rodas, Oleg Rodionov, Ludwig +Rogiers, Paul Rolfe, Michail Rontionov, Arjen Roodselaar, Arthur Rosa, +Tobias Rosenkranz, Yernagula Roshit, Diego Roux, Ryszard Rozak, Dan Ruelas-Petrisko, Luca Rufer, Huang Rui, Graham Rushton, Jan Egil Ruud, Denis Rystsov, Pawel Sagan, Robert Sammelson, Adrian Sampson, John Sanguinetti, Josep Sans, Dave Sargeant, Luca Sasselli, Philippe Sauter, diff --git a/docs/guide/control.rst b/docs/guide/control.rst index a01396040..8553de997 100644 --- a/docs/guide/control.rst +++ b/docs/guide/control.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _verilator control files: @@ -187,6 +188,10 @@ The grammar of control commands is as follows: .. option:: isolate_assignments -module "" [-task ""] -var "" + Deprecated and has no effect (ignored). + + In versions before 5.050: + Used to indicate that the assignments to this signal in any blocks should be isolated into new blocks. Same as :option:`/*verilator&32;isolate_assignments*/` metacomment. diff --git a/docs/guide/copyright.rst b/docs/guide/copyright.rst index 75b00d4ab..b5a59e1fe 100644 --- a/docs/guide/copyright.rst +++ b/docs/guide/copyright.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ********* Copyright diff --git a/docs/guide/deprecations.rst b/docs/guide/deprecations.rst index 75cad0492..7d69ab1c2 100644 --- a/docs/guide/deprecations.rst +++ b/docs/guide/deprecations.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Deprecations ============ diff --git a/docs/guide/environment.rst b/docs/guide/environment.rst index 1b3fd5ed4..a66ebe755 100644 --- a/docs/guide/environment.rst +++ b/docs/guide/environment.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _environment: diff --git a/docs/guide/example_binary.rst b/docs/guide/example_binary.rst index fd36fe8ce..c02469907 100644 --- a/docs/guide/example_binary.rst +++ b/docs/guide/example_binary.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _example create-binary execution: diff --git a/docs/guide/example_cc.rst b/docs/guide/example_cc.rst index 2a8384e85..d50caaa1d 100644 --- a/docs/guide/example_cc.rst +++ b/docs/guide/example_cc.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _example c++ execution: diff --git a/docs/guide/example_common_install.rst b/docs/guide/example_common_install.rst index 0e38fa6ff..1c963f39d 100644 --- a/docs/guide/example_common_install.rst +++ b/docs/guide/example_common_install.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 First you need Verilator installed, see :ref:`Installation`. In brief, if you installed Verilator using the package manager of your operating system, diff --git a/docs/guide/example_dist.rst b/docs/guide/example_dist.rst index f3e1765f5..8427394c0 100644 --- a/docs/guide/example_dist.rst +++ b/docs/guide/example_dist.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _examples in the distribution: diff --git a/docs/guide/example_sc.rst b/docs/guide/example_sc.rst index 349645080..06ec49070 100644 --- a/docs/guide/example_sc.rst +++ b/docs/guide/example_sc.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _example systemc execution: diff --git a/docs/guide/examples.rst b/docs/guide/examples.rst index 5e0b2e260..35fead694 100644 --- a/docs/guide/examples.rst +++ b/docs/guide/examples.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _examples: @@ -9,10 +10,10 @@ Examples This section covers the following examples: -* :ref:`Example Create-Binary Execution` -* :ref:`Example C++ Execution` -* :ref:`Example SystemC Execution` -* :ref:`Examples in the Distribution` +- :ref:`Example Create-Binary Execution` +- :ref:`Example C++ Execution` +- :ref:`Example SystemC Execution` +- :ref:`Examples in the Distribution` .. toctree:: :maxdepth: 1 diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index 14fecf63e..190367346 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _simulation runtime arguments: @@ -50,8 +51,8 @@ Options: .. option:: +verilator+log+file+ - Log all stdout and stderr to the specified output filename. If not specified - the normal stdout/stderr streams are used. + Log all stdout and stderr to the specified output filename. If not specified + the normal stdout/stderr streams are used. .. option:: +verilator+noassert @@ -138,6 +139,20 @@ Options: Displays program version and exits. +.. option:: +verilator+vpi+[:] + + Load a VPI shared library before simulation starts. Only available when the + model was Verilated with :vlopt:`--vpi` and :vlopt:`--main` (or + :vlopt:`--binary`). ```` is the path to the shared library. If + ``:`` is given, that named no-argument function is called; + otherwise the library's ``vlog_startup_routines`` array (IEEE 1800 38.37.2) is + invoked. May be repeated to load multiple libraries. + + Runtime loading is supported on POSIX platforms only (it relies on the + executable exporting its VPI symbols to the loaded library); on Windows the + argument is rejected and the VPI code must instead be statically linked + into the model. + .. option:: +verilator+wno+unsatconstr+ Disable unsatisfied constraint warnings at simulation runtime. When set to diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 705bf3f71..807216565 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 =================== verilator Arguments @@ -490,6 +491,10 @@ Summary: Rarely needed - for developer use. Enable all dumping in the given source file at level 3. +.. option:: --dump-ast-patterns + + Rarely needed. Enable dumping AstNodeExpr pattern statistics. + .. option:: --dump-defines With :vlopt:`-E`, suppress normal output, and instead print a list of @@ -657,8 +662,28 @@ Summary: .. option:: -fno-assemble +.. option:: -fno-bit-scan-loops + + Rarely needed. Disable converting bit counting loops into built-in operations. + .. option:: -fno-case + Rarely needed. Disable all case statement optimizations. + + Alias for all other `-fno-case-*` options. + +.. option:: -fno-case-decoder + + Rarely needed. Disable converting case statements into decoder tables. + +.. option:: -fno-case-table + + Rarely needed. Disable converting case statements into table lookups. + +.. option:: -fno-case-tree + + Rarely needed. Disable converting case statements into bit-wise branch trees. + .. option:: -fno-combine .. option:: -fno-const @@ -737,12 +762,41 @@ Summary: this is not recommended as may cause additional warnings and ordering issues. +.. option:: -fno-ico-change-detect + + Rarely needed. Disable input change detection in the input combinational + ('ico') region. With change detection enabled (the default, unless + :vlopt:`--vpi` is passed), the input combinational logic is evaluated only + when a top level input has actually changed, rather than unconditionally on + the first scheduling iteration. + + The change detection logic assumes a top level input only ever changes + externally between evaluations. The optimization is automatically disabled + for top level input signals that are written within the design. Accesses via + the VPI cannot be analyzed at compile time, therefore :vlopt:`--vpi` + disables this optimization for all inputs; it may be turned back on by + explicitly passing :vlopt:`-fico-change-detect <-fno-ico-change-detect>`. + .. option:: -fno-inline + Rarely needed. Disable module inlining. + +.. option:: -fno-inline-cfuncs + + Rarely needed. Disable inlining of small generated C++ functions into their + callers. + + This optimization is automatically disabled when :vlopt:`--prof-cfuncs` is + used. + .. option:: -fno-inline-funcs + Rarely needed. Disable inlining of SystemVerilog functions and tasks. + .. option:: -fno-inline-funcs-eager + Rarely needed. Disable eager inlining of SystemVerilog functions and tasks. + .. option:: -fno-life .. option:: -fno-life-post @@ -944,27 +998,21 @@ Summary: .. option:: --inline-cfuncs - Inline small C++ function (internal AstCFunc) calls directly into their - callers when the function has at most nodes. This reduces - function call overhead when :vlopt:`--output-split-cfuncs` places - functions in separate compilation units that the C++ compiler cannot - inline. + Tune the inlining of small generated C++ function. Functions no bigger than + nodes will be inlined if possible. The default is 20. - Set to 0 to disable this optimization. The default is 20. - - This optimization is automatically disabled when :vlopt:`--prof-cfuncs` - or :vlopt:`--trace` is used. + See also :vlopt:`--inline-cfuncs-product` and :vlopt:`-fno-inline-cfuncs`. .. option:: --inline-cfuncs-product - Tune the inlining of C++ function (internal AstCFunc) calls for larger - functions. When a function is too large to always inline (exceeds - :vlopt:`--inline-cfuncs` threshold), it may still be inlined if the - function size multiplied by the number of call sites is at most . + Tune the inlining of small generated C++ function. If a function's node + count multiplied by the number of calls is not bigger than , the + function will be inlined if possible. - This allows functions that are called only once or twice to be inlined - even if they exceed the small function threshold. Set to 0 to only inline - functions below the :vlopt:`--inline-cfuncs` threshold. The default is 200. + This allows functions that are called only once or twice to be inlined even + if they exceed the small function threshold. The default is 200. + + See also :vlopt:`--inline-cfuncs` and :vlopt:`-fno-inline-cfuncs`. .. option:: --inline-mult diff --git a/docs/guide/exe_verilator_coverage.rst b/docs/guide/exe_verilator_coverage.rst index b5f0598c2..388f67cfb 100644 --- a/docs/guide/exe_verilator_coverage.rst +++ b/docs/guide/exe_verilator_coverage.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 verilator_coverage ================== diff --git a/docs/guide/exe_verilator_gantt.rst b/docs/guide/exe_verilator_gantt.rst index 5b45969f9..b9944575c 100644 --- a/docs/guide/exe_verilator_gantt.rst +++ b/docs/guide/exe_verilator_gantt.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 verilator_gantt =============== diff --git a/docs/guide/exe_verilator_profcfunc.rst b/docs/guide/exe_verilator_profcfunc.rst index c79f86436..a29a5a34c 100644 --- a/docs/guide/exe_verilator_profcfunc.rst +++ b/docs/guide/exe_verilator_profcfunc.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 verilator_profcfunc =================== diff --git a/docs/guide/executables.rst b/docs/guide/executables.rst index ac4e3f85e..7d86091b5 100644 --- a/docs/guide/executables.rst +++ b/docs/guide/executables.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ********************************* Executable and Argument Reference diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index 0e944860e..8e8d5d183 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 =================== Language Extensions @@ -341,6 +342,10 @@ or "`ifdef`"'s may break other tools. .. option:: /*verilator&32;isolate_assignments*/ + Deprecated and has no effect (ignored). + + In versions before 5.050: + Used after a signal declaration to indicate the assignments to this signal in any blocks should be isolated into new blocks. When large combinatorial block results in a :option:`UNOPTFLAT` warning, attaching diff --git a/docs/guide/faq.rst b/docs/guide/faq.rst index ec55a9a9c..96245574c 100644 --- a/docs/guide/faq.rst +++ b/docs/guide/faq.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ****************************** FAQ/Frequently Asked Questions @@ -82,17 +83,17 @@ the licenses for details. Some examples: -* Any SystemVerilog or other input fed into Verilator remains your own. +- Any SystemVerilog or other input fed into Verilator remains your own. -* Any of your VPI/DPI C++ routines that Verilator calls remain your own. +- Any of your VPI/DPI C++ routines that Verilator calls remain your own. -* Any of your main() C++ code that calls into Verilator remains your own. +- Any of your main() C++ code that calls into Verilator remains your own. -* If you change Verilator itself, for example, changing or adding a file +- If you change Verilator itself, for example, changing or adding a file under the src/ directory in the repository, you must make the source code available under the GNU Lesser Public License. -* If you change a header Verilator provides, for example, under include/ in +- If you change a header Verilator provides, for example, under include/ in the repository, you must make the source code available under the GNU Lesser Public License. @@ -384,33 +385,33 @@ example of how to do this. How do I get faster build times? """""""""""""""""""""""""""""""" -* When running make, pass the make variable VM_PARALLEL_BUILDS=1, so that +- When running make, pass the make variable VM_PARALLEL_BUILDS=1, so that builds occur in parallel. Note this is now set by default if an output file is large enough to be split due to the :vlopt:`--output-split` option. -* Verilator emits any infrequently executed "cold" routines into separate +- Verilator emits any infrequently executed "cold" routines into separate __Slow.cpp files. This can accelerate compilation as optimization can be disabled on these routines. See the OPT_FAST and OPT_SLOW make variables and :ref:`Benchmarking & Optimization`. -* Use a recent compiler. Newer compilers tend to be faster. +- Use a recent compiler. Newer compilers tend to be faster. -* Compile in parallel on many machines and use caching; see the web for the +- Compile in parallel on many machines and use caching; see the web for the ccache, sccache, distcc, or icecream packages. ccache will skip GCC runs between identical source builds, even across different users. If ccache was installed when Verilator was built, it is used, or see OBJCACHE environment variable to override this. Also see the :vlopt:`--output-split` option and :ref: `Profiling ccache efficiency`. -* To reduce the compile time of classes that use a Verilated module (e.g., +- To reduce the compile time of classes that use a Verilated module (e.g., a top CPP file) you may wish to add a :option:`/*verilator&32;no_inline_module*/` metacomment to your top-level module. This will decrease the amount of code in the model's Verilated class, improving compile times of any instantiating top-level C++ code, at a relatively small cost of execution performance. -* Use :ref:`hierarchical verilation`. +- Use :ref:`hierarchical verilation`. Why do so many files need to recompile when I add a signal? @@ -498,12 +499,12 @@ equal, the best performance is when Verilator sees all of the design. So, look at the hierarchy of your design, labeling instances as to if they are SystemC or Verilog. Then: -* A module with only SystemC instances below must be SystemC. +- A module with only SystemC instances below must be SystemC. -* A module with a mix of Verilog and SystemC instances below must be +- A module with a mix of Verilog and SystemC instances below must be SystemC. (As Verilator cannot connect to lower-level SystemC instances.) -* A module with only Verilog instances below can be either, but for best +- A module with only Verilog instances below can be either, but for best performance should be Verilog. (The exception is if you have a design that is instantiated many times; in this case, Verilating one of the lower modules and instantiating that Verilated instances multiple times diff --git a/docs/guide/files.rst b/docs/guide/files.rst index 2e5fa2225..c2dfb5004 100644 --- a/docs/guide/files.rst +++ b/docs/guide/files.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ***** Files @@ -42,138 +43,141 @@ For --cc/--sc, it creates: .. list-table:: - * - *{prefix}*.json + - - *{prefix}*.json - JSON build definition compiling (from --make json) - * - *{prefix}*.mk + - - *{prefix}*.mk - Make include file for compiling (from --make gmake) - * - *{prefix}*\ _classes.mk + - - *{prefix}*\ _classes.mk - Make include file with class names (from --make gmake) - * - *{prefix}*.h + - - *{prefix}*.h - Model header - * - *{prefix}*.cpp + - - *{prefix}*.cpp - Model C++ file - * - *{prefix}*\ ___024root.h + - - *{prefix}*\ ___024root.h - Top-level internal header file (from SystemVerilog $root) - * - *{prefix}*\ ___024root.cpp + - - *{prefix}*\ ___024root.cpp - Top-level internal C++ file (from SystemVerilog $root) - * - *{prefix}*\ ___024root\ *{__n}*.cpp + - - *{prefix}*\ ___024root\ *{__n}*.cpp - Additional top-level internal C++ files - * - *{prefix}*\ ___024root__Slow\ *{__n}*.cpp + - - *{prefix}*\ ___024root__Slow\ *{__n}*.cpp - Infrequent cold routines - * - *{prefix}*\ ___024root__Trace\ *{__n}*.cpp + - - *{prefix}*\ ___024root__Trace\ *{__n}*.cpp - Wave file generation code (from --trace-\*) - * - *{prefix}*\ ___024root__Trace__Slow\ *{__n}*.cpp + - - *{prefix}*\ ___024root__Trace__Slow\ *{__n}*.cpp - Wave file generation code (from --trace-\*) - * - *{prefix}*\ __Dpi.h + - - *{prefix}*\ __Dpi.h - DPI import and export declarations (from --dpi) - * - *{prefix}*\ __Dpi.cpp + - - *{prefix}*\ __Dpi.cpp - Global DPI export wrappers (from --dpi) - * - *{prefix}*\ __Dpi_Export\ *{__n}*.cpp + - - *{prefix}*\ __Dpi_Export\ *{__n}*.cpp - DPI export wrappers scoped to this particular model (from --dpi) - * - *{prefix}*\ __Inlines.h + - - *{prefix}*\ __Inlines.h - Inline support functions - * - *{prefix}*\ __Syms.h + - - *{prefix}*\ __Syms.h - Global symbol table header - * - *{prefix}*\ __Syms.cpp + - - *{prefix}*\ __Syms.cpp - Global symbol table C++ - * - *{prefix}{each_verilog_module}*.h + - - *{prefix}{each_verilog_module}*.h - Lower level internal header files - * - *{prefix}{each_verilog_module}*.cpp + - - *{prefix}{each_verilog_module}*.cpp - Lower level internal C++ files - * - *{prefix}{each_verilog_module}{__n}*.cpp + - - *{prefix}{each_verilog_module}{__n}*.cpp - Additional lower C++ files For --hierarchical mode, it creates: .. list-table:: - * - V\ *{hier_block}*\ / + - - V\ *{hier_block}*/ - Directory to Verilate each hierarchical block (from --hierarchical) - * - *{prefix}*\ __hierVer.d + - - *{prefix}*\ __hierVer.d - Make dependencies of the top module (from --hierarchical) - * - *{prefix}*\ _hier.mk + - - *{prefix}*\ _hier.mk - Make file for hierarchical blocks (from --make gmake) - * - *{prefix}*\ __hierMkJsonArgs.f + - - *{prefix}*\ __hierMkJsonArgs.f - Arguments for hierarchical Verilation (from --make json) - * - *{prefix}*\ __hierMkArgs.f + - - *{prefix}*\ __hierMkArgs.f - Arguments for hierarchical Verilation (from --make gmake) - * - *{prefix}*\ __hierParameters.v + - - *{prefix}*\ __hierParameters.v - Module parameters for hierarchical blocks - * - *{prefix}*\ __hier.dir - - Directory to store .dot, .vpp, .tree of top module (from --hierarchical) + - - *{prefix}*\ __hier.dir + - Directory to store .dot, .vpp, .tree of top module (from + --hierarchical) In specific debug and other modes, it also creates: .. list-table:: - * - *{prefix}*.sarif + - - *{prefix}*.sarif - SARIF diagnostics (from --diagnostics-sarif) - * - *{prefix}*.tree.json + - - *{prefix}*.tree.json - JSON tree information (from --json-only) - * - *{prefix}*.tree.meta.json + - - *{prefix}*.tree.meta.json - JSON tree metadata (from --json-only) - * - *{prefix}*\ __cdc.txt + - - *{prefix}*\ __cdc.txt - Clock Domain Crossing checks (from --cdc) - * - *{prefix}*\ __stats.txt + - - *{prefix}*\ __stats.txt - Statistics (from --stats) - * - *{prefix}*\ __idmap.txt + - - *{prefix}*\ __idmap.txt - Symbol demangling (from --protect-ids) - * - *{prefix}*\ __ver.d + - - *{prefix}*\ __ver.d - Make dependencies (from -MMD) - * - *{prefix}*\ __verFiles.dat + - - *{prefix}*\ __verFiles.dat - Timestamps (from --skip-identical) - * - *{prefix}{misc}*.dot + - - *{prefix}{misc}*.dot - Debugging graph files (from --debug) - * - *{prefix}{misc}*.tree + - - *{prefix}{misc}*.tree - Debugging files (from --debug) - * - *{prefix}*\ __inputs.vpp + - - *{prefix}*\ __inputs.vpp - Pre-processed verilog for all files (from --debug) - * - *{prefix}*\ _ *{each_verilog_base_filename}*.vpp + - - *{prefix}*\ _ *{each_verilog_base_filename}*.vpp - Pre-processed verilog for each file (from --debug) After running Make, the C++ compiler may produce the following: .. list-table:: - * - verilated{misc}*.d + - - verilated{misc}*.d - Intermediate dependencies - * - verilated{misc}*.o + - - verilated{misc}*.o - Intermediate objects - * - {mod_prefix}{misc}*.d + - - {mod_prefix}{misc}*.d - Intermediate dependencies - * - {mod_prefix}{misc}*.o + - - {mod_prefix}{misc}*.o - Intermediate objects - * - *{prefix}*\ + - - *{prefix}*\ - Final executable (from --exe) - * - lib\ *{prefix}*.a + - - lib\ *{prefix}*.a - Final archive (default lib mode) - * - libverilated.a + - - libverilated.a - Runtime for verilated model (default lib mode) - * - *{prefix}*\ __ALL.a + - - *{prefix}*\ __ALL.a - Library of all Verilated objects - * - *{prefix}*\ __ALL.cpp + - - *{prefix}*\ __ALL.cpp - Include of all code for single compile - * - *{prefix}{misc}*.d + - - *{prefix}{misc}*.d - Intermediate dependencies - * - *{prefix}{misc}*.o + - - *{prefix}{misc}*.o - Intermediate objects The Verilated executable may produce the following: .. list-table:: - * - coverage.dat - - Code coverage output, and default input filename for :command:`verilator_coverage` - * - gmon.out - - GCC/clang code profiler output, often fed into :command:`verilator_profcfunc` - * - profile.vlt + - - coverage.dat + - Code coverage output, and default input filename for + :command:`verilator_coverage` + - - gmon.out + - GCC/clang code profiler output, often fed into + :command:`verilator_profcfunc` + - - profile.vlt - --prof-pgo data file for :ref:`Thread PGO` - * - profile_exec.dat + - - profile_exec.dat - --prof-exec data file for :command:`verilator_gantt` Verilator_gantt may produce the following: .. list-table:: - * - profile_exec.vcd + - - profile_exec.vcd - Gantt report waveform output diff --git a/docs/guide/index.rst b/docs/guide/index.rst index 5df45c271..8b7271352 100644 --- a/docs/guide/index.rst +++ b/docs/guide/index.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ###################### Verilator User's Guide diff --git a/docs/guide/install-cmake.rst b/docs/guide/install-cmake.rst index 03566ac01..6d840a552 100644 --- a/docs/guide/install-cmake.rst +++ b/docs/guide/install-cmake.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _cmakeinstallation: @@ -16,16 +17,16 @@ Linux). Quick Install ============= -1. Install Python for your platform from https://www.python.org/downloads/. -2. Install CMake for your platform from https://cmake.org/download/ or +#. Install Python for your platform from https://www.python.org/downloads/. +#. Install CMake for your platform from https://cmake.org/download/ or build it from source. -3. If the compiler of your choice is MSVC, then install +#. If the compiler of your choice is MSVC, then install https://visualstudio.microsoft.com/downloads/. If the compiler of your choice is Clang, then install https://releases.llvm.org/download.html or build it from source. -4. For flex and bison use https://github.com/lexxmark/winflexbison to build +#. For flex and bison use https://github.com/lexxmark/winflexbison to build and install. -5. For build on Windows using MSVC set environment variable WIN_FLEX_BISON +#. For build on Windows using MSVC set environment variable WIN_FLEX_BISON to install directory. For build on Windows/Linux/OS-X using ninja set the environment variable FLEX_INCLUDE to the directory containing FlexLexer.h and ensure that flex/bison is available within the PATH. diff --git a/docs/guide/install.rst b/docs/guide/install.rst index 09a58a94f..99d8f66a5 100644 --- a/docs/guide/install.rst +++ b/docs/guide/install.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _installation: @@ -384,12 +385,10 @@ the files: make install - .. Docker Build Environment .. include:: ../../ci/docker/buildenv/README.rst - .. Docker Run Environment .. include:: ../../ci/docker/run/README.rst diff --git a/docs/guide/languages.rst b/docs/guide/languages.rst index 04fb44ba4..b2bd17e69 100644 --- a/docs/guide/languages.rst +++ b/docs/guide/languages.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 *************** Input Languages @@ -102,11 +103,11 @@ Time With :vlopt:`--timing`, all timing controls are supported: -* delay statements, -* event control statements not only at the top of a process, -* intra-assignment timing controls, -* net delays, -* ``wait`` statements, +- delay statements, +- event control statements not only at the top of a process, +- intra-assignment timing controls, +- net delays, +- ``wait`` statements, as well as all flavors of ``fork``. @@ -128,26 +129,26 @@ simulation (perhaps using :vlopt:`--build`) and run it. With :vlopt:`--no-timing`, all timing controls cause the :option:`NOTIMING` error, except: -* delay statements - they are ignored (as they are in synthesis), though they - do issue a :option:`STMTDLY` warning, -* intra-assignment timing controls - they are ignored, though they do issue +- delay statements - they are ignored (as they are in synthesis), though + they do issue a :option:`STMTDLY` warning, +- intra-assignment timing controls - they are ignored, though they do issue an :option:`ASSIGNDLY` warning, -* net delays - they are ignored, -* event controls at the top of the procedure, +- net delays - they are ignored, +- event controls at the top of the procedure, Forks cause this error as well, except: -* forks with no statements, -* ``fork..join`` or ``fork..join_any`` with one statement, -* forks with :vlopt:`--bbox-unsup`. +- forks with no statements, +- ``fork..join`` or ``fork..join_any`` with one statement, +- forks with :vlopt:`--bbox-unsup`. If neither :vlopt:`--timing` nor :vlopt:`--no-timing` is specified, all timing controls cause the :option:`NEEDTIMINGOPT` error, except event controls at the top of the process. Forks cause this error as well, except: -* forks with no statements, -* ``fork..join`` or ``fork..join_any`` with one statement, -* forks with :vlopt:`--bbox-unsup`. +- forks with no statements, +- ``fork..join`` or ``fork..join_any`` with one statement, +- forks with :vlopt:`--bbox-unsup`. Timing controls and forks can also be ignored in specific files or parts of files. The :option:`/*verilator&32;timing_off*/` and diff --git a/docs/guide/overview.rst b/docs/guide/overview.rst index 5ffe52ed2..bf57423a0 100644 --- a/docs/guide/overview.rst +++ b/docs/guide/overview.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ******** Overview @@ -47,9 +48,9 @@ The best place to get started is to try the :ref:`Examples`. uses the shorthand, e.g., "IEEE 1364-2005", to refer to the, e.g., 2005 version of this standard. -.. [#] SystemVerilog is defined by the `Institute of Electrical and - Electronics Engineers (IEEE) Standard for SystemVerilog - Unified - Hardware Design, Specification, and Verification Language`, Standard - 1800, released in 2005, 2009, 2012, 2017, and 2023. The Verilator - documentation uses the shorthand e.g., "IEEE 1800-2023", to refer to - the, e.g., 2023 version of this standard. +.. [#] SystemVerilog is defined by the `Institute of Electrical and Electronics + Engineers (IEEE) Standard for SystemVerilog - Unified Hardware Design, + Specification, and Verification Language`, Standard 1800, released in + 2005, 2009, 2012, 2017, and 2023. The Verilator documentation uses the + shorthand e.g., "IEEE 1800-2023", to refer to the, e.g., 2023 version of + this standard. diff --git a/docs/guide/simulating.rst b/docs/guide/simulating.rst index 89baf9ccc..6ace6014d 100644 --- a/docs/guide/simulating.rst +++ b/docs/guide/simulating.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _simulating: @@ -209,7 +210,8 @@ For simple coverage points, use the ``cover property`` construct: DefaultClock: cover property (@(posedge clk) cyc==3); -This adds a coverage point that tracks whether the condition has been observed. +This adds a coverage point that tracks whether the condition has been +observed. .. _covergroup coverage: @@ -218,8 +220,8 @@ Covergroup Coverage With :vlopt:`--coverage` or :vlopt:`--coverage-user`, Verilator will translate covergroup coverage points the user has inserted manually in -SystemVerilog code into the Verilated model. Verilator supports -coverpoints with value and transition bins, and cross points. +SystemVerilog code into the Verilated model. Verilator supports coverpoints +with value and transition bins, and cross points. .. _fsm coverage: @@ -244,15 +246,15 @@ encodings in these common forms: - Single-process FSMs, whose state dispatch is written as ``case (state)`` or as a top-level ``if`` / ``else if`` chain comparing the same state variable against known state values -- Two-process and three-block FSMs, where a clocked state register is paired - with a combinational next-state block using the same supported +- Two-process and three-block FSMs, where a clocked state register is + paired with a combinational next-state block using the same supported ``case`` or top-level ``if`` / ``else if`` dispatch forms -Scalar state encodings may be wider than 32 bits. This allows sparse -state encodings, such as high-Hamming-distance enum or localparam values, -to be preserved in the detected FSM model. Verilator uses the declared -enum item name, parameter name, or localparam name as the reported state -label where possible. +Scalar state encodings may be wider than 32 bits. This allows sparse state +encodings, such as high-Hamming-distance enum or localparam values, to be +preserved in the detected FSM model. Verilator uses the declared enum item +name, parameter name, or localparam name as the reported state label where +possible. Simple input guards are supported when they appear inside a recognized state branch, or as a top-level conjunction containing exactly one state @@ -267,18 +269,17 @@ deeply nested control recovery, or cross-module state alias tracing. The following metacomments may be attached to the state variable to steer the extracted coverage model: -- ``/*verilator fsm_state*/`` forces the variable to be treated as - FSM state. -- ``/*verilator fsm_reset_arc*/`` marks reset transitions as - user-visible reset arcs instead of defaulting to a hidden reset-only - summary. -- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch - arcs that would otherwise be skipped by the conservative extractor. +- ``/*verilator fsm_state*/`` forces the variable to be treated as FSM + state. +- ``/*verilator fsm_reset_arc*/`` marks reset transitions as user-visible + reset arcs instead of defaulting to a hidden reset-only summary. +- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch arcs that + would otherwise be skipped by the conservative extractor. -State registers may also be wrapped by a transparent instance, for -example a project flop wrapper or primitive. Such wrappers must be -described explicitly with a VLT command file action before Verilator will -use their data, state, clock, or reset connections for FSM extraction: +State registers may also be wrapped by a transparent instance, for example +a project flop wrapper or primitive. Such wrappers must be described +explicitly with a VLT command file action before Verilator will use their +data, state, clock, or reset connections for FSM extraction: .. code-block:: sv @@ -298,11 +299,10 @@ Optional reset metadata may also be supplied: fsm_register_wrapper -module "my_fsm_flop" -d "state_i" -q "state_o" -clock "clk_i" \ -reset "rst_ni" -reset_value "ResetValue" -Reset arcs are emitted only when the configured reset port has an -inferable edge in the wrapper and the configured reset value parameter is -statically resolvable. If reset metadata is incomplete, Verilator warns -and may still emit FSM state and transition coverage, but reset arcs are -omitted. +Reset arcs are emitted only when the configured reset port has an inferable +edge in the wrapper and the configured reset value parameter is statically +resolvable. If reset metadata is incomplete, Verilator warns and may still +emit FSM state and transition coverage, but reset arcs are omitted. Reset transitions are included in the collected data either way. By default, :command:`verilator_coverage` summarizes reset-only arcs rather @@ -433,11 +433,11 @@ coverage point insertions into the model and collect the coverage data. To get the coverage data from the model, write the coverage with either: -1. Using :vlopt:`--binary` or :vlopt:`--main`, and Verilator will dump +#. Using :vlopt:`--binary` or :vlopt:`--main`, and Verilator will dump coverage when the test completes to the filename specified with :vlopt:`+verilator+coverage+file+\`. -2. In the user wrapper code, typically at the end once a test passes, call +#. In the user wrapper code, typically at the end once a test passes, call ``Verilated::threadContextp()->coveragep()->write`` with an argument of the filename for the coverage data file to write coverage data to (typically "logs/coverage.dat"). @@ -502,12 +502,12 @@ how execution time is distributed in a verilated model. With the :vlopt:`--prof-exec` option, Verilator will: -* Add code to the Verilated model to record execution flow. +- Add code to the Verilated model to record execution flow. -* Add code to save profiling data in non-human-friendly form to the file +- Add code to save profiling data in non-human-friendly form to the file specified with :vlopt:`+verilator+prof+exec+file+\`. -* In multithreaded models, add code to record each macro-task's start and +- In multithreaded models, add code to record each macro-task's start and end time across several calls to eval. (What is a macro-task? See the Verilator internals document (:file:`docs/internals.rst` in the distribution.) @@ -607,8 +607,8 @@ There are two forms of profile-guided optimizations. Unfortunately, for best results, they must each be performed from the highest level code to the lowest, which means performing them separately and in this order: -* :ref:`Thread PGO` -* :ref:`Compiler PGO` +- :ref:`Thread PGO` +- :ref:`Compiler PGO` Other forms of PGO may be supported in the future, such as clock and reset toggle rate PGO, branch prediction PGO, statement execution time PGO, or @@ -674,7 +674,7 @@ multithreaded models. Please see the appropriate compiler documentation to use PGO with GCC or Clang. The process in GCC 10 was as follows: -1. Compile the Verilated model with the compiler's "-fprofile-generate" +#. Compile the Verilated model with the compiler's "-fprofile-generate" flag: .. code-block:: bash @@ -685,10 +685,10 @@ Clang. The process in GCC 10 was as follows: Or, if calling make yourself, add -fprofile-generate appropriately to your Makefile. -2. Run your simulation. This will create \*.gcda file(s) in the same +#. Run your simulation. This will create \*.gcda file(s) in the same directory as the source files. -3. Recompile the model with -fprofile-use. The compiler will read the +#. Recompile the model with -fprofile-use. The compiler will read the \*.gcda file(s). For GCC: diff --git a/docs/guide/verilating.rst b/docs/guide/verilating.rst index 977d73c38..6a856248d 100644 --- a/docs/guide/verilating.rst +++ b/docs/guide/verilating.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ********** Verilating @@ -7,21 +8,21 @@ Verilating Verilator may be used in five major ways: -* With the :vlopt:`--binary` option, Verilator will translate the design +- With the :vlopt:`--binary` option, Verilator will translate the design into an executable, via generating C++ and compiling it. See :ref:`Binary, C++ and SystemC Generation`. -* With the :vlopt:`--cc` or :vlopt:`--sc` options, Verilator will translate +- With the :vlopt:`--cc` or :vlopt:`--sc` options, Verilator will translate the design into C++ or SystemC code, respectively. See :ref:`Binary, C++ and SystemC Generation`. -* With the :vlopt:`--lint-only` option, Verilator will lint the design to +- With the :vlopt:`--lint-only` option, Verilator will lint the design to check for warnings but will not typically create any output files. -* With the :vlopt:`--json-only` option, Verilator will create JSON output +- With the :vlopt:`--json-only` option, Verilator will create JSON output that may be used to feed into other user-designed tools. -* With the :vlopt:`-E` option, Verilator will preprocess the code according +- With the :vlopt:`-E` option, Verilator will preprocess the code according to IEEE preprocessing rules and write the output to standard out. This is useful to feed other tools and to debug how "\`define" statements are expanded. @@ -127,9 +128,9 @@ Usage Users need to mark one or more moderate-size modules as hierarchy block(s). There are two ways to mark a module: -* Write :option:`/*verilator&32;hier_block*/` metacomment in HDL code. +- Write :option:`/*verilator&32;hier_block*/` metacomment in HDL code. -* Add a :option:`hier_block` line in the :ref:`Verilator Control Files`. +- Add a :option:`hier_block` line in the :ref:`Verilator Control Files`. Then pass the :vlopt:`--hierarchical` option to Verilator. @@ -145,28 +146,28 @@ Limitations Hierarchy blocks have some limitations, including: -* Internals of the hierarchy block cannot be accessed using dot (.) from +- Internals of the hierarchy block cannot be accessed using dot (.) from the upper module(s) or other hierarchy blocks, except that ports of a hierarchy block instance can be accessed from the directly enclosing nested hierarchy block, or from the top level non-hierarchical portions of the design if not a nested hierarchy block. -* Modport cannot be used at the hierarchical block boundary. +- Modport cannot be used at the hierarchical block boundary. -* The simulation speed is likely not as fast as flat Verilation, in which +- The simulation speed is likely not as fast as flat Verilation, in which all modules are globally scheduled. -* Generated clocks may not work correctly if generated in the hierarchical +- Generated clocks may not work correctly if generated in the hierarchical model and passed into another hierarchical model or the top module. -* Delays are not allowed in hierarchy blocks. +- Delays are not allowed in hierarchy blocks. But, the following usage is supported: -* Nested hierarchy blocks. A hierarchy block may instantiate other +- Nested hierarchy blocks. A hierarchy block may instantiate other hierarchy blocks. -* Parameterized hierarchy block. Parameters of a hierarchy block can be +- Parameterized hierarchy block. Parameters of a hierarchy block can be overridden using ``#(.param_name(value))`` construct. diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index d1818afd5..ce3a0d58c 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 =================== Errors and Warnings @@ -882,6 +883,22 @@ List Of Warnings with a newline." +.. option:: FINALDLY + + Error issued when a non-blocking assignment `<=` is used in a + `final` block. + + This error can be disabled. If disabled, the assignment will be + executed as a `=` blocking assignment. + + Faulty example: + + .. include:: ../../docs/gen/ex_FINALDLY_faulty.rst + + Results in: + + .. include:: ../../docs/gen/ex_FINALDLY_msg.rst + .. option:: FSMMULTI Warns that the same always block contains multiple enum-typed case @@ -1178,7 +1195,7 @@ List Of Warnings .. option:: INITIALDLY - .. TODO better example + Historical, never issued since version 5.050. Warns that the code has a delayed assignment inside of an ``initial`` or ``final`` block. If this message is suppressed, Verilator will convert @@ -2366,11 +2383,6 @@ List Of Warnings the conflict. If you run with :vlopt:`--report-unoptflat`, Verilator will suggest possible candidates for :option:`/*verilator&32;split_var*/`. - The UNOPTFLAT warning may also occur where outputs from a block of logic - are independent, but occur in the same always block. To fix this, use - the :option:`/*verilator&32;isolate_assignments*/` metacomment described - above. - Before version 5.000, the UNOPTFLAT warning may also have been due to clock enables, identified from the reported path going through a clock gating instance. To fix these, the clock_enable meta comment was used. diff --git a/docs/internals.rst b/docs/internals.rst index 7b86beff6..fb3b5281d 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -113,9 +113,9 @@ pointer to the ``AstNode`` currently being processed. There are notable sub-hierarchies of the ``AstNode`` sub-types, namely: -1. All AST nodes representing data types derive from ``AstNodeDType``. +#. All AST nodes representing data types derive from ``AstNodeDType``. -2. All AST nodes representing expressions (i.e.: anything that stands for, +#. All AST nodes representing expressions (i.e.: anything that stands for, or evaluates to a value) derive from ``AstNodeExpr``. @@ -666,23 +666,23 @@ The second visitor in ``V3Timing.cpp``, ``TimingControlVisitor``, uses the information provided by ``TimingSuspendableVisitor`` and transforms each timing control into a ``co_await``. -* event controls are turned into ``co_await`` on a trigger scheduler's +- event controls are turned into ``co_await`` on a trigger scheduler's ``trigger`` method. The awaited trigger scheduler is the one corresponding to the sentree referenced by the event control. This sentree is also referenced by the ``AstCAwait`` node, to be used later by the static scheduling code. -* if an event control waits on a local variable or class member, it uses a +- if an event control waits on a local variable or class member, it uses a local trigger which it evaluates inline. It awaits a dynamic trigger scheduler multiple times: for trigger evaluation, updates, and resumption. The dynamic trigger scheduler is responsible for resuming the coroutine at the correct point of evaluation. -* delays are turned into ``co_await`` on a delay scheduler's ``delay`` +- delays are turned into ``co_await`` on a delay scheduler's ``delay`` method. The created ``AstCAwait`` nodes also reference a special sentree related to delays, to be used later by the static scheduling code. -* ``join`` and ``join_any`` are turned into ``co_await`` on a +- ``join`` and ``join_any`` are turned into ``co_await`` on a ``VlForkSync``'s ``join`` method. Each forked process gets a ``VlForkSync::done`` call at the end. @@ -732,24 +732,24 @@ event `a` was called first - which is necessary to know. There are two functions for managing timing logic called by ``_eval()``: -* ``_timing_ready()``, which commits all coroutines whose triggers were - not set in the current iteration, -* ``_timing_resume()``, which calls `resume()` on all trigger and delay +- ``_timing_ready()``, which commits all coroutines whose triggers were not + set in the current iteration, +- ``_timing_resume()``, which calls `resume()` on all trigger and delay schedulers whose triggers were set in the current iteration. Thanks to this separation a coroutine: -* awaiting a trigger cannot be suspended and resumed in the same iteration +- awaiting a trigger cannot be suspended and resumed in the same iteration (``test_regress/t/t_timing_eval_act.v``) - which is necessary to make Verilator more predictable; this is the reason for introduction of 3rd stage in `VlTriggerScheduler` and thanks to this it is guaranteed that downstream logic will be evaluated before resumption (assuming that the coroutine wasn't already triggered in previous iteration); -* cannot be resumed before it is suspended - +- cannot be resumed before it is suspended - ``test_regress/t/t_event_control_double_excessive.v``; -* firing cannot cannot be lost - (``test_regress/t/t_event_control_double_lost.v``) - which is possible when - triggers are not evaluated right before awaiting. +- firing cannot cannot be lost + (``test_regress/t/t_event_control_double_lost.v``) - which is possible + when triggers are not evaluated right before awaiting. All coroutines are committed and resumed in the 'act' eval loop. With timing features enabled, the ``_eval()`` function takes this form: @@ -937,14 +937,15 @@ macro-task's dataset fits in one core's local caches. To achieve spatial locality, we tag each variable with the set of macro-tasks that access it. Let's call this set the "footprint" of that -variable. The variables in a given module have a set of footprints. We group -variables with identical non-empty footprints, emit those groups in deterministic -footprint-key order, then emit variables with no footprint information last. +variable. The variables in a given module have a set of footprints. We +group variables with identical non-empty footprints, emit those groups in +deterministic footprint-key order, then emit variables with no footprint +information last. -The first emitted variable in each footprint group is aligned to a cache-line -boundary. This avoids false sharing between different macro-task footprints -without building a complete pairwise-distance graph over all footprints, which -would use excessive memory on very large models. +The first emitted variable in each footprint group is aligned to a +cache-line boundary. This avoids false sharing between different macro-task +footprints without building a complete pairwise-distance graph over all +footprints, which would use excessive memory on very large models. This is an old idea. Simulators designed at DEC in the early 1990s used similar techniques to optimize both single-thread and multithread modes. @@ -1299,15 +1300,15 @@ the ```` field is `` : ``, where ```` will be used as the base name of the generated operand accessors, and ```` is one of: -1. An ``AstNode`` sub-class, defining the operand to be of that type, +#. An ``AstNode`` sub-class, defining the operand to be of that type, always no-null, and with an always null ``nextp()``. That is, the child node is always present, and is a single ``AstNode`` (as opposed to a list). -2. ``Optional[]``. This is just like in point 1 above, +#. ``Optional[]``. This is just like in point 1 above, but defines the child node to be optional, meaning it may be null. -3. ``List[AstNode sub-class]`` describes a list operand, which means the +#. ``List[AstNode sub-class]`` describes a list operand, which means the child node may have a non-null ``nextp()`` and in addition the child itself may be null, representing an empty list. @@ -1394,7 +1395,7 @@ calling ``accept`` on ``AstIf`` will look in turn for: There are three ways data is passed between visitor functions. -1. A visitor-class member variable. This is generally for passing "parent" +#. A visitor-class member variable. This is generally for passing "parent" information down to children. ``m_modp`` is a common example. It's set to NULL in the constructor, where that node (``AstModule`` visitor) sets it, then the children are iterated, then it's cleared. Children under an @@ -1404,7 +1405,7 @@ There are three ways data is passed between visitor functions. visitor; otherwise exiting the lower for will lose the upper for's setting. -2. User attributes. Each ``AstNode`` (**Note.** The AST node, not the +#. User attributes. Each ``AstNode`` (**Note.** The AST node, not the visitor) has five user attributes, which may be accessed as an integer using the ``user1()`` through ``user4()`` methods, or as a pointer (of type ``AstNUser``) using the ``user1p()`` through ``user4p()`` methods @@ -1435,7 +1436,7 @@ There are three ways data is passed between visitor functions. so it's ok to call fairly often. For example, it's commonly called on every module. -3. Parameters can be passed between the visitors in close to the "normal" +#. Parameters can be passed between the visitors in close to the "normal" function caller to callee way. This is the second ``vup`` parameter of type ``AstNUser`` that is ignored on most of the visitor functions. V3Width does this, but it proved messier than the above and is @@ -2125,19 +2126,19 @@ To print a node: ``src/.gdbinit`` and ``src/.gdbinit.py`` define handy utilities for working with JSON AST dumps. For example: -* ``jstash nodep`` - Perform a JSON AST dump and save it into GDB value +- ``jstash nodep`` - Perform a JSON AST dump and save it into GDB value history (e.g. ``$1``) -* ``jtree nodep`` - Perform a JSON AST dump and pretty print it using +- ``jtree nodep`` - Perform a JSON AST dump and pretty print it using ``astsee_verilator``. -* ``jtree $1`` - Pretty print a dump that was previously saved by +- ``jtree $1`` - Pretty print a dump that was previously saved by ``jstash``. -* ``jtree nodep -d '.file, .timeunit'`` - Perform a JSON AST dump, filter +- ``jtree nodep -d '.file, .timeunit'`` - Perform a JSON AST dump, filter out some fields and pretty print it. -* ``jtree 0x55555613dca0`` - Pretty print using address literal (rather +- ``jtree 0x55555613dca0`` - Pretty print using address literal (rather than actual pointer). -* ``jtree $1 nodep`` - Diff ``nodep`` against an older dump. +- ``jtree $1 nodep`` - Diff ``nodep`` against an older dump. A detailed description of ``jstash`` and ``jtree`` can be displayed using ``gdb``'s ``help`` command. @@ -2243,25 +2244,25 @@ Adding a New Feature Generally, what would you do to add a new feature? -1. File an issue (if there isn't already) so others know what you're +#. File an issue (if there isn't already) so others know what you're working on. -2. Make a testcase in the test_regress/t/t_EXAMPLE format, see `Testing`. +#. Make a testcase in the test_regress/t/t_EXAMPLE format, see `Testing`. -3. If grammar changes are needed, look at the IEEE 1800-2023 Appendix A, as +#. If grammar changes are needed, look at the IEEE 1800-2023 Appendix A, as src/verilog.y generally follows the same rule layout. -4. If a new Ast type is needed, add it to the appropriate V3AstNode*.h. +#. If a new Ast type is needed, add it to the appropriate V3AstNode*.h. Follow the convention described above about the AstNode type hierarchy. Ordering of definitions is enforced by ``astgen``. -5. Now you can run ``test_regress/t/t_.py --debug`` and it'll +#. Now you can run ``test_regress/t/t_.py --debug`` and it'll probably fail, but you'll see a ``test_regress/obj_dir/t_/*.tree`` file which you can examine to see if the parsing worked. See also the sections above on debugging. -6. Modify the later visitor functions to process the new feature as needed. +#. Modify the later visitor functions to process the new feature as needed. Adding a New Pass diff --git a/docs/security.rst b/docs/security.rst index 4080bad92..3adb6c8d9 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -1,6 +1,7 @@ -.. for github, vim: syntax=reStructuredText -.. SPDX-FileCopyrightText: 2025-2026 Wilson Snyder -.. SPDX-License-Identifier: CC0-1.0 +.. + for github, vim: syntax=reStructuredText + SPDX-FileCopyrightText: 2025-2026 Wilson Snyder + SPDX-License-Identifier: CC0-1.0 Security Policy =============== diff --git a/docs/spelling.txt b/docs/spelling.txt index a3565cb1d..f99491317 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -477,6 +477,7 @@ Syms Synopsys SystemC SystemVerilog +Szpotanski Takatsukasa Tambe Tarik @@ -537,6 +538,7 @@ Verilog Vighnesh Viktor Vilp +VlRNG VlWide Vlip Vm @@ -684,6 +686,7 @@ countones cout covergroup covergroups +coverpoint coverpoints cpp cppstyle @@ -861,6 +864,7 @@ hx hyperthreading hyperthreads icecream +ico idmap ifdef ifdefed @@ -916,6 +920,7 @@ linters linux liu livelock +liveness lldb ln loc @@ -924,6 +929,7 @@ localparams localtime logicals longint +lookups lossy lsb lubc @@ -943,6 +949,7 @@ misconnected misconversion misdetecting misoptimized +misoptimizing missized mk mno @@ -1204,6 +1211,7 @@ typename uint un unbased +unclocked uncomment undef undefineall diff --git a/include/AGENTS.md b/include/AGENTS.md new file mode 100644 index 000000000..0ac1fcbfe --- /dev/null +++ b/include/AGENTS.md @@ -0,0 +1,60 @@ + + +# include/ -- Verilator runtime library + +Covers the C++ runtime under `include/` (`verilated.h/.cpp`, `verilated_*.h/.cpp`) +that every generated model links against. Read the repository-root +[AGENTS.md](../AGENTS.md) first. The rules here differ from `src/`: this code +ships to users, runs every simulation cycle, and must stay portable and fast. + +______________________________________________________________________ + +# Orientation + +- **This is the runtime, not the compiler.** The passes in `src/` *emit* C++ that + calls into this library; this code then *runs* during simulation. Optimize it + for execution speed and portability, not for compile-time clarity. +- **Key files:** `verilated.h` (core model API), `verilated_types.h` (data + types), `verilated_random.cpp` (constrained-random runtime), `verilated_cov.*` + (coverage), `verilated_threads.*` (MT runtime), `verilated_timing.*` + (`--timing` runtime), `verilated_vcd_c.*`/`verilated_fst_c.*` (tracing). +- A runtime-only fix lives entirely here and does not rebuild `verilator_bin`. + +______________________________________________________________________ + +# Before you open a PR + +- **C++14 baseline.** The runtime must build under `--no-timing` with C++14; C++20 + features are allowed only in `--timing` code paths. +- **Public API naming and docs.** Prefix public classes and types with + `Verilated`/`Vl` to avoid collisions with user code, and document the API with + `///` comments -- they feed doc generation. +- **No exceptions in runtime code** -- use error returns or assertions; exceptions + add overhead on every path. +- **Use fixed-width model types** (`CData`/`SData`/`IData`/`QData`/`VlWide`), never + `size_t`, for model data. Process wide data word-by-word (`VL_ZERO_W`, + `VL_MEMCPY_W`), never bit-by-bit or byte-by-byte. +- **Do all string parsing at verilation time** -- never parse strings during + simulation; emit structured data or compile-time hints instead. +- **Keep per-cycle code lean.** Do not add vtables to high-frequency objects + (8 bytes per instance); guard optional features behind + `hasClasses()`/`hasEvents()`-style checks; functions called per cycle must avoid + mutexes -- use atomics or lockless designs. +- **Emit no runtime loops** the compiler could have expanded at verilation time; + prefer a single runtime call. +- **Thread safety.** Annotate with the hierarchy `VL_PURE` > `VL_MT_SAFE` > + `VL_MT_STABLE`; annotations must match the implementation. Annotate + mutex-protected members with `VL_GUARDED_BY` and document acquisition ordering. + Prefer has-a over is-a: a guarded class wrapping the unguarded internal one, with + the guarded version as the default public API. +- **Keep runtime and compiler headers separate** -- never include `verilated.h` + into the compiler in `src/`. + +## File-specific rules + +| File | Rule | +|---|---| +| `verilated_random.cpp` | Emit only portable SMT-LIB 2.6 -- no solver-specific or MaxSMT extensions; the generated solver text is the model's runtime constraint interface | +| `verilated_cov.cpp` | Coverage runtime is shared by all models -- keep per-point overhead minimal and the on-disk format stable for `verilator_coverage` | diff --git a/include/fstcpp/fstcpp_assertion.h b/include/fstcpp/fstcpp_assertion.h index e9606f665..8a4d549ad 100644 --- a/include/fstcpp/fstcpp_assertion.h +++ b/include/fstcpp/fstcpp_assertion.h @@ -81,6 +81,12 @@ std::abort(); \ } +#define FST_FAIL_STRING(s) \ + do { \ + std::cerr << (s) << std::endl; \ + std::abort(); \ + } while (0) + // We turn on all DCHECKs to CHECKs temporarily for better safety. #if 1 # define FST_DCHECK(a) FST_CHECK(a) diff --git a/include/fstcpp/fstcpp_variable_info.h b/include/fstcpp/fstcpp_variable_info.h index 470bb8ef1..0074705b1 100644 --- a/include/fstcpp/fstcpp_variable_info.h +++ b/include/fstcpp/fstcpp_variable_info.h @@ -335,10 +335,10 @@ public: // Double variables should not use these array-based emitValueChange overloads. // We implement them to satisfy the VairableInfo::dispatchHelper template instantiation. void emitValueChange(uint64_t, const uint32_t *, EncodingType) { - throw std::runtime_error("emitValueChange(uint32_t*) not supported for Double"); + FST_FAIL_STRING("emitValueChange(uint32_t*) not supported for Double"); } void emitValueChange(uint64_t, const uint64_t *, EncodingType) { - throw std::runtime_error("emitValueChange(uint64_t*) not supported for Double"); + FST_FAIL_STRING("emitValueChange(uint64_t*) not supported for Double"); } void dumpInitialBits(std::vector &buf) const { diff --git a/include/fstcpp/fstcpp_writer.cpp b/include/fstcpp/fstcpp_writer.cpp index 2756e23b9..386a76162 100644 --- a/include/fstcpp/fstcpp_writer.cpp +++ b/include/fstcpp/fstcpp_writer.cpp @@ -193,7 +193,7 @@ Handle Writer::createVar( // (void)type; // (void)svt; // (void)sdt; -// throw std::runtime_error("TODO"); +// FST_FAIL_STRING("TODO"); // return 0; // } // LCOV_EXCL_STOP @@ -353,11 +353,7 @@ void compressUsingZlib( uncompressed_size, level ); - if (z_status != Z_OK) { - throw std::runtime_error( - "Failed to compress data with zlib, error code: " + std::to_string(z_status) - ); - } + FST_CHECK_EQ(z_status, Z_OK); compressed_data.resize(compressed_bound); } diff --git a/include/verilated.cpp b/include/verilated.cpp index 6bf868cad..d981f7c0a 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -89,6 +89,12 @@ # include # define _VL_HAVE_GETRLIMIT #endif +#if VM_VPI +# include +# ifndef _WIN32 +# include // dlopen +# endif +#endif #include "verilated_threads.h" // clang-format on @@ -260,6 +266,58 @@ void VL_WARN_MT(const char* filename, int linenum, const char* hier, const char* }}); } +//=========================================================================== +// Runtime VPI shared library loading (--vpi) + +// Load one VPI shared library named by a +verilator+vpi+[:] argument. +// 'arg' is the payload after the prefix: either "" (invoke the library's +// vlog_startup_routines array) or ":" (invoke the named bootstrap). +void Verilated::loadVpiLib(const std::string& arg) VL_MT_UNSAFE { +#if VM_VPI + if (arg.empty()) return; +#ifdef _WIN32 + VL_FATAL_MT("", 0, "", + "+verilator+vpi+: runtime VPI library loading is not supported on" + " Windows; link the VPI code into the model instead"); +#else + using vlog_startup_t = void (*)(); + // Split : on the last ':' + const std::string::size_type colon_pos = arg.rfind(':'); + const bool has_entry = (colon_pos != std::string::npos); + const std::string libpath = has_entry ? arg.substr(0, colon_pos) : arg; + const std::string entry_name = has_entry ? arg.substr(colon_pos + 1) : std::string{}; + void* handle = dlopen(libpath.c_str(), RTLD_LAZY); + if (!handle) + // The library path is stable; the dlerror() text is platform-specific, so put it on + // a separate "- " line (test golden files strip "- " lines, keeping output portable). + VL_FATAL_MT( + "", 0, "", + (std::string{"Cannot load VPI library: "} + libpath + "\n- dlerror: " + dlerror()) + .c_str()); + if (has_entry) { + vlog_startup_t bsp = reinterpret_cast(dlsym(handle, entry_name.c_str())); + if (!bsp) + VL_FATAL_MT( + "", 0, "", + (std::string{"Cannot find VPI bootstrap '"} + entry_name + "' in: " + libpath) + .c_str()); + bsp(); + } else { + vlog_startup_t* routinesp + = reinterpret_cast(dlsym(handle, "vlog_startup_routines")); + if (!routinesp) + VL_FATAL_MT( + "", 0, "", + (std::string{"Cannot find 'vlog_startup_routines' in: "} + libpath).c_str()); + for (int j = 0; routinesp[j]; ++j) routinesp[j](); + } +#endif +#else + // Never reached: the command-line handler only calls this when compiled with --vpi. + (void)arg; +#endif +} + //=========================================================================== // Debug prints @@ -318,6 +376,12 @@ void VL_PRINTF_MT(const char* formatp, ...) VL_MT_SAFE { }}); } +void VL_FFLUSH_MT() VL_MT_SAFE { + VerilatedThreadMsgQueue::post(VerilatedMsg{[=]() { // + Verilated::runFlushCallbacks(); + }}); +} + template static size_t _vl_snprintf_string(std::string& str, const char* format, snprintf_args_ts... args) VL_MT_SAFE { @@ -499,9 +563,10 @@ IData VL_URANDOM_SEEDED_II(IData seed) VL_MT_SAFE { } IData VL_SCOPED_RAND_RESET_I(int obits, uint64_t scopeHash, uint64_t salt) VL_MT_UNSAFE { - if (Verilated::threadContextp()->randReset() == 0) return 0; + const int randReset = Verilated::threadContextp()->randReset(); + if (randReset == 0) return 0; IData data = ~0; - if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize + if (randReset != 1) { // if 2, randomize VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt}; data = rng.rand64(); } @@ -510,9 +575,10 @@ IData VL_SCOPED_RAND_RESET_I(int obits, uint64_t scopeHash, uint64_t salt) VL_MT } QData VL_SCOPED_RAND_RESET_Q(int obits, uint64_t scopeHash, uint64_t salt) VL_MT_UNSAFE { - if (Verilated::threadContextp()->randReset() == 0) return 0; + const int randReset = Verilated::threadContextp()->randReset(); + if (randReset == 0) return 0; QData data = ~0ULL; - if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize + if (randReset != 1) { // if 2, randomize VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt}; data = rng.rand64(); } @@ -522,10 +588,17 @@ QData VL_SCOPED_RAND_RESET_Q(int obits, uint64_t scopeHash, uint64_t salt) VL_MT WDataOutP VL_SCOPED_RAND_RESET_W(int obits, WDataOutP outwp, uint64_t scopeHash, uint64_t salt) VL_MT_UNSAFE { - if (Verilated::threadContextp()->randReset() != 2) { return VL_RAND_RESET_W(obits, outwp); } - VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt}; - for (int i = 0; i < VL_WORDS_I(obits) - 1; ++i) outwp[i] = rng.rand64(); - outwp[VL_WORDS_I(obits) - 1] = rng.rand64() & VL_MASK_E(obits); + const int words = VL_WORDS_I(obits); + const int randReset = Verilated::threadContextp()->randReset(); + if (randReset == 0) { + VL_MEMSET_ZERO_W(outwp, words); + } else if (randReset == 1) { + VL_MEMSET_ONES_W(outwp, words); + } else { + VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt}; + for (int i = 0; i < words; ++i) outwp[i] = rng.rand64(); + } + outwp[words - 1] &= VL_MASK_E(obits); return outwp; } @@ -550,30 +623,14 @@ WDataOutP VL_SCOPED_RAND_RESET_ASSIGN_W(int obits, WDataOutP outwp, uint64_t sco } IData VL_RAND_RESET_I(int obits) VL_MT_SAFE { - if (Verilated::threadContextp()->randReset() == 0) return 0; + const int randReset = Verilated::threadContextp()->randReset(); + if (randReset == 0) return 0; IData data = ~0; - if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize - data = VL_RANDOM_I(); - } + if (randReset != 1) data = VL_RANDOM_I(); // if 2, randomize data &= VL_MASK_I(obits); return data; } -QData VL_RAND_RESET_Q(int obits) VL_MT_SAFE { - if (Verilated::threadContextp()->randReset() == 0) return 0; - QData data = ~0ULL; - if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize - data = VL_RANDOM_Q(); - } - data &= VL_MASK_Q(obits); - return data; -} - -WDataOutP VL_RAND_RESET_W(int obits, WDataOutP outwp) VL_MT_SAFE { - for (int i = 0; i < VL_WORDS_I(obits) - 1; ++i) outwp[i] = VL_RAND_RESET_I(32); - outwp[VL_WORDS_I(obits) - 1] = VL_RAND_RESET_I(32) & VL_MASK_E(obits); - return outwp; -} WDataOutP VL_ZERO_RESET_W(int obits, WDataOutP outwp) VL_MT_SAFE { // Not inlined to speed up compilation of slowpath code return VL_ZERO_W(obits, outwp); @@ -3028,40 +3085,97 @@ void VerilatedContext::assertOn(bool flag) VL_MT_SAFE { } bool VerilatedContext::assertOnGet(VerilatedAssertType_t type, VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE { - // Check if selected directive type bit in the assertOn is enabled for assertion type. - // Note: it is assumed that this is checked only for one type at the time. - - // Flag unspecified assertion types as disabled. - if (type == 0) return false; - - // Get index of 3-bit group guarding assertion type status. - // Since the assertOnGet is generated __always__ for a single assert type, we assume that only - // a single bit will be set. Thus, ceil log2 will work fine. - VL_DEBUG_IFDEF(assert((type & (type - 1)) == 0);); - const IData typeMaskPosition = VL_CLOG2_I(type); - - // Check if directive type bit is enabled in corresponding assertion type bits. - return m_s.m_assertOn & (directive << (typeMaskPosition * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH)); + return assertCtlGet(VerilatedAssertCtlQuery::ASSERT_CTL_ON, type, directive); +} +uint32_t VerilatedContext::assertOnMask(VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_PURE { + // Place the directive bits at each selected assertion type's 3-bit group. + uint32_t mask = 0; + for (int i = 0; i < std::numeric_limits::digits; ++i) { + if (VL_BITISSET_I(types, i)) mask |= directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH); + } + return mask; } void VerilatedContext::assertOnSet(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { - // For each assertion type, set directive bits. - - // Iterate through all positions of assertion type bits. If bit for this assertion type is set, - // set directive type bits mask at this group index. - for (int i = 0; i < std::numeric_limits::digits; ++i) { - if (VL_BITISSET_I(types, i)) - m_s.m_assertOn |= directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH); - } + m_s.m_assertOn |= assertOnMask(types, directives); } void VerilatedContext::assertOnClear(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { - // Iterate through all positions of assertion type bits. If bit for this assertion type is set, - // clear directive type bits mask at this group index. - for (int i = 0; i < std::numeric_limits::digits; ++i) { - if (VL_BITISSET_I(types, i)) - m_s.m_assertOn &= ~(directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH)); + m_s.m_assertOn &= ~assertOnMask(types, directives); +} +void VerilatedContext::assertCtl(uint32_t controlType, VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { + // IEEE 1800-2023 Table 20-5 control_type. Lock freezes the On/Off state of the + // selected bits until Unlock; On/Off/Kill leave locked bits unchanged. + const uint32_t mask = assertOnMask(types, directives); + const uint32_t lockedMask = mask & ~m_s.m_assertLock; + switch (controlType) { + case 1: // Lock + m_s.m_assertLock |= mask; + break; + case 2: // Unlock + m_s.m_assertLock &= ~mask; + break; + case 3: // On + m_s.m_assertOn |= lockedMask; + break; + case 4: // Off + m_s.m_assertOn &= ~lockedMask; + break; + case 5: { // Kill + m_s.m_assertOn &= ~lockedMask; + for (int slot = 0; slot < static_cast(ASSERT_CONTROL_SLOT_COUNT); ++slot) { + if (VL_BITISSET_I(lockedMask, slot)) { m_s.m_assertKill[slot]++; } + } + break; } + case 6: // PassOn + m_s.m_assertPassOnVacuous |= lockedMask; + m_s.m_assertPassOnNonvacuous |= lockedMask; + break; + case 7: // PassOff + m_s.m_assertPassOnVacuous &= ~lockedMask; + m_s.m_assertPassOnNonvacuous &= ~lockedMask; + break; + case 8: // FailOn + m_s.m_assertFailOn |= lockedMask; + break; + case 9: // FailOff + m_s.m_assertFailOn &= ~lockedMask; + break; + case 10: // NonvacuousOn + m_s.m_assertPassOnNonvacuous |= lockedMask; + break; + case 11: // VacuousOff + m_s.m_assertPassOnVacuous &= ~lockedMask; + break; + default: + VL_WARN_MT("", 0, "", + ("Bad $assertcontrol control_type '" + std::to_string(controlType) + + "' (IEEE 1800-2023 Table 20-5)") + .c_str()); + } +} +uint32_t +VerilatedContext::assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type, + VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE { + const uint32_t mask = assertOnMask(type, directive); + if (!mask) return 0; + switch (query) { // LCOV_EXCL_BR_LINE + case VerilatedAssertCtlQuery::ASSERT_CTL_ON: return (m_s.m_assertOn & mask) != 0; + case VerilatedAssertCtlQuery::ASSERT_CTL_KILL: + assert(mask && (mask & (mask - 1)) == 0); + return m_s.m_assertKill[VL_CLOG2_I(mask)]; + case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_VACUOUS: + return (m_s.m_assertPassOnVacuous & mask) != 0; + case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_NONVACUOUS: + return (m_s.m_assertPassOnNonvacuous & mask) != 0; + case VerilatedAssertCtlQuery::ASSERT_CTL_FAIL_ON: return (m_s.m_assertFailOn & mask) != 0; + default: // LCOV_EXCL_START + VL_FATAL_MT("", 0, "", "Internal: Bad assertCtlGet query"); + VL_UNREACHABLE; + } // LCOV_EXCL_STOP } void VerilatedContext::calcUnusedSigs(bool flag) VL_MT_SAFE { const VerilatedLockGuard lock{m_mutex}; @@ -3480,6 +3594,17 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) { // and the run can be reproduced by passing +verilator+seed+. if (u64 == 0) u64 = pickRandomSeed(); randSeed(static_cast(u64)); + } else if (commandArgVlString(arg, "+verilator+vpi+", str)) { + // With --vpi, load the requested shared library now. Without --vpi there is + // no VPI runtime, so warn the argument is ignored. +#if VM_VPI + Verilated::loadVpiLib(str); +#else + VL_WARN_MT( + "COMMAND_LINE", 0, "", + ("+verilator+vpi+ ignored: simulation was not compiled with --vpi '" + arg + "'") + .c_str()); // LCOV_EXCL_LINE (gcov zeroes this wrapped continuation line) +#endif } else if (arg == "+verilator+V") { VerilatedImp::versionDump(); // Someday more info too VL_FATAL_MT("COMMAND_LINE", 0, "", @@ -3893,6 +4018,7 @@ std::unique_ptr VerilatedModel::traceConfig() const { retu // cppcheck-suppress unusedFunction // Used by applications uint32_t VerilatedVarProps::entSize() const VL_MT_SAFE { + if (m_entSize) return m_entSize; uint32_t size = 1; switch (vltype()) { case VLVT_PTR: size = sizeof(void*); break; @@ -4009,6 +4135,27 @@ VerilatedVar* VerilatedScope::varInsert(const char* namep, void* datap, bool isP return &(m_varsp->find(namep)->second); } +VerilatedVar* VerilatedScope::varInsertSized(const char* namep, void* datap, bool isParam, + VerilatedVarType vltype, int vlflags, int udims, + uint32_t entSize...) VL_MT_UNSAFE { + if (!m_varsp) m_varsp = new VerilatedVarNameMap; + VerilatedVar var(namep, datap, vltype, static_cast(vlflags), udims, 0, + isParam, entSize); + + va_list ap; + va_start(ap, entSize); + for (int i = 0; i < udims; ++i) { + const int msb = va_arg(ap, int); + const int lsb = va_arg(ap, int); + var.m_unpacked[i].m_left = msb; + var.m_unpacked[i].m_right = lsb; + } + va_end(ap); + + m_varsp->emplace(namep, std::move(var)); + return &(m_varsp->find(namep)->second); +} + VerilatedVar* VerilatedScope::forceableVarInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, void* forceReadSignalData, diff --git a/include/verilated.h b/include/verilated.h index 707ede5ce..73c7e5b2a 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -137,7 +137,9 @@ enum VerilatedVarType : uint8_t { VLVT_UINT64, // AKA QData VLVT_WDATA, // AKA VlWide VLVT_STRING, // C++ string - VLVT_REAL // AKA double + VLVT_REAL, // AKA double + VLVT_STRUCT, // SystemVerilog unpacked struct + VLVT_UNION // SystemVerilog unpacked union }; enum VerilatedVarFlags : uint32_t { @@ -154,7 +156,8 @@ enum VerilatedVarFlags : uint32_t { VLVF_CONTINUOUSLY = (1 << 11), // Is continously assigned VLVF_FORCEABLE = (1 << 12), // Forceable VLVF_SIGNED = (1 << 13), // Signed integer - VLVF_BITVAR = (1 << 14) // Four state bit (vs two state logic) + VLVF_BITVAR = (1 << 14), // Four state bit (vs two state logic) + VLVF_NET = (1 << 15) // Net object }; // IEEE 1800-2023 Table 20-6 @@ -175,6 +178,15 @@ enum class VerilatedAssertDirectiveType : uint8_t { DIRECTIVE_TYPE_COVER = (1 << 1), DIRECTIVE_TYPE_ASSUME = (1 << 2), }; + +/// Runtime query selector for assertion-control state +enum class VerilatedAssertCtlQuery : uint8_t { + ASSERT_CTL_ON, + ASSERT_CTL_KILL, + ASSERT_CTL_PASS_ON_VACUOUS, + ASSERT_CTL_PASS_ON_NONVACUOUS, + ASSERT_CTL_FAIL_ON, +}; using VerilatedAssertType_t = std::underlying_type::type; using VerilatedAssertDirectiveType_t = std::underlying_type::type; @@ -356,6 +368,10 @@ private: static constexpr size_t ASSERT_ON_WIDTH = ASSERT_DIRECTIVE_TYPE_MASK_WIDTH * std::numeric_limits::digits + 1; + // Build the assertion-control bit mask for the given assertion x directive types. + static uint32_t assertOnMask(VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_PURE; + static constexpr size_t ASSERT_CONTROL_SLOT_COUNT = ASSERT_ON_WIDTH - 1; protected: // TYPES @@ -375,6 +391,16 @@ protected: // for each VerilatedAssertType we store // 3-bits, one for each directive type. Last // bit guards internal directive types. + std::atomic m_assertLock{0}; // Locked assertion bits (IEEE 1800-2023 20.11 + // Lock/Unlock); same layout as m_assertOn. While + // a bit is locked, On/Off/Kill leave it unchanged. + std::atomic m_assertPassOnVacuous{ + std::numeric_limits::max()}; // Enabled vacuous pass actions + std::atomic m_assertPassOnNonvacuous{ + std::numeric_limits::max()}; // Enabled nonvacuous pass actions + std::atomic m_assertFailOn{ + std::numeric_limits::max()}; // Enabled fail actions + std::array, ASSERT_CONTROL_SLOT_COUNT> m_assertKill{}; bool m_calcUnusedSigs = false; // Waves file on, need all signals calculated bool m_fatalOnError = true; // Fatal on $stop/non-fatal error bool m_fatalOnVpiError = true; // Fatal on vpi error/unsupported @@ -484,6 +510,13 @@ public: /// Clear enabled status for given assertion types void assertOnClear(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE; + /// Apply assertion control for given control, assertion, and directive types + void assertCtl(uint32_t controlType, VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_MT_SAFE; + /// Get assertion-control runtime state. Boolean queries return 0/1, Kill returns + /// the generation count. + uint32_t assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type, + VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE; /// Return if calculating of unused signals (for traces) bool calcUnusedSigs() const VL_MT_SAFE { return m_s.m_calcUnusedSigs; } /// Enable calculation of unused signals (for traces) @@ -751,6 +784,9 @@ public: // But internals only - called from verilated modules, VerilatedSyms void exportInsert(int finalize, const char* namep, void* cb) VL_MT_UNSAFE; VerilatedVar* varInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, int udims, int pdims, ...) VL_MT_UNSAFE; + VerilatedVar* varInsertSized(const char* namep, void* datap, bool isParam, + VerilatedVarType vltype, int vlflags, int udims, uint32_t entSize, + ...) VL_MT_UNSAFE; VerilatedVar* forceableVarInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, void* forceReadSignalData, const char* forceReadSignalName, @@ -1000,6 +1036,9 @@ public: static void scTraceBeforeElaborationError() VL_ATTR_NORETURN VL_MT_SAFE; static void stackCheck(QData needSize) VL_MT_UNSAFE; + // Internal: Load a VPI shared library (+verilator+vpi+[:]) + static void loadVpiLib(const std::string& arg) VL_MT_UNSAFE; + // Internal: Get and set DPI context static const VerilatedScope* dpiScope() VL_MT_SAFE { return t_s.t_dpiScopep; } static void dpiScope(const VerilatedScope* scopep) VL_MT_SAFE { t_s.t_dpiScopep = scopep; } diff --git a/include/verilated.mk.in b/include/verilated.mk.in index 9d3f3b2e7..a90907755 100644 --- a/include/verilated.mk.in +++ b/include/verilated.mk.in @@ -52,6 +52,9 @@ CFG_GCH_IF_CLANG = @CFG_GCH_IF_CLANG@ CFG_LDFLAGS_VERILATED = @CFG_LDFLAGS_VERILATED@ # Linker libraries for multithreading CFG_LDLIBS_THREADS = @CFG_LDLIBS_THREADS@ +# Linker flags/libraries for runtime VPI library loading (+verilator+vpi+) +CFG_LDFLAGS_DYNAMIC = @CFG_LDFLAGS_DYNAMIC@ +CFG_LDLIBS_DYNAMIC = @CFG_LDLIBS_DYNAMIC@ ###################################################################### # Programs @@ -93,6 +96,7 @@ VK_CPPFLAGS_ALWAYS += \ -DVM_TRACE_FST=$(VM_TRACE_FST) \ -DVM_TRACE_VCD=$(VM_TRACE_VCD) \ -DVM_TRACE_SAIF=$(VM_TRACE_SAIF) \ + -DVM_VPI=$(VM_VPI) \ $(CFG_CXXFLAGS_NO_UNUSED) \ ifeq ($(CFG_WITH_CCWARN),yes) # Local... Else don't burden users diff --git a/include/verilated_cov_model.h b/include/verilated_cov_model.h new file mode 100644 index 000000000..519fd214c --- /dev/null +++ b/include/verilated_cov_model.h @@ -0,0 +1,63 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage model interfaces +/// +/// Defines interface classes to runtime covergroup coverage-collection classes. +/// These are used to query coverage achievement at runtime, and (future) +/// when writing coverage to the coverage database. +/// +//============================================================================= + +#ifndef VERILATOR_VERILATED_COV_MODEL_H_ +#define VERILATOR_VERILATED_COV_MODEL_H_ + +#include "verilatedos.h" + +#include +#include + +// Per-bin classification. A bin's kind is which set it lives in (structural), +// not a per-bin field. Only Normal feeds coverage(); the rest are recorded. +// Enumerators are 'KIND_'-prefixed because the bare LRM terms collide with +// macros (e.g. IGNORE), which the preprocessor would expand. +enum class VlCovBinKind : uint8_t { + KIND_NORMAL = 0, // Base coverage-collecting bin + KIND_DEFAULT = 1, // Bin declared with 'default' range (which is excluded per LRM) + KIND_IGNORE = 2, // Ignore bin + KIND_ILLEGAL = 3 // Illegal bin +}; + +//============================================================================= +// VlCoverpointIf +/// Read-side view of a coverpoint. The writer queries bins by index; the +/// implementor computes names/kinds on demand. Bounded bin count, so random +/// access by index is the primary usage. + +class VlCoverpointIf VL_NOT_FINAL { +public: + // CONSTRUCTORS + virtual ~VlCoverpointIf() = default; + + // METHODS + // All bins, across every set; index range [0, binCount()) + virtual int binCount() const = 0; + // Bin name in declaration order (e.g. "myBin" or "b[3]") + virtual std::string binName(int i) const = 0; + virtual VlCovBinKind binKind(int i) const = 0; + // Bins covered / effective total (Normal set only) for the coverage calc + virtual void coverageParts(double& covered, double& total) const = 0; +}; + +#endif // Guard diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp new file mode 100644 index 000000000..9bf0fc53f --- /dev/null +++ b/include/verilated_covergroup.cpp @@ -0,0 +1,83 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage collection runtime implementation +/// +/// Linked when covergroups are present. The coverage-database registration +/// is compiled only with "verilator --coverage". +/// +//============================================================================= + +#include "verilatedos.h" + +#include "verilated_covergroup.h" + +#if VM_COVERAGE +#include "verilated_cov.h" +#endif + +void VlCoverpoint::init(const char* hier, uint32_t atLeast, int nBins) { + m_hier = hier; + m_atLeast = atLeast; + m_total = nBins; + m_counts.assign(nBins, 0); +} + +void VlCoverpoint::addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name, + const char* file, int line, int col) { + m_namers.emplace_back(set, count, m_nextBase, naming, name, file, line, col); + m_nextBase += count; + if (set == VlCovBinKind::KIND_NORMAL) m_normal += count; +} + +const VlCovNamer& VlCoverpoint::namerFor(int i) const { + // Namers are appended in ascending, contiguous index order covering [0, m_total), + // and i is always a valid bin index, so the matching namer always exists. + for (const VlCovNamer& nm : m_namers) { + if (i < nm.base() + nm.count()) return nm; + } + VL_UNREACHABLE; +} + +std::string VlCoverpoint::binName(int i) const { + const VlCovNamer& nm = namerFor(i); + std::string name = nm.name(); + if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(i - nm.base()) + ']'; + return name; +} + +#if VM_COVERAGE +void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) { + for (int i = 0; i < binCount(); ++i) { + const VlCovNamer& nm = namerFor(i); + const VlCovBinKind kind = binKind(i); + const std::string binp = binName(i); + const std::string full = m_hier + "." + binp; + const std::string lineStr = std::to_string(nm.line()); + const std::string colStr = std::to_string(nm.col()); + if (kind == VlCovBinKind::KIND_NORMAL) { + VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", + nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", + binp.c_str()); + } else { + const char* const binType = kind == VlCovBinKind::KIND_IGNORE ? "ignore" + : kind == VlCovBinKind::KIND_ILLEGAL ? "illegal" + : "default"; + VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", + nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", + binp.c_str(), "bin_type", binType); + } + } +} +#endif // VM_COVERAGE diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h new file mode 100644 index 000000000..1ffc98ff7 --- /dev/null +++ b/include/verilated_covergroup.h @@ -0,0 +1,144 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage collection runtime +/// +/// VlCoverpoint owns per-instance bin-count storage for one coverpoint, +/// computes coverage, builds bin names on demand, and registers bins with the +/// coverage database. It implements the VlCoverpointIf read interface. +/// +/// Generated covergroup code holds one VlCoverpoint per coverpoint, configures +/// it in the constructor (init + add*Namer), increments bins from sample(), +/// and registers via registerBins(). +/// +//============================================================================= + +#ifndef VERILATOR_VERILATED_COVERGROUP_H_ +#define VERILATOR_VERILATED_COVERGROUP_H_ + +#include "verilatedos.h" + +#include "verilated_cov_model.h" + +#include +#include +#include + +class VerilatedCovContext; + +// How a namer builds the names of the bins it covers. +enum class VlCovBinNaming : uint8_t { + Single, // "" one bin + Array, // "[i]" bins b[N] value array +}; + +// Specifies the naming scheme for a range of bins, allowing the +// specific name to be computed on-demand. +// All name strings are borrowed literals from the generated code. +class VlCovNamer final { + // MEMBERS + VlCovBinKind m_set; // which set the bins belong to + int m_count; // bins this namer covers (1 for Single) + int m_base; // first bin index (declaration order), assigned on append + VlCovBinNaming m_naming; // how bin names are built + const char* m_name; // bin name (Single) or array base name (Array) + const char* m_file; // declaration file + int m_line; // declaration line + int m_col; // declaration column + +public: + // CONSTRUCTORS + VlCovNamer(VlCovBinKind set, int count, int base, VlCovBinNaming naming, const char* name, + const char* file, int line, int col) + : m_set{set} + , m_count{count} + , m_base{base} + , m_naming{naming} + , m_name{name} + , m_file{file} + , m_line{line} + , m_col{col} {} + + // METHODS + VlCovBinKind set() const { return m_set; } + int count() const { return m_count; } + int base() const { return m_base; } + VlCovBinNaming naming() const { return m_naming; } + const char* name() const { return m_name; } + const char* file() const { return m_file; } + int line() const { return m_line; } + int col() const { return m_col; } +}; + +//============================================================================= +// VlCoverpoint +/// Per-instance coverpoint runtime. Bins are stored in declaration order; a +/// bin's set/name come from the owning namer. coverage() is computed on demand +/// by scanning bin counts, keeping the sample() hot path a plain counter bump. + +class VlCoverpoint final : public VlCoverpointIf { + // MEMBERS + std::string m_hier; // "covergroup.coverpoint" + uint32_t m_atLeast = 1; // option.at_least (coverpoint-wide) + int m_total = 0; // bins across all sets + int m_normal = 0; // Normal bins (coverage denominator) + int m_nextBase = 0; // running append cursor + std::vector m_counts; // [m_total], one per bin + std::vector m_namers; // appended in declaration order + + // PRIVATE METHODS + const VlCovNamer& namerFor(int i) const; // obtain the bin-specific name producer + void addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name, + const char* file, int line, int col); + +public: + // CONSTRUCTORS + VlCoverpoint() = default; + + // METHODS + // ---- configuration (from generated constructor) ---- + void init(const char* hier, uint32_t atLeast, int nBins); + void addSingleNamer(VlCovBinKind set, const char* name, const char* file, int line, int col) { + addNamer(set, 1, VlCovBinNaming::Single, name, file, line, col); + } + void addArrayNamer(VlCovBinKind set, int count, const char* name, const char* file, int line, + int col) { + addNamer(set, count, VlCovBinNaming::Array, name, file, line, col); + } + void registerBins(VerilatedCovContext* covcontextp, const char* page); + + // ---- hot path (from generated sample()) ---- + void incrementBin(int i) { ++m_counts[i]; } // Normal bin: count only + void recordHit(int i) { ++m_counts[i]; } // Ignore/Illegal/Default: count only + + // ---- VlCoverpointIf ---- + int binCount() const override { return m_total; } + std::string binName(int i) const override; + VlCovBinKind binKind(int i) const override { return namerFor(i).set(); } + void coverageParts(double& covered, double& total) const override { + // Count Normal bins that reached option.at_least on demand, so the hot + // path (incrementBin) stays a plain counter bump. + int numCovered = 0; + for (const VlCovNamer& nm : m_namers) { + if (nm.set() != VlCovBinKind::KIND_NORMAL) continue; + for (int i = nm.base(); i < nm.base() + nm.count(); ++i) { + if (m_counts[i] >= m_atLeast) ++numCovered; + } + } + covered = numCovered; + total = m_normal; + } +}; + +#endif // Guard diff --git a/include/verilated_force.h b/include/verilated_force.h index d50d27c18..5cbafdc62 100644 --- a/include/verilated_force.h +++ b/include/verilated_force.h @@ -101,12 +101,13 @@ using VlForceStorageType = typename VlForceStorageTypeOf>::ty class VlForceVec final { private: struct Entry final { - int m_lsb; // Inclusive lower bit - int m_msb; // Inclusive upper bit + int m_lsb; // Inclusive lower bit for scalar path or element index for unpacked + int m_msb; // Inclusive upper bit for scalar path or element index for unpacked int m_rhsLsb; // Destination index that maps to RHS index 0 const void* m_rhsDatap; // Pointer to RHS storage - - bool operator<(const Entry& other) const { return m_msb < other.m_msb; } + int m_bitLsb = 0; + int m_bitMsb = 0; + int m_elemWidth = 0; }; std::vector m_entries; // Sorted by msb, non-overlapping @@ -134,6 +135,41 @@ private: return it; } + std::size_t trimElementBitRange(int elem, int bitLsb, int bitMsb) { + auto it = std::lower_bound(m_entries.begin(), m_entries.end(), elem, + [](const Entry& e, int idx) { return e.m_msb < idx; }); + while (it != m_entries.end() && it->m_lsb <= elem) { + if (it->m_elemWidth == 0 || it->m_bitMsb < bitLsb || it->m_bitLsb > bitMsb) { + ++it; + continue; + } + if (it->m_bitLsb < bitLsb && it->m_bitMsb > bitMsb) { + Entry high = *it; + high.m_bitLsb = bitMsb + 1; + it->m_bitMsb = bitLsb - 1; + m_entries.insert(it + 1, high); + break; + } + if (it->m_bitLsb < bitLsb) { + it->m_bitMsb = bitLsb - 1; + ++it; + continue; + } + if (it->m_bitMsb > bitMsb) { + it->m_bitLsb = bitMsb + 1; + break; + } + it = m_entries.erase(it); + } + auto ins = std::lower_bound(m_entries.begin(), m_entries.end(), elem, + [](const Entry& e, int idx) { return e.m_msb < idx; }); + while (ins != m_entries.end() && ins->m_lsb <= elem + && (ins->m_elemWidth == 0 || ins->m_bitLsb <= bitLsb)) { + ++ins; + } + return static_cast(ins - m_entries.begin()); + } + static QData extractRhsChunk(const Entry& entry, int rhsLsb, int width) { assert(width > 0 && width <= VL_QUADSIZE); assert(rhsLsb >= 0); @@ -195,6 +231,22 @@ private: return *static_cast*>(entry.m_rhsDatap); } + template + static typename std::enable_if::value, Elem>::type + blendElem(Elem cur, const Entry& e) { + const Entry bitEntry{e.m_bitLsb, e.m_bitMsb, e.m_rhsLsb, e.m_rhsDatap, 0, 0, 0}; + return applyEntry(cur, bitEntry); + } + + template + static typename std::enable_if::value, Elem>::type blendElem(Elem cur, + const Entry& e) { + Elem res = cur; + const Entry bitEntry{e.m_bitLsb, e.m_bitMsb, e.m_rhsLsb, e.m_rhsDatap, 0, 0, 0}; + applyEntry(res, bitEntry, e.m_bitLsb, e.m_bitMsb, 0); + return res; + } + template typename std::enable_if::value>::type applyEntries(T& val) const { for (const auto& entry : m_entries) { @@ -229,15 +281,19 @@ public: using ElemRef = decltype(VlForceArrayIndexer::elem(result, static_cast(0))); using Elem = VlForceBaseType; - const int total = static_cast(VlForceArrayIndexer::size); for (const auto& entry : m_entries) { - const Elem* const rhsBasep = static_cast(entry.m_rhsDatap); const int startIdx = entry.m_lsb; const int endIdx = entry.m_msb; for (int idx = startIdx; idx <= endIdx; idx++) { - const int rhsIndex = idx - entry.m_rhsLsb; const std::size_t uidx = static_cast(idx); - VlForceArrayIndexer::elem(result, uidx) = rhsBasep[rhsIndex]; + Elem& dst = VlForceArrayIndexer::elem(result, uidx); + if (entry.m_elemWidth == 0) { + const Elem* const rhsBasep = static_cast(entry.m_rhsDatap); + const int rhsIndex = idx - entry.m_rhsLsb; + dst = rhsBasep[rhsIndex]; + } else { + dst = blendElem(dst, entry); + } } } return result; @@ -250,14 +306,18 @@ public: T readIndex(T origVal, int index) const { if (m_entries.empty()) return origVal; - const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index, - [](const Entry& e, int idx) { return e.m_msb < idx; }); - if (it != m_entries.end() && it->m_lsb <= index) { - const int rhsIndex = index - it->m_rhsLsb; - const T* const rhsBasep = static_cast(it->m_rhsDatap); - return rhsBasep[rhsIndex]; + T result = origVal; + for (auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index, + [](const Entry& e, int idx) { return e.m_msb < idx; }); + it != m_entries.end() && it->m_lsb <= index; ++it) { + if (it->m_elemWidth == 0) { + const int rhsIndex = index - it->m_rhsLsb; + result = static_cast(it->m_rhsDatap)[rhsIndex]; + } else { + result = blendElem(result, *it); + } } - return origVal; + return result; } IData readSelI(int lbits, WDataInP valp, int lsb, int width) const { @@ -291,11 +351,28 @@ public: m_entries.insert(it, {lsb, msb, rhsLsb, rhsDatap}); } + void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb, int bitLsb, int bitMsb, + int elemWidth) { + assert(lsb == msb); + assert(rhsDatap); + assert(elemWidth > 0); + assert(0 <= bitLsb && bitLsb <= bitMsb && bitMsb < elemWidth); + const std::size_t at = trimElementBitRange(lsb, bitLsb, bitMsb); + m_entries.insert(m_entries.begin() + at, + Entry{lsb, msb, rhsLsb, rhsDatap, bitLsb, bitMsb, elemWidth}); + } + void release(int lsb, int msb) { assert(lsb <= msb); trimEntries(lsb, msb); } + void release(int lsb, int msb, int bitLsb, int bitMsb) { + assert(lsb == msb); + assert(bitLsb <= bitMsb); + trimElementBitRange(lsb, bitLsb, bitMsb); + } + void touch() {} }; diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index db4c39e5c..349fe44da 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -74,10 +74,8 @@ extern void VL_FATAL_MT(const char* filename, int linenum, const char* hier, extern void VL_WARN_MT(const char* filename, int linenum, const char* hier, const char* msg) VL_MT_SAFE; -// clang-format off /// Print a string, multithread safe. Eventually VL_PRINTF will get called. extern void VL_PRINTF_MT(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; -// clang-format on /// Print a debug message from internals with standard prefix, with printf style format extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; @@ -85,6 +83,9 @@ extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; /// Print a debug message from string via VL_DBG_MSGF inline void VL_DBG_MSGS(const std::string& str) VL_MT_SAFE { VL_DBG_MSGF("%s", str.c_str()); } +/// Flush stdout +extern void VL_FFLUSH_MT() VL_MT_SAFE; + // EMIT_RULE: VL_RANDOM: oclean=dirty inline IData VL_RANDOM_I() VL_MT_SAFE { return vl_rand64(); } inline QData VL_RANDOM_Q() VL_MT_SAFE { return vl_rand64(); } @@ -123,10 +124,6 @@ extern WDataOutP VL_SCOPED_RAND_RESET_ASSIGN_W(int obits, WDataOutP outwp, uint6 /// Random reset a signal of given width (init time only) extern IData VL_RAND_RESET_I(int obits) VL_MT_SAFE; -/// Random reset a signal of given width (init time only) -extern QData VL_RAND_RESET_Q(int obits) VL_MT_SAFE; -/// Random reset a signal of given width (init time only) -extern WDataOutP VL_RAND_RESET_W(int obits, WDataOutP outwp) VL_MT_SAFE; /// Zero reset a signal (slow - else use VL_ZERO_W) extern WDataOutP VL_ZERO_RESET_W(int obits, WDataOutP outwp) VL_MT_SAFE; @@ -268,6 +265,41 @@ static inline VlQueue VL_CVT_UNPACK_TO_Q(const VlUnpacked& q) VL_ return ret; } +// Masked match functions +static inline IData VL_MATCHMASKED_I(int, IData lhs, WDataInP matchp) VL_PURE { + size_t i = 0; + while (true) { + const IData mask = matchp[i * 2]; + const IData bits = matchp[i * 2 + 1]; + if ((mask & lhs) == bits) break; + ++i; + } + return i; +} +static inline IData VL_MATCHMASKED_Q(int, QData lhs, WDataInP matchp) VL_PURE { + size_t i = 0; + while (true) { + const QData mask = VL_SET_QW(matchp + i * 4); + const QData bits = VL_SET_QW(matchp + i * 4 + 2); + if ((mask & lhs) == bits) break; + ++i; + } + return i; +} +static inline IData VL_MATCHMASKED_W(int lbits, WDataInP lhsp, WDataInP matchp) VL_MT_SAFE { + const int iwords = VL_WORDS_I(lbits); + size_t i = 0; + while (true) { + const WDataInP maskp = matchp + (i * iwords * 2); + const WDataInP bitsp = matchp + (i * iwords * 2 + iwords); + EData diff = 0; + for (int j = 0; j < iwords; ++j) diff |= (maskp[j] & lhsp[j]) ^ bitsp[j]; + if (!diff) break; + ++i; + } + return i; +} + // Return double from lhs (numeric) unsigned double VL_ITOR_D_W(int lbits, WDataInP const lwp) VL_PURE; static inline double VL_ITOR_D_I(int, IData lhs) VL_PURE { @@ -867,15 +899,31 @@ static inline IData VL_CLOG2_W(int words, WDataInP const lwp) VL_PURE { return 0; } +static inline IData VL_MOSTSETBITP1_I(IData lhs) VL_PURE { + if (VL_UNLIKELY(!lhs)) return 0; // __builtin_clz is undefined for 0 +#if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(VL_NO_BUILTINS) + return VL_EDATASIZE - __builtin_clz(lhs); +#else + for (int bit = VL_EDATASIZE - 1; bit >= 0; --bit) { + if (VL_BITISSET_E(lhs, bit)) return bit + 1; + } + return 0; // LCOV_EXCL_LINE // Can't get here - one bit must be set +#endif +} +static inline IData VL_MOSTSETBITP1_Q(QData lhs) VL_PURE { + if (VL_UNLIKELY(!lhs)) return 0; +#if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(VL_NO_BUILTINS) + return 64 - __builtin_clzll(static_cast(lhs)); +#else + const IData hi = static_cast(lhs >> 32ULL); + return hi ? (VL_EDATASIZE + VL_MOSTSETBITP1_I(hi)) + : VL_MOSTSETBITP1_I(static_cast(lhs)); +#endif +} static inline IData VL_MOSTSETBITP1_W(int words, WDataInP const lwp) VL_PURE { - // MSB set bit plus one; similar to FLS. 0=value is zero for (int i = words - 1; i >= 0; --i) { - if (VL_UNLIKELY(lwp[i])) { // Shorter worst case if predict not taken - for (int bit = VL_EDATASIZE - 1; bit >= 0; --bit) { - if (VL_UNLIKELY(VL_BITISSET_E(lwp[i], bit))) return i * VL_EDATASIZE + bit + 1; - } - // Can't get here - one bit must be set - } + // Shorter worst case if predict not taken + if (VL_UNLIKELY(lwp[i])) return i * VL_EDATASIZE + VL_MOSTSETBITP1_I(lwp[i]); } return 0; } @@ -973,14 +1021,10 @@ static inline IData VL_EQ_R(int words, VlQueue q, WDataInP const rwp) VL_PURE } else if (sizeof(T) == 4) { for (int i = 0; (i < wordsInQ + 1); ++i) { nequal |= (q.at(wordsInQ - i) ^ rwp[i]); } } else if (sizeof(T) == 8) { - QData temp = 0; int qSize = q.size() - 1; for (int i = 0; (i < qSize); i += 2) { - temp = q.at(qSize - i); nequal |= (static_cast(q.at(qSize - i)) >> 32 ^ rwp[i + 1]); - temp = rwp[i + 1]; nequal |= (static_cast(q.at(qSize - i)) ^ rwp[i]); - temp = rwp[i]; } } return (nequal == 0); @@ -990,7 +1034,6 @@ template static inline IData VL_EQ_R(int words, const VlQueue>& q, WDataInP const rwp) VL_PURE { EData nequal = 0; - const int wordsInQ = q.size() * N_Words; if ((q.size() * N_Words) != words) { return false; } int count = 0; for (int qIndex = q.size() - 1; qIndex >= 0; qIndex--) { @@ -1901,7 +1944,6 @@ static inline void VL_STREAMR_RRI(int lbits, VlQueue& to_q, const VlQueue>& from_q, IData rd) VL_MT_SAFE { to_q.clear(); - VL_CONSTEXPR_CXX17 size_t otherSize = 4 * N_Words; VL_CONSTEXPR_CXX17 size_t sizeOfThis = sizeof(T_Value); T_Value temp = 0; for (auto val : from_q) { diff --git a/include/verilated_sym_props.h b/include/verilated_sym_props.h index c124fb9e9..11e2fe8b8 100644 --- a/include/verilated_sym_props.h +++ b/include/verilated_sym_props.h @@ -76,6 +76,7 @@ class VerilatedVarProps VL_NOT_FINAL { const uint32_t m_magic; // Magic number const VerilatedVarType m_vltype; // Data type const VerilatedVarFlags m_vlflags; // Direction + const uint32_t m_entSize; // Element size in bytes, or 0 to derive from type std::vector m_unpacked; // Unpacked array ranges std::vector m_packed; // Packed array ranges VerilatedRange m_packedDpi; // Flattened packed array range @@ -104,10 +105,12 @@ class VerilatedVarProps VL_NOT_FINAL { // CONSTRUCTORS protected: friend class VerilatedScope; - VerilatedVarProps(VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims) + VerilatedVarProps(VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, + uint32_t entSize = 0) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags{vlflags} { + , m_vlflags{vlflags} + , m_entSize{entSize} { // Only preallocate the ranges initUnpacked(udims, nullptr); initPacked(pdims, nullptr); @@ -119,12 +122,14 @@ public: VerilatedVarProps(VerilatedVarType vltype, int vlflags) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) {} // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} {} VerilatedVarProps(VerilatedVarType vltype, int vlflags, Unpacked, int udims, const int* ulims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initUnpacked(udims, ulims); } // With packed @@ -132,14 +137,16 @@ public: VerilatedVarProps(VerilatedVarType vltype, int vlflags, Packed, int pdims, const int* plims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initPacked(pdims, plims); } VerilatedVarProps(VerilatedVarType vltype, int vlflags, Unpacked, int udims, const int* ulims, Packed, int pdims, const int* plims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initUnpacked(udims, ulims); initPacked(pdims, plims); } @@ -164,6 +171,7 @@ public: bool isDpiCLayout() const { return ((m_vlflags & VLVF_DPI_CLAY) != 0); } bool isSigned() const { return ((m_vlflags & VLVF_SIGNED) != 0); } bool isBitVar() const { return ((m_vlflags & VLVF_BITVAR) != 0); } + bool isNet() const { return ((m_vlflags & VLVF_NET) != 0); } int udims() const VL_MT_SAFE { return m_unpacked.size(); } int pdims() const VL_MT_SAFE { return m_packed.size(); } int dims() const VL_MT_SAFE { return pdims() + udims(); } @@ -265,6 +273,8 @@ protected: // CONSTRUCTORS VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam); + VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, + VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, uint32_t entSize); VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, std::unique_ptr forceControlSignals); @@ -296,6 +306,13 @@ inline VerilatedVar::VerilatedVar(const char* namep, void* datap, VerilatedVarTy , m_datap{datap} , m_namep{namep} , m_isParam{isParam} {} +inline VerilatedVar::VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, + VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, + uint32_t entSize) + : VerilatedVarProps{vltype, vlflags, udims, pdims, entSize} + , m_datap{datap} + , m_namep{namep} + , m_isParam{isParam} {} inline VerilatedVar::VerilatedVar( const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, diff --git a/include/verilated_types.h b/include/verilated_types.h index 40847b65e..a48b99c4c 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -337,6 +337,7 @@ public: ~VlProcess() { if (m_parentp) m_parentp->detach(this); + if (t_currentp == this) t_currentp = m_parentp.get(); } void attach(VlProcess* childp) { m_children.insert(childp); } @@ -2069,8 +2070,24 @@ struct VlNull final { operator T*() const { return nullptr; } + template + bool operator==(T* rhs) const { + return !rhs; + } + template + bool operator==(const T* rhs) const { + return !rhs; + } }; -inline bool operator==(const void* ptr, VlNull) { return !ptr; } + +template +inline bool operator==(T* lhs, VlNull) { + return !lhs; +} +template +inline bool operator==(const T* lhs, VlNull) { + return !lhs; +} //=================================================================== // Verilog class reference container diff --git a/include/verilated_vpi.cpp b/include/verilated_vpi.cpp index 8abbc5205..65afe20f3 100644 --- a/include/verilated_vpi.cpp +++ b/include/verilated_vpi.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,25 @@ constexpr unsigned VL_VPI_LINE_SIZE_ = 8192; //====================================================================== // Implementation +static const char* _vl_vpi_find_unescaped_dot(const char* posp) { + for (; *posp; ++posp) { + if (*posp == '\\') { + while (*posp && *posp != ' ') ++posp; + if (!*posp) return nullptr; + } else if (*posp == '.') { + return posp; + } + } + return nullptr; +} + +static std::string _vl_vpi_member_local_name(const char* const namep) { + const char* localp = namep; + const char* posp = namep; + while ((posp = _vl_vpi_find_unescaped_dot(posp))) localp = ++posp; + return localp; +} + // Base VPI handled object class VerilatedVpio VL_NOT_FINAL { // CONSTANTS @@ -201,6 +221,9 @@ public: } const VerilatedVar* varp() const { return m_varp; } const VerilatedScope* scopep() const { return m_scopep; } + bool isStructOrUnion() const { + return varp()->vltype() == VLVT_STRUCT || varp()->vltype() == VLVT_UNION; + } // Returns the number of the currently indexed dimension (starting at -1 for none). int32_t indexedDim() const { return m_indexedDim; } // Returns whether the currently indexed dimension is unpacked. @@ -363,6 +386,8 @@ class VerilatedVpioVar VL_NOT_FINAL : public VerilatedVpioVarBase { uint32_t m_entSize = 0; // memoized variable size uint32_t m_bitOffset = 0; int32_t m_partselBits = -1; // Part-select width, -1 means no part-select active + std::string m_name; + std::string m_fullNameOverride; protected: void* m_varDatap = nullptr; // varp()->datap() adjusted for array entries @@ -373,6 +398,17 @@ public: : VerilatedVpioVarBase{varp, scopep} { m_entSize = varp->entSize(); m_varDatap = varp->datap(); + if (_vl_vpi_find_unescaped_dot(varp->name())) { + m_name = _vl_vpi_member_local_name(varp->name()); + } + } + VerilatedVpioVar(const VerilatedVar* varp, const VerilatedScope* scopep, void* datap, + const std::string& name, const std::string& fullname) + : VerilatedVpioVarBase{varp, scopep} { + m_entSize = varp->entSize(); + m_varDatap = datap; + m_name = name; + m_fullNameOverride = fullname; } explicit VerilatedVpioVar(const VerilatedVpioVar* vop) : VerilatedVpioVarBase{vop} { @@ -380,6 +416,8 @@ public: m_entSize = vop->m_entSize; m_varDatap = vop->m_varDatap; m_index = vop->m_index; + m_name = vop->m_name; + m_fullNameOverride = vop->m_fullNameOverride; m_partselBits = vop->m_partselBits; m_bitOffset = vop->m_bitOffset; // Not copying m_prevDatap, must be nullptr @@ -395,15 +433,22 @@ public: uint32_t bitOffset() const override { return m_bitOffset; } int32_t partselBits() const { return m_partselBits; } uint32_t bitSize() const { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return 0; if (m_partselBits >= 0) return static_cast(m_partselBits); return VerilatedVpioVarBase::bitSize(); } uint32_t size() const override { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return 0; if (m_partselBits >= 0) return static_cast(m_partselBits); return VerilatedVpioVarBase::size(); } + const VerilatedRange* rangep() const override { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return nullptr; + return VerilatedVpioVarBase::rangep(); + } uint32_t entSize() const { return m_entSize; } const std::vector& index() const { return m_index; } + const char* name() const override { return m_name.empty() ? varp()->name() : m_name.c_str(); } // Create a part-selected view of this variable with the given bit range [hi:lo]. VerilatedVpioVar* withPartSelect(int32_t hi, int32_t lo) const { if (isIndexedDimUnpacked()) return nullptr; @@ -456,22 +501,43 @@ public: return ret; } + VerilatedVpioVar* withMember(const VerilatedVar* memberVarp) const { + const char* const parentName = varp()->name(); + const std::string memberName = memberVarp->name(); + const size_t parentLen = std::strlen(parentName); + + void* const parentDatap = varp()->datap(); + void* const memberDatap = memberVarp->datap(); + if (VL_UNLIKELY(!parentDatap) || VL_UNLIKELY(!memberDatap)) return nullptr; + const auto offset + = static_cast(memberDatap) - static_cast(parentDatap); + + const std::string localName = _vl_vpi_member_local_name(memberVarp->name()); + + return new VerilatedVpioVar{memberVarp, scopep(), + static_cast(varDatap()) + offset, localName, + std::string{fullname()} + memberName.substr(parentLen)}; + } uint32_t type() const override { uint32_t type; // TODO have V3EmitCSyms.cpp put vpiType directly into constant table switch (varp()->vltype()) { case VLVT_REAL: type = vpiRealVar; break; case VLVT_STRING: type = vpiStringVar; break; + case VLVT_STRUCT: type = varp()->isNet() ? vpiStructNet : vpiStructVar; break; + case VLVT_UNION: type = varp()->isNet() ? vpiUnionNet : vpiUnionVar; break; default: type = varp()->isBitVar() ? vpiBitVar : vpiReg; break; } - if (isIndexedDimUnpacked()) return vpiRegArray; + if (isIndexedDimUnpacked()) + return isStructOrUnion() && varp()->isNet() ? vpiNetArray : vpiRegArray; return type; } const char* fullname() const override { - static thread_local std::string t_out; - t_out = std::string{scopep()->name()} + "." + name(); - for (auto idx : index()) t_out += "[" + std::to_string(idx) + "]"; - return t_out.c_str(); + m_fullname = m_fullNameOverride.empty() + ? std::string{scopep()->name()} + "." + varp()->name() + : m_fullNameOverride; + for (auto idx : index()) m_fullname += "[" + std::to_string(idx) + "]"; + return m_fullname.c_str(); } void* prevDatap() const { return m_prevDatap; } void* varDatap() const override { return m_varDatap; } @@ -590,25 +656,63 @@ public: } }; +class VerilatedVpioMemberIter final : public VerilatedVpio { + const VerilatedScope* const m_scopep; + const VerilatedVarNameMap* const m_varsp; + VerilatedVarNameMap::const_iterator m_it; + VerilatedVpioVar* m_varp; + const std::string m_namePrefix; + bool m_started = false; + + static std::string namePrefix(const VerilatedVpioVar* vop) { + return std::string{vop->varp()->name()} + "."; + } + + vpiHandle atEnd() { + delete this; // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + return nullptr; + } + +public: + explicit VerilatedVpioMemberIter(const VerilatedVpioVar* vop) + : m_scopep{vop->scopep()} + , m_varsp{m_scopep->varsp()} + , m_varp{new VerilatedVpioVar{vop}} + , m_namePrefix{namePrefix(vop)} {} + ~VerilatedVpioMemberIter() override { VL_DO_CLEAR(delete m_varp, m_varp = nullptr); } + // cppcheck-suppress duplInheritedMember + static VerilatedVpioMemberIter* castp(vpiHandle h) { + return dynamic_cast(reinterpret_cast(h)); + } + uint32_t type() const override { return vpiIterator; } + vpiHandle dovpi_scan() override { + if (VL_UNLIKELY(!m_varsp)) return atEnd(); + if (VL_UNLIKELY(!m_started)) { + m_it = m_varsp->begin(); + m_started = true; + } else if (VL_LIKELY(m_it != m_varsp->end())) { + ++m_it; + } + for (; m_it != m_varsp->end(); ++m_it) { + const char* const name = m_it->second.name(); + if (std::strncmp(name, m_namePrefix.c_str(), m_namePrefix.length()) != 0) continue; + // Only direct members, not grandchildren + if (_vl_vpi_find_unescaped_dot(name + m_namePrefix.length())) continue; + VerilatedVpioVar* const memberp = m_varp->withMember(&(m_it->second)); + if (VL_UNLIKELY(!memberp)) continue; + return memberp->castVpiHandle(); + } + return atEnd(); + } +}; + class VerilatedVpioModule final : public VerilatedVpioScope { public: explicit VerilatedVpioModule(const VerilatedScope* modulep) : VerilatedVpioScope{modulep} { // Look for '.' not inside escaped identifier - const std::string scopename = m_fullname; - std::string::size_type pos = std::string::npos; - size_t i = 0; - while (i < scopename.length()) { - if (scopename[i] == '\\') { - while (i < scopename.length() && scopename[i] != ' ') ++i; - ++i; // Proc ' ', it should always be there. Then grab '.' on next cycle - } else { - while (i < scopename.length() && scopename[i] != '.') ++i; - if (i < scopename.length()) pos = i++; - } - } - if (VL_UNLIKELY(pos == std::string::npos)) m_toplevel = true; + if (VL_UNLIKELY(!_vl_vpi_find_unescaped_dot(m_fullname))) m_toplevel = true; } // cppcheck-suppress duplInheritedMember static VerilatedVpioModule* castp(vpiHandle h) { @@ -1770,6 +1874,8 @@ const char* VerilatedVpiError::strFromVpiObjType(PLI_INT32 vpiVal) VL_PURE { }; // clang-format on if (VL_UNCOVERABLE(vpiVal < 0)) return names[0]; + // vpiUnionNet is outside the otherwise contiguous SystemVerilog object type range. + if (vpiVal == vpiUnionNet) return "vpiUnionNet"; if (vpiVal <= vpiAutomatics) return names[vpiVal]; if (vpiVal >= vpiPackage && vpiVal <= vpiPropFormalDecl) return sv_names1[(vpiVal - vpiPackage)]; @@ -2135,6 +2241,7 @@ void VerilatedVpiError::selfTest() VL_MT_UNSAFE_ONE { SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEnumVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStructVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUnionVar); + SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUnionNet); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBitVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClassObj); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiChandleVar); @@ -2404,6 +2511,162 @@ static bool vl_vpi_parse_indices(std::string& name, std::vector& indi return true; } +static const VerilatedScope* _vl_vpi_top_port_scopep(const VerilatedScope* const scopep) { + if (VL_UNLIKELY(!scopep)) return nullptr; + if (scopep->type() != VerilatedScope::SCOPE_MODULE) return nullptr; + if (std::strcmp(scopep->name(), "TOP") == 0) return nullptr; + if (_vl_vpi_find_unescaped_dot(scopep->name())) return nullptr; + return Verilated::threadContextp()->scopeFind("TOP"); +} + +static bool _vl_vpi_find_dotted_var(const std::string& scopename, const std::string& basename, + const VerilatedScope*& scopep, const VerilatedVar*& varp, + std::string& fullname) { + if (scopename.empty()) return false; + + // Unpacked struct/union members are exposed as synthetic vars whose names contain dots + // (e.g. "mystruct.member"), so the boundary between the scope and the variable name is + // ambiguous. Walk the boundary leftward, moving one scope segment at a time onto the front + // of the dotted variable name, and try each candidate scope. An exhausted scope means the + // variable lives in the toplevel "TOP" scope. + std::string dottedName = basename; + std::string dottedScope = scopename; + while (true) { + const std::string::size_type lastDot = dottedScope.rfind('.'); + dottedName = (lastDot == std::string::npos ? dottedScope : dottedScope.substr(lastDot + 1)) + + "." + dottedName; + dottedScope.resize(lastDot == std::string::npos ? 0 : lastDot); + scopep = Verilated::threadContextp()->scopeFind(dottedScope.empty() ? "TOP" + : dottedScope.c_str()); + if (scopep) { + if (const VerilatedScope* const topScopep = _vl_vpi_top_port_scopep(scopep)) { + if (const VerilatedVar* const topVarp = topScopep->varFind(dottedName.c_str())) { + scopep = topScopep; + varp = topVarp; + fullname = dottedScope.empty() ? dottedName : dottedScope + "." + dottedName; + return true; + } + } + varp = scopep->varFind(dottedName.c_str()); + if (varp) return true; + } + if (lastDot == std::string::npos) return false; + } +} + +static VerilatedVpioVar* _vl_vpi_handle_member_by_name(const std::string& name, + const VerilatedVpioVar* vop) { + const VerilatedScope* const scopep = vop->scopep(); + if (VL_UNLIKELY(!scopep)) return nullptr; + const std::string memberName = std::string{vop->varp()->name()} + "." + name; + const VerilatedVar* const memberVarp = scopep->varFind(memberName.c_str()); + if (!memberVarp) return nullptr; + return vop->withMember(memberVarp); +} + +static VerilatedVpioVar* _vl_vpi_handle_apply_indices(VerilatedVpioVar* vop, + const std::vector& indices) { + for (const PLI_INT32 index : indices) { + VerilatedVpioVar* const nextVop = vop->withIndex(index); + VL_DO_CLEAR(delete vop, vop = nullptr); + if (!nextVop) return nullptr; + vop = nextVop; + } + return vop; +} + +static bool _vl_vpi_parse_optional_indices(std::string& name, std::vector& indices) { + indices.clear(); + if (name.empty()) return false; + if (name.back() != ']') return true; + + VlVpiBitRange bitRange; + return vl_vpi_parse_indices(name, indices, &bitRange) && !bitRange.valid; +} + +static void _vl_vpi_split_dotted_name(const std::string& name, std::vector& parts) { + parts.clear(); + const char* const namep = name.c_str(); + const char* beginp = namep; + const char* posp = beginp; + while ((posp = _vl_vpi_find_unescaped_dot(posp))) { + parts.emplace_back(beginp, posp - beginp); + beginp = ++posp; + } + parts.emplace_back(beginp); +} + +static VerilatedVpioVar* +_vl_vpi_handle_indexed_member_from_scope(const VerilatedScope* const scopep, + const std::vector& parts, + const size_t firstPart) { + if (VL_UNLIKELY(!scopep) || VL_UNLIKELY(firstPart >= parts.size())) return nullptr; + + std::string baseName = parts[firstPart]; + std::vector indices; + if (!_vl_vpi_parse_optional_indices(baseName, indices)) return nullptr; + + const VerilatedScope* varScopep = scopep; + const VerilatedVar* baseVarp = nullptr; + std::string fullnameOverride; + if (const VerilatedScope* const topScopep = _vl_vpi_top_port_scopep(scopep)) { + if (const VerilatedVar* const topVarp = topScopep->varFind(baseName.c_str())) { + varScopep = topScopep; + baseVarp = topVarp; + fullnameOverride = std::string{scopep->name()} + "." + baseName; + } + } + if (!baseVarp) baseVarp = scopep->varFind(baseName.c_str()); + if (!baseVarp) return nullptr; + + VerilatedVpioVar* baseVop + = fullnameOverride.empty() + ? new VerilatedVpioVar{baseVarp, varScopep} + : new VerilatedVpioVar{baseVarp, varScopep, baseVarp->datap(), + _vl_vpi_member_local_name(baseVarp->name()), + fullnameOverride}; + VerilatedVpioVar* vop = _vl_vpi_handle_apply_indices(baseVop, indices); + if (!vop) return nullptr; + + for (size_t i = firstPart + 1; i < parts.size(); ++i) { + std::string memberName = parts[i]; + if (!_vl_vpi_parse_optional_indices(memberName, indices)) { + VL_DO_CLEAR(delete vop, vop = nullptr); + return nullptr; + } + + VerilatedVpioVar* const memberVop = _vl_vpi_handle_member_by_name(memberName, vop); + VL_DO_CLEAR(delete vop, vop = nullptr); + if (!memberVop) return nullptr; + + vop = _vl_vpi_handle_apply_indices(memberVop, indices); + if (!vop) return nullptr; + } + return vop; +} + +static VerilatedVpioVar* +_vl_vpi_handle_dotted_indexed_member_by_name(const std::string& scopeAndName) { + std::vector parts; + _vl_vpi_split_dotted_name(scopeAndName, parts); + if (parts.size() < 2) return nullptr; + + for (size_t firstPart = parts.size() - 1; firstPart > 0; --firstPart) { + std::string scopeName = parts[0]; + for (size_t i = 1; i < firstPart; ++i) scopeName += "." + parts[i]; + const VerilatedScope* const scopep + = Verilated::threadContextp()->scopeFind(scopeName.c_str()); + if (!scopep) continue; + if (VerilatedVpioVar* const vop + = _vl_vpi_handle_indexed_member_from_scope(scopep, parts, firstPart)) { + return vop; + } + } + + const VerilatedScope* const topScopep = Verilated::threadContextp()->scopeFind("TOP"); + return _vl_vpi_handle_indexed_member_from_scope(topScopep, parts, 0); +} + // for obtaining handles vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { @@ -2425,7 +2688,9 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { const VerilatedVar* varp = nullptr; const VerilatedScope* scopep; + std::string fullnameOverride; const VerilatedVpioScope* const voScopep = VerilatedVpioScope::castp(scope); + const VerilatedVpioVar* const voVarp = VerilatedVpioVar::castp(scope); if (0 == std::strncmp(scopeAndName.c_str(), "$root.", std::strlen("$root."))) { scopeAndName.erase(0, std::strlen("$root.")); @@ -2433,6 +2698,12 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { const bool scopeIsPackage = VerilatedVpioPackage::castp(scope) != nullptr; scopeAndName = std::string{voScopep->fullname()} + (scopeIsPackage ? "" : ".") + scopeAndName; + } else if (voVarp && voVarp->isStructOrUnion()) { + if (VerilatedVpioVar* const memberp + = _vl_vpi_handle_member_by_name(scopeAndName, voVarp)) { + return memberp->castVpiHandle(); + } + scopeAndName = std::string{voVarp->fullname()} + "." + scopeAndName; } { // This doesn't yet follow the hierarchy in the proper way @@ -2491,8 +2762,18 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { } if (!varp) { scopep = Verilated::threadContextp()->scopeFind(scopename.c_str()); - if (!scopep) return nullptr; - varp = scopep->varFind(basename.c_str()); + if (scopep) { varp = scopep->varFind(basename.c_str()); } + // Unpacked struct members are exposed as synthetic variables with dotted names. + // Exact unindexed member names can be found directly; indexed member paths need the + // component walker so array indices are applied before member offsets. + if (!varp + && !_vl_vpi_find_dotted_var(scopename, basename, scopep, varp, fullnameOverride)) { + if (VerilatedVpioVar* const memberp + = _vl_vpi_handle_dotted_indexed_member_by_name(scopeAndName)) { + return memberp->castVpiHandle(); + } + return nullptr; + } } } if (!varp) return nullptr; @@ -2501,6 +2782,11 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { vpiHandle resultHandle; if (varp->isParam()) { resultHandle = (new VerilatedVpioParam{varp, scopep})->castVpiHandle(); + } else if (!fullnameOverride.empty()) { + resultHandle + = (new VerilatedVpioVar{varp, scopep, varp->datap(), + _vl_vpi_member_local_name(varp->name()), fullnameOverride}) + ->castVpiHandle(); } else { resultHandle = (new VerilatedVpioVar{varp, scopep})->castVpiHandle(); } @@ -2642,6 +2928,11 @@ vpiHandle vpi_iterate(PLI_INT32 type, vpiHandle object) { if (vop) return ((new VerilatedVpioRegIter{vop})->castVpiHandle()); return nullptr; } + case vpiMember: { + const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object); + if (!vop || !vop->isStructOrUnion()) return nullptr; + return ((new VerilatedVpioMemberIter{vop})->castVpiHandle()); + } case vpiParameter: { const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object); if (VL_UNLIKELY(!vop)) return nullptr; @@ -2734,6 +3025,11 @@ PLI_INT32 vpi_get(PLI_INT32 property, vpiHandle object) { if (VL_UNLIKELY(!vop)) return vpiUndefined; return vop->varp()->isSigned(); } + case vpiPacked: { + const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object); + if (VL_LIKELY(vop && vop->isStructOrUnion())) return 0; + [[fallthrough]]; + } default: VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported property %s, nothing will be returned", __func__, VerilatedVpiError::strFromVpiProp(property)); diff --git a/python-dev-requirements.txt b/python-dev-requirements.txt index 11f9fad84..e90349d2c 100644 --- a/python-dev-requirements.txt +++ b/python-dev-requirements.txt @@ -19,8 +19,10 @@ breathe==4.36.0 compiledb==0.10.7 distro==1.9.0 +docstrfmt==2.2.0 gersemi==0.23.1 mbake==1.4.3 +mdformat==1.0.0 mypy==1.19.0 pylint==3.0.2 ruff==0.14.8 @@ -31,4 +33,4 @@ sphinxcontrib-spelling==8.0.2 yamlfix==1.19.1 yapf==0.43.0 -git+https://github.com/antmicro/astsee.git +git+https://github.com/antmicro/astsee.git@222480a8ec13b312809ea4acc08c81b4c0f4da2f diff --git a/src/AGENTS.md b/src/AGENTS.md new file mode 100644 index 000000000..cb53dc31c --- /dev/null +++ b/src/AGENTS.md @@ -0,0 +1,241 @@ + + +# src/ -- Verilator compiler sources + +Covers all C++ under `src/`, including astgen inputs and the parser/lexer +(`verilog.y`, `verilog.l`). Read the repository-root [AGENTS.md](../AGENTS.md) +first. This file has two parts: **Orientation** explains the AST and pass model; +**Before you open a PR** is the style and correctness checklist. + +______________________________________________________________________ + +# Orientation: the AST and the visitor model + +- **Everything is an `AstNode`.** Each construct is an `Ast*` subclass (`AstAdd`, + `AstVar`, `AstIf`). The design under analysis is one tree, with statement lists + threaded by sibling `nextp()` links. +- **Children sit in numbered slots `op1p()`..`op4p()`, accessed by name.** Use the + named accessors (`lhsp()`, `condp()`, `thensp()`), not the raw slots -- the + numbering is an implementation detail. +- **`astgen` generates node boilerplate.** Declare children and cross-node + pointers with `@astgen op` / `@astgen ptr` annotations in the `V3AstNode*.h` + headers; it emits accessors, clone, and broken-check code. Do not hand-write + what astgen can generate. +- **A pass is a visitor.** Convention: a class with a private constructor and a + static `apply()` entry point, named after its file (`TimingSuspendableVisitor` + in `V3Timing.cpp`). It walks the tree through `visit(AstFoo*)` handlers and + `iterateChildren()`. To understand a pass, read its top-of-file comment first -- + every `.cpp` opens with one describing the algorithm. +- **Scratch state lives on nodes.** Passes stash data in `user1p()`..`user5p()` + (and `user1()`..`user5()`), claimed for the pass lifetime with a `VNUser*InUse` + guard. Save and restore visitor members across recursion with `VL_RESTORER`. +- **Three downcasts, three null behaviors:** `VN_IS` returns bool, `VN_CAST` + returns nullptr on mismatch, `VN_AS` asserts the type. `V3Broken` re-validates + tree invariants between passes, so trust resolved pointers (`dtypep()`, + `varp()`) instead of adding defensive null checks for impossible cases. +- `docs/internals.rst` is the authoritative reference for the AST, the pass list, + and node lifetime. + +______________________________________________________________________ + +# Before you open a PR + +## Code style + +- Mark every variable, parameter, pointer, and member function `const` where possible. +- Pointer variables take a `p` suffix and pointer locals are doubly const where + possible: `AstVar* const varp`; non-pointers never use the `p` suffix. +- Do not use `auto` except for iterators or genuinely unwieldy types. +- Use pre-increment (`++i`) unless you specifically need post-increment's old value. +- Brace-initialize node and struct construction: `new AstIf{fl, condp, thenp, elsep}`. +- Never use C-style casts; instead use `static_cast` for non-AST types and + `VN_AS`/`VN_CAST` for AST downcasts. +- `static constexpr` for compile-time constants, not `#define` or file-scope const. +- Mark every `class`/`struct` `final` or `VL_NOT_FINAL` -- a distribution test + scans all definitions. +- Keep functions under roughly 100-150 lines; thread shared state through a + context struct rather than long parameter lists. +- Keep headers lean: move implementation to `.cpp`; convert large lambdas into + named member functions -- lambdas are opaque in stack traces and block reuse. +- Start every new `.cpp` with a top-of-file comment explaining the algorithm. +- Comments are capitalized sentences written for an unknown future reader, without + "I"/"we"/"our"; remove commented-out code -- version control preserves history. +- No `using namespace`; prefix non-namespaced symbols with `VL`/`Vl`. +- Prefer semantic predicates over enum comparisons: `varp->isClassMember()`, not + `varp->varType() == VVarType::MEMBER`. +- `new*` functions return a `new` object; `make*` functions do something more + complex -- pick the prefix accordingly. +- Name compiler-created temporaries with a `__V` prefix plus a context suffix + (`__VInside`, `__VCase`); runtime utility functions use a `vl_` prefix. +- Use `VL_*` bit/word macros from `verilatedos.h` (`VL_WORDS_I`, `VL_MASK_I`); do + not include `` directly. +- Replace magic numbers with named `static constexpr` constants. + +## AST construction and manipulation + +- Build logic as AST nodes, never as raw C text in `AstCStmt` -- later passes + (V3Name, `--protect`) rename AST identifiers but cannot see into raw strings. + +- Know the cast forms (above) and never pair `VN_IS` with `VN_AS` on the same + node -- use a single `VN_CAST`: + + ```cpp + // BAD: redundant double check + if (VN_IS(nodep, VarRef)) { AstVarRef* const refp = VN_AS(nodep, VarRef); } + // GOOD: single conditional cast + if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) { ... } + ``` + +- Use `UASSERT_OBJ(cond, nodep, "...")` over `UASSERT` when a node is in scope; + use `v3fatalSrc("...")` for unreachable paths, never a silent `if (!ptr) return;`. + +- Use `VL_DO_DANGLING(pushDeletep(nodep), nodep)` instead of `deleteTree()` in + visitors -- deferred deletion is safe against re-entry and unlinking order. + `deleteTree()` is only for fresh nodes that never entered the tree. + +- Every new AST member needs both `dump()` and `dumpJson()` support -- never wrap + these in `LCOV_EXCL`; cover them by adding a construct to `t_debug_emitv.v`. + Override `isSame()` to include any new semantically meaningful field. + +- Pointers to nodes outside op1p-op4p require a `broken()` override and + `cloneRelink()` support -- avoid storing out-of-tree node pointers when possible. + +- Never allocate AstNode objects on the stack or by value -- always pointers. + +- Prefer a new `visit()` in an existing visitor over `nodep->foreach(...)` -- + better for runtime, and handles diverse node types better. Prefer named + accessors over `op1p()`..`op4p()`, and verify traversal order is preserved + when converting manual iteration. + +- Prefer `AstForeach` over generating unrolled loop bodies -- constant-size code + instead of O(N); wrap the body in `AstBegin` for scope isolation. + +- Always `skipRefp()` when comparing or resolving dtypes -- missing it breaks + typedef support; prove the paths with typedef tests. + +- Use `num().isOpaque()` rather than `isDouble() || isString()` when guarding + V3Number comparisons against non-integer types. + +- Use `FileLine::operatorCompare` for source-position ordering -- never hand-roll + filename/lineno comparisons. + +- Identify compiler-generated constructs by an attribute flag on the node (with + dump support), never by name-pattern matching -- magic names break with escaped + identifiers. + +- Use `V3Number` arithmetic for `AstConst` values wider than 32 bits -- `1 << i` + silently overflows at `i >= 32`. + +- Use `VMemberMap`/`findMember()` for name lookups in structs, classes, modules, + and packages -- O(1) versus quadratic linear scans. + +- Never emit raw source filenames or identifiers in generated code -- pass them + through `protect()`/`putsQuoted` so `--protect` does not leak source. + +## Visitors and passes + +- `VL_RESTORER` on every visitor member a `visit()` modifies before iterating + children -- even when nesting "cannot happen" today. +- Every pass using `userNp()` needs a `VNUserNInUse` guard, and the header + documents which user fields it uses. +- Use `iterateAndNextNull()` rather than `iterate()` -- the null-safe form + prevents copy-paste errors during refactors. +- Derive read-only visitors from `VNVisitorConst` with `iterateChildrenConst`. +- Reset per-module visitor state in `visit(AstNodeModule*)`, including numeric ID + counters, to keep generated names stable. +- Capture first-occurrence module state inside the node's own `visit()` handler, + not via a `foreach` pre-scan from `visit(AstNodeModule)` -- source order already + matches IEEE declaration-before-use. +- Avoid `backp()` -- it returns parent or prior sibling depending on position and + causes O(n^2) hunts; build maps or capture context during forward traversal. +- When raw node pointers key a map or set, erase entries when the node is deleted + -- allocators reuse addresses, so stale entries alias new nodes. +- Derive graph-shaped passes from V3Graph (`V3GraphVertex`/`V3GraphEdge`) -- it + gives dump, color, rank, topological sort, and reachability for free. +- When a change outgrows local rewrites, create a dedicated pass instead of + growing an existing one. +- State explicitly how side effects are preserved in optimizations involving + purity, expression lifting, or simplification. + +## Errors and warnings + +(See the root checklist for choosing the diagnostic API and its required test.) + +- Append `nodep->prettyNameQ()` for user-facing names; use `name()` only in + debug/UINFO output. Enclose specific values in single quotes: `'value'`. +- End messages with periods, never exclamation marks; do not write "Error:" in the + text -- the macro prints the prefix. +- State what was attempted and what was found: "Instance attempts to connect to + 'PARAM' as a parameter, but it is a variable". Add a `warnMore()` suggestion + where possible. +- Name warning codes object-first and short (`ASCRANGE`, not `RANGEASC`); rename + via `renamedTo()` so old suppressions keep working. +- Set warning suppression on `AstVar`, not `AstVarRef` -- VarRefs get recreated + and lose `warnIsOff`. +- "Unsupported:" messages describe the specific unsupported context, not just the + construct name -- each message must be distinct. +- When replacing or refactoring a pass, keep existing user-facing error strings -- + `.out` goldens and documentation depend on the wording. + +## Performance and memory + +- O(n^2) is never acceptable -- build maps for batch lookups; any quadratic loop + needs explicit justification in a comment. +- Prefer `std::map` for per-module structures (many small instances); use + `unordered_map` only for one-per-netlist data, and never let `unordered_*` + iteration order reach generated output. +- Prefer `emplace` over `insert` and check the returned `.second` instead of a + separate `find()`. `reserve()` strings and vectors when the size is estimable. +- Add no new static or global mutable data -- statics are being eliminated for + future parallelism. +- Use Verilator's fixed-width data types for model data (`CData`/`SData`/`IData`/ + `QData`/`VlWide`), not `size_t`. Process wide data word-by-word + (`VL_ZERO_W`, `VL_MEMCPY_W`), never bit-by-bit. +- No exceptions in verilated runtime code; do string parsing at verilation time, + never during simulation. +- Wrap unlikely hot-path branches in `VL_UNLIKELY`/`VL_LIKELY`. +- Count what every new pass does via V3Stats -- stats become deterministic + regression anchors. + +## Thread safety + +- Annotate with the hierarchy `VL_PURE` > `VL_MT_SAFE` > `VL_MT_STABLE`: PURE has + no side effects and calls only PURE; MT_SAFE is safe under locks; MT_STABLE is + safe only while tree topology is stable. Annotations must match the + implementation. +- Never include `verilated.h` in the compiler itself -- use `verilatedos.h`. +- Annotate mutex-protected members with `VL_GUARDED_BY` and document acquisition + ordering. `++` on shared state and container `empty()` are not thread-safe. + +## Parser and lexer (verilog.y, verilog.l) + +- Preserve IEEE Appendix A BNF comments (`// IEEE: {rule}`); comment explicitly + when accepting syntax beyond IEEE as an extension. +- The parser only builds AST nodes -- defer semantic validation, `VN_IS` checks, + and context-dependent logic to V3LinkParse/V3Width and later passes. +- Represent hierarchical paths as structured nodes (`AstDot`/parse-ref chains via + `idDotted`), never concatenated strings -- preserves per-segment FileLine. +- Prefer tightening a grammar rule's operand type over a runtime cast-chain guard + in a later visitor -- illegal operands then fail with a clean syntax error. +- Solve ambiguities with token-pipeline look-ahead (`tokenPipeScan*`) rather than + limiting grammar rules; mark unsupported rules with `//UNSUP`. +- Sort token declarations alphabetically by string literal; sort `yD_*` + productions by token name. +- Add a test for every `|` alternative and optional clause of a new or changed + grammar rule -- untested alternatives are where parse regressions hide. + +## File-specific rules + +| File | Rule | +|---|---| +| `src/V3Options.cpp` | Chain `.notForRerun()` onto `DECL_OPTION()` for options that do not affect semantic output | +| `src/V3Ast.cpp` | For composite types (queues, dynamic arrays) use `computeCastableImp()` on subtypes -- shallow `width()`/`similarDType()` checks miss nested incompatibility | +| `src/V3AstNode*.h` | Every node class gets a what-construct comment and every member a semantic-purpose comment; put enum type definitions in `V3AstAttr.h` | +| `src/V3AstNodeExpr.h` | `CCast` is only for basic C types (char/short/int/QData) -- never 4-state logic or structs | +| `src/V3AstNodeOther.h` | `cloneRelink` must propagate all stateful flags (e.g. `maybePointedTo`) and update internal references | +| `src/V3Const.cpp` | Check `keepIfEmpty` before removing empty functions -- flagged functions must survive for codegen/side effects | +| `src/V3Coverage.cpp` | Instrumentation contexts are opt-in (allowlist), never blocklist -- blocklists silently break when new contexts appear | +| `src/V3Inline.cpp` | Preserve `VarXRef::varp()` during passes -- pin-reconnection needs it before V3LinkDot re-resolves | +| `src/V3Sched*.cpp` | Every change needs a test proving necessity; isolate unrelated scheduler changes into separate PRs -- high-risk area | diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad2178203..90a0d3ab4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,7 @@ set(HEADERS V3AstNodeExpr.h V3AstNodeOther.h V3AstNodeStmt.h + V3AstPatterns.h V3AstUserAllocator.h V3Begin.h V3Branch.h @@ -175,7 +176,6 @@ set(HEADERS V3Simulate.h V3Slice.h V3Split.h - V3SplitAs.h V3SplitVar.h V3StackCount.h V3Stats.h @@ -183,7 +183,6 @@ set(HEADERS V3String.h V3Subst.h V3SymTable.h - V3TSP.h V3Table.h V3Task.h V3ThreadPool.h @@ -203,7 +202,6 @@ set(HEADERS V3WidthCommit.h V3WidthRemove.h VlcBucket.h - VlcCovergroup.h VlcOptions.h VlcPoint.h VlcSource.h @@ -253,7 +251,6 @@ set(COMMON_SOURCES V3DfgDataType.cpp V3DfgDecomposition.cpp V3DfgDfgToAst.cpp - V3DfgDumpPatterns.cpp V3DfgOptimizer.cpp V3DfgPasses.cpp V3DfgPeephole.cpp @@ -330,6 +327,7 @@ set(COMMON_SOURCES V3ParseGrammar.cpp V3ParseImp.cpp V3ParseLex.cpp + V3PatternStats.cpp V3PreProc.cpp V3PreShell.cpp V3Premit.cpp @@ -351,14 +349,12 @@ set(COMMON_SOURCES V3Scoreboard.cpp V3Slice.cpp V3Split.cpp - V3SplitAs.cpp V3SplitVar.cpp V3StackCount.cpp V3Stats.cpp V3StatsReport.cpp V3String.cpp V3Subst.cpp - V3TSP.cpp V3Table.cpp V3Task.cpp V3ThreadPool.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 08c8899fc..f5a728063 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -264,7 +264,6 @@ RAW_OBJS_PCH_ASTNOMT = \ V3DfgDataType.o \ V3DfgDecomposition.o \ V3DfgDfgToAst.o \ - V3DfgDumpPatterns.o \ V3DfgOptimizer.o \ V3DfgPasses.o \ V3DfgPeephole.o \ @@ -314,6 +313,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3OrderProcessDomains.o \ V3OrderSerial.o \ V3Param.o \ + V3PatternStats.o \ V3Premit.o \ V3ProtectLib.o \ V3RandSequence.o \ @@ -333,11 +333,9 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Scoreboard.o \ V3Slice.o \ V3Split.o \ - V3SplitAs.o \ V3SplitVar.o \ V3StackCount.o \ V3Subst.o \ - V3TSP.o \ V3Table.o \ V3Task.o \ V3Timing.o \ diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 995f4fd5c..13cd270b4 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -351,7 +351,7 @@ public: class ActiveDlyVisitor final : public VNVisitor { public: - enum CheckType : uint8_t { CT_SEQ, CT_COMB, CT_INITIAL, CT_SUSPENDABLE }; + enum CheckType : uint8_t { CT_COMB, CT_FINAL }; private: // MEMBERS @@ -359,15 +359,9 @@ private: // VISITORS void visit(AstAssignDly* nodep) override { - // Non-blocking assignments are OK in sequential processes - if (m_check == CT_SEQ || m_check == CT_SUSPENDABLE) return; - // Issue appropriate warning - if (m_check == CT_INITIAL) { - nodep->v3warn(INITIALDLY, - "Non-blocking assignment '<=' in initial/final block\n" - << nodep->warnMore() - << "... This will be executed as a blocking assignment '='!"); + if (m_check == CT_FINAL) { + nodep->v3warn(FINALDLY, "Non-blocking assignment '<=' in final block"); } else { nodep->v3warn(COMBDLY, "Non-blocking assignment '<=' in combinational logic process\n" @@ -465,11 +459,7 @@ class ActiveVisitor final : public VNVisitor { wantactivep->addStmtsp(nodep); // Warn and convert any delayed assignments - { - ActiveDlyVisitor{nodep, !m_clockedProcess ? ActiveDlyVisitor::CT_COMB - : oldsentreep ? ActiveDlyVisitor::CT_SEQ - : ActiveDlyVisitor::CT_SUSPENDABLE}; - } + if (!m_clockedProcess) ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_COMB}; // Delete sensitivity list if (oldsentreep) VL_DO_DANGLING(oldsentreep->deleteTree(), oldsentreep); @@ -509,17 +499,11 @@ class ActiveVisitor final : public VNVisitor { void visit(AstInitialStatic* nodep) override { moveUnderSpecial(nodep); } void visit(AstInitial* nodep) override { - const bool timedInitial - = v3Global.opt.timing().isSetTrue() && nodep->exists([](const AstNode* const subp) { - return VN_IS(subp, Delay) || VN_IS(subp, EventControl); - }); - const ActiveDlyVisitor dlyvisitor{nodep, timedInitial ? ActiveDlyVisitor::CT_SUSPENDABLE - : ActiveDlyVisitor::CT_INITIAL}; visitSenItems(nodep); moveUnderSpecial(nodep); } void visit(AstFinal* nodep) override { - const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL}; + const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_FINAL}; moveUnderSpecial(nodep); } void visit(AstCoverToggle* nodep) override { moveUnderSpecial(nodep); } diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 0ccf162fa..cbf462260 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -20,9 +20,140 @@ #include "V3AstUserAllocator.h" #include "V3Stats.h" +#include "V3UniqueNames.h" VL_DEFINE_DEBUG_FUNCTIONS; +namespace { + +class DefaultDisableLocalVisitor final : public VNVisitor { + // STATE + AstNode* m_scopep = nullptr; + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + nodep->defaultDisablep(nullptr); + iterateChildren(nodep); + } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + nodep->defaultDisablep(nullptr); + iterateChildren(nodep); + } + void visit(AstDefaultDisable* nodep) override { + UASSERT_OBJ(nodep, m_scopep, + "default disable iff must be inside a module or generate block"); + AstDefaultDisable* defaultp = nullptr; + if (const AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) { + defaultp = modp->defaultDisablep(); + } else { + defaultp = VN_AS(m_scopep, GenBlock)->defaultDisablep(); + } + if (VL_UNLIKELY(defaultp)) { + nodep->v3error("Only one 'default disable iff' allowed per " + << (VN_IS(m_scopep, NodeModule) ? "module" : "generate block") + << " (IEEE 1800-2023 16.15)"); + } else if (AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) { + modp->defaultDisablep(nodep); + } else { + VN_AS(m_scopep, GenBlock)->defaultDisablep(nodep); + } + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit DefaultDisableLocalVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +class DefaultDisablePropagateVisitor final : public VNVisitor { + // STATE + AstDefaultDisable* m_defaultDisablep = nullptr; + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + if (!nodep->defaultDisablep()) nodep->defaultDisablep(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit DefaultDisablePropagateVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +// Lower a sequence used as an event control ('@seq', IEEE 1800-2023 9.4.2.4) into a +// synthesized event fired by an internal 'cover sequence' on each end-of-match +class SeqEventLowerVisitor final : public VNVisitor { + // STATE + AstNodeModule* m_modp = nullptr; // Current module + V3UniqueNames m_names{"__VseqEvent"}; // Synthesized event names + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_modp); + m_modp = nodep; + iterateChildren(nodep); + } + void visit(AstSenItem* nodep) override { + AstFuncRef* const funcrefp = VN_CAST(nodep->sensp(), FuncRef); + if (funcrefp && VN_IS(funcrefp->taskp(), Sequence)) { + FileLine* const flp = nodep->fileline(); + AstVar* const eventp = new AstVar{flp, VVarType::MODULETEMP, m_names.get(nodep), + m_modp->findBasicDType(VBasicDTypeKwd::EVENT)}; + eventp->lifetime(VLifetime::STATIC_EXPLICIT); + m_modp->addStmtsp(eventp); + v3Global.setHasEvents(); + funcrefp->unlinkFrBack(); + nodep->sensp(new AstVarRef{flp, eventp, VAccess::READ}); + const bool automaticActual = funcrefp->exists([](const AstNodeVarRef* refp) { + return refp->varp() && refp->varp()->lifetime().isAutomatic(); + }); + if (automaticActual) { + nodep->v3error("Arguments to a sequence used as an event control must be" + " static (IEEE 1800-2023 9.4.2.4)"); + VN_AS(funcrefp->taskp(), Sequence)->isReferenced(false); + VL_DO_DANGLING(pushDeletep(funcrefp), funcrefp); + return; + } + AstFireEvent* const firep + = new AstFireEvent{flp, new AstVarRef{flp, eventp, VAccess::WRITE}, false}; + AstCover* const coverp + = new AstCover{flp, new AstPropSpec{flp, nullptr, nullptr, funcrefp}, firep, + VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + coverp->isSeqEvent(true); + m_modp->addStmtsp(coverp); + return; + } + iterateChildren(nodep); + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit SeqEventLowerVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +} // namespace + +void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) { + { DefaultDisableLocalVisitor{nodep}; } + { DefaultDisablePropagateVisitor{nodep}; } +} + +void V3AssertCommon::lowerSequenceEvents(AstNetlist* nodep) { + { SeqEventLowerVisitor{nodep}; } + V3Global::dumpCheckGlobalTree("assertseqevent", 0, dumpTreeEitherLevel() >= 3); +} + //###################################################################### // AssertDeFutureVisitor // If any AstFuture, then move all non-future varrefs to be one cycle behind, @@ -153,12 +284,26 @@ class AssertVisitor final : public VNVisitor { bool m_inRestrict = false; // True inside restrict assertion AstNode* m_passsp = nullptr; // Current pass statement AstNode* m_failsp = nullptr; // Current fail statement + AstNodeCoverOrAssert* m_assertp = nullptr; // Current assertion AstFinal* m_finalp = nullptr; // Current final block + VDouble0 m_statLiftedCaseExprs; // Count of purified case expressions + AstNodeFTask* m_ftaskp = nullptr; // Current function/task + V3UniqueNames m_caseTempNames{"__VCase"}; // Map from (expression, senTree) to AstAlways that computes delayed values of the expression std::unordered_map, std::unordered_map, AstAlways*>> m_modExpr2Sen2DelayedAlwaysp; // METHODS + static string assertCtlGetCall(const char* query, VAssertType type, + VAssertDirectiveType directiveType) { + return "vlSymsp->_vm_contextp__->assertCtlGet(VerilatedAssertCtlQuery::"s + query + ", "s + + std::to_string(type) + ", "s + std::to_string(directiveType) + ")"s; + } + static const char* assertPassOnQuery(bool vacuous) { + static constexpr const char* queries[2] + = {"ASSERT_CTL_PASS_ON_NONVACUOUS", "ASSERT_CTL_PASS_ON_VACUOUS"}; + return queries[vacuous]; + } static AstNodeExpr* assertOnCond(FileLine* fl, VAssertType type, VAssertDirectiveType directiveType) { // cppcheck-suppress missingReturn @@ -178,9 +323,7 @@ class AssertVisitor final : public VNVisitor { case VAssertDirectiveType::ASSUME: { if (v3Global.opt.assertOn()) { return new AstCExpr{fl, AstCExpr::Pure{}, - "vlSymsp->_vm_contextp__->assertOnGet("s + std::to_string(type) - + ", "s + std::to_string(directiveType) + ")"s, - 1}; + assertCtlGetCall("ASSERT_CTL_ON", type, directiveType), 1}; } return new AstConst{fl, AstConst::BitFalse{}}; } @@ -196,6 +339,27 @@ class AssertVisitor final : public VNVisitor { } VL_UNREACHABLE; } + + static bool isControlled(VAssertDirectiveType directiveType) { + return (static_cast(directiveType) + & (static_cast(VAssertDirectiveType::ASSERT) + | static_cast(VAssertDirectiveType::COVER) + | static_cast(VAssertDirectiveType::ASSUME))); + } + static AstNodeExpr* assertPassOnCond(FileLine* fl, VAssertType type, + VAssertDirectiveType directiveType, bool vacuous) { + if (!isControlled(directiveType)) return new AstConst{fl, AstConst::BitTrue{}}; + if (!v3Global.opt.assertOn()) return new AstConst{fl, AstConst::BitFalse{}}; + return new AstCExpr{fl, AstCExpr::Pure{}, + assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType), 1}; + } + static AstNodeExpr* assertFailOnCond(FileLine* fl, VAssertType type, + VAssertDirectiveType directiveType) { + if (!isControlled(directiveType)) return new AstConst{fl, AstConst::BitTrue{}}; + if (!v3Global.opt.assertOn()) return new AstConst{fl, AstConst::BitFalse{}}; + return new AstCExpr{fl, AstCExpr::Pure{}, + assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType), 1}; + } string assertDisplayMessage(const AstNode* nodep, const string& prefix, const string& message, VDisplayType severity) { if (severity == VDisplayType::DT_ERROR || severity == VDisplayType::DT_FATAL) { @@ -207,36 +371,6 @@ class AssertVisitor final : public VNVisitor { + cvtToStr(nodep->fileline()->lineno()) + ": %m" + ((message != "") ? ": " : "") + message + "\n"); } - static bool resolveAssertType(AstAssertCtl* nodep) { - if (!nodep->assertTypesp()) { - nodep->ctlAssertTypes(VAssertType{ALL_ASSERT_TYPES}); - return true; - } - if (const AstConst* const assertTypesp = VN_CAST(nodep->assertTypesp(), Const)) { - nodep->ctlAssertTypes(VAssertType{assertTypesp->toSInt()}); - return true; - } - return false; - } - static bool resolveControlType(AstAssertCtl* nodep) { - if (const AstConst* const constp = VN_CAST(nodep->controlTypep(), Const)) { - nodep->ctlType(constp->toSInt()); - return true; - } - return false; - } - static bool resolveDirectiveType(AstAssertCtl* nodep) { - if (!nodep->directiveTypesp()) { - nodep->ctlDirectiveTypes(VAssertDirectiveType::ASSERT | VAssertDirectiveType::ASSUME - | VAssertDirectiveType::COVER); - return true; - } - if (const AstConst* const directiveTypesp = VN_CAST(nodep->directiveTypesp(), Const)) { - nodep->ctlDirectiveTypes(VAssertDirectiveType{directiveTypesp->toSInt()}); - return true; - } - return false; - } void replaceDisplay(AstDisplay* nodep, const string& prefix) { nodep->fmtp()->text( assertDisplayMessage(nodep, prefix, nodep->fmtp()->text(), nodep->displayType())); @@ -278,8 +412,8 @@ class AssertVisitor final : public VNVisitor { } static AstIf* newIfAssertOn(AstNode* bodyp, VAssertDirectiveType directiveType, VAssertType type = VAssertType::INTERNAL) { - // Add a internal if to check assertions are on. - // Don't make this a AND term, as it's unlikely to need to test this. + // Add an internal if to check assertions are on. + // Don't make this an AND term, as it's unlikely to need to test this. FileLine* const fl = bodyp->fileline(); AstNodeExpr* const condp = assertOnCond(fl, type, directiveType); @@ -288,6 +422,30 @@ class AssertVisitor final : public VNVisitor { newp->user2(true); // Mark as an assertOn() check return newp; } + static AstNodeStmt* newIfAssertPassOn(AstNode* bodyp, VAssertDirectiveType directiveType, + VAssertType type, bool vacuous) { + // Add an internal if to check assertion passOn is enabled. + // Don't make this an AND term, as it's unlikely to need to test this. + FileLine* const fl = bodyp->fileline(); + AstNodeExpr* const condp = assertPassOnCond(fl, type, directiveType, vacuous); + AstNodeIf* const newp = new AstIf{fl, condp, bodyp}; + newp->isBoundsCheck(true); // To avoid LATCH warning + newp->user1(true); // Don't assert/cover this if + newp->user2(true); // Mark as an assertOn() check + return newp; + } + static AstNodeStmt* newIfAssertFailOn(AstNode* bodyp, VAssertDirectiveType directiveType, + VAssertType type) { + // Add an internal if to check assertion failOn is enabled. + // Don't make this an AND term, as it's unlikely to need to test this. + FileLine* const fl = bodyp->fileline(); + AstNodeExpr* const condp = assertFailOnCond(fl, type, directiveType); + AstNodeIf* const newp = new AstIf{fl, condp, bodyp}; + newp->isBoundsCheck(true); // To avoid LATCH warning + newp->user1(true); // Don't assert/cover this if + newp->user2(true); // Mark as an assertOn() check + return newp; + } static AstIf* assertCond(const AstNodeCoverOrAssert* nodep, AstNodeExpr* propp, AstNode* passsp, AstNode* failsp) { @@ -444,17 +602,26 @@ class AssertVisitor final : public VNVisitor { if (failsp) failsp->unlinkFrBackWithNext(); bool selfDestruct = false; - if (const AstCover* const snodep = VN_CAST(nodep, Cover)) { + bool passspGated = false; + const AstCover* const coverp = VN_CAST(nodep, Cover); + // A sequence event control is not an assertion directive; no assertion control + const bool seqEvent = coverp && coverp->isSeqEvent(); + if (coverp) { ++m_statCover; - if (!v3Global.opt.coverageUser()) { + if (seqEvent) { + // Keep the event-fire action, with no coverage bucket + } else if (!v3Global.opt.coverageUser()) { selfDestruct = true; } else { // V3Coverage assigned us a bucket to increment. - AstCoverInc* const covincp = VN_AS(snodep->coverincsp(), CoverInc); - UASSERT_OBJ(covincp, snodep, "Missing AstCoverInc under assertion"); + AstCoverInc* const covincp = VN_AS(coverp->coverincsp(), CoverInc); + UASSERT_OBJ(covincp, coverp, "Missing AstCoverInc under assertion"); covincp->unlinkFrBackWithNext(); // next() might have AstAssign for trace if (message != "") covincp->declp()->comment(message); if (passsp) { + passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(), + /*vacuous=*/false); + passspGated = true; passsp = AstNode::addNext(covincp, passsp); } else { passsp = covincp; @@ -475,8 +642,10 @@ class AssertVisitor final : public VNVisitor { VL_RESTORER(m_passsp); VL_RESTORER(m_failsp); + VL_RESTORER(m_assertp); m_passsp = passsp; m_failsp = failsp; + m_assertp = nodep; iterate(nodep->propp()); AstNode* propExprp; @@ -488,10 +657,20 @@ class AssertVisitor final : public VNVisitor { propExprp = nodep->propp()->unlinkFrBack(); } FileLine* const flp = nodep->fileline(); + bool passspAlreadyGated = false; + if (passsp && VN_IS(passsp, If)) passspAlreadyGated = VN_AS(passsp, If)->user1(); + if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr) + && !seqEvent) { + passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(), + /*vacuous=*/false); + } + if (failsp && !VN_IS(propExprp, PExpr)) { + failsp = newIfAssertFailOn(failsp, nodep->directive(), nodep->userType()); + } AstNode* bodysp = assertBody(nodep, propExprp, passsp, failsp); if (disablep) bodysp = new AstIf{flp, new AstLogNot{flp, disablep}, bodysp}; // Add assertOn check last, for better combining - bodysp = newIfAssertOn(bodysp, nodep->directive(), nodep->userType()); + if (!seqEvent) bodysp = newIfAssertOn(bodysp, nodep->directive(), nodep->userType()); if (sentreep) bodysp = new AstAlways{flp, VAlwaysKwd::ALWAYS, sentreep, bodysp}; if (passsp && !passsp->backp()) VL_DO_DANGLING(pushDeletep(passsp), passsp); @@ -582,8 +761,36 @@ class AssertVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); return; } + } - iterateChildren(nodep); + iterateChildren(nodep); + + if (nodep->user2()) { + // Combine consecutive assertOn checks if possible + if (AstIf* const backp = VN_CAST(nodep->backp(), If)) { + if (backp->nextp() == nodep // + && backp->user2() // + && backp->condp()->sameTree(nodep->condp())) { + ++m_statAssertOnCombined; + backp->addThensp(nodep->thensp()->unlinkFrBackWithNext()); + nodep->unlinkFrBack(); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } + // Combine nested assertOn checks if possible + if (nodep->thensp() && !nodep->thensp()->nextp() && isEmptyStmt(nodep->elsesp())) { + AstIf* const checkp = VN_CAST(nodep->thensp(), If); + if (checkp // + && checkp->user2() // + && checkp->condp()->sameTree(nodep->condp())) { + ++m_statAssertOnCombined; + nodep->addThensp(checkp->thensp()->unlinkFrBackWithNext()); + VL_DO_DANGLING(pushDeletep(checkp->unlinkFrBack()), checkp); + return; + } + } + return; } // Swap assertOn check with single statement 'if' statement to bubble up for combining @@ -611,24 +818,32 @@ class AssertVisitor final : public VNVisitor { } } } - - // Combine consecutive assertOn checks if possible - if (nodep->user2()) { - if (AstIf* const backp = VN_CAST(nodep->backp(), If)) { - if (backp->nextp() == nodep // - && backp->user2() // - && backp->condp()->sameTree(nodep->condp())) { - ++m_statAssertOnCombined; - backp->addThensp(nodep->thensp()->unlinkFrBackWithNext()); - nodep->unlinkFrBack(); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } - } - } } //========== Case assertions void visit(AstCase* nodep) override { + // Introduce temporary variable for AstCase if needed - it is done here and not in V3Case + // because this phase is before V3Scope and V3Case is not. Doing it before V3Scope ensures + // that V3Scope will take care of a scope creation + // We also need to do it before V3Begin, co that pragmas like `unique0` also work correctly + if (!nodep->exprp()->isPure()) { + ++m_statLiftedCaseExprs; + FileLine* const fl = nodep->exprp()->fileline(); + AstVar* const varp = new AstVar{fl, VVarType::BLOCKTEMP, m_caseTempNames.get(nodep), + nodep->exprp()->dtypep()}; + AstNodeExpr* const origp = nodep->exprp()->unlinkFrBack(); + nodep->addHereThisAsNext( + new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, origp}); + nodep->exprp(new AstVarRef{fl, varp, VAccess::READ}); + if (m_ftaskp) { + varp->funcLocal(true); + varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + m_ftaskp->stmtsp()->addHereThisAsNext(varp); + } else { + m_modp->stmtsp()->addHereThisAsNext(varp); + } + VIsCached::clearCacheTree(); + } iterateChildren(nodep); if (!nodep->user1SetOnce()) { bool has_default = false; @@ -807,8 +1022,11 @@ class AssertVisitor final : public VNVisitor { if (nodep->pass() && m_passsp) { // Cover adds COVERINC by AstNode::addNext, thus need to clone next too. stmtsp = m_passsp->cloneTree(true); + stmtsp = newIfAssertPassOn(stmtsp, m_assertp->directive(), m_assertp->userType(), + nodep->vacuous()); } else if (!nodep->pass() && m_failsp) { stmtsp = m_failsp->cloneTree(true); + stmtsp = newIfAssertFailOn(stmtsp, m_assertp->directive(), m_assertp->userType()); } if (stmtsp) { stmtsp->foreachAndNext([](AstNodeVarRef* const refp) { @@ -914,34 +1132,51 @@ class AssertVisitor final : public VNVisitor { visitAssertionIterate(nodep, nodep->failsp()); } void visit(AstAssertCtl* nodep) override { - if (VN_IS(m_modp, Class) || VN_IS(m_modp, Iface)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: assertcontrols in classes or interfaces"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; - } - iterateChildren(nodep); - if (!resolveAssertType(nodep)) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: non-constant assert assertion-type expression"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + bool assertTypeConst = true; + if (!nodep->assertTypesp()) { + nodep->ctlAssertTypes(VAssertType{ALL_ASSERT_TYPES}); + } else if (const AstConst* const assertTypesp = VN_CAST(nodep->assertTypesp(), Const)) { + nodep->ctlAssertTypes(VAssertType{assertTypesp->toSInt()}); + } else { + assertTypeConst = false; + } + + bool controlTypeConst = false; + if (const AstConst* const constp = VN_CAST(nodep->controlTypep(), Const)) { + nodep->ctlType(constp->toSInt()); + controlTypeConst = true; + } + if (controlTypeConst + && (nodep->ctlType() < VAssertCtlType::LOCK + || nodep->ctlType() > VAssertCtlType::VACUOUS_OFF)) { + nodep->unlinkFrBack(); + nodep->v3error("Bad $assertcontrol control_type '" + << cvtToStr(static_cast(nodep->ctlType())) + << "' (IEEE 1800-2023 Table 20-5)"); + VL_DO_DANGLING(pushDeletep(nodep), nodep); return; } - if (nodep->ctlAssertTypes() != ALL_ASSERT_TYPES - && nodep->ctlAssertTypes().containsAny(VAssertType::EXPECT | VAssertType::UNIQUE - | VAssertType::UNIQUE0 + if (assertTypeConst && nodep->ctlAssertTypes() != ALL_ASSERT_TYPES + && nodep->ctlAssertTypes().containsAny(VAssertType::UNIQUE | VAssertType::UNIQUE0 | VAssertType::PRIORITY)) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: assert control assertion_type"); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return; } - if (!resolveControlType(nodep)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: non-const assert control type expression"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; + + bool directiveTypeConst = true; + if (!nodep->directiveTypesp()) { + nodep->ctlDirectiveTypes(VAssertDirectiveType::ASSERT | VAssertDirectiveType::ASSUME + | VAssertDirectiveType::COVER); + } else if (const AstConst* const directiveTypesp + = VN_CAST(nodep->directiveTypesp(), Const)) { + nodep->ctlDirectiveTypes(VAssertDirectiveType{directiveTypesp->toSInt()}); + } else { + directiveTypeConst = false; } - if (!resolveDirectiveType(nodep)) { + if (!directiveTypeConst) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: non-const assert directive type expression"); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); @@ -949,43 +1184,18 @@ class AssertVisitor final : public VNVisitor { } FileLine* const fl = nodep->fileline(); - switch (nodep->ctlType()) { - case VAssertCtlType::ON: - UINFO(9, "Generating assertctl for a module: " << m_modp); - nodep->replaceWith( - new AstCStmt{fl, "vlSymsp->_vm_contextp__->assertOnSet("s - + std::to_string(nodep->ctlAssertTypes()) + ", "s - + std::to_string(nodep->ctlDirectiveTypes()) + ");\n"s}); - break; - case VAssertCtlType::OFF: - case VAssertCtlType::KILL: { - UINFO(9, "Generating assertctl for a module: " << m_modp); - nodep->replaceWith( - new AstCStmt{fl, "vlSymsp->_vm_contextp__->assertOnClear("s - + std::to_string(nodep->ctlAssertTypes()) + " ,"s - + std::to_string(nodep->ctlDirectiveTypes()) + ");\n"s}); - break; - } - case VAssertCtlType::LOCK: - case VAssertCtlType::UNLOCK: - case VAssertCtlType::PASS_ON: - case VAssertCtlType::PASS_OFF: - case VAssertCtlType::FAIL_ON: - case VAssertCtlType::FAIL_OFF: - case VAssertCtlType::NONVACUOUS_ON: - case VAssertCtlType::VACUOUS_OFF: { - nodep->unlinkFrBack(); - nodep->v3warn(E_UNSUPPORTED, "Unsupported: $assertcontrol control_type '" << cvtToStr( - static_cast(nodep->ctlType())) << "'"); - break; - } - default: { - nodep->unlinkFrBack(); - nodep->v3warn(EC_ERROR, "Bad $assertcontrol control_type '" - << cvtToStr(static_cast(nodep->ctlType())) - << "' (IEEE 1800-2023 Table 20-5)"); - } + UINFO(9, "Generating assertctl for a module: " << m_modp); + AstCStmt* const newp = new AstCStmt{fl}; + newp->add("vlSymsp->_vm_contextp__->assertCtl("); + newp->add(nodep->controlTypep()->unlinkFrBack()); + newp->add(", "); + if (nodep->assertTypesp()) { + newp->add(nodep->assertTypesp()->unlinkFrBack()); + } else { + newp->add(std::to_string(ALL_ASSERT_TYPES)); } + newp->add(", " + std::to_string(nodep->ctlDirectiveTypes()) + ");\n"); + nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstAssertIntrinsic* nodep) override { // @@ -1006,11 +1216,12 @@ class AssertVisitor final : public VNVisitor { VL_RESTORER(m_modp); VL_RESTORER(m_modPastNum); VL_RESTORER(m_modStrobeNum); - VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp); + VL_RESTORER(m_finalp); + VL_RESTORER_CLEAR(m_modExpr2Sen2DelayedAlwaysp); m_modp = nodep; m_modPastNum = 0; m_modStrobeNum = 0; - m_modExpr2Sen2DelayedAlwaysp.clear(); + m_finalp = nullptr; iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { @@ -1057,6 +1268,9 @@ public: V3Stats::addStat("Assertions, $past variables", m_statPastVars); V3Stats::addStat("Assertions, assertOn checks combined", m_statAssertOnCombined); V3Stats::addStat("Assertions, assertOn checks hoisted", m_statAssertOnHoisted); + V3Stats::addStat("Assertions, lifted impure case expressions", m_statLiftedCaseExprs); + // Rewrites can change purity, e.g. by compiling out assertion statements with --no-assert + VIsCached::clearCacheTree(); } }; diff --git a/src/V3Assert.h b/src/V3Assert.h index dbaac13bf..3bda2528e 100644 --- a/src/V3Assert.h +++ b/src/V3Assert.h @@ -24,6 +24,12 @@ //============================================================================ +class V3AssertCommon final { +public: + static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED; + static void lowerSequenceEvents(AstNetlist* nodep) VL_MT_DISABLED; +}; + class V3Assert final { public: static void assertAll(AstNetlist* nodep) VL_MT_DISABLED; diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index bf50e97a1..b96696cff 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -26,6 +26,7 @@ #include "V3AssertNfa.h" +#include "V3Assert.h" #include "V3Const.h" #include "V3Graph.h" #include "V3Task.h" @@ -77,6 +78,10 @@ public: AstNodeExpr* m_andRhsCondp = nullptr; // OWNED; RHS final condition (may be nullptr) // Reject sink for SAnd rejectOnFail wiring; not a state-signal source bool m_isRejectSink = false; + // In-window vertex of a strong s_always[m:n]: if its state is still set at + // end-of-simulation the universal-quantifier window never completed, which is + // a liveness failure (IEEE 1800-2023 16.12.11 strong semantics). + bool m_strongPending = false; // CONSTRUCTORS explicit SvaStateVertex(V3Graph* graphp) @@ -109,6 +114,9 @@ public: // Reject when source is active and condp is false; set only on // outermost required-step Link bool m_rejectOnFail = false; + // Optional dynamic condition vertex for m_rejectOnFail. Used when the + // success condition is another NFA state rather than a static expression. + SvaStateVertex* m_condVtxp = nullptr; // CONSTRUCTORS SvaTransEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, AstNodeExpr* condp, @@ -176,6 +184,10 @@ struct BuildResult final { // Mid-window sources for range delays (pure boolean RHS): match-only (isUnbounded) std::vector midSources; bool errorEmitted = false; // Builder already emitted specific error; skip generic + // For cover_sequence: when true, midSources already enumerate every + // end-of-match, so wireMatchAndMidSources must NOT add the main + // termVtxp -> matchVertex Link (would double-count via the merge vertex). + bool termIsMidMerge = false; bool valid() const { return termVertexp != nullptr; } static BuildResult fail(bool errored = false) { return {nullptr, nullptr, {}, errored}; } static BuildResult failWithError() { return {nullptr, nullptr, {}, true}; } @@ -186,6 +198,78 @@ static AstNodeExpr* sampled(AstNodeExpr* exprp) { return sp; } +static string assertCtlGetCall(const char* query, VAssertType type, + VAssertDirectiveType directiveType) { + return "vlSymsp->_vm_contextp__->assertCtlGet(VerilatedAssertCtlQuery::"s + query + ", "s + + std::to_string(type) + ", "s + std::to_string(directiveType) + ")"s; +} + +static const char* assertPassOnQuery(bool vacuous) { + static constexpr const char* queries[2] + = {"ASSERT_CTL_PASS_ON_NONVACUOUS", "ASSERT_CTL_PASS_ON_VACUOUS"}; + return queries[vacuous]; +} + +static AstNodeExpr* assertOnCond(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType) { + if (!v3Global.opt.assertOn()) { return new AstConst{flp, AstConst::BitFalse{}}; } + return new AstCExpr{flp, AstCExpr::Pure{}, + assertCtlGetCall("ASSERT_CTL_ON", type, directiveType), 1}; +} + +static AstNodeExpr* assertKillGet(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType) { + return new AstCExpr{flp, AstCExpr::Pure{}, + assertCtlGetCall("ASSERT_CTL_KILL", type, directiveType), 32}; +} + +static string assertActionControlPrefix(VAssertDirectiveType directiveType) { + const int controlled = !!(static_cast(directiveType) + & (static_cast(VAssertDirectiveType::ASSERT) + | static_cast(VAssertDirectiveType::COVER) + | static_cast(VAssertDirectiveType::ASSUME))); + const int checkRuntime = controlled & static_cast(v3Global.opt.assertOn()); + return "("s + std::to_string(controlled ^ 1) + " || ("s + std::to_string(checkRuntime) + + " && "s; +} + +static AstNodeExpr* assertPassOnCond(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType, bool vacuous) { + return new AstCExpr{flp, AstCExpr::Pure{}, + assertActionControlPrefix(directiveType) + + assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType) + + "))"s, + 1}; +} + +static AstNodeExpr* assertFailOnCond(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType) { + return new AstCExpr{flp, AstCExpr::Pure{}, + assertActionControlPrefix(directiveType) + + assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType) + "))"s, + 1}; +} + +static AstIf* newPassOnIf(FileLine* flp, AstNodeExpr* firep, AstNode* bodyp, VAssertType type, + VAssertDirectiveType directiveType, bool vacuous) { + AstNodeExpr* const condp + = new AstLogAnd{flp, firep, assertPassOnCond(flp, type, directiveType, vacuous)}; + AstIf* const ifp = new AstIf{flp, condp, bodyp}; + ifp->isBoundsCheck(true); + ifp->user1(true); + return ifp; +} + +static AstNodeStmt* newIfAssertFailOn(AstNode* bodyp, VAssertDirectiveType directiveType, + VAssertType type) { + FileLine* const flp = bodyp->fileline(); + AstNodeExpr* const condp = assertFailOnCond(flp, type, directiveType); + AstIf* const ifp = new AstIf{flp, condp, bodyp}; + ifp->isBoundsCheck(true); + ifp->user1(true); + return ifp; +} + //###################################################################### // NFA Builder @@ -198,6 +282,28 @@ class SvaNfaBuilder final { // (IEEE 1800-2023 16.12.14 outer-wraps-inner). std::vector m_outerAbortStack; bool m_inUnboundedScope = false; // Sticky: nodes created after inherit liveness + bool m_markStrongPending = false; // Mark new vertices as strong s_always in-window + // IEEE 1800-2023 16.14.3 cover sequence: each end-of-match fires the action, + // not just the first. Builder builds parallel-branch (no first-match-wins) + // topology when true. Default false preserves cover_property semantics. + bool m_isCoverSeq = false; + // Unsupported endpoint topology must reject, not ignore, or the wait hangs + bool m_isSeqEvent = false; + + struct RangeDelayRejectInfo final { + SvaStateVertex* startp = nullptr; + int range = 0; + int rhsLen = 0; + }; + + void warnEndpointUnsupported(FileLine* flp, const string& what) const { + if (m_isSeqEvent) { + flp->v3warn(E_UNSUPPORTED, + "Unsupported: sequence used as an event control with " << what); + } else { + flp->v3warn(COVERIGN, "Ignoring unsupported: cover sequence with " << what); + } + } AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) { if (m_throughoutStack.empty()) return baseCondp; @@ -289,6 +395,74 @@ class SvaNfaBuilder final { return 0; } + // Contiguous match-length range [lo,hi] for an operand whose length varies + // from AT MOST ONE ranged cycle delay; {-1,-1} otherwise. Drives the + // variable-length `intersect` lowering (IEEE 1800-2023 16.9.6): more than + // one ranged delay would make the per-length realization ambiguous, so it + // is reported unsupported rather than mis-paired. + static std::pair lengthRange(AstNodeExpr* nodep) { + if (AstSExpr* const sexprp = VN_CAST(nodep, SExpr)) { + AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay); + if (!delayp || !delayp->isCycleDelay()) return {-1, -1}; + std::pair delayRange; + if (delayp->isRangeDelay()) { + if (delayp->isUnbounded()) return {-1, -1}; + const int minD = getConstInt(delayp->lhsp()); + const int maxD = getConstInt(delayp->rhsp()); + if (minD < 0 || maxD < 0 || maxD < minD) return {-1, -1}; + delayRange = {minD, maxD}; + } else { + const int d = getConstInt(delayp->lhsp()); + if (d < 0) return {-1, -1}; + delayRange = {d, d}; + } + std::pair preRange{0, 0}; + if (AstNodeExpr* const prep = sexprp->preExprp()) { + preRange = lengthRange(prep); + if (preRange.first < 0) return {-1, -1}; + } + const std::pair bodyRange = lengthRange(sexprp->exprp()); + if (bodyRange.first < 0) return {-1, -1}; + const int variableParts = (preRange.first != preRange.second) + + (delayRange.first != delayRange.second) + + (bodyRange.first != bodyRange.second); + if (variableParts > 1) return {-1, -1}; + return {preRange.first + delayRange.first + bodyRange.first, + preRange.second + delayRange.second + bodyRange.second}; + } + if (AstSThroughout* const throughp = VN_CAST(nodep, SThroughout)) { + return lengthRange(throughp->rhsp()); + } + if (nodep->isMultiCycleSva()) return {-1, -1}; + return {0, 0}; // plain boolean -- 0 cycles + } + + // Clone `operand` with its sole variable ranged cycle delay pinned so the + // total match length is exactly `len`. `lo` is the operand's minimum length + // (lengthRange().first). A fixed operand has no such delay and is returned + // as a plain clone (callers only request its single achievable length). + static AstNodeExpr* realizeAtLength(AstNodeExpr* operand, int len, int lo) { + AstNodeExpr* const clonep = operand->cloneTreePure(false); + AstDelay* rangeDelayp = nullptr; + clonep->foreach([&](AstDelay* dp) { + if (!rangeDelayp && dp->isRangeDelay() && !dp->isUnbounded() + && getConstInt(dp->lhsp()) != getConstInt(dp->rhsp())) { + rangeDelayp = dp; + } + }); + if (rangeDelayp) { + FileLine* const flp = rangeDelayp->fileline(); + const int pinned = getConstInt(rangeDelayp->lhsp()) + (len - lo); + AstNodeExpr* const oldMinp = rangeDelayp->lhsp(); + oldMinp->replaceWith(new AstConst{flp, static_cast(pinned)}); + VL_DO_DANGLING(oldMinp->deleteTree(), oldMinp); + // Drop the max bound so it lowers as a fixed `##d`, not `##[d:d]`. + AstNode* const oldMaxp = rangeDelayp->rhsp()->unlinkFrBack(); + VL_DO_DANGLING(oldMaxp->deleteTree(), oldMaxp); + } + return clonep; + } + // Cuts AST size from O(N * sizeof(exprp)) to O(N) + O(sizeof(exprp)) by // sharing a single `VarRef` across N check edges. Hoist also matches the // IEEE 1800-2023 16.9.9 "single preponed-region snapshot" semantic for @@ -298,8 +472,8 @@ class SvaNfaBuilder final { AstVar* tryHoistSampled(AstNodeExpr* exprp, FileLine* flp, int cloneCount) { constexpr int kHoistThreshold = 2; if (cloneCount < kHoistThreshold) return nullptr; - AstVar* const tempVarp = new AstVar{flp, VVarType::MODULETEMP, m_propTempNames.get(exprp), - m_modp->findBitDType()}; + AstVar* const tempVarp + = new AstVar{flp, VVarType::MODULETEMP, m_propTempNames.get(exprp), exprp->dtypep()}; m_modp->addStmtsp(tempVarp); AstAssign* const assignp = new AstAssign{flp, new AstVarRef{flp, tempVarp, VAccess::WRITE}, sampled(exprp->cloneTreePure(false))}; @@ -330,6 +504,7 @@ class SvaNfaBuilder final { vtxp->m_throughoutConds.push_back(cp->cloneTreePure(false)); } if (m_inUnboundedScope) vtxp->m_isUnbounded = true; + if (m_markStrongPending) vtxp->m_strongPending = true; return vtxp; } @@ -365,7 +540,7 @@ class SvaNfaBuilder final { // failure, sets outErrorEmitted per semantic-error policy and returns false. bool applyRangeDelay(AstDelay* delayp, AstNodeExpr* rhsExprp, SvaStateVertex*& currentp, std::vector& midSources, FileLine* flp, - bool& outErrorEmitted) { + bool& outErrorEmitted, RangeDelayRejectInfo* rangeRejectInfop = nullptr) { const int minDelay = getConstInt(delayp->lhsp()); if (minDelay < 0) { delayp->v3error("Range delay minimum is not a non-negative elaboration-time constant" @@ -405,6 +580,15 @@ class SvaNfaBuilder final { // count is O(1) in range regardless of user input; no adversarial N // blowup is possible. constexpr int kChainLimit = 256; + // IEEE 1800-2023 16.14.3: only a small bounded range before a plain + // boolean enumerates every end-of-match below. The counter FSM drops + // overlapping ends and the nested-sequence merge collapses them, so + // reject those for a cover sequence rather than under-count. + if (m_isCoverSeq && (range > kChainLimit || VN_IS(rhsExprp, SExpr))) { + warnEndpointUnsupported(flp, "this ranged cycle delay"); + outErrorEmitted = true; + return false; + } if (range > kChainLimit) { // Large range: counter FSM. Overlapping triggers during an active // count are dropped (non-overlapping semantics only). @@ -414,8 +598,14 @@ class SvaNfaBuilder final { guardedEdge(currentp, counterVtxp, flp); currentp = counterVtxp; } else if (VN_IS(rhsExprp, SExpr)) { - // Nested-SExpr RHS: merge all [M,N] positions; continuation is per-attempt. + // Nested-SExpr RHS: merge all [M,N] positions. Candidate-local misses + // are not assertion rejects while a later position can still match. + if (rangeRejectInfop) { + const int rhsLen = fixedLength(rhsExprp); + if (rhsLen >= 0) *rangeRejectInfop = {currentp, range, rhsLen}; + } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); + mergeVtxp->m_isUnbounded = true; guardedLink(currentp, mergeVtxp, flp); for (int i = 0; i < range; ++i) { SvaStateVertex* const nextVtxp = scopedCreateVertex(); @@ -424,16 +614,25 @@ class SvaNfaBuilder final { currentp = nextVtxp; } currentp = mergeVtxp; + m_inUnboundedScope = true; } else { // Pure boolean RHS: register chain. Each mid-position links to // match (match-only); last position is the reject source. - AstVar* const hoistVarp = tryHoistSampled(rhsExprp, flp, range); + // For cover_sequence (IEEE 1800-2023 16.14.3) the advance edge is + // unconditional so every (start, end) pair fires independently -- + // dropping NOT(b) turns "first-match-wins" into "every end fires". + AstVar* const hoistVarp + = m_isCoverSeq ? nullptr : tryHoistSampled(rhsExprp, flp, range); midSources.push_back(currentp); for (int i = 0; i < range; ++i) { SvaStateVertex* const nextVtxp = scopedCreateVertex(); - AstNodeExpr* const notExprp - = new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)}; - guardedEdge(currentp, nextVtxp, notExprp, flp); + if (m_isCoverSeq) { + guardedEdge(currentp, nextVtxp, flp); + } else { + AstNodeExpr* const notExprp + = new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)}; + guardedEdge(currentp, nextVtxp, notExprp, flp); + } if (i < range - 1) midSources.push_back(nextVtxp); currentp = nextVtxp; } @@ -441,12 +640,44 @@ class SvaNfaBuilder final { return true; } + void addFiniteRangeReject(const RangeDelayRejectInfo& info, const BuildResult& result, + FileLine* flp) { + if (!info.startp) return; + + SvaStateVertex* const expiryVtxp + = addDelayChain(info.startp, info.range + info.rhsLen, flp); + SvaStateVertex* const expiryMatchp = scopedCreateVertex(); + std::vector sources = result.midSources; + sources.push_back(result.termVertexp); + for (SvaStateVertex* const srcp : sources) { + AstNodeExpr* const condp + = result.finalCondp ? sampled(result.finalCondp->cloneTreePure(false)) : nullptr; + SvaStateVertex* const successNowp = scopedCreateVertex(); + guardedLink(srcp, successNowp, condp, flp); + SvaStateVertex* stagep = successNowp; + guardedLink(stagep, expiryMatchp, flp); + for (int i = 0; i < info.range; ++i) { + SvaStateVertex* const nextp = scopedCreateVertex(); + guardedEdge(stagep, nextp, flp); + stagep = nextp; + guardedLink(stagep, expiryMatchp, flp); + } + } + + SvaStateVertex* const sinkVtxp = m_graph.createStateVertex(); + sinkVtxp->m_isRejectSink = true; + SvaTransEdge* const rejectp = m_graph.addLink(expiryVtxp, sinkVtxp); + rejectp->m_rejectOnFail = true; + rejectp->m_condVtxp = expiryMatchp; + } + BuildResult buildSExpr(AstSExpr* sexprp, SvaStateVertex* entryVtxp, bool isTopLevelStep = false) { AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay); if (!delayp || !delayp->isCycleDelay()) return BuildResult::fail(); FileLine* const flp = sexprp->fileline(); + AstNodeExpr* const exprp = sexprp->exprp(); // Handle LHS (preExpr) SvaStateVertex* currentp = entryVtxp; @@ -469,10 +700,12 @@ class SvaNfaBuilder final { // Handle delay std::vector rangeMidSources; + RangeDelayRejectInfo rangeRejectInfo; + const bool addRangeReject = isTopLevelStep && !m_inUnboundedScope; if (delayp->isRangeDelay()) { bool errorEmitted = false; if (!applyRangeDelay(delayp, sexprp->exprp(), currentp, rangeMidSources, flp, - errorEmitted)) { + errorEmitted, addRangeReject ? &rangeRejectInfo : nullptr)) { return BuildResult::fail(errorEmitted); } } else { @@ -487,8 +720,11 @@ class SvaNfaBuilder final { } // Multi-cycle RHS: recurse (only plain boolean is returned as finalCondp). - AstNodeExpr* const exprp = sexprp->exprp(); - if (exprp->isMultiCycleSva()) return buildExpr(exprp, currentp, isTopLevelStep); + if (exprp->isMultiCycleSva()) { + const BuildResult result = buildExpr(exprp, currentp, isTopLevelStep); + if (result.valid()) addFiniteRangeReject(rangeRejectInfo, result, flp); + return result; + } return {currentp, exprp, std::move(rangeMidSources)}; } @@ -516,6 +752,10 @@ class SvaNfaBuilder final { if (exceedsAssertUnrollLimit(repp, totalSites)) return BuildResult::failWithError(); AstVar* const hoistVarp = tryHoistSampled(exprp, flp, totalSites); + // Cover-sequence (IEEE 1800-2023 16.14.3): collect each end-of-match + // position so they all fire the action, not just the merged terminal. + std::vector consMidSources; + SvaStateVertex* currentp = entryVtxp; for (int i = 0; i < minN; ++i) { if (i > 0) { @@ -531,6 +771,10 @@ class SvaNfaBuilder final { if (isTopLevelStep && (i == 0 || i == minN - 1)) { linkp->m_rejectOnFail = true; } currentp = condVtxp; } + // After minN: currentp is the first valid end-of-match position for [*m:n]. + if (m_isCoverSeq && (repp->unbounded() || repp->maxCountp())) { + consMidSources.push_back(currentp); + } if (repp->unbounded()) { if (minN == 0) { @@ -564,11 +808,19 @@ class SvaNfaBuilder final { guardedLink(nextVtxp, checkVtxp, sampledRefOrClone(hoistVarp, exprp, flp), flp); guardedLink(checkVtxp, mergeVtxp, flp); currentp = checkVtxp; + if (m_isCoverSeq) consMidSources.push_back(checkVtxp); } currentp = mergeVtxp; } // finalCond = nullptr (already checked via Links) - return {currentp, nullptr, {}}; + BuildResult res; + res.termVertexp = currentp; + res.finalCondp = nullptr; + res.midSources = std::move(consMidSources); + // mergeVtxp is the OR of all the end-positions we already pushed to + // midSources, so the main termVtxp -> matchVertex Link would duplicate. + res.termIsMidMerge = m_isCoverSeq && !res.midSources.empty(); + return res; } // always[lo:hi] / s_always[lo:hi] (IEEE 1800-2023 16.12.11). @@ -577,10 +829,37 @@ class SvaNfaBuilder final { FileLine* const flp = nodep->fileline(); AstNodeExpr* const propp = nodep->propp(); const int lo = getConstInt(nodep->loBoundp()); + if (VN_IS(nodep->hiBoundp(), Unbounded)) { + // Weak always [lo:$]: unbounded upper bound (IEEE 1800-2023 16.12.11). + // p must hold at every clock tick at least lo cycles after the attempt + // start; those ticks are not required to exist, so there is no + // end-of-trace obligation (weak). The self-loop keeps the attempt live + // every cycle; each observed cycle is a safety obligation, so a false p + // rejects immediately. + UASSERT_OBJ(!nodep->isStrong() && lo >= 0, nodep, + "Unbounded always must be weak with non-negative lo (V3Width)"); + SvaStateVertex* const livep = addDelayChain(entryVtxp, lo, flp); + livep->m_isUnbounded = true; + guardedEdge(livep, livep, flp); // stay active every subsequent cycle + SvaStateVertex* const sinkp = m_graph.createStateVertex(); + sinkp->m_isRejectSink = true; + SvaTransEdge* const rejEdgep + = guardedLink(livep, sinkp, sampled(propp->cloneTreePure(false)), flp); + if (isTopLevelStep) rejEdgep->m_rejectOnFail = true; + return {livep, nullptr, {}}; + } const int hi = getConstInt(nodep->hiBoundp()); UASSERT_OBJ(lo >= 0 && hi >= lo, nodep, "PropAlways bounds invariant (V3Width)"); if (exceedsAssertUnrollLimit(nodep, hi - lo + 1)) return BuildResult::failWithError(); AstVar* const hoistVarp = tryHoistSampled(propp, flp, hi - lo + 1); + // Strong s_always[m:n]: mark every in-window registered vertex so an + // attempt still mid-window at end-of-simulation is reported as a liveness + // failure (IEEE strong: the n+1 ticks must exist). An attempt that has + // completed earlier in the trace has already cleared its state, so it is + // not flagged; an attempt whose final tick coincides with $finish is still + // flagged, matching the strong reference. Weak always[m:n] is not marked. + VL_RESTORER(m_markStrongPending); + m_markStrongPending = nodep->isStrong(); SvaStateVertex* currentp = addDelayChain(entryVtxp, lo, flp); for (int k = 0; k <= hi - lo; ++k) { if (k > 0) { @@ -607,6 +886,15 @@ class SvaNfaBuilder final { UASSERT_OBJ(maxN >= minN, repp, "GotoRep range max < min (V3Width invariant)"); if (exceedsAssertUnrollLimit(repp, maxN)) return BuildResult::failWithError(); + // IEEE 1800-2023 16.14.3: a ranged goto repetition b[->M:N] ends at every + // M..N-th match, but only the shared merge vertex below reaches the + // terminal, so a cover sequence would under-count. Reject the ranged form + // (the single-count b[->N] has one end and is enumerated correctly). + if (m_isCoverSeq && hasMax && maxN > minN) { + warnEndpointUnsupported(flp, "a ranged goto repetition"); + return BuildResult::failWithError(); + } + // Wait + match per iter -> 2 sites per iteration; range form needs // sites for every iteration in [0..maxN). NOT($sampled(x)) matches // $sampled(NOT(x)) at the value level (IEEE 1800-2023 16.9.9); @@ -661,6 +949,15 @@ class SvaNfaBuilder final { if (!lhs.valid() || !rhs.valid()) { // LCOV_EXCL_START -- sub-build fail bail return BuildResult::fail(lhs.errorEmitted || rhs.errorEmitted); } // LCOV_EXCL_STOP + // IEEE 1800-2023 16.14.3: a cover sequence counts every end-of-match. A + // sequence operand of 'or' can end more than once, but only its final + // end reaches the merge vertex below, so reject sequence operands rather + // than under-count. Plain boolean disjunction has one end per cycle and + // is handled by the OR-fold. + if (m_isCoverSeq && (lhs.termVertexp != entryVtxp || rhs.termVertexp != entryVtxp)) { + warnEndpointUnsupported(flp, "a sequence operand of 'or'"); + return BuildResult::failWithError(); + } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); if (lhs.finalCondp) { guardedLink(lhs.termVertexp, mergeVtxp, sampled(lhs.finalCondp->cloneTreePure(false)), @@ -807,6 +1104,188 @@ class SvaNfaBuilder final { return result; } + // Collect the boolean leaf checks of a fixed-length sequence keyed by their + // clock offset from the start. Returns false for anything other than nested + // AstSExpr with fixed cycle delays over boolean leaves (e.g. throughout). + static bool flattenFixedSeq(AstNodeExpr* nodep, int baseOffset, + std::map>& out) { + if (AstSExpr* const sexprp = VN_CAST(nodep, SExpr)) { + AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay); + if (!delayp || !delayp->isCycleDelay() || delayp->isUnbounded()) return false; + const int delayCycles = getConstInt(delayp->lhsp()); + if (delayCycles < 0) return false; + if (delayp->isRangeDelay() && getConstInt(delayp->rhsp()) != delayCycles) return false; + int preLen = 0; + if (AstNodeExpr* const prep = sexprp->preExprp()) { + if (!flattenFixedSeq(prep, baseOffset, out)) return false; + preLen = fixedLength(prep); + if (preLen < 0) return false; + } + return flattenFixedSeq(sexprp->exprp(), baseOffset + preLen + delayCycles, out); + } + if (nodep->isMultiCycleSva()) return false; + out[baseOffset].push_back(nodep); + return true; + } + + // Conjoin two equal-length fixed sequences into one: at each clock offset + // AND the boolean checks of both operands (IEEE 1800-2023 16.9.6 -- both + // operands match the same window). Returns null if either operand is not a + // plain fixed sequence of boolean leaves. + static AstNodeExpr* conjoinFixedSeqs(AstNodeExpr* lhsp, AstNodeExpr* rhsp, FileLine* flp) { + std::map> checks; + if (!flattenFixedSeq(lhsp, 0, checks) || !flattenFixedSeq(rhsp, 0, checks)) return nullptr; + if (checks.empty()) return nullptr; + AstNodeExpr* resultp = nullptr; + int prevOffset = 0; + for (const auto& offsetChecks : checks) { + const int offset = offsetChecks.first; + AstNodeExpr* condp = nullptr; + for (AstNodeExpr* const leafp : offsetChecks.second) { + AstNodeExpr* const clonep = leafp->cloneTreePure(false); + if (!condp) { + condp = clonep; + } else { + condp = new AstLogAnd{flp, condp, clonep}; + condp->dtypeSetBit(); + } + } + if (!resultp) { + if (offset > 0) { + AstDelay* const delayp = new AstDelay{ + flp, new AstConst{flp, static_cast(offset)}, /*isCycle=*/true}; + resultp + = new AstSExpr{flp, new AstConst{flp, AstConst::BitTrue{}}, delayp, condp}; + resultp->dtypeSetBit(); + } else { + resultp = condp; + } + } else { + AstDelay* const delayp = new AstDelay{ + flp, new AstConst{flp, static_cast(offset - prevOffset)}, + /*isCycle=*/true}; + resultp = new AstSExpr{flp, resultp, delayp, condp}; + resultp->dtypeSetBit(); + } + prevOffset = offset; + } + return resultp; + } + + // `seq` is a simple ranged sequence `start ##[m:n] end` (start/end boolean, + // start may be absent). Used to collapse a both-variable intersect to one + // ranged delay. + struct SimpleRanged final { + bool ok = false; + AstNodeExpr* startp = nullptr; // may be null (absent start) + AstNodeExpr* endp = nullptr; + }; + static SimpleRanged asSimpleRanged(AstNodeExpr* nodep) { + AstSExpr* const sexprp = VN_CAST(nodep, SExpr); + if (!sexprp) return {}; + AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay); + if (!delayp || !delayp->isCycleDelay() || !delayp->isRangeDelay() || delayp->isUnbounded()) + return {}; + if (getConstInt(delayp->lhsp()) == getConstInt(delayp->rhsp())) return {}; + AstNodeExpr* const prep = sexprp->preExprp(); + if (prep && fixedLength(prep) != 0) return {}; + if (fixedLength(sexprp->exprp()) != 0) return {}; + return {true, prep, sexprp->exprp()}; + } + + // Build the NFA for a synthesized intersect lowering tree, then free it. + // buildExpr returns the terminal condition (finalCondp) by reference into the + // tree; detach a clone so the tree can be freed here. The graph already holds + // clones/hoists of every edge condition, so nothing else dangles. + BuildResult buildFromLoweringTree(AstNodeExpr* treep, SvaStateVertex* entryVtxp, + bool isTopLevelStep) { + BuildResult result = buildExpr(treep, entryVtxp, isTopLevelStep); + if (result.valid() && result.finalCondp) { + result.finalCondp = result.finalCondp->cloneTreePure(false); + } + VL_DO_DANGLING(treep->deleteTree(), treep); + return result; + } + + // Empty common-length intersection -- unequal fixed lengths, or disjoint + // ranged lengths. IEEE 1800-2023 16.9.6 requires both operands to match + // over a window of the same length, so with no common length the intersect + // simply never matches. This is legal (matching nothing), not an error, so + // lower to a constant false rather than rejecting legal code. + BuildResult buildNeverMatchIntersect(AstNodeExpr* nodep, SvaStateVertex* entryVtxp, + bool isTopLevelStep) { + AstNodeExpr* const falsep = new AstConst{nodep->fileline(), AstConst::BitFalse{}}; + return buildFromLoweringTree(falsep, entryVtxp, isTopLevelStep); + } + + // Lower `seq1 intersect seq2` when an operand's match length varies + // (IEEE 1800-2023 16.9.6: both match over one window, equal start and end). + // The common length range is [lo,hi] = intersection of the two operands' + // achievable lengths. The equal-length combiner is avoided -- it mis-handles + // operands with an internal boolean check -- by lowering to plain sequences: + // - lo == hi (one shared length, e.g. one fixed + one ranged operand): + // pin each operand to that length and conjoin them cycle-by-cycle into a + // single fixed sequence. + // - lo < hi with simple `bool ##[m:n] bool` operands: collapse to one + // ranged delay `(start1 & start2) ##[lo:hi] (end1 & end2)`. (An OR of + // per-length branches cannot reject correctly -- a single missed length + // would fail the whole intersect, cf. Lesson 48.) + // - otherwise unsupported (clean error, not the legacy fall-through crash). + BuildResult buildVarLenIntersect(AstSIntersect* nodep, SvaStateVertex* entryVtxp, + bool isTopLevelStep) { + const std::pair lhsRange = lengthRange(nodep->lhsp()); + const std::pair rhsRange = lengthRange(nodep->rhsp()); + if (lhsRange.first < 0 || rhsRange.first < 0) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: intersect with this variable-length operand"); + return BuildResult::failWithError(); + } + const int lo = std::max(lhsRange.first, rhsRange.first); + const int hi = std::min(lhsRange.second, rhsRange.second); + if (lo > hi) { + // Disjoint length ranges share no common length -> never matches. + return buildNeverMatchIntersect(nodep, entryVtxp, isTopLevelStep); + } + FileLine* const flp = nodep->fileline(); + if (lo == hi) { + AstNodeExpr* const lp = realizeAtLength(nodep->lhsp(), lo, lhsRange.first); + AstNodeExpr* const rp = realizeAtLength(nodep->rhsp(), lo, rhsRange.first); + AstNodeExpr* const conjp = conjoinFixedSeqs(lp, rp, flp); + VL_DO_DANGLING(lp->deleteTree(), lp); + VL_DO_DANGLING(rp->deleteTree(), rp); + if (!conjp) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: intersect operand is not a plain boolean sequence"); + return BuildResult::failWithError(); + } + return buildFromLoweringTree(conjp, entryVtxp, isTopLevelStep); + } + const SimpleRanged sl = asSimpleRanged(nodep->lhsp()); + const SimpleRanged sr = asSimpleRanged(nodep->rhsp()); + if (!sl.ok || !sr.ok) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: intersect of two sequences that each vary in length over a" + " range with internal structure"); + return BuildResult::failWithError(); + } + const auto andBool = [&](AstNodeExpr* ap, AstNodeExpr* bp) -> AstNodeExpr* { + AstNodeExpr* const aClonep + = ap ? ap->cloneTreePure(false) : new AstConst{flp, AstConst::BitTrue{}}; + AstNodeExpr* const bClonep + = bp ? bp->cloneTreePure(false) : new AstConst{flp, AstConst::BitTrue{}}; + AstLogAnd* const andp = new AstLogAnd{flp, aClonep, bClonep}; + andp->dtypeSetBit(); + return andp; + }; + AstDelay* const delayp = new AstDelay{flp, new AstConst{flp, static_cast(lo)}, + /*isCycle=*/true}; + delayp->rhsp(new AstConst{flp, static_cast(hi)}); + AstSExpr* const reducedp + = new AstSExpr{flp, andBool(sl.startp, sr.startp), delayp, andBool(sl.endp, sr.endp)}; + reducedp->dtypeSetBit(); + return buildFromLoweringTree(reducedp, entryVtxp, isTopLevelStep); + } + BuildResult buildThroughout(AstSThroughout* nodep, SvaStateVertex* entryVtxp, bool isTopLevelStep = false) { // Mark entryVtxp so "cond false at tick 0" is detected as throughout-drop. @@ -970,10 +1449,13 @@ class SvaNfaBuilder final { } public: - SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames) + SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames, + bool isCoverSeq = false, bool isSeqEvent = false) : m_graph{graph} , m_modp{modp} - , m_propTempNames{propTempNames} {} + , m_propTempNames{propTempNames} + , m_isCoverSeq{isCoverSeq} + , m_isSeqEvent{isSeqEvent} {} // Reset scope between antecedent and consequent: liveness must not leak. void resetScope() { @@ -1009,18 +1491,26 @@ public: return buildAndCombiner(andp->lhsp(), andp->rhsp(), entryVtxp, andp->fileline()); } if (AstSIntersect* const intp = VN_CAST(nodep, SIntersect)) { - // IEEE 1800-2023 16.9.6: SAnd with equal-length constraint. - // Variable-length intersect deferred to follow-up. + // IEEE 1800-2023 16.9.6: both operands match over one window with + // equal start and end (equal length). Lower to a single sequence + // that conjoins both operands' per-cycle checks -- correct under + // concurrent attempts, where the done-latch combiner conflates the + // two operands' start times and over-accepts. The combiner remains + // only as a fallback for operands that do not flatten. const int lhsLen = fixedLength(intp->lhsp()); const int rhsLen = fixedLength(intp->rhsp()); - if (lhsLen < 0 || rhsLen < 0) return BuildResult::fail(); - if (lhsLen != rhsLen) { - intp->v3error("Intersect sequence length mismatch: left " + std::to_string(lhsLen) - + " cycles, right " + std::to_string(rhsLen) - + " cycles (IEEE 1800-2023 16.9.6)"); - return BuildResult::failWithError(); + if (lhsLen >= 0 && rhsLen >= 0) { + if (lhsLen != rhsLen) { + // Unequal fixed lengths share no common length -> never matches. + return buildNeverMatchIntersect(intp, entryVtxp, isTopLevelStep); + } + if (AstNodeExpr* const conjp + = conjoinFixedSeqs(intp->lhsp(), intp->rhsp(), intp->fileline())) { + return buildFromLoweringTree(conjp, entryVtxp, isTopLevelStep); + } + return buildAndCombiner(intp->lhsp(), intp->rhsp(), entryVtxp, intp->fileline()); } - return buildAndCombiner(intp->lhsp(), intp->rhsp(), entryVtxp, intp->fileline()); + return buildVarLenIntersect(intp, entryVtxp, isTopLevelStep); } if (AstSWithin* const withinp = VN_CAST(nodep, SWithin)) { return buildSWithin(withinp, entryVtxp, isTopLevelStep); @@ -1129,6 +1619,9 @@ class SvaNfaLowering final { AstNodeExpr* matchCondp; // Final boolean match condition (may be nullptr) AstVar* disableCntVarp; // disable counter var (may be nullptr) AstVar* snapshotVarp; // disable snapshot var (may be nullptr) + VAssertType assertType; // Assertion type for control tasks + VAssertDirectiveType directiveType; // Directive type for control tasks + AstVar* killVarp; // Last observed kill generation SvaGraph& graph; // NFA graph }; @@ -1147,6 +1640,15 @@ class SvaNfaLowering final { if (!ap) return bp; return new AstLogOr{flp, ap, bp}; } + static AstNodeExpr* killActive(LowerCtx& c) { + return new AstNeq{c.flp, new AstVarRef{c.flp, c.killVarp, VAccess::READ}, + assertKillGet(c.flp, c.assertType, c.directiveType)}; + } + static AstNodeExpr* notKillActive(LowerCtx& c) { return new AstLogNot{c.flp, killActive(c)}; } + static AstNodeExpr* gateNotKill(LowerCtx& c, AstNodeExpr* exprp) { + if (!exprp) return nullptr; + return new AstLogAnd{c.flp, exprp, notKillActive(c)}; + } // Phase 3 output signals struct SignalSet final { @@ -1186,6 +1688,7 @@ class SvaNfaLowering final { UASSERT_OBJ(nextStatep, c.vtx[i], "Registered vertex has no clocked incoming contribution"); + nextStatep = gateNotKill(c, nextStatep); AstAssignDly* const assignp = new AstAssignDly{ c.flp, new AstVarRef{c.flp, c.vtx[i]->datap()->stateVarp, VAccess::WRITE}, @@ -1256,7 +1759,8 @@ class SvaNfaLowering final { = new AstEq{c.flp, new AstVarRef{c.flp, cntp, VAccess::READ}, new AstConst{c.flp, AstConst::WidthedValue{}, 32, counterMax}}; - AstNodeExpr* const donep = new AstLogOr{c.flp, matchedNowp, counterAtEndp}; + AstNodeExpr* const donep = new AstLogOr{ + c.flp, killActive(c), new AstLogOr{c.flp, matchedNowp, counterAtEndp}}; AstAssignDly* const clearActivep = new AstAssignDly{c.flp, new AstVarRef{c.flp, activep, VAccess::WRITE}, @@ -1276,7 +1780,8 @@ class SvaNfaLowering final { = new AstAssignDly{c.flp, new AstVarRef{c.flp, cntp, VAccess::WRITE}, new AstConst{c.flp, AstConst::WidthedValue{}, 32, 0u}}; setActivep->addNext(resetCountp); - AstIf* const startIfp = new AstIf{c.flp, incomingp, setActivep, nullptr}; + AstIf* const startIfp + = new AstIf{c.flp, gateNotKill(c, incomingp), setActivep, nullptr}; AstIf* const topIfp = new AstIf{c.flp, new AstVarRef{c.flp, activep, VAccess::READ}, doneIfp, startIfp}; @@ -1335,19 +1840,31 @@ class SvaNfaLowering final { AstIf* const setRIfp = new AstIf{c.flp, gateRp, setRp, nullptr}; setLIfp->addNext(setRIfp); - AstIf* const topp = new AstIf{ - c.flp, c.vtx[ai]->datap()->stateSigp->cloneTreePure(false), clearLp, setLIfp}; + AstNodeExpr* const clearCondp = new AstLogOr{ + c.flp, killActive(c), c.vtx[ai]->datap()->stateSigp->cloneTreePure(false)}; + AstIf* const topp = new AstIf{c.flp, clearCondp, clearLp, setLIfp}; m_modp->addStmtsp( new AstAlways{c.flp, VAlwaysKwd::ALWAYS, c.senTreep->cloneTree(false), topp}); } } + void emitKillAckNba(LowerCtx& c) { + AstAssignDly* const ackp + = new AstAssignDly{c.flp, new AstVarRef{c.flp, c.killVarp, VAccess::WRITE}, + assertKillGet(c.flp, c.assertType, c.directiveType)}; + m_modp->addStmtsp(new AstAlways{c.flp, VAlwaysKwd::ALWAYS, c.senTreep->cloneTree(false), + new AstIf{c.flp, killActive(c), ackp, nullptr}}); + } + // Phase 3/3a/3b: Compute terminal match/reject signals, required-step reject, // throughout-drop reject; clean up intermediate state signals. // Phase 3: terminalActive and rejectBase from Links to matchVertex. // Builder only adds Links (non-clocked) to matchVertex via addLink in - // wireMatchAndMidSources. - void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs) { + // wireMatchAndMidSources. When outPerMidSrcsp is non-null, also collect + // the per-edge match signal (IEEE 1800-2023 16.14.3 cover sequence: each + // end-of-match fires the action independently, no OR-fold). + void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs, + std::vector* outPerMidSrcsp = nullptr) { for (const SvaTransEdge* const tep : c.edges) { if (tep->toVtxp() != c.graph.m_matchVertexp) continue; const int fi = tep->fromVtxp()->color(); @@ -1359,6 +1876,18 @@ class SvaNfaLowering final { if (snapshotOkp) { srcSigp = new AstLogAnd{c.flp, srcSigp, snapshotOkp->cloneTreePure(false)}; } + if (outPerMidSrcsp) { + // Per-mid signal must also AND in matchCondp (the final boolean + // check, e.g. sampled(b) for `a ##[1:3] b`). assembleResult does + // this for the OR-collapsed terminalActivep; we replicate it + // per-edge here so each end-of-match is gated identically. + AstNodeExpr* perMidp = srcSigp->cloneTreePure(false); + if (c.matchCondp) { + perMidp = new AstLogAnd{c.flp, perMidp, + sampled(c.matchCondp->cloneTreePure(false))}; + } + outPerMidSrcsp->push_back(perMidp); + } if (tep->fromVtxp()->m_isCounter) { sigs.terminalActivep @@ -1409,7 +1938,8 @@ class SvaNfaLowering final { } } - SignalSet computeSignals(LowerCtx& c, std::vector* outRequiredStepSrcsp) { + SignalSet computeSignals(LowerCtx& c, std::vector* outRequiredStepSrcsp, + std::vector* outPerMidSrcsp = nullptr) { SignalSet sigs; // Snapshot comparison expression for disable-iff counter. @@ -1421,19 +1951,29 @@ class SvaNfaLowering final { new AstVarRef{c.flp, c.disableCntVarp, VAccess::READ}}; } - computeTerminalMatchAndReject(c, snapshotOkp, sigs); + computeTerminalMatchAndReject(c, snapshotOkp, sigs, outPerMidSrcsp); // Phase 3a: required-step rejection. - // Builder only sets m_rejectOnFail on non-clocked Links with m_condp, - // and the source always has a resolved stateSig. + // Builder only sets m_rejectOnFail on non-clocked Links with m_condp + // or m_condVtxp, and the source always has a resolved stateSig. for (const SvaTransEdge* const tep : c.edges) { if (!tep->m_rejectOnFail) continue; const int fi = tep->fromVtxp()->color(); - UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && tep->m_condp, tep->fromVtxp(), - "rejectOnFail Link must have condp and source stateSig"); + UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && (tep->m_condp || tep->m_condVtxp), + tep->fromVtxp(), + "rejectOnFail Link must have condp/condVtxp and source stateSig"); AstNodeExpr* const srcSigp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false); - AstNodeExpr* const notCondp = new AstLogNot{c.flp, tep->m_condp->cloneTreePure(false)}; - AstNodeExpr* const failp = new AstLogAnd{c.flp, srcSigp, notCondp}; + AstNodeExpr* condp = nullptr; + if (tep->m_condVtxp) { + const int ci = tep->m_condVtxp->color(); + UASSERT_OBJ(c.vtx[ci]->datap()->stateSigp, tep->m_condVtxp, + "rejectOnFail condVtxp missing stateSig"); + condp = c.vtx[ci]->datap()->stateSigp->cloneTreePure(false); + } else { + condp = tep->m_condp->cloneTreePure(false); + } + AstNodeExpr* const notCondp = new AstLogNot{c.flp, condp}; + AstNodeExpr* const failp = gateNotKill(c, new AstLogAnd{c.flp, srcSigp, notCondp}); if (outRequiredStepSrcsp) { outRequiredStepSrcsp->push_back(failp->cloneTreePure(false)); } @@ -1441,6 +1981,9 @@ class SvaNfaLowering final { } computeThroughoutReject(c, sigs); + sigs.terminalActivep = gateNotKill(c, sigs.terminalActivep); + sigs.rejectBasep = gateNotKill(c, sigs.rejectBasep); + sigs.throughoutRejectp = gateNotKill(c, sigs.throughoutRejectp); // Clean up intermediate state signals. These are orphan subtrees // (never linked into the enclosing AST); deleteTree() is immediate @@ -1449,8 +1992,18 @@ class SvaNfaLowering final { AstNodeExpr*& sigp = c.vtx[i]->datap()->stateSigp; if (sigp) VL_DO_DANGLING(sigp->deleteTree(), sigp); } - // Disable iff gating on throughout/required-step rejects (IEEE 16.12). + // Disable iff gating (IEEE 1800-2023 16.12). The edge counter misses a + // continuously-true disable, so gate on the current level value too. if (c.disableExprp) { + // terminalActivep is always set, so gate it unconditionally. + AstNodeExpr* const notTermp + = new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)}; + sigs.terminalActivep = new AstLogAnd{c.flp, sigs.terminalActivep, notTermp}; + if (sigs.rejectBasep) { + AstNodeExpr* const notDisp + = new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)}; + sigs.rejectBasep = new AstLogAnd{c.flp, sigs.rejectBasep, notDisp}; + } if (sigs.throughoutRejectp) { AstNodeExpr* const notDisp = new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)}; @@ -1648,7 +2201,10 @@ public: AstNodeExpr* disableExprp = nullptr, bool negated = false, AstNodeExpr** outMatchpp = nullptr, AstVar* disableCntVarp = nullptr, AstVar* snapshotVarp = nullptr, - std::vector* outRequiredStepSrcsp = nullptr) { + std::vector* outRequiredStepSrcsp = nullptr, + std::vector* outPerMidSrcsp = nullptr, + VAssertType assertType = VAssertType::INTERNAL, + VAssertDirectiveType directiveType = VAssertDirectiveType::INTERNAL) { const std::string baseName = m_names.get(""); // Number vertices with sequential colors for array indexing. @@ -1681,6 +2237,10 @@ public: } AstNodeDType* const u32DTypep = m_modp->findBasicDType(VBasicDTypeKwd::UINT32); + AstVar* const killVarp + = new AstVar{flp, VVarType::MODULETEMP, baseName + "__kill", u32DTypep}; + killVarp->lifetime(VLifetime::STATIC_EXPLICIT); + m_modp->addStmtsp(killVarp); for (int i = 0; i < N; ++i) { if (vtx[i]->m_isAndCombiner) { const std::string base = baseName + "__a" + std::to_string(i); @@ -1721,9 +2281,9 @@ public: } // Build lowering context for phase sub-functions. - LowerCtx c{flp, N, vtx, edges, startIdx, - matchIdx, senTreep, disableExprp, matchCondp, disableCntVarp, - snapshotVarp, graph}; + LowerCtx c{flp, N, vtx, edges, startIdx, + matchIdx, senTreep, disableExprp, matchCondp, disableCntVarp, + snapshotVarp, assertType, directiveType, killVarp, graph}; // Phase 1: Resolve combinational Links via fixed-point propagation. resolveLinks(c, triggerExprp); @@ -1732,14 +2292,42 @@ public: emitStateRegisterNba(c); emitCounterFsmNba(c); emitAndCombinerDoneLatchNba(c); + emitKillAckNba(c); // Phase 3/3a/3b: Compute terminal match/reject signals (cleans up stateSig). - const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp); + const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp, outPerMidSrcsp); AstNodeExpr* const resultp = assembleResult( flp, isCover, negated, matchCondp, sigs.terminalActivep, sigs.rejectBasep, sigs.throughoutRejectp, sigs.requiredStepRejectp, outMatchpp); + // Strong s_always[m:n] end-of-simulation liveness: if any in-window state + // is still set at $finish, the universal-quantifier window never completed + // (IEEE 1800-2023 16.12.11 strong semantics). Fire the assertion failure + // from a final block; V3Assert turns the DT_ERROR display into the standard + // "Assertion failed in %m" message. + AstNodeExpr* pendingp = nullptr; + for (int i = 0; i < N; ++i) { + if (!vtx[i]->m_strongPending || !vtx[i]->datap()->stateVarp) continue; + AstNodeExpr* const svp = new AstVarRef{flp, vtx[i]->datap()->stateVarp, VAccess::READ}; + if (!pendingp) { + pendingp = svp; + } else { + pendingp = new AstLogOr{flp, pendingp, svp}; + } + } + if (pendingp) { + AstCExpr* const assertOnp + = new AstCExpr{flp, AstCExpr::Pure{}, "vlSymsp->_vm_contextp__->assertOn()", 1}; + AstNodeExpr* const condp = new AstLogAnd{flp, assertOnp, pendingp}; + AstDisplay* const dispp + = new AstDisplay{flp, VDisplayType::DT_ERROR, "", nullptr, nullptr}; + dispp->fmtp()->timeunit(m_modp->timeunit()); + AstNodeStmt* const firep = dispp; + if (v3Global.opt.stopFail()) firep->addNext(new AstStop{flp, false}); + m_modp->addStmtsp(new AstFinal{flp, new AstIf{flp, condp, firep}}); + } + // Clear userp on every vertex before vertexData unique_ptrs are destroyed. for (int i = 0; i < N; ++i) vtx[i]->userp(nullptr); return resultp; @@ -1765,7 +2353,10 @@ class AssertNfaVisitor final : public VNVisitor { // Wire match vertex and mid-window sources for a successful NFA build. static void wireMatchAndMidSources(SvaGraph& graph, const BuildResult& result, FileLine* flp) { graph.createMatchVertex(); - graph.addLink(result.termVertexp, graph.m_matchVertexp); + // Skip the main term Link when midSources already cover every + // end-of-match (cover_sequence path); otherwise the per-mid extraction + // double-counts via the merge vertex. + if (!result.termIsMidMerge) { graph.addLink(result.termVertexp, graph.m_matchVertexp); } for (SvaStateVertex* srcVtxp : result.midSources) { AstNodeExpr* condp = nullptr; for (AstNodeExpr* const tc : srcVtxp->m_throughoutConds) { @@ -1968,6 +2559,38 @@ class AssertNfaVisitor final : public VNVisitor { return parts; } + static bool canSplitImplicationPassActions(const PropertyParts& parts) { + UASSERT(parts.hasImplication, + "Implication pass action split requested without implication"); + UASSERT(parts.triggerExprp, "Implication pass action split requested without trigger"); + // Direct vacuous/nonvacuous classification uses the antecedent value in the current + // assertion attempt. Leave delayed antecedents on the existing NFA pass path. + return !hasMultiCycleExpr(parts.triggerExprp); + } + + static void splitImplicationPassActions(AstAssert* assertp, const PropertyParts& parts, + AstNodeExpr* nonvacuousMatchp) { + FileLine* const flp = assertp->fileline(); + + AstNode* const passsp = assertp->passsp()->unlinkFrBackWithNext(); + AstNode* splitsp = nullptr; + + if (!parts.isFollowedBy) { + AstNodeExpr* const vacuousp + = new AstLogNot{flp, sampled(parts.triggerExprp->cloneTreePure(false))}; + AstNode* const vacuousBodyp = passsp->cloneTree(false); + splitsp = newPassOnIf(flp, vacuousp, vacuousBodyp, assertp->userType(), + assertp->directive(), /*vacuous=*/true); + } + + AstIf* const nonvacuousp + = newPassOnIf(flp, nonvacuousMatchp, passsp, assertp->userType(), assertp->directive(), + /*vacuous=*/false); + splitsp = splitsp ? AstNode::addNext(splitsp, nonvacuousp) + : static_cast(nonvacuousp); + assertp->addPasssp(splitsp); + } + // Allocate disable-iff counter + snapshot vars and unlink the original // disable expression from the PropSpec. Returns {cntp, snapp} or // {nullptr, nullptr} if no counter is needed. @@ -2014,6 +2637,43 @@ class AssertNfaVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); } + // Hoist a leading clocking event (IEEE 1800-2023 16.7): + bool hoistClockedSeq(AstPropSpec* specp) { + while (AstSClocked* const clockedp = VN_CAST(specp->propp(), SClocked)) { + if (specp->sensesp()) { + clockedp->v3warn(E_UNSUPPORTED, "Unsupported: multiclocked sequence or property"); + replaceBodyOnBuildError(specp->fileline(), specp, true); + return true; + } + for (const AstSenItem* sp = clockedp->sensesp(); sp; + sp = VN_CAST(sp->nextp(), SenItem)) { + if (!sp->edgeType().anEdge()) { + clockedp->v3warn(E_UNSUPPORTED, + "Unsupported: non-edge clocking event on a sequence; " + "use an edge such as @(posedge clk)"); + replaceBodyOnBuildError(specp->fileline(), specp, true); + return true; + } + } + specp->sensesp(clockedp->sensesp()->unlinkFrBackWithNext()); + AstNodeExpr* const bodyp = clockedp->exprp()->unlinkFrBack(); + clockedp->replaceWith(bodyp); + VL_DO_DANGLING(pushDeletep(clockedp), clockedp); + } + // A clocking event anywhere else in the sequence is not supported. + const AstSClocked* nestedp = nullptr; + specp->propp()->foreach([&](const AstSClocked* p) { + if (!nestedp) nestedp = p; + }); + if (nestedp) { + nestedp->v3warn(E_UNSUPPORTED, + "Unsupported: clocking event inside sequence expression"); + replaceBodyOnBuildError(specp->fileline(), specp, true); + return true; + } + return false; + } + // Build the NFA graph for a property body, handling both the antecedent // |-> consequent and simple sequence cases. Returns the consequent/body // BuildResult (invalid on parse/build failure). @@ -2066,7 +2726,11 @@ class AssertNfaVisitor final : public VNVisitor { cumulativeOrp->cloneTreePure(false)}; m_modp->addStmtsp( new AstAlways{flp, VAlwaysKwd::ALWAYS, perSrcSenTreep->cloneTree(false), - new AstIf{flp, condp, failsp->cloneTree(true), nullptr}}); + new AstIf{flp, condp, + newIfAssertFailOn(failsp->cloneTree(true), + assertWithFailp->directive(), + assertWithFailp->userType()), + nullptr}}); cumulativeOrp = new AstLogOr{flp, cumulativeOrp, srcp->cloneTreePure(false)}; } VL_DO_DANGLING(pushDeletep(cumulativeOrp), cumulativeOrp); @@ -2189,6 +2853,10 @@ class AssertNfaVisitor final : public VNVisitor { inlineAllSequenceRefs(assertp->propp()); + if (AstPropSpec* const specp = VN_CAST(assertp->propp(), PropSpec)) { + if (hoistClockedSeq(specp)) return; + } + AstNode* const propp = assertp->propp(); if (!hasMultiCycleExpr(propp)) return; if (isBareTopLevelUntil(propp)) return; @@ -2217,11 +2885,17 @@ class AssertNfaVisitor final : public VNVisitor { bool senTreeOwned = false; // True if we created senTreep locally AstPropSpec* const propSpecp = VN_CAST(assertp->propp(), PropSpec); UASSERT_OBJ(propSpecp, assertp, "Concurrent assertion must have PropSpec"); + AstCover* const coverp = VN_CAST(assertp, Cover); + const bool isCover = coverp != nullptr; + const bool isCoverSeq = coverp && coverp->isCoverSeq(); + // A sequence event control is not an assertion directive; no default + // disable iff, no assertion control + const bool isSeqEvent = coverp && coverp->isSeqEvent(); // Inherit module defaults (IEEE 14.12, 16.15) when assertion has none. if (!propSpecp->sensesp() && m_defaultClockingp) { propSpecp->sensesp(m_defaultClockingp->sensesp()->cloneTree(true)); } - if (!propSpecp->disablep() && m_defaultDisablep) { + if (!propSpecp->disablep() && m_defaultDisablep && !isSeqEvent) { propSpecp->disablep(m_defaultDisablep->condp()->cloneTreePure(true)); } if (!senTreep && propSpecp->sensesp()) { @@ -2233,10 +2907,9 @@ class AssertNfaVisitor final : public VNVisitor { if (!senTreep) return; FileLine* const flp = assertp->fileline(); - const bool isCover = VN_IS(assertp, Cover); SvaGraph graph; - SvaNfaBuilder builder{graph, m_modp, m_propTempNames}; + SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq, isSeqEvent}; const BuildResult result = buildAssertionGraph(builder, graph, seqBodyp, parts, flp); if (result.valid()) wireMatchAndMidSources(graph, result, flp); @@ -2259,8 +2932,11 @@ class AssertNfaVisitor final : public VNVisitor { const bool disableExprUnlinked = disableCntVarp && disableExprp; AstAssert* const assertAssertp = VN_CAST(assertp, Assert); - const bool needMatch - = !isCover && !parts.hasImplication && assertAssertp && assertAssertp->passsp(); + const bool splitImplicationPasssp = assertAssertp && assertAssertp->passsp() + && parts.hasImplication + && canSplitImplicationPassActions(parts); + const bool needMatch = assertAssertp && assertAssertp->passsp() + && (!parts.hasImplication || splitImplicationPasssp); AstNodeExpr* matchExprp = nullptr; AstAssert* const assertWithFailp = VN_CAST(assertp, Assert); @@ -2268,12 +2944,23 @@ class AssertNfaVisitor final : public VNVisitor { = !isCover && !parts.hasImplication && assertWithFailp && assertWithFailp->failsp(); std::vector requiredStepSrcs; - AstNodeExpr* const alwaysTriggerp = new AstConst{flp, AstConst::BitTrue{}}; - AstNodeExpr* const outputExprp - = m_loweringp->lower(flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, - disableExprp ? disableExprp->cloneTreePure(false) : nullptr, - negated, needMatch ? &matchExprp : nullptr, disableCntVarp, - snapshotVarp, needPerSrcFail ? &requiredStepSrcs : nullptr); + // For `cover sequence` (IEEE 1800-2023 16.14.3) collect per-edge match + // signals so each end-of-match fires the action independently, rather + // than getting OR-folded into a single per-cycle terminalActive. + // coverp / isCoverSeq are computed earlier (passed to SvaNfaBuilder). + std::vector perMidSrcs; + + AstNodeExpr* const alwaysTriggerp + = isSeqEvent ? new AstConst{flp, AstConst::BitTrue{}} + : assertOnCond(flp, assertp->userType(), assertp->directive()); + AstNodeExpr* const outputExprp = m_loweringp->lower( + flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, + disableExprp ? disableExprp->cloneTreePure(false) : nullptr, negated, + needMatch ? &matchExprp : nullptr, disableCntVarp, snapshotVarp, + needPerSrcFail ? &requiredStepSrcs : nullptr, isCoverSeq ? &perMidSrcs : nullptr, + isSeqEvent ? VAssertType{VAssertType::INTERNAL} : assertp->userType(), + isSeqEvent ? VAssertDirectiveType{VAssertDirectiveType::INTERNAL} + : assertp->directive()); AstSenTree* const perSrcSenTreep = (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr; @@ -2283,12 +2970,48 @@ class AssertNfaVisitor final : public VNVisitor { if (disableExprUnlinked) VL_DO_DANGLING(pushDeletep(disableExprp), disableExprp); if (result.finalCondp && !result.finalCondp->backp()) pushDeletep(result.finalCondp); - attachMatchHandlers(flp, assertAssertp, assertWithFailp, needMatch ? matchExprp : nullptr, - perSrcSenTreep, requiredStepSrcs); + if (splitImplicationPasssp) { + splitImplicationPassActions(assertAssertp, parts, matchExprp); + matchExprp = nullptr; + } else { + attachMatchHandlers(flp, assertAssertp, assertWithFailp, + needMatch ? matchExprp : nullptr, perSrcSenTreep, + requiredStepSrcs); + matchExprp = nullptr; + } - AstNode* const innerPropp = propSpecp->propp(); - innerPropp->replaceWith(outputExprp); - VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); + if (isCoverSeq && perMidSrcs.size() > 1) { + // Clone AstCover (N-1) times, each gated by its own per-mid signal. + // V3Assert sees N independent covers and emits N `if (cond_i) {coverinc; + // userAction}` bodies; the shared AstCoverDecl bucket is incremented + // per fire, matching IEEE "executed each time the sequence matches." + // Clones reuse AstCover->propp's original SVA tree, but we overwrite + // each clone's inner propp with the corresponding per-mid signal + // BEFORE the next iterator step, so hasMultiCycleExpr() returns false + // and processAssertion skips them on revisit. + std::vector coverList; + coverList.push_back(coverp); + for (size_t i = 1; i < perMidSrcs.size(); ++i) { + AstCover* const clonep = coverp->cloneTree(false); + coverp->addNextHere(clonep); + coverList.push_back(clonep); + } + for (size_t i = 0; i < perMidSrcs.size(); ++i) { + AstPropSpec* const clonePropSpecp = VN_CAST(coverList[i]->propp(), PropSpec); + AstNode* const innerp = clonePropSpecp->propp(); + innerp->replaceWith(perMidSrcs[i]); + VL_DO_DANGLING(pushDeletep(innerp), innerp); + } + // Discard the OR-collapsed fallback signal -- cover_sequence path + // does not use it. + VL_DO_DANGLING(outputExprp->deleteTree(), outputExprp); + } else { + AstNode* const innerPropp = propSpecp->propp(); + innerPropp->replaceWith(outputExprp); + VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); + // If we collected per-mid (N==1) but didn't clone, drop the spare. + for (AstNodeExpr* const sp : perMidSrcs) pushDeletep(sp); + } UINFO(4, "NFA converted assertion at " << flp << endl); } @@ -2301,7 +3024,7 @@ class AssertNfaVisitor final : public VNVisitor { VL_RESTORER(m_defaultDisablep); m_modp = nodep; m_defaultClockingp = nullptr; - m_defaultDisablep = nullptr; + m_defaultDisablep = nodep->defaultDisablep(); SvaNfaLowering lowering{nodep}; m_loweringp = &lowering; iterateChildren(nodep); @@ -2310,9 +3033,12 @@ class AssertNfaVisitor final : public VNVisitor { if (nodep->isDefault() && !m_defaultClockingp) m_defaultClockingp = nodep; iterateChildren(nodep); } - void visit(AstDefaultDisable* nodep) override { - if (!m_defaultDisablep) m_defaultDisablep = nodep; + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); } + void visit(AstDefaultDisable* nodep) override {} void visit(AstAssert* nodep) override { processAssertion(nodep); } void visit(AstCover* nodep) override { processAssertion(nodep); } void visit(AstRestrict* nodep) override { diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index c61299a66..d9332ff59 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -23,6 +23,7 @@ #include "V3AssertPre.h" +#include "V3Assert.h" #include "V3Const.h" #include "V3Task.h" #include "V3UniqueNames.h" @@ -60,6 +61,7 @@ private: AstNodeExpr* m_disablep = nullptr; // Last disable AstIf* m_disableSeqIfp = nullptr; // Used for handling disable iff in sequences AstPExpr* m_pexprp = nullptr; // Last AstPExpr + bool m_underCover = false; // True if the enclosing assertion is a cover // Other: V3UniqueNames m_cycleDlyNames{"__VcycleDly"}; // Cycle delay counter name generator V3UniqueNames m_consRepNames{"__VconsRep"}; // Consecutive repetition counter name generator @@ -91,7 +93,10 @@ private: fromAlways = true; } if (!senip) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: Unclocked assertion"); + nodep->v3error("Concurrent assertion has no clock (IEEE 1800-2023 16.16)\n" + << nodep->warnMore() + << "... Suggest provide a clocking event, a default" + " clocking, or a clocked procedural context"); newp = new AstSenTree{nodep->fileline(), nullptr}; } else { if (cassertp && fromAlways) cassertp->senFromAlways(true); @@ -1281,7 +1286,10 @@ private: // Don't iterate pexprp here -- it was already iterated when created // (in visit(AstSExpr*)), so delays and disable iff are already processed. } else if (nodep->isOverlapped()) { - nodep->replaceWith(new AstLogOr{flp, new AstLogNot{flp, lhsp}, rhsp}); + AstNodeExpr* const exprp + = m_underCover ? static_cast(new AstLogAnd{flp, lhsp, rhsp}) + : new AstLogOr{flp, new AstLogNot{flp, lhsp}, rhsp}; + nodep->replaceWith(exprp); } else { if (m_disablep) { lhsp = new AstAnd{flp, new AstNot{flp, m_disablep->cloneTreePure(false)}, lhsp}; @@ -1290,7 +1298,9 @@ private: AstPast* const pastp = new AstPast{flp, lhsp}; pastp->dtypeFrom(lhsp); pastp->sentreep(newSenTree(nodep)); - AstNodeExpr* const exprp = new AstOr{flp, new AstNot{flp, pastp}, rhsp}; + AstNodeExpr* const exprp + = m_underCover ? static_cast(new AstAnd{flp, pastp, rhsp}) + : new AstOr{flp, new AstNot{flp, pastp}, rhsp}; exprp->dtypeSetBit(); nodep->replaceWith(exprp); } @@ -1438,12 +1448,6 @@ private: } void visit(AstDefaultDisable* nodep) override { - if (m_defaultDisablep) { - nodep->v3error("Only one 'default disable iff' allowed per module" - " (IEEE 1800-2023 16.15)"); - } else { - m_defaultDisablep = nodep; - } VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstInferredDisable* nodep) override { @@ -1462,7 +1466,9 @@ private: iterateAndNextNull(nodep->sensesp()); if (m_senip && m_senip != nodep->sensesp()) nodep->v3warn(E_UNSUPPORTED, "Unsupported: Only one PSL clock allowed per assertion"); - if (!nodep->disablep() && m_defaultDisablep) { + const AstCover* const coverp = VN_CAST(nodep->backp(), Cover); + const bool seqEvent = coverp && coverp->isSeqEvent(); + if (!nodep->disablep() && m_defaultDisablep && !seqEvent) { nodep->disablep(m_defaultDisablep->condp()->cloneTreePure(true)); } m_disablep = nodep->disablep(); @@ -1474,6 +1480,10 @@ private: nodep->propp(new AstSampled{nodep->fileline(), nodep->propp()->unlinkFrBack(), propDtp->dtypep()}); } + // cover counts non-vacuous matches only (IEEE 1800-2023 16.15.2), so an + // implication antecedent must hold; assert passes vacuously instead. + VL_RESTORER(m_underCover); + m_underCover = VN_IS(nodep->backp(), Cover); iterate(nodep->propp()); } void visit(AstPExpr* nodep) override { @@ -1565,10 +1575,15 @@ private: VL_RESTORER(m_modp); m_defaultClockingp = nullptr; m_defaultClkEvtVarp = nullptr; - m_defaultDisablep = nullptr; + m_defaultDisablep = nodep->defaultDisablep(); m_modp = nodep; iterateChildren(nodep); } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } void visit(AstProperty* nodep) override { // The body will be visited when will be substituted in place of property reference // (AstFuncRef) diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 9fe36df38..8c9d6198e 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1745,6 +1745,156 @@ AstNodeDType* AstNode::getCommonClassTypep(AstNode* node1p, AstNode* node2p) { return nullptr; } +//###################################################################### +// Renders the canonical pattern S-expression for a single AstNode + +class VNPatternString final { + std::ostream& m_os; + + std::map m_internedConsts; // Interned constants + std::map m_internedWordWidths; // Interned widths + std::map m_internedWideWidths; // Interned widths + + // Whether to dump the widhtMin as well + const bool m_dumpWidthMin = v3Global.widthMinUsage() != VWidthMinUsage::MATCHES_WIDTH; + + static std::string toLetters(size_t value, bool lowerCase = false) { + const char base = lowerCase ? 'a' : 'A'; + std::string s; + do { s += static_cast(base + value % 26); } while (value /= 26); + return s; + } + + const std::string& internConst(const AstConst& nodep) { + const auto pair = m_internedConsts.emplace(nodep.num().ascii(false), ""); + if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1); + return pair.first->second; + } + + const std::string& internWordWidth(int value) { + const auto pair = m_internedWordWidths.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true); + return pair.first->second; + } + + const std::string& internWideWidth(int value) { + const auto pair = m_internedWideWidths.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1); + return pair.first->second; + } + + // True if the node has no operands (e.g. a VarRef or Const) + static bool isLeaf(const AstNode* nodep) { + const VNTypeInfo& typeInfo = VNType::typeInfo(nodep->type()); + for (const VNTypeInfo::OpEn& opType : typeInfo.m_opType) { + if (opType != VNTypeInfo::OP_UNUSED) return false; + } + return true; + } + + // Render operand, return true if not unused + void renderOperand(VNTypeInfo::OpEn opType, const AstNode* const opp, uint32_t depth) { + switch (opType) { + case VNTypeInfo::OP_UNUSED: // + break; + case VNTypeInfo::OP_USED: // + m_os << ' '; + render(opp, depth); + break; + case VNTypeInfo::OP_LIST: + m_os << " ["; + for (const AstNode* nodep = opp; nodep; nodep = nodep->nextp()) { + if (nodep != opp) m_os << ", "; + render(opp, depth); + } + m_os << ']'; + break; + case VNTypeInfo::OP_OPTIONAL: + if (opp) { + m_os << " "; + render(opp, depth); + } else { + m_os << " nil"; + } + break; + } + } + + // Render the node into the stream. + void render(const AstNode* nodep, uint32_t depth) { + if (const AstConst* const constp = VN_CAST(nodep, Const)) { + // Base case 1: constant + if (constp->isZero()) { + m_os << "(CONST ZERO)"; + } else if (constp->isEqAllOnes()) { + m_os << "(CONST ONES)"; + } else { + m_os << "(CONST #" << internConst(*constp) << ')'; + } + } else if (isLeaf(nodep)) { + // Base case 2: expression with no operands (e.g. a variable reference) + m_os << '(' << nodep->typeName() << ')'; + } else if (depth == 0) { + // Base case 3: deep expression + m_os << '_'; + } else { + // Recursively print an S-expression for the expression + m_os << '('; + // Name + m_os << nodep->typeName(); + // Operands + const VNTypeInfo& typeInfo = VNType::typeInfo(nodep->type()); + renderOperand(typeInfo.m_opType[0], nodep->op1p(), depth - 1); + renderOperand(typeInfo.m_opType[1], nodep->op2p(), depth - 1); + renderOperand(typeInfo.m_opType[2], nodep->op3p(), depth - 1); + renderOperand(typeInfo.m_opType[3], nodep->op4p(), depth - 1); + // S-expression end + m_os << ')'; + } + + // Annotate type + m_os << ':'; + const AstNodeDType* const dtypep = nodep->dtypep() ? nodep->dtypep()->skipRefp() : nullptr; + if (!dtypep) { + m_os << '?'; + } else if (dtypep->isCompound() || VN_IS(dtypep, UnpackArrayDType)) { + dtypep->dumpSmall(m_os); + } else { + const int width = nodep->width(); + if (width == 1) { + m_os << '1'; + } else if (width <= VL_QUADSIZE) { + m_os << internWordWidth(width); + } else { + m_os << internWideWidth(width); + } + if (m_dumpWidthMin) { + m_os << '/'; + const int widthMin = nodep->widthMin(); + if (widthMin == 1) { + m_os << '1'; + } else if (widthMin <= VL_QUADSIZE) { + m_os << internWordWidth(widthMin); + } else { + m_os << internWideWidth(widthMin); + } + } + } + } + +public: + VNPatternString(std::ostream& os, const AstNode* nodep, uint32_t depth) + : m_os{os} { + render(nodep, depth); + } +}; + +std::string AstNodeExpr::patternString(uint32_t depth) const { + std::ostringstream oss; + VNPatternString{oss, this, depth}; + return oss.str(); +} + //###################################################################### // VNDeleter diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index c12627886..afca1bcc2 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -342,7 +342,6 @@ public: VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT_RD, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT_RW, // V3LinkParse moves to AstVar::sigPublic - VAR_ISOLATE_ASSIGNMENTS, // V3LinkParse moves to AstVar::attrIsolateAssign VAR_SC_BIGUINT, // V3LinkParse moves to AstVar::attrScBigUint VAR_SC_BV, // V3LinkParse moves to AstVar::attrScBv VAR_SFORMAT, // V3LinkParse moves to AstVar::attrSFormat @@ -364,7 +363,7 @@ public: "TYPEID", "TYPENAME", "VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC", "VAR_FSM_STATE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT", - "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_ISOLATE_ASSIGNMENTS", + "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR" }; // clang-format on @@ -1191,6 +1190,19 @@ public: = {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"}; return names[m_e]; } + // VlCovBinKind enumerator naming the bin's set + const char* binSetEnum() const { + switch (m_e) { + case BINS_IGNORE: return "VlCovBinKind::KIND_IGNORE"; + case BINS_ILLEGAL: return "VlCovBinKind::KIND_ILLEGAL"; + case BINS_DEFAULT: return "VlCovBinKind::KIND_DEFAULT"; + default: return "VlCovBinKind::KIND_NORMAL"; + } + } + // Normal bins (feed coverage) are anything but ignore/illegal/default + bool binIsNormal() const { + return m_e != BINS_IGNORE && m_e != BINS_ILLEGAL && m_e != BINS_DEFAULT; + } }; constexpr bool operator==(const VCoverBinsType& lhs, VCoverBinsType::en rhs) { return lhs.m_e == rhs; @@ -1416,6 +1428,7 @@ public: ET_EVENT, // VlEventBase::isFired // Involving an expression ET_TRUE, + ET_INITIAL_NBA, // Event that is fired initially and never again // ET_COMBO, // Sensitive to all combo inputs to this block ET_COMBO_STAR, // Sensitive to all combo inputs to this block (from .*) @@ -1434,6 +1447,7 @@ public: true, // ET_NEGEDGE true, // ET_EVENT true, // ET_TRUE + true, // ET_INITIAL_NBA false, // ET_COMBO false, // ET_COMBO_STAR @@ -1457,14 +1471,14 @@ public: } const char* ascii() const { static const char* const names[] - = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "COMBO", - "COMBO_STAR", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; + = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "ET_INITIAL_NBA", + "COMBO", "COMBO_STAR", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; return names[m_e]; } const char* verilogKwd() const { - static const char* const names[] - = {"[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "*", - "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; + static const char* const names[] = { + "[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "[initial_nba]", + "*", "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; return names[m_e]; } // Return true iff this and the other have mutually exclusive transitions diff --git a/src/V3AstInlines.h b/src/V3AstInlines.h index 8a4477060..6ef16eec6 100644 --- a/src/V3AstInlines.h +++ b/src/V3AstInlines.h @@ -163,6 +163,13 @@ bool AstVar::sameNode(const AstNode* samep) const { return m_name == asamep->m_name && varType() == asamep->varType(); } +AstMatchMasked::AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarScope* matchp) + : ASTGEN_SUPER_MatchMasked(fl) { + this->lhsp(lhsp); + this->matchp(new AstVarRef{fl, matchp, VAccess::READ}); + dtypeSetUInt32(); +} + AstVarRef::AstVarRef(FileLine* fl, AstVar* varp, const VAccess& access) : ASTGEN_SUPER_VarRef(fl, varp, access) { if (v3Global.assertDTypesResolved()) { diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 1253c8527..fcb2183ba 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -170,6 +170,11 @@ public: void generic(bool flag) { m_generic = flag; } std::pair dimensions(bool includeBasic) const; uint32_t arrayUnpackedElements() const; // 1, or total multiplication of all dimensions + // Fixed aggregate streaming properties + bool isStreamableFixedAggregate() const; + bool containsUnpackedStruct() const; + int widthStream() const; + string vlEnumType() const; // Return VerilatedVarType: VLVT_UINT32, etc static int uniqueNumInc() { return ++s_uniqueNum; } const char* charIQWN() const { return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I"); diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 41d980697..e8f065861 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -73,6 +73,9 @@ public: // Returns an error message if widthMin() is not correct otherwise returns nullptr like // broken() virtual const char* widthMismatch() const VL_MT_STABLE { return nullptr; } + + // S-expression inspired dump of node and operands for debugging + std::string patternString(uint32_t depth = 0) const; }; class AstNodeBiop VL_NOT_FINAL : public AstNodeExpr { // Binary expression @@ -1855,6 +1858,22 @@ public: bool index() const { return m_index; } bool isExprCoverageEligible() const override { return false; } }; +class AstMatchMasked final : public AstNodeExpr { + // This is a non-source construct, created internally to represent + // some case statements. It is a '(mask & _) == bits' matching loop + // where {mask, bits} pairs are packed into a single wide 'matchp', + // and the result is the index of the first matching entry. + // See VL_DECODER_* runtime functions. + // @astgen op1 := lhsp : AstNodeExpr + // @astgen op2 := matchp : AstVarRef +public: + inline AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarScope* matchp); + ASTGEN_MEMBERS_AstMatchMasked; + string emitVerilog() override { V3ERROR_NA_RETURN(""); } + string emitC() override { return "VL_MATCHMASKED_%lq(%lw, %li, %ri)"; } + bool cleanOut() const override { return true; } + static uint32_t fold(const V3Number& lhs, AstVar* matchVarp); +}; class AstMatches final : public AstNodeExpr { // "matches" operator: "expr matches pattern" // @astgen op1 := lhsp : AstNodeExpr // Expression to match @@ -2225,6 +2244,23 @@ public: bool sameNode(const AstNode* /*samep*/) const override { return true; } bool isSystemFunc() const override { return true; } }; +class AstSClocked final : public AstNodeExpr { + // Sequence expression with an explicit leading clocking event + // IEEE 1800-2023 16.7: sequence_expr ::= clocking_event sequence_expr + // The clocking event is hoisted to the enclosing assertion clock by V3AssertNfa. + // @astgen op1 := sensesp : AstSenItem + // @astgen op2 := exprp : AstNodeExpr +public: + AstSClocked(FileLine* fl, AstSenItem* sensesp, AstNodeExpr* exprp) + : ASTGEN_SUPER_SClocked(fl) { + this->sensesp(sensesp); + this->exprp(exprp); + } + ASTGEN_MEMBERS_AstSClocked; + string emitVerilog() override { V3ERROR_NA_RETURN(""); } + string emitC() override { V3ERROR_NA_RETURN(""); } + bool cleanOut() const override { V3ERROR_NA_RETURN(false); } +}; class AstSConsRep final : public AstNodeExpr { // Consecutive repetition [*N], [*N:M], [+], [*] (IEEE 1800-2023 16.9.2) // op1 := exprp -- the repeated expression @@ -5701,6 +5737,22 @@ public: void dump(std::ostream& str) const override; void dumpJson(std::ostream& str) const override; }; +class AstMostSetBitP1 final : public AstNodeUniop { + // Most-significant set bit plus one (bit-width); 0 if value is zero +public: + AstMostSetBitP1(FileLine* fl, AstNodeExpr* lhsp) + : ASTGEN_SUPER_MostSetBitP1(fl, lhsp) { + dtypeSetInteger2State(); + } + ASTGEN_MEMBERS_AstMostSetBitP1; + void numberOperate(V3Number& out, const V3Number& lhs) override { out.opMostSetBitP1(lhs); } + string emitVerilog() override { return "%f$mostsetbitp1(%l)"; } + string emitC() override { return "VL_MOSTSETBITP1_%lq(%lW, %P, %li)"; } + bool cleanOut() const override { return true; } + bool cleanLhs() const override { return true; } + bool sizeMattersLhs() const override { return false; } + int instrCount() const override { return widthInstrs() * 16; } +}; class AstNToI final : public AstNodeUniop { // String to any-size integral public: diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index f3692d05e..3c25a9992 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -97,7 +97,6 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode { string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8) uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees bool m_taskPublic : 1; // Public task - bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_classMethod : 1; // Class method bool m_didProto : 1; // Did prototype processing bool m_prototype : 1; // Just a prototype @@ -130,7 +129,6 @@ protected: : AstNode{t, fl} , m_name{name} , m_taskPublic{false} - , m_attrIsolateAssign{false} , m_classMethod{false} , m_didProto{false} , m_prototype{false} @@ -179,8 +177,6 @@ public: uint64_t dpiOpenParent() const { return m_dpiOpenParent; } bool taskPublic() const { return m_taskPublic; } void taskPublic(bool flag) { m_taskPublic = flag; } - bool attrIsolateAssign() const { return m_attrIsolateAssign; } - void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } bool classMethod() const { return m_classMethod; } void classMethod(bool flag) { m_classMethod = flag; } bool didProto() const { return m_didProto; } @@ -301,6 +297,7 @@ class AstNodeModule VL_NOT_FINAL : public AstNode { VLifetime m_lifetime; // Lifetime VTimescale m_timeunit; // Global time unit VOptionBool m_unconnectedDrive; // State of `unconnected_drive + AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope bool m_modPublic : 1; // Module has public references bool m_modTrace : 1; // Tracing this module @@ -351,6 +348,8 @@ public: string origName() const override { return m_origName; } string someInstanceName() const VL_MT_SAFE { return m_someInstanceName; } void someInstanceName(const string& name) { m_someInstanceName = name; } + AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; } + void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; } bool inLibrary() const { return m_inLibrary; } void inLibrary(bool flag) { m_inLibrary = flag; } void depth(int value) { m_depth = value; } @@ -1001,6 +1000,8 @@ public: // this matters, the caller must handle the dtype difference as appropriate. If 'mergeDType' is // false, the returned VarScope will have _->dtypep()->sameTree(initp->dtypep()) return true. AstVarScope* findConst(AstConst* initp, bool mergeDType); + // Rebuild hashes and missing variable scopes after potential removals + void rebuildVarScopesAndCache(); }; class AstConstraint final : public AstNode { // Constraint @@ -1184,12 +1185,21 @@ public: }; class AstCoverpointRef final : public AstNode { // Reference to a coverpoint used in a cross - const string m_name; // coverpoint name + // @astgen op1 := exprp : Optional[AstNodeExpr] // Non-standard: hierarchical/dotted + // // reference (implicit coverpoint), e.g. + // // 'cross a.b'; nullptr for a plain coverpoint + // // name. An AstDot at parse time, resolved + // // by the time V3Covergroup runs. + const string m_name; // coverpoint name; empty when exprp() carries a hierarchical reference public: AstCoverpointRef(FileLine* fl, const string& name) : ASTGEN_SUPER_CoverpointRef(fl) , m_name{name} {} + AstCoverpointRef(FileLine* fl, AstNodeExpr* exprp) + : ASTGEN_SUPER_CoverpointRef(fl) { + this->exprp(exprp); + } ASTGEN_MEMBERS_AstCoverpointRef; void dump(std::ostream& str) const override; void dumpJson(std::ostream& str) const override; @@ -1752,6 +1762,7 @@ public: class Combo {}; // for constructor type-overload selection class Static {}; // for constructor type-overload selection class Initial {}; // for constructor type-overload selection + class InitialNBA {}; // for constructor type-overload selection class Final {}; // for constructor type-overload selection class Never {}; // for constructor type-overload selection AstSenItem(FileLine* fl, VEdgeType edgeType, AstNodeExpr* senp, AstNodeExpr* condp = nullptr) @@ -1769,6 +1780,9 @@ public: AstSenItem(FileLine* fl, Initial) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_INITIAL} {} + AstSenItem(FileLine* fl, InitialNBA) + : ASTGEN_SUPER_SenItem(fl) + , m_edgeType{VEdgeType::ET_INITIAL_NBA} {} AstSenItem(FileLine* fl, Final) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_FINAL} {} @@ -2131,15 +2145,16 @@ class AstVar final : public AstNode { bool m_funcReturn : 1; // Return variable for a function bool m_attrScBv : 1; // User force bit vector attribute bool m_attrScBigUint : 1; // User force sc_biguint attribute - bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_attrSFormat : 1; // User sformat attribute bool m_attrSplitVar : 1; // declared with split_var metacomment bool m_attrFsmState : 1; // declared with fsm_state metacomment bool m_attrFsmRegisterWrapper : 1; // connected to an fsm_register_wrapper instance bool m_attrFsmResetArc : 1; // declared with fsm_reset_arc metacomment bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment + bool m_constPoolEntry : 1; // Constant pool variable bool m_fileDescr : 1; // File descriptor bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration + bool m_icoMaybeWritten : 1; // Design might write this input signal - for ico change detect bool m_isConst : 1; // Table contains constant data bool m_isContinuously : 1; // Ever assigned continuously (for force/release) bool m_hasStrengthAssignment : 1; // Is on LHS of assignment with strength specifier @@ -2194,15 +2209,16 @@ class AstVar final : public AstNode { m_funcReturn = false; m_attrScBv = false; m_attrScBigUint = false; - m_attrIsolateAssign = false; m_attrSFormat = false; m_attrSplitVar = false; m_attrFsmState = false; m_attrFsmRegisterWrapper = false; m_attrFsmResetArc = false; m_attrFsmArcInclCond = false; + m_constPoolEntry = false; m_fileDescr = false; m_gotNansiType = false; + m_icoMaybeWritten = false; m_isConst = false; m_isContinuously = false; m_hasStrengthAssignment = false; @@ -2343,13 +2359,14 @@ public: void attrFileDescr(bool flag) { m_fileDescr = flag; } void attrScBv(bool flag) { m_attrScBv = flag; } void attrScBigUint(bool flag) { m_attrScBigUint = flag; } - void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } void attrSFormat(bool flag) { m_attrSFormat = flag; } void attrSplitVar(bool flag) { m_attrSplitVar = flag; } void attrFsmState(bool flag) { m_attrFsmState = flag; } void attrFsmRegisterWrapper(bool flag) { m_attrFsmRegisterWrapper = flag; } void attrFsmResetArc(bool flag) { m_attrFsmResetArc = flag; } void attrFsmArcInclCond(bool flag) { m_attrFsmArcInclCond = flag; } + bool constPoolEntry() const { return m_constPoolEntry; } + void setConstPoolEntry() { m_constPoolEntry = true; } void rand(const VRandAttr flag) { m_rand = flag; } void usedParam(bool flag) { m_usedParam = flag; } void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; } @@ -2383,6 +2400,8 @@ public: void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; } bool hasUserInit() const { return m_hasUserInit; } void hasUserInit(bool flag) { m_hasUserInit = flag; } + void icoMaybeWritten(bool flag) { m_icoMaybeWritten = flag; } + bool icoMaybeWritten() const { return m_icoMaybeWritten; } bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; } void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; } bool isHideLocal() const { return m_isHideLocal; } @@ -2462,12 +2481,6 @@ public: bool isWor() const { return varType().isWor(); } bool isWiredNet() const { return varType().isWiredNet(); } bool isTemp() const { return varType().isTemp(); } - bool isToggleCoverable() const { - return ((isIO() || isSignal()) - && (isIO() || isBitLogic()) - // Wrapper would otherwise duplicate wrapped module's coverage - && !isSc() && !isPrimaryIO() && !isConst() && !isDouble() && !isString()); - } bool isClassMember() const { return varType() == VVarType::MEMBER; } bool isVirtIface() const { if (AstIfaceRefDType* const dtp = VN_CAST(dtypep(), IfaceRefDType)) { @@ -2519,7 +2532,6 @@ public: bool attrFsmRegisterWrapper() const { return m_attrFsmRegisterWrapper; } bool attrFsmResetArc() const { return m_attrFsmResetArc; } bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; } - bool attrIsolateAssign() const { return m_attrIsolateAssign; } AstIface* sensIfacep() const { return m_sensIfacep; } VRandAttr rand() const { return m_rand; } string verilogKwd() const override; @@ -2531,7 +2543,6 @@ public: // This is getting connected to fromp; keep attributes // Note the method below too if (fromp->attrFileDescr()) attrFileDescr(true); - if (fromp->attrIsolateAssign()) attrIsolateAssign(true); if (fromp->isContinuously()) isContinuously(true); } void propagateWrapAttrFrom(const AstVar* fromp) { @@ -2810,6 +2821,7 @@ class AstGenBlock final : public AstNodeGen { std::string m_name; // Name of block const bool m_unnamed; // Originally unnamed (name change does not affect this) const bool m_implied; // Not inserted by user + AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope public: AstGenBlock(FileLine* fl, const string& name, AstNode* itemsp, bool implied) @@ -2826,6 +2838,8 @@ public: void name(const std::string& name) override { m_name = name; } bool unnamed() const { return m_unnamed; } bool implied() const { return m_implied; } + AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; } + void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; } }; class AstGenCase final : public AstNodeGen { // Generate 'case' diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 336da7bae..fbbceaec7 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -59,6 +59,7 @@ protected: public: ASTGEN_MEMBERS_AstNodeAssign; // Clone single node, just get same type back. + void dump(std::ostream& str) const override; virtual AstNodeAssign* cloneType(AstNodeExpr* lhsp, AstNodeExpr* rhsp) = 0; bool hasDType() const override VL_MT_SAFE { return true; } virtual bool cleanRhs() const { return true; } @@ -941,13 +942,16 @@ public: class AstPExprClause final : public AstNodeStmt { const bool m_pass; // True if will be replaced by passing assertion clause, false for // assertion failure clause + const bool m_vacuous; // True if pass is vacuous public: ASTGEN_MEMBERS_AstPExprClause; - explicit AstPExprClause(FileLine* fl, bool pass = true) + explicit AstPExprClause(FileLine* fl, bool pass = true, bool vacuous = false) : ASTGEN_SUPER_PExprClause(fl) - , m_pass{pass} {} + , m_pass{pass} + , m_vacuous{vacuous} {} bool pass() const { return m_pass; } + bool vacuous() const { return m_vacuous; } }; class AstPrintTimeScale final : public AstNodeStmt { // Parents: stmtlist @@ -1606,12 +1610,22 @@ public: }; class AstCover final : public AstNodeCoverOrAssert { // @astgen op3 := coverincsp: List[AstNode] // Coverage node + bool m_isCoverSeq = false; // 'cover sequence' (IEEE 1800-2023 16.14.3): fires per + // end-of-match, not per property success + bool m_isSeqEvent = false; // Synthesized for a sequence used as an event control + // (IEEE 1800-2023 9.4.2.4) public: ASTGEN_MEMBERS_AstCover; AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type, const string& name = "") : ASTGEN_SUPER_Cover(fl, propp, stmtsp, type, VAssertDirectiveType::COVER, name) {} string verilogKwd() const override { return "cover"; } + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + bool isCoverSeq() const { return m_isCoverSeq; } + void isCoverSeq(bool flag) { m_isCoverSeq = flag; } + bool isSeqEvent() const { return m_isSeqEvent; } + void isSeqEvent(bool flag) { m_isSeqEvent = flag; } }; class AstRestrict final : public AstNodeCoverOrAssert { public: diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 3bd3f66f6..26126e5fd 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include // Routines for dumping dict fields (NOTE: due to leading ',' they can't be used for first field in @@ -546,6 +547,26 @@ AstConst* AstConst::parseParamLiteral(FileLine* fl, const string& literal) { string AstConstraintRef::name() const { return constrp()->name(); } +uint32_t AstMatchMasked::fold(const V3Number& lhs, AstVar* matchVarp) { + const V3Number& numTable = VN_AS(matchVarp->valuep(), Const)->num(); + V3Number numMask{matchVarp, lhs.width(), 0}; + V3Number numBits{matchVarp, lhs.width(), 0}; + V3Number numAnd{matchVarp, lhs.width(), 0}; + const int width = lhs.width(); + const int entryWidth = VL_WORDS_I(width) * VL_EDATASIZE; + uint32_t i = 0; + while (true) { + const int lsb = 2 * i * entryWidth; + const int msb = lsb + width - 1; + numMask.opSel(numTable, msb, lsb); + numBits.opSel(numTable, msb + entryWidth, lsb + entryWidth); + numAnd.opAnd(numMask, lhs); + if (numAnd.isCaseEq(numBits)) break; + ++i; + } + return i; +} + AstNetlist::AstNetlist() : ASTGEN_SUPER_Netlist(new FileLine{FileLine::builtInFilename()}) , m_typeTablep{new AstTypeTable{fileline()}} @@ -630,6 +651,7 @@ void AstVar::combineType(const AstVar* otherp) { varType(otherp->varType()); direction(otherp->direction()); } + if (otherp->icoMaybeWritten()) icoMaybeWritten(true); } void AstVar::combineType(VVarType type) { // These flags get combined with the existing settings of the flags. @@ -682,9 +704,14 @@ string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string& return ostatic + dtypep()->cType(oname, forFunc, asRef); } -string AstVar::vlEnumType() const { +string AstNodeDType::vlEnumType() const { string arg; - const AstBasicDType* const bdtypep = basicp(); + const AstNodeDType* dtypep = skipRefp(); + while (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + dtypep = adtypep->subDTypep()->skipRefp(); + } + const AstBasicDType* const bdtypep = dtypep->basicp(); + const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType); const bool strtype = bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING; if (bdtypep && bdtypep->keyword() == VBasicDTypeKwd::CHARPTR) { return "VLVT_PTR"; @@ -694,6 +721,8 @@ string AstVar::vlEnumType() const { arg += "VLVT_STRING"; } else if (isDouble()) { arg += "VLVT_REAL"; + } else if (sdtypep && !sdtypep->packed()) { + arg += VN_IS(sdtypep, StructDType) ? "VLVT_STRUCT" : "VLVT_UNION"; } else if (widthMin() <= 8) { arg += "VLVT_UINT8"; } else if (widthMin() <= 16) { @@ -709,6 +738,8 @@ string AstVar::vlEnumType() const { return arg; } +string AstVar::vlEnumType() const { return dtypep()->vlEnumType(); } + string AstVar::vlEnumDir() const { string out; if (isInout()) { @@ -738,6 +769,7 @@ string AstVar::vlEnumDir() const { if (AstBasicDType* const basicp = dtypep()->skipRefp()->basicp()) { if (basicp->keyword() == VBasicDTypeKwd::BIT) out += "|VLVF_BITVAR"; } + if (isNet()) out += "|VLVF_NET"; return out; } @@ -1259,6 +1291,47 @@ uint32_t AstNodeDType::arrayUnpackedElements() const { return entries; } +bool AstNodeDType::isStreamableFixedAggregate() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->isStreamableFixedAggregate(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) return true; + if (!VN_IS(sdtypep, StructDType)) return false; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + if (!itemp->dtypep()->isStreamableFixedAggregate()) return false; + } + return true; + } + return dtypep->isIntegralOrPacked() || dtypep->isDouble(); +} + +bool AstNodeDType::containsUnpackedStruct() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->containsUnpackedStruct(); + } + const AstStructDType* const sdtypep = VN_CAST(dtypep, StructDType); + return sdtypep && !sdtypep->packed(); +} + +int AstNodeDType::widthStream() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->widthStream() * adtypep->elementsConst(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (!VN_IS(sdtypep, StructDType) || sdtypep->packed()) return width(); + int width = 0; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + width += itemp->dtypep()->widthStream(); + } + return width; + } + return dtypep->width(); +} + std::pair AstNodeDType::dimensions(bool includeBasic) const { // How many array dimensions (packed,unpacked) does this Var have? uint32_t packed = 0; @@ -1322,6 +1395,12 @@ AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) { } else if (VN_IS(nodep, Sel)) { nodep = VN_AS(nodep, Sel)->fromp(); continue; + } else if (VN_IS(nodep, AssocSel)) { + nodep = VN_AS(nodep, AssocSel)->fromp(); + continue; + } else if (VN_IS(nodep, WildcardSel)) { + nodep = VN_AS(nodep, WildcardSel)->fromp(); + continue; } else if (overMembers && VN_IS(nodep, MemberSel)) { nodep = VN_AS(nodep, MemberSel)->fromp(); continue; @@ -1581,6 +1660,7 @@ AstConstPool::AstConstPool(FileLine* fl) AstVarScope* AstConstPool::createNewEntry(const string& name, AstNodeExpr* initp) { FileLine* const fl = initp->fileline(); AstVar* const varp = new AstVar{fl, VVarType::MODULETEMP, name, initp->dtypep()}; + varp->setConstPoolEntry(); varp->isConst(true); varp->isStatic(true); varp->valuep(initp->cloneTree(false)); @@ -1700,6 +1780,31 @@ AstVarScope* AstConstPool::findConst(AstConst* initp, bool mergeDType) { return varScopep; } +void AstConstPool::rebuildVarScopesAndCache() { + m_tables.clear(); + m_consts.clear(); + std::unordered_map varScopeps; + for (AstVarScope* vscp = m_scopep->varsp(); vscp; vscp = VN_CAST(vscp->nextp(), VarScope)) { + varScopeps.emplace(vscp->varp(), vscp); + } + for (AstNode* nodep = m_modp->stmtsp(); nodep; nodep = nodep->nextp()) { + AstVar* const varp = VN_CAST(nodep, Var); + if (!varp) continue; + AstNode* const valuep = varp->valuep(); + if (!valuep) continue; + const bool isTable = VN_IS(valuep, InitArray); + const AstConst* const constp = VN_CAST(valuep, Const); + if (!isTable && !constp) continue; + AstVarScope*& vscp = varScopeps[varp]; + if (!vscp) { + vscp = new AstVarScope{varp->fileline(), m_scopep, varp}; + m_scopep->addVarsp(vscp); + } + if (isTable) m_tables.emplace(V3Hasher::uncachedHash(valuep).value(), vscp); + if (constp) m_consts.emplace(constp->num().toHash().value(), vscp); + } +} + //====================================================================== // Per-type Debugging @@ -2133,6 +2238,16 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, immediate); dumpJsonBoolFuncIf(str, senFromAlways); } +void AstCover::dump(std::ostream& str) const { + this->AstNodeCoverOrAssert::dump(str); + if (isCoverSeq()) str << " [COVERSEQ]"; + if (isSeqEvent()) str << " [SEQEVENT]"; +} +void AstCover::dumpJson(std::ostream& str) const { + dumpJsonBoolFuncIf(str, isCoverSeq); + dumpJsonBoolFuncIf(str, isSeqEvent); + this->AstNodeCoverOrAssert::dumpJson(str); +} void AstClocking::dump(std::ostream& str) const { this->AstNode::dump(str); if (isDefault()) str << " [DEFAULT]"; @@ -2724,6 +2839,10 @@ void AstNodeArrayDType::dumpJson(std::ostream& str) const { dumpJsonStr(str, "declRange", cvtToStr(declRange())); dumpJsonGen(str); } +void AstNodeAssign::dump(std::ostream& str) const { + this->AstNode::dump(str); + if (timingControlp()) str << " [TIMING=" << nodeAddr(timingControlp()) << "]"; +} string AstPackArrayDType::prettyDTypeName(bool full) const { std::ostringstream os; if (const auto subp = subDTypep()) os << subp->prettyDTypeName(full); @@ -3138,6 +3257,7 @@ int AstVarRef::instrCount() const { } void AstVar::dump(std::ostream& str) const { this->AstNode::dump(str); + if (constPoolEntry()) str << " [CONSTPOOL]"; if (isSc()) str << " [SC]"; if (isPrimaryIO()) str << (isInout() ? " [PIO]" : (isWritable() ? " [PO]" : " [PI]")); if (isPrimaryClock()) str << " [PCLK]"; @@ -3158,7 +3278,6 @@ void AstVar::dump(std::ostream& str) const { if (noReset()) str << " [!RST]"; if (processQueue()) str << " [PROCQ]"; if (sampled()) str << " [SAMPLED]"; - if (attrIsolateAssign()) str << " [aISO]"; if (attrFsmState()) str << " [aFSMSTATE]"; if (attrFsmResetArc()) str << " [aFSMRESETARC]"; if (attrFsmArcInclCond()) str << " [aFSMARCCOND]"; @@ -3169,6 +3288,7 @@ void AstVar::dump(std::ostream& str) const { str << " [FUNC]"; } if (hasUserInit()) str << " [UINIT]"; + if (icoMaybeWritten()) str << " [ICOMAYBEWRITTEN]"; if (isDpiOpenArray()) str << " [DPIOPENA]"; if (ignorePostWrite()) str << " [IGNPWR]"; if (ignoreSchedWrite()) str << " [IGNWR]"; @@ -3179,6 +3299,7 @@ void AstVar::dump(std::ostream& str) const { void AstVar::dumpJson(std::ostream& str) const { dumpJsonStrFunc(str, origName); dumpJsonStrFunc(str, verilogName); + dumpJsonBoolFuncIf(str, constPoolEntry); dumpJsonBoolFuncIf(str, isSc); dumpJsonBoolFuncIf(str, isPrimaryIO); dumpJsonBoolFuncIf(str, isPrimaryClock); @@ -3193,11 +3314,11 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, noReset); dumpJsonBoolFuncIf(str, processQueue); dumpJsonBoolFuncIf(str, sampled); - dumpJsonBoolFuncIf(str, attrIsolateAssign); dumpJsonBoolFuncIf(str, attrFsmState); dumpJsonBoolFuncIf(str, attrFsmResetArc); dumpJsonBoolFuncIf(str, attrFsmArcInclCond); dumpJsonBoolFuncIf(str, attrFileDescr); + dumpJsonBoolFuncIf(str, icoMaybeWritten); dumpJsonBoolFuncIf(str, isDpiOpenArray); dumpJsonBoolFuncIf(str, isFuncReturn); dumpJsonBoolFuncIf(str, isFuncLocal); diff --git a/src/V3SplitAs.h b/src/V3AstPatterns.h similarity index 67% rename from src/V3SplitAs.h rename to src/V3AstPatterns.h index dc5c4cce6..451a518d4 100644 --- a/src/V3SplitAs.h +++ b/src/V3AstPatterns.h @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: Break always into separate statements to reduce temps +// DESCRIPTION: Verilator: Dump AstNodeExpr patterns for analysis // // Code available from: https://verilator.org // @@ -14,8 +14,8 @@ // //************************************************************************* -#ifndef VERILATOR_V3SPLITAS_H_ -#define VERILATOR_V3SPLITAS_H_ +#ifndef VERILATOR_V3ASTPATTERNS_H_ +#define VERILATOR_V3ASTPATTERNS_H_ #include "config_build.h" #include "verilatedos.h" @@ -24,9 +24,11 @@ class AstNetlist; //============================================================================ -class V3SplitAs final { +class V3AstPatterns final { public: - static void splitAsAll(AstNetlist* nodep) VL_MT_DISABLED; + // Accumulate the patterns in the netlist, and dump the collected + // statistics to '__ast_patterns_.txt'. + static void dumpAll(const AstNetlist* nodep, const std::string& suffix) VL_MT_DISABLED; }; #endif // Guard diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 36e151053..5cce373b0 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -61,7 +61,6 @@ class BeginVisitor final : public VNVisitor { // NODE STATE // AstCase::user1 -> bool, if already purified - V3UniqueNames m_caseTempNames; // For generating unique temporary variable names used by cases // STATE - across all visitors BeginState* const m_statep; // Current global state @@ -75,7 +74,6 @@ class BeginVisitor final : public VNVisitor { string m_unnamedScope; // Name of begin blocks, including unnamed blocks int m_ifDepth = 0; // Current if depth bool m_keepBegins = false; // True if begins should not be inlined - VDouble0 m_statPurifiedCaseExpr; // Count of purified case expressions // METHODS @@ -132,7 +130,6 @@ class BeginVisitor final : public VNVisitor { } void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); - VL_RESTORER(m_caseTempNames); m_modp = nodep; // Rename it (e.g. class under a generate) if (m_unnamedScope != "") { @@ -140,12 +137,9 @@ class BeginVisitor final : public VNVisitor { UINFO(8, " rename to " << nodep->name()); m_statep->userMarkChanged(nodep); } - VL_RESTORER(m_displayScope); - VL_RESTORER(m_namedScope); - VL_RESTORER(m_unnamedScope); - m_displayScope = ""; - m_namedScope = ""; - m_unnamedScope = ""; + VL_RESTORER_CLEAR(m_displayScope); + VL_RESTORER_CLEAR(m_namedScope); + VL_RESTORER_CLEAR(m_unnamedScope); iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { @@ -166,15 +160,13 @@ class BeginVisitor final : public VNVisitor { // naming; so that any begin's inside the function will rename // inside the function. // Process children - VL_RESTORER(m_displayScope); + VL_RESTORER_COPY(m_displayScope); VL_RESTORER(m_ftaskp); VL_RESTORER(m_liftedp); - VL_RESTORER(m_namedScope); - VL_RESTORER(m_unnamedScope); - VL_RESTORER(m_caseTempNames); + VL_RESTORER_CLEAR(m_namedScope); + VL_RESTORER_CLEAR(m_unnamedScope); m_displayScope = dot(m_displayScope, nodep->name()); - m_namedScope = ""; - m_unnamedScope = ""; + m_ftaskp = nodep; m_liftedp = nullptr; iterateChildren(nodep); @@ -195,9 +187,9 @@ class BeginVisitor final : public VNVisitor { void visit(AstGenBlock* nodep) override { // GenBlocks were only useful in variable creation, change names and delete UINFO(8, " " << nodep); - VL_RESTORER(m_displayScope); - VL_RESTORER(m_namedScope); - VL_RESTORER(m_unnamedScope); + VL_RESTORER_COPY(m_displayScope); + VL_RESTORER_COPY(m_namedScope); + VL_RESTORER_COPY(m_unnamedScope); UASSERT_OBJ(!m_keepBegins, nodep, "Should be able to eliminate all AstGenBlock"); dotNames(nodep->name(), nodep->fileline(), "__BEGIN__"); iterateAndNextNull(nodep->itemsp()); @@ -231,9 +223,9 @@ class BeginVisitor final : public VNVisitor { void visit(AstBegin* nodep) override { // Begin blocks were only useful in variable creation, change names and delete UINFO(8, " " << nodep); - VL_RESTORER(m_displayScope); - VL_RESTORER(m_namedScope); - VL_RESTORER(m_unnamedScope); + VL_RESTORER_COPY(m_displayScope); + VL_RESTORER_COPY(m_namedScope); + VL_RESTORER_COPY(m_unnamedScope); { VL_RESTORER(m_keepBegins); m_keepBegins = false; @@ -265,9 +257,9 @@ class BeginVisitor final : public VNVisitor { void visit(AstNodeBlock* nodep) override { // Begin/Fork blocks were only useful in variable creation, change names and delete UINFO(8, " " << nodep); - VL_RESTORER(m_displayScope); - VL_RESTORER(m_namedScope); - VL_RESTORER(m_unnamedScope); + VL_RESTORER_COPY(m_displayScope); + VL_RESTORER_COPY(m_namedScope); + VL_RESTORER_COPY(m_unnamedScope); { VL_RESTORER(m_keepBegins); m_keepBegins = VN_IS(nodep, Fork); @@ -415,29 +407,7 @@ class BeginVisitor final : public VNVisitor { // any BEGINs, but V3Coverage adds them all under the module itself. iterateChildren(nodep); } - void visit(AstCase* nodep) override { - // Introduce temporary variable for AstCase if needed - it is done here and not in V3Case - // because this phase is before V3Scope and V3Case is not. Doing it before V3Scope ensures - // that V3Scope will take care of a scope creation - if (!nodep->exprp()->isPure() && !nodep->user1SetOnce()) { - ++m_statPurifiedCaseExpr; - FileLine* const fl = nodep->exprp()->fileline(); - AstVar* const varp = new AstVar{fl, VVarType::XTEMP, m_caseTempNames.get(nodep), - nodep->exprp()->dtypep()}; - nodep->exprp(new AstExprStmt{fl, - new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, - nodep->exprp()->unlinkFrBack()}, - new AstVarRef{fl, varp, VAccess::READ}}); - if (m_ftaskp) { - varp->funcLocal(true); - varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_ftaskp->stmtsp()->addHereThisAsNext(varp); - } else { - m_modp->stmtsp()->addHereThisAsNext(varp); - } - } - iterateChildren(nodep); - } + void visit(AstCase* nodep) override { iterateChildren(nodep); } // VISITORS - LINT CHECK void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered VL_RESTORER(m_keepBegins); @@ -464,10 +434,8 @@ class BeginVisitor final : public VNVisitor { public: // CONSTRUCTORS BeginVisitor(AstNetlist* nodep, BeginState* statep) - : m_caseTempNames{"__VCase"} - , m_statep{statep} { + : m_statep{statep} { iterate(nodep); - V3Stats::addStatSum("Impure case expressions", m_statPurifiedCaseExpr); } ~BeginVisitor() override = default; }; diff --git a/src/V3Broken.cpp b/src/V3Broken.cpp index fab0077cc..ba5aa0135 100644 --- a/src/V3Broken.cpp +++ b/src/V3Broken.cpp @@ -252,18 +252,15 @@ private: void visit(AstScope* nodep) override { VL_RESTORER(m_inScope); m_inScope = true; - VL_RESTORER(m_cFuncNames); - m_cFuncNames.clear(); + VL_RESTORER_CLEAR(m_cFuncNames); processAndIterate(nodep); } void visit(AstNodeModule* nodep) override { - VL_RESTORER(m_cFuncNames); - m_cFuncNames.clear(); + VL_RESTORER_CLEAR(m_cFuncNames); processAndIterate(nodep); } void visit(AstNodeUOrStructDType* nodep) override { - VL_RESTORER(m_cFuncNames); - m_cFuncNames.clear(); + VL_RESTORER_CLEAR(m_cFuncNames); processAndIterate(nodep); } void visit(AstNodeVarRef* nodep) override { diff --git a/src/V3CUse.cpp b/src/V3CUse.cpp index 2fddf7f82..51d6fd688 100644 --- a/src/V3CUse.cpp +++ b/src/V3CUse.cpp @@ -65,6 +65,7 @@ class CUseVisitor final : public VNVisitorConst { iterateConst(nodep->lhsp()->dtypep()); } void visit(AstNodeDType* nodep) override { + if (nodep->user1SetOnce()) return; // Process once if (nodep->virtRefDTypep()) iterateConst(nodep->virtRefDTypep()); if (nodep->virtRefDType2p()) iterateConst(nodep->virtRefDType2p()); diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 4eca73e01..146f81a89 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -42,10 +42,6 @@ VL_DEFINE_DEBUG_FUNCTIONS; -#define CASE_OVERLAP_WIDTH 16 // Maximum width we can check for overlaps in -#define CASE_BARF 999999 // Magic width when non-constant -#define CASE_ENCODER_GROUP_DEPTH 8 // Levels of priority to be ORed together in top IF tree - //###################################################################### class CaseLintVisitor final : public VNVisitorConst { @@ -119,252 +115,782 @@ class CaseLintVisitor final : public VNVisitorConst { } void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } -public: // CONSTRUCTORS explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); } explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); } ~CaseLintVisitor() override = default; + +public: + static void apply(AstCase* nodep) { CaseLintVisitor{nodep}; } + static void apply(AstGenCase* nodep) { CaseLintVisitor{nodep}; } }; //###################################################################### // Case state, as a visitor of each AstNode class CaseVisitor final : public VNVisitor { - // NODE STATE - // Cleared each Case - // AstIf::user3() -> bool. Set true to indicate clone not needed - const VNUser3InUse m_inuser3; + // Maximum width for detailed analysis + constexpr static int CASE_DETAILS_MAX_WIDTH = 16; + // Levels of priority to be ORed together in top IF tree + constexpr static int CASE_ENCODER_GROUP_DEPTH = 8; + // Maximum size for tiny lookup tables - materialized in code + constexpr static size_t CASE_TABLE_TINY_BITS = 32; // Up to 2 instructions to materialize + // Maximum size for normal lookup tables - stored in constant pool + constexpr static size_t CASE_TABLE_MAX_BITS = 1ULL << 16; // 64Kbits / 8KBytes + // Minimum number of the branches a table must replace to be worth a load + constexpr static size_t CASE_TABLE_MIN_BRANCHES = 3; + + // TYPES + // Record for each case value + struct CaseRecord final { + AstCaseItem* itemp; // Case item that covers value + AstConst* constp; // Expression within 'itemp' that covers value (nullptr for default) + AstNode* stmtsp; // Statements of 'itemp' (might be nullptr if case is empty) + }; + + // Record for each LHS of a decoder pattern + struct LhsRecord final { + AstNodeExpr* lhsp = nullptr; // LHS of the assignment + AstNodeAssign* preDefaultp = nullptr; // Default assignment *before the case statement* + size_t nCaseAssigns = 0; // Number of AstAssigns to this LHS in case clauses + size_t nCaseAssignDlys = 0; // Number of AstAssignDlys to this LHS in case clauses + size_t offset = 0; // Offset in the table for this LHS + + static size_t s_nextId; // Static unique Id counter + size_t id = ++s_nextId; // Unique Id for sorting + }; + + // NODE STATE: + // AstVarScope::user1() -> bool: true if written to, only in parts of analysis phase // STATE - VDouble0 m_statCaseFast; // Statistic tracking - VDouble0 m_statCaseSlow; // Statistic tracking + // Statistics tracking, as a struct so can be passed to 'const' methods + struct Stats final { + VDouble0 caseDecoder; // Cases using decoder method + VDouble0 caseTableNormal; // Cases using table method with normal table + VDouble0 caseTableTiny; // Cases using table method with tiny table + VDouble0 caseFast; // Cases using fast bit tree method + VDouble0 caseGeneric; // Cases using generic if/else tree method + VDouble0 provenAssertions; // Assertions proven to hold + } m_stats; const AstNode* m_alwaysp = nullptr; // Always in which case is located + size_t m_nTmps = 0; // Sequence numbers for temporary variables + AstScope* m_scopep = nullptr; // Current scope - // Per-CASE - int m_caseWidth = 0; // Width of valueItems - int m_caseItems = 0; // Number of caseItem unique values - bool m_caseIncomplete = false; // Proven incomplete - bool m_caseNoOverlapsAllCovered = false; // Proven to be synopsys parallel_case compliant - // For each possible value, the case branch we need - std::array m_valueItem; - bool m_needToClearCache = false; // Whether cache needs to be cleared + // STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise + bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions) + bool m_caseHasDefault = false; // Indicates the case statement has a default case + size_t m_caseNCaseItems = 0; // Number of AstCaseItems in the case statement + size_t m_caseNConditions = 0; // Number of conditions in the case statement + // Map from LHSs of decoder pattern to corresponding LhsRecord. + std::unordered_map, LhsRecord> m_caseLhsRecords; + // Values of 'm_caseLhsRecords' in sorted order, if case statement is a decoder pattern + std::vector m_caseDecoderRecords; + size_t m_caseDecoderEntryWidth = 0; // Width of each entry in the decoder table + size_t m_caseTableWidth = 0; // Total width of the case table - 0 means can't optimize + bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid + struct final { + bool exhaustive = false; // Proven exhaustive + bool exhaustiveOverEnumOnly = false; // Exhaustive over enum values only + bool noOverlaps = false; // Proven no overlaps between cases + // Map from value (index) to the CaseRecord that covers this value + std::array records; + } m_caseDetails; // METHODS - //! Determine whether we should check case items are complete - //! @return Enum's dtype if should check, nullptr if shouldn't - const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { + + // Xs in case or casez are impossible due to two state simulations. + // Returns true if the item is never possible + static bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { + const AstConst* const constp = VN_CAST(itemExprp, Const); + if (!constp) return false; + if (casep->casex() || casep->caseInside()) return false; + if (casep->casez()) return constp->num().isAnyX(); + return constp->num().isFourState(); + } + + // Returns the matching mask and bits for a case item constant condition + // TODO: with 'neverItem' checked, this is alway the same for all types of cases + // 4-state will have to fix this up + static std::pair matchPattern(const AstCase* /*casep*/, + const AstConst* condp) { + // As one to encourage NRVO/move + std::pair pairMaskBits{ + std::piecewise_construct, // + std::forward_as_tuple(condp->fileline(), condp->width(), 0), + std::forward_as_tuple(condp->fileline(), condp->width(), 0)}; + pairMaskBits.first.opBitsNonXZ(condp->num()); + pairMaskBits.second.opBitsOne(condp->num()); + return pairMaskBits; + } + + // If the given statement is an assignment that fits the decoder pattern, + // return it, otherwise return nullptr + static AstNodeAssign* checkDecoderAssign(AstNode* stmtp) { + // Only Assign and AssignDly are supported + if (!VN_IS(stmtp, Assign) && !VN_IS(stmtp, AssignDly)) return nullptr; + AstNodeAssign* const assp = VN_AS(stmtp, NodeAssign); + // Only if no timing control + if (assp->timingControlp()) return nullptr; + // Only if assigning a constant + if (!VN_IS(assp->rhsp(), Const)) return nullptr; + // Only if it's a packed value + AstNodeDType* const dtypep = assp->rhsp()->dtypep(); + if (dtypep->isString() || dtypep->isDouble()) return nullptr; + // Only if the LHS has no reads (can be relaxed, but need to prove there is no r/w hazard) + if (assp->lhsp()->exists([](AstVarRef* refp) { return refp->access().isReadOrRW(); })) { + return nullptr; + } + // This is an assignment that fits the decoder pattern + return assp; + } + + // Analyze if the given case item fits the decoder pattern, return true iff so. + // Updates 'm_caseLhsRecords'. + bool analyzeDecoderCaseItem(AstCaseItem* cip) { + // AstVarScope::user1() -> bool: true if written to + const VNUser1InUse user1InUse; + for (AstNode* stmtp = cip->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + // Must be an assignment that fits the decoder pattern + AstNodeAssign* const assp = checkDecoderAssign(stmtp); + if (!assp) return false; + // Must assign each LHS exactly once - RHS is Const + const bool multipleAssignments = assp->lhsp()->exists([](AstVarRef* refp) { // + return refp->varScopep()->user1SetOnce(); + }); + if (multipleAssignments) return false; + // Update LhsRecord + LhsRecord& lhsRecord = m_caseLhsRecords[*assp->lhsp()]; + if (!lhsRecord.lhsp) lhsRecord.lhsp = assp->lhsp(); + lhsRecord.nCaseAssigns += VN_IS(assp, Assign); + lhsRecord.nCaseAssignDlys += VN_IS(assp, AssignDly); + } + return true; + } + + // Determine whether we should check case items are complete + // Returns enum's dtype if should check, nullptr if shouldn't + static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr; const AstEnumDType* const enumDtp = VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType); if (!enumDtp) return nullptr; // Case isn't enum const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp(); if (!basicp) return nullptr; // Not simple type (perhaps IEEE illegal) - if (basicp->width() > 32) return nullptr; return enumDtp; } - //! @return True if case items are complete, false if there are uncovered enums - bool checkCaseEnumComplete(const AstCase* const nodep, const AstEnumDType* const dtype) { - const uint32_t numCases = 1UL << m_caseWidth; - for (AstEnumItem* itemp = dtype->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), EnumItem)) { - AstConst* const econstp = VN_AS(itemp->valuep(), Const); - V3Number nummask{itemp, econstp->width()}; - nummask.opBitsNonX(econstp->num()); - const uint32_t mask = nummask.toUInt(); - V3Number numval{itemp, econstp->width()}; - numval.opBitsOne(econstp->num()); - const uint32_t val = numval.toUInt(); + // Check and warn if case items are not complete over the given enum type. + // Returns true iff the case items cover all enum values/patterns. + bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) { + const uint32_t numCases = 1UL << nodep->exprp()->width(); + bool fullyCovered = true; + string missingItems; + for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) { + const auto& match = matchPattern(nodep, VN_AS(eip->valuep(), Const)); + const uint32_t mask = match.first.toUInt(); + const uint32_t bits = match.second.toUInt(); + + // Check all cases to see if they cover this enum value/pattern for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) == val) { - if (!m_valueItem[i]) { - if (!nodep->unique0Pragma()) - nodep->v3warn(CASEINCOMPLETE, "Enum item " - << itemp->prettyNameQ() - << " not covered by case\n"); - m_caseIncomplete = true; - return false; // enum has uncovered value by case items - } - } + if ((i & mask) != bits) continue; // This case is not for this enum value + if (m_caseDetails.records[i].itemp) continue; // Covered case + if (!missingItems.empty()) missingItems += ", "; + missingItems += eip->prettyNameQ(); + fullyCovered = false; + break; } } - return true; // enum is fully covered + if (!missingItems.empty() && !nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, "Enum item(s) not covered by case: " << missingItems); + } + return fullyCovered; // enum is fully covered } - bool isCaseTreeFast(AstCase* nodep) { - int width = 0; - bool opaque = false; - m_caseItems = 0; - m_caseNoOverlapsAllCovered = true; - for (AstCaseItem* itemp = nodep->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), CaseItem)) { - for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { - if (icondp->width() > width) width = icondp->width(); - if (icondp->isDouble()) opaque = true; - if (!VN_IS(icondp, Const)) width = CASE_BARF; // Can't parse; not a constant - m_caseItems++; + + // Check and warn if case items are not complete over all possible values. + // Returns true iff the case items cover all values of the case expression. + bool checkExhaustivePacked(AstCase* nodep) { + const uint32_t numCases = 1UL << nodep->exprp()->width(); + for (uint32_t i = 0; i < numCases; ++i) { + if (m_caseDetails.records[i].itemp) continue; // Covered case + if (!nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, + "Case values incompletely covered (example pattern 0x" << std::hex + << i << ")"); } + // TODO: warn for more than one uncovered case, not just the first + return false; } - m_caseWidth = width; - if (width == 0 || width > CASE_OVERLAP_WIDTH || opaque) { - m_caseNoOverlapsAllCovered = false; - return false; // Too wide for analysis + + // It's an exhaustive case statement + return true; + } + + // Analyze each value in the case statement. Updates 'm_caseDetails' and issues warnings. + void analyzeCaseDetails(AstCase* nodep) { + const uint32_t numValues = 1UL << nodep->exprp()->width(); + // Clear case records + for (uint32_t i = 0; i < numValues; ++i) { + m_caseDetails.records[i].itemp = nullptr; + m_caseDetails.records[i].constp = nullptr; + m_caseDetails.records[i].stmtsp = nullptr; } - UINFO(8, "Simple case statement: " << nodep); - const uint32_t numCases = 1UL << m_caseWidth; - // Zero list of items for each value - for (uint32_t i = 0; i < numCases; ++i) m_valueItem[i] = nullptr; // Now pick up the values for each assignment // We can cheat and use uint32_t's because we only support narrow case's bool reportedOverlap = false; - bool reportedSubcase = false; - bool hasDefaultCase = false; - std::map caseItemMap; // case condition -> case item + bool hasDefault = false; + m_caseDetails.noOverlaps = true; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { - // UINFOTREE(9, icondp, "", "caseitem"); - AstConst* const iconstp = VN_AS(icondp, Const); - UASSERT_OBJ(iconstp, nodep, "above 'can't parse' should have caught this"); - if (neverItem(nodep, iconstp)) { - // X in casez can't ever be executed + + // Default case + if (itemp->isDefault()) { + // Default was moved to be the last item by V3LinkDot. Fill remaining cases + for (uint32_t i = 0; i < numValues; ++i) { + CaseRecord& caseRecord = m_caseDetails.records[i]; + if (!caseRecord.itemp) { + caseRecord.itemp = itemp; + caseRecord.stmtsp = itemp->stmtsp(); + } + } + hasDefault = true; + continue; + } + + for (AstConst* iconstp = VN_AS(itemp->condsp(), Const); iconstp; + iconstp = VN_AS(iconstp->nextp(), Const)) { + // Some items can never match due to 2-state simulation + if (neverItem(nodep, iconstp)) continue; + + const auto& match = matchPattern(nodep, iconstp); + const uint32_t mask = match.first.toUInt(); + const uint32_t bits = match.second.toUInt(); + + bool foundNewCase = false; + const AstConst* firstOverlapConstp = nullptr; + uint32_t firstOverlapValue = 0; + for (uint32_t i = 0; i < numValues; ++i) { + if ((i & mask) != bits) continue; + + CaseRecord& caseRecord = m_caseDetails.records[i]; + + // If this is the first case that covers this value, record it + if (!caseRecord.itemp) { + caseRecord.itemp = itemp; + caseRecord.constp = iconstp; + caseRecord.stmtsp = itemp->stmtsp(); + foundNewCase = true; + continue; + } + + // There is an overlap. If it's within the same CaseItem, it is legal + if (caseRecord.itemp == itemp) continue; + + // Otherwise record the first overlapping case + if (!firstOverlapConstp) { + firstOverlapConstp = caseRecord.constp; + firstOverlapValue = i; + m_caseDetails.noOverlaps = false; + } + } + + // Only report first overlap + if (reportedOverlap || !firstOverlapConstp) continue; + + // Report first overlap + if (nodep->priorityPragma()) { + // If this is a priority case, we only want to complain if every possible + // value for this item is already hit by some other item. This is true if + // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only + // covering item is this item itself, which is legal overlap within one + // item. + if (!foundNewCase) { + iconstp->v3warn(CASEOVERLAP, + "Case item ignored: every matching value is covered " + "by an earlier condition\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of previous condition\n" + << firstOverlapConstp->warnContextPrimary()); + reportedOverlap = true; + } } else { - const bool isCondWildcard = iconstp->num().isAnyXZ(); - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - const uint32_t mask = nummask.toUInt(); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - const uint32_t val = numval.toUInt(); - - uint32_t firstOverlap = 0; - const AstNode* overlappedCondp = nullptr; - bool foundHit = false; - for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) == val) { - if (!m_valueItem[i]) { - m_valueItem[i] = icondp; - caseItemMap[icondp] = itemp; - foundHit = true; - } else if (!overlappedCondp) { - // Overlapping case item expressions in the - // same case item are legal - if (caseItemMap[m_valueItem[i]] != itemp) { - firstOverlap = i; - overlappedCondp = m_valueItem[i]; - m_caseNoOverlapsAllCovered = false; - } - } - } - } - if (!nodep->priorityPragma()) { - // If this case statement doesn't have the priority - // keyword, we want to warn on any overlap. - if (!reportedOverlap && overlappedCondp) { - std::ostringstream examplePattern; - if (isCondWildcard) { - examplePattern << " (example pattern 0x" << std::hex - << firstOverlap << ")"; - } - icondp->v3warn(CASEOVERLAP, - "Case conditions overlap" - << examplePattern.str() << "\n" - << icondp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of overlapping condition\n" - << overlappedCondp->warnContextSecondary()); - reportedOverlap = true; - } - } else { - // If this is a priority case, we only want to complain - // if every possible value for this item is already hit - // by some other item. This is true if foundHit is - // false. - if (!reportedSubcase && !foundHit) { - icondp->v3warn(CASEOVERLAP, - "Case item ignored: every matching value is covered " - "by an earlier condition\n" - << icondp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of previous condition\n" - << overlappedCondp->warnContextPrimary()); - reportedSubcase = true; - } + // If this case statement doesn't have the priority keyword, + // we want to warn on any overlap. + std::ostringstream examplePattern; + if (iconstp->num().isAnyXZ()) { + examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue + << ")"; } + iconstp->v3warn(CASEOVERLAP, + "Case conditions overlap" + << examplePattern.str() << "\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of overlapping condition\n" + << firstOverlapConstp->warnContextSecondary()); + reportedOverlap = true; } } - // Defaults were moved to last in the caseitem list by V3LinkDot - if (itemp->isDefault()) { // Case statement's default... Fill the table - for (uint32_t i = 0; i < numCases; ++i) { - if (!m_valueItem[i]) m_valueItem[i] = itemp; - } - caseItemMap[itemp] = itemp; - hasDefaultCase = true; - } } - if (!hasDefaultCase) { - const AstEnumDType* const dtype = getEnumCompletionCheckDType(nodep); - if (dtype) { - if (!checkCaseEnumComplete(nodep, dtype)) { - // checkCaseEnumComplete has already warned of incompletion - m_caseNoOverlapsAllCovered = false; - return false; - } + + // If there was no default, check exhaustiveness + m_caseDetails.exhaustiveOverEnumOnly = false; + m_caseDetails.exhaustive = hasDefault; + if (!hasDefault) { + if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) { + // Only checks enum values are covered, not all bit patterns of the case expression + const bool exhaustiveOverEnum = checkExhaustiveEnum(nodep, enump); + m_caseDetails.exhaustiveOverEnumOnly = exhaustiveOverEnum; + m_caseDetails.exhaustive = exhaustiveOverEnum; } else { - for (uint32_t i = 0; i < numCases; ++i) { - if (!m_valueItem[i]) { // has uncovered case - if (!nodep->unique0Pragma()) - nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered " - "(example pattern 0x" - << std::hex << i << ")"); - m_caseIncomplete = true; - m_caseNoOverlapsAllCovered = false; - return false; - } - } + m_caseDetails.exhaustive = checkExhaustivePacked(nodep); } } - if (m_caseItems <= 3 - // Avoid e.g. priority expanders from going crazy in expansion - || (m_caseWidth >= 8 && (m_caseItems <= (m_caseWidth + 1)))) { - return false; // Not worth simplifying - } - - // Convert valueItem from AstCaseItem* to the expression - // Not done earlier, as we may now have a nullptr because it's just a ";" NOP branch - for (uint32_t i = 0; i < numCases; ++i) { - if (AstNode* const condp = m_valueItem[i]) { - const AstCaseItem* const caseItemp = caseItemMap[condp]; - UASSERT_OBJ(caseItemp, condp, "caseItemp should exist"); - m_valueItem[i] = caseItemp->stmtsp(); - } - } - return true; // All is fine + // Records now valid + m_caseDetailsValid = true; } - AstNode* replaceCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) { - if (msb < 0) { - // There's no space for a IF. We know upperValue is thus down to a specific - // exact value, so just return the tree value - // Note can't clone here, as we're going to check for equivalence above - AstNode* const foundp = m_valueItem[upperValue]; - return foundp; - } else { - // Make left and right subtrees - // cexpr[msb:lsb] == 1 - AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue); - AstNode* tree1p = replaceCaseFastRecurse( - cexprp, msb - 1, upperValue | (1UL << static_cast(msb))); + void analyzeDecoderPattern(AstCase* nodep) { + // Check each LHS record + for (auto it = m_caseLhsRecords.cbegin(); it != m_caseLhsRecords.cend();) { + const LhsRecord& lhsRecord = it->second; - if (tree0p == tree1p) { - // Same logic on both sides, so we can just return one of 'em - return tree0p; + // Delete records that have no assignments in any case item (only pre-defaults) + if (!lhsRecord.nCaseAssigns && !lhsRecord.nCaseAssignDlys) { + it = m_caseLhsRecords.erase(it); + continue; } - // We could have a "checkerboard" with A B A B, we can use the same IF on both edges + ++it; + + // If mixed assignments, it's not a decoder pattern + if (lhsRecord.nCaseAssigns && lhsRecord.nCaseAssignDlys) return; + + // If assigned in all branches, it's good - but only if every table entry will be + // covered, i.e. the case has a default, or is exhaustive over all bit patterns. + // Enum-only exhaustiveness is not enough: out-of-enum values leave entries + // uncovered. + if (m_caseHasDefault + || (m_caseDetailsValid && m_caseDetails.exhaustive + && !m_caseDetails.exhaustiveOverEnumOnly)) { + if (lhsRecord.nCaseAssigns == m_caseNCaseItems) continue; + if (lhsRecord.nCaseAssignDlys == m_caseNCaseItems) continue; + } + + // Otherwise it needs to have a pre-default assignment + AstNode* const preDefaultp = lhsRecord.preDefaultp; + if (!preDefaultp) return; + // And the pre-default needs to be the same type + if (lhsRecord.nCaseAssigns && !VN_IS(preDefaultp, Assign)) return; + if (lhsRecord.nCaseAssignDlys && !VN_IS(preDefaultp, AssignDly)) return; + } + // All cases check out, can optimize if there are some entries left + if (m_caseLhsRecords.empty()) return; + + // Gather all the LhsRecords and sort them - there is a copy here, it's ok, won't be many + m_caseDecoderRecords.reserve(m_caseLhsRecords.size()); + for (const auto& item : m_caseLhsRecords) m_caseDecoderRecords.emplace_back(item.second); + std::sort(m_caseDecoderRecords.begin(), m_caseDecoderRecords.end(), + [](const LhsRecord& a, const LhsRecord& b) { + // Sort by width, then id + const int aWidth = a.lhsp->width(); + const int bWidth = b.lhsp->width(); + if (aWidth != bWidth) return aWidth < bWidth; + return a.id < b.id; + }); + + // We can either create a single lookup table for all LHSs, or one for each LHS. + // With a single table, we need to select out of the lookup via a temporary variable. + // With one table per LHS, we need to do multiple loads. The table is likely to incur a + // D-cache miss on large designs, so we choose single table. + + const int caseWidth = nodep->exprp()->width(); + + // Safely check if table with 'entryWidth' entries would fit within 'maxWidth' bits + const auto fitsLimit = [&](size_t entryWidth, size_t maxWidth) -> bool { + size_t totalWidth = entryWidth; + // Multiply cases - iterative to avoid overflow + for (int i = 0; i < caseWidth; ++i) { + totalWidth <<= 1; + if (totalWidth > maxWidth) return false; + } + return true; + }; + + // Check if the whole table would fit in a tiny table packed tightly + m_caseDecoderEntryWidth = 0; + for (LhsRecord& lhsRecord : m_caseDecoderRecords) { + lhsRecord.offset = m_caseDecoderEntryWidth; + m_caseDecoderEntryWidth += lhsRecord.lhsp->width(); + } + // If it fits, we will pack it tightly + if (fitsLimit(m_caseDecoderEntryWidth, CASE_TABLE_TINY_BITS)) { + m_caseTableWidth = m_caseDecoderEntryWidth << caseWidth; // Can optimize + return; + } + + // Tabel will be bigish. To avoid expensive bit swizzling, align each entry to a + // word boundary if it would cross a word boundary. + m_caseDecoderEntryWidth = 0; + for (LhsRecord& lhsRecord : m_caseDecoderRecords) { + const size_t width = lhsRecord.lhsp->width(); + const size_t lsbWord = VL_BITWORD_E(m_caseDecoderEntryWidth); + const size_t msbWord = VL_BITWORD_E(m_caseDecoderEntryWidth + width - 1); + if (lsbWord != msbWord) { + m_caseDecoderEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; + } + lhsRecord.offset = m_caseDecoderEntryWidth; + m_caseDecoderEntryWidth += width; + } + // Check the table fits max size + const size_t alignedEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; + if (fitsLimit(alignedEntryWidth, CASE_TABLE_MAX_BITS)) { + m_caseTableWidth = alignedEntryWidth << caseWidth; // Can optimize + return; + } + + // Can optimize as AstMatchMasked, no other info needed + } + + // Analyze case statement. Updates 'm_case*' members. Reports warnings. + void analyzeCase(AstCase* nodep) { + // Reset all analysis results + m_caseOpaque = false; + m_caseHasDefault = false; + m_caseNCaseItems = 0; + m_caseNConditions = 0; + m_caseDecoderRecords.clear(); + m_caseDecoderEntryWidth = 0; + m_caseTableWidth = 0; + m_caseLhsRecords.clear(); + m_caseDetailsValid = false; + + AstNode* const caseExprp = nodep->exprp(); + + // Mark opaque if not a packed value - TODO: can this be a class? + if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true; + + // Gather pre-default assignments of decoder pattern + { + // AstVarScope::user1() -> bool: true if written to + const VNUser1InUse user1InUse; + for (AstNode* prevp = nodep->prevp(); prevp; prevp = prevp->prevp()) { + AstNodeAssign* const assp = checkDecoderAssign(prevp); + if (!assp) break; // Stop if not a decoder assignment + // Stop if multiple assignments + const bool multipleAssignments = assp->lhsp()->exists([&](AstVarRef* refp) { // + return refp->varScopep()->user1SetOnce(); + }); + if (multipleAssignments) break; + // Store pre-default assignment + LhsRecord& lhsRecord = m_caseLhsRecords[*assp->lhsp()]; + lhsRecord.lhsp = assp->lhsp(); + lhsRecord.preDefaultp = assp; + } + } + + // Check each case item + bool canBeDecoder = true; + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + // Check conditions + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + // Count conditions that can actually match. + if (!neverItem(nodep, VN_AS(condp, NodeExpr))) ++m_caseNConditions; + // Mark opaque if non-constant condition + if (!VN_IS(condp, Const)) { + m_caseOpaque = true; + canBeDecoder = false; // Can't be a decoder if opaque + } + } + // Check if it has a default case + if (cip->isDefault()) m_caseHasDefault = true; + // Count case items + ++m_caseNCaseItems; + // Check if it fits the decoder pattern, if still possible + if (canBeDecoder) canBeDecoder = analyzeDecoderCaseItem(cip); + } + + // Nothing else to do if not a packed type, or non-const conditions + if (m_caseOpaque) return; + + // If small enough, analyse details + if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep); + + // Check if it actually fits a full decoder pattern + if (canBeDecoder) analyzeDecoderPattern(nodep); + } + + AstNodeStmt* connectDecoderOutputs(AstCase* nodep, AstNodeExpr* exprp, + const char* tmpPrefixp) { + FileLine* const flp = nodep->fileline(); + + // If there is only one LHS, just use the result + if (m_caseDecoderRecords.size() == 1) { + const LhsRecord& lhsRecord = m_caseDecoderRecords[0]; + const int width = lhsRecord.lhsp->width(); + AstNodeExpr* const rhsp + = exprp->width() == width ? exprp : new AstSel{flp, exprp, 0, width}; + AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false); + if (lhsRecord.nCaseAssigns) { + return new AstAssign{flp, lhsp, rhsp}; + } else if (lhsRecord.nCaseAssignDlys) { + return new AstAssignDly{flp, lhsp, rhsp}; + } else { + nodep->v3fatalSrc("Unknown assignment type"); + } + } + + // There are multiple LHSs, store the lookup result in a temporary + const std::string name = tmpPrefixp + std::to_string(m_nTmps++); + AstVarScope* const tempVscp = m_scopep->createTemp(name, m_caseDecoderEntryWidth); + AstNodeExpr* const tempWritep = new AstVarRef{flp, tempVscp, VAccess::WRITE}; + AstNodeStmt* const resultp = new AstAssign{flp, tempWritep, exprp}; + + // For each LHS, select out the result + for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { + const int width = lhsRecord.lhsp->width(); + const int lsb = lhsRecord.offset; + AstNodeExpr* const tempReadp = new AstVarRef{flp, tempVscp, VAccess::READ}; + AstNodeExpr* const rhsp = new AstSel{flp, tempReadp, lsb, width}; + AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false); + if (lhsRecord.nCaseAssigns) { + resultp->addNext(new AstAssign{flp, lhsp, rhsp}); + } else if (lhsRecord.nCaseAssignDlys) { + resultp->addNext(new AstAssignDly{flp, lhsp, rhsp}); + } else { + nodep->v3fatalSrc("Unknown assignment type"); + } + } + return resultp; + } + + AstNodeStmt* convertCaseTable(AstCase* nodep) { + // Create the table constant + FileLine* const flp = nodep->fileline(); + AstConst* const tablep + = new AstConst{flp, AstConst::WidthedValue{}, static_cast(m_caseTableWidth), 0}; + const uint32_t tableEntries = 1U << nodep->exprp()->width(); + + const bool isTinyTable = m_caseTableWidth <= CASE_TABLE_TINY_BITS; + if (isTinyTable) { + ++m_stats.caseTableTiny; + } else { + ++m_stats.caseTableNormal; + } + + // Populate the table. Align entries to a word boundary to avoid bit swizzling at runtime. + const uint32_t entryWidth = isTinyTable + ? m_caseDecoderEntryWidth + : VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; + for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { + const int lhsWidth = lhsRecord.lhsp->width(); + const int lhsOffset = lhsRecord.offset; + + // Broadcast the pre-default assignment + if (lhsRecord.preDefaultp) { + AstConst* const rhsp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const); + for (uint32_t index = 0; index < tableEntries; ++index) { + const uint32_t tableOffset = index * entryWidth + lhsOffset; + tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); + } + } + + // Populate table based on each case item. In reverse order so earlier items win + for (AstCaseItem* cip = VN_AS(nodep->itemsp()->lastp(), CaseItem); cip; + cip = VN_AS(cip->prevp(), CaseItem)) { + // Find the RHS in this case + AstConst* const rhsp = [&]() -> AstConst* { + for (AstNode* stmtp = cip->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + AstNodeAssign* const ap = VN_AS(stmtp, NodeAssign); + if (lhsRecord.lhsp->sameTree(ap->lhsp())) return VN_AS(ap->rhsp(), Const); + } + // Not assigned in this case, use the pre-assigned default + return VN_AS(lhsRecord.preDefaultp->rhsp(), Const); + }(); + + // If default, broadcast it + if (cip->isDefault()) { + for (uint32_t index = 0; index < tableEntries; ++index) { + const uint32_t tableOffset = index * entryWidth + lhsOffset; + tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); + } + continue; + } + + // Iterate case conditions in reverse order + for (AstConst* condp = VN_AS(cip->condsp()->lastp(), Const); condp; + condp = VN_AS(condp->prevp(), Const)) { + if (neverItem(nodep, condp)) continue; // If item never matches, ignore it + const auto& match = matchPattern(nodep, condp); + const uint32_t matchMask = match.first.toUInt(); + const uint32_t matchBits = match.second.toUInt(); + const uint32_t inverseMask = ~matchMask & ((1U << condp->width()) - 1); + // This iterates through all integers that are a subset of the inverse mask, + // i.e.: all don't care values masked out + for (uint32_t i = inverseMask; true; i = (i - 1) & inverseMask) { + const uint32_t index = i | matchBits; + const uint32_t tableOffset = index * entryWidth + lhsOffset; + tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); + if (!i) break; + } + } + } + } + + // Create the table in the constant pool, unless using an inline table + AstVarScope* const tableVscp = [&]() -> AstVarScope* { + if (isTinyTable) return nullptr; + AstVarScope* vscp = v3Global.rootp()->constPoolp()->findConst(tablep, true); + VL_DO_DANGLING(tablep->deleteTree(), tablep); // findConst clones + return vscp; + }(); + + // Create the lookup table reference and index + AstNodeExpr* const tableRefp + = tableVscp ? static_cast(new AstVarRef{flp, tableVscp, VAccess::READ}) + : static_cast(tablep); + AstNodeExpr* const caseExprp + = new AstExtend{flp, nodep->exprp()->cloneTreePure(false), 32}; + AstNodeExpr* const scalep = new AstConst{flp, entryWidth}; + AstNodeExpr* const tableLsbp = new AstMul{flp, scalep, caseExprp}; + AstNodeExpr* const tableSelp + = new AstSel{flp, tableRefp, tableLsbp, static_cast(m_caseDecoderEntryWidth)}; + + // Connect outputs + return connectDecoderOutputs(nodep, tableSelp, "__VcaseTableOut"); + } + + AstNodeStmt* convertCaseDecoder(AstCase* nodep) { + ++m_stats.caseDecoder; + + FileLine* const flp = nodep->fileline(); + + // Gather all the case conditions, paird with their statements. A 'nullptr' condition + // matches anything (the default case, or the catch-all added below). + std::vector> clauses; // (condition, item statements) + clauses.reserve(m_caseNConditions + 1); + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + if (cip->isDefault()) { + clauses.emplace_back(nullptr, cip->stmtsp()); + continue; + } + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + AstConst* const iconstp = VN_AS(condp, Const); + // Skip items that can never match in 2-state simulation (e.g. X in casez) + if (neverItem(nodep, iconstp)) continue; + clauses.emplace_back(iconstp, cip->stmtsp()); + } + } + // If the case has no default item and is not provably exhaustive, unmatched selector + // values fall back to the pre-defaults. Represent that with a catch-all clause (null + // condition and no statements, so every LHS uses its pre-default). 'analyzeDecoderPattern' + // guarantees every LHS has a pre-default in this case. + const bool provenExhaustive = m_caseDetailsValid && m_caseDetails.exhaustive + && !m_caseDetails.exhaustiveOverEnumOnly; + if (clauses.back().first && !provenExhaustive) clauses.emplace_back(nullptr, nullptr); + + // Number of entries in decoder table + const int decoderEnries = clauses.size(); + + // Build the match table: a {matchBits, matchMask} packed pair per clause, shared by all + // LHSs. Each field is rounded up to a whole EDATA word boundary to avoid bit swizzling + // at runtime. We use a packed value so the runtime function can take a VlWide pointer + // without templating on the array size. + const int condWidth = nodep->exprp()->width(); + const int matchWidth = 2 * VL_WORDS_I(condWidth) * VL_EDATASIZE; + AstConst* const matchp + = new AstConst{flp, AstConst::WidthedValue{}, decoderEnries * matchWidth, 0}; + for (int i = 0; i < decoderEnries; ++i) { + const int entryLsb = i * matchWidth; + + // If the entry has a condition, use it's match bits and mask + if (AstConst* const condp = clauses[i].first) { + const auto& match = matchPattern(nodep, condp); + matchp->num().opSelInto(match.first, entryLsb, condWidth); + matchp->num().opSelInto(match.second, entryLsb + matchWidth / 2, condWidth); + continue; + } + + // Otherwise use zero for mask and bits, which matches anything + V3Number numZero{flp, condWidth, 0}; + matchp->num().opSelInto(numZero, entryLsb, condWidth); + matchp->num().opSelInto(numZero, entryLsb + matchWidth / 2, condWidth); + } + + // Create the table initializer + AstRange* const rangep = new AstRange{flp, decoderEnries - 1, 0}; + AstNodeDType* const entryDtypep = nodep->findBitDType( + m_caseDecoderEntryWidth, m_caseDecoderEntryWidth, VSigning::UNSIGNED); + AstNodeDType* const tableDtypep = new AstUnpackArrayDType{flp, entryDtypep, rangep}; + v3Global.rootp()->typeTablep()->addTypesp(tableDtypep); + AstInitArray* const tablep = new AstInitArray{flp, tableDtypep, nullptr}; + + // Build a single value table for all LHSs: one entry per clause, packing each LHS's value + // at its offset. The entry width is the table packing computed by 'analyzeDecoderPattern' + // Rounded up to a whole EDATA word boundary to avoid bit swizzling at runtime. + for (int i = 0; i < decoderEnries; ++i) { + AstNode* const stmtsp = clauses[i].second; + AstConst* const entryp = new AstConst{flp, AstConst::WidthedValue{}, + static_cast(m_caseDecoderEntryWidth), 0}; + for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { + AstNodeExpr* const lhsp = lhsRecord.lhsp; + // Find the value assigned to this LHS in the clause's statements + AstConst* valConstp = nullptr; + for (AstNode* stmtp = stmtsp; stmtp; stmtp = stmtp->nextp()) { + AstNodeAssign* const assignp = VN_AS(stmtp, NodeAssign); + if (!lhsp->sameTree(assignp->lhsp())) continue; + valConstp = VN_AS(assignp->rhsp(), Const); + break; + } + // Not assigned in this clause, so use the pre-assigned default + if (!valConstp) { + UASSERT_OBJ(lhsRecord.preDefaultp, nodep, + "Decoder LHS unassigned in case item without a pre-default"); + valConstp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const); + } + entryp->num().opSelInto(valConstp->num(), lhsRecord.offset, lhsp->width()); + } + tablep->addIndexValuep(i, entryp); + } + + // Create the tables + AstVarScope* const matchVscp = v3Global.rootp()->constPoolp()->findConst(matchp, true); + AstVarScope* const tableVscp = v3Global.rootp()->constPoolp()->findTable(tablep); + VL_DO_DANGLING(matchp->deleteTree(), matchp); + VL_DO_DANGLING(tablep->deleteTree(), tablep); + + // AstMatchMasked produces the index of the matching entry + AstNodeExpr* const tableRefp = new AstVarRef{flp, tableVscp, VAccess::READ}; + AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false); + AstMatchMasked* const indexp = new AstMatchMasked{flp, caseExprp, matchVscp}; + AstNodeExpr* const entryp = new AstArraySel{flp, tableRefp, indexp}; + + return connectDecoderOutputs(nodep, entryp, "__VcaseDecoderOut"); + } + + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const { + // Base case: If reached the last bit, upperValue equals an exact value, just return + // the statements from that CaseItem. Note: Not cloning here as the caller will do + // an identity check. + if (msb < 0) return m_caseDetails.records[upperValue].stmtsp; + + // Recursive case: + // Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively + const uint32_t upperValue0 = upperValue; + const uint32_t upperValue1 = upperValue | (1UL << msb); + AstNode* tree0p = convertCaseFastRecurse(cexprp, msb - 1, upperValue0); + AstNode* tree1p = convertCaseFastRecurse(cexprp, msb - 1, upperValue1); + + // If same logic on both sides, we can just return one of them + if (tree0p == tree1p) return tree0p; + + // We could have a "checkerboard" with A B A B, we can use the same IF on both edges + { bool same = true; - for (uint32_t a = upperValue, b = (upperValue | (1UL << msb)); - a < (upperValue | (1UL << msb)); a++, b++) { - if (m_valueItem[a] != m_valueItem[b]) { + for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) { + if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) { same = false; break; } @@ -373,161 +899,120 @@ class CaseVisitor final : public VNVisitor { VL_DO_DANGLING(tree1p->deleteTree(), tree1p); return tree0p; } - - // Must have differing logic, so make a selection - - // Case expressions can't be linked twice, so clone them - if (tree0p && !tree0p->user3()) tree0p = tree0p->cloneTree(true); - if (tree1p && !tree1p->user3()) tree1p = tree1p->cloneTree(true); - - // Alternate scheme if we ever do multiple bits at a time: - // V3Number nummask (cexprp, cexprp->width(), (1UL<fileline(), cexprp->cloneTreePure(false), - // new AstConst(cexprp->fileline(), nummask)); - AstNodeExpr* const and1p - = new AstSel{cexprp->fileline(), cexprp->cloneTreePure(false), msb, 1}; - AstNodeExpr* const eqp - = new AstNeq{cexprp->fileline(), new AstConst{cexprp->fileline(), 0}, and1p}; - AstIf* const ifp = new AstIf{cexprp->fileline(), eqp, tree1p, tree0p}; - ifp->user3(1); // So we don't bother to clone it - return ifp; } + + // Must have differing logic. Test the bit and convert to an If. + + // Clone if needed + if (tree0p && tree0p->backp()) tree0p = tree0p->cloneTree(true); + if (tree1p && tree1p->backp()) tree1p = tree1p->cloneTree(true); + // Create the If statement + FileLine* const flp = cexprp->fileline(); + AstNodeExpr* const condp = new AstSel{flp, cexprp->cloneTreePure(false), msb, 1}; + AstIf* const ifp = new AstIf{flp, condp, tree1p, tree0p}; + return ifp; } - void replaceCaseFast(AstCase* nodep) { - // CASEx(cexpr,.... - // -> tree of IF(msb, IF(msb-1, 11, 10) - // IF(msb-1, 01, 00)) - AstNodeExpr* cexprp; - AstExprStmt* cexprStmtp = nullptr; - if (nodep->exprp()->isPure()) { - cexprp = nodep->exprp()->unlinkFrBack(); - } else { - cexprStmtp = VN_AS(nodep->exprp()->unlinkFrBack(), ExprStmt); - cexprp = cexprStmtp->resultp()->cloneTreePure(false); - } - - if (debug() >= 9) { // LCOV_EXCL_START - for (uint32_t i = 0; i < (1UL << m_caseWidth); ++i) { - if (const AstNode* const itemp = m_valueItem[i]) { - UINFO(9, "Value " << std::hex << i << " " << itemp); - } - } - } // LCOV_EXCL_STOP - - // Handle any assertions - replaceCaseParallel(nodep, m_caseNoOverlapsAllCovered); - - AstNode::user3ClearTree(); - AstNode* ifrootp = replaceCaseFastRecurse(cexprp, m_caseWidth - 1, 0UL); - if (cexprStmtp) { - cexprStmtp->resultp()->unlinkFrBack()->deleteTree(); - AstIf* const ifp = VN_AS(ifrootp, If); - cexprStmtp->resultp(ifp->condp()->unlinkFrBack()); - ifp->condp(cexprStmtp); - m_needToClearCache = true; - } - - // Case expressions can't be linked twice, so clone them - if (ifrootp && !ifrootp->user3()) ifrootp = ifrootp->cloneTree(true); - - if (ifrootp) { - nodep->replaceWith(ifrootp); - } else { - nodep->unlinkFrBack(); - } - VL_DO_DANGLING(nodep->deleteTree(), nodep); - VL_DO_DANGLING(cexprp->deleteTree(), cexprp); - UINFOTREE(9, ifrootp, "", "_simp"); + // CASEx(cexpr,.... + // -> tree of IF(msb, IF(msb-1, 11, 10) + // IF(msb-1, 01, 00)) + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCaseFast(AstCase* nodep) { + ++m_stats.caseFast; + const int caseWidth = nodep->exprp()->width(); + AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); + return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp; } - void replaceCaseComplicated(AstCase* nodep) { - // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) - // -> IF((cexpr==icond1),istmts1, - // IF((EQ (AND MASK cexpr) (AND MASK icond1) - // ,istmts2, istmts3 - AstNodeExpr* cexprp; - AstExprStmt* cexprStmtp = nullptr; - if (nodep->exprp()->isPure()) { - cexprp = nodep->exprp()->unlinkFrBack(); - } else { - cexprStmtp = VN_AS(nodep->exprp(), ExprStmt)->unlinkFrBack(); - cexprp = cexprStmtp->resultp()->cloneTreePure(false); - } - // We'll do this in two stages. First stage, convert the conditions to - // the appropriate IF AND terms. - UINFOTREE(9, nodep, "", "_comp_IN::"); - bool hadDefault = false; + // Convet case statement using generic if/else tree + // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) + // -> IF((cexpr==icond1),istmts1, + // IF((EQ (AND MASK cexpr) (AND MASK icond1) + // ,istmts2, istmts3 + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCaseGeneric(AstCase* nodep) { + ++m_stats.caseGeneric; + // We'll do this in two stages. + // First stage, convert the conditions to the appropriate IF AND terms. + bool hasDefault = false; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - if (!itemp->condsp()) { - // Default clause. Just make true, we'll optimize it away later - itemp->addCondsp(new AstConst{itemp->fileline(), AstConst::BitTrue{}}); - hadDefault = true; - } else { - // Expressioned clause - AstNodeExpr* icondNextp = nullptr; - AstNodeExpr* ifexprp = nullptr; // If expression to test - for (AstNodeExpr* icondp = itemp->condsp(); icondp; icondp = icondNextp) { - icondNextp = VN_AS(icondp->nextp(), NodeExpr); - icondp->unlinkFrBack(); - AstNodeExpr* condp = nullptr; // Default is to use and1p/and2p - AstConst* const iconstp = VN_CAST(icondp, Const); - if (iconstp && neverItem(nodep, iconstp)) { - // X in casez can't ever be executed - VL_DO_DANGLING(icondp->deleteTree(), icondp); - VL_DANGLING(iconstp); - // For simplicity, make expression that is not equal, and let later - // optimizations remove it - condp = new AstConst{itemp->fileline(), AstConst::BitFalse{}}; - } else if (AstInsideRange* const irangep = VN_CAST(icondp, InsideRange)) { - // Similar logic in V3Width::visit(AstInside) - condp = irangep->newAndFromInside(cexprp->cloneTreePure(true), - irangep->lhsp()->unlinkFrBack(), - irangep->rhsp()->unlinkFrBack()); - VL_DO_DANGLING2(icondp->deleteTree(), icondp, irangep); - } else if (iconstp && iconstp->num().isFourState() - && (nodep->casex() || nodep->casez() || nodep->caseInside())) { - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - AstNodeExpr* const and1p - = new AstAnd{itemp->fileline(), cexprp->cloneTreePure(false), - new AstConst{itemp->fileline(), nummask}}; - AstNodeExpr* const and2p = new AstAnd{ - itemp->fileline(), new AstConst{itemp->fileline(), numval}, - new AstConst{itemp->fileline(), nummask}}; - VL_DO_DANGLING(icondp->deleteTree(), icondp); - VL_DANGLING(iconstp); - condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); - } else { - // Not a caseX mask, we can build CASEEQ(cexpr icond) - AstNodeExpr* const and1p = cexprp->cloneTreePure(false); - AstNodeExpr* const and2p = icondp; - condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); - } - if (!ifexprp) { - ifexprp = condp; - } else { - ifexprp = new AstLogOr{itemp->fileline(), ifexprp, condp}; - } - } - // Replace expression in tree - itemp->addCondsp(ifexprp); + FileLine* const flp = itemp->fileline(); + + // Default clause. Just make true, we'll optimize it away later + if (itemp->isDefault()) { + itemp->addCondsp(new AstConst{flp, AstConst::BitTrue{}}); + hasDefault = true; + continue; } + + // Regular clause. Construct the condition expression. + AstNodeExpr* newCondp = nullptr; + for (AstNodeExpr *itemExprp = itemp->condsp(), *nextp; itemExprp; itemExprp = nextp) { + nextp = VN_AS(itemExprp->nextp(), NodeExpr); + itemExprp->unlinkFrBack(); + + // If case never matches, ignore it + if (neverItem(nodep, itemExprp)) { + VL_DO_DANGLING(itemExprp->deleteTree(), itemExprp); + continue; + } + + // Compute the term to add to the condition expression + AstNodeExpr* const termp = [&]() -> AstNodeExpr* { + // Will need a copy of the caseExpr regardless + AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false); + + // InsideRange: Similar logic in V3Width::visit(AstInside) + if (AstInsideRange* const itemRangep = VN_CAST(itemExprp, InsideRange)) { + AstNodeExpr* const resultp = itemRangep->newAndFromInside( // + caseExprp, // + itemRangep->lhsp()->unlinkFrBack(), + itemRangep->rhsp()->unlinkFrBack()); + VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemRangep); + return resultp; + } + + // Check if we need to perform a wildcard match, this needs masking + if (AstConst* const itemConstp = VN_CAST(itemExprp, Const)) { + // TODO: 4-state will need to fix this + if (itemConstp->num().isFourState() + && (nodep->casex() || nodep->casez() || nodep->caseInside())) { + // Wildcard match, make 'caseExpr' & 'mask' == 'itemExpr' & 'mask' + const auto& match = matchPattern(nodep, itemConstp); + const V3Number& matchMask = match.first; + const V3Number& matchBits = match.second; + VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp); + return AstEq::newTyped( + flp, // + new AstConst{flp, matchBits}, + new AstAnd{flp, caseExprp, new AstConst{flp, matchMask}}); + } + } + + // Regular case, use simple equality comparison + return AstEq::newTyped(flp, caseExprp, itemExprp); + }(); + + // 'Or' new term with previous terms + newCondp = newCondp ? new AstLogOr{flp, newCondp, termp} : termp; + } + // Replace expression in tree. Needs to be non-null, so add a constant false if + // needed + if (!newCondp) newCondp = new AstConst{flp, AstConst::BitFalse{}}; + itemp->addCondsp(newCondp); } - VL_DO_DANGLING(cexprp->deleteTree(), cexprp); - if (!hadDefault) { - // If there was no default, add a empty one, this greatly simplifies below code - // and constant propagation will just eliminate it for us later. + + // If there was no default, add a empty one, this greatly simplifies below code + // and constant propagation will just eliminate it for us later. + if (!hasDefault) { nodep->addItemsp(new AstCaseItem{ nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr}); } - UINFOTREE(9, nodep, "", "_comp_COND"); + // Now build the IF statement tree - // The tree can be quite huge. Pull ever group of 8 out, and make a OR tree. + // The tree can be quite huge. Pull every group of 8 out, and make a OR tree. // This reduces the depth for the bottom elements, at the cost of // some of the top elements. If we ever have profiling data, we // should pull out the most common item from here and instead make @@ -538,8 +1023,11 @@ class CaseVisitor final : public VNVisitor { AstIf* itemnextp = nullptr; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - AstNode* const istmtsp = itemp->stmtsp(); // Maybe null -- no action. + + // Grab the statements from this item. May be empty. + AstNode* const istmtsp = itemp->stmtsp(); if (istmtsp) istmtsp->unlinkFrBackWithNext(); + // Expressioned clause AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack(); { // Prepare for next group @@ -575,67 +1063,114 @@ class CaseVisitor final : public VNVisitor { itemnextp = newp; } } - UINFOTREE(9, nodep, "", "_comp_TREE"); - // Handle any assertions - replaceCaseParallel(nodep, false); - // Replace the CASE... with IF... - if (grouprootp) { - UINFOTREE(9, grouprootp, "", "_new"); - nodep->replaceWith(grouprootp); - if (cexprStmtp) { - pushDeletep(cexprStmtp->resultp()->unlinkFrBack()); - cexprStmtp->resultp(grouprootp->condp()->unlinkFrBack()); - grouprootp->condp(cexprStmtp); - m_needToClearCache = true; + return grouprootp; + } + + // Convert the given case statement to a representation not using AstCase + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCase(AstCase* nodep) { + // Determine if we should use the lookup table method + const bool useTable = [&]() { + // Not if disabled + if (!v3Global.opt.fCaseTable()) return false; + // Not if analysis tells us we can't + if (!m_caseTableWidth) return false; + // Always if tiny - it is materialized inline, so there is no load to amortize + if (m_caseTableWidth <= CASE_TABLE_TINY_BITS) return true; + // For a normal (constant-pool) table, weigh the indexed load against the branch + // lowering it would replace. That lowering's depth is bounded by the selector + // width (a balanced bit tree tests ~one bit per level) and by the number of + // distinct values (a generic if/else does ~one compare per value). A few compares + // are cheaper than a load that is likely to be a cache miss, so only table once that + // depth is exceeded. + const size_t branches = std::min(nodep->exprp()->width(), m_caseNConditions); + if (branches < CASE_TABLE_MIN_BRANCHES) return false; + return true; + }(); + if (useTable) return convertCaseTable(nodep); + + // Determine if we should use the decoder method. + const bool useDecoder = [&]() { + // Not if disabled + if (!v3Global.opt.fCaseDecoder()) return false; + // Not if not a decoder pattern + if (m_caseDecoderRecords.empty()) return false; + // Only worth it once the branch lowering it would replace is deep enough (see + // useTable) + const size_t branches = std::min(nodep->exprp()->width(), m_caseNConditions); + if (branches < CASE_TABLE_MIN_BRANCHES) return false; + return true; + }(); + if (useDecoder) return convertCaseDecoder(nodep); + + // Determine if we should use the fast bitwise branching tree method + const bool useFastBitTree = [&]() { + // Not if disabled + if (!v3Global.opt.fCaseTree()) return false; + // Can't do it without the detailed analysis + if (!m_caseDetailsValid) return false; + // Can't do it if not exhaustive + if (!m_caseDetails.exhaustive) return false; + // Not worth doing if there are few conditions + if (m_caseNConditions <= 3) return false; + // Avoid e.g. priority expanders from going crazy in expansion + const size_t caseWidth = nodep->exprp()->width(); + if (caseWidth >= 8 && m_caseNConditions <= (caseWidth + 1)) return false; + // Otherwise use the bit tree + return true; + }(); + if (useFastBitTree) return convertCaseFast(nodep); + + // Convert using the generic if/else tree method + // If a case statement is exhaustive, presume signals involved aren't forming a latch + // TODO: this is broken, but it is as was before + if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) { + m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); + } + return convertCaseGeneric(nodep); + } + + // VISITORS + void visit(AstCase* nodep) override { + UASSERT_OBJ(nodep->exprp()->isPure(), nodep, + "Impure case expression should have been removed by V3LiftExpr"); + + CaseLintVisitor::apply(nodep); + + // Convert any children first + iterateChildren(nodep); + + // Analyze this case statement + analyzeCase(nodep); + + // Take the 'notParallelp' statements under the case statement created by V3Assert. + // If the statement was proven to have no overlaps and all cases covered, + // it can be removed. Otherwise insert the assertion after the case statement. + if (AstNode* const stmtp = nodep->notParallelp()) { + stmtp->unlinkFrBackWithNext(); + if (m_caseDetailsValid && m_caseDetails.exhaustive && m_caseDetails.noOverlaps) { + ++m_stats.provenAssertions; + VL_DO_DANGLING(stmtp->deleteTree(), stmtp); + } else { + nodep->addNextHere(stmtp); } + } + + // Convert the case statement and replace the original + if (AstNode* const replacementp = convertCase(nodep)) { + nodep->replaceWith(replacementp); } else { nodep->unlinkFrBack(); } VL_DO_DANGLING(nodep->deleteTree(), nodep); } - void replaceCaseParallel(AstCase* nodep, bool noOverlapsAllCovered) { - // Take the notParallelp tree under the case statement created by V3Assert - // If the statement was proven to have no overlaps and all cases - // covered, we're done with it. - // Else, convert to a normal statement parallel with the case statement. - if (nodep->notParallelp() && !noOverlapsAllCovered) { - AstNode* const parp = nodep->notParallelp()->unlinkFrBackWithNext(); - nodep->addNextHere(parp); - } - } - - bool neverItem(const AstCase* casep, const AstConst* itemp) { - // Xs in case or casez are impossible due to two state simulations - if (casep->casex()) { - } else if (casep->casez() || casep->caseInside()) { - if (itemp->num().isAnyX()) return true; - } else { - if (itemp->num().isFourState()) return true; - } - return false; - } - - // VISITORS - void visit(AstCase* nodep) override { - VL_RESTORER(m_caseIncomplete); - { CaseLintVisitor{nodep}; } + void visit(AstScope* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; iterateChildren(nodep); - UINFOTREE(9, nodep, "", "case_old"); - if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) { - // It's a simple priority encoder or complete statement - // we can make a tree of statements to avoid extra comparisons - ++m_statCaseFast; - VL_DO_DANGLING(replaceCaseFast(nodep), nodep); - } else { - // If a case statement is whole, presume signals involved aren't forming a latch - if (m_alwaysp && !m_caseIncomplete) - m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); - ++m_statCaseSlow; - VL_DO_DANGLING(replaceCaseComplicated(nodep), nodep); - } } - //-------------------- + void visit(AstAlways* nodep) override { VL_RESTORER(m_alwaysp); m_alwaysp = nodep; @@ -645,17 +1180,19 @@ class CaseVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit CaseVisitor(AstNetlist* nodep) { - for (auto& itr : m_valueItem) itr = nullptr; - iterate(nodep); - if (m_needToClearCache) VIsCached::clearCacheTree(); - } + explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); } ~CaseVisitor() override { - V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); - V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow); + V3Stats::addStat("Optimizations, Cases decoder", m_stats.caseDecoder); + V3Stats::addStat("Optimizations, Cases table normal", m_stats.caseTableNormal); + V3Stats::addStat("Optimizations, Cases table tiny", m_stats.caseTableTiny); + V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast); + V3Stats::addStat("Optimizations, Cases complex", m_stats.caseGeneric); + V3Stats::addStat("Optimizations, Cases proven assertions", m_stats.provenAssertions); } }; +size_t CaseVisitor::LhsRecord::s_nextId = 0; + //###################################################################### // Case class functions @@ -666,5 +1203,5 @@ void V3Case::caseAll(AstNetlist* nodep) { } void V3Case::caseLint(AstGenCase* nodep) { UINFO(4, __FUNCTION__ << ": "); - { CaseLintVisitor{nodep}; } + CaseLintVisitor::apply(nodep); } diff --git a/src/V3Class.cpp b/src/V3Class.cpp index 43f417313..2ccc1044b 100644 --- a/src/V3Class.cpp +++ b/src/V3Class.cpp @@ -114,7 +114,7 @@ class ClassVisitor final : public VNVisitor { classScopep->aboveScopep(), classScopep->aboveCellp()}; packagep->addStmtsp(scopep); // Iterate - VL_RESTORER(m_prefix); + VL_RESTORER_CLEAR(m_prefix); VL_RESTORER(m_classPackagep); VL_RESTORER(m_classScopep); VL_RESTORER(m_packageScopep); @@ -129,7 +129,7 @@ class ClassVisitor final : public VNVisitor { void visit(AstNodeModule* nodep) override { // Visit for NodeModules that are not AstClass (AstClass is-a AstNodeModule) // Classes are always under a Package (perhaps $unit) or a module - VL_RESTORER(m_prefix); + VL_RESTORER_CLEAR(m_prefix); VL_RESTORER(m_modp); m_modp = nodep; m_prefix = nodep->name() + "__03a__03a"; // :: diff --git a/src/V3Clean.cpp b/src/V3Clean.cpp index 2c326bbb7..07920f8de 100644 --- a/src/V3Clean.cpp +++ b/src/V3Clean.cpp @@ -232,6 +232,11 @@ class CleanVisitor final : public VNVisitor { ensureClean(nodep->rhsp()); setClean(nodep, true); } + void visit(AstMatchMasked* nodep) override { + iterateChildren(nodep); + ensureClean(nodep->lhsp()); + setClean(nodep, true); + } void visit(AstSel* nodep) override { operandBiop(nodep); setClean(nodep, nodep->cleanOut()); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index de2e1d64f..203fef45a 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -286,7 +286,7 @@ class ConstBitOpTreeVisitor final : public VNVisitorConst { // Get the mask that selects the bits that are relevant in this term V3Number maskNum{srcp, m_width, 0}; - maskNum.opBitsNonX(m_bitPolarity); // 'x' -> 0, 0->1, 1->1 + maskNum.opBitsNonXZ(m_bitPolarity); // 'x' -> 0, 0->1, 1->1 const uint64_t maskVal = maskNum.toUQuad(); UASSERT_OBJ(maskVal != 0, m_refp, "Should have been recognized as having const 0 result"); @@ -973,6 +973,96 @@ class ConstVisitor final : public VNVisitor { return !numv.isNumber() ? numv : V3Number{nodep, nodep->width(), numv}; } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } + + AstStructSel* newStructSel(AstNodeExpr* const fromp, const AstMemberDType* const itemp) { + AstStructSel* const selp = new AstStructSel{fromp->fileline(), fromp, itemp->name()}; + selp->dtypeFrom(itemp->dtypep()); + return selp; + } + + void collectFixedAggregateTerms(AstNodeExpr* const fromp, std::vector& termps, + const bool packReal) { + const AstNodeDType* const dtypep = fromp->dtypep()->skipRefp(); + if (const AstUnpackArrayDType* const unpackDtypep = VN_CAST(dtypep, UnpackArrayDType)) { + const int left = unpackDtypep->left(); + const int right = unpackDtypep->right(); + const int step = left <= right ? 1 : -1; + for (int idx = left;; idx += step) { + AstArraySel* const selp + = new AstArraySel{fromp->fileline(), fromp->cloneTreePure(false), idx}; + collectFixedAggregateTerms(selp, termps, packReal); + if (idx == right) break; + } + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) { + termps.push_back(fromp); + return; + } + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + collectFixedAggregateTerms(newStructSel(fromp->cloneTreePure(false), itemp), + termps, packReal); + } + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else if (packReal && dtypep->isDouble()) { + termps.push_back(new AstRealToBits{fromp->fileline(), fromp}); + } else { + termps.push_back(fromp); + } + } + + AstNodeExpr* packFixedAggregate(AstNodeExpr* const fromp) { + std::vector termps; + collectFixedAggregateTerms(fromp, termps, true); + UASSERT(!termps.empty(), "No stream terms"); + AstNodeExpr* resultp = termps[0]; + for (size_t i = 1; i < termps.size(); ++i) { + resultp = new AstConcat{resultp->fileline(), resultp, termps[i]}; + } + return resultp; + } + + void replaceAssignToFixedAggregate(AstNodeAssign* const nodep, AstNodeExpr* const dstp, + AstNodeExpr* srcp) { + const int dstWidth = dstp->dtypep()->widthStream(); + std::vector termps; + collectFixedAggregateTerms(dstp, termps, false); + int srcWidth = srcp->width(); + if (srcWidth < dstWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dstWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + extendp->fileline(), extendp, + new AstConst{extendp->fileline(), static_cast(dstWidth - srcWidth)}, + dstWidth}; + srcWidth = dstWidth; + } + AstNodeAssign* newp = nullptr; + int offset = 0; + for (size_t i = 0; i < termps.size(); ++i) { + AstNodeExpr* const termp = termps[i]; + const int width = termp->dtypep()->widthStream(); + const int lsb = srcWidth - offset - width; + offset += width; + AstNodeExpr* rhsp + = new AstSel{srcp->fileline(), srcp->cloneTreePure(false), lsb, width}; + if (termp->dtypep()->skipRefp()->isDouble()) { + rhsp = new AstBitsToRealD{rhsp->fileline(), rhsp}; + } + AstNodeAssign* const assignp = nodep->cloneType(termp, rhsp); + assignp->dtypeFrom(termp); + newp = AstNode::addNext(newp, assignp); + } + nodep->addNextHere(newp); + VL_DO_DANGLING(pushDeletep(srcp), srcp); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + } + bool operandConst(const AstNode* nodep) { return VN_IS(nodep, Const); } bool operandAsvConst(const AstNode* nodep) { // BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...) @@ -1327,6 +1417,45 @@ class ConstVisitor final : public VNVisitor { return false; } + bool matchMaskedZero(const AstAnd* nodep) { + // Turn masking of known zero bits into constant zero. Commonly appears after V3Expand. + const AstConst* const maskp = VN_AS(nodep->lhsp(), Const); + const AstNodeExpr* const rhsp = nodep->rhsp(); + const uint32_t msbP1 = maskp->num().mostSetBitP1(); + if (!msbP1) return false; // Don't rewrite, separate rule matches for this + const uint32_t msb = msbP1 - 1; + const uint32_t lsb = maskp->num().leastSetBitP1() - 1; + + if (const AstShiftL* const shiftp = VN_CAST(rhsp, ShiftL)) { + // 'a << S' forces the low S bits to zero + if (AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { + return scp->num().fitsInUInt() // + && (scp->num().toUInt() > msb); + } + } + if (const AstShiftR* const shiftp = VN_CAST(rhsp, ShiftR)) { + // 'a >> S' forces the high S bits to zero. Check against the width of the shifted + // operand, V3Expand can create shifts wider than their inputs + if (AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { + return scp->num().fitsInUInt() + && (lsb + scp->num().toUInt() + >= static_cast(shiftp->lhsp()->widthMin())); + } + } + if (const AstMul* const mulp = VN_CAST(rhsp, Mul)) { + // 'C * a' forces the low N bits to zero where 'C' has low zero bits + if (AstConst* const cp = VN_CAST(mulp->lhsp(), Const)) { + return cp->num().leastSetBitP1() > msb + 1; + } + } + if (const AstExtend* const extendp = VN_CAST(rhsp, Extend)) { + // Zero-extension forces the bits above the source width to zero + return lsb >= static_cast(extendp->lhsp()->width()); + } + + return false; + } + bool matchBitOpTree(AstNodeExpr* nodep) { if (nodep->widthMin() != 1) return false; if (!v3Global.opt.fConstBitOpTree()) return false; @@ -1474,16 +1603,46 @@ class ConstVisitor final : public VNVisitor { && static_cast(nodep->widthConst()) == nodep->fromp()->width()); } bool operandSelExtend(AstSel* nodep) { - // A pattern created by []'s after offsets have been removed - // SEL(EXTEND(any,width,...),(width-1),0) -> ... - // Since select's return unsigned, this is always an extend - // cppcheck-suppress constVariablePointer // children unlinked below + if (!m_doV) return false; AstExtend* const extendp = VN_CAST(nodep->fromp(), Extend); - if (!(m_doV && extendp && VN_IS(nodep->lsbp(), Const) && nodep->lsbConst() == 0 - && static_cast(nodep->widthConst()) == extendp->lhsp()->width())) - return false; - VL_DO_DANGLING(replaceWChild(nodep, extendp->lhsp()), nodep); - return true; + if (!extendp) return false; + AstConst* const lsbp = VN_CAST(nodep->lsbp(), Const); + if (!lsbp) return false; + const int width = nodep->widthConst(); + const int lsb = lsbp->toSInt(); + const int msb = lsb + width - 1; + AstNodeExpr* const lhsp = extendp->lhsp(); + if (!lhsp->isPure()) return false; + + // Selecting the entire extended expression, replace with that + if (lsb == 0 && msb == lhsp->width() - 1) { + lhsp->unlinkFrBack(); + nodep->replaceWithKeepDType(lhsp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + // Select entirely in the extended part - replace with zero + if (lsb >= lhsp->width()) { + replaceZero(nodep); + return true; + } + // Select entirely in the extended expression - replace with select from that + if (msb < lhsp->width()) { + lhsp->unlinkFrBack(); + lsbp->unlinkFrBack(); + nodep->replaceWithKeepDType(new AstSel{nodep->fileline(), lhsp, lsbp, width}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + // Select straddles both sides, but is just a shorter extend + if (lsb == 0) { + lhsp->unlinkFrBack(); + nodep->replaceWithKeepDType(new AstExtend{nodep->fileline(), lhsp}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + + return false; } bool operandSelBiLower(AstSel* nodep) { // SEL(ADD(a,b),(width-1),0) -> ADD(SEL(a),SEL(b)) @@ -2393,6 +2552,25 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(conp), conp); // Further reduce, either node may have more reductions. return true; + } else if (m_doV && VN_IS(nodep->rhsp(), CvtPackedToArray) + && lowerAsFixedAggregate(nodep->lhsp()->dtypep())) { + AstCvtPackedToArray* const cvtp + = VN_AS(nodep->rhsp(), CvtPackedToArray)->unlinkFrBack(); + AstNodeExpr* srcp = cvtp->fromp()->unlinkFrBack(); + if (lowerAsFixedAggregate(srcp->dtypep())) { + srcp = packFixedAggregate(srcp); + } else if (AstNodeStream* const streamp = VN_CAST(srcp, NodeStream)) { + AstNodeExpr* const streamSrcp = streamp->lhsp(); + if (lowerAsFixedAggregate(streamSrcp->dtypep())) { + AstNodeExpr* const packedp = packFixedAggregate(streamSrcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + } + } + VL_DO_DANGLING(pushDeletep(cvtp), cvtp); + replaceAssignToFixedAggregate(nodep, nodep->lhsp()->unlinkFrBack(), srcp); + return true; } else if (m_doV && VN_IS(nodep->rhsp(), StreamR) && !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) { // The right-streaming operator on rhs of assignment does not @@ -2402,7 +2580,9 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack(); AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); const AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); - if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { + if (lowerAsFixedAggregate(srcDTypep)) { + srcp = packFixedAggregate(srcp); + } else if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { int srcElementBits = 0; if (const AstNodeDType* const elemDtp = srcDTypep->subDTypep()) { @@ -2429,6 +2609,19 @@ class ConstVisitor final : public VNVisitor { srcp = new AstShiftL{srcp->fileline(), srcp, new AstConst{srcp->fileline(), offset}, packedBits}; } + if (!VN_IS(dstDTypep, UnpackArrayDType) && !VN_IS(dstDTypep, QueueDType) + && !VN_IS(dstDTypep, DynArrayDType)) { + const int sWidth = srcp->width(); + const int dWidth = nodep->lhsp()->width(); + if (sWidth < dWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + srcp->fileline(), extendp, + new AstConst{srcp->fileline(), static_cast(dWidth - sWidth)}, + dWidth}; + } + } nodep->rhsp(srcp); VL_DO_DANGLING(pushDeletep(streamp), streamp); // Further reduce, any of the nodes may have more reductions. @@ -2438,7 +2631,7 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* streamp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack(); AstNodeDType* const dstDTypep = dstp->dtypep()->skipRefp(); - AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); // Handle unpacked/queue/dynarray source -> queue/dynarray dest via // CvtArrayToArray (StreamL reverses, so reverse=true) @@ -2466,6 +2659,7 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(streamp), streamp); return true; } + if (lowerAsFixedAggregate(srcDTypep)) srcp = packFixedAggregate(srcp); const int sWidth = srcp->width(); const int dWidth = dstp->width(); // Connect the rhs to the stream operator and update its width @@ -2556,7 +2750,7 @@ class ConstVisitor final : public VNVisitor { } else { // Source narrower than destination: left-justify by shifting left. // The right stream operator packs left-to-right, so remaining - // LSBs are zero-filled (IEEE 1800-2023 11.4.14.2). + // LSBs are zero-filled (IEEE 1800-2023 11.4.14.3). if (!VN_IS(srcp->dtypep()->skipRefp(), QueueDType)) { AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); @@ -2580,6 +2774,13 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); + if (lowerAsFixedAggregate(srcDTypep)) { + AstNodeExpr* const packedp = packFixedAggregate(srcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + srcp = packedp; + } if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType) || VN_IS(srcDTypep, UnpackArrayDType))) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { @@ -2887,6 +3088,15 @@ class ConstVisitor final : public VNVisitor { } } void visit(AstClassOrPackageRef* nodep) override { iterateChildren(nodep); } + + void visit(AstMatchMasked* nodep) override { + // Do not iterate the tables, they must be constant pool entries + iterate(nodep->lhsp()); + if (AstConst* const constp = VN_CAST(nodep->lhsp(), Const)) { + replaceNum(nodep, AstMatchMasked::fold(constp->num(), nodep->matchp()->varp())); + } + } + void visit(AstPin* nodep) override { iterateChildren(nodep); } void replaceLogEq(AstLogEq* nodep) { @@ -3149,7 +3359,8 @@ class ConstVisitor final : public VNVisitor { iterateChildren(nodep); UASSERT_OBJ(nodep->varp(), nodep, "Not linked"); bool did = false; - if (m_doV && nodep->varp()->valuep() && !m_attrp) { + if (m_doV && (!nodep->varp()->constPoolEntry() || m_selp) && nodep->varp()->valuep() + && !m_attrp) { // UINFOTREE(1, valuep, "", "visitvaref"); iterateAndNextNull(nodep->varp()->valuep()); // May change nodep->varp()->valuep() AstNode* const valuep = nodep->varp()->valuep(); @@ -4081,6 +4292,8 @@ class ConstVisitor final : public VNVisitor { // Zero on one side or the other TREEOP ("AstAdd {$lhsp.isZero, $rhsp}", "replaceWRhs(nodep)"); TREEOP ("AstAnd {$lhsp.isZero, $rhsp, $rhsp.isPure}", "replaceZero(nodep)"); // Can't use replaceZeroChkPure as we make this pattern in ChkPure + // Masking that always yields zero + TREEOP ("AstAnd {$lhsp.castConst, matchMaskedZero(nodep)}", "replaceZeroChkPure(nodep, $rhsp)"); // This visit function here must allow for short-circuiting. TREEOPS("AstLogAnd {$lhsp.isZero}", "replaceZero(nodep)"); TREEOP ("AstLogAnd{$lhsp.isZero, $rhsp}", "replaceZero(nodep)"); diff --git a/src/V3Control.cpp b/src/V3Control.cpp index e95e3c086..3ac7aa727 100644 --- a/src/V3Control.cpp +++ b/src/V3Control.cpp @@ -182,7 +182,6 @@ class V3ControlFTask final { V3ControlVarResolver m_params; // Parameters in function/task V3ControlVarResolver m_ports; // Ports in function/task V3ControlVarResolver m_vars; // Variables in function/task - bool m_isolate = false; // Isolate function return bool m_noinline = false; // Don't inline function/task bool m_public = false; // Public function/task @@ -190,7 +189,6 @@ public: V3ControlFTask() = default; void update(const V3ControlFTask& f) { // Don't overwrite true with false - if (f.m_isolate) m_isolate = true; if (f.m_noinline) m_noinline = true; if (f.m_public) m_public = true; m_params.update(f.m_params); @@ -203,7 +201,6 @@ public: V3ControlVarResolver& ports() { return m_ports; } V3ControlVarResolver& vars() { return m_vars; } - void setIsolate(bool set) { m_isolate = set; } void setNoInline(bool set) { m_noinline = set; } void setPublic(bool set) { m_public = set; } @@ -212,8 +209,6 @@ public: ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::NO_INLINE_TASK}); if (m_public) ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::PUBLIC_TASK}); - // Only functions can have isolate (return value) - if (VN_IS(ftaskp, Func)) ftaskp->attrIsolateAssign(m_isolate); } }; @@ -981,13 +976,7 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta // Semantics: Most of the attributes operate on signals if (pattern.empty()) { - if (attr == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { - if (ftask.empty()) { - fl->v3error("isolate_assignments only applies to signals or functions/tasks"); - } else { - V3ControlResolver::s().modules().at(module).ftasks().at(ftask).setIsolate(true); - } - } else if (attr == VAttrType::VAR_PUBLIC) { + if (attr == VAttrType::VAR_PUBLIC) { if (ftask.empty()) { // public module, this is the only exception from var here V3ControlResolver::s().modules().at(module).addModulePragma( diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 63cb65b8a..9db38cb6e 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -166,10 +166,13 @@ class CoverageVisitor final : public VNVisitor { // METHODS + // Return non-nullptr reason if this variable shouldn't have toggle coverage const char* varIgnoreToggle(const AstVar* nodep) { - // Return true if this shouldn't be traced - // See also similar rule in V3TraceDecl::varIgnoreTrace - if (!nodep->isToggleCoverable()) return "Not relevant signal type"; + const bool cover = nodep->isIO() || (nodep->isSignal() && nodep->isBitLogic()); + if (!cover) return "Not relevant signal"; + if (nodep->isConst()) return "Signal is constant"; + if (nodep->isDouble()) return "Signal is double"; + if (nodep->isString()) return "Signal is string"; if (!v3Global.opt.coverageUnderscore()) { const string prettyName = nodep->prettyName(); if (prettyName[0] == '_') return "Leading underscore"; @@ -276,8 +279,8 @@ class CoverageVisitor final : public VNVisitor { const AstNodeModule* const origModp = m_modp; VL_RESTORER(m_modp); VL_RESTORER(m_state); - VL_RESTORER(m_exprTempNames); - VL_RESTORER(m_funcTemps); + VL_RESTORER_COPY(m_exprTempNames); + VL_RESTORER_COPY(m_funcTemps); createHandle(nodep); m_modp = nodep; m_state.m_inModOff = false; // Haven't made top shell, so tops are real tops @@ -291,8 +294,8 @@ class CoverageVisitor final : public VNVisitor { void visit(AstClass* nodep) override { VL_RESTORER(m_modp); VL_RESTORER(m_state); - VL_RESTORER(m_exprTempNames); - VL_RESTORER(m_funcTemps); + VL_RESTORER_COPY(m_exprTempNames); + VL_RESTORER_COPY(m_funcTemps); createHandle(nodep); m_modp = nodep; // Covergroup declarations are not executable statements; suppress line/expr/toggle @@ -353,8 +356,8 @@ class CoverageVisitor final : public VNVisitor { void visit(AstNodeFTask* nodep) override { VL_RESTORER(m_ftaskp); - VL_RESTORER(m_exprTempNames); - VL_RESTORER(m_funcTemps); + VL_RESTORER_COPY(m_exprTempNames); + VL_RESTORER_COPY(m_funcTemps); m_ftaskp = nodep; if (!nodep->dpiImport()) iterateProcedure(nodep); } @@ -372,6 +375,8 @@ class CoverageVisitor final : public VNVisitor { } else { itemp->addElsesp(stmtp); } + } else if (AstBegin* const itemp = VN_CAST(nodep, Begin)) { + itemp->addStmtsp(stmtp); } else { nodep->v3fatalSrc("Bad node type"); } @@ -530,8 +535,10 @@ class CoverageVisitor final : public VNVisitor { newent.cleanup(); } } - } else if (VN_IS(dtypep, QueueDType)) { + } else if (VN_IS(dtypep, QueueDType) || VN_IS(dtypep, AssocArrayDType) + || VN_IS(dtypep, WildcardArrayDType)) { // Not covered + varp->v3warn(COVERIGN, "Coverage ignored for type " << dtypep->prettyTypeName()); } else { dtypep->v3fatalSrc("Unexpected node data type in toggle coverage generation: " << dtypep->prettyTypeName()); @@ -755,7 +762,7 @@ class CoverageVisitor final : public VNVisitor { } void visit(AstGenBlock* nodep) override { // Similar to AstBegin - VL_RESTORER(m_beginHier); + VL_RESTORER_COPY(m_beginHier); if (nodep->name() != "") { m_beginHier = m_beginHier + (m_beginHier != "" ? "__DOT__" : "") + nodep->name(); } @@ -769,8 +776,10 @@ class CoverageVisitor final : public VNVisitor { // generate blocks; each point should get separate consideration. // (Currently ignored for line coverage, since any generate iteration // covers the code in that line.) - VL_RESTORER(m_beginHier); + VL_RESTORER_COPY(m_beginHier); VL_RESTORER(m_inToggleOff); + VL_RESTORER(m_exprStmtsp); + m_exprStmtsp = nodep; m_inToggleOff = true; if (nodep->name() != "") { m_beginHier = m_beginHier + (m_beginHier != "" ? "__DOT__" : "") + nodep->name(); @@ -815,6 +824,7 @@ class CoverageVisitor final : public VNVisitor { if (pair.second) { varp = new AstVar{fl, VVarType::MODULETEMP, m_exprTempNames.get(frefp), dtypep}; + varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); pair.first->second = varp; if (m_ftaskp) { varp->funcLocal(true); @@ -864,7 +874,7 @@ class CoverageVisitor final : public VNVisitor { UASSERT_OBJ(m_exprs.empty(), nodep, "unexpected expression coverage garbage"); VL_RESTORER(m_seeking); VL_RESTORER(m_objective); - VL_RESTORER(m_exprs); + VL_RESTORER_CLEAR(m_exprs); // Already asserted above it's empty. m_seeking = SEEKING; m_objective = false; diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 727b5689c..f243b0c33 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -68,6 +68,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { , crossBins{cb} {} }; std::vector m_binInfos; // All bins in current covergroup + std::set m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy) + std::vector m_convCpVars; // VlCoverpoint members of converted coverpoints + AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type VMemberMap m_memberMap; // Member names cached for fast lookup @@ -88,6 +91,17 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Clear bin info for this covergroup (deleting any orphaned cross pseudo-bins) clearBinInfos(); + // Coverpoints referenced by a cross keep the legacy per-bin-member path (the cross + // reads those members); collect their names before they are consumed by the cross. + m_crossedCpNames.clear(); + m_convCpVars.clear(); + for (AstCoverCross* crossp : m_coverCrosses) { + for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) { + if (const AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef)) + if (!refp->exprp()) m_crossedCpNames.insert(refp->name()); + } + } + // For each coverpoint, generate sampling code for (AstCoverpoint* cpp : m_coverpoints) generateCoverpointCode(cpp); @@ -504,6 +518,13 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Create implicit automatic bins if no regular bins exist createImplicitAutoBins(coverpointp, exprp, autoBinMax); + // Eligible coverpoints route through the VlCoverpoint runtime; the rest (cross-fed or + // transition-bearing) keep the legacy per-bin-member path below. + if (coverpointConvertible(coverpointp)) { + generateConvertedCoverpoint(coverpointp, exprp, atLeastValue); + return; + } + // Generate member variables and matching code for each bin // Process in two passes: first non-default bins, then default bins std::vector defaultBins; @@ -595,47 +616,213 @@ class FunctionalCoverageVisitor final : public VNVisitor { UINFO(4, " Successfully added if statement for bin: " << binp->name()); } + // Build the condition under which a default bin matches: NOT(OR of all normal bins). + AstNodeExpr* buildDefaultCondition(AstCoverpoint* coverpointp, AstNodeExpr* exprp, + FileLine* fl) { + AstNodeExpr* anyBinMatchp = nullptr; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT + || cbinp->binsType() == VCoverBinsType::BINS_IGNORE + || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) + continue; + AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); + UASSERT_OBJ(binCondp, cbinp, + "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); + anyBinMatchp = anyBinMatchp ? new AstOr{fl, anyBinMatchp, binCondp} : binCondp; + } + return anyBinMatchp ? static_cast(new AstNot{fl, anyBinMatchp}) + : static_cast(new AstConst{fl, AstConst::BitTrue{}}); + } + + //==================================================================== + // VlCoverpoint conversion (eligible coverpoints) + + // True if a coverpoint routes through the VlCoverpoint runtime. Cross-fed coverpoints + // (the cross reads their per-bin members) and transition-bearing ones stay legacy. + bool coverpointConvertible(AstCoverpoint* coverpointp) { + if (m_crossedCpNames.count(coverpointp->name())) return false; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + if (VN_AS(binp, CoverBin)->transp()) return false; + } + return true; + } + + // A 'this->m_member' reference for embedding in an AstCStmt + AstVarRef* memberRef(FileLine* fl, AstVar* varp) { + AstVarRef* const refp = new AstVarRef{fl, varp, VAccess::READ}; + refp->selfPointer(VSelfPointerText{VSelfPointerText::This{}}); + return refp; + } + + // Individual equality targets of an array bin (bins b[] = {values/ranges}), in order. + // An open-ended bound ('$', AstUnbounded) resolves to the coverpoint domain: '[lo:$]' + // covers [lo:maxVal] and '[$:hi]' covers [0:hi]. One target is produced per value; a + // range whose resolved size would exceed COVER_BINS_LIMIT (e.g. an open '[lo:$]' over a + // wide coverpoint) is unsupported -- emits COVERIGN, sets unsupportedOut, yields nothing. + std::vector extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp, + bool& unsupportedOut) { + unsupportedOut = false; + const int width = exprp->width(); + const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1); + std::vector values; + for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { + if (AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) { + AstNodeExpr* const lhsp = V3Const::constifyEdit(irp->lhsp()); + AstNodeExpr* const rhsp = V3Const::constifyEdit(irp->rhsp()); + const bool loUnb = VN_IS(lhsp, Unbounded); + const bool hiUnb = VN_IS(rhsp, Unbounded); + AstConst* const minp = VN_CAST(lhsp, Const); + AstConst* const maxp = VN_CAST(rhsp, Const); + if ((!minp && !loUnb) || (!maxp && !hiUnb)) { + arrayBinp->v3error("Non-constant expression in array bins range; " + "range bounds must be constants"); + return values; + } + if ((minp && minp->num().isFourState()) || (maxp && maxp->num().isFourState())) { + arrayBinp->v3error("Four-state (x/z) value in array bins range bound; " + "range bounds must be two-state constants"); + return values; + } + const uint64_t lo = loUnb ? 0 : minp->toUQuad(); + const uint64_t hi = hiUnb ? maxVal : maxp->toUQuad(); + if (hi < lo) continue; // empty range contributes no bins + // Guard against a '$'-bounded or otherwise huge range exploding the bin count. + const uint64_t span = hi - lo; // == valueCount - 1 (no overflow: hi >= lo) + if (span >= static_cast(COVER_BINS_LIMIT) + || values.size() + span + 1 > static_cast(COVER_BINS_LIMIT)) { + arrayBinp->v3warn(COVERIGN, "Unsupported: array 'bins' covering more than " + << COVER_BINS_LIMIT + << " values (e.g. an open '[lo:$]' range over " + "a wide coverpoint); bin ignored"); + unsupportedOut = true; + for (AstNodeExpr* const vp : values) VL_DO_DANGLING(pushDeletep(vp), vp); + values.clear(); + return values; + } + for (uint64_t v = lo; v <= hi; ++v) + values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{}, width, + static_cast(v)}); + } else { + values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); + } + } + return values; + } + + // Emit a 'this->m_cp.addSingleNamer/addArrayNamer(...)' statement for one bin + AstCStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count) { + FileLine* const fl = binp->fileline(); + AstCStmt* const cs = new AstCStmt{fl}; + cs->add(memberRef(fl, cpVarp)); + const std::string loc = "\"" + std::string{fl->filename()} + "\", " + + std::to_string(fl->lineno()) + ", " + + std::to_string(fl->firstColumn()) + ");"; + if (count < 0) { // single bin + cs->add(".addSingleNamer(" + std::string{binp->binsType().binSetEnum()} + ", \"" + + binp->name() + "\", " + loc); + } else { // value array bin + cs->add(".addArrayNamer(" + std::string{binp->binsType().binSetEnum()} + ", " + + std::to_string(count) + ", \"" + binp->name() + "\", " + loc); + } + return cs; + } + + // Emit 'if (iff && cond) m_cp.incrementBin(idx);' (or recordHit, + illegal action) in sample() + void emitConvHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, int idx, + AstNodeExpr* condp) { + FileLine* const fl = binp->fileline(); + AstCStmt* const hitp = new AstCStmt{fl}; + hitp->add(memberRef(fl, cpVarp)); + hitp->add((binp->binsType().binIsNormal() ? ".incrementBin(" : ".recordHit(") + + std::to_string(idx) + ");"); + AstNode* actionp = hitp; + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + actionp->addNext(makeIllegalBinAction(fl, "Illegal bin " + binp->prettyNameQ() + + " hit in coverpoint " + + coverpointp->prettyNameQ())); + } + AstNodeExpr* const guardedp = applyCoverpointIffCondition(coverpointp, fl, condp); + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set in converted coverpoint"); + m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr}); + } + + // Route an eligible coverpoint through a VlCoverpoint member: emit the member, its + // sample() increments, the constructor configuration (init + namers), and registration. + void generateConvertedCoverpoint(AstCoverpoint* coverpointp, AstNodeExpr* exprp, + int atLeastValue) { + FileLine* const fl = coverpointp->fileline(); + UINFO(4, " Converting coverpoint to VlCoverpoint: " << coverpointp->name()); + + if (!m_vlCoverpointDTypep) { + m_vlCoverpointDTypep = new AstCDType{fl, "VlCoverpoint"}; + v3Global.rootp()->typeTablep()->addTypesp(m_vlCoverpointDTypep); + } + AstVar* const cpVarp = new AstVar{fl, VVarType::MEMBER, "__Vcp_" + coverpointp->name(), + m_vlCoverpointDTypep}; + cpVarp->isStatic(false); + m_covergroupp->addMembersp(cpVarp); + m_convCpVars.push_back(cpVarp); + + // Walk bins (non-default, then default), assigning sequential indices that match the + // namer append order; emit sample increments and collect namer statements. + std::vector namerStmts; + std::vector defaultBins; + int idx = 0; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT) { + defaultBins.push_back(cbinp); + continue; + } + if (cbinp->isArray()) { // value array: bins b[N] = {...} -> b[0]..b[N-1] + bool unsupported = false; + std::vector values = extractArrayValues(cbinp, exprp, unsupported); + if (unsupported) continue; // bin ignored (COVERIGN emitted); reserve no slot + namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast(values.size()))); + for (AstNodeExpr* valuep : values) { + emitConvHitIf(coverpointp, cbinp, cpVarp, idx++, + new AstEq{cbinp->fileline(), exprp->cloneTree(false), valuep}); + } + } else { + namerStmts.push_back(makeNamer(cpVarp, cbinp, -1)); + // buildBinCondition is null for 'ignore_bins = default' (no ranges); the bin + // still gets a reserved slot (recorded, never incremented). + if (AstNodeExpr* const condp = buildBinCondition(cbinp, exprp)) + emitConvHitIf(coverpointp, cbinp, cpVarp, idx, condp); + ++idx; + } + } + for (AstCoverBin* const defBinp : defaultBins) { + namerStmts.push_back(makeNamer(cpVarp, defBinp, -1)); + emitConvHitIf(coverpointp, defBinp, cpVarp, idx++, + buildDefaultCondition(coverpointp, exprp, defBinp->fileline())); + } + + // Constructor: init (allocates), namers, then registration (under --coverage) + const std::string hier = m_covergroupp->name() + "." + coverpointp->name(); + AstCStmt* const initp = new AstCStmt{fl}; + initp->add(memberRef(fl, cpVarp)); + initp->add(".init(\"" + hier + "\", " + std::to_string(atLeastValue) + ", " + + std::to_string(idx) + ");"); + m_constructorp->addStmtsp(initp); + for (AstCStmt* const ns : namerStmts) m_constructorp->addStmtsp(ns); + if (v3Global.opt.coverage()) { + AstCStmt* const regp = new AstCStmt{fl}; + regp->add(memberRef(fl, cpVarp)); + regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"v_covergroup/" + + m_covergroupp->name() + "\");"); + m_constructorp->addStmtsp(regp); + } + } + // Generate matching code for default bins // Default bins match when value doesn't match any other explicit bin void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp, AstNodeExpr* exprp, AstVar* hitVarp) { UINFO(4, " Generating default bin match for: " << defBinp->name()); - // Build OR of all non-default, non-ignore bins - AstNodeExpr* anyBinMatchp = nullptr; - - for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { - AstCoverBin* const cbinp = VN_AS(binp, CoverBin); - - // Skip default, ignore, and illegal bins - if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT - || cbinp->binsType() == VCoverBinsType::BINS_IGNORE - || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - continue; - } - - // Build condition for this bin - AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); - UASSERT_OBJ(binCondp, cbinp, - "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); - - // OR with previous conditions - if (anyBinMatchp) { - anyBinMatchp = new AstOr{defBinp->fileline(), anyBinMatchp, binCondp}; - } else { - anyBinMatchp = binCondp; - } - } - - // Default matches when NO explicit bin matches - AstNodeExpr* defaultCondp = nullptr; - if (anyBinMatchp) { - // NOT (bin1 OR bin2 OR ... OR binN) - defaultCondp = new AstNot{defBinp->fileline(), anyBinMatchp}; - } else { - // No other bins - default always matches (shouldn't happen in practice) - defaultCondp = new AstConst{defBinp->fileline(), AstConst::BitTrue{}}; - } + AstNodeExpr* defaultCondp = buildDefaultCondition(coverpointp, exprp, defBinp->fileline()); // Apply iff condition if present if (AstNodeExpr* iffp = coverpointp->iffp()) { @@ -903,34 +1090,10 @@ class FunctionalCoverageVisitor final : public VNVisitor { int atLeastValue) { UINFO(4, " Generating array bins for: " << arrayBinp->name()); - // Extract all values from the range list - std::vector values; - for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { - if (AstInsideRange* const insideRangep = VN_CAST(rangep, InsideRange)) { - // For InsideRange [min:max], create bins for each value - AstNodeExpr* const minp = V3Const::constifyEdit(insideRangep->lhsp()); - AstNodeExpr* const maxp = V3Const::constifyEdit(insideRangep->rhsp()); - AstConst* const minConstp = VN_CAST(minp, Const); - AstConst* const maxConstp = VN_CAST(maxp, Const); - if (minConstp && maxConstp) { - const int minVal = minConstp->toSInt(); - const int maxVal = maxConstp->toSInt(); - UINFO(6, " Expanding InsideRange [" << minVal << ":" << maxVal << "]"); - for (int val = minVal; val <= maxVal; ++val) { - values.push_back(new AstConst{insideRangep->fileline(), - AstConst::WidthedValue{}, - (int)exprp->width(), (uint32_t)val}); - } - } else { - arrayBinp->v3error("Non-constant expression in array bins range; " - "range bounds must be constants"); - return; - } - } else { - // Single value - should be an expression - values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); - } - } + // Extract all values from the range list (resolves '$', caps/ignores huge ranges) + bool unsupported = false; + std::vector values = extractArrayValues(arrayBinp, exprp, unsupported); + if (unsupported) return; // bin ignored (COVERIGN emitted) // Create a separate bin for each value int index = 0; @@ -1132,6 +1295,18 @@ class FunctionalCoverageVisitor final : public VNVisitor { while (itemp) { AstNode* const nextp = itemp->nextp(); AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef); + if (refp->exprp()) { + // Non-standard hierarchical/dotted cross item (e.g. 'cross a.b'): an implicit + // coverpoint over the referenced expression (carried in refp->exprp()). The + // grammar already warned NONSTD; implicit coverpoints are not yet implemented, so + // generate no sampling code for this cross. When support is added the implicit + // coverpoint should be synthesized upstream (V3LinkParse) as a real AstCoverpoint + // so it flows through the normal coverpoint path - by here coverpoint lowering has + // already run. + refp->v3warn(COVERIGN, + "Unsupported: cross of hierarchical reference (implicit coverpoint)"); + return; + } // Find the referenced coverpoint via name map (O(log n) vs O(n) linear scan) const auto it = m_coverpointMap.find(refp->name()); AstCoverpoint* const foundCpp = (it != m_coverpointMap.end()) ? it->second : nullptr; @@ -1320,15 +1495,50 @@ class FunctionalCoverageVisitor final : public VNVisitor { void generateCoverageMethodBody(AstFunc* funcp) { FileLine* const fl = funcp->fileline(); + AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Count total bins (excluding ignore_bins and illegal_bins) + // Converted coverpoints hold their bins in VlCoverpoint. Combine their contributions + // (via coverageParts) with any remaining legacy cross/cross-fed bins as the same flat + // covered/total ratio the all-legacy path below computes. Normal bins only: ignore, + // illegal, and default are excluded (LRM 19.5). + if (!m_convCpVars.empty()) { + AstCStmt* const headp = new AstCStmt{fl}; + headp->add("double __Vcov = 0.0; double __Vtot = 0.0;"); + funcp->addStmtsp(headp); + for (AstVar* const cpVarp : m_convCpVars) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("{ double __Vc = 0.0; double __Vt = 0.0; "); + cs->add(memberRef(fl, cpVarp)); + cs->add(".coverageParts(__Vc, __Vt); __Vcov += __Vc; __Vtot += __Vt; }"); + funcp->addStmtsp(cs); + } + int legacyRegular = 0; + for (const BinInfo& bi : m_binInfos) { + if (!bi.binp->binsType().binIsNormal()) continue; + ++legacyRegular; + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("if ("); + cs->add(memberRef(fl, bi.varp)); + cs->add(" >= " + std::to_string(bi.atLeast) + ") __Vcov += 1.0;"); + funcp->addStmtsp(cs); + } + if (legacyRegular) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("__Vtot += " + std::to_string(legacyRegular) + ".0;"); + funcp->addStmtsp(cs); + } + AstCStmt* const retp = new AstCStmt{fl}; + retp->add(new AstVarRef{fl, returnVarp, VAccess::WRITE}); + retp->add(" = (__Vtot != 0.0) ? (100.0 * __Vcov / __Vtot) : 100.0;"); + funcp->addStmtsp(retp); + return; + } + + // Count total bins (Normal only: excludes ignore/illegal/default) int totalBins = 0; for (const BinInfo& bi : m_binInfos) { UINFO(6, " Bin: " << bi.binp->name() << " type=" << bi.binp->binsType().ascii()); - if (bi.binp->binsType() != VCoverBinsType::BINS_IGNORE - && bi.binp->binsType() != VCoverBinsType::BINS_ILLEGAL) { - totalBins++; - } + if (bi.binp->binsType().binIsNormal()) totalBins++; } UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size()); @@ -1337,7 +1547,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { // No coverage to compute - return 100%. // Any parser-generated initialization of returnVar is overridden by our assignment. UINFO(4, " Empty covergroup, returning 100.0"); - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, new AstConst{fl, AstConst::RealDouble{}, 100.0}}); UINFO(4, " Added assignment to return 100.0"); @@ -1356,11 +1565,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // For each regular bin, if count > 0, increment covered_count for (const BinInfo& bi : m_binInfos) { - // Skip ignore_bins and illegal_bins in coverage calculation - if (bi.binp->binsType() == VCoverBinsType::BINS_IGNORE - || bi.binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - continue; - } + // Skip ignore/illegal/default bins in coverage calculation + if (!bi.binp->binsType().binIsNormal()) continue; // if (bin_count >= at_least) covered_count++; AstIf* ifp = new AstIf{ @@ -1375,9 +1581,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { funcp->addStmtsp(ifp); } - // Find the return variable - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Calculate coverage: (covered_count / total_bins) * 100.0 // return_var = (double)covered_count / (double)total_bins * 100.0 @@ -1524,15 +1727,12 @@ class FunctionalCoverageVisitor final : public VNVisitor { VL_RESTORER(m_covergroupp); VL_RESTORER(m_sampleFuncp); VL_RESTORER(m_constructorp); - VL_RESTORER(m_coverpoints); - VL_RESTORER(m_coverpointMap); - VL_RESTORER(m_coverCrosses); + VL_RESTORER_CLEAR(m_coverpoints); + VL_RESTORER_CLEAR(m_coverpointMap); + VL_RESTORER_CLEAR(m_coverCrosses); m_covergroupp = nodep; m_sampleFuncp = nullptr; m_constructorp = nullptr; - m_coverpoints.clear(); - m_coverpointMap.clear(); - m_coverCrosses.clear(); // Extract and store the clocking event from AstCovergroup node // The parser creates this node to preserve the event information diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index 1a08da9e8..411ec57eb 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -597,6 +597,7 @@ public: // We may have removed some datatypes, cleanup nodep->typeTablep()->repairCache(); VIsCached::clearCacheTree(); // Removing assignments may affect isPure + nodep->constPoolp()->rebuildVarScopesAndCache(); } ~DeadVisitor() override { V3Stats::addStatSum("Optimizations, deadified FTasks", m_statFTasksDeadified); diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index 1f6758629..f2ed71ebc 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -275,6 +275,7 @@ class DelayedVisitor final : public VNVisitor { bool m_inSuspendableOrFork = false; // True in suspendable processes and forks bool m_ignoreBlkAndNBlk = false; // Suppress delayed assignment BLKANDNBLK bool m_inNonCombLogic = false; // We are in non-combinational logic + bool m_needsInitialTrigger = false; // Whether a NodeProcedure needs a initial trigger AstVarRef* m_currNbaLhsRefp = nullptr; // Current NBA LHS variable reference // STATE - during NBA conversion (after visit) @@ -291,6 +292,7 @@ class DelayedVisitor final : public VNVisitor { VDouble0 m_nSchemeValueQueuesWhole; // Number of variables using Scheme::ValueQueueWhole VDouble0 m_nSchemeValueQueuesPartial; // Number of variables using Scheme::ValueQueuePartial VDouble0 m_nSharedSetFlags; // "Set" flags actually shared by Scheme::FlagShared variables + VDouble0 m_nInitialNBA; // Number of procedural blocks with initial NBA // METHODS @@ -999,6 +1001,12 @@ class DelayedVisitor final : public VNVisitor { m_writeRefs(nodep->varScopep()).emplace_back(nodep, nonBlocking, m_inNonCombLogic); } + template + static bool isProcedureWithSentreep(const AstNodeProcedure* const nodep) { + const Procedure_T* const procedurep = AstNode::cast(nodep); + return procedurep && procedurep->sentreep(); + } + // VISITORS void visit(AstNetlist* nodep) override { iterateChildren(nodep); @@ -1112,21 +1120,36 @@ class DelayedVisitor final : public VNVisitor { iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { + VL_RESTORER(m_needsInitialTrigger); const size_t firstNBAAddedIndex = m_nbas.size(); { VL_RESTORER(m_inSuspendableOrFork); VL_RESTORER(m_procp); VL_RESTORER(m_ignoreBlkAndNBlk); VL_RESTORER(m_inNonCombLogic); - m_inSuspendableOrFork = nodep->isSuspendable(); + // When we are dealing with initial block we need to + // treat it as suspendable when we meet a NBA + m_inSuspendableOrFork = nodep->isSuspendable() || VN_IS(nodep, Initial); m_procp = nodep; - if (m_inSuspendableOrFork) { + if (nodep->isSuspendable()) { m_ignoreBlkAndNBlk = false; m_inNonCombLogic = true; } iterateChildren(nodep); } - if (m_timingDomains.empty()) return; + auto containsClocled = [](const AstSenItem* itemp) { + while (itemp) { + if (itemp->edgeType().clockedStmt()) return true; + itemp = VN_AS(itemp->nextp(), SenItem); + } + return false; + }; + const bool addInitialTrigger = m_needsInitialTrigger + && !(isProcedureWithSentreep(nodep) + || isProcedureWithSentreep(nodep) + || isProcedureWithSentreep(nodep)) + && !containsClocled(m_activep->sentreep()->sensesp()); + if (m_timingDomains.empty() && !addInitialTrigger) return; // There were some timing domains involved in the process. Add all of them as sensitivities // of all NBA targets in this process. Note this is a bit of a sledgehammer, we should only @@ -1135,6 +1158,11 @@ class DelayedVisitor final : public VNVisitor { // First gather all senItems AstSenItem* senItemp = nullptr; + if (addInitialTrigger) { + senItemp = new AstSenItem{nodep->fileline(), AstSenItem::InitialNBA{}}; + ++m_nInitialNBA; + } + for (const AstSenTree* const domainp : m_timingDomains) { if (domainp->sensesp()) senItemp = AstNode::addNext(senItemp, domainp->sensesp()->cloneTree(true)); @@ -1218,6 +1246,8 @@ class DelayedVisitor final : public VNVisitor { UASSERT_OBJ(m_inSuspendableOrFork || m_activep->hasClocked(), nodep, "<= assignment in non-clocked block, should have been converted in V3Active"); + m_needsInitialTrigger |= m_timingDomains.empty(); + // Record scope of this NBA nodep->user2p(m_scopep); @@ -1327,6 +1357,7 @@ public: V3Stats::addStat("NBA, variables using ValueQueuePartial scheme", m_nSchemeValueQueuesPartial); V3Stats::addStat("Optimizations, NBA flags shared", m_nSharedSetFlags); + V3Stats::addStat("Procedures needing initial NBA trigger", m_nInitialNBA); } }; diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 637cf184f..2fd5afb95 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -96,6 +96,11 @@ std::unique_ptr DfgGraph::clone() const { vtxp2clonep.emplace(&vtx, cp); break; } // LCOV_EXCL_STOP + case VDfgType::MatchMasked: { + DfgMatchMasked* const cp = new DfgMatchMasked{*clonep, vtx.fileline(), vtx.dtype()}; + vtxp2clonep.emplace(&vtx, cp); + break; + } case VDfgType::Sel: { DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtype()}; cp->lsb(vtx.as()->lsb()); @@ -615,6 +620,12 @@ DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataT dfg.addVertex(*this); } +bool DfgVertex::unsafe() const { + if (is()) return true; + if (is()) return !as()->bitp()->is(); + return false; +} + void DfgVertex::typeCheck(const DfgGraph& dfg) const { #define CHECK(cond, msg) \ @@ -672,6 +683,15 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const { CHECK(v.dtype() == DfgDataType::select(v.srcp()->dtype(), v.lsb(), v.size()), "sel"); return; } + case VDfgType::MatchMasked: { + const DfgMatchMasked& v = *as(); + CHECK(v.isPacked(), "Should be Packed type"); + CHECK(v.size() == 32U, "Should yield a 32-bit result"); + CHECK(v.lhsp()->isPacked(), "Lhs should be packed"); + CHECK(v.matchp()->isPacked(), "Match should be Packed type"); + CHECK(v.matchp()->is(), "Match should be a variable"); + return; + } case VDfgType::Mux: { const DfgMux& v = *as(); CHECK(v.isPacked(), "Should be Packed type"); @@ -945,6 +965,9 @@ void DfgVertex::unlinkDelete(DfgGraph& dfg) { delete this; } +//###################################################################### +// Renders the canonical pattern S-expression for a single DfgVertex + class DfgPatternString final { std::ostream& m_os; diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 95320daad..d80be4d84 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -195,6 +195,8 @@ public: UASSERT_OBJ(m_dtype.isPacked(), this, "Non packed vertex has no 'width'"); return m_dtype.size(); } + // Has terminating side-effect + bool unsafe() const; // Type check vertex (for debugging) void typeCheck(const DfgGraph& dfg) const; @@ -817,8 +819,11 @@ bool DfgVertex::isCheaperThanLoad() const { if (is()) return true; // Variables if (is()) return true; - // Array sels are just address computation - if (is()) return true; + // Array sels are just address computation, but the address itself can be expensive + if (const DfgArraySel* aselp = cast()) { + if (aselp->bitp()->is()) return false; + return true; + } // Small select from variable if (const DfgSel* const selp = cast()) { if (!selp->fromp()->is()) return false; diff --git a/src/V3DfgBreakCycles.cpp b/src/V3DfgBreakCycles.cpp index 73789cb9b..c1e3b28c5 100644 --- a/src/V3DfgBreakCycles.cpp +++ b/src/V3DfgBreakCycles.cpp @@ -31,8 +31,109 @@ VL_DEFINE_DEBUG_FUNCTIONS; namespace V3DfgBreakCycles { -// Throughout these algorithm, we use the DfgUserMap as a map to the SCC number -using Vtx2Scc = DfgUserMap; +// Mutable map from vertices to the SCC index they belong to. Initialized +// from a proper analysis, then updated as we break cycles. After updates +// the information is consertvative, meaning a vertex that is marked as +// cyclic might not actually be if an SCC split into multple SCCs after a +// fixup. However it should always be true that if a vertex is thought to +// be non-cyclic based on this datastructure, then it indeed is not cyclic. +class SccInfo final { + DfgGraph& m_dfg; // The annotated graph + // The map from vertices to their SCC index (0 if trivial SCC - non cyclic) + std::unordered_map m_vtx2Scc; + size_t m_nCyclicVertices = 0; // Number of vertices in non-trivial SCCs + +public: + SccInfo(DfgGraph& dfg) + : m_dfg{dfg} { + // Initialize m_vtx2Scc for the graph from a proper analysis + DfgUserMap vtx2Scc = dfg.makeUserMap(); + V3DfgPasses::colorStronglyConnectedComponents(dfg, vtx2Scc); + m_dfg.forEachVertex([&](const DfgVertex& vtx) { + const uint64_t sccIdx = vtx2Scc[vtx]; + m_vtx2Scc[&vtx] = sccIdx; + if (sccIdx) ++m_nCyclicVertices; + }); + } + + // Is the graph cyclic still? + bool isCyclic() const { return m_nCyclicVertices; } + + // Returns the index of the SCC the given vertex is a part of + // This is 0 iff the vertex is in a trivial SCC (no cycles through vertex) + uint64_t get(const DfgVertex& vtx) const { return m_vtx2Scc.at(&vtx); } + + // Add a new vertex to graph that is part of the given SCC + void add(const DfgVertex& vtx, uint64_t sccIdx) { + const bool newEntry = m_vtx2Scc.emplace(&vtx, sccIdx).second; + UASSERT_OBJ(newEntry, &vtx, "Vertex already inserted"); + if (sccIdx) ++m_nCyclicVertices; + } + + bool stillCyclicFwd(const DfgVertex& vtx) const { + // If it only reads non-cyclic vertices, it has also become acyclic + return vtx.foreachSource([&](const DfgVertex& src) { // + return m_vtx2Scc.at(&src); + }); + } + + bool stillCyclicBwd(const DfgVertex& vtx) const { + // If itonly drives non-cyclic vertices, it has also become acyclic + return vtx.foreachSink([&](const DfgVertex& dst) { // + return m_vtx2Scc.at(&dst); + }); + } + + // The given vertex is known to have become acyclic after some edits, + // propagate through graph to mark connected vertices as well. + void updateAcyclic(const DfgVertex& vtx) { + // Mark as acyclic + uint64_t& sccIdxr = m_vtx2Scc.at(&vtx); + UASSERT_OBJ(sccIdxr, &vtx, "Vertex should be cyclic"); + sccIdxr = 0; + --m_nCyclicVertices; + // Propagate the update through the graph forward + vtx.foreachSink([&](const DfgVertex& dst) { + // Sink was known to be acyclic, stop + if (!m_vtx2Scc.at(&dst)) return false; + if (!stillCyclicFwd(dst)) updateAcyclic(dst); + return false; + }); + // Propagate the update through the graph backward + vtx.foreachSource([&](const DfgVertex& src) { + // Source was known to be acyclic, stop + if (!m_vtx2Scc.at(&src)) return false; + if (!stillCyclicBwd(src)) updateAcyclic(src); + return false; + }); + } + + /* + Check stored information is consistent with actual SCCs. Note we + can't detect during updates if an SCC has split into multiple SCCs. + Consider: + A -- E -- G + / \ / \ + B C H I + \ / \ / + D -- F -- J + If we fixed up E, the original SCC would split into two, (A,B,C,D) + and (G,H,I,J), but also F would no longer be cyclic. For this reason, + we can only assert that anything that we think is not cyclic based + on the SccInfo must indeed be not cyclic, but might still think a + vertex is cyclic based on the SccInfo but it actually isn't. + */ + void validate() const { + // Re-compute the actual SCCs + DfgUserMap actual = m_dfg.makeUserMap(); + V3DfgPasses::colorStronglyConnectedComponents(m_dfg, actual); + // Assert that if we think a vertex is not cyclic, it indeed is not + m_dfg.forEachVertex([&](const DfgVertex& vtx) { + // 'think not cyclic' implies 'actually not cyclic' + UASSERT_OBJ(m_vtx2Scc.at(&vtx) || !actual.at(vtx), &vtx, "Inconsisten SccInfo"); + }); + } +}; class TraceDriver final : public DfgVisitor { // TYPES @@ -67,11 +168,12 @@ class TraceDriver final : public DfgVisitor { // STATE DfgGraph& m_dfg; // The graph being processed - Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map + SccInfo& m_sccInfo; // The SccInfo instance // The strongly connected component we are currently trying to escape uint64_t m_component = 0; uint32_t m_lsb = 0; // LSB to extract from the currently visited Vertex uint32_t m_msb = 0; // MSB to extract from the currently visited Vertex + std::vector m_idxs; // Indices to extract from the currently visited Vertex // Result of tracing the currently visited Vertex. Use SET_RESULT below! DfgVertex* m_resp = nullptr; DfgVertex* m_defaultp = nullptr; // When tracing a variable, this is its 'defaultp', if any @@ -86,11 +188,11 @@ class TraceDriver final : public DfgVisitor { // Create and return a new Vertex. Always use this to create new vertices. // Fileline is taken from 'refp', but 'refp' is otherwise not used. - // Also sets m_vtx2Scc[vtx] to 0, indicating the new vertex is not part of - // a strongly connected component. This should always be true, as all the - // vertices we create here are driven from outside the component we are - // trying to escape, and will sink into that component. Given those are - // separate SCCs, these new vertices must be acyclic. + // Also adds the vertex to m_sccInfo with scc index 0, indicating the new + // vertex is not part of a strongly connected component. This should always + // be true, as all the vertices we create here are driven from outside the + // component we are trying to escape, and will sink into that component. + // Given those are separate SCCs, these new vertices must be acyclic. template Vertex* make(const DfgVertex* refp, uint32_t width) { static_assert(std::is_base_of::value // @@ -99,7 +201,7 @@ class TraceDriver final : public DfgVisitor { if VL_CONSTEXPR_CXX17 (std::is_same::value) { DfgConst* const vtxp = new DfgConst{m_dfg, refp->fileline(), width, 0}; - m_vtx2Scc[vtxp] = 0; + m_sccInfo.add(*vtxp, 0); return reinterpret_cast(vtxp); } else { // TODO: this is a gross hack around lack of C++17 'if constexpr' Vtx is always Vertex @@ -108,7 +210,7 @@ class TraceDriver final : public DfgVisitor { using Vtx = typename std::conditional::value, DfgSel, Vertex>::type; Vtx* const vtxp = new Vtx{m_dfg, refp->fileline(), DfgDataType::packed(width)}; - m_vtx2Scc[vtxp] = 0; + m_sccInfo.add(*vtxp, 0); return reinterpret_cast(vtxp); } } @@ -123,12 +225,11 @@ class TraceDriver final : public DfgVisitor { DfgVertexVar* const varp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep); varp->vscp()->varp()->isInternal(true); varp->tmpForp(varp->vscp()); - m_vtx2Scc[varp] = 0; + m_sccInfo.add(*varp, 0); return varp; } - // Continue tracing drivers of the given vertex, at the given LSB. - // Every visitor should call this to continue the traversal. + // Trace drivers of the given packed vertex, at the given bit range. DfgVertex* trace(DfgVertex* const vtxp, const uint32_t msb, const uint32_t lsb) { UASSERT_OBJ(vtxp->isPacked(), vtxp, "Can only trace packed type vertices"); UASSERT_OBJ(vtxp->size() > msb, vtxp, "Traced Vertex too narrow"); @@ -146,7 +247,7 @@ class TraceDriver final : public DfgVisitor { // If already traced this vtxp/msb/lsb, just use the result. // This is important to avoid combinatorial explosion when the // same sub-expression is needed multiple times. - } else if (m_vtx2Scc.at(vtxp) != m_component) { + } else if (m_sccInfo.get(*vtxp) != m_component) { // If the currently traced vertex is in a different component, // then we found what we were looking for. respr = vtxp; @@ -170,6 +271,7 @@ class TraceDriver final : public DfgVisitor { // Otherwise visit the vertex to trace it VL_RESTORER(m_msb); VL_RESTORER(m_lsb); + VL_RESTORER_CLEAR(m_idxs); VL_RESTORER(m_resp); m_msb = msb; m_lsb = lsb; @@ -184,6 +286,40 @@ class TraceDriver final : public DfgVisitor { return respr; } + // Trace drivers of the given array vertex, at the current m_idxs, m_msb, m_lsb. + DfgVertex* traceSameIdx(DfgVertex* const vtxp) { + UASSERT_OBJ(vtxp->isArray(), vtxp, "Should be an array vertex"); + UASSERT_OBJ(!m_idxs.empty(), vtxp, "Should have a pending index"); + VL_RESTORER(m_resp); + m_resp = nullptr; + iterate(vtxp); // Resolve the element, the array visitors set 'm_resp' + UASSERT_OBJ(m_resp, vtxp, "Tracing driver failed for " << vtxp->typeName()); + UASSERT_OBJ(m_resp->width() == (m_msb - m_lsb + 1), vtxp, "Wrong result width"); + return m_resp; + } + + // Trace drivers of the given array vertex, pushing the given index, at current m_msb, m_lsb. + DfgVertex* tracePushIdx(DfgVertex* const vtxp, const uint32_t idx) { + const size_t nIdxs = m_idxs.size(); + m_idxs.push_back(idx); + DfgVertex* const resp = traceSameIdx(vtxp); + m_idxs.pop_back(); + UASSERT_OBJ(m_idxs.size() == nIdxs, vtxp, "Index stack size mismatch"); + return resp; + } + + // Trace drivers of the given vertex, popping the innermost index, at current m_msb, m_lsb. + DfgVertex* tracePopIdx(DfgVertex* const vtxp) { + UASSERT_OBJ(!m_idxs.empty(), vtxp, "Should have a pending index"); + const size_t nIdxs = m_idxs.size(); + const uint32_t idx = m_idxs.back(); + m_idxs.pop_back(); + DfgVertex* const resp = m_idxs.empty() ? trace(vtxp, m_msb, m_lsb) : traceSameIdx(vtxp); + m_idxs.push_back(idx); + UASSERT_OBJ(m_idxs.size() == nIdxs, vtxp, "Index stack size mismatch"); + return resp; + } + template Vertex* traceBitwiseBinary(Vertex* vtxp) { static_assert(std::is_base_of::value, @@ -287,25 +423,26 @@ class TraceDriver final : public DfgVisitor { // Gather terms std::vector termps; + uint32_t lsb = m_lsb; for (const Driver& driver : drivers) { // Driver is below the searched LSB, move on - if (m_lsb > driver.m_msb) continue; + if (lsb > driver.m_msb) continue; // Driver is above the searched MSB, done if (driver.m_lsb > m_msb) break; // Gap below this driver, trace default to fill it - if (driver.m_lsb > m_lsb) { + if (driver.m_lsb > lsb) { UASSERT_OBJ(m_defaultp, vtxp, "Should have a default driver if needs tracing"); - termps.emplace_back(trace(m_defaultp, driver.m_lsb - 1, m_lsb)); - m_lsb = driver.m_lsb; + termps.emplace_back(trace(m_defaultp, driver.m_lsb - 1, lsb)); + lsb = driver.m_lsb; } // Driver covers searched range, pick the needed/available bits const uint32_t lim = std::min(m_msb, driver.m_msb); - termps.emplace_back(trace(driver.m_vtxp, lim - driver.m_lsb, m_lsb - driver.m_lsb)); - m_lsb = lim + 1; + termps.emplace_back(trace(driver.m_vtxp, lim - driver.m_lsb, lsb - driver.m_lsb)); + lsb = lim + 1; } - if (m_msb >= m_lsb) { + if (m_msb >= lsb) { UASSERT_OBJ(m_defaultp, vtxp, "Should have a default driver if needs tracing"); - termps.emplace_back(trace(m_defaultp, m_msb, m_lsb)); + termps.emplace_back(trace(m_defaultp, m_msb, lsb)); } // The earlier cheks cover the case when either a whole driver or the default covers @@ -324,33 +461,58 @@ class TraceDriver final : public DfgVisitor { SET_RESULT(resp); } - void visit(DfgVarPacked* vtxp) override { - UASSERT_OBJ(!vtxp->isVolatile(), vtxp, "Should not trace through volatile VarPacked"); + void visit(DfgSpliceArray* vtxp) override { + // Explicit per-element driver (a UnitArray wrapping the element value) + if (DfgVertex* const driverp = vtxp->driverAt(m_idxs.back())) { + // Consume this index, then trace the element value + SET_RESULT(tracePopIdx(driverp->as()->srcp())); + return; + } + // TODO: this is unreachable, as syntheis can't create it today. + // // Element not driven explicitly, so it comes from the default array. Keep the + // // index pending (the default is the whole array, indexed the same way) and + // // continue tracing it. + // UASSERT_OBJ(m_defaultp, vtxp, "Independent array element should have a driver or + // default"); SET RESULT(traceSameIdx(m_defaultp)); + } + + void visit(DfgVertexVar* vtxp) override { + UASSERT_OBJ(!vtxp->isVolatile(), vtxp, "Should not trace through volatile variable"); VL_RESTORER(m_defaultp); m_defaultp = vtxp->defaultp(); - SET_RESULT(trace(vtxp->srcp(), m_msb, m_lsb)); + DfgVertex* const drvp = vtxp->srcp() ? vtxp->srcp() : m_defaultp; + UASSERT_OBJ(drvp, vtxp, "Should not have to trace undriven variable"); + // Packed variable: trace the driver. Array variable: continue navigating it at + // the pending element (both at the same bit range). + SET_RESULT(m_idxs.empty() ? trace(drvp, m_msb, m_lsb) : traceSameIdx(drvp)); } void visit(DfgArraySel* vtxp) override { - // Only constant select - const DfgConst* const idxp = vtxp->bitp()->cast(); - if (!idxp) return; - // From a variable - const DfgVarArray* varp = vtxp->fromp()->cast(); - if (!varp) return; - UASSERT_OBJ(!varp->isVolatile(), vtxp, "Should not trace through volatile VarArray"); - // Skip through intermediate variables - while (varp->srcp() && varp->srcp()->is()) { - varp = varp->srcp()->as(); - UASSERT_OBJ(!varp->isVolatile(), vtxp, "Should not trace through volatile VarArray"); + // If constant index, push it and trace the selected element through the array + // structure of 'fromp'. This handles arbitrarily nested (multi-dimensional) + // arrays, as each nested ArraySel pushes a further index. + if (const DfgConst* const idxp = vtxp->bitp()->cast()) { + SET_RESULT(tracePushIdx(vtxp->fromp(), idxp->toU32())); + return; } - // Find driver - const DfgVertex* srcp = varp->srcp(); - if (const DfgSpliceArray* const splicep = srcp->cast()) { - srcp = splicep->driverAt(idxp->toSizeT()); - } - // Trace the driver, which at this point must be a UnitArray - SET_RESULT(trace(srcp->as()->srcp(), m_msb, m_lsb)); + + // If index is not constant, independence was proven only if the 'fromp' is + // independent, so no need to trace that, just reference it with the traced + // index. This only happens at the packed ArraySel leaf. + UASSERT_OBJ(m_idxs.empty(), vtxp, "Non-constant index below an outer array index"); + DfgArraySel* const resp = make(vtxp, vtxp->width()); + resp->fromp(vtxp->fromp()); + resp->bitp(trace(vtxp->bitp(), vtxp->bitp()->width() - 1, 0)); + DfgSel* const selp = make(vtxp, m_msb - m_lsb + 1); + selp->fromp(resp); + selp->lsb(m_lsb); + SET_RESULT(selp); + } + + void visit(DfgUnitArray* vtxp) override { + // Single-element array adapter, the pending index must be 0, unwrap the element + UASSERT_OBJ(m_idxs.back() == 0, vtxp, "UnitArray element index should be 0"); + SET_RESULT(tracePopIdx(vtxp->srcp())); } void visit(DfgConcat* vtxp) override { @@ -589,13 +751,23 @@ class TraceDriver final : public DfgVisitor { SET_RESULT(resp); } + void visit(DfgMatchMasked* vtxp) override { + DfgMatchMasked* const resp = make(vtxp, vtxp->width()); + resp->lhsp(trace(vtxp->lhsp(), vtxp->lhsp()->width() - 1, 0)); + resp->matchp(trace(vtxp->matchp(), vtxp->matchp()->width() - 1, 0)); + DfgSel* const selp = make(vtxp, m_msb - m_lsb + 1); + selp->fromp(resp); + selp->lsb(m_lsb); + SET_RESULT(selp); + } + #undef SET_RESULT public: // CONSTRUCTOR - TraceDriver(DfgGraph& dfg, Vtx2Scc& vtx2Scc) + TraceDriver(DfgGraph& dfg, SccInfo& sccInfo) : m_dfg{dfg} - , m_vtx2Scc{vtx2Scc} { + , m_sccInfo{sccInfo} { #ifdef VL_DEBUG if (v3Global.opt.debugCheck()) { m_lineCoverageFile.open( // @@ -612,11 +784,147 @@ public: // trace can always succeed. DfgVertex* apply(DfgVertex& vtx, uint32_t lsb, uint32_t width) { VL_RESTORER(m_component); - m_component = m_vtx2Scc.at(&vtx); + m_component = m_sccInfo.get(vtx); return trace(&vtx, lsb + width - 1, lsb); } }; +// A bit mask for each bit in a value, which can be either packed or aggregate type +class BitMask final { + // TYPES + const DfgDataType& m_dtype; + union { + V3Number m_num; // The bits, if packed + std::vector m_sub; // The per element masks, if aggregate + }; + +public: + // CONSTRUCTOR + BitMask(FileLine* flp, const DfgDataType& dtype) + : m_dtype{dtype} { + UASSERT_OBJ(!m_dtype.isNull(), flp, "Expected non-null data type"); + if (m_dtype.isPacked()) { + new (&m_num) V3Number{flp, static_cast(m_dtype.size()), 0}; + } else { + new (&m_sub) std::vector{}; + m_sub.reserve(m_dtype.size()); + for (uint32_t i = 0; i < m_dtype.size(); ++i) + m_sub.emplace_back(flp, m_dtype.elemDtype()); + } + } + BitMask(const DfgVertex& vtx) + : BitMask{vtx.fileline(), vtx.dtype()} {} + BitMask(const BitMask& other) + : m_dtype{other.m_dtype} { + if (m_dtype.isPacked()) { + new (&m_num) V3Number{other.m_num}; + } else { + new (&m_sub) std::vector{other.m_sub}; + } + } + BitMask& operator=(const BitMask& other) { + if (this != &other) { + UASSERT(m_dtype == other.m_dtype, "Expected same data type"); + if (other.m_dtype.isPacked()) { + m_num = other.m_num; + } else { + m_sub = other.m_sub; + } + } + return *this; + } + ~BitMask() { + if (m_dtype.isPacked()) { + m_num.~V3Number(); + } else { + m_sub.~vector(); + } + } + + // METHODS + V3Number& num() { + UASSERT(m_dtype.isPacked(), "Expected packed data type"); + return m_num; + } + const V3Number& num() const { + UASSERT(m_dtype.isPacked(), "Expected packed data type"); + return m_num; + } + std::vector& sub() { + UASSERT(m_dtype.isArray(), "Expected array data type"); + return m_sub; + } + + bool isZero() const { + if (m_dtype.isPacked()) return m_num.isEqZero(); + + for (const BitMask& sub : m_sub) { + if (!sub.isZero()) return false; + } + return true; + } + bool isOnes() const { + if (m_dtype.isPacked()) return m_num.isEqAllOnes(); + + for (const BitMask& sub : m_sub) { + if (!sub.isOnes()) return false; + } + return true; + } + void setZero() { + if (m_dtype.isPacked()) { + m_num.setAllBits0(); + } else { + for (BitMask& sub : m_sub) sub.setZero(); + } + } + void setOnes() { + if (m_dtype.isPacked()) { + m_num.setAllBits1(); + } else { + for (BitMask& sub : m_sub) sub.setOnes(); + } + } + + bool operator==(const BitMask& other) const { + UASSERT(m_dtype == other.m_dtype, "Expected same data type"); + if (m_dtype.isPacked()) return m_num.isCaseEq(other.m_num); + return m_sub == other.m_sub; + } + bool operator!=(const BitMask& other) const { return !(*this == other); } + + // 'this' has a clear bit where 'that' has a set bit, that is, + // 'this' has a proper subset of bits set compared to 'that'. + bool isContractionOf(const BitMask& that) const { + UASSERT(m_dtype == that.m_dtype, "Expected same data type"); + if (m_dtype.isPacked()) { + const size_t words = VL_WORDS_I(m_dtype.size()); + for (size_t i = 0; i < words; ++i) { + if (~m_num.edataWord(i) & that.m_num.edataWord(i)) return true; + } + return false; + } + + UASSERT(m_dtype.isArray(), "Expected array data type"); + for (size_t i = 0; i < m_sub.size(); ++i) { + if (m_sub[i].isContractionOf(that.m_sub[i])) return true; + } + return false; + } + + std::string toString() const { + if (m_dtype.isPacked()) return "0x" + m_num.displayed(m_num.fileline(), "%x"); + std::string result; + result += "{"; + for (const BitMask& sub : m_sub) { + if (&sub != &m_sub.front()) result += ", "; + result += sub.toString(); + } + result += "}"; + return result; + } +}; + class IndependentBits final : public DfgVisitor { // TYPES struct VertexState final { @@ -625,9 +933,9 @@ class IndependentBits final : public DfgVisitor { }; // STATE - const Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map + const SccInfo& m_sccInfo; // The SccInfo instance // Vertex to current bit mask map. The mask is set for the bits that are independent of the SCC - std::unordered_map m_vtxp2Mask; + std::unordered_map m_vtxp2Mask; // Work list for the traversal (min-queue of vertex RPO numbers) std::priority_queue, std::greater> m_workQueue; std::unordered_map m_vtxp2State; // State of each vertex @@ -637,21 +945,13 @@ class IndependentBits final : public DfgVisitor { #endif // METHODS - // Predicate to check if a vertex should be analysed directly - bool handledDirectly(const DfgVertex& vtx) const { - if (!vtx.isPacked()) return false; - if (vtx.is()) return false; - return true; - } // Retrieve the mask for the given vertex (create it with value 0 if needed) - V3Number& mask(const DfgVertex& vtx) { - UASSERT_OBJ(handledDirectly(vtx), &vtx, "Vertex should not be handled direclty"); - // Look up (or create) mask for 'vtxp' + BitMask& mask(const DfgVertex& vtx) { + // Look up (or create) mask for 'vtx' return m_vtxp2Mask .emplace(std::piecewise_construct, // - std::forward_as_tuple(&vtx), // - std::forward_as_tuple(vtx.fileline(), static_cast(vtx.width()), 0)) // + std::forward_as_tuple(&vtx), std::forward_as_tuple(vtx)) .first->second; } @@ -660,7 +960,7 @@ class IndependentBits final : public DfgVisitor { // TODO: Use C++20 std::source_location instead of a macro #ifdef VL_DEBUG #define MASK(vtxp) \ - ([this](const DfgVertex& vtx) -> V3Number& { \ + ([this](const DfgVertex& vtx) -> BitMask& { \ if (VL_UNLIKELY(m_lineCoverageFile.is_open())) m_lineCoverageFile << __LINE__ << '\n'; \ return mask(vtx); \ }(*vtxp)) @@ -686,13 +986,24 @@ class IndependentBits final : public DfgVisitor { } } - void propagateFromDriver(V3Number& m, const DfgVertex* srcp) { + void propagateFromDriver(BitMask& m, const DfgVertex* srcp) { // If there is no driver, we are done if (!srcp) return; // If it is driven by a splice, we need to combine the masks of the drivers if (const DfgSplicePacked* const splicep = srcp->cast()) { splicep->foreachDriver([&](const DfgVertex& src, uint32_t lo) { - m.opSelInto(MASK(&src), lo, src.width()); + m.num().opSelInto(MASK(&src).num(), lo, src.width()); + return false; + }); + return; + } + if (const DfgSpliceArray* const splicep = srcp->cast()) { + splicep->foreachDriver([&](const DfgVertex& src, uint32_t lo) { + if (const DfgUnitArray* const uap = src.cast()) { + m.sub().at(lo) = MASK(uap->srcp()); + } else { + // m.sub().at(lo) = Can't happen MASK(&src); + } return false; }); return; @@ -703,15 +1014,15 @@ class IndependentBits final : public DfgVisitor { // VISITORS void visit(DfgVertex* vtxp) override { // LCOV_EXCL_START - UASSERT_OBJ(handledDirectly(*vtxp), vtxp, "Vertex should be handled direclty"); UINFO(9, "IndependentBits - Unhandled vertex type: " << vtxp->typeName()); // Conservative assumption about all bits being dependent prevails } // LCOV_EXCL_STOP - void visit(DfgVarPacked* vtxp) override { + void visit(DfgVertexVar* vtxp) override { // We cannot trace through a volatile variable, so pretend all bits are dependent if (vtxp->isVolatile()) return; - V3Number& m = MASK(vtxp); + + BitMask& m = MASK(vtxp); DfgVertex* const srcp = vtxp->srcp(); DfgVertex* const defaultp = vtxp->defaultp(); // If there is a default driver, we start from that @@ -720,70 +1031,62 @@ class IndependentBits final : public DfgVisitor { propagateFromDriver(m, srcp); } + void visit(DfgVertexSplice* vtxp) override { + propagateFromDriver(MASK(vtxp), vtxp); // Needed to continue traversal + } + + void visit(DfgUnitArray* vtxp) override { MASK(vtxp).sub().at(0) = MASK(vtxp->srcp()); } + void visit(DfgArraySel* vtxp) override { - // Only constant select - const DfgConst* const idxp = vtxp->bitp()->cast(); - if (!idxp) return; - // From a variable - const DfgVarArray* varp = vtxp->fromp()->cast(); - if (!varp) return; - // We cannot trace through a volatile variable, so pretend all bits are dependent - if (varp->isVolatile()) return; - // Skip through intermediate variables - while (varp->srcp() && varp->srcp()->is()) { - varp = varp->srcp()->as(); - if (varp->isVolatile()) return; + DfgVertex* const fromp = vtxp->fromp(); + + // If constant index, copy mask of relevant element + if (const DfgConst* const idxp = vtxp->bitp()->cast()) { + MASK(vtxp) = MASK(vtxp->fromp()).sub().at(idxp->toSizeT()); + return; } - // Find driver - const DfgVertex* srcp = varp->srcp(); - if (!srcp) return; - if (const DfgSpliceArray* const splicep = srcp->cast()) { - srcp = splicep->driverAt(idxp->toSizeT()); - if (!srcp) return; - } - const DfgUnitArray* uap = srcp->cast(); - if (!uap) return; - srcp = uap->srcp(); - // Propagate from driver - propagateFromDriver(MASK(vtxp), srcp); + + // If index is not constant, independent only if the index is indenpendent, and the array + // is independent. TODO: could relax by '&' reducing, not sure if worth it. + if (MASK(vtxp->bitp()).isOnes() && MASK(fromp).isOnes()) MASK(vtxp).setOnes(); } void visit(DfgConcat* vtxp) override { const DfgVertex* const rhsp = vtxp->rhsp(); const DfgVertex* const lhsp = vtxp->lhsp(); - V3Number& m = MASK(vtxp); - m.opSelInto(MASK(rhsp), 0, rhsp->width()); - m.opSelInto(MASK(lhsp), rhsp->width(), lhsp->width()); + V3Number& m = MASK(vtxp).num(); + m.opSelInto(MASK(rhsp).num(), 0, rhsp->width()); + m.opSelInto(MASK(lhsp).num(), rhsp->width(), lhsp->width()); } void visit(DfgRep* vtxp) override { const uint32_t count = vtxp->count(); const DfgVertex* const srcp = vtxp->srcp(); const uint32_t sWidth = srcp->width(); - V3Number& vMask = MASK(vtxp); - V3Number& sMask = MASK(srcp); + V3Number& vMask = MASK(vtxp).num(); + V3Number& sMask = MASK(srcp).num(); for (uint32_t i = 0; i < count; ++i) vMask.opSelInto(sMask, i * sWidth, sWidth); } void visit(DfgSel* vtxp) override { const uint32_t lsb = vtxp->lsb(); const uint32_t msb = lsb + vtxp->width() - 1; - MASK(vtxp).opSel(MASK(vtxp->fromp()), msb, lsb); + MASK(vtxp).num().opSel(MASK(vtxp->fromp()).num(), msb, lsb); } void visit(DfgExtend* vtxp) override { const DfgVertex* const srcp = vtxp->srcp(); const uint32_t sWidth = srcp->width(); - V3Number& s = MASK(srcp); - V3Number& m = MASK(vtxp); + V3Number& s = MASK(srcp).num(); + V3Number& m = MASK(vtxp).num(); m.opSelInto(s, 0, sWidth); m.opSetRange(sWidth, vtxp->width() - sWidth, '1'); } void visit(DfgExtendS* vtxp) override { const DfgVertex* const srcp = vtxp->srcp(); const uint32_t sWidth = srcp->width(); - V3Number& s = MASK(srcp); - V3Number& m = MASK(vtxp); + V3Number& s = MASK(srcp).num(); + V3Number& m = MASK(vtxp).num(); m.opSelInto(s, 0, sWidth); m.opSetRange(sWidth, vtxp->width() - sWidth, s.bitIs0(sWidth - 1) ? '0' : '1'); } @@ -793,71 +1096,62 @@ class IndependentBits final : public DfgVisitor { } void visit(DfgAnd* vtxp) override { // - MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp())); + MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num()); } void visit(DfgOr* vtxp) override { // - MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp())); + MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num()); } void visit(DfgXor* vtxp) override { // - MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp())); + MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num()); } void visit(DfgAdd* vtxp) override { - V3Number& m = MASK(vtxp); - m.opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp())); + V3Number& m = MASK(vtxp).num(); + m.opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num()); floodTowardsMsb(m); } void visit(DfgSub* vtxp) override { // Same as Add: 2's complement (a - b) == (a + ~b + 1) - V3Number& m = MASK(vtxp); - m.opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp())); + V3Number& m = MASK(vtxp).num(); + m.opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num()); floodTowardsMsb(m); } - void visit(DfgRedAnd* vtxp) override { // - if (MASK(vtxp->lhsp()).isEqAllOnes()) { // - MASK(vtxp).setAllBits1(); - } + void visit(DfgRedAnd* vtxp) override { + const bool independent = MASK(vtxp->lhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } - void visit(DfgRedOr* vtxp) override { // - if (MASK(vtxp->lhsp()).isEqAllOnes()) { // - MASK(vtxp).setAllBits1(); - } + void visit(DfgRedOr* vtxp) override { + const bool independent = MASK(vtxp->lhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } - void visit(DfgRedXor* vtxp) override { // - if (MASK(vtxp->lhsp()).isEqAllOnes()) { // - MASK(vtxp).setAllBits1(); - } + void visit(DfgRedXor* vtxp) override { + const bool independent = MASK(vtxp->lhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgEq* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgNeq* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgLt* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgLte* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgGt* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgGte* vtxp) override { - const bool independent - = MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes(); - MASK(vtxp).setBit(0, independent ? '1' : '0'); + const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes(); + if (independent) MASK(vtxp).setOnes(); } void visit(DfgShiftRS* vtxp) override { @@ -869,14 +1163,10 @@ class IndependentBits final : public DfgVisitor { if (DfgConst* const rConstp = rhsp->cast()) { const uint32_t shiftAmount = rConstp->toU32(); if (shiftAmount >= width) { - if (MASK(lhsp).bitIs0(width - 1)) { - MASK(vtxp).setAllBits0(); - } else { - MASK(vtxp).setAllBits1(); - } + if (MASK(lhsp).num().bitIs1(width - 1)) { MASK(vtxp).setOnes(); } } else { - V3Number& m = MASK(vtxp); - m.opShiftRS(MASK(lhsp), rConstp->num(), width); + V3Number& m = MASK(vtxp).num(); + m.opShiftRS(MASK(lhsp).num(), rConstp->num(), width); m.opSetRange(width - shiftAmount, shiftAmount, '1'); } return; @@ -885,9 +1175,9 @@ class IndependentBits final : public DfgVisitor { // Otherwise, as the shift amount is non-negative, all independent // and consecutive top bits in the lhs yield an independent result // if the shift amount is independent. - if (MASK(rhsp).isEqAllOnes()) { - V3Number& m = MASK(vtxp); - m = MASK(lhsp); + if (MASK(rhsp).isOnes()) { + V3Number& m = MASK(vtxp).num(); + m = MASK(lhsp).num(); floodTowardsLsb(m); } } @@ -901,10 +1191,10 @@ class IndependentBits final : public DfgVisitor { if (DfgConst* const rConstp = rhsp->cast()) { const uint32_t shiftAmount = rConstp->toU32(); if (shiftAmount >= width) { - MASK(vtxp).setAllBits1(); + MASK(vtxp).setOnes(); } else { - V3Number& m = MASK(vtxp); - m.opShiftR(MASK(lhsp), rConstp->num()); + V3Number& m = MASK(vtxp).num(); + m.opShiftR(MASK(lhsp).num(), rConstp->num()); m.opSetRange(width - shiftAmount, shiftAmount, '1'); } return; @@ -913,9 +1203,9 @@ class IndependentBits final : public DfgVisitor { // Otherwise, as the shift amount is non-negative, all independent // and consecutive top bits in the lhs yield an independent result // if the shift amount is independent. - if (MASK(rhsp).isEqAllOnes()) { - V3Number& m = MASK(vtxp); - m = MASK(lhsp); + if (MASK(rhsp).isOnes()) { + V3Number& m = MASK(vtxp).num(); + m = MASK(lhsp).num(); floodTowardsLsb(m); } } @@ -929,10 +1219,10 @@ class IndependentBits final : public DfgVisitor { if (DfgConst* const rConstp = rhsp->cast()) { const uint32_t shiftAmount = rConstp->toU32(); if (shiftAmount >= width) { - MASK(vtxp).setAllBits1(); + MASK(vtxp).setOnes(); } else { - V3Number& m = MASK(vtxp); - m.opShiftL(MASK(lhsp), rConstp->num()); + V3Number& m = MASK(vtxp).num(); + m.opShiftL(MASK(lhsp).num(), rConstp->num()); m.opSetRange(0, shiftAmount, '1'); } return; @@ -941,31 +1231,30 @@ class IndependentBits final : public DfgVisitor { // Otherwise, as the shift amount is non-negative, all independent // and consecutive bottom bits in the lhs yield an independent result // if the shift amount is independent. - if (MASK(rhsp).isEqAllOnes()) { - V3Number& m = MASK(vtxp); - m = MASK(lhsp); + if (MASK(rhsp).isOnes()) { + V3Number& m = MASK(vtxp).num(); + m = MASK(lhsp).num(); floodTowardsMsb(m); } } void visit(DfgCond* vtxp) override { - if (MASK(vtxp->condp()).isEqAllOnes()) { - MASK(vtxp).opAnd(MASK(vtxp->thenp()), MASK(vtxp->elsep())); + if (MASK(vtxp->condp()).isOnes()) { + MASK(vtxp).num().opAnd(MASK(vtxp->thenp()).num(), MASK(vtxp->elsep()).num()); } } + void visit(DfgMatchMasked* vtxp) override { + if (MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->matchp()).isOnes()) { MASK(vtxp).setOnes(); } + } + #undef MASK // Enqueue sinks of vertex to work list for traversal - only called from constructor void enqueueSinks(DfgVertex& vtx) { vtx.foreachSink([&](DfgVertex& sink) { // Ignore if sink is not part of an SCC, already has all bits marked independent - if (!m_vtx2Scc.at(sink)) return false; - // If a vertex is not handled directly, recursively enqueue its sinks instead - if (!handledDirectly(sink)) { - enqueueSinks(sink); - return false; - } + if (!m_sccInfo.get(sink)) return false; // Otherwise just enqueue it VertexState& state = m_vtxp2State.at(&sink); if (!state.m_isOnWorkList) { @@ -991,8 +1280,8 @@ class IndependentBits final : public DfgVisitor { postOrderEnumeration.emplace_back(&vtx); }; - IndependentBits(DfgGraph& dfg, const Vtx2Scc& vtx2Scc) - : m_vtx2Scc{vtx2Scc} { + IndependentBits(DfgGraph& dfg, const SccInfo& sccInfo) + : m_sccInfo{sccInfo} { #ifdef VL_DEBUG if (v3Global.opt.debugCheck()) { @@ -1028,18 +1317,15 @@ class IndependentBits final : public DfgVisitor { // always assign constants bits, which are always independent (eg Extend/Shift) // Enqueue sinks of all SCC vertices that have at least one independent bit for (DfgVertex* const vtxp : rpoEnumeration) { - if (!handledDirectly(*vtxp)) continue; - if (m_vtx2Scc.at(vtxp)) continue; - mask(*vtxp).setAllBits1(); + if (m_sccInfo.get(*vtxp)) continue; + mask(*vtxp).setOnes(); } for (DfgVertex* const vtxp : rpoEnumeration) { - if (!handledDirectly(*vtxp)) continue; - if (!m_vtx2Scc.at(vtxp)) continue; + if (!m_sccInfo.get(*vtxp)) continue; iterate(vtxp); - UINFO(9, "Initial independent bits of " - << vtxp << " " << vtxp->typeName() - << " are: " << mask(*vtxp).displayed(vtxp->fileline(), "%b")); - if (!mask(*vtxp).isEqZero()) enqueueSinks(*vtxp); + UINFO(9, "Initial independent bits of " << vtxp << " " << vtxp->typeName() + << " are: " << mask(*vtxp).toString()); + if (!mask(*vtxp).isZero()) enqueueSinks(*vtxp); } // Propagate independent bits until no more changes are made @@ -1049,14 +1335,12 @@ class IndependentBits final : public DfgVisitor { m_workQueue.pop(); m_vtxp2State.at(currp).m_isOnWorkList = false; // Should not enqueue vertices that are not in an SCC - UASSERT_OBJ(m_vtx2Scc.at(currp), currp, "Vertex should be in an SCC"); - // Should only enqueue packed vertices - UASSERT_OBJ(handledDirectly(*currp), currp, "Vertex should be handled directly"); + UASSERT_OBJ(m_sccInfo.get(*currp), currp, "Vertex should be in an SCC"); // Grab current mask of item - const V3Number& currMask = mask(*currp); + const BitMask& currMask = mask(*currp); // Remember current mask so we can check if it changed - const V3Number prevMask = currMask; + const BitMask prevMask = currMask; UINFO(9, "Analyzing independent bits of " << currp << " " << currp->typeName()); @@ -1064,19 +1348,16 @@ class IndependentBits final : public DfgVisitor { iterate(currp); // If mask changed, enqueue sinks - if (!prevMask.isCaseEq(currMask)) { + if (prevMask != currMask) { UINFO(9, "Independent bits of " // << currp << " " << currp->typeName() << " changed" // - << "\n form: " << prevMask.displayed(currp->fileline(), "%b") - << "\n to: " << currMask.displayed(currp->fileline(), "%b")); + << "\n form: " << prevMask.toString() + << "\n to: " << currMask.toString()); enqueueSinks(*currp); // Check the mask only ever expands (no bit goes 1 -> 0) if (VL_UNLIKELY(v3Global.opt.debugCheck())) { - V3Number notCurr{currMask}; - notCurr.opNot(currMask); - V3Number prevAndNotCurr{currMask}; - prevAndNotCurr.opAnd(prevMask, notCurr); - UASSERT_OBJ(prevAndNotCurr.isEqZero(), currp, "Mask should only expand"); + UASSERT_OBJ(!currMask.isContractionOf(prevMask), currp, + "Mask should only expand"); } } } @@ -1087,32 +1368,32 @@ public: // returns a map from vertices to a bit mask, where a bit in the mask is // set if the corresponding bit in that vertex is known to be independent // of the values of vertices in the same SCC as the vertex resides in. - static std::unordered_map apply(DfgGraph& dfg, - const Vtx2Scc& vtx2Scc) { - return std::move(IndependentBits{dfg, vtx2Scc}.m_vtxp2Mask); + static std::unordered_map apply(DfgGraph& dfg, + const SccInfo& sccInfo) { + return std::move(IndependentBits{dfg, sccInfo}.m_vtxp2Mask); } }; class FixUp final { DfgGraph& m_dfg; // The graph being processed - Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map - TraceDriver m_traceDriver{m_dfg, m_vtx2Scc}; + SccInfo& m_sccInfo; // The SccInfo instance + TraceDriver m_traceDriver{m_dfg, m_sccInfo}; // The independent bits map - const std::unordered_map m_independentBits - = IndependentBits::apply(m_dfg, m_vtx2Scc); - size_t m_nImprovements = 0; // Number of improvements mde + const std::unordered_map m_independentBits + = IndependentBits::apply(m_dfg, m_sccInfo); + size_t m_nImprovements = 0; // Number of improvements made // Returns a bitmask set if that bit of 'vtx' is used (has a sink) - static V3Number computeUsedBits(DfgVertex& vtx) { - V3Number result{vtx.fileline(), static_cast(vtx.width()), 0}; + static BitMask computeUsedBits(DfgVertex& vtx) { + BitMask result{vtx}; vtx.foreachSink([&result](DfgVertex& sink) { // If used via a Sel, mark the selected bits used if (const DfgSel* const selp = sink.cast()) { - result.opSetRange(selp->lsb(), selp->width(), '1'); + result.num().opSetRange(selp->lsb(), selp->width(), '1'); return false; } // Used without a Sel, so all bits are used - result.setAllBits1(); + result.setOnes(); return true; }); return result; @@ -1149,7 +1430,7 @@ class FixUp final { // If dependent, fall back on using the part of the variable DfgSel* const selp = new DfgSel{m_dfg, vtx.fileline(), DfgDataType::packed(width)}; // Same component as 'vtxp', as reads 'vtxp' and will replace 'vtxp' - m_vtx2Scc[selp] = m_vtx2Scc.at(vtx); + m_sccInfo.add(*selp, m_sccInfo.get(vtx)); // Do not connect selp->fromp yet, need to do afer replacing 'vtxp' selp->lsb(lsb); termps.emplace_back(selp); @@ -1165,20 +1446,18 @@ class FixUp final { UASSERT_OBJ(!vtx.is(), &vtx, "Should not be a splice"); // Get which bits of 'vtxp' are independent of the SCCs - const V3Number& indpBits = m_independentBits.at(&vtx); - UINFO(9, "Independent bits of '" << debugStr(vtx) << "' are " - << indpBits.displayed(vtx.fileline(), "%b")); + const BitMask& indpBits = m_independentBits.at(&vtx); + UINFO(9, "Independent bits of '" << debugStr(vtx) << "' are " << indpBits.toString()); // Can't do anything if all bits are dependent - if (indpBits.isEqZero()) return; + if (indpBits.isZero()) return; // Figure out which bits of 'vtxp' are used - const V3Number usedBits = computeUsedBits(vtx); - UINFO(9, "Used bits of '" << debugStr(vtx) << "' are " - << usedBits.displayed(vtx.fileline(), "%b")); + const BitMask usedBits = computeUsedBits(vtx); + UINFO(9, "Used bits of '" << debugStr(vtx) << "' are " << usedBits.toString()); // Nothing to do if no used bits are independen (all used bits are dependent) V3Number usedAndIndependent{vtx.fileline(), static_cast(vtx.width()), 0}; - usedAndIndependent.opAnd(usedBits, indpBits); + usedAndIndependent.opAnd(usedBits.num(), indpBits.num()); if (usedAndIndependent.isEqZero()) return; // We are computing the terms to concatenate and replace 'vtxp' with @@ -1187,18 +1466,18 @@ class FixUp final { // Iterate through consecutive used/unused ranges FileLine* const flp = vtx.fileline(); const uint32_t width = vtx.width(); - bool isUsed = usedBits.bitIs1(0); // Is current range used + bool isUsed = usedBits.num().bitIs1(0); // Is current range used uint32_t lsb = 0; // LSB of current range for (uint32_t msb = 0; msb < width; ++msb) { - const bool endRange = msb == width - 1 || isUsed != usedBits.bitIs1(msb + 1); + const bool endRange = msb == width - 1 || isUsed != usedBits.num().bitIs1(msb + 1); if (!endRange) continue; if (isUsed) { // The range is used, compute the replacement terms - gatherTerms(termps, vtx, indpBits, msb, lsb); + gatherTerms(termps, vtx, indpBits.num(), msb, lsb); } else { // The range is not used, just use constant 0 as a placeholder DfgConst* const constp = new DfgConst{m_dfg, flp, msb - lsb + 1, 0}; - m_vtx2Scc[constp] = 0; + m_sccInfo.add(*constp, 0); termps.emplace_back(constp); } // Next iteration @@ -1208,7 +1487,7 @@ class FixUp final { // Concatenate all the terms to create the replacement DfgVertex* replacementp = termps.front(); - const uint64_t vComp = m_vtx2Scc.at(vtx); + const uint64_t vComp = m_sccInfo.get(vtx); for (size_t i = 1; i < termps.size(); ++i) { DfgVertex* const termp = termps[i]; const uint32_t catWidth = replacementp->width() + termp->width(); @@ -1220,9 +1499,9 @@ class FixUp final { // component, it's part of that component, otherwise its not // cyclic (all terms are from outside the original component, // and feed into the original component). - const uint64_t tComp = m_vtx2Scc.at(termp); - const uint64_t rComp = m_vtx2Scc.at(replacementp); - m_vtx2Scc[catp] = tComp == vComp || rComp == vComp ? vComp : 0; + const uint64_t tComp = m_sccInfo.get(*termp); + const uint64_t rComp = m_sccInfo.get(*replacementp); + m_sccInfo.add(*catp, tComp == vComp || rComp == vComp ? vComp : 0); replacementp = catp; } @@ -1234,6 +1513,22 @@ class FixUp final { if (!selp->fromp()) selp->fromp(&vtx); } } + + // If the vertex still has sinks, then the replacement is still cyclic, and vice versa + UASSERT_OBJ(!vtx.hasSinks() == !m_sccInfo.get(*replacementp), &vtx, + "Replacement vertex SCC inconsistent"); + + // If we broke the cycle through this vertex we can update the SccInfo + if (!vtx.hasSinks()) { + m_sccInfo.updateAcyclic(vtx); + if (!m_sccInfo.get(*replacementp)) { + replacementp->foreachSink([&](DfgVertex& dst) { + if (!m_sccInfo.get(dst)) return false; + if (!m_sccInfo.stillCyclicFwd(dst)) m_sccInfo.updateAcyclic(dst); + return false; + }); + } + } } void main(DfgVertexVar& var) { @@ -1244,10 +1539,10 @@ class FixUp final { fixUpPacked(var); } else if (var.is()) { // For array variables, fix up element-wise - const uint64_t component = m_vtx2Scc.at(var); + const uint64_t component = m_sccInfo.get(var); var.foreachSink([&](DfgVertex& sink) { // Ignore if sink is not part of same cycle - if (m_vtx2Scc.at(sink) != component) return false; + if (m_sccInfo.get(sink) != component) return false; // Only handle ArraySels with constant index DfgArraySel* const aselp = sink.cast(); if (!aselp) return false; @@ -1261,23 +1556,27 @@ class FixUp final { UINFO(9, "FixUp made " << m_nImprovements << " improvements"); } - FixUp(DfgGraph& dfg, Vtx2Scc& vtx2Scc) + FixUp(DfgGraph& dfg, SccInfo& sccInfo) : m_dfg{dfg} - , m_vtx2Scc{vtx2Scc} {} + , m_sccInfo{sccInfo} {} public: // Compute which bits of vertices are independent of the SCC they reside // in, and replace rferences to used independent bits with an equivalent // vertex that is not part of the same SCC. - static size_t apply(DfgGraph& dfg, Vtx2Scc& vtx2Scc) { - FixUp fixUp{dfg, vtx2Scc}; + static size_t apply(DfgGraph& dfg, SccInfo& sccInfo) { + if (!sccInfo.isCyclic()) return 0; + + FixUp fixUp{dfg, sccInfo}; // TODO: Compute minimal feedback vertex set and cut only those. // Sadly that is a computationally hard problem. - // Fix up cyclic variables + // Fix up cyclic variables, stop as soon as the graph becomes acyclic for (DfgVertexVar& vtx : dfg.varVertices()) { - if (vtx2Scc.at(vtx)) fixUp.main(vtx); + if (!sccInfo.get(vtx)) continue; + fixUp.main(vtx); + if (!sccInfo.isCyclic()) break; } // Return the number of improvements made return fixUp.m_nImprovements; @@ -1319,35 +1618,35 @@ breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) { // Iterate while an improvement can be made and the graph is still cyclic do { - // Color SCCs (populates DfgVertex::user()) - Vtx2Scc vtx2Scc = res.makeUserMap(); - const uint32_t numNonTrivialSCCs - = V3DfgPasses::colorStronglyConnectedComponents(res, vtx2Scc); - - // Congrats if it has become acyclic - if (!numNonTrivialSCCs) { - UINFO(7, "Graph became acyclic after " << nImprovements << " improvements"); - dump(7, res, "result-acyclic"); - ++ctx.m_breakCyclesContext.m_nFixed; - return {std::move(resultp), true}; - } + // Compute SCCs + SccInfo sccInfo{res}; // Fix up independent ranges in vertices UINFO(9, "New iteration after " << nImprovements << " improvements"); prevNImprovements = nImprovements; - const size_t nFixed = FixUp::apply(res, vtx2Scc); + const size_t nFixed = FixUp::apply(res, sccInfo); if (nFixed) { nImprovements += nFixed; ctx.m_breakCyclesContext.m_nImprovements += nFixed; dump(9, res, "FixUp"); } + + // Validate SccInfo if in debug mode + if (v3Global.opt.debugCheck()) sccInfo.validate(); + + // Congrats if it has become acyclic + if (!sccInfo.isCyclic()) { + UINFO(7, "Graph became acyclic after " << nImprovements << " improvements"); + dump(7, res, "result-acyclic"); + ++ctx.m_breakCyclesContext.m_nFixed; + return {std::move(resultp), true}; + } } while (nImprovements != prevNImprovements); if (dumpDfgLevel() >= 9) { - Vtx2Scc vtx2Scc = res.makeUserMap(); - V3DfgPasses::colorStronglyConnectedComponents(res, vtx2Scc); + const SccInfo sccInfo{res}; res.dumpDotFilePrefixed("breakCycles-remaining", [&](const DfgVertex& vtx) { - return vtx2Scc[vtx]; // + return sccInfo.get(vtx); // }); } diff --git a/src/V3DfgContext.h b/src/V3DfgContext.h index dc1b54666..21e7af31a 100644 --- a/src/V3DfgContext.h +++ b/src/V3DfgContext.h @@ -234,6 +234,26 @@ private: addStat("temporaries introduced", m_temporariesIntroduced); } }; +class V3DfgRemoveSelectsContext final : public V3DfgSubContext { + // Only V3DfgContext can create an instance + friend class V3DfgContext; + +public: + // STATE + VDouble0 m_removedFullWidth; // Number of full width selects removed + VDouble0 m_replacedWithSelFromFull; // Number of selects replaced with sel from full driver + VDouble0 m_replacedWithSelFromPart; // Number of selects replaced with sel from partial driver + VDouble0 m_replacedWithPart; // Number of selects replaced with part of driver +private: + V3DfgRemoveSelectsContext() + : V3DfgSubContext{"RemoveSelects"} {} + ~V3DfgRemoveSelectsContext() { + addStat("full width selects removed", m_removedFullWidth); + addStat("replaced with sel from full driver", m_replacedWithSelFromFull); + addStat("replaced with sel from partial driver", m_replacedWithSelFromPart); + addStat("replaced with partial driver", m_replacedWithPart); + } +}; class V3DfgRemoveUnobservableContext final : public V3DfgSubContext { // Only V3DfgContext can create an instance friend class V3DfgContext; @@ -399,6 +419,7 @@ public: V3DfgPeepholeContext m_peepholeContext; V3DfgPushDownSelsContext m_pushDownSelsContext; V3DfgRegularizeContext m_regularizeContext; + V3DfgRemoveSelectsContext m_removeSelectsContext; V3DfgRemoveUnobservableContext m_removeUnobservableContext; V3DfgSynthesisContext m_synthContext; diff --git a/src/V3DfgCse.cpp b/src/V3DfgCse.cpp index 7a60b4be7..c1b3862e4 100644 --- a/src/V3DfgCse.cpp +++ b/src/V3DfgCse.cpp @@ -72,6 +72,7 @@ class V3DfgCse final { } // Vertices with no internal information + case VDfgType::MatchMasked: case VDfgType::Mux: case VDfgType::UnitArray: return V3Hash{}; @@ -197,6 +198,7 @@ class V3DfgCse final { } // Vertices with no internal information + case VDfgType::MatchMasked: case VDfgType::Mux: case VDfgType::UnitArray: return true; diff --git a/src/V3DfgDataType.h b/src/V3DfgDataType.h index 5af232b84..3011d254f 100644 --- a/src/V3DfgDataType.h +++ b/src/V3DfgDataType.h @@ -139,6 +139,7 @@ public: // Returns a Packed type of the given width static const DfgDataType& packed(uint32_t width) { + UASSERT(width > 0, "Width must be positive"); // Find or create the right sized packed type const DfgDataType*& entryr = s_packedTypes[width]; if (!entryr) entryr = new DfgDataType{width}; diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index ca349ccb3..b2af96461 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -234,6 +234,12 @@ class DfgToAstVisitor final : DfgVisitor { AstVar* const varp = sinkp->as()->vscp()->varp(); m_resultp = new AstCReset{vtxp->fileline(), varp, false}; } + void visit(DfgMatchMasked* vtxp) override { + FileLine* const flp = vtxp->fileline(); + AstNodeExpr* const lhsp = convertDfgVertexToAstNodeExpr(vtxp->lhsp()); + AstVarScope* const matchp = vtxp->matchp()->as()->vscp(); + m_resultp = new AstMatchMasked{flp, lhsp, matchp}; + } void visit(DfgRep* vtxp) override { FileLine* const flp = vtxp->fileline(); diff --git a/src/V3DfgDumpPatterns.cpp b/src/V3DfgDumpPatterns.cpp deleted file mode 100644 index 18611f180..000000000 --- a/src/V3DfgDumpPatterns.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementations of simple passes over DfgGraph -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#ifndef VERILATOR_V3DFGPATTERNSTATS_H_ -#define VERILATOR_V3DFGPATTERNSTATS_H_ - -#include "V3Dfg.h" -#include "V3DfgPasses.h" -#include "V3File.h" - -#include - -class V3DfgPatternStats final { - static constexpr uint32_t MIN_PATTERN_DEPTH = 1; - static constexpr uint32_t MAX_PATTERN_DEPTH = 4; - - // Maps from pattern to the number of times it appears, for each pattern depth - std::vector> m_patterCounts{MAX_PATTERN_DEPTH + 1}; - - void dump(std::ostream& os) { - using Line = std::pair; - for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { - os << "DFG patterns with depth " << i << '\n'; - - // Pick up pattern accumulators with given depth - auto& patternCounts = m_patterCounts[i]; - - // Remove patterns also present at shallower depths - for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) { - for (const auto& pair : m_patterCounts[j]) patternCounts.erase(pair.first); - } - - // Sort patterns, first by descending frequency, then lexically - std::vector lines; - lines.reserve(patternCounts.size()); - for (const auto& pair : patternCounts) lines.emplace_back(pair); - std::sort(lines.begin(), lines.end(), [](const Line& a, const Line& b) { - if (a.second != b.second) return a.second > b.second; - return a.first < b.first; - }); - - // Print each pattern - for (const auto& line : lines) { - os << ' ' << std::setw(12) << std::right << line.second; - os << ' ' << std::left << line.first << '\n'; - } - - // Trailing new-line to separate sections - os << '\n'; - } - } - -public: - V3DfgPatternStats() = default; - ~V3DfgPatternStats() { - // File to dump to - const std::string filename - = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + "__dfg_patterns.txt"; - // Open, write, close - const std::unique_ptr ofp{V3File::new_ofstream(filename)}; - if (ofp->fail()) v3fatal("Can't write file: " << filename); - dump(*ofp); - } - - void accumulate(const DfgGraph& dfg) { - dfg.forEachVertex([&](const DfgVertex& vtx) { - for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { - m_patterCounts[i][vtx.patternString(i)] += 1; - } - }); - } -}; - -void V3DfgPasses::dumpPatterns(const std::vector>& graphs) { - V3DfgPatternStats patternStats; - for (auto& cp : graphs) patternStats.accumulate(*cp); -} - -#endif diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index b2fa0f9e8..07002049d 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -78,9 +78,15 @@ class DataflowOptimize final { if (AstVarScope* const vscp = VN_CAST(nodep, VarScope)) { const AstVar* const varp = vscp->varp(); // Force and trace have already been processed - const bool hasExtRd = varp->isPrimaryIO() || varp->isSigUserRdPublic(); - const bool hasExtWr - = (varp->isPrimaryIO() && varp->isNonOutput()) || varp->isSigUserRWPublic(); + const bool hasExtRd = // + varp->isPrimaryIO() // Top level port - readable + || varp->isSigUserRdPublic() // Readable by user + || varp->constPoolEntry() // Stored in AstConstPool hashmap, but read only + ; + const bool hasExtWr = // + (varp->isPrimaryIO() && varp->isNonOutput()) // Top level port - writable + || varp->isSigUserRWPublic() // Writable by user + ; if (hasExtRd) DfgVertexVar::setHasExtRdRefs(vscp); if (hasExtWr) DfgVertexVar::setHasExtWrRefs(vscp); return; @@ -139,6 +145,13 @@ class DataflowOptimize final { dfg.mergeGraphs(std::move(madeAcyclicComponents)); endOfStage("breakCycles", dfg, cyclicComps); + // Remove redundant selects + V3DfgPasses::removeSelects(dfg, m_ctx.m_removeSelectsContext); + for (std::unique_ptr& compp : cyclicComps) { + V3DfgPasses::removeSelects(*compp, m_ctx.m_removeSelectsContext); + } + endOfStage("removeSelects", dfg, cyclicComps); + // Split the acyclic DFG into [weakly] connected components std::vector> acyclicComps = dfg.splitIntoComponents("acyclic"); UASSERT(dfg.size() == 0, "DfgGraph should have become empty"); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 3e14397c6..7fc4b4100 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -78,7 +78,13 @@ void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) { && !vVtxp->hasExtWrRefs() // && !vVtxp->hasModWrRefs(); VL_DO_DANGLING(vVtxp->unlinkDelete(dfg), vVtxp); - if (srcp) VL_DO_DANGLING(srcp->unlinkDelete(dfg), srcp); + if (srcp) { + srcp->foreachSource([&](DfgVertex& src) { + src.as()->setDrivesUnusedVars(); + return false; + }); + VL_DO_DANGLING(srcp->unlinkDelete(dfg), srcp); + } if (delAst) { VL_DO_DANGLING(vscp->unlinkFrBack()->deleteTree(), vscp); ++ctx.m_varsDeleted; @@ -108,6 +114,78 @@ void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) { } } +void V3DfgPasses::removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) { + + std::vector selps; + for (DfgVertex& vtx : dfg.opVertices()) { + DfgSel* const selp = vtx.cast(); + if (!selp) continue; + selps.push_back(selp); + } + + for (DfgSel* const selp : selps) { + FileLine* const flp = selp->fileline(); + const DfgDataType& dtype = selp->dtype(); + + // Remove full width selects + if (selp->fromp()->dtype() == dtype) { + ++ctx.m_removedFullWidth; + selp->replaceWith(selp->fromp()); + continue; + } + + // Push selects through synthesis temporaries only + DfgVarPacked* const varp = selp->fromp()->cast(); + if (!varp || !varp->tmpForp()) continue; + DfgVertex* const srcp = varp->srcp(); + if (!srcp) continue; + // Don't inline CReset + if (srcp->is()) continue; + + const uint32_t lsb = selp->lsb(); + const uint32_t msb = lsb + selp->width() - 1; + + // If driven whole, select from the driver + if (!srcp->is()) { + ++ctx.m_replacedWithSelFromFull; + DfgSel* const newSelp = new DfgSel{dfg, flp, dtype}; + newSelp->lsb(lsb); + newSelp->fromp(srcp); + selp->replaceWith(newSelp); + continue; + } + + // Otherwise attemt to select from the partial driver + DfgSplicePacked* const splicep = srcp->as(); + DfgVertex* driverp = nullptr; + uint32_t driverLsb = 0; + splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLsb) { + const uint32_t dMsb = dLsb + src.width() - 1; + // If it does not cover the whole searched bit range, move on + if (lsb < dLsb || dMsb < msb) return false; + // Save the driver + driverp = &src; + driverLsb = dLsb; + return true; + }); + if (!driverp) continue; + + // If partial driver is the whole thing we are looking for, just replace with the driver + if (driverp->dtype() == dtype) { + ++ctx.m_replacedWithPart; + selp->replaceWith(driverp); + continue; + } + + // Otherwise create a new select from the partial driver + ++ctx.m_replacedWithSelFromPart; + DfgSel* const newSelp = new DfgSel{dfg, flp, dtype}; + newSelp->lsb(lsb - driverLsb); + newSelp->fromp(driverp); + selp->replaceWith(newSelp); + } +} + void V3DfgPasses::inlineVars(DfgGraph& dfg) { for (DfgVertexVar& vtx : dfg.varVertices()) { // Nothing to inline it into diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index a7d2b04fb..d0d8e62d8 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -41,6 +41,8 @@ void removeUnobservable(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; // Synthesize DfgLogic vertices into primitive operations. // Removes all DfgLogic (even those that were not synthesized). void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; +// Remove redundant selects +void removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) VL_MT_DISABLED; // Attempt to make the given cyclic graph into an acyclic, or "less cyclic" // equivalent. If the returned pointer is null, then no improvement was // possible on the input graph. Otherwise the returned graph is an improvement @@ -65,7 +67,8 @@ void regularize(DfgGraph&, V3DfgRegularizeContext&) VL_MT_DISABLED; // Convert DfgGraph back into Ast, and insert converted graph back into the Ast. void dfgToAst(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; // Dump the patterns in the given graphs -void dumpPatterns(const std::vector>&) VL_MT_DISABLED; +void dumpPatterns(const std::vector>&, + const std::string& suffix = "") VL_MT_DISABLED; //=========================================================================== // Intermediate/internal operations diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index f89285eb7..fa29339fe 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1188,6 +1188,102 @@ class V3DfgPeephole final : public DfgVisitor { return {fromp, lsb}; } + // Given a pair of vertices, returns a vertex representing the common LSBs of the two, + // and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found. + std::pair commonLSBs(DfgVertex* ap, DfgVertex* bp) { + if (ap == bp) return {ap, ap->width()}; + + // If both constants, check LSBs + if (DfgConst* const aConstp = ap->cast()) { + if (DfgConst* const bConstp = bp->cast()) { + const V3Number& aNum = aConstp->num(); + const V3Number& bNum = bConstp->num(); + // Max match is the shorter constant + const uint32_t maxMatch = std::min(aConstp->width(), bConstp->width()); + // Check all bits + uint32_t matchWidth = 0; + for (; matchWidth < maxMatch; ++matchWidth) { + if (aNum.bitIs0(matchWidth) != bNum.bitIs0(matchWidth)) break; + } + // Will always return the shorter constant in case it can be used directly + DfgConst* const shorterp = aConstp->width() < bConstp->width() ? aConstp : bConstp; + return {matchWidth ? shorterp : nullptr, matchWidth}; + } + } + + // If Concat, check against the RHS + if (DfgConcat* const catp = ap->cast()) return commonLSBs(catp->rhsp(), bp); + if (DfgConcat* const catp = bp->cast()) return commonLSBs(ap, catp->rhsp()); + + // If selecting the LSBs, check against the source of the Sel + if (DfgSel* const selp = ap->cast()) { + if (selp->lsb() == 0) { + DfgVertex* const fromp = selp->fromp(); + const std::pair common = commonLSBs(fromp, bp); + return {common.first, std::min(common.second, selp->width())}; + } + } + if (DfgSel* const selp = bp->cast()) { + if (selp->lsb() == 0) { + DfgVertex* const fromp = selp->fromp(); + const std::pair common = commonLSBs(ap, fromp); + return {common.first, std::min(common.second, selp->width())}; + } + } + + // Otherwise no common LSBs + return {nullptr, 0}; + } + + // Given a pair of vertices, returns a vertex representing the common MSBs of the two, + // and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found. + std::pair commonMSBs(DfgVertex* ap, DfgVertex* bp) { + if (ap == bp) return {ap, ap->width()}; + + // If both constants, check MSBs + if (DfgConst* const aConstp = ap->cast()) { + if (DfgConst* const bConstp = bp->cast()) { + const uint32_t aMsb = aConstp->width() - 1; + const uint32_t bMsb = bConstp->width() - 1; + const V3Number& aNum = aConstp->num(); + const V3Number& bNum = bConstp->num(); + // Max match is the shorter constant + const uint32_t maxMatch = std::min(aConstp->width(), bConstp->width()); + // Check all bits + uint32_t matchWidth = 0; + for (; matchWidth < maxMatch; ++matchWidth) { + if (aNum.bitIs0(aMsb - matchWidth) != bNum.bitIs0(bMsb - matchWidth)) break; + } + // Will always return the shorter constant in case it can be used directly + DfgConst* const shorterp = aMsb < bMsb ? aConstp : bConstp; + return {matchWidth ? shorterp : nullptr, matchWidth}; + } + } + + // If Concat, check against the LHS + if (DfgConcat* const catp = ap->cast()) return commonMSBs(catp->lhsp(), bp); + if (DfgConcat* const catp = bp->cast()) return commonMSBs(ap, catp->lhsp()); + + // If selecting the MSBs, check against the source of the Sel + if (DfgSel* const selp = ap->cast()) { + DfgVertex* const fromp = selp->fromp(); + if (selp->msb() == fromp->width() - 1) { + const std::pair common = commonMSBs(fromp, bp); + return {common.first, std::min(common.second, selp->width())}; + } + } + if (DfgSel* const selp = bp->cast()) { + DfgVertex* const fromp = selp->fromp(); + if (selp->msb() == fromp->width() - 1) { + const std::pair common = commonMSBs(ap, fromp); + return {common.first, std::min(common.second, selp->width())}; + } + } + + // Otherwise no common MSBs + return {nullptr, 0}; + } + // VISIT methods void visit(DfgVertex*) override {} @@ -1789,10 +1885,24 @@ class V3DfgPeephole final : public DfgVisitor { if (!idxp) return; DfgVarArray* const varp = vtxp->fromp()->cast(); if (!varp) return; - if (varp->vscp()->varp()->isForced()) return; - if (varp->vscp()->varp()->isSigUserRWPublic()) return; + AstVar* const astVarp = varp->vscp()->varp(); + if (astVarp->isForced()) return; + if (astVarp->isSigUserRWPublic()) return; DfgVertex* const srcp = varp->srcp(); - if (!srcp) return; + if (!srcp) { + if (vtxp->isPacked()) { + if (AstInitArray* const iap = VN_CAST(astVarp->valuep(), InitArray)) { + if (AstConst* const valp + = VN_CAST(iap->getIndexDefaultedValuep(idxp->toSizeT()), Const)) { + APPLYING(FOLD_ARRAYSEL_TABLE) { + replace(new DfgConst{m_dfg, valp->fileline(), valp->num()}); + return; + } + } + } + } + return; + } if (DfgSpliceArray* const splicep = srcp->cast()) { DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT()); @@ -2159,7 +2269,7 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgLogAnd* const vtxp) override { - if (binary(vtxp)) return; + if (binary(vtxp) || vtxp->rhsp()->unsafe()) return; DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); @@ -2177,11 +2287,11 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgLogIf* const vtxp) override { - if (binary(vtxp)) return; + if (binary(vtxp) || vtxp->rhsp()->unsafe()) return; } void visit(DfgLogOr* const vtxp) override { - if (binary(vtxp)) return; + if (binary(vtxp) || vtxp->rhsp()->unsafe()) return; DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); @@ -2641,6 +2751,16 @@ class V3DfgPeephole final : public DfgVisitor { } } + void visit(DfgMatchMasked* const vtxp) override { + if (DfgConst* const constp = vtxp->lhsp()->cast()) { + APPLYING(FOLD_MATCHMASKED) { + AstVar* const matchVarp = vtxp->matchp()->as()->vscp()->varp(); + replace(makeI32(vtxp->fileline(), AstMatchMasked::fold(constp->num(), matchVarp))); + return; + } + } + } + //========================================================================= // DfgVertexTernary //========================================================================= @@ -2770,13 +2890,13 @@ class V3DfgPeephole final : public DfgVisitor { } if (vtxp->dtype() == m_bitDType) { - if (isSame(condp, thenp)) { // a ? a : b becomes a | b + if (isSame(condp, thenp) && !elsep->unsafe()) { // a ? a : b becomes a | b APPLYING(REPLACE_COND_WITH_THEN_BRANCH_COND) { replace(make(vtxp, condp, elsep)); return; } } - if (isSame(condp, elsep)) { // a ? b : a becomes a & b + if (isSame(condp, elsep) && !thenp->unsafe()) { // a ? b : a becomes a & b APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_COND) { replace(make(vtxp, condp, thenp)); return; @@ -2785,28 +2905,28 @@ class V3DfgPeephole final : public DfgVisitor { } if (vtxp->width() <= VL_QUADSIZE) { - if (isZero(thenp)) { // a ? 0 : b becomes ~a & b + if (isZero(thenp) && !elsep->unsafe()) { // a ? 0 : b becomes ~a & b APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) { DfgVertex* const maskp = replicate(vtxp, make(condp, condp)); replace(make(vtxp, maskp, elsep)); return; } } - if (isOnes(thenp)) { // a ? 1 : b becomes a | b + if (isOnes(thenp) && !elsep->unsafe()) { // a ? 1 : b becomes a | b APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) { DfgVertex* const maskp = replicate(vtxp, condp); replace(make(vtxp, maskp, elsep)); return; } } - if (isZero(elsep)) { // a ? b : 0 becomes a & b + if (isZero(elsep) && !thenp->unsafe()) { // a ? b : 0 becomes a & b APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) { DfgVertex* const maskp = replicate(vtxp, condp); replace(make(vtxp, maskp, thenp)); return; } } - if (isOnes(elsep)) { // a ? b : 1 becomes ~a | b + if (isOnes(elsep) && !thenp->unsafe()) { // a ? b : 1 becomes ~a | b APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) { DfgVertex* const maskp = replicate(vtxp, make(condp, condp)); replace(make(vtxp, maskp, thenp)); @@ -2815,7 +2935,8 @@ class V3DfgPeephole final : public DfgVisitor { } if (DfgOr* const tOrp = thenp->cast()) { - if (isSame(tOrp->lhsp(), elsep)) { // a ? b | c : b becomes b | (a & c) + if (isSame(tOrp->lhsp(), elsep) + && !tOrp->rhsp()->unsafe()) { // a ? b | c : b becomes b | (a & c) APPLYING(REPLACE_COND_THEN_OR_LHS) { DfgVertex* const maskp = replicate(vtxp, condp); DfgAnd* const andp = make(vtxp, maskp, tOrp->rhsp()); @@ -2823,7 +2944,8 @@ class V3DfgPeephole final : public DfgVisitor { return; } } - if (isSame(tOrp->rhsp(), elsep)) { // a ? b | c : c becomes c | (a & b) + if (isSame(tOrp->rhsp(), elsep) + && !tOrp->lhsp()->unsafe()) { // a ? b | c : c becomes c | (a & b) APPLYING(REPLACE_COND_THEN_OR_RHS) { DfgVertex* const maskp = replicate(vtxp, condp); DfgAnd* const andp = make(vtxp, maskp, tOrp->lhsp()); @@ -2834,56 +2956,62 @@ class V3DfgPeephole final : public DfgVisitor { } } - if (DfgConcat* const tConcatp = thenp->cast()) { - if (DfgConcat* const eConcatp = elsep->cast()) { - DfgVertex* const tRhsp = tConcatp->rhsp(); - DfgVertex* const tLhsp = tConcatp->lhsp(); - DfgVertex* const eRhsp = eConcatp->rhsp(); - DfgVertex* const eLhsp = eConcatp->lhsp(); - - if (isSame(tRhsp, eRhsp)) { - APPLYING(REPLACE_COND_SAME_CAT_RHS) { - DfgCond* const newCondp - = make(flp, tLhsp->dtype(), condp, tLhsp, eLhsp); - replace(make(vtxp, newCondp, tRhsp)); - return; - } - } - - if (isSame(tLhsp, eLhsp)) { - APPLYING(REPLACE_COND_SAME_CAT_LHS) { - DfgCond* const newCondp - = make(flp, tRhsp->dtype(), condp, tRhsp, eRhsp); - replace(make(vtxp, tLhsp, newCondp)); + if (!thenp->is() && !elsep->is()) { + const std::pair cLSBs = commonLSBs(thenp, elsep); + if (cLSBs.first) { + APPLYING(REPLACE_COND_COMMON_LSBS) { + // Create new RHS + const uint32_t rWidth = cLSBs.second; + DfgVertex* rhsp = cLSBs.first; + if (rWidth != rhsp->width()) { + const DfgDataType& rDtype = DfgDataType::packed(rWidth); + rhsp = make(flp, rDtype, rhsp, 0U); + } + // If it's all the same, just replace + if (rhsp->dtype() == vtxp->dtype()) { + // Note this branch can only be hit if rules run in the right order, + // it might have missing code coverage after a refactor. + replace(rhsp); return; } + // Create new LHS + const uint32_t lWidth = vtxp->width() - rWidth; + const DfgDataType& lDtype = DfgDataType::packed(lWidth); + DfgVertex* const lThenp = make(flp, lDtype, thenp, rWidth); + DfgVertex* const lElsep = make(flp, lDtype, elsep, rWidth); + DfgVertex* const lhsp = make(flp, lDtype, condp, lThenp, lElsep); + // Replace with concat + replace(make(vtxp, lhsp, rhsp)); + return; } } - if (!tConcatp->hasMultipleSinks()) { - if (DfgConcat* const tRCatp = tConcatp->rhsp()->cast()) { - if (!tRCatp->hasMultipleSinks()) { - if (DfgSel* const tLSelp = tConcatp->lhsp()->cast()) { - if (DfgSel* const tRRSelp = tRCatp->rhsp()->cast()) { - if (tLSelp->lsb() == tRCatp->width() // - && tRRSelp->lsb() == 0 // - && isSame(tLSelp->fromp(), elsep) // - && isSame(tRRSelp->fromp(), elsep)) { - APPLYING(REPLACE_COND_INSERT) { - DfgVertex* const newTp = tRCatp->lhsp(); - DfgVertex* const newEp = make( - flp, newTp->dtype(), elsep, tRRSelp->width()); - DfgCond* const newCp = make(flp, newTp->dtype(), - condp, newTp, newEp); - replace(make( - vtxp, tLSelp, - make(tRCatp, newCp, tRRSelp))); - return; - } - } - } - } + const std::pair cMSBs = commonMSBs(thenp, elsep); + if (cMSBs.first) { + APPLYING(REPLACE_COND_COMMON_MSBS) { + // Create new LHS + const uint32_t lWidth = cMSBs.second; + DfgVertex* lhsp = cMSBs.first; + if (lWidth != lhsp->width()) { + const DfgDataType& lDtype = DfgDataType::packed(lWidth); + lhsp = make(flp, lDtype, lhsp, lhsp->width() - lWidth); } + // If it's all the same, just replace + if (lhsp->dtype() == vtxp->dtype()) { + // Note this branch can only be hit if rules run in the right order, + // it might have missing code coverage after a refactor. + replace(lhsp); + return; + } + // Create new RHS + const uint32_t rWidth = vtxp->width() - lWidth; + const DfgDataType& rDtype = DfgDataType::packed(rWidth); + DfgVertex* const rThenp = make(flp, rDtype, thenp, 0U); + DfgVertex* const rElsep = make(flp, rDtype, elsep, 0U); + DfgVertex* const rhsp = make(flp, rDtype, condp, rThenp, rElsep); + // Replace with concat + replace(make(vtxp, lhsp, rhsp)); + return; } } } diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index d8d295b2d..1c9de14e2 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -27,9 +27,11 @@ // Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests). // clang-format off #define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ARRAYSEL_TABLE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_LHS_OF_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MATCHMASKED) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MUX_FROM_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MUX_FROM_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_REP) \ @@ -112,17 +114,16 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SAME_REP_ON_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_LSBS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_MSBS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONES_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONE_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INSERT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_RHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_LHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_ELSE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_THEN) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_THEN_OR_LHS) \ diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp index 706c9c0c9..a23b19c4b 100644 --- a/src/V3DfgSynthesize.cpp +++ b/src/V3DfgSynthesize.cpp @@ -402,6 +402,29 @@ class AstToDfgConverter final : public VNVisitor { DfgVertex* const vtxp = make(nodep->fileline(), *dtypep); nodep->user2p(vtxp); } + void visit(AstMatchMasked* nodep) override { + UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); + UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + + const DfgDataType* const dtypep = DfgDataType::fromAst(nodep->dtypep()); + if (!dtypep) { + m_foundUnhandled = true; + ++m_ctx.m_conv.nonRepDType; + return; + } + + iterate(nodep->lhsp()); + if (m_foundUnhandled) return; + iterate(nodep->matchp()); + if (m_foundUnhandled) return; + + FileLine* const flp = nodep->fileline(); + DfgMatchMasked* const vtxp = make(flp, *dtypep); + vtxp->lhsp(nodep->lhsp()->user2u().to()); + vtxp->matchp(nodep->matchp()->user2u().to()); + nodep->user2p(vtxp); + } void visit(AstReplicate* nodep) override { UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); @@ -1982,6 +2005,11 @@ static void dfgSelectLogicForSynthesis(DfgGraph& dfg) { for (DfgVertex& vtx : dfg.opVertices()) { DfgLogic* const logicp = vtx.cast(); if (!logicp) continue; + // If drives an unused variable, synthesize it so the partial logic can be removed + if (logicp->drivesUnusedVars()) { + worklist.push_front(*logicp); + continue; + } // Blocks corresponding to continuous assignments if (logicp->nodep()->keyword() == VAlwaysKwd::CONT_ASSIGN) { worklist.push_front(*logicp); @@ -1993,10 +2021,8 @@ static void dfgSelectLogicForSynthesis(DfgGraph& dfg) { worklist.push_front(*logicp); continue; } - // Simple blocks driving exactly 1 variable, e.g if (rst) a = b else a = c; - if (!logicp->hasMultipleSinks() && cfg.nBlocks() <= 4 && cfg.nEdges() <= 4) { - worklist.push_front(*logicp); - } + // Blocks driving exactly 1 variable + if (!logicp->hasMultipleSinks()) worklist.push_front(*logicp); } // Now expand to cover all logic driving the same set of variables and mark diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index a3d3adf8e..dd51a58a9 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -299,6 +299,7 @@ public: void fromp(DfgVertex* vtxp) { srcp(vtxp); } uint32_t lsb() const { return m_lsb; } void lsb(uint32_t value) { m_lsb = value; } + uint32_t msb() const { return m_lsb + width() - 1; } }; class DfgUnitArray final : public DfgVertexUnary { @@ -328,6 +329,21 @@ public: ASTGEN_MEMBERS_DfgVertexBinary; }; +class DfgMatchMasked final : public DfgVertexBinary { + // Dfg equivalent of AstMatchMasked +public: + DfgMatchMasked(DfgGraph& dfg, FileLine* flp, const DfgDataType& dtype) + : DfgVertexBinary{dfg, dfgType(), flp, dtype} {} + ASTGEN_MEMBERS_DfgMatchMasked; + + DfgVertex* lhsp() const { return inputp(0); } + void lhsp(DfgVertex* vtxp) { inputp(0, vtxp); } + DfgVertex* matchp() const { return inputp(1); } + void matchp(DfgVertex* vtxp) { inputp(1, vtxp); } + + std::string srcName(size_t idx) const override { return idx ? "matchp" : "lhsp"; } +}; + class DfgMux final : public DfgVertexBinary { // AstSel is binary, but 'lsbp' is very often constant. As AstSel is fairly // common, we special case as a DfgSel for the constant 'lsbp', and as @@ -496,6 +512,7 @@ class DfgLogic final : public DfgVertexVariadic { AstScope* const m_scopep; // The AstScope m_nodep is under, iff scoped const std::unique_ptr m_cfgp; std::vector m_synth; // Vertices this logic was synthesized into + bool m_drivesUnusedVars = false; // Logic drives unused variables bool m_selectedForSynthesis = false; // Logic selected for synthesis bool m_nonSynthesizable = false; // Logic is not synthesizeable (by DfgSynthesis) bool m_reverted = false; // Logic was synthesized (in part if non-synthesizable) then reverted @@ -522,6 +539,8 @@ public: const CfgGraph& cfg() const { return *m_cfgp; } std::vector& synth() { return m_synth; } const std::vector& synth() const { return m_synth; } + bool drivesUnusedVars() const { return m_drivesUnusedVars; } + void setDrivesUnusedVars() { m_drivesUnusedVars = true; } bool selectedForSynthesis() const { return m_selectedForSynthesis; } void setSelectedForSynthesis() { m_selectedForSynthesis = true; } bool nonSynthesizable() const { return m_nonSynthesizable; } diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 8eb82b31f..11f2ac14a 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -18,8 +18,6 @@ #include "V3EmitCFunc.h" -#include "V3TSP.h" - #include #include diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 6eedcbc69..5e527870c 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1134,7 +1134,7 @@ public: } void visit(AstFFlush* nodep) override { if (!nodep->filep()) { - putns(nodep, "Verilated::runFlushCallbacks();\n"); + putns(nodep, "VL_FFLUSH_MT();"); } else { putns(nodep, "VL_FFLUSH_I("); iterateAndNextConstNull(nodep->filep()); @@ -1590,6 +1590,9 @@ public: puts(")"); } } + void visit(AstMatchMasked* nodep) override { + emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->matchp(), nullptr); + } void visit(AstMemberSel* nodep) override { iterateAndNextConstNull(nodep->fromp()); putnbs(nodep, "->"); diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 1746139b8..8b2f2add0 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -419,20 +419,14 @@ class EmitCHeader final : public EmitCConstInit { puts("return !(*this == rhs);\n}\n"); putns(sdtypep, "\nbool operator<(const " + EmitCUtil::prefixNameProtect(sdtypep) + "& rhs) const {\n"); - puts("return "); - puts("std::tie("); for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { - if (itemp != sdtypep->membersp()) puts(", "); - putns(itemp, itemp->nameProtect()); + putns(itemp, "if (" + itemp->nameProtect() + " < rhs." + itemp->nameProtect() + + ") return true;\n"); + putns(itemp, "if (rhs." + itemp->nameProtect() + " < " + itemp->nameProtect() + + ") return false;\n"); } - puts(")\n < std::tie("); - for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; - itemp = VN_AS(itemp->nextp(), MemberDType)) { - if (itemp != sdtypep->membersp()) puts(", "); - putns(itemp, "rhs." + itemp->nameProtect()); - } - puts(");\n"); + puts("return false;\n"); puts("}\n"); puts("};\n"); puts("template <>\n"); @@ -689,6 +683,8 @@ class EmitCHeader final : public EmitCConstInit { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.opt.coverage() || v3Global.useCovergroup()) + puts("#include \"verilated_covergroup.h\"\n"); if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n"); if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n"); if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n"); diff --git a/src/V3EmitCMain.cpp b/src/V3EmitCMain.cpp index 000567eaf..0d3fd0b6d 100644 --- a/src/V3EmitCMain.cpp +++ b/src/V3EmitCMain.cpp @@ -104,6 +104,8 @@ private: puts("\n"); if (v3Global.opt.vpi()) { + // VPI shared libraries requested via +verilator+vpi+ are loaded by + // contextp->commandArgs() above, before the statically-linked startup routines. puts("// Hook VPI startup routines and invoke callback\n"); puts("if (vlog_startup_routines) {\n"); puts(/**/ "for (auto routinep = &vlog_startup_routines[0]; *routinep; routinep++)" diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 211eea99f..034b1e807 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -70,6 +70,8 @@ class EmitCModel final : public EmitCFunc { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.opt.coverage() || v3Global.useCovergroup()) + puts("#include \"verilated_covergroup.h\"\n"); if (v3Global.dpi()) puts("#include \"svdpi.h\"\n"); // Declare foreign instances up front to make C++ happy diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index 79add9f3b..bcf79c164 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -176,26 +176,26 @@ class EmitCSyms final : EmitCBaseVisitorConst { return pos != std::string::npos ? scpname.substr(pos + 1) : scpname; } - static std::tuple getDimensions(const AstVar* const varp) { + static std::tuple getDimensions(const AstNodeDType* const rootDtypep) { int pdim = 0; int udim = 0; std::string bounds; - if (const AstBasicDType* const basicp = varp->basicp()) { - // Range is always first, it's not in "C" order - for (AstNodeDType* dtypep = varp->dtypep(); dtypep;) { - // Skip AstRefDType/AstTypedef, or return same node - dtypep = dtypep->skipRefp(); - if (const AstNodeArrayDType* const adtypep = VN_CAST(dtypep, NodeArrayDType)) { - bounds += " ,"; - bounds += std::to_string(adtypep->left()); - bounds += ","; - bounds += std::to_string(adtypep->right()); - if (VN_IS(dtypep, PackArrayDType)) - pdim++; - else - udim++; - dtypep = adtypep->subDTypep(); - } else { + // Range is always first, it's not in "C" order + for (const AstNodeDType* dtypep = rootDtypep; dtypep;) { + // Skip AstRefDType/AstTypedef, or return same node + dtypep = dtypep->skipRefp(); + if (const AstNodeArrayDType* const adtypep = VN_CAST(dtypep, NodeArrayDType)) { + bounds += " ,"; + bounds += std::to_string(adtypep->left()); + bounds += ","; + bounds += std::to_string(adtypep->right()); + if (VN_IS(dtypep, PackArrayDType)) + pdim++; + else + udim++; + dtypep = adtypep->subDTypep(); + } else { + if (const AstBasicDType* const basicp = dtypep->basicp()) { if (basicp->isRanged()) { bounds += " ,"; bounds += std::to_string(basicp->left()); @@ -203,13 +203,33 @@ class EmitCSyms final : EmitCBaseVisitorConst { bounds += std::to_string(basicp->right()); pdim++; } - break; // AstBasicDType - nothing below, 1 } + break; // Non-array leaf } } return {pdim, udim, bounds}; } + static std::tuple getDimensions(const AstVar* const varp) { + return getDimensions(varp->dtypep()); + } + + static int getUnpackedElements(const AstNodeDType* rootDtypep) { + int elements = 1; + for (const AstNodeDType* dtypep = rootDtypep; dtypep;) { + dtypep = dtypep->skipRefp(); + const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType); + if (!adtypep) break; + elements *= adtypep->elementsConst(); + dtypep = adtypep->subDTypep(); + } + return elements; + } + + static bool needsEmittedEntSize(const std::string& vlEnumType) { + return vlEnumType == "VLVT_STRUCT" || vlEnumType == "VLVT_UNION"; + } + static std::pair isForceControlSignal(const AstVar* const signalVarp) { // __VforceRd should not show up here because it is never public, but just in case it does, // it should be skipped because forceableVarInsert creates its VerilatedVar. @@ -225,15 +245,47 @@ class EmitCSyms final : EmitCBaseVisitorConst { return std::pair{false, ""}; } + static void appendVarProperties(std::string& stmt, const std::string& vlEnumType, + const std::string& vlEnumDir, const int udim, const int pdim, + const std::string& bounds, const std::string& entSize = "") { + stmt += vlEnumType; // VLVT_UINT32 etc + stmt += ", "; + stmt += vlEnumDir; // VLVD_IN etc + stmt += ", "; + stmt += std::to_string(udim); + if (!entSize.empty()) { + stmt += ", "; + stmt += entSize; + } else { + stmt += ", "; + stmt += std::to_string(pdim); + } + stmt += bounds; + stmt += ")"; + } + static std::string memberVlEnumDir(const AstVar* const varp, + const AstNodeDType* const dtypep) { + std::string out = "((" + varp->vlEnumDir() + ") & ~(VLVF_SIGNED|VLVF_BITVAR))"; + const AstNodeDType* const skipDTypep = dtypep->skipRefp(); + if (skipDTypep->isSigned()) out += "|VLVF_SIGNED"; + if (const AstBasicDType* const basicp = skipDTypep->basicp()) { + if (basicp->keyword() == VBasicDTypeKwd::BIT) out += "|VLVF_BITVAR"; + } + return out; + } + static std::string insertVarStatement(const ScopeVarData& svd, const AstScope* const scopep, const AstVar* const varp, const int udim, const int pdim, const std::string& bounds) { - std::string stmt; - stmt += protect("__Vscopep_" + svd.m_scopeName) + "->varInsert(\""; - stmt += V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)) + '"'; - const std::string varName = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + protect(varp->name()); + const std::string vlEnumType = varp->vlEnumType(); + const bool needsEntSize = needsEmittedEntSize(vlEnumType); + + std::string stmt; + stmt += protect("__Vscopep_" + svd.m_scopeName); + stmt += needsEntSize ? "->varInsertSized(\"" : "->varInsert(\""; + stmt += V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)) + '"'; if (!varp->isParam()) { stmt += ", &("; @@ -250,18 +302,87 @@ class EmitCSyms final : EmitCBaseVisitorConst { stmt += "))), true, "; } - stmt += varp->vlEnumType(); // VLVT_UINT32 etc - stmt += ", "; - stmt += varp->vlEnumDir(); // VLVD_IN etc - stmt += ", "; - stmt += std::to_string(udim); - stmt += ", "; - stmt += std::to_string(pdim); - stmt += bounds; - stmt += ")"; + const std::string entSize = needsEntSize + ? "sizeof(" + varName + ") / " + + std::to_string(getUnpackedElements(varp->dtypep())) + : ""; + appendVarProperties(stmt, vlEnumType, varp->vlEnumDir(), udim, pdim, bounds, entSize); return stmt; } + static std::string insertDTypeVarStatement(const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyName, + const std::string& cName, + const AstNodeDType* const dtypep, const int udim, + const int pdim, const std::string& bounds) { + const std::string vlEnumType = dtypep->vlEnumType(); + const bool needsEntSize = needsEmittedEntSize(vlEnumType); + std::string stmt; + stmt += protect("__Vscopep_" + svd.m_scopeName); + stmt += needsEntSize ? "->varInsertSized(\"" : "->varInsert(\""; + stmt += V3OutFormatter::quoteNameControls(prettyName) + '"'; + stmt += ", &("; + stmt += VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()); + stmt += "."; + stmt += cName; + stmt += "), false, "; + const std::string varName + = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + cName; + const std::string entSize + = needsEntSize + ? "sizeof(" + varName + ") / " + std::to_string(getUnpackedElements(dtypep)) + : ""; + appendVarProperties(stmt, vlEnumType, memberVlEnumDir(svd.m_varp, dtypep), udim, pdim, + bounds, entSize); + return stmt; + } + + static void addUOrStructMemberVars(std::vector& stmts, const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyPrefix, const std::string& cPrefix, + const AstNodeUOrStructDType* const sdtypep) { + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + const AstNodeDType* const itemDTypep = itemp->dtypep(); + const std::string prettyName + = prettyPrefix + "." + AstNode::vpiName(itemp->shortName()); + const std::string cName = cPrefix + "." + itemp->nameProtect(); + const std::tuple dimensions = getDimensions(itemDTypep); + const int pdim = std::get<0>(dimensions); + const int udim = std::get<1>(dimensions); + const std::string bounds = std::get<2>(dimensions); + stmts.emplace_back(insertDTypeVarStatement(svd, scopep, prettyName, cName, itemDTypep, + udim, pdim, bounds) + + ";"); + if (const AstNodeUOrStructDType* const subp + = VN_CAST(itemDTypep->skipRefp(), NodeUOrStructDType)) { + if (!subp->packed()) + addUOrStructMemberVars(stmts, svd, scopep, prettyName, cName, subp); + } else if (VN_IS(itemDTypep->skipRefp(), UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, prettyName, cName, + itemDTypep); + } + } + } + + static void addUnpackedArrayUOrStructMemberVars(std::vector& stmts, + const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyPrefix, + const std::string& cPrefix, + const AstNodeDType* const dtypep) { + const AstNodeDType* const skipDTypep = dtypep->skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(skipDTypep, UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, prettyPrefix, cPrefix + "[0]", + adtypep->subDTypep()); + } else if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(skipDTypep, NodeUOrStructDType)) { + if (!sdtypep->packed()) + addUOrStructMemberVars(stmts, svd, scopep, prettyPrefix, cPrefix, sdtypep); + } + } + std::string insertForceableVarStatement(const ScopeVarData& svd, const AstScope* const scopep, const AstVar* const varp, const int udim, const int pdim, const std::string& bounds) { @@ -1061,6 +1182,16 @@ std::vector EmitCSyms::getSymCtorStmts() { const std::string stmt = insertVarStatement(svd, scopep, varp, udim, pdim, bounds) + ";"; add(stmt); + if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(varp->dtypeSkipRefp(), NodeUOrStructDType)) { + if (!sdtypep->packed()) { + addUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty, + protect(varp->name()), sdtypep); + } + } else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty, + protect(varp->name()), varp->dtypep()); + } } } } diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp index f76945279..a63d328f3 100644 --- a/src/V3EmitMk.cpp +++ b/src/V3EmitMk.cpp @@ -567,6 +567,12 @@ public: of.puts("VM_TRACE_VCD = "); of.puts(v3Global.opt.traceEnabledVcd() ? "1" : "0"); of.puts("\n"); + of.puts("# VPI enabled? 0/1 (from --vpi)\n"); + of.puts("VM_VPI = "); + of.puts(v3Global.opt.vpi() ? "1" : "0"); + of.puts("\n"); + // Link flags for runtime VPI library loading are emitted by emitOverallMake() after + // verilated.mk is included (so $(CFG_LDFLAGS_DYNAMIC)/$(CFG_LDLIBS_DYNAMIC) are defined). of.puts("\n### Object file lists...\n"); for (int support = 0; support < 3; ++support) { @@ -729,6 +735,17 @@ public: of.puts("\n### Executable rules... (from --exe)\n"); of.puts("VPATH += $(VM_USER_DIR)\n"); of.puts("\n"); + + if (v3Global.opt.vpi()) { + // Runtime VPI library loading (+verilator+vpi+) needs the executable to + // export its VPI symbols so the dlopen'd library can resolve them, plus the + // dl library for dlopen/dlsym. The exact flags are probed at configure time + // (CFG_LDFLAGS_DYNAMIC / CFG_LDLIBS_DYNAMIC in verilated.mk). + of.puts("# Runtime VPI library loading (+verilator+vpi+) link requirements\n"); + of.puts("LDFLAGS += $(CFG_LDFLAGS_DYNAMIC)\n"); + of.puts("LDLIBS += $(CFG_LDLIBS_DYNAMIC)\n"); + of.puts("\n"); + } } const string compilerIncludePch diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 4201a0ad5..405204084 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -1050,8 +1050,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { if (nodep->packed()) puts("packed "); { puts("{\n"); - VL_RESTORER(m_packedps); - m_packedps.clear(); + VL_RESTORER_CLEAR(m_packedps); for (AstMemberDType* itemp = nodep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { iterateConst(itemp); @@ -1142,6 +1141,13 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { puts(") "); iterateConst(nodep->propp()); } + void visit(AstSClocked* nodep) override { + puts("@("); + iterateConst(nodep->sensesp()); + puts(") "); + iterateConst(nodep->exprp()); + puts("\n"); + } void visit(AstPropAlways* nodep) override { puts(nodep->isStrong() ? "s_always" : "always"); if (!VN_IS(nodep->loBoundp(), Unbounded) || !VN_IS(nodep->hiBoundp(), Unbounded)) { diff --git a/src/V3Error.h b/src/V3Error.h index 6d4914329..01e7d6447 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -107,6 +107,7 @@ public: ENUMITEMWIDTH, // Error: enum item width mismatch ENUMVALUE, // Error: enum type needs explicit cast EOFNEWLINE, // End-of-file missing newline + FINALDLY, // Final delayed statement FSMMULTI, // Multiple FSM candidates in one always block FUNCTIMECTL, // Functions cannot have timing/delay/wait FUTURE, // Feature is under development and not yet supported @@ -227,8 +228,8 @@ public: "BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED", - "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FSMMULTI", - "FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", + "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FINALDLY", + "FSMMULTI", "FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IEEEMAYDEPRECATE", "IFDEPTH", "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", @@ -269,11 +270,11 @@ public: bool pretendError() const VL_MT_SAFE { return (m_e == ASSIGNIN || m_e == BADSTDPRAGMA || m_e == BADVLTPRAGMA || m_e == BLKANDNBLK || m_e == BLKLOOPINIT || m_e == CONTASSREG || m_e == ENCAPSULATED - || m_e == ENDLABEL || m_e == ENUMITEMWIDTH || m_e == ENUMVALUE || m_e == HIERPARAM - || m_e == FUNCTIMECTL || m_e == IMPURE || m_e == MODMISSING || m_e == NOTREDOP - || m_e == PARAMNODEFAULT || m_e == PINNOTFOUND || m_e == PKGNODECL - || m_e == PROCASSWIRE || m_e == PROTOTYPEMIS || m_e == SUPERNFIRST - || m_e == ZEROREPL); + || m_e == ENDLABEL || m_e == ENUMITEMWIDTH || m_e == ENUMVALUE || m_e == FINALDLY + || m_e == HIERPARAM || m_e == FUNCTIMECTL || m_e == IMPURE || m_e == MODMISSING + || m_e == NOTREDOP || m_e == PARAMNODEFAULT || m_e == PINNOTFOUND + || m_e == PKGNODECL || m_e == PROCASSWIRE || m_e == PROTOTYPEMIS + || m_e == SUPERNFIRST || m_e == ZEROREPL); } // Warnings to mention manual bool mentionManual() const VL_MT_SAFE { diff --git a/src/V3Expand.cpp b/src/V3Expand.cpp index 267360e15..f58474273 100644 --- a/src/V3Expand.cpp +++ b/src/V3Expand.cpp @@ -206,31 +206,19 @@ class ExpandVisitor final : public VNVisitor { static AstNodeExpr* newWordGrabShift(FileLine* fl, int word, AstNodeExpr* lhsp, int shift) { // Extract the expression to grab the value for the specified word, if it's the shift // of shift bits from lhsp - AstNodeExpr* newp; // Negative word numbers requested for lhs when it's "before" what we want. // We get a 0 then. const int othword = word - shift / VL_EDATASIZE; - AstNodeExpr* const llowp = newAstWordSelClone(lhsp, othword); - if (const int loffset = VL_BITBIT_E(shift)) { - AstNodeExpr* const lhip = newAstWordSelClone(lhsp, othword - 1); - const int nbitsonright = VL_EDATASIZE - loffset; // bits that end up in lword - newp = new AstOr{ - fl, - new AstAnd{fl, new AstConst{fl, AstConst::SizedEData{}, VL_MASK_E(loffset)}, - new AstShiftR{fl, lhip, - new AstConst{fl, static_cast(nbitsonright)}, - VL_EDATASIZE}}, - new AstAnd{fl, - new AstConst{fl, AstConst::SizedEData{}, - static_cast(~VL_MASK_E(loffset))}, - new AstShiftL{fl, llowp, - new AstConst{fl, static_cast(loffset)}, - VL_EDATASIZE}}}; - newp = V3Const::constifyEditCpp(newp); - } else { - newp = llowp; - } - return newp; + AstNodeExpr* const lop = newAstWordSelClone(lhsp, othword); + const uint32_t loShift = VL_BITBIT_E(shift); + if (!loShift) return lop; + AstNodeExpr* const hip = newAstWordSelClone(lhsp, othword - 1); + const uint32_t hiShift = VL_EDATASIZE - loShift; // Complement offset + AstNodeExpr* const newp + = new AstOr{fl, // + new AstShiftR{fl, hip, new AstConst{fl, hiShift}, VL_EDATASIZE}, + new AstShiftL{fl, lop, new AstConst{fl, loShift}, VL_EDATASIZE}}; + return V3Const::constifyEditCpp(newp); } // Return expression indexing the word that contains 'lsbp' + the given word offset diff --git a/src/V3Force.cpp b/src/V3Force.cpp index 75f3cf256..3a7b51ce9 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -870,30 +870,8 @@ class ForceDiscoveryVisitor final : public VNVisitorConst { ForceState::ForceRangeInfo rangeInfo = m_state.getForceRangeInfo(nodep->lhsp(), forcedVarp, true); - // Start from a cloned RHS expression; adjust below for partial bit selects. - const AstSel* const selLhsp = VN_CAST(nodep->lhsp(), Sel); - AstNodeExpr* rhsExprp = nodep->rhsp()->cloneTreePure(false); - - // For bitwise selects inside arrays, merge updated bits with preserved base bits. - if (rangeInfo.m_hasArraySel && rangeInfo.m_arrayInfo.m_hasBitSel && selLhsp - && ForceState::isBitwiseDType(selLhsp->fromp())) { - AstNodeExpr* const baseExprp = selLhsp->fromp()->cloneTreePure(false); - baseExprp->foreach( - [](AstVarRef* const refp) { ForceState::markNonReplaceable(refp); }); - - // Pad the selected value back to full base width before masking/or-ing. - rhsExprp = ForceState::zeroPadToBaseWidth(rhsExprp, selLhsp->fromp()->width(), - rangeInfo.m_padLsb, rangeInfo.m_padMsb); - - // Keep untouched base bits and insert the newly forced bit range. - // rhsExpr = (baseExpr & ~mask(range)) | (zeroPad(force_rhs) & mask(range)); - AstConst* const maskConstp = ForceState::makeRangeMaskConst( - nodep->lhsp(), selLhsp->fromp()->width(), rangeInfo.m_padLsb, rangeInfo.m_padMsb); - AstNodeExpr* const maskedOldp - = new AstAnd{nodep->lhsp()->fileline(), baseExprp, - new AstNot{nodep->lhsp()->fileline(), maskConstp}}; - rhsExprp = new AstOr{nodep->lhsp()->fileline(), maskedOldp, rhsExprp}; - } + // Keep narrow rhs, VlForceVec blends unpacked-array bit-select forces at read time + AstNodeExpr* const rhsExprp = nodep->rhsp()->cloneTreePure(false); m_state.addForceAssignment(forcedVarp, lhsVarRefp->varScopep(), rhsExprp, nodep, rangeInfo.m_rangeLsb, rangeInfo.m_rangeMsb, rangeInfo.m_padLsb, @@ -1114,6 +1092,11 @@ class ForceConvertVisitor final : public VNVisitor { // Verilog pseudocode: // forceVec.addForce(range_lsb, range_msb, &forceRHS[id], rhs_lsb); + const AstSel* const selLhsp = VN_CAST(lhsp, Sel); + const bool arrayBitSel + = info.m_hasArraySel && selLhsp && ForceState::getArraySelInfo(lhsp).m_hasBitSel + && ForceState::isBitwiseDType(selLhsp->fromp()) + && (info.m_padMsb - info.m_padLsb + 1) < selLhsp->fromp()->width(); AstNodeExpr* const rhsDatap = ForceState::buildRhsDataExpr(flp, info); AstCExpr* const rhsAddrp = new AstCExpr{flp}; rhsAddrp->add("&("); @@ -1124,7 +1107,13 @@ class ForceConvertVisitor final : public VNVisitor { ForceState::makeConst32(flp, info.m_rangeLsb)}; addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_rangeMsb)); addForceCallp->addPinsp(rhsAddrp); - addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_rangeLsb)); + addForceCallp->addPinsp( + ForceState::makeConst32(flp, arrayBitSel ? info.m_padLsb : info.m_rangeLsb)); + if (arrayBitSel) { + addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_padLsb)); + addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_padMsb)); + addForceCallp->addPinsp(ForceState::makeConst32(flp, selLhsp->fromp()->width())); + } addForceCallp->dtypeSetVoid(); AstNodeStmt* const stmtp = addForceCallp->makeStmt(); @@ -1164,12 +1153,21 @@ class ForceConvertVisitor final : public VNVisitor { const ForceState::ForceRangeInfo rangeInfo = m_state.getForceRangeInfo(lhsp, releasedVarp, false); + const AstSel* const selLhsp = VN_CAST(lhsp, Sel); + const bool arrayBitSel + = rangeInfo.m_hasArraySel && selLhsp && rangeInfo.m_arrayInfo.m_hasBitSel + && ForceState::isBitwiseDType(selLhsp->fromp()) + && (rangeInfo.m_padMsb - rangeInfo.m_padLsb + 1) < selLhsp->fromp()->width(); AstCMethodHard* const releaseCallp = new AstCMethodHard{ flp, new AstVarRef{flp, varInfo->m_forceVecVscp, VAccess::WRITE}, VCMethod::FORCE_RELEASE, ForceState::makeConst32(flp, rangeInfo.m_rangeLsb)}; releaseCallp->addPinsp(ForceState::makeConst32(flp, rangeInfo.m_rangeMsb)); + if (arrayBitSel) { + releaseCallp->addPinsp(ForceState::makeConst32(flp, rangeInfo.m_padLsb)); + releaseCallp->addPinsp(ForceState::makeConst32(flp, rangeInfo.m_padMsb)); + } releaseCallp->dtypeSetVoid(); - // forceVec.release(range_lsb, range_msb); + // forceVec.release(range_lsb, range_msb [, bit_lsb, bit_msb]); AstNodeStmt* const releasep = releaseCallp->makeStmt(); AstAssign* clearEnp = nullptr; diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp index 0071d8f75..ee02c0781 100644 --- a/src/V3Fork.cpp +++ b/src/V3Fork.cpp @@ -561,10 +561,9 @@ class ForkVisitor final : public VNVisitor { // replace body with a call to that task. Returns true iff wrapped. bool taskify(AstBegin* beginp) { // Visit statement to gather variables (And recursively process) - VL_RESTORER(m_forkLocalsp); + VL_RESTORER_CLEAR(m_forkLocalsp); VL_RESTORER(m_capturedVarsp); VL_RESTORER(m_capturedArgsp); - m_forkLocalsp.clear(); m_capturedVarsp = nullptr; m_capturedArgsp = nullptr; iterate(beginp); diff --git a/src/V3FsmDetect.cpp b/src/V3FsmDetect.cpp index 4a2ccf8af..5ffee9e4c 100644 --- a/src/V3FsmDetect.cpp +++ b/src/V3FsmDetect.cpp @@ -30,6 +30,7 @@ #include "V3Ast.h" #include "V3Control.h" #include "V3Graph.h" +#include "V3UniqueNames.h" #include #include @@ -987,14 +988,23 @@ class FsmDetectVisitor final : public VNVisitor { return assp; } + static AstVarRef* tryExtractVarRef(AstNodeExpr* const exprp) { + AstVarRef* const varp = VN_CAST(AstArraySel::baseFromp(exprp, true), VarRef); + if (!varp) { + exprp->v3warn(COVERIGN, + "Ignoring unsupported: FSM coverage with " << exprp->prettyTypeName()); + return nullptr; + } + return varp; + } + static AstNodeAssign* nodeStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp, AstVarScope*& fromVscp) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); - UASSERT_OBJ(lhsp, assp, "register commit lhs should be normalized to a VarRef"); + AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp()); AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef); - if (!rhsp) return nullptr; + if (!rhsp || !lhsp) return nullptr; stateVscp = lhsp->varScopep(); fromVscp = rhsp->varScopep(); return assp; @@ -1006,11 +1016,9 @@ class FsmDetectVisitor final : public VNVisitor { FsmStateValue& resetValue) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); - UASSERT_OBJ(lhsp, assp, - "conditional register commit lhs should be normalized to a VarRef"); + AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp()); AstCond* const rhsp = VN_CAST(assp->rhsp(), Cond); - if (!rhsp) return nullptr; + if (!rhsp || !lhsp) return nullptr; if (AstVarRef* const elsep = VN_CAST(rhsp->elsep(), VarRef)) { if (constValueStatus(rhsp->thenp(), resetValue) != ConstValueStatus::OK) return nullptr; @@ -1033,7 +1041,7 @@ class FsmDetectVisitor final : public VNVisitor { FsmStateValue& value) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); + AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(assp->lhsp(), true), VarRef); UASSERT_OBJ(lhsp, assp, "direct constant state assignment lhs should be normalized to a VarRef"); if (constValueStatus(assp->rhsp(), value) != ConstValueStatus::OK) return nullptr; @@ -1220,7 +1228,8 @@ class FsmDetectVisitor final : public VNVisitor { AstVarRef* vrefp = VN_CAST(eqp->lhsp(), VarRef); AstNodeExpr* valuep = eqp->rhsp(); if (!vrefp) { - vrefp = VN_AS(eqp->rhsp(), VarRef); + vrefp = tryExtractVarRef(eqp->rhsp()); + if (!vrefp) { return false; } valuep = eqp->lhsp(); } @@ -2082,6 +2091,7 @@ public: class FsmLowerVisitor final { // STATE - across all visitors const FsmState& m_state; + V3UniqueNames m_fsmBuildNames; // METHODS // Rebuild a state-typed constant using the tracked state variable @@ -2133,8 +2143,8 @@ class FsmLowerVisitor final { AstNodeModule* const modp = scopep->modp(); AstNodeDType* const prevDTypep = scopep->findLogicDType( sampleVscp->width(), sampleVscp->width(), sampleVscp->dtypep()->numeric()); - AstVarScope* const prevVscp - = scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), prevDTypep); + const std::string tmpName = m_fsmBuildNames.get(stateVscp->varp()->shortName()); + AstVarScope* const prevVscp = scopep->createTemp(tmpName, prevDTypep); // The saved previous-state temp crosses the scheduler's pre/post split // in the same way as Verilator's built-in NBA shadow variables, so keep // both vars marked as post-life participants for stable MT ordering. @@ -2283,7 +2293,8 @@ public: // concrete coverage instrumentation while the saved scoped pointers are // still valid in the same pass. explicit FsmLowerVisitor(const FsmState& state) - : m_state{state} { + : m_state{state} + , m_fsmBuildNames{"__Vfsmcov_prev"} { for (const DetectedFsm& fsm : m_state.fsms()) { buildOne(*fsm.graphp); } } }; diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index 4875ac75c..e41b61daf 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -471,6 +471,11 @@ class GateOkVisitor final : public VNVisitorConst { // assign to get randomization etc clearSimple("CReset"); } + void visit(AstMatchMasked* nodep) override { + if (!m_isSimple) return; + // This node can be expensive + clearSimple("MatchMasked"); + } //-------------------- void visit(AstNode* nodep) override { if (!m_isSimple) return; // Fastpath @@ -543,89 +548,62 @@ class GateInline final { // Logic block with pending substitutions are stored in this map, together with their ordinal std::unordered_map m_hasPending; size_t m_statInlined = 0; // Statistic tracking - signals inlined - size_t m_statRefs = 0; // Statistic tracking - size_t m_statExcluded = 0; // Statistic tracking + size_t m_statNotInlined = 0; // Statistic tracking - signals not inlined due to cost + size_t m_statRefs = 0; // Statistic tracking - number of input variable references replaced // METHODS - static bool isCheapWide(const AstNodeExpr* exprp) { + static bool isCheap(const AstNodeExpr* exprp) { + // Constant is cheap + if (VN_IS(exprp, Const)) return true; + // Variable reference is cheap + if (VN_IS(exprp, NodeVarRef)) return true; + // AstSel is cheap if the fromp is cheap, and not a wide needing bit swizzling if (const AstSel* const selp = VN_CAST(exprp, Sel)) { + if (!isCheap(selp->fromp())) return false; + if (!selp->isWide()) return true; + if (!VN_IS(selp->lsbp(), Const)) return false; if (selp->lsbConst() % VL_EDATASIZE != 0) return false; - exprp = selp->fromp(); + return true; } - if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) exprp = aselp->fromp(); - return VN_IS(exprp, Const) || VN_IS(exprp, NodeVarRef); - } - static bool excludedWide(GateVarVertex* const vVtxp, const AstNodeExpr* const rhsp) { - // Handle wides with logic drivers that are too wide for V3Expand. - if (!vVtxp->varScp()->isWide() // - || vVtxp->varScp()->widthWords() <= v3Global.opt.expandLimit() // - || vVtxp->inEmpty() // - || isCheapWide(rhsp)) - return false; - - const GateLogicVertex* const lVtxp - = vVtxp->inEdges().frontp()->fromp()->as(); - - // Exclude from inlining variables READ multiple times. - // To decouple actives thus simplifying scheduling, exclude only those - // VarRefs that are referenced under the same active as they were assigned. - if (const AstActive* const primaryActivep = lVtxp->activep()) { - size_t reads = 0; - for (const V3GraphEdge& edge : vVtxp->outEdges()) { - const GateLogicVertex* const lvp = edge.top()->as(); - if (lvp->activep() != primaryActivep) continue; - - reads += edge.weight(); - if (reads > 1) return true; - } + // AstArraySel is cheap if the fromp is cheap + if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) { + return isCheap(aselp->fromp()); } + // Otherwise it is not cheap return false; } bool shouldInline(GateVarVertex* vVtxp, GateLogicVertex* lVtxp, size_t nReads, AstNodeExpr* substp, bool allowMultiIn) { - AstVarScope* const vscp = vVtxp->varScp(); - // Always inline constants if (VN_IS(substp, Const)) return true; - // Don't inline non-constant static initializers + // Don't inline non-constant static initializers - these are scheduled differently if (lVtxp->staticInit()) return false; // Inline simple variable references if (VN_IS(substp, VarRef)) return true; // Only inline arrays if a simple variable or constant - if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) return false; - // Inline constant array selects - if (VN_IS(substp, ArraySel) && nReads <= 1) return true; - - // Don't inline expensive wide operations - if (excludedWide(vVtxp, substp)) { - ++m_statExcluded; - UINFO(9, "Gate inline exclude '" << vVtxp->name() << "'"); - vVtxp->clearReducible("Excluded wide"); // Check once. - return false; - } - - if (nReads == 0) { - // Reads no variables, likely unfolded constant expression - return true; - } else if (nReads == 1) { - // Reads one variable - return true; - } else { - // Reads more two or more variables - if (!allowMultiIn) return false; - // Do it if not used, or used only once, ignoring slow code - int n = 0; - for (V3GraphEdge& edge : vVtxp->outEdges()) { - const GateLogicVertex* const dstVtxp = edge.top()->as(); - // Ignore slow code, or if the destination is not used - if (dstVtxp->slow()) continue; - if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue; - n += edge.weight(); - if (n > 1) return false; + if (VN_IS(vVtxp->varScp()->dtypep()->skipRefp(), UnpackArrayDType)) return false; + // Inline if reads no variables - unfolded constant expression, nullary builtin e.g.: $time + if (nReads == 0) return true; + // If it reads one variable, inline if not wide, or if cheap + if (nReads == 1 && (!substp->isWide() || isCheap(substp))) return true; + // Don't inline on first round if reads more than one variable + if (nReads > 1 && !allowMultiIn) return false; + // Reads multiple variables, or is expensive to compute. + // Inline if used only once, ignoring slow code, or dead code that can be deleted. + int n = 0; + for (V3GraphEdge& edge : vVtxp->outEdges()) { + const GateLogicVertex* const dstVtxp = edge.top()->as(); + // Ignore slow code, or if the destination is not used + if (dstVtxp->slow()) continue; + if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue; + n += edge.weight(); + if (n > 1) { + ++m_statNotInlined; + return false; } - return true; } + return true; } void recordSubstitution(AstVarScope* vscp, AstNodeExpr* substp, AstNode* logicp) { @@ -724,7 +702,7 @@ class GateInline final { if (!okVisitor.varAssigned(vVtxp->varScp())) continue; // Expression we are considering to substitute with - AstNodeExpr* const substp = okVisitor.substitutionp(); + AstNodeExpr* const substp = V3Const::constifyEdit(okVisitor.substitutionp()); // Number of variables read by the substitution const size_t nReads = okVisitor.readVscps().size(); @@ -832,9 +810,9 @@ class GateInline final { } ~GateInline() { - V3Stats::addStat("Optimizations, Gate sigs deleted", m_statInlined); - V3Stats::addStat("Optimizations, Gate inputs replaced", m_statRefs); - V3Stats::addStat("Optimizations, Gate excluded wide expressions", m_statExcluded); + V3Stats::addStat("Optimizations, Gate signals inlined", m_statInlined); + V3Stats::addStat("Optimizations, Gate signals not inlined due to cost", m_statNotInlined); + V3Stats::addStat("Optimizations, Gate reads replaced", m_statRefs); } public: diff --git a/src/V3Global.cpp b/src/V3Global.cpp index e0b6f42ea..3fd207be3 100644 --- a/src/V3Global.cpp +++ b/src/V3Global.cpp @@ -245,6 +245,8 @@ std::vector V3Global::verilatedCppFiles() { if (v3Global.opt.vpi()) result.emplace_back("verilated_vpi.cpp"); if (v3Global.opt.savable()) result.emplace_back("verilated_save.cpp"); if (v3Global.opt.coverage()) result.emplace_back("verilated_cov.cpp"); + if (v3Global.opt.coverage() || v3Global.useCovergroup()) + result.emplace_back("verilated_covergroup.cpp"); for (const string& base : v3Global.opt.traceSourceBases()) result.emplace_back(base + "_c.cpp"); if (v3Global.usesProbDist()) result.emplace_back("verilated_probdist.cpp"); diff --git a/src/V3Global.h b/src/V3Global.h index 4825a540a..08f9db3a7 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -44,27 +45,90 @@ class V3ThreadPool; //====================================================================== // Restorer -/// Save a given variable's value on the stack, restoring it at end-of-scope. -// Object must be named, or it will not persist until end-of-scope. -// Constructor needs () or GCC 4.8 false warning. -#define VL_RESTORER(var) const VRestorer> restorer_##var(var); -/// Get the copy of the variable previously saved by VL_RESTORER() +// For all flavours, 'var' must be a named scalar variable (simple identifier). + +// Copy given variable's value on the stack, copy saved value back at end-of-scope. +// Only usable for trivially copyable types; use VL_RESTORER_COPY/VL_RESTORER_CLEAR for +// non-trivially copyable types, which are more efficient. +#define VL_RESTORER(var) \ + const VRestorerTrivial> restorer_##var(var); + +// This is the equivalent of VL_RESTORER for non-trivially copyable types. Still more efficient +// than VL_RESTORER as uses move to restore. +#define VL_RESTORER_COPY(var) \ + const VRestorerCopy> restorer_##var(var); + +// Swap 'var' with a no-args constructed object of the same type, effectively clearing it, then +// swap back at end-of-scope. No copying involved. Use this e.g. for std containers where they +// would be .clear()'d right after the VL_RESTORER instance. +#define VL_RESTORER_CLEAR(var) \ + const VRestorerClear> restorer_##var(var); + +// Get const reference to the saved copy #define VL_RESTORER_PREV(var) restorer_##var.saved() -// Object used by VL_RESTORER. This object must be an auto variable, not -// allocated on the heap or otherwise. +// Implementation of VL_RESTORER template -class VRestorer final { +class VRestorerTrivial final { + static_assert(std::is_trivially_copyable::value, + "Use VL_RESTORER_{COPY,CLEAR} for non trivially copyable types"); T& m_ref; // Reference to object we're saving and restoring const T m_saved; // Value saved, for later restore public: - explicit VRestorer(T& permr) - : m_ref{permr} - , m_saved{permr} {} - ~VRestorer() { m_ref = m_saved; } + explicit VRestorerTrivial(T& val) + : m_ref{val} + , m_saved{val} {} + ~VRestorerTrivial() { m_ref = m_saved; } + VL_UNCOPYABLE(VRestorerTrivial); + // Must be stack allocated + void* operator new(size_t) = delete; + void operator delete(void*) = delete; + + const T& saved() const { return m_saved; } +}; + +// Implementation of VL_RESTORER_COPY +template +class VRestorerCopy final { + static_assert(!std::is_trivially_copyable::value, + "Use VL_RESTORER for trivially copyable types"); + T& m_ref; // Reference to object we're saving and restoring + T m_saved; // Value saved, for later restore + +public: + explicit VRestorerCopy(T& val) + : m_ref{val} + , m_saved{val} {} + ~VRestorerCopy() { m_ref = std::move(m_saved); } + VL_UNCOPYABLE(VRestorerCopy); + // Must be stack allocated + void* operator new(size_t) = delete; + void operator delete(void*) = delete; + + const T& saved() const { return m_saved; } +}; + +// Implementation of VL_RESTORER_CLEAR. +template +class VRestorerClear final { + static_assert(!std::is_trivially_copyable::value, + "Use VL_RESTORER for trivially copyable types"); + T& m_ref; // Reference to object we're saving and restoring + T m_saved{}; // Owns the swapped-out value; starts empty + +public: + explicit VRestorerClear(T& val) + : m_ref{val} { + std::swap(m_ref, m_saved); + } + ~VRestorerClear() { std::swap(m_ref, m_saved); } + VL_UNCOPYABLE(VRestorerClear); + // Must be stack allocated + void* operator new(size_t) = delete; + void operator delete(void*) = delete; + const T& saved() const { return m_saved; } - VL_UNCOPYABLE(VRestorer); }; //###################################################################### diff --git a/src/V3HierBlock.cpp b/src/V3HierBlock.cpp index 79bd3667c..8a77354ce 100644 --- a/src/V3HierBlock.cpp +++ b/src/V3HierBlock.cpp @@ -331,12 +331,9 @@ class HierBlockUsageCollectVisitor final : public VNVisitorConst { } // This is a hierarchical block, gather parts - VL_RESTORER(m_params); - VL_RESTORER(m_typeParams); - VL_RESTORER(m_childrenp); - m_params.clear(); - m_typeParams.clear(); - m_childrenp.clear(); + VL_RESTORER_CLEAR(m_params); + VL_RESTORER_CLEAR(m_typeParams); + VL_RESTORER_CLEAR(m_childrenp); iterateChildrenConst(nodep); // Create the graph vertex for this hier block V3HierBlock* const blockp = new V3HierBlock{m_graphp, nodep, m_params, m_typeParams}; diff --git a/src/V3Inline.cpp b/src/V3Inline.cpp index 87b9e0b26..7b0a4ecd3 100644 --- a/src/V3Inline.cpp +++ b/src/V3Inline.cpp @@ -29,6 +29,7 @@ #include "V3Inline.h" #include "V3AstUserAllocator.h" +#include "V3Graph.h" #include "V3Inst.h" #include "V3Stats.h" @@ -41,208 +42,334 @@ VL_DEFINE_DEBUG_FUNCTIONS; static const int INLINE_MODS_SMALLER = 100; // If a mod is < this # nodes, can always inline it //###################################################################### -// Inlining state. Kept as AstNodeModule::user1p via AstUser1Allocator +// Bipartite module instantiation graph containing module and cell vertices -namespace { +class InlineModModuleVertex; +class InlineModCellVertex; -struct ModuleState final { - bool m_inlined = false; // Whether to inline this module - unsigned m_cellRefs = 0; // Number of AstCells instantiating this module - std::vector m_childCells; // AstCells under this module (to speed up traversal) -}; - -using ModuleStateUser1Allocator = AstUser1Allocator; - -} // namespace - -//###################################################################### -// Visitor that determines which modules will be inlined - -class InlineMarkVisitor final : public VNVisitor { +class InlineModGraph final : public V3Graph { // NODE STATE - // Output - // AstNodeModule::user1() // OUTPUT: ModuleState instance (via m_moduleState) - // Internal state (can be cleared after this visit completes) - // AstNodeModule::user2() // CIL_*. Allowed to automatically inline module - // AstNodeModule::user4() // int. Statements in module - const VNUser2InUse m_inuser2; - const VNUser4InUse m_inuser4; + // AstNodeModule::user4p() -> InlineModModuleVertex*, the module vertex + // AstCell::user4p() -> InlineModCellVertex*, the cell vertex - ModuleStateUser1Allocator& m_moduleState; + VNUser4InUse m_user4InUse; - // For the user2 field: - enum : uint8_t { - CIL_NOTHARD = 0, // Inline not supported - CIL_NOTSOFT, // Don't inline unless user overrides - CIL_MAYBE, // Might inline - CIL_USER - }; // Pragma suggests inlining - - // STATE - AstNodeModule* m_modp = nullptr; // Current module - VDouble0 m_statUnsup; // Statistic tracking - std::vector m_allMods; // All modules, in top-down order. - - // Within the context of a given module, LocalInstanceMap maps - // from child modules to the count of each child's local instantiations. - using LocalInstanceMap = std::unordered_map; - - // We keep a LocalInstanceMap for each module in the design - std::unordered_map m_instances; +public: + InlineModGraph() + : V3Graph{} {} + ~InlineModGraph() override = default; // METHODS - void cantInline(const char* reason, bool hard) { - if (hard) { - if (m_modp->user2() != CIL_NOTHARD) { - UINFO(4, " No inline hard: " << reason << " " << m_modp); - m_modp->user2(CIL_NOTHARD); - ++m_statUnsup; - } - } else { - if (m_modp->user2() == CIL_MAYBE) { - UINFO(4, " No inline soft: " << reason << " " << m_modp); - m_modp->user2(CIL_NOTSOFT); + InlineModModuleVertex* getInlineModModuleVertexp(AstNodeModule* modp); + InlineModCellVertex* getInlineModCellVertexp(AstCell* cellp); + void addEdge(InlineModModuleVertex& from, InlineModCellVertex& to); + void addEdge(InlineModCellVertex& from, InlineModModuleVertex& to); + + // debug + std::string dotRankDir() const override { return "LR"; } +}; + +class InlineModEitherVertex VL_NOT_FINAL : public V3GraphVertex { + VL_RTTI_IMPL(InlineModEitherVertex, V3GraphVertex) +protected: + explicit InlineModEitherVertex(InlineModGraph& graph) + : V3GraphVertex{&graph} {} +}; + +class InlineModModuleVertex final : public InlineModEitherVertex { + VL_RTTI_IMPL(InlineModModuleVertex, InlineModEitherVertex) + AstNodeModule* const m_modp; // The module + const char* m_noInlineHardWyp = nullptr; // First reason the module can never be inlined + const char* m_noInlineSoftWyp = nullptr; // First reason not to inline unless forced + const char* m_shouldInlineWhyp = nullptr; // First reason why this module should be inlined + size_t m_size = 0; // The size (statement count) of the module + size_t mutable m_flattenedSize = 0; // The size of the module if flattened + bool mutable m_flattenedSizeValid = false; // Whether the flattened size is valid + size_t mutable m_instanceCount = 0; // The number of total instances of this module + bool mutable m_instanceCountValid = false; // Whether the instance count is valid + +public: + InlineModModuleVertex(InlineModGraph& graph, AstNodeModule* modp) + : InlineModEitherVertex{graph} + , m_modp{modp} {} + ~InlineModModuleVertex() override = default; + + // ACCESSORS + AstNodeModule* modp() const { return m_modp; } + size_t size() const { return m_size; } + void size(size_t value) { m_size = value; } + void sizeInc(size_t value = 1) { m_size += value; } + bool noInlineHard() const { return m_noInlineHardWyp; } + void setNoInlineHard(const char* whyp) { + if (!m_noInlineHardWyp) m_noInlineHardWyp = whyp; + } + bool noInlineSoft() const { return m_noInlineSoftWyp; } + void setNoInlineSoft(const char* whyp) { + if (!m_noInlineSoftWyp) m_noInlineSoftWyp = whyp; + } + bool shouldInline() const { return m_shouldInlineWhyp; } + void setShouldInline(const char* whyp) { + if (!m_shouldInlineWhyp) m_shouldInlineWhyp = whyp; + } + // Mark every instance below this module for inlining + void setFlatten(); + + // Total size of module, with all hierarchy below flattened + size_t flattenedSize() const { + if (!m_flattenedSizeValid) { + m_flattenedSizeValid = true; + m_flattenedSize = m_size; + for (const V3GraphEdge& e1 : outEdges()) { + for (const V3GraphEdge& e2 : e1.top()->outEdges()) { + InlineModModuleVertex* const mVtxp = e2.top()->as(); + m_flattenedSize += mVtxp->flattenedSize(); + } } } + return m_flattenedSize; } + // Total number of instances of this module in the whole hierarchy of the design + size_t instanceCount() const { + if (!m_instanceCountValid) { + m_instanceCountValid = true; + m_instanceCount = 0; + for (const V3GraphEdge& e1 : inEdges()) { + for (const V3GraphEdge& e2 : e1.fromp()->inEdges()) { + InlineModModuleVertex* const mVtxp = e2.fromp()->as(); + m_instanceCount += mVtxp->instanceCount(); + } + } + if (!m_instanceCount) { + UASSERT_OBJ(m_modp->isTop(), m_modp, "non-top level module should have instances"); + m_instanceCount = 1; + } + } + return m_instanceCount; + } + + // debug + FileLine* fileline() const override { return m_modp->fileline(); } + std::string dotShape() const override { return "box"; } + std::string dotColor() const override { + return m_noInlineHardWyp ? "red" + : m_shouldInlineWhyp ? "blue" + : m_noInlineSoftWyp ? "orange" + : "black"; + } + std::string name() const override VL_MT_STABLE { + std::string str = m_modp->typeName() + " "s + cvtToHex(m_modp); + str += "\n" + m_modp->name() + " @ " + fileline()->ascii(); + str += "\ninstanceCount: " + std::to_string(instanceCount()); + str += "\nsize: " + std::to_string(m_size); + str += "\nflattenedSize: " + std::to_string(flattenedSize()); + if (m_shouldInlineWhyp) str += "\nShouldInline: "s + m_shouldInlineWhyp; + if (m_noInlineHardWyp) str += "\nNoInlineHard: "s + m_noInlineHardWyp; + if (m_noInlineSoftWyp) str += "\nNoInlineSoft: "s + m_noInlineSoftWyp; + str += "\n"; + return str; + } +}; + +class InlineModCellVertex final : public InlineModEitherVertex { + VL_RTTI_IMPL(InlineModCellVertex, InlineModEitherVertex) + AstCell* const m_cellp; // The cell (instance) + const char* m_doInlineWyp = nullptr; // First reason this instance should be inlined + bool m_flatten = false; // Whether this cell and below already flattened (avoid O(n^2)) + +public: + InlineModCellVertex(InlineModGraph& graph, AstCell* cellp) + : InlineModEitherVertex{graph} + , m_cellp{cellp} {} + ~InlineModCellVertex() override = default; + + // ACCESSORS + AstCell* cellp() const { return m_cellp; } + bool doInline() const { return m_doInlineWyp; } + void setDoInline(const char* whyp) { + if (!m_doInlineWyp) m_doInlineWyp = whyp; + } + bool flatten() const { return m_flatten; } + void setFlatten() { m_flatten = true; } + + // The module vertx this cell is instantiating + InlineModModuleVertex& instanceOf() const { + UASSERT_OBJ(outSize1(), this, "Cell should have exactly one outgoing edge"); + return *outEdges().frontp()->top()->as(); + } + // The module vertex this cell is instantiated in + InlineModModuleVertex& instanceIn() const { + UASSERT_OBJ(inSize1(), this, "Cell should have exactly one incoming edge"); + return *inEdges().frontp()->fromp()->as(); + } + + // debug + FileLine* fileline() const override { return m_cellp->fileline(); } + std::string dotColor() const override { return m_doInlineWyp ? "green" : "black"; } + std::string dotShape() const override { return "ellipse"; } + std::string name() const override VL_MT_STABLE { + std::string str = m_cellp->typeName() + " "s + cvtToHex(m_cellp); + str += "\n" + m_cellp->name() + " @ " + fileline()->ascii(); + if (m_doInlineWyp) str += "\nDoInline: "s + m_doInlineWyp; + str += "\n"; + return str; + } +}; + +InlineModModuleVertex* InlineModGraph::getInlineModModuleVertexp(AstNodeModule* modp) { + if (!modp->user4p()) modp->user4p(new InlineModModuleVertex{*this, modp}); + return modp->user4u().to(); +} +InlineModCellVertex* InlineModGraph::getInlineModCellVertexp(AstCell* cellp) { + if (!cellp->user4p()) cellp->user4p(new InlineModCellVertex{*this, cellp}); + return cellp->user4u().to(); +} + +void InlineModGraph::addEdge(InlineModModuleVertex& parent, InlineModCellVertex& cell) { + UASSERT_OBJ(cell.inEmpty(), &cell, "Cell should have at most one incoming edge"); + new V3GraphEdge{this, &parent, &cell, 1, /* cutable: */ false}; +} +void InlineModGraph::addEdge(InlineModCellVertex& cell, InlineModModuleVertex& submodule) { + UASSERT_OBJ(cell.outEmpty(), &cell, "Cell should have at most one outgoing edge"); + new V3GraphEdge{this, &cell, &submodule, 1, /* cutable: */ false}; +} + +void InlineModModuleVertex::setFlatten() { + for (V3GraphEdge& edge : outEdges()) { + InlineModCellVertex& cVtx = *edge.top()->as(); + if (cVtx.flatten()) continue; + cVtx.setFlatten(); + InlineModModuleVertex& iVtx = cVtx.instanceOf(); + if (!iVtx.noInlineHard() && !iVtx.noInlineSoft()) cVtx.setDoInline("flatten parent"); + iVtx.setFlatten(); + } +} + +//###################################################################### +// Visitor that builds the bipartite module instantiation graph + +class InlineModGraphBuilder final : public VNVisitor { + // STATE + std::unique_ptr m_graphp{new InlineModGraph}; // The graph being built + InlineModModuleVertex* m_modVtxp = nullptr; // Vertex of module currently being iterated // VISITORS void visit(AstNodeModule* nodep) override { - UASSERT_OBJ(!m_modp, nodep, "Unsupported: Nested modules"); - VL_RESTORER(m_modp); - m_modp = nodep; - m_allMods.push_back(nodep); - m_modp->user2(CIL_MAYBE); - m_modp->user4(0); // statement count - if (VN_IS(m_modp, Iface)) { - // Inlining an interface means we no longer have a cell handle to resolve to. - // If inlining moves post-scope this can perhaps be relaxed. - cantInline("modIface", true); - } - if (m_modp->modPublic() && (m_modp->isTop() || !v3Global.opt.flatten())) { - cantInline("modPublic", false); - } - // If the instance is a --lib-create library stub instance, and need tracing, - // then don't inline as we need to know its a lib stub for sepecial handling - // in V3TraceDecl. See #7001. - if (m_modp->verilatorLib() && v3Global.opt.trace()) { - cantInline("verilatorLib with --trace", false); + if (nodep == v3Global.rootp()->constPoolp()->modp()) return; // Ignore const pool module + + UASSERT_OBJ(!m_modVtxp, nodep, "Unsupported: Nested modules"); + + // Create the module vertex + InlineModModuleVertex* const vtxp = m_graphp->getInlineModModuleVertexp(nodep); + + // Check if the module itself is not inlineable + + // Inlining an interface means we no longer have a cell handle to resolve to. + // If inlining moves post-scope this can perhaps be relaxed. + if (VN_IS(nodep, Iface)) vtxp->setNoInlineHard("Interface"); + // Never inline packages - TODO: conceptually fine, but why not? + if (VN_IS(nodep, Package)) vtxp->setNoInlineHard("Package"); + // A --lib-create library stub instance that needs tracing must not be + // inlined, so we still know it is a lib stub in V3TraceDecl (see #7001) + if (nodep->verilatorLib() && v3Global.opt.trace()) { + vtxp->setNoInlineHard("verilatorLib with --trace"); } - iterateChildren(nodep); + // Don't inline public modules by default + if (nodep->modPublic()) vtxp->setNoInlineSoft("Public module"); + + // Iterate children + VL_RESTORER(m_modVtxp); + m_modVtxp = vtxp; + iterateChildrenConst(nodep); } + void visit(AstClass* nodep) override { - // TODO allow inlining of modules that have classes - // (Probably wait for new inliner scheme) - cantInline("class", true); - iterateChildren(nodep); + // TODO allow inlining of modules that contain classes + if (m_modVtxp) m_modVtxp->setNoInlineHard("Contains class"); + iterateChildrenConst(nodep); // TODO: this is only needed or FTaskRef cleanup } + + // Cells instantiate modules void visit(AstCell* nodep) override { - m_moduleState(nodep->modp()).m_cellRefs++; - m_moduleState(m_modp).m_childCells.push_back(nodep); - m_instances[m_modp][nodep->modp()]++; - iterateChildren(nodep); + UASSERT_OBJ(m_modVtxp, nodep, "Cell should be under a module"); + + // Create the cell vertex + InlineModCellVertex* const vtxp = m_graphp->getInlineModCellVertexp(nodep); + + // Add containing-module/instantiated-module edges + m_graphp->addEdge(*m_modVtxp, *vtxp); + m_graphp->addEdge(*vtxp, *m_graphp->getInlineModModuleVertexp(nodep->modp())); + + // Iterate children + iterateChildrenConst(nodep); } + void visit(AstPragma* nodep) override { if (nodep->pragType() == VPragmaType::INLINE_MODULE) { - if (!m_modp) { + if (!m_modVtxp) { nodep->v3error("Inline pragma not under a module"); // LCOV_EXCL_LINE - } else if (m_modp->user2() == CIL_MAYBE || m_modp->user2() == CIL_NOTSOFT) { - m_modp->user2(CIL_USER); + } else { + m_modVtxp->setShouldInline("Pragma INLINE_MODULE"); } - // Remove so it does not propagate to upper cell - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } else if (nodep->pragType() == VPragmaType::NO_INLINE_MODULE) { - if (!m_modp) { - nodep->v3error("Inline pragma not under a module"); // LCOV_EXCL_LINE - } else if (!v3Global.opt.flatten()) { - cantInline("Pragma NO_INLINE_MODULE", false); - } - // Remove so it does not propagate to upper cell - // Remove so don't propagate to upper cell... VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + return; } + + if (nodep->pragType() == VPragmaType::NO_INLINE_MODULE) { + if (!m_modVtxp) { + nodep->v3error("Inline pragma not under a module"); // LCOV_EXCL_LINE + } else { + m_modVtxp->setNoInlineSoft("Pragma NO_INLINE_MODULE"); + } + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + return; + } + + iterateChildrenConst(nodep); } - void visit(AstVarXRef* nodep) override { - // Keep varp - V3Const::constifyEdit is called during pinReconnectSimple - // which needs varp to be set. V3LinkDot will re-resolve after inlining. - } + + // TODO: Bit nasty to do this here, but historically present, and still necessary void visit(AstNodeFTaskRef* nodep) override { + if (m_modVtxp) m_modVtxp->sizeInc(); // Remove link. V3LinkDot will reestablish it after inlining. // MethodCalls not currently supported by inliner, so keep linked if (!nodep->classOrPackagep() && !VN_IS(nodep, MethodCall)) { nodep->taskp(nullptr); VIsCached::clearCacheTree(); } - iterateChildren(nodep); + iterateChildrenConst(nodep); } - void visit(AstAlways* nodep) override { - if (nodep->keyword() != VAlwaysKwd::CONT_ASSIGN) nodep->user4Inc(); // statement count - iterateChildren(nodep); - } - void visit(AstNodeAssign* nodep) override { - // Don't count assignments, as they'll likely flatten out - // Still need to iterate though to nullify VarXRefs - const int oldcnt = m_modp->user4(); - iterateChildren(nodep); - m_modp->user4(oldcnt); - } - void visit(AstNetlist* nodep) override { - // Build ModuleState, user2, and user4 for all modules. - // Also build m_allMods and m_instances. - iterateChildren(nodep); - // Iterate through all modules in bottom-up order. - // Make a final inlining decision for each. - for (AstNodeModule* const modp : vlstd::reverse_view(m_allMods)) { - - // If we're going to inline some modules into this one, - // update user4 (statement count) to reflect that: - int statements = modp->user4(); - for (const auto& pair : m_instances[modp]) { - const AstNodeModule* const childp = pair.first; - if (m_moduleState(childp).m_inlined) { // inlining child - statements += childp->user4() * pair.second; - } - } - modp->user4(statements); - - const int allowed = modp->user2(); - const int refs = m_moduleState(modp).m_cellRefs; - - // Should we automatically inline this module? - // If --flatten is specified, then force everything to be inlined that can be. - // inlineMult = 2000 by default. - // If a mod*#refs is < this # nodes, can inline it - // Packages aren't really "under" anything so they confuse this algorithm - const bool doit = !VN_IS(modp, Package) // - && allowed != CIL_NOTHARD // - && allowed != CIL_NOTSOFT // - && (allowed == CIL_USER // - || v3Global.opt.flatten() // - || refs == 1 // - || statements < INLINE_MODS_SMALLER // - || v3Global.opt.inlineMult() < 1 // - || refs * statements < v3Global.opt.inlineMult()); - m_moduleState(modp).m_inlined = doit; - UINFO(4, " Inline=" << doit << " Possible=" << allowed << " Refs=" << refs - << " Stmts=" << statements << " " << modp); - } - } - //-------------------- + // Base node void visit(AstNode* nodep) override { - if (m_modp) m_modp->user4Inc(); // Inc statement count - iterateChildren(nodep); + if (m_modVtxp) m_modVtxp->sizeInc(); + iterateChildrenConst(nodep); } + // CONSTRUCTORS + explicit InlineModGraphBuilder(AstNetlist* nodep) { + // Build the module instantiation graph + iterateConst(nodep); + // Order vertices (any topological order is fine), can't be cyclic at this point + m_graphp->order(); + // Check that the first vertex is the top level module (everything, including packages, + // have a corresponding AstCell under the top level at this point). + UASSERT_OBJ(m_graphp->vertices().frontp()->as()->modp()->isTop(), + nodep, "First vertex should be top level module"); +#ifdef VL_DEBUG + for (const V3GraphVertex& vtx : m_graphp->vertices()) { + // First vertex is the top levelmodule, we checked above + if (&vtx == m_graphp->vertices().frontp()) continue; + // Otherwise it should have instantiations + UASSERT_OBJ(!vtx.inEmpty(), &vtx, "Should have edges from root"); + } +#endif + } + ~InlineModGraphBuilder() override = default; + public: - // CONSTRUCTORS - explicit InlineMarkVisitor(AstNode* nodep, ModuleStateUser1Allocator& moduleState) - : m_moduleState{moduleState} { - iterate(nodep); - } - ~InlineMarkVisitor() override { - V3Stats::addStat("Optimizations, Inline unsupported", m_statUnsup); + static std::unique_ptr apply(AstNetlist* nodep) { + return std::move(InlineModGraphBuilder{nodep}.m_graphp); } }; @@ -264,8 +391,12 @@ class InlineRelinkVisitor final : public VNVisitor { m_pinSubstitutedXRefs; // VarXRefs created by pin substitution in this relink pass. // Their dotted/inlinedDots already represent the parent's scope // and must not be rewritten on the immediate visit. + InlineModGraph& m_graph; // The instance graph AstNodeModule* const m_modp; // The module we are inlining into + // The vertex of the module we are inlining into, for updating the graph + InlineModModuleVertex* const m_mVtxp = m_graph.getInlineModModuleVertexp(m_modp); const AstCell* const m_cellp; // The cell being inlined + size_t m_nPlaceholders = 0; // Unique identifier sequence number for placeholder variables // VISITORS @@ -282,6 +413,11 @@ class InlineRelinkVisitor final : public VNVisitor { void visit(AstCell* nodep) override { // Cell under the inline cell, need to rename to avoid conflicts nodep->name(m_cellp->name() + "__DOT__" + nodep->name()); + // Need to update graph + nodep->user4p(nullptr); // clone copied user4p, reset to make new vertex + InlineModCellVertex* const vtxp = m_graph.getInlineModCellVertexp(nodep); + m_graph.addEdge(*m_mVtxp, *vtxp); + m_graph.addEdge(*vtxp, *m_graph.getInlineModModuleVertexp(nodep->modp())); iterateChildren(nodep); } void visit(AstClass* nodep) override { @@ -464,8 +600,10 @@ class InlineRelinkVisitor final : public VNVisitor { public: // CONSTRUCTORS - InlineRelinkVisitor(AstNodeModule* cloneModp, AstNodeModule* oldModp, AstCell* cellp) - : m_modp{oldModp} + InlineRelinkVisitor(AstNodeModule* cloneModp, AstNodeModule* oldModp, AstCell* cellp, + InlineModGraph& graph) + : m_graph{graph} + , m_modp{oldModp} , m_cellp{cellp} { // CellInlines added by V3Begin for generate/named blocks have origModName // "__BEGIN__"; only those added by prior V3Inline passes carry a real module @@ -607,7 +745,7 @@ void connectPort(AstNodeModule* modp, AstVar* nodep, AstNodeExpr* pinExprp) { } // Inline 'cellp' into 'modp'. 'last' indicatest this is tha last instance of the inlined module -void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last) { +void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last, InlineModGraph& graph) { UINFO(5, " Inline Cell " << cellp); UINFO(5, " into Module " << modp); @@ -662,8 +800,8 @@ void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last) { connectPort(modp, newModVarp, pinExprp); } - // Cleanup var names, etc, to not conflict, relink replaced variables - { InlineRelinkVisitor{inlinedp, modp, cellp}; } + // Cleanup var names, etc, to not conflict, relink replaced variables, adjust graph + { InlineRelinkVisitor{inlinedp, modp, cellp, graph}; } // Move statements from the inlined module into the module we are inlining into if (AstNode* const stmtsp = inlinedp->stmtsp()) { modp->addStmtsp(stmtsp->unlinkFrBackWithNext()); @@ -675,39 +813,60 @@ void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last) { } // Apply all inlining decisions -void process(AstNetlist* netlistp, ModuleStateUser1Allocator& moduleStates) { +void process(AstNetlist* netlistp, InlineModGraph& graph) { // NODE STATE - // Input: - // AstNodeModule::user1p() // ModuleState instance (via moduleState) - // // Cleared entire netlist // AstIfaceRefDType::user1() // Whether the cell pointed to by this // // AstIfaceRefDType has been inlined // AstCell::user3p() // AstCell*, the clone - // AstVar::user3p() // AstVar*, the clone clone + // AstVar::user3p() // AstVar*, the clone // Cleared each cell // AstVar::user2p() // AstVarRef*/AstConst* This port is connected to (AstPin::expr()) - const VNUser3InUse m_user3InUse; + const VNUser1InUse user1InUse; + const VNUser3InUse user3InUse; // Number of inlined instances, for statistics VDouble0 m_nInlined; - // We want to inline bottom up. The modules under the netlist are in - // dependency order (top first, leaves last), so find the end of the list. - AstNode* nodep = netlistp->modulesp(); - while (nodep->nextp()) nodep = nodep->nextp(); + // Gather all cells that need to be inlined (this is in topological order) + std::vector cVtxps; + for (V3GraphVertex& vtx : graph.vertices()) { + InlineModCellVertex* const cVtxp = vtx.cast(); + if (!cVtxp) continue; + if (!cVtxp->doInline()) continue; + cVtxps.push_back(cVtxp); + } - // Iterate module list backwards (stop when we get back to the Netlist) - while (AstNodeModule* const modp = VN_CAST(nodep, NodeModule)) { - nodep = nodep->backp(); + // Inline cells bottom up (leaves into roots) + for (InlineModCellVertex* const cVtxp : vlstd::reverse_view(cVtxps)) { + // Pick up parts before deleting + InlineModModuleVertex& mVtx = cVtxp->instanceIn(); + InlineModModuleVertex* const iVtxp = &cVtxp->instanceOf(); + AstCell* const cellp = cVtxp->cellp(); + const bool last = iVtxp->inSize1(); + UASSERT_OBJ(!iVtxp->noInlineHard(), cellp, "Should not be inlining if not possible"); - // Consider each cell inside the current module for inlining - for (AstCell* const cellp : moduleStates(modp).m_childCells) { - ModuleState& childState = moduleStates(cellp->modp()); - if (!childState.m_inlined) continue; - ++m_nInlined; - inlineCell(modp, cellp, --childState.m_cellRefs == 0); + // Update + ++m_nInlined; + mVtx.sizeInc(iVtxp->size()); // For debug dump only + + // Delete the cell we are inlining + VL_DO_DANGLING(cVtxp->unlinkDelete(&graph), cVtxp); + // Delete the module we are inlining if this is the last instance + if (last) { + while (!iVtxp->outEmpty()) { + InlineModCellVertex* const tVtxp + = iVtxp->outEdges().frontp()->top()->as(); + // Bottom up ordering ensures this + UASSERT_OBJ(!tVtxp->doInline(), tVtxp, "Should have been inlined"); + VL_DO_DANGLING(tVtxp->unlinkDelete(&graph), tVtxp); + } + VL_DO_DANGLING(iVtxp->unlinkDelete(&graph), iVtxp); } + + // Do it + inlineCell(mVtx.modp(), cellp, last, graph); + if (dumpGraphLevel() >= 9) graph.dumpDotFilePrefixed("inlinemod-cell"); } V3Stats::addStat("Optimizations, Inlined instances", m_nInlined); @@ -729,25 +888,65 @@ void process(AstNetlist* netlistp, ModuleStateUser1Allocator& moduleStates) { void V3Inline::inlineAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); - { - const VNUser1InUse m_inuser1; // output of InlineMarkVisitor, input to InlineVisitor. - ModuleStateUser1Allocator moduleState; // AstUser1Allocator + // Build the bipartite module instantiation graph + std::unique_ptr graphp = InlineModGraphBuilder::apply(nodep); + if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-graph"); - // Scoped to clean up temp userN's - { InlineMarkVisitor{nodep, moduleState}; } - - // Inline the modles we decided to inline - ModuleInliner::process(nodep, moduleState); - - // Check inlined modules have been removed during traversal. Otherwise we might have blown - // up Verilator memory consumption. - for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp; - modp = VN_AS(modp->nextp(), NodeModule)) { - UASSERT_OBJ(!moduleState(modp).m_inlined, modp, - "Inlined module should have been deleted when the last instance " - "referencing it was inlined"); + // Decide which instances to inline + const size_t designSize + = graphp->vertices().frontp()->as()->flattenedSize(); + for (V3GraphVertex& vtx : graphp->vertices()) { + if (InlineModModuleVertex* const mVtxp = vtx.cast()) { + // If this module is less than 10% of the design, flatten this module + if (mVtxp->flattenedSize() * 10 < designSize) mVtxp->setFlatten(); + // Don't inline if can't inline + if (mVtxp->noInlineHard()) continue; + // Don't inline if soft off + if (mVtxp->noInlineSoft()) continue; + // If all instances of this module combined are less than 20% of the design, inline all + size_t totalSize = mVtxp->flattenedSize() * mVtxp->instanceCount(); + if (totalSize * 5 < designSize) { + for (V3GraphEdge& edge : mVtxp->inEdges()) { + InlineModCellVertex* const cVtxp = edge.fromp()->as(); + cVtxp->setDoInline("< 20% of design"); + } + } + // No more decisions based on module vertex + continue; } + + // The instantiation + InlineModCellVertex& cVtx = *vtx.as(); + // The module instantiated by this cell + InlineModModuleVertex& mVtx = cVtx.instanceOf(); + + // Don't inline if can't inline, duh! + if (mVtx.noInlineHard()) continue; + + // If it should be inlined, inlined it + if (mVtx.shouldInline()) cVtx.setDoInline("should inline"); + // If --flatten, inline it + if (v3Global.opt.flatten()) cVtx.setDoInline("--flatten"); + + // Don't inline for other reasons if soft off + if (mVtx.noInlineSoft()) continue; + + // If instatiated in exactly one static site, inline it + if (mVtx.inSize1()) cVtx.setDoInline("Single static instance"); + // If small, inline it + if (mVtx.size() < INLINE_MODS_SMALLER) cVtx.setDoInline("Small"); + // If inlineMult is 0, inline it + if (v3Global.opt.inlineMult() < 1) cVtx.setDoInline("inlineMult < 1"); + // If it would yield less than the given number of ops, inline it + const size_t inlinedSize = mVtx.inEdges().size() * mVtx.size(); + const size_t limit = v3Global.opt.inlineMult(); + if (inlinedSize < limit) cVtx.setDoInline("inlinedSize < inlineMult"); } + if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-decision"); + + // Inline the modles we decided to inline + ModuleInliner::process(nodep, *graphp); + if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-inlined"); V3Global::dumpCheckGlobalTree("inline", 0, dumpTreeEitherLevel() >= 3); } diff --git a/src/V3InlineCFuncs.cpp b/src/V3InlineCFuncs.cpp index 99efbc201..cdfc24062 100644 --- a/src/V3InlineCFuncs.cpp +++ b/src/V3InlineCFuncs.cpp @@ -15,14 +15,13 @@ //************************************************************************* // V3InlineCFuncs's Transformations: // -// For each CCall to a small CFunc: -// - Check if function is eligible for inlining (small enough, same scope) -// - Clone local variables with unique names to avoid collisions -// - Replace CCall with cloned function body statements +// Build a bipartite call graph containing function and call site vertices, +// then iterate functions leaf to root, inlining if size heuristics are met. +// Finally, remove unused functions. // // Two tunables control inlining: -// --inline-cfuncs : Always inline if size <= n (default 20) -// --inline-cfuncs-product : Also inline if size * call_count <= n (default 200) +// --inline-cfuncs : Inline if size <= n +// --inline-cfuncs-product : Also inline if size * call_count <= n // //************************************************************************* @@ -31,231 +30,465 @@ #include "V3InlineCFuncs.h" #include "V3AstUserAllocator.h" +#include "V3ExecGraph.h" +#include "V3Graph.h" #include "V3Stats.h" -#include #include VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### -// Helper visitor to check if a CFunc contains C statements -// Uses clearOptimizable pattern for debugging +// Bipartite call graph containing function and call site vertices -class CFuncInlineCheckVisitor final : public VNVisitorConst { - // STATE - bool m_optimizable = true; // True if function can be inlined - string m_whyNot; // Reason why not optimizable - AstNode* m_whyNotNodep = nullptr; // Node that caused non-optimizable +class InlineCFuncsFunctionVertex; +class InlineCFuncsCallSiteVertex; - // METHODS - void clearOptimizable(AstNode* nodep, const string& why) { - if (m_optimizable) { - m_optimizable = false; - m_whyNot = why; - m_whyNotNodep = nodep; - UINFO(9, "CFunc not inlineable: " << why); - if (nodep) UINFO(9, ": " << nodep); - UINFO(9, ""); - } - } +class InlineCFuncsCallGraph final : public V3Graph { +public: + InlineCFuncsCallGraph() + : V3Graph{} {} + ~InlineCFuncsCallGraph() override = default; - // VISITORS - void visit(AstCStmt* nodep) override { clearOptimizable(nodep, "contains AstCStmt"); } - void visit(AstCExpr* nodep) override { clearOptimizable(nodep, "contains AstCExpr"); } - void visit(AstCStmtUser* nodep) override { clearOptimizable(nodep, "contains AstCStmtUser"); } - void visit(AstCExprUser* nodep) override { clearOptimizable(nodep, "contains AstCExprUser"); } - void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } + void addEdge(InlineCFuncsFunctionVertex& from, InlineCFuncsCallSiteVertex& top); + void addEdge(InlineCFuncsCallSiteVertex& from, InlineCFuncsFunctionVertex& top); +}; + +class EitherVertex VL_NOT_FINAL : public V3GraphVertex { + VL_RTTI_IMPL(EitherVertex, V3GraphVertex) +protected: + explicit EitherVertex(InlineCFuncsCallGraph& graph) + : V3GraphVertex{&graph} {} +}; + +class InlineCFuncsFunctionVertex final : public EitherVertex { + VL_RTTI_IMPL(InlineCFuncsFunctionVertex, EitherVertex) + AstCFunc* const m_cfuncp; // The function + const char* m_noInlineWyp = nullptr; // First reason the function should not be inlined + const char* m_keepWyp = nullptr; // Why the function should not be removed + size_t m_size = 0; // The size of the function public: - // CONSTRUCTORS - explicit CFuncInlineCheckVisitor(AstCFunc* cfuncp) { iterateConst(cfuncp); } + InlineCFuncsFunctionVertex(InlineCFuncsCallGraph& graph, AstCFunc* cfuncp) + : EitherVertex{graph} + , m_cfuncp{cfuncp} {} + ~InlineCFuncsFunctionVertex() override = default; // ACCESSORS - bool optimizable() const { return m_optimizable; } - string whyNot() const { return m_whyNot; } - AstNode* whyNotNodep() const { return m_whyNotNodep; } + AstCFunc* cfuncp() const { return m_cfuncp; } + size_t size() const { return m_size; } + void sizeInc(size_t value = 1) { m_size += value; } + bool noInline() const { return m_noInlineWyp; } + void setNoInline(const char* whyp) { + if (!m_noInlineWyp) m_noInlineWyp = whyp; + } + bool keep() const { return m_keepWyp; } + void setKeep(const char* whyp) { + if (!m_keepWyp) m_keepWyp = whyp; + } + std::string dotColor() const override { + return m_noInlineWyp ? "red" : m_keepWyp ? "orange" : "black"; + } + + // debug + FileLine* fileline() const override { return m_cfuncp->fileline(); } + std::string dotShape() const override { return "box"; } + std::string name() const override VL_MT_STABLE { + std::string str = cvtToHex(m_cfuncp); + str += "\n" + m_cfuncp->name(); + str += "\nsize: " + std::to_string(m_size); + if (m_noInlineWyp) str += "\nNoInline: "s + m_noInlineWyp; + if (m_keepWyp) str += "\nKeep: "s + m_keepWyp; + return str; + } }; +class InlineCFuncsCallSiteVertex final : public EitherVertex { + VL_RTTI_IMPL(InlineCFuncsCallSiteVertex, EitherVertex) + AstCCall* const m_callp; // The call site + const char* m_noInlineWyp = nullptr; // First reason the function should not be inlined + +public: + InlineCFuncsCallSiteVertex(InlineCFuncsCallGraph& graph, AstCCall* callp) + : EitherVertex{graph} + , m_callp{callp} {} + ~InlineCFuncsCallSiteVertex() override = default; + + // ACCESSORS + AstCCall* callp() const { return m_callp; } + bool noInline() const { return m_noInlineWyp; } + void setNoInline(const char* whyp) { + if (!m_noInlineWyp) m_noInlineWyp = whyp; + } + + // debug + FileLine* fileline() const override { return m_callp->fileline(); } + std::string dotColor() const override { return m_noInlineWyp ? "red" : "black"; } + std::string dotShape() const override { return "ellipse"; } + std::string name() const override VL_MT_STABLE { + std::string str = cvtToHex(m_callp); + if (m_noInlineWyp) str += "\nNoInline: "s + m_noInlineWyp; + return str; + } +}; + +void InlineCFuncsCallGraph::addEdge(InlineCFuncsFunctionVertex& caller, + InlineCFuncsCallSiteVertex& callsite) { + UASSERT_OBJ(callsite.inEmpty(), &callsite, "Call site should have at most one incoming edge"); + new V3GraphEdge{this, &caller, &callsite, 1, true}; // Can cut caller -> callsite +} +void InlineCFuncsCallGraph::addEdge(InlineCFuncsCallSiteVertex& callsite, + InlineCFuncsFunctionVertex& callee) { + UASSERT_OBJ(callsite.outEmpty(), &callsite, "Call site should have at most one outgoing edge"); + new V3GraphEdge{this, &callsite, &callee, 1, false}; +} + //###################################################################### class InlineCFuncsVisitor final : public VNVisitor { // NODE STATE - // AstCFunc::user1() -> vector of AstCCall* pointing to this function - // AstCFunc::user2() -> bool: true if checked for C statements - // AstCFunc::user3() -> bool: true if contains C statements (not inlineable) + // AstCFunc::user1p() -> InlineCFuncsFunctionVertex*, the function vertex + // AstCCall::user1p() -> InlineCFuncsCallSiteVertex*, the call site vertex + // AstVar::user2p() -> AstVar*, the cloned inlined local variable const VNUser1InUse m_user1InUse; - const VNUser2InUse m_user2InUse; - const VNUser3InUse m_user3InUse; - AstUser1Allocator> m_callSites; // STATE - VDouble0 m_statInlined; // Statistic tracking - const int m_threshold1; // Size threshold: always inline if size <= this - const int m_threshold2; // Product threshold: inline if size * calls <= this - AstCFunc* m_callerFuncp = nullptr; // Current caller function - // Tuples of (StmtExpr to replace, CFunc to inline from, caller func for vars) - std::vector> m_toInline; + InlineCFuncsCallGraph m_graph; // The call graph + VDouble0 m_statCallsInlined; // Number of calls inlined + VDouble0 m_statFuncsInlined; // Number of functions inlined at least once + VDouble0 m_statFuncsRemoved; // Number of fully-inlined functions removed + // Size threshold: always inline if size <= this + const size_t m_sizeThreshold = v3Global.opt.inlineCFuncs(); + // Product threshold: inline if size * calls <= this + const size_t m_prodThreshold = v3Global.opt.inlineCFuncsProduct(); + // Maximum size of caller to consider inlining into + const size_t m_maxSizeCFunc = []() -> size_t { + int maxCFunc = v3Global.opt.outputSplitCFuncs(); + int maxFile = v3Global.opt.outputSplit(); + if (maxCFunc <= 0) maxCFunc = std::numeric_limits::max(); + if (maxFile <= 0) maxFile = std::numeric_limits::max(); + return std::min(maxCFunc, maxFile); + }(); + const size_t m_maxSizeTrace = []() -> size_t { + int maxTrace = v3Global.opt.outputSplitCTrace(); + int maxFile = v3Global.opt.outputSplit(); + if (maxTrace <= 0) maxTrace = std::numeric_limits::max(); + if (maxFile <= 0) maxFile = std::numeric_limits::max(); + return std::min(maxTrace, maxFile); + }(); + InlineCFuncsFunctionVertex* m_cfuncVtxp = nullptr; // Vertex of currently iterated function + bool m_inExecGraph = false; // True while inside an AstExecGraph subtree // METHODS - - // Check if a function contains any $c() calls (user or internal) - // Results are cached in user2/user3 for efficiency - bool containsCStatements(AstCFunc* cfuncp) { - if (!cfuncp->user2()) { - // Not yet checked - run the check visitor - cfuncp->user2(true); // Mark as checked - const CFuncInlineCheckVisitor checker{cfuncp}; - cfuncp->user3(!checker.optimizable()); // Store result (true = contains C stmts) - } - return cfuncp->user3(); + InlineCFuncsFunctionVertex* getInlineCFuncsFunctionVertexp(AstCFunc* cfuncp) { + if (!cfuncp->user1p()) cfuncp->user1p(new InlineCFuncsFunctionVertex{m_graph, cfuncp}); + return cfuncp->user1u().to(); } - // Check if a function is eligible for inlining into caller - bool isInlineable(const AstCFunc* callerp, AstCFunc* cfuncp) { - // Must be in the same scope (same class) to access the same members - if (callerp->scopep() != cfuncp->scopep()) return false; + InlineCFuncsCallSiteVertex* getInlineCFuncsCallSiteVertexp(AstCCall* callp) { + if (!callp->user1p()) callp->user1p(new InlineCFuncsCallSiteVertex{m_graph, callp}); + return callp->user1u().to(); + } - // Check for $c() calls that might use 'this' - if (containsCStatements(cfuncp)) return false; + AstCLocalScope* inlineCall(AstCFunc* const callerp, // + AstCCall* const callp, // + AstCFunc* const calleep, // + const size_t seqNum) { + UINFO(6, "Inlining CFunc " << calleep->name() << " into " << callerp->name() + << " at call site " << callp); - // Check it's a void function (not a coroutine) - if (cfuncp->rtnTypeVoid() != "void") return false; + AstNodeStmt* const callSitep = VN_AS(callp->backp(), StmtExpr); + ++m_statCallsInlined; - // Don't inline functions marked dontCombine (e.g. trace, entryPoint) - if (cfuncp->dontCombine()) return false; - - // Don't inline entry point functions - if (cfuncp->entryPoint()) return false; - - // Must have statements to inline - if (!cfuncp->stmtsp()) return false; - - // Check size thresholds - const size_t funcSize = cfuncp->nodeCount(); - - // Always inline if small enough - if (funcSize <= static_cast(m_threshold1)) return true; - - // Also inline if size * call_count is reasonable - const size_t callCount = m_callSites(cfuncp).size(); - if (callCount > 0 && funcSize * callCount <= static_cast(m_threshold2)) { - return true; + // Callee might be empty, just delete the call + if (!calleep->stmtsp()) { + VL_DO_DANGLING(pushDeletep(callSitep->unlinkFrBack()), callSitep); + return nullptr; } - return false; + // Replace call site with a local scope + FileLine* const flp = callSitep->fileline(); + AstCLocalScope* const lscopep = new AstCLocalScope{flp, nullptr}; + callSitep->replaceWith(lscopep); + VL_DO_DANGLING(pushDeletep(callSitep), callSitep); + lscopep->addStmtsp(new AstComment{flp, "Inlined CFunc: " + calleep->name()}); + + // Although it's in a local scope, we still make names of cloned locals unique + const std::string varPrefix + = "__Vinline_" + std::to_string(seqNum) + "_" + calleep->name() + "_"; + + // AstVar::user2p() -> AstVar*, the cloned inlined local variable + const VNUser2InUse user2InUse; + + // Clone local variables, add them to the local scope + for (AstVar* varp = calleep->varsp(); varp; varp = VN_AS(varp->nextp(), Var)) { + AstVar* const newVarp = varp->cloneTree(false); + newVarp->name(varPrefix + varp->name()); + lscopep->addStmtsp(newVarp); + varp->user2p(newVarp); + } + + // Clone the function body + AstNode* const bodyp = calleep->stmtsp()->cloneTree(true); + lscopep->addStmtsp(bodyp); + + // Retarget local variable references to the cloned locals + // Rename locals defined in the body, TODO: there should be none after #6280 + // Reset vertex pointers on calls + bodyp->foreachAndNext([&](AstNode* nodep) { + if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) { + if (AstVar* const varp = VN_AS(refp->varp()->user2p(), Var)) refp->varp(varp); + } else if (AstVar* const varp = VN_CAST(nodep, Var)) { + varp->name(varPrefix + varp->name()); + } else if (AstCCall* const callp = VN_CAST(nodep, CCall)) { + callp->user1p(nullptr); + } + }); + + // Return the local scope + return lscopep; + } + + void doInlining() { + // Need to gather vertices as we are changing the graph + std::vector m_fVtxps; + for (V3GraphVertex& vtx : m_graph.vertices()) { + if (InlineCFuncsFunctionVertex* const fVtxp = vtx.cast()) { + m_fVtxps.emplace_back(fVtxp); + } + } + + // Iterate functions leaf to root + for (InlineCFuncsFunctionVertex* const calleeVtxp : vlstd::reverse_view(m_fVtxps)) { + // Should we inline this function? + if (calleeVtxp->noInline()) continue; // Told not to + + // Check size heuristics + const bool doIt = [&]() { + // Inline if small enough + if (calleeVtxp->size() <= m_sizeThreshold) return true; + // Inline if not too much bloat + const size_t nCalls = calleeVtxp->inEdges().size(); + if (nCalls * calleeVtxp->size() <= m_prodThreshold) return true; + // Otherwise don't inline + return false; + }(); + if (!doIt) continue; + + // Ok, attempt to inline call sites + size_t nInlined = 0; + for (const V3GraphEdge* const edgep : calleeVtxp->inEdges().unlinkable()) { + InlineCFuncsCallSiteVertex* const callVtxp + = edgep->fromp()->as(); + + AstCFunc* const calleep = calleeVtxp->cfuncp(); + AstCCall* const callp = callVtxp->callp(); + UINFO(6, "Consider inlining " << calleep->name() << " at call site " << callp); + // Should we inline this call site? + if (callVtxp->noInline()) continue; // Told not to + if (callVtxp->inEmpty()) continue; // Don't know where it's called from + + // Pick up the caller + UASSERT_OBJ(callVtxp->inSize1(), callVtxp->callp(), + "Expected exactly one input edge for call site"); + InlineCFuncsFunctionVertex* const callerVtxp + = callVtxp->inEdges().frontp()->fromp()->as(); + AstCFunc* const callerp = callerVtxp->cfuncp(); + + // Don't make a function bigger than the limit + const size_t limit = callerp->isTrace() ? m_maxSizeTrace : m_maxSizeCFunc; + if (callerVtxp->size() + calleeVtxp->size() > limit) continue; + + // Can't do it if it's in a different scope, self pointers differ + if (callerp->scopep() != calleep->scopep()) continue; + + // Inline it + if (!nInlined) ++m_statFuncsInlined; + AstNode* const inlinedp = inlineCall(callerp, callp, calleep, nInlined++); + + // Need to adjust the graph: + // 1. Delete inlined call site + VL_DO_DANGLING(callVtxp->unlinkDelete(&m_graph), callVtxp); + // 2. Add new inlined call sites - also increments size of caller + VL_RESTORER(m_cfuncVtxp); + m_cfuncVtxp = callerVtxp; + if (inlinedp) iterateChildrenConst(inlinedp); + } + } + } + + void removeUnusedFuncs() { + // Iterate root to leaves + for (V3GraphVertex* const vtxp : m_graph.vertices().unlinkable()) { + InlineCFuncsFunctionVertex* const fVtxp = vtxp->cast(); + if (!fVtxp) continue; + // Keep if still called + if (!fVtxp->inEmpty()) continue; + // Keep for other reasons + if (fVtxp->keep()) continue; + + AstCFunc* const funcp = fVtxp->cfuncp(); + UINFO(6, "Removing unused CFunc " << funcp); + ++m_statFuncsRemoved; + + // Unlink all call sites + for (const V3GraphEdge* const edgep : vtxp->outEdges().unlinkable()) { + edgep->top()->unlinkEdges(&m_graph); + } + // Delete function vertex + vtxp->unlinkDelete(&m_graph); + // Delete the function + VL_DO_DANGLING(pushDeletep(funcp->unlinkFrBack()), funcp); + } + + // Delete inlined/deleted call site vertices (for debugging only) + for (V3GraphVertex* const vtxp : m_graph.vertices().unlinkable()) { + InlineCFuncsCallSiteVertex* const cVtxp = vtxp->cast(); + if (!cVtxp) continue; + if (!cVtxp->inEmpty()) continue; + if (!cVtxp->outEmpty()) continue; + vtxp->unlinkDelete(&m_graph); + } } // VISITORS - void visit(AstCCall* nodep) override { - iterateChildren(nodep); - - AstCFunc* const cfuncp = nodep->funcp(); - if (!cfuncp) return; - - // Track call site for call counting - m_callSites(cfuncp).emplace_back(nodep); - } - void visit(AstCFunc* nodep) override { - VL_RESTORER(m_callerFuncp); - m_callerFuncp = nodep; - iterateChildren(nodep); + // Create the function vertex + InlineCFuncsFunctionVertex* const vtxp = getInlineCFuncsFunctionVertexp(nodep); + + // Check if the function itself is not inlineable + if (nodep->rtnTypeVoid() != "void") vtxp->setNoInline("Not void"); + if (nodep->dpiImportPrototype()) vtxp->setNoInline("DPI import prototype"); + if (nodep->recursive()) vtxp->setNoInline("Recursive"); + if (nodep->argsp()) vtxp->setNoInline("Has arguments"); + if (nodep->isVirtual()) vtxp->setNoInline("Virtual method"); + + // Check if the function should not be removed + if (nodep->entryPoint()) vtxp->setKeep("Entry point"); + if (nodep->dpiImportPrototype()) vtxp->setKeep("DPI import prototype"); + if (nodep->dpiExportDispatcher()) vtxp->setKeep("DPI export implementation"); + if (nodep->isVirtual()) vtxp->setKeep("Virtual method"); + + // Iterate children + VL_RESTORER(m_cfuncVtxp); + m_cfuncVtxp = vtxp; + iterateChildrenConst(nodep); } - void visit(AstNodeModule* nodep) override { - // Process per module for better cache behavior - m_toInline.clear(); + // Inlineable calls + void visit(AstCCall* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + AstCFunc* const calleep = nodep->funcp(); - // Phase 1: Collect call sites within this module - iterateChildren(nodep); + // Create the call site vertex + InlineCFuncsCallSiteVertex* const vtxp = getInlineCFuncsCallSiteVertexp(nodep); - // Phase 2: Determine which calls to inline - collectInlineCandidates(nodep); + // Check if the call site is not inlineable + if (!VN_IS(nodep->backp(), StmtExpr)) vtxp->setNoInline("Not in statement position"); + if (m_inExecGraph) vtxp->setNoInline("In ExecGraph"); + if (calleep->isVirtual()) vtxp->setNoInline("Virtual method"); - // Phase 3: Perform inlining for this module - doInlining(); + // Add caller/callee edges + if (m_cfuncVtxp) m_graph.addEdge(*m_cfuncVtxp, *vtxp); + m_graph.addEdge(*vtxp, *getInlineCFuncsFunctionVertexp(calleep)); + + // Iterate children + iterateChildrenConst(nodep); } - void visit(AstNode* nodep) override { iterateChildren(nodep); } + // Nodes that reference functions/calls + void visit(AstNetlist* nodep) override { + UASSERT_OBJ(!nodep->evalp(), nodep, "evalp should not be null at this stage"); + UASSERT_OBJ(!nodep->evalNbap(), nodep, "evalNbap should be null at this stage"); + iterateChildrenConst(nodep); + } - // Collect calls that should be inlined within this module - void collectInlineCandidates(AstNodeModule* modp) { - for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { - AstCFunc* const callerp = VN_CAST(stmtp, CFunc); - if (!callerp) continue; + void visit(AstNodeCCall* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + getInlineCFuncsFunctionVertexp(nodep->funcp())->setKeep("Called elsewhere"); + iterateChildrenConst(nodep); + } - callerp->foreach([&](AstCCall* callp) { - AstCFunc* const cfuncp = callp->funcp(); - if (!cfuncp) return; - if (!isInlineable(callerp, cfuncp)) return; + void visit(AstAddrOfCFunc* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + getInlineCFuncsFunctionVertexp(nodep->funcp())->setKeep("Referenced by AddressOfCFunc"); + iterateChildrenConst(nodep); + } - // Walk up to find the containing StmtExpr - AstNode* stmtNodep = callp; - while (stmtNodep && !VN_IS(stmtNodep, StmtExpr) && !VN_IS(stmtNodep, CFunc)) { - stmtNodep = stmtNodep->backp(); - } + // Nodes preventing inlining + void visit(AstTraceDecl* nodep) override { + // Referenced by TraceInc + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains TraceDecl"); - AstStmtExpr* const stmtExprp = VN_CAST(stmtNodep, StmtExpr); - if (!stmtExprp) return; - - m_toInline.emplace_back(stmtExprp, cfuncp, callerp); - }); + if (AstCCall* const callp = nodep->dtypeCallp()) { + getInlineCFuncsCallSiteVertexp(callp)->setNoInline("Referenced by TraceDecl"); } + iterateChildrenConst(nodep); + } + void visit(AstExecGraph* nodep) override { + // AstExecGraph is not cloneable, so can't inline the containing function + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains ExecGraph"); + // Also mark functions referenced in the dependency graph + for (const V3GraphVertex& vtx : nodep->depGraphp()->vertices()) { + getInlineCFuncsFunctionVertexp(vtx.as()->funcp()) + ->setKeep("MTask function"); + } + VL_RESTORER(m_inExecGraph); + m_inExecGraph = true; + iterateChildrenConst(nodep); + } + void visit(AstCStmt* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CStmt"); + iterateChildrenConst(nodep); + } + void visit(AstCExpr* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CExpr"); + iterateChildrenConst(nodep); + } + void visit(AstCStmtUser* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CStmtUser"); + iterateChildrenConst(nodep); + } + void visit(AstCExprUser* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CExprUser"); + iterateChildrenConst(nodep); + } + void visit(AstCReturn* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CReturn"); + iterateChildrenConst(nodep); } - // Perform the actual inlining after iteration is complete - void doInlining() { - for (const auto& tuple : m_toInline) { - AstStmtExpr* const stmtExprp = std::get<0>(tuple); - AstCFunc* const cfuncp = std::get<1>(tuple); - AstCFunc* const callerp = std::get<2>(tuple); - - UINFO(6, "Inlining CFunc " << cfuncp->name() << " into " << callerp->name()); - ++m_statInlined; - - // Clone local variables with unique names to avoid collisions - std::map varMap; - for (AstVar* varp = cfuncp->varsp(); varp; varp = VN_AS(varp->nextp(), Var)) { - const string newName = "__Vinline_" + cfuncp->name() + "_" + varp->name(); - AstVar* const newVarp = varp->cloneTree(false); - newVarp->name(newName); - callerp->addVarsp(newVarp); - varMap[varp] = newVarp; - } - - // Clone the function body - AstNode* const bodyp = cfuncp->stmtsp()->cloneTree(true); - - // Retarget variable references to the cloned variables - // Must iterate all sibling statements, not just the first - if (!varMap.empty()) { - for (AstNode* stmtp = bodyp; stmtp; stmtp = stmtp->nextp()) { - stmtp->foreach([&](AstVarRef* refp) { - auto it = varMap.find(refp->varp()); - if (it != varMap.end()) refp->varp(it->second); - }); - } - } - - // Replace the statement with the inlined body - stmtExprp->addNextHere(bodyp); - VL_DO_DANGLING(stmtExprp->unlinkFrBack()->deleteTree(), stmtExprp); - } + // Base node + void visit(AstNode* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + iterateChildrenConst(nodep); } public: // CONSTRUCTORS - explicit InlineCFuncsVisitor(const AstNetlist* nodep) - : m_threshold1{v3Global.opt.inlineCFuncs()} - , m_threshold2{v3Global.opt.inlineCFuncsProduct()} { - // Don't inline when profiling or tracing - if (v3Global.opt.profCFuncs() || v3Global.opt.trace()) return; - // Process modules one at a time for better cache behavior - iterateAndNextNull(nodep->modulesp()); + explicit InlineCFuncsVisitor(AstNetlist* nodep) { + // Phase 1: Build call graph + iterateConst(nodep); + // Make acyclic in case there is recursion + m_graph.acyclic(V3GraphEdge::followAlwaysTrue); + // Order vertices (any topological order is fine) + m_graph.order(); + if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-graph"); + // Phase 2: Inline calls + doInlining(); + if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-inlined"); + // Phase 3: Remove unused functions + removeUnusedFuncs(); + if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-kept"); } ~InlineCFuncsVisitor() override { - V3Stats::addStat("Optimizations, Inlined CFuncs", m_statInlined); + V3Stats::addStat("Optimizations, Inline CFuncs, calls inlined", m_statCallsInlined); + V3Stats::addStat("Optimizations, Inline CFuncs, functions inlined", m_statFuncsInlined); + V3Stats::addStat("Optimizations, Inline CFuncs, functions removed", m_statFuncsRemoved); } }; @@ -264,6 +497,8 @@ public: void V3InlineCFuncs::inlineAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); + // Don't inline when profiling per-function (it would lose granularity) + if (v3Global.opt.profCFuncs()) return; { InlineCFuncsVisitor{nodep}; } // Destruct before checking V3Global::dumpCheckGlobalTree("inlinecfuncs", 0, dumpTreeEitherLevel() >= 6); } diff --git a/src/V3Interface.cpp b/src/V3Interface.cpp index 51cc2d11e..0d5fe47fb 100644 --- a/src/V3Interface.cpp +++ b/src/V3Interface.cpp @@ -43,7 +43,7 @@ class InlineIntfRefVisitor final : public VNVisitor { // VISITORS void visit(AstNetlist* nodep) override { iterateChildren(nodep->topModulep()); } void visit(AstCell* nodep) override { - VL_RESTORER(m_scope); + VL_RESTORER_COPY(m_scope); if (m_scope.empty()) { m_scope = nodep->name(); } else { diff --git a/src/V3LibMap.cpp b/src/V3LibMap.cpp index 5a2e153b3..619073551 100644 --- a/src/V3LibMap.cpp +++ b/src/V3LibMap.cpp @@ -47,8 +47,8 @@ class LibMapVisitor final : public VNVisitor { } void visit(AstLibrary* nodep) override { - VL_RESTORER(m_lib); - VL_RESTORER(m_rel); + VL_RESTORER_CLEAR(m_lib); + VL_RESTORER_CLEAR(m_rel); m_lib = nodep->name(); m_rel = V3Os::filenameDir(nodep->fileline()->filename()); iterateChildren(nodep); diff --git a/src/V3Life.cpp b/src/V3Life.cpp index 77ac85c87..7b4d5ab1a 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -60,7 +60,7 @@ public: class LifeVarEntry final { // Last assignment to this varscope, nullptr if no longer relevant - AstNodeStmt* m_assignp = nullptr; + AstNodeStmt* m_assignp = nullptr; // Last simple assignment AstConst* m_constp = nullptr; // Known constant value bool m_isNew = true; // Is just created // First access was a set (and thus block above may have a set that can be deleted @@ -77,6 +77,17 @@ public: m_isNew = false; m_setBeforeUse = setBeforeUse; } + string ascii() const { // LCOV_EXCL_START + std::ostringstream os; + os << "[Life "; + if (m_isNew) os << " isNew"; + if (m_setBeforeUse) os << " setBeforeUse"; + if (m_everSet) os << " everSet"; + if (m_assignp) os << " assignp=" << AstNode::nodeAddr(m_assignp); + if (m_constp) os << " constp=" << AstNode::nodeAddr(m_constp); + os << "]"; + return os.str(); + } // LCOV_EXCL_STOP void simpleAssign(AstNodeStmt* nodep) { // New simple A=.... assignment UASSERT_OBJ(!m_isNew, nodep, "Uninitialized new entry"); @@ -84,7 +95,10 @@ public: m_constp = nullptr; m_everSet = true; if (AstNodeAssign* const assp = VN_CAST(nodep, Assign)) { - if (VN_IS(assp->rhsp(), Const)) m_constp = VN_AS(assp->rhsp(), Const); + if (VN_IS(assp->rhsp(), Const)) { + m_constp = VN_AS(assp->rhsp(), Const); + UINFO(9, "assign-const " << assp->rhsp() << " = " << m_constp); + } } } void complexAssign() { // A[x]=... or some complicated assignment @@ -179,6 +193,7 @@ public: // Aha, variable is constant; substitute in. // We'll later constant propagate UINFO(4, " replaceconst: " << varrefp); + UINFO(9, " replaceval: " << constp); varrefp->replaceWith(constp->cloneTree(false)); m_replacedVref = true; VL_DO_DANGLING(varrefp->deleteTree(), varrefp); @@ -264,7 +279,9 @@ class LifeVisitor final : public VNVisitor { LifeBlock* m_lifep = nullptr; // Current active lifetime map for current scope // METHODS - void setNoopt() { + void setNoopt(const char* reasonp) { + (void)reasonp; + // UINFO(9, "setNoopt " << reasonp); m_noopt = true; m_lifep->clear(); } @@ -310,7 +327,7 @@ class LifeVisitor final : public VNVisitor { void visit(AstNodeAssign* nodep) override { if (nodep->isTimingControl() || VN_IS(nodep, AssignForce)) { // V3Life doesn't understand time sense nor force assigns - don't optimize - setNoopt(); + setNoopt("timing|force"); if (nodep->isTimingControl()) m_containsTiming = true; iterateChildren(nodep); return; @@ -325,7 +342,7 @@ class LifeVisitor final : public VNVisitor { // V3Life doesn't understand time sense if (nodep->isTimingControl()) { // Don't optimize - setNoopt(); + setNoopt("assigndly"); m_containsTiming = true; } // Don't treat as normal assign @@ -337,19 +354,19 @@ class LifeVisitor final : public VNVisitor { UINFO(4, " IF " << nodep); // Condition is part of PREVIOUS block iterateAndNextNull(nodep->condp()); - LifeBlock* const prevLifep = m_lifep; - LifeBlock* const ifLifep = new LifeBlock{prevLifep, m_statep}; - LifeBlock* const elseLifep = new LifeBlock{prevLifep, m_statep}; + LifeBlock* const ifLifep = new LifeBlock{m_lifep, m_statep}; + LifeBlock* const elseLifep = new LifeBlock{m_lifep, m_statep}; { + VL_RESTORER(m_lifep); m_lifep = ifLifep; iterateAndNextNull(nodep->thensp()); } { + VL_RESTORER(m_lifep); m_lifep = elseLifep; iterateAndNextNull(nodep->elsesp()); } - m_lifep = prevLifep; - UINFO(4, " join "); + UINFO(4, " join " << nodep); // Find sets on both flows m_lifep->dualBranch(ifLifep, elseLifep); // For the next assignments, clear any variables that were read or written in the block @@ -357,6 +374,7 @@ class LifeVisitor final : public VNVisitor { elseLifep->lifeToAbove(); VL_DO_DANGLING(delete ifLifep, ifLifep); VL_DO_DANGLING(delete elseLifep, elseLifep); + UINFO(4, " if-done " << nodep); } void visit(AstLoop* nodep) override { // Similar problem to AstJumpBlock, don't optimize loop bodies - most are unrolled @@ -366,14 +384,14 @@ class LifeVisitor final : public VNVisitor { VL_RESTORER(m_noopt); VL_RESTORER(m_lifep); m_lifep = new LifeBlock{m_lifep, m_statep}; - setNoopt(); + setNoopt("loop"); iterateAndNextNull(nodep->stmtsp()); UINFO(4, " joinloop"); // For the next assignments, clear any variables that were read or written in the block m_lifep->lifeToAbove(); VL_DO_DANGLING(delete m_lifep, m_lifep); } - if (m_containsTiming) setNoopt(); + if (m_containsTiming) setNoopt("timing"); } void visit(AstJumpBlock* nodep) override { // As with Loop's we can't predict if a JumpGo will kill us or not @@ -384,14 +402,14 @@ class LifeVisitor final : public VNVisitor { VL_RESTORER(m_noopt); VL_RESTORER(m_lifep); m_lifep = new LifeBlock{m_lifep, m_statep}; - setNoopt(); + setNoopt("jumpblock"); iterateAndNextNull(nodep->stmtsp()); UINFO(4, " joinjump"); // For the next assignments, clear any variables that were read or written in the block m_lifep->lifeToAbove(); VL_DO_DANGLING(delete m_lifep, m_lifep); } - if (m_containsTiming) setNoopt(); + if (m_containsTiming) setNoopt("timing"); } void visit(AstNodeCCall* nodep) override { // UINFO(4, " CCALL " << nodep); @@ -399,7 +417,7 @@ class LifeVisitor final : public VNVisitor { // Enter the function and trace it // else is non-inline or public function we optimize separately if (nodep->funcp()->entryPoint()) { - setNoopt(); + setNoopt("ccall"); } else { m_tracingCall = true; iterate(nodep->funcp()); @@ -409,8 +427,8 @@ class LifeVisitor final : public VNVisitor { // UINFO(4, " CFUNC " << nodep); if (!m_tracingCall && !nodep->entryPoint()) return; m_tracingCall = false; - if (nodep->recursive()) setNoopt(); - if (nodep->noLife()) setNoopt(); + if (nodep->recursive()) setNoopt("recursive"); + if (nodep->noLife()) setNoopt("nolife"); if (nodep->dpiImportPrototype() && !nodep->dpiPure()) { m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment } @@ -429,7 +447,7 @@ class LifeVisitor final : public VNVisitor { void visit(AstNode* nodep) override { if (nodep->isTimingControl()) { // V3Life doesn't understand time sense - don't optimize - setNoopt(); + setNoopt("timing"); m_containsTiming = true; } iterateChildren(nodep); diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp index 4393a6955..6c4c637a0 100644 --- a/src/V3LinkCells.cpp +++ b/src/V3LinkCells.cpp @@ -157,11 +157,11 @@ class LinkConfigsVisitor final : public VNVisitor { m_isDefault = true; iterateAndNextNull(nodep->usep()); } else if (nodep->isCell()) { - VL_RESTORER(m_cell); + VL_RESTORER_CLEAR(m_cell); m_cell = nodep->cellp()->name(); iterateAndNextNull(nodep->usep()); } else { - VL_RESTORER(m_hierInst); + VL_RESTORER_COPY(m_hierInst); { VL_RESTORER(m_dotp); m_dotp = VN_AS(nodep->cellp(), Dot); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index a04b7bbdf..0bb78f8e8 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1013,18 +1013,23 @@ public: if (checkUnresolvedRef(VN_CAST(dtypep, RefDType))) return true; } else if (const AstParamTypeDType* const paramTypep = VN_CAST(symp->nodep(), ParamTypeDType)) { - // ParamTypeDType child may be wrapped in RequireDType or unwrapped + // Before V3Param the declared default is in childDTypep (possibly + // wrapped in a RequireDType); after V3Param it is consumed and the + // bound type is the resolved data type, e.g. a type parameter + // inherited from a specialized base class (REQ #(Item) -> class Item). AstNode* childp = paramTypep->childDTypep(); if (const AstRequireDType* const reqp = VN_CAST(childp, RequireDType)) { childp = reqp->lhsp(); } - if (isValidTypeNode(childp)) return true; - if (checkUnresolvedRef(VN_CAST(childp, RefDType))) return true; + const AstNode* const checkp = childp ? childp : paramTypep->skipRefp(); + if (isValidTypeNode(checkp)) return true; + if (checkUnresolvedRef(VN_CAST(checkp, RefDType))) return true; } return false; } VSymEnt* resolveClassOrPackage(VSymEnt* lookSymp, AstClassOrPackageRef* nodep, bool fallback, - bool classOnly, const string& forWhat) { + bool classOnly, const string& forWhat, + bool deferIfUnresolved = false) { if (nodep->classOrPackageSkipp()) return getNodeSym(nodep->classOrPackageSkipp()); VSymEnt* foundp; VSymEnt* searchSymp = lookSymp; @@ -1050,6 +1055,7 @@ public: nodep->classOrPackageNodep(foundp->nodep()); return foundp; } + if (deferIfUnresolved) return nullptr; const string suggest = suggestSymFallback(lookSymp, nodep->name(), LinkNodeMatcherClassOrPackage{}); nodep->v3error((classOnly ? "Class" : "Package/class") @@ -1253,7 +1259,7 @@ class LinkDotFindVisitor final : public VNVisitor { const bool standalonePkg = !m_modSymp && (m_statep->forPrearray() && VN_IS(nodep, Package)); const bool doit = (m_modSymp || standalonePkg); - VL_RESTORER(m_scope); + VL_RESTORER_COPY(m_scope); VL_RESTORER(m_classOrPackagep); VL_RESTORER(m_modSymp); VL_RESTORER(m_curSymp); @@ -1329,7 +1335,7 @@ class LinkDotFindVisitor final : public VNVisitor { void visit(AstClass* nodep) override { // FindVisitor:: UASSERT_OBJ(m_curSymp, nodep, "Class not under module/package/$unit"); UINFO(8, " " << nodep); - VL_RESTORER(m_scope); + VL_RESTORER_COPY(m_scope); VL_RESTORER(m_classOrPackagep); VL_RESTORER(m_modSymp); VL_RESTORER(m_curSymp); @@ -1378,7 +1384,7 @@ class LinkDotFindVisitor final : public VNVisitor { if (nodep->recursive() && m_inRecursion) return; iterateChildren(nodep); // Recurse in, preserving state - VL_RESTORER(m_scope); + VL_RESTORER_COPY(m_scope); VL_RESTORER(m_modSymp); VL_RESTORER(m_curSymp); VL_RESTORER(m_paramNum); @@ -1727,7 +1733,6 @@ class LinkDotFindVisitor final : public VNVisitor { newvarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); newvarp->funcReturn(true); newvarp->trace(false); // Not user visible - newvarp->attrIsolateAssign(nodep->attrIsolateAssign()); nodep->fvarp(newvarp); // Explicit insert required, as the var name shadows the upper level's task name m_statep->insertSym(m_curSymp, newvarp->name(), newvarp, nullptr /*classOrPackagep*/); @@ -1994,6 +1999,17 @@ class LinkDotFindVisitor final : public VNVisitor { // No need to insert, only the real typedef matters, but need to track for errors nodep->user1p(m_curSymp); } + void visit(AstNodeUOrStructDType* nodep) override { // FindVisitor:: + UASSERT_OBJ(m_curSymp, nodep, "Struct/union dtype not under module/package/$unit"); + VL_RESTORER(m_curSymp); + m_curSymp = m_statep->insertBlock(m_curSymp, "__Vdtype" + cvtToStr(nodep->uniqueNum()), + nodep, m_classOrPackagep); + iterateChildren(nodep); + } + void visit(AstMemberDType* nodep) override { // FindVisitor:: + iterateChildren(nodep); + m_statep->insertSym(m_curSymp, nodep->name(), nodep, m_classOrPackagep); + } void visit(AstParamTypeDType* nodep) override { // FindVisitor:: UASSERT_OBJ(m_curSymp, nodep, "Parameter type not under module/package/$unit"); @@ -3512,6 +3528,20 @@ class LinkDotResolveVisitor final : public VNVisitor { << declp->warnContextSecondary()); } } + void checkMemberDeclOrder(AstNode* nodep, AstMemberDType* declp) { + const uint32_t declTokenNum = declp->fileline()->tokenNum(); + if (nodep->fileline()->tokenNum() < declTokenNum) { + UINFO(1, "Related node " << nodep->fileline()->tokenNum() << " " << nodep); + UINFO(1, "Related decl " << declTokenNum << " " << declp); + nodep->v3error("Reference to " + << nodep->prettyNameQ() << " before declaration (IEEE 1800-2023 6.18)\n" + << nodep->warnMore() + << "... Suggest move the declaration before the reference\n" + << nodep->warnContextPrimary() << '\n' + << declp->warnOther() << "... Location of original declaration\n" + << declp->warnContextSecondary()); + } + } void replaceWithCheckBreak(AstNode* oldp, AstNodeDType* newp) { // Flag now to avoid V3Broken throwing an internal error @@ -3537,9 +3567,24 @@ class LinkDotResolveVisitor final : public VNVisitor { if (nodep && nodep->isParam()) nodep->usedParam(true); } + static VSymEnt* findIdFallbackSkipMemberDType(VSymEnt* lookp, const string& name) { + VSymEnt* shadowEntp = nullptr; // Shadowing variable: not a type, kept for error report + while (lookp) { + VSymEnt* const foundp = lookp->findIdFlat(name); + if (foundp && !VN_IS(foundp->nodep(), MemberDType)) { + // A variable is not a type candidate (IEEE 1800-2023 6.18); skip it so an + // enclosing type is found, but keep it to preserve the "found: VAR" error. + if (!VN_IS(foundp->nodep(), Var)) return foundp; + if (!shadowEntp) shadowEntp = foundp; + } + lookp = lookp->fallbackp(); + } + return shadowEntp; + } + void symIterateChildren(AstNode* nodep, VSymEnt* symp) { // Iterate children, changing to given context, with restore to old context - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); VL_RESTORER(m_curSymp); m_curSymp = symp; m_ds.init(m_curSymp); @@ -3547,7 +3592,7 @@ class LinkDotResolveVisitor final : public VNVisitor { } void symIterateNull(AstNode* nodep, VSymEnt* symp) { // Iterate node, changing to given context, with restore to old context - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); VL_RESTORER(m_curSymp); m_curSymp = symp; m_ds.init(m_curSymp); @@ -3760,10 +3805,8 @@ class LinkDotResolveVisitor final : public VNVisitor { LINKDOT_VISIT_START(); UINFO(5, indent() << "visit " << nodep); checkNoDot(nodep); - VL_RESTORER(m_usedPins); - VL_RESTORER(m_usedDefParamPins); - m_usedPins.clear(); - m_usedDefParamPins.clear(); + VL_RESTORER_CLEAR(m_usedPins); + VL_RESTORER_CLEAR(m_usedDefParamPins); UASSERT_OBJ(nodep->modp(), nodep, "Instance has unlinked module"); // V3LinkCell should have errored out VL_RESTORER(m_cellp); @@ -3800,10 +3843,8 @@ class LinkDotResolveVisitor final : public VNVisitor { LINKDOT_VISIT_START(); UINFO(5, indent() << "visit " << nodep); // Can be under dot if called as package::class and that class resolves, so no checkNoDot - VL_RESTORER(m_usedPins); - VL_RESTORER(m_usedDefParamPins); - m_usedPins.clear(); - m_usedDefParamPins.clear(); + VL_RESTORER_CLEAR(m_usedPins); + VL_RESTORER_CLEAR(m_usedDefParamPins); UASSERT_OBJ(nodep->classp(), nodep, "ClassRef has unlinked class"); UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp() || V3Error::errorCount(), nodep, "class reference parameter not removed by V3Param"); @@ -4533,6 +4574,16 @@ class LinkDotResolveVisitor final : public VNVisitor { } else { (void)defp; // Prevent unused variable warning } + } else if (AstMemberDType* const defp = VN_CAST(foundp->nodep(), MemberDType)) { + if (allowVar) { + checkMemberDeclOrder(nodep, defp); + AstRefDType* const refp = new AstRefDType{nodep->fileline(), nodep->name()}; + refp->refDTypep(defp); + replaceWithCheckBreak(nodep, refp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + ok = true; + m_ds.m_dotText = ""; + } } else if (AstEnumItem* const valuep = VN_CAST(foundp->nodep(), EnumItem)) { if (allowVar) { AstNode* const newp @@ -4698,19 +4749,18 @@ class LinkDotResolveVisitor final : public VNVisitor { LINKDOT_VISIT_START(); UINFO(8, indent() << "visit " << nodep); UINFO(9, indent() << m_ds.ascii()); - VL_RESTORER(m_usedPins); - VL_RESTORER(m_usedDefParamPins); - m_usedPins.clear(); - m_usedDefParamPins.clear(); + VL_RESTORER_CLEAR(m_usedPins); + VL_RESTORER_CLEAR(m_usedDefParamPins); UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp(), nodep, "class reference parameter not removed by V3Param"); { - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); VL_RESTORER(m_pinSymp); if (!nodep->classOrPackageSkipp() && nodep->name() != "local::") { + const bool deferIfUnresolved = m_statep->forPrimary() && m_insideClassExtParam; m_statep->resolveClassOrPackage(m_ds.m_dotSymp, nodep, m_ds.m_dotPos != DP_PACKAGE, - false, ":: reference"); + false, ":: reference", deferIfUnresolved); } // ClassRef's have pins, so track @@ -4778,7 +4828,7 @@ class LinkDotResolveVisitor final : public VNVisitor { } } VL_RESTORER(m_curSymp); - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep); iterateChildren(nodep); } @@ -4968,6 +5018,10 @@ class LinkDotResolveVisitor final : public VNVisitor { refdtypep->v3error("Self-referential enumerated type definition"); } } + void visit(AstNodeUOrStructDType* nodep) override { + LINKDOT_VISIT_START(); + symIterateChildren(nodep, m_statep->getNodeSym(nodep)); + } void visit(AstEnumItemRef* nodep) override { // Resolve its reference // EnumItemRefs are created by the first pass, but V3Param may regenerate due to @@ -4994,7 +5048,7 @@ class LinkDotResolveVisitor final : public VNVisitor { // Created here so should already be resolved. LINKDOT_VISIT_START(); UINFO(5, indent() << "visit " << nodep); - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); VL_RESTORER(m_randSymp); VL_RESTORER(m_randMethodCallp); { @@ -5449,7 +5503,7 @@ class LinkDotResolveVisitor final : public VNVisitor { checkNoDot(nodep); { VL_RESTORER(m_curSymp); - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); if (nodep->name() != "") { m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep); UINFO(5, indent() << "cur=se" << cvtToHex(m_curSymp)); @@ -5464,7 +5518,7 @@ class LinkDotResolveVisitor final : public VNVisitor { checkNoDot(nodep); { VL_RESTORER(m_curSymp); - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); if (nodep->name() != "") { m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep); UINFO(5, indent() << "cur=se" << cvtToHex(m_curSymp)); @@ -5583,7 +5637,7 @@ class LinkDotResolveVisitor final : public VNVisitor { checkNoDot(nodep); VL_RESTORER(m_curSymp); VL_RESTORER(m_currentWithp); - VL_RESTORER(m_restrictedNamesUsed); + VL_RESTORER_COPY(m_restrictedNamesUsed); { m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep); m_currentWithp = nodep; @@ -5753,7 +5807,7 @@ class LinkDotResolveVisitor final : public VNVisitor { VL_RESTORER(m_curSymp); VL_RESTORER(m_modSymp); VL_RESTORER(m_modp); - VL_RESTORER(m_ifClassImpNames); + VL_RESTORER_COPY(m_ifClassImpNames); VL_RESTORER(m_insideClassExtParam); { m_ds.init(m_curSymp); @@ -5985,7 +6039,7 @@ class LinkDotResolveVisitor final : public VNVisitor { if (nodep->classOrPackagep()) { foundp = m_statep->getNodeSym(nodep->classOrPackagep())->findIdFlat(nodep->name()); } else if (m_ds.m_dotPos == DP_FIRST || m_ds.m_dotPos == DP_NONE) { - foundp = m_curSymp->findIdFallback(nodep->name()); + foundp = findIdFallbackSkipMemberDType(m_curSymp, nodep->name()); } else { // Defensive: dotPos should be DP_FIRST/DP_NONE or classOrPackagep set. v3fatalSrc("Unexpected dotPos=" @@ -6109,7 +6163,7 @@ class LinkDotResolveVisitor final : public VNVisitor { void visit(AstDisable* nodep) override { LINKDOT_VISIT_START(); checkNoDot(nodep); - VL_RESTORER(m_ds); + VL_RESTORER_COPY(m_ds); m_ds.init(m_curSymp); m_ds.m_dotPos = DP_FIRST; m_ds.m_disablep = nodep; @@ -6189,10 +6243,8 @@ class LinkDotResolveVisitor final : public VNVisitor { UASSERT_OBJ(ifacep, nodep, "Port parameters of AstIfaceRefDType without ifacep()"); if (ifacep->dead()) return; checkNoDot(nodep); - VL_RESTORER(m_usedPins); - VL_RESTORER(m_usedDefParamPins); - m_usedPins.clear(); - m_usedDefParamPins.clear(); + VL_RESTORER_CLEAR(m_usedPins); + VL_RESTORER_CLEAR(m_usedDefParamPins); VL_RESTORER(m_pinSymp); m_pinSymp = m_statep->getNodeSym(ifacep); iterateAndNextNull(nodep->paramsp()); diff --git a/src/V3LinkInc.cpp b/src/V3LinkInc.cpp index f8e3c6138..e473325cd 100644 --- a/src/V3LinkInc.cpp +++ b/src/V3LinkInc.cpp @@ -307,6 +307,7 @@ class LinkIncVisitor final : public VNVisitor { return new AstSub{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::Xor: return new AstXor{nodep->fileline(), lhsp, rhsp}; + default:; // Error below // LCOV_EXCL_LINE } } nodep->v3fatalSrc("Unhandled compound assignment operation"); diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 21e3cdad9..70115033f 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -262,6 +262,9 @@ class LinkJumpVisitor final : public VNVisitor { if (it != m_beginDisableBegins.end()) return it->second; AstBegin* const beginBodyp = new AstBegin{fl, "", nullptr, false}; + // Disable-by-name rewrites kill this detached block-body process, so mark it as process + // backed to ensure fork/join kill-accounting hooks are always emitted. + beginBodyp->setNeedProcess(); if (beginp->stmtsp()) beginBodyp->addStmtsp(beginp->stmtsp()->unlinkFrBackWithNext()); AstFork* const forkp = new AstFork{fl, VJoinType::JOIN}; diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp index 012c87166..066097239 100644 --- a/src/V3LinkLevel.cpp +++ b/src/V3LinkLevel.cpp @@ -291,6 +291,7 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { varp->sigPublic(true); // User needs to be able to get to it... oldvarp->primaryIO(false); varp->primaryIO(true); + varp->icoMaybeWritten(oldvarp->icoMaybeWritten()); if (varp->isRef() || varp->isConstRef()) { varp->v3warn(E_UNSUPPORTED, "Unsupported: ref/const ref as primary input/output: " diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index e6beb5760..3e3565842 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -273,7 +273,7 @@ class LinkParseVisitor final : public VNVisitor { << nodep->verilogKwd() << "'"); } - VL_RESTORER(m_portDups); + VL_RESTORER_COPY(m_portDups); collectPorts(nodep->stmtsp()); iterateChildren(nodep); @@ -606,10 +606,6 @@ class LinkParseVisitor final : public VNVisitor { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->sigUserRWPublic(true); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } else if (nodep->attrType() == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { - UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); - m_varp->attrIsolateAssign(true); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (nodep->attrType() == VAttrType::VAR_SFORMAT) { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->attrSFormat(true); @@ -767,12 +763,12 @@ class LinkParseVisitor final : public VNVisitor { VL_RESTORER(m_genblkAbove); VL_RESTORER(m_genblkNum); VL_RESTORER(m_beginDepth); - VL_RESTORER(m_implTypedef); VL_RESTORER(m_lifetime); VL_RESTORER(m_lifetimeAllowed); VL_RESTORER(m_moduleWithGenericIface); VL_RESTORER(m_randSequenceNum); VL_RESTORER(m_valueModp); + VL_RESTORER_CLEAR(m_implTypedef); // Module: Create sim table for entire module and iterate cleanFileline(nodep); @@ -784,7 +780,6 @@ class LinkParseVisitor final : public VNVisitor { m_genblkAbove = 0; m_genblkNum = 0; m_beginDepth = 0; - m_implTypedef.clear(); m_valueModp = nodep; m_lifetime = nodep->lifetime().makeImplicit(); m_lifetimeAllowed = VN_IS(nodep, Class); @@ -801,7 +796,7 @@ class LinkParseVisitor final : public VNVisitor { "Verilator top-level internals"); } - VL_RESTORER(m_portDups); + VL_RESTORER_COPY(m_portDups); collectPorts(nodep->stmtsp()); iterateChildren(nodep); diff --git a/src/V3LinkWith.cpp b/src/V3LinkWith.cpp index 8ea84a3f4..f93d8d589 100644 --- a/src/V3LinkWith.cpp +++ b/src/V3LinkWith.cpp @@ -32,7 +32,7 @@ class LinkWithVisitor final : public VNVisitor { // STATE // Below state needs to be preserved between each module call. - string m_randcIllegalWhy; // Why randc illegal + const char* m_randcIllegalWhyp = nullptr; // Why randc illegal AstNode* m_randcIllegalp = nullptr; // Node causing randc illegal AstNodeExpr* m_currentRandomizeSelectp = nullptr; // fromp() of current `randomize()` call bool m_inRandomizeWith = false; // If in randomize() with (and no other with afterwards) @@ -49,24 +49,24 @@ class LinkWithVisitor final : public VNVisitor { iterateChildren(nodep); } void visit(AstConstraintBefore* nodep) override { - VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalWhyp); VL_RESTORER(m_randcIllegalp); - m_randcIllegalWhy = "'solve before' (IEEE 1800-2023 18.5.9)"; + m_randcIllegalWhyp = "'solve before' (IEEE 1800-2023 18.5.9)"; m_randcIllegalp = nodep; iterateChildrenConst(nodep); } void visit(AstDist* nodep) override { - VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalWhyp); VL_RESTORER(m_randcIllegalp); - m_randcIllegalWhy = "'constraint dist' (IEEE 1800-2023 18.5.3)"; + m_randcIllegalWhyp = "'constraint dist' (IEEE 1800-2023 18.5.3)"; m_randcIllegalp = nodep; iterateChildrenConst(nodep); } void visit(AstConstraintExpr* nodep) override { - VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalWhyp); VL_RESTORER(m_randcIllegalp); if (nodep->isSoft()) { - m_randcIllegalWhy = "'constraint soft' (IEEE 1800-2023 18.5.13.1)"; + m_randcIllegalWhyp = "'constraint soft' (IEEE 1800-2023 18.5.13.1)"; m_randcIllegalp = nodep; } iterateChildrenConst(nodep); @@ -76,7 +76,7 @@ class LinkWithVisitor final : public VNVisitor { if (nodep->varp()) { // Else due to dead code, might not have var pointer if (nodep->varp()->isRandC() && m_randcIllegalp) { nodep->v3error("Randc variables not allowed in " - << m_randcIllegalWhy << '\n' + << m_randcIllegalWhyp << '\n' << nodep->warnContextPrimary() << '\n' << m_randcIllegalp->warnOther() << "... Location of restricting expression\n" diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 8adf7ce78..d97e4ed50 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -1308,9 +1308,17 @@ uint32_t V3Number::mostSetBitP1() const { } return 0; } + +uint32_t V3Number::leastSetBitP1() const { + for (int bit = 0; bit < width(); ++bit) { + if (!bitIs0(bit)) return bit + 1; + } + return 0; +} + //====================================================================== -V3Number& V3Number::opBitsNonX(const V3Number& lhs) { // 0/1->1, X/Z->0 +V3Number& V3Number::opBitsNonXZ(const V3Number& lhs) { // 0/1->1, X/Z->0 // Correct number of zero bits/width matters // op i, L(lhs) bit return NUM_ASSERT_OP_ARGS1(lhs); @@ -1456,6 +1464,20 @@ V3Number& V3Number::opCLog2(const V3Number& lhs) { setZero(); return *this; } +V3Number& V3Number::opMostSetBitP1(const V3Number& lhs) { + // Most-significant set bit plus one (bit-width / find-last-set); 0 if value is zero + NUM_ASSERT_OP_ARGS1(lhs); + NUM_ASSERT_LOGIC_ARGS1(lhs); + if (lhs.isFourState()) return setAllBitsX(); + for (int bit = lhs.width() - 1; bit >= 0; bit--) { + if (lhs.bitIs1(bit)) { + setLong(bit + 1); + return *this; + } + } + setZero(); + return *this; +} V3Number& V3Number::opLogNot(const V3Number& lhs) { NUM_ASSERT_OP_ARGS1(lhs); diff --git a/src/V3Number.h b/src/V3Number.h index 771f5edd4..d81e32d72 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -728,6 +728,7 @@ public: uint32_t countBits(const V3Number& ctrl1, const V3Number& ctrl2, const V3Number& ctrl3) const; uint32_t countOnes() const; uint32_t mostSetBitP1() const; // Highest bit set + 1, e.g. for 16 return 5, for 0 return 0 + uint32_t leastSetBitP1() const; // Lowest bit set + 1, e.g. for 14 return 2, for 0 return 0 // Operators bool operator<(const V3Number& rhs) const { return isLtXZ(rhs); } @@ -741,7 +742,7 @@ public: // MATH // "this" is the output, as we need the output width before some computations - V3Number& opBitsNonX(const V3Number& lhs); // 0/1->1, X/Z->0 + V3Number& opBitsNonXZ(const V3Number& lhs); // 0/1->1, X/Z->0 V3Number& opBitsOne(const V3Number& lhs); // 1->1, 0/X/Z->0 V3Number& opBitsXZ(const V3Number& lhs); // 0/1->0, X/Z->1 V3Number& opBitsZ(const V3Number& lhs); // Z->1, 0/1/X->0 @@ -760,6 +761,7 @@ public: V3Number& opOneHot(const V3Number& lhs); V3Number& opOneHot0(const V3Number& lhs); V3Number& opCLog2(const V3Number& lhs); + V3Number& opMostSetBitP1(const V3Number& lhs); V3Number& opClean(const V3Number& lhs, uint32_t bits); V3Number& opConcat(const V3Number& lhs, const V3Number& rhs); V3Number& opLenN(const V3Number& lhs); diff --git a/src/V3Options.cpp b/src/V3Options.cpp index b8d7a0a29..f9502e00c 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -542,7 +542,7 @@ bool V3Options::fileStatNormal(const string& filename) { } string V3Options::fileExists(const string& filename) { - // Surprisingly, for VCS and other simulators, this process + // Surprisingly, for some other simulators, this process // is quite slow; presumably because of re-reading each directory // many times. So we read a whole dir at once and cache it @@ -1077,6 +1077,9 @@ void V3Options::notify() VL_MT_DISABLED { // Preprocessor defines based on options used if (timing().isSetTrue()) V3PreShell::defineCmdLine("VERILATOR_TIMING", "1"); + // If VPI is used, and no explicit ico change detect option was passed, disable it by default + if (m_vpi && m_fIcoChangeDetect.isDefault()) m_fIcoChangeDetect.setTrueOrFalse(false); + // === Leave last // Mark options as available m_available = true; @@ -1445,7 +1448,15 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-facyc-simp", FOnOff, &m_fAcycSimp); DECL_OPTION("-fassemble", FOnOff, &m_fAssemble); - DECL_OPTION("-fcase", FOnOff, &m_fCase); + DECL_OPTION("-fbit-scan-loops", FOnOff, &m_fBitScanLoops); + DECL_OPTION("-fcase", CbFOnOff, [this](bool flag) { + m_fCaseDecoder = flag; + m_fCaseTable = flag; + m_fCaseTree = flag; + }); + DECL_OPTION("-fcase-decoder", FOnOff, &m_fCaseDecoder); + DECL_OPTION("-fcase-table", FOnOff, &m_fCaseTable); + DECL_OPTION("-fcase-tree", FOnOff, &m_fCaseTree); DECL_OPTION("-fcombine", FOnOff, &m_fCombine); DECL_OPTION("-fconst", FOnOff, &m_fConst); DECL_OPTION("-fconst-before-dfg", FOnOff, &m_fConstBeforeDfg); @@ -1483,7 +1494,11 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-ffunc-opt-balance-cat", FOnOff, &m_fFuncBalanceCat); DECL_OPTION("-ffunc-opt-split-cat", FOnOff, &m_fFuncSplitCat); DECL_OPTION("-fgate", FOnOff, &m_fGate); + DECL_OPTION("-fico-change-detect", CbFOnOff, [this](bool flag) { // + m_fIcoChangeDetect.setTrueOrFalse(flag); + }); DECL_OPTION("-finline", FOnOff, &m_fInline); + DECL_OPTION("-finline-cfuncs", FOnOff, &m_fInlineCFuncs); DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs); DECL_OPTION("-finline-funcs-eager", FOnOff, &m_fInlineFuncsEager); DECL_OPTION("-flife", FOnOff, &m_fLife); @@ -2345,7 +2360,10 @@ void V3Options::optimize(int level) { const bool flag = level > 0; m_fAcycSimp = flag; m_fAssemble = flag; - m_fCase = flag; + m_fBitScanLoops = flag; + m_fCaseDecoder = flag; + m_fCaseTable = flag; + m_fCaseTree = flag; m_fCombine = flag; m_fConst = flag; m_fConstBitOpTree = flag; @@ -2356,6 +2374,7 @@ void V3Options::optimize(int level) { m_fExpand = flag; m_fGate = flag; m_fInline = flag; + m_fInlineCFuncs = flag; m_fLife = flag; m_fLifePost = flag; m_fLocalize = flag; diff --git a/src/V3Options.h b/src/V3Options.h index 729c68f47..182379fe9 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -392,7 +392,10 @@ private: // MEMBERS (optimizations) bool m_fAcycSimp; // main switch: -fno-acyc-simp: acyclic pre-optimizations bool m_fAssemble; // main switch: -fno-assemble: assign assemble - bool m_fCase; // main switch: -fno-case: case tree conversion + bool m_fBitScanLoops; // main switch: -fno-bit-scan-loops: convert bit scan loops to builtins + bool m_fCaseDecoder; // main switch: -fno-case-decoder: case decoder conversion + bool m_fCaseTable; // main switch: -fno-case-table: case table conversion + bool m_fCaseTree; // main switch: -fno-case-tree: case tree conversion bool m_fCombine; // main switch: -fno-combine: common icode packing bool m_fConst; // main switch: -fno-const: constant folding bool m_fConstBeforeDfg = true; // main switch: -fno-const-before-dfg for testing only! @@ -410,7 +413,10 @@ private: bool m_fFuncBalanceCat = true; // main switch: -fno-func-balance-cat: expansion of C macros bool m_fFuncSplitCat = true; // main switch: -fno-func-split-cat: expansion of C macros bool m_fGate; // main switch: -fno-gate: gate wire elimination + // main switch: -fno-ico-change-detect: input change detection optimization + VOptionBool m_fIcoChangeDetect{VOptionBool::OPT_DEFAULT_TRUE}; bool m_fInline; // main switch: -fno-inline: module inlining + bool m_fInlineCFuncs; // main switch: -fno-inline-cfuncs: inline small C functions bool m_fInlineFuncs = true; // main switch: -fno-inline-funcs: function inlining bool m_fInlineFuncsEager = true; // main switch: -fno-inline-funcs-eager: don't inline eagerly bool m_fLife; // main switch: -fno-life: variable lifetime @@ -547,6 +553,9 @@ public: bool decorationNodes() const VL_MT_SAFE { return m_decorationNodes; } bool diagnosticsSarif() const VL_MT_SAFE { return m_diagnosticsSarif; } bool dpiHdrOnly() const { return m_dpiHdrOnly; } + bool dumpAstPatterns() const { + return m_dumpLevel.count("ast-patterns") && m_dumpLevel.at("ast-patterns"); + } bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); } bool dumpDfgPatterns() const { return m_dumpLevel.count("dfg-patterns") && m_dumpLevel.at("dfg-patterns"); @@ -723,7 +732,10 @@ public: // ACCESSORS (optimization options) bool fAcycSimp() const { return m_fAcycSimp; } bool fAssemble() const { return m_fAssemble; } - bool fCase() const { return m_fCase; } + bool fBitScanLoops() const { return m_fBitScanLoops; } + bool fCaseDecoder() const { return m_fCaseDecoder; } + bool fCaseTable() const { return m_fCaseTable; } + bool fCaseTree() const { return m_fCaseTree; } bool fCombine() const { return m_fCombine; } bool fConst() const { return m_fConst; } bool fConstBeforeDfg() const { return m_fConstBeforeDfg; } @@ -745,7 +757,9 @@ public: bool fFuncSplitCat() const { return m_fFuncSplitCat; } bool fFunc() const { return fFuncSplitCat() || fFuncBalanceCat(); } bool fGate() const { return m_fGate; } + VOptionBool fIcoChangeDetect() const { return m_fIcoChangeDetect; } bool fInline() const { return m_fInline; } + bool fInlineCFuncs() const { return m_fInlineCFuncs; } bool fInlineFuncs() const { return m_fInlineFuncs; } bool fInlineFuncsEager() const { return m_fInlineFuncsEager; } bool fLife() const { return m_fLife; } diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 063c55d3a..72c33a6e7 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1348,6 +1348,17 @@ class ParamProcessor final { } } + // True if a $bits/$size type query in nodep's parameters reads another type parameter. + static bool defaultParamsHaveTypeQueryOnParamType(const AstClassRefDType* nodep) { + bool found = false; + nodep->foreach([&](const AstAttrOf* attrp) { + if (found || !attrp->attrType().isTypeQuery()) return; + const AstRefDType* const refp = VN_CAST(attrp->fromp(), RefDType); + if (refp && VN_IS(refp->refDTypep(), ParamTypeDType)) found = true; + }); + return found; + } + // Check if exprp's class matches origp's class after deparameterization. // Handles both the simple case (user4p link from defaultsResolved) and the // nested case where the default's inner class has non-default sub-parameters @@ -1362,6 +1373,9 @@ class ParamProcessor final { const AstNodeModule* const defaultClonep = VN_CAST(origClassRefp->classp()->user4p(), Class); if (defaultClonep && defaultClonep == exprClassRefp->classp()) return true; + // Skip the comparison when the default's $bits/$size reads another type parameter, as + // deparameterizing it below would resolve that shared type at the wrong width (#7711). + if (defaultParamsHaveTypeQueryOnParamType(origClassRefp)) return false; // Slow path: deparameterize the default type and compare the result. // Different templates can never match; use origName() because exprp's // class may already be a specialization (clone) of the template. @@ -2557,11 +2571,9 @@ class ParamVisitor final : public VNVisitor { // Iterate the body { VL_RESTORER(m_modp); - VL_RESTORER(m_ifacePortNames); - VL_RESTORER(m_ifaceInstCells); + VL_RESTORER_CLEAR(m_ifacePortNames); + VL_RESTORER_CLEAR(m_ifaceInstCells); m_modp = modp; - m_ifacePortNames.clear(); - m_ifaceInstCells.clear(); iterateChildren(modp); } } @@ -3227,7 +3239,7 @@ class ParamVisitor final : public VNVisitor { // Note this clears nodep->genforp(), so begin is no longer special } } else { - VL_RESTORER(m_generateHierName); + VL_RESTORER_COPY(m_generateHierName); m_generateHierName += "." + nodep->prettyName(); iterateChildren(nodep); } diff --git a/src/V3PatternStats.cpp b/src/V3PatternStats.cpp new file mode 100644 index 000000000..8d4d5a63d --- /dev/null +++ b/src/V3PatternStats.cpp @@ -0,0 +1,129 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Dump data structure pattern frequencies for analysis +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "V3PchAstNoMT.h" + +#include "V3Ast.h" +#include "V3AstPatterns.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3File.h" + +#include +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +//============================================================================ +// Accumulates and dumps the pattern statistics + +class V3PatternStats VL_NOT_FINAL { +public: + static constexpr uint32_t MIN_PATTERN_DEPTH = 1; + static constexpr uint32_t MAX_PATTERN_DEPTH = 4; + +private: + const std::string m_what; // Description of what is being dumped + // Maps from pattern to the number of times it appears, for each pattern depth + std::vector> m_patternCounts{MAX_PATTERN_DEPTH + 1}; + + void dump(std::ostream& os) { + using Line = std::pair; + for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { + os << m_what << " patterns with depth " << i << '\n'; + + // Pick up pattern accumulators with given depth + auto& patternCounts = m_patternCounts[i]; + + // Remove patterns also present at shallower depths + for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) { + for (const auto& pair : m_patternCounts[j]) patternCounts.erase(pair.first); + } + + // Sort patterns, first by descending frequency, then lexically + std::vector lines; + lines.reserve(patternCounts.size()); + for (const auto& pair : patternCounts) lines.emplace_back(pair); + std::sort(lines.begin(), lines.end(), [](const Line& a, const Line& b) { + if (a.second != b.second) return a.second > b.second; + return a.first < b.first; + }); + + // Print each pattern + for (const auto& line : lines) { + os << ' ' << std::setw(12) << std::right << line.second; + os << ' ' << std::left << line.first << '\n'; + } + + // Trailing new-line to separate sections + os << '\n'; + } + } + +public: + V3PatternStats(const std::string& what) + : m_what{what} {} + ~V3PatternStats() = default; + + void accumulate(const std::string& pattern, uint32_t depth) { + m_patternCounts[depth][pattern] += 1; + } + + void dumpToFile(const std::string& filename) { + // Open, write, close + const std::unique_ptr ofp{V3File::new_ofstream(filename)}; + if (ofp->fail()) v3fatal("Can't write file: " << filename); + dump(*ofp); + } +}; + +//============================================================================ +// V3AstPatterns top level + +void V3AstPatterns::dumpAll(const AstNetlist* nodep, const std::string& suffix) { + UINFO(2, __FUNCTION__ << ":"); + V3PatternStats patternStats{"AST"}; + nodep->foreach([&](const AstNodeExpr* exprp) { + for (uint32_t i = V3PatternStats::MIN_PATTERN_DEPTH; + i <= V3PatternStats::MAX_PATTERN_DEPTH; ++i) { + patternStats.accumulate(exprp->patternString(i), i); + } + }); + const std::string fileName = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + + "__ast_patterns_" + suffix + ".txt"; + patternStats.dumpToFile(fileName); + V3Global::dumpCheckGlobalTree("astpatterns", 0, false, false); +} + +//============================================================================ +// V3DfgPasses top level + +void V3DfgPasses::dumpPatterns(const std::vector>& graphs, + const std::string& suffix) { + V3PatternStats patternStats{"DFG"}; + for (auto& cp : graphs) { + cp->forEachVertex([&](const DfgVertex& vtx) { + for (uint32_t i = V3PatternStats::MIN_PATTERN_DEPTH; + i <= V3PatternStats::MAX_PATTERN_DEPTH; ++i) { + patternStats.accumulate(vtx.patternString(i), i); + } + }); + } + const std::string fileName = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + + "__dfg_patterns" + suffix + ".txt"; + patternStats.dumpToFile(fileName); +} diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 50c23da38..c5a9fa9d1 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -139,34 +139,52 @@ class PremitVisitor final : public VNVisitor { } void visitShift(AstNodeBiop* nodep) { - // Shifts of > 32/64 bits in C++ will wrap-around and generate non-0s UINFO(4, " ShiftFix " << nodep); - const AstConst* const shiftp = VN_CAST(nodep->rhsp(), Const); - if (shiftp && shiftp->num().mostSetBitP1() > 32) { - shiftp->v3warn( - E_UNSUPPORTED, - "Unsupported: Shifting of by over 32-bit number isn't supported." - << " (This isn't a shift of 32 bits, but a shift of 2^32, or 4 billion!)\n"); - } - if (nodep->widthMin() <= 64 // Else we'll use large operators which work right - // C operator's width must be < maximum shift which is - // based on Verilog width - && nodep->width() < (1LL << nodep->rhsp()->widthMin())) { - AstNode* newp; - if (VN_IS(nodep, ShiftL)) { - newp = new AstShiftLOvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), - nodep->rhsp()->unlinkFrBack()}; - } else if (VN_IS(nodep, ShiftR)) { - newp = new AstShiftROvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), - nodep->rhsp()->unlinkFrBack()}; - } else { - UASSERT_OBJ(VN_IS(nodep, ShiftRS), nodep, "Bad case"); - newp = new AstShiftRSOvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), - nodep->rhsp()->unlinkFrBack()}; + UASSERT_OBJ(VN_IS(nodep, ShiftL) || VN_IS(nodep, ShiftR) || VN_IS(nodep, ShiftRS), nodep, + "Bad case"); + // Shift larger than the width of the type (overshift) is undefined behavour in C++ + // (in practice will shift by the wrapped shift amount). These are requierd to go to + // zero/msbs, so replacing them here. + FileLine* const flp = nodep->fileline(); + if (const AstConst* const shiftp = VN_CAST(nodep->rhsp(), Const)) { + // Shift amount known to be constant. If oversized shift, replace with zero/msbs. + // Otherwise we can leave the original shifts which have better constant folding + // than the *Ovr versions. + const bool isOversized = shiftp->num().mostSetBitP1() > 32 // + || (shiftp->num().toSQuad() >= nodep->width()); + if (isOversized) { + AstNodeExpr* newp = nullptr; + if (VN_IS(nodep, ShiftRS)) { + AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const msbp = new AstSel{flp, lhsp, nodep->width() - 1, 1}; + newp = new AstExtendS{flp, msbp, nodep->width()}; + } else { + newp = new AstConst{flp, AstConst::DTyped{}, nodep->dtypep()}; + } + nodep->replaceWithKeepDType(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } else { + // Shift amount not known at compile time. Convert to *Ovr version. Don't need to do + // if it would use a wide operation which works correctly at runtime, of if the max + // value of the shift amount is less than the with of the shifted value. + if (nodep->widthMin() <= VL_QUADSIZE + && (nodep->width() < (1LL << nodep->rhsp()->widthMin()))) { + AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); + AstNodeExpr* newp = nullptr; + if (VN_IS(nodep, ShiftL)) { + newp = new AstShiftLOvr{flp, lhsp, rhsp}; + } else if (VN_IS(nodep, ShiftR)) { + newp = new AstShiftROvr{flp, lhsp, rhsp}; + } else { + newp = new AstShiftRSOvr{flp, lhsp, rhsp}; + } + nodep->replaceWithKeepDType(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; } - nodep->replaceWithKeepDType(newp); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - return; } iterateChildren(nodep); checkNode(nodep); @@ -347,6 +365,14 @@ class PremitVisitor final : public VNVisitor { } checkNode(nodep); } + void visit(AstMatchMasked* nodep) override { + iterateChildren(nodep); + if (!nodep->user1SetOnce()) { + // Don't want this replicated by V3Expand + AstVar* const varp = createTemp(nodep); + varp->noSubst(true); // Do not re-inline in V3Subst + } + } void visit(AstCond* nodep) override { // Convert AstCond to AstIf in order to avoid evaluating // sub-expressions in both branches unconditionally. diff --git a/src/V3RandSequence.cpp b/src/V3RandSequence.cpp index f8f6b155c..b56215bac 100644 --- a/src/V3RandSequence.cpp +++ b/src/V3RandSequence.cpp @@ -136,7 +136,7 @@ class RandSequenceVisitor final : public VNVisitor { m_modp->addStmtsp(taskp); // Create local (not output) break variable - VL_RESTORER(m_localizeRemaps); + VL_RESTORER_COPY(m_localizeRemaps); AstVar* breakVarp = newBreakVar(nodep->fileline(), false); taskp->addStmtsp(breakVarp); @@ -417,9 +417,9 @@ class RandSequenceVisitor final : public VNVisitor { m_rsp = nodep; VL_RESTORER(m_startProdp); - VL_RESTORER(m_localizes); - VL_RESTORER(m_localizeNames); - VL_RESTORER(m_localizeRemaps); + VL_RESTORER_COPY(m_localizes); + VL_RESTORER_COPY(m_localizeNames); + VL_RESTORER_COPY(m_localizeRemaps); findLocalizes(nodep); // Find first production @@ -482,7 +482,7 @@ class RandSequenceVisitor final : public VNVisitor { // TODO we could do this only if break exists in the downstream production, // but as-is we'll optimize it away in most cases anyways VL_RESTORER(m_breakVarp); - VL_RESTORER(m_localizeRemaps); + VL_RESTORER_COPY(m_localizeRemaps); m_breakVarp = newBreakVar(nodep->fileline(), true); m_prodFuncp->addStmtsp(m_breakVarp); diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 3fcaf5535..2786ea305 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -1053,7 +1053,7 @@ class ConstraintExprVisitor final : public VNVisitor { nodep->user1(false); return; } - bool anyChild = false; + int anyChild = false; // Used as bool if (AstNodeExpr* const cp = VN_CAST(nodep->op1p(), NodeExpr)) { propagateUser1InlineRecurse(cp); anyChild |= cp->user1(); @@ -1171,16 +1171,18 @@ class ConstraintExprVisitor final : public VNVisitor { // else: Global constraints keep nodep alive for write_var processing relinker.relink(exprp); - // For global constraints: check shared path-level set - // For inline constraints: check per-instance set (each __Vrandwith has own randomizer) - // For class-level constraints: check varp->user3() + // Global / inline / class-level member-select refs key on the full path + // (so same-type sub-objects c1.x, c2.x stay distinct); a plain class-level + // variable keys on user3(). const bool alreadyWritten = isGlobalConstrained ? m_writtenVars.count(smtName) > 0 : m_inlineInitTaskp ? m_inlineWrittenVars.count(smtName) > 0 + : membersel ? m_writtenVars.count(smtName) > 0 : varp->user3(); const bool shouldWriteVar = !alreadyWritten; if (shouldWriteVar) { // Track this variable path as written - if (isGlobalConstrained) m_writtenVars.insert(smtName); + if (isGlobalConstrained || (membersel && !m_inlineInitTaskp)) + m_writtenVars.insert(smtName); if (m_inlineInitTaskp) m_inlineWrittenVars.insert(smtName); // For global constraints, delete nodep after processing if (isGlobalConstrained && !nodep->backp()) VL_DO_DANGLING(pushDeletep(nodep), nodep); @@ -1509,6 +1511,41 @@ class ConstraintExprVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); iterate(sump); } + void visit(AstRedXor* nodep) override { + if (editFormat(nodep)) return; + + // Build popcount expansion: (extract x 1 1) ^ (extract x 2 2) ^ ... + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); + + AstNodeExpr* redxorp = new AstSel{fl, argp, 0, 1}; + redxorp->user1(true); + for (int i = 1; i < argp->width(); i++) { + AstSel* const selp = new AstSel{fl, argp->cloneTreePure(false), i, 1}; + selp->user1(true); + + redxorp = new AstXor{fl, redxorp, selp}; + redxorp->user1(true); + } + + nodep->replaceWith(redxorp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(redxorp); + } + void visit(AstRedAnd* nodep) override { + if (editFormat(nodep)) return; + // Convert to (~x == 0) + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); + const V3Number numZero{fl, argp->width(), 0}; + AstNodeExpr* const negp = new AstNot{fl, argp}; + negp->user1(true); + AstNodeExpr* const eqp = new AstEq{fl, negp, new AstConst{fl, numZero}}; + eqp->user1(true); + nodep->replaceWith(eqp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(eqp); + } void visit(AstRedOr* nodep) override { if (editFormat(nodep)) return; // Convert to (x != 0) @@ -2109,10 +2146,15 @@ class ConstraintExprVisitor final : public VNVisitor { nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp}); } else { iterateAndNextNull(nodep->bodyp()); - nodep->replaceWith(new AstBegin{fl, "", - new AstForeach{fl, nodep->headerp()->unlinkFrBack(), - nodep->bodyp()->unlinkFrBackWithNext()}, - true}); + AstNode* bodyp = nodep->bodyp()->unlinkFrBackWithNext(); + // Prepend bucket preamble stmts stored by lowerDistConstraints (foreach case) + if (AstNode* const preamblep = nodep->user3p()) { + preamblep->addNext(bodyp); + bodyp = preamblep; + nodep->user3p(nullptr); + } + nodep->replaceWith(new AstBegin{ + fl, "", new AstForeach{fl, nodep->headerp()->unlinkFrBack(), bodyp}, true}); } VL_DO_DANGLING(nodep->deleteTree(), nodep); } @@ -3041,6 +3083,7 @@ class RandomizeVisitor final : public VNVisitor { // AstVar::user3() -> bool. Handled in constraints // AstClass::user3p() -> AstVar*. Constrained randomizer variable // AstConstraint::user3p() -> AstTask*. Pointer to resize procedure + // AstConstraintForeach::user3p() -> AstNode*. Dist bucket preamble stmts (foreach case) // AstClass::user4p() -> AstVar*. Constraint mode state variable // AstVar::user4p() -> AstVar*. Size variable for constrained queues // AstMemberSel::user2p() -> AstNodeModule*. Pointer to containing module @@ -3070,6 +3113,8 @@ class RandomizeVisitor final : public VNVisitor { std::map m_staticConstraintModeVars; // Static constraint mode vars per class std::map m_staticRandModeVars; // Static rand mode vars per class + std::map> + m_prePostWrap; // Per-handle-type pre/post virtual wrapper presence // METHODS // Check if two nodes are semantically equivalent (not pointer equality): @@ -3762,12 +3807,75 @@ class RandomizeVisitor final : public VNVisitor { } return nullptr; } - void addPrePostCall(AstClass* const classp, AstFunc* const funcp, const string& name) { + void addPrePostCall(AstClass* const classp, AstNodeFTask* const funcp, const string& name) { if (AstTask* const userFuncp = findPrePostTask(classp, name)) { AstTaskRef* const callp = new AstTaskRef{userFuncp->fileline(), userFuncp}; funcp->addStmtsp(callp->makeStmt()); } } + // Per-class virtual wrapper that invokes the class's effective + // pre_randomize/post_randomize. IEEE 1800-2023 18.6.2: pre_randomize and + // post_randomize "appear to behave as virtual methods" because randomize() + // is virtual. The inline `randomize() with` path builds a non-virtual + // function on the static handle type, so it dispatches pre/post through + // this wrapper to reach the dynamic type's override. + AstTask* getCreatePrePostCallback(AstClass* const classp, const string& which) { + const string name = "__V" + which; + if (AstTask* const existingp = VN_CAST(m_memberMap.findMember(classp, name), Task)) { + return existingp; + } + AstTask* const taskp = new AstTask{classp->fileline(), name, nullptr}; + taskp->classMethod(true); + taskp->isVirtual(classp->isExtended()); + classp->addMembersp(taskp); + m_memberMap.insert(classp, taskp); + addPrePostCall(classp, taskp, which); + return taskp; + } + // Build the virtual pre/post wrappers across classp's whole hierarchy so a + // `randomize() with` through a base handle dispatches to a derived + // override. Returns whether a pre/post wrapper exists anywhere in the + // hierarchy (cached per static handle type). + std::pair buildPrePostVirtualWrappers(AstClass* const classp) { + const auto cachedIt = m_prePostWrap.find(classp); + if (cachedIt != m_prePostWrap.end()) return cachedIt->second; + std::vector hierp{classp}; + v3Global.rootp()->foreach([&](AstClass* subp) { + if (subp != classp && AstClass::isClassExtendedFrom(subp, classp)) + hierp.push_back(subp); + }); + bool hasPre = false; + bool hasPost = false; + for (AstClass* const cp : hierp) { + if (findPrePostTask(cp, "pre_randomize")) { + getCreatePrePostCallback(cp, "pre_randomize"); + hasPre = true; + } + if (findPrePostTask(cp, "post_randomize")) { + getCreatePrePostCallback(cp, "post_randomize"); + hasPost = true; + } + } + // Ensure the static handle type owns the slot whenever a subclass + // overrides, so the virtual call resolves on a base handle. + if (hasPre) getCreatePrePostCallback(classp, "pre_randomize"); + if (hasPost) getCreatePrePostCallback(classp, "post_randomize"); + const std::pair result{hasPre, hasPost}; + m_prePostWrap.emplace(classp, result); + return result; + } + void addVirtualPrePostCall(AstFunc* const randomizeFuncp, AstClass* const classp, + const string& which) { + FileLine* const fl = classp->fileline(); + AstTask* const wrapperp = getCreatePrePostCallback(classp, which); + AstClassRefDType* const refDTypep = new AstClassRefDType{fl, classp, nullptr}; + v3Global.rootp()->typeTablep()->addTypesp(refDTypep); + AstMethodCall* const callp + = new AstMethodCall{fl, new AstThisRef{fl, refDTypep}, wrapperp->name(), nullptr}; + callp->taskp(wrapperp); + callp->dtypeSetVoid(); + randomizeFuncp->addStmtsp(callp->makeStmt()); + } // Check if a class (including inherited members) has any rand class-type members bool classHasRandClassMembers(AstClass* classp) { return classp->existsMember([](const AstClass*, const AstVar* varp) { @@ -3776,6 +3884,22 @@ class RandomizeVisitor final : public VNVisitor { return VN_IS(dtypep, ClassRefDType); }); } + // True if this class owns a global constraint: a member-select chain rooted + // at a rand class-typed handle reaching into a sub-object. + bool classOwnsGlobalConstraint(const AstClass* classp) const { + return classp->existsMember([](const AstClass*, const AstConstraint* constrp) { + bool owns = false; + constrp->foreach([&](const AstMemberSel* memberSelp) { + const AstNode* rootp = memberSelp->fromp(); + while (const AstMemberSel* const sp = VN_CAST(rootp, MemberSel)) + rootp = sp->fromp(); + if (const AstVarRef* const refp = VN_CAST(rootp, VarRef)) { + if (VN_IS(refp->varp()->dtypep()->skipRefp(), ClassRefDType)) owns = true; + } + }); + return owns; + }); + } // Get or create __VrandCb_pre/__VrandCb_post task for nested callbacks AstTask* getCreateNestedCallbackTask(AstClass* classp, const string& suffix) { const string name = "__VrandCb_" + suffix; @@ -4331,20 +4455,41 @@ class RandomizeVisitor final : public VNVisitor { // Replace AstDist with weighted bucket selection via AstConstraintIf chain. // Supports both constant and variable weight expressions. - void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp) { + void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp, + AstConstraintForeach* foreachp = nullptr) { + // When inside a foreach, bucket preamble stmts are stored in foreachp->user3p() + // (as a linked list) so visit(AstConstraintForeach*) can inject them into the + // real AstForeach body. Outside a foreach, they go directly into taskp. + AstNode* foreachTailp = nullptr; + auto addStmt = [&](AstNode* nodep) { + if (foreachp) { + if (!foreachTailp) { + foreachp->user3p(nodep); + foreachTailp = nodep; + } else { + foreachTailp->addNext(nodep); + foreachTailp = nodep; + } + } else { + taskp->addStmtsp(nodep); + } + }; + for (AstNode *nextip, *itemp = constrItemsp; itemp; itemp = nextip) { nextip = itemp->nextp(); // Recursively handle ConstraintIf nodes (dist can be inside if/else) if (AstConstraintIf* const cifp = VN_CAST(itemp, ConstraintIf)) { - if (cifp->thensp()) lowerDistConstraints(taskp, cifp->thensp()); - if (cifp->elsesp()) lowerDistConstraints(taskp, cifp->elsesp()); + if (cifp->thensp()) // LCOV_EXCL_LINE + lowerDistConstraints(taskp, cifp->thensp(), foreachp); // LCOV_EXCL_LINE + if (cifp->elsesp()) lowerDistConstraints(taskp, cifp->elsesp(), foreachp); continue; } // Recursively handle ConstraintForeach nodes (dist can be inside foreach) if (AstConstraintForeach* const cfep = VN_CAST(itemp, ConstraintForeach)) { - if (cfep->bodyp()) lowerDistConstraints(taskp, cfep->bodyp()); + if (cfep->bodyp()) // LCOV_EXCL_LINE + lowerDistConstraints(taskp, cfep->bodyp(), cfep); // LCOV_EXCL_LINE continue; } @@ -4364,7 +4509,7 @@ class RandomizeVisitor final : public VNVisitor { AstConstraintIf* const liftedp = liftLogIfChainToConstraintIf(topLogIfp); constrExprp->replaceWith(liftedp); VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp); - lowerDistConstraints(taskp, liftedp->thensp()); + lowerDistConstraints(taskp, liftedp->thensp(), foreachp); continue; } } @@ -4426,6 +4571,47 @@ class RandomizeVisitor final : public VNVisitor { continue; } + // IEEE 1800-2023 18.5.3: values not in the distribution must never appear. + // Build the union of all non-zero-weight ranges as a single hard ConstraintExpr + AstNodeExpr* unionExprp = nullptr; + for (const auto& bucket : buckets) { + AstNodeExpr* memberp; + if (const AstInsideRange* const irp = VN_CAST(bucket.rangep, InsideRange)) { + // (distExpr >= lo) && (distExpr <= hi); signed comparisons for signed vars + const bool isSigned = distp->exprp()->isSigned(); + AstNodeExpr* const distExprGtep = distp->exprp()->cloneTreePure(false); + AstNodeExpr* const distExprLtep = distp->exprp()->cloneTreePure(false); + distExprGtep->user1(true); + distExprLtep->user1(true); + AstNodeExpr* const gep + = isSigned ? static_cast(new AstGteS{ + fl, distExprGtep, irp->lhsp()->cloneTreePure(false)}) + : static_cast(new AstGte{ + fl, distExprGtep, irp->lhsp()->cloneTreePure(false)}); + AstNodeExpr* const lep + = isSigned ? static_cast(new AstLteS{ + fl, distExprLtep, irp->rhsp()->cloneTreePure(false)}) + : static_cast(new AstLte{ + fl, distExprLtep, irp->rhsp()->cloneTreePure(false)}); + gep->user1(true); + lep->user1(true); + memberp = new AstLogAnd{fl, gep, lep}; + } else { + // distExpr == val + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); + memberp = new AstEq{fl, distExprCopyp, bucket.rangep->cloneTreePure(false)}; + } + memberp->user1(true); + if (!unionExprp) { + unionExprp = memberp; + } else { + unionExprp = new AstLogOr{fl, memberp, unionExprp}; + unionExprp->user1(true); + } + } + AstConstraintExpr* const membershipp = new AstConstraintExpr{fl, unionExprp}; + // Build totalWeight expression: w[0] + w[1] + ... + w[N-1] AstNodeExpr* totalWeightExprp = nullptr; for (auto& bucket : buckets) { @@ -4447,8 +4633,8 @@ class RandomizeVisitor final : public VNVisitor { totalVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); totalVarp->funcLocal(true); totalVarp->isInternal(true); - taskp->addStmtsp(totalVarp); - taskp->addStmtsp( + addStmt(totalVarp); + addStmt( new AstAssign{fl, new AstVarRef{fl, totalVarp, VAccess::WRITE}, totalWeightExprp}); // bucketVar = (rand64() % totalWeight) + 1 @@ -4459,11 +4645,11 @@ class RandomizeVisitor final : public VNVisitor { bucketVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); bucketVarp->funcLocal(true); bucketVarp->isInternal(true); - taskp->addStmtsp(bucketVarp); + addStmt(bucketVarp); - AstNodeExpr* randp = new AstRand{fl, nullptr, false}; + AstNodeExpr* const randp = new AstRand{fl, nullptr, false}; randp->dtypeSetUInt64(); - taskp->addStmtsp(new AstAssign{ + addStmt(new AstAssign{ fl, new AstVarRef{fl, bucketVarp, VAccess::WRITE}, new AstAdd{ fl, new AstConst{fl, AstConst::Unsized64{}, 1}, @@ -4488,27 +4674,59 @@ class RandomizeVisitor final : public VNVisitor { for (int i = static_cast(buckets.size()) - 1; i >= 0; --i) { AstNodeExpr* constraintExprp; if (const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange)) { - AstNodeExpr* const exprCopy1p = distp->exprp()->cloneTreePure(false); - exprCopy1p->user1(true); - AstNodeExpr* const exprCopy2p = distp->exprp()->cloneTreePure(false); - exprCopy2p->user1(true); - AstGte* const gtep - = new AstGte{fl, exprCopy1p, irp->lhsp()->cloneTreePure(false)}; - gtep->user1(true); - AstLte* const ltep - = new AstLte{fl, exprCopy2p, irp->rhsp()->cloneTreePure(false)}; - ltep->user1(true); - constraintExprp = new AstLogAnd{fl, gtep, ltep}; + // Pick distExpr = lo + rand64() % (hi - lo + 1) for a uniform value in range + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); + const int distWidth = distp->exprp()->width(); + // Compute range size in 64-bit to avoid overflow + const AstConst* const lopC = VN_CAST(irp->lhsp(), Const); + const AstConst* const hipC = VN_CAST(irp->rhsp(), Const); + AstNodeExpr* rangeSzp; + if (lopC && hipC) { + const uint64_t rsz = hipC->toUQuad() - lopC->toUQuad() + 1; + rangeSzp = new AstConst{fl, AstConst::Unsized64{}, rsz}; + } else { + const bool isSigned = irp->lhsp()->isSigned(); + AstNodeExpr* const lo64p + = isSigned + ? static_cast( + new AstExtendS{fl, irp->lhsp()->cloneTreePure(false), 64}) + : static_cast( + new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64}); + lo64p->dtypeSetUInt64(); + AstNodeExpr* const hi64p + = isSigned + ? static_cast( + new AstExtendS{fl, irp->rhsp()->cloneTreePure(false), 64}) + : static_cast( + new AstExtend{fl, irp->rhsp()->cloneTreePure(false), 64}); + hi64p->dtypeSetUInt64(); + rangeSzp = new AstAdd{fl, new AstConst{fl, AstConst::Unsized64{}, 1ULL}, + new AstSub{fl, hi64p, lo64p}}; + } + AstNodeExpr* const rand64p = new AstRand{fl, nullptr, false}; + rand64p->dtypeSetUInt64(); + // offset = rand64() % rangeSize (result in [0, rangeSize-1]) + AstNodeExpr* const offset64p = new AstModDiv{fl, rand64p, rangeSzp}; + // Truncate offset to dist expression width, then add lo + AstNodeExpr* const offsetp = new AstCCast{fl, offset64p, distWidth}; + AstNodeExpr* const lop = irp->lhsp()->cloneTreePure(false); + AstNodeExpr* const valuep = new AstAdd{fl, lop, offsetp}; + valuep->dtypeFrom(distp->exprp()); + constraintExprp = new AstEq{fl, distExprCopyp, valuep}; constraintExprp->user1(true); } else { - AstNodeExpr* const exprCopyp = distp->exprp()->cloneTreePure(false); - exprCopyp->user1(true); + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); constraintExprp - = new AstEq{fl, exprCopyp, buckets[i].rangep->cloneTreePure(false)}; + = new AstEq{fl, distExprCopyp, buckets[i].rangep->cloneTreePure(false)}; constraintExprp->user1(true); } AstConstraintExpr* const thenp = new AstConstraintExpr{fl, constraintExprp}; + // Per IEEE 18.5.3: weights are a preference, not a hard constraint. + // The solver may discard this when it conflicts with other constraints. + thenp->isSoft(true); if (!chainp) { chainp = thenp; @@ -4522,6 +4740,8 @@ class RandomizeVisitor final : public VNVisitor { if (chainp) { constrExprp->replaceWith(chainp); VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp); + // Hard membership precedes the soft bucket chain in the constraint list. + chainp->addHereThisAsNext(membershipp); } // Clean up nodes used only as clone templates (never inserted into tree) @@ -4534,6 +4754,16 @@ class RandomizeVisitor final : public VNVisitor { } } + static bool isDynArrOfClassTypeRecurse(const AstNodeDType* const dtypep) { + const AstNodeDType* const refp = dtypep->skipRefp(); + if (VN_IS(refp, DynArrayDType) || VN_IS(refp, QueueDType)) { + return isDynArrOfClassTypeRecurse(refp->subDTypep()); + } else if (VN_IS(refp, ClassRefDType)) { + return true; + } + return false; + } + // VISITORS void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); @@ -4561,6 +4791,10 @@ class RandomizeVisitor final : public VNVisitor { UINFO(9, "Define randomize() for " << nodep); nodep->baseMostClassp()->needRNG(true); + // Detect global-constraint ownership BEFORE the constraint items are + // unlinked into setup tasks below (after that the member-selects are gone). + const bool basicFirst = classOwnsGlobalConstraint(nodep); + FileLine* fl = nodep->fileline(); AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep); AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var); @@ -4766,6 +5000,18 @@ class RandomizeVisitor final : public VNVisitor { // Refresh array element tables after resize for (AstVar* const arrVarp : sizeArraysIt->second) { + // Array elements of class data type are passed to the solver as separate + // variables, so passing the original array variable is redundant, because it + // won't be referenced + if (isDynArrOfClassTypeRecurse(arrVarp->dtypep())) { + const uint32_t unpackedDims = arrVarp->dtypep()->dimensions(false).second; + if (unpackedDims > 1) { + arrVarp->v3warn( + E_UNSUPPORTED, + "Unsupported: Nested array element access in global constraint"); + } + continue; + } AstCMethodHard* const methodp = new AstCMethodHard{ fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE}, VCMethod::RANDOMIZER_WRITE_VAR}; @@ -4857,29 +5103,41 @@ class RandomizeVisitor final : public VNVisitor { beginValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1}; } + AstFunc* const basicRandomizep + = V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME); + addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp); + // A basicFirst owner basic-randomizes first, then the solver overrides the + // constrained leaves, so a globally-constrained leaf (not user3) is still + // basic-randomized when its type is randomized standalone. AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE}; - randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp}); + randomizep->addStmtsp(new AstAssign{ + fl, fvarRefp, basicFirst ? new AstFuncRef{fl, basicRandomizep} : beginValp}); const auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep); const bool needsSizePhase = sizeArraysIt != m_sizeConstrainedArrays.end() && !sizeArraysIt->second.empty(); - if (!needsSizePhase) { + // Size-only resize fallback (no element constraint, so no two-pass phase). + // Must run after the solver .next() that sets the size variable; emit it + // after whichever assignment below holds the solver call. + const auto emitResizeFallback = [&]() { + if (needsSizePhase) return; if (AstTask* const resizeAllTaskp = VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) { AstTaskRef* const resizeTaskRefp = new AstTaskRef{fl, resizeAllTaskp}; randomizep->addStmtsp(resizeTaskRefp->makeStmt()); } - } + }; + if (!basicFirst) emitResizeFallback(); AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false); fvarRefReadp->access(VAccess::READ); - AstFunc* const basicRandomizep - = V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME); - addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp); - AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep}; + AstNodeExpr* const secondHalfp + = basicFirst ? beginValp : new AstFuncRef{fl, basicRandomizep}; randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false), - new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}}); + new AstAnd{fl, fvarRefReadp, secondHalfp}}); + + if (basicFirst) emitResizeFallback(); // Call nested post_randomize on rand class-type members (IEEE 18.4.1) if (classHasRandClassMembers(nodep)) { @@ -5171,7 +5429,17 @@ class RandomizeVisitor final : public VNVisitor { AstFunc* const randomizeFuncp = V3Randomize::newRandomizeFunc( m_memberMap, classp, m_inlineUniqueNames.get(nodep), false); - addPrePostCall(classp, randomizeFuncp, "pre_randomize"); + // A base-handle `randomize() with` must still reach a derived + // pre/post_randomize. Route them through per-class virtual wrappers + // when the static handle type participates in inheritance. + const std::pair prePostWrap = classp->isExtended() + ? buildPrePostVirtualWrappers(classp) + : std::pair{false, false}; + if (prePostWrap.first) { + addVirtualPrePostCall(randomizeFuncp, classp, "pre_randomize"); + } else { + addPrePostCall(classp, randomizeFuncp, "pre_randomize"); + } // Call nested pre_randomize on rand class-type members (IEEE 18.4.1) if (classHasRandClassMembers(classp)) { @@ -5333,7 +5601,11 @@ class RandomizeVisitor final : public VNVisitor { randomizeFuncp->addStmtsp((new AstTaskRef{nodep->fileline(), postTaskp})->makeStmt()); } - addPrePostCall(classp, randomizeFuncp, "post_randomize"); + if (prePostWrap.second) { + addVirtualPrePostCall(randomizeFuncp, classp, "post_randomize"); + } else { + addPrePostCall(classp, randomizeFuncp, "post_randomize"); + } // Replace the node with a call to that function nodep->name(randomizeFuncp->name()); @@ -5405,15 +5677,21 @@ public: explicit RandomizeVisitor(AstNetlist* nodep) : m_inlineUniqueNames{"__Vrandwith"} { createRandomizeClassVars(nodep); - // Mark variables in global constraints - // These should not be randomized in nested class's __VBasicRand - nodep->foreach([&](AstConstraint* constrp) { - constrp->foreach([&](AstMemberSel* memberSelp) { - // Only mark if this MemberSel was created during constraint cloning - if (memberSelp->user2p()) { + // Flag local constraint leaves as solver-owned so __VBasicRand skips them. + // Runs before any class is lowered. Only a randomized class counts, and + // only a leaf owned by the constraint's own class: a leaf reached through + // a global constraint stays basic-randomized so a standalone randomize() + // of its type still randomizes it (issue #7833); a class-typed sub-object + // is skipped so its own members are still basic-randomized. + nodep->foreach([&](AstClass* const classp) { + if (!classp->user1()) return; + classp->foreachMember([&](AstClass* const ownerClassp, AstConstraint* const constrp) { + constrp->foreach([&](AstMemberSel* const memberSelp) { AstVar* const varp = memberSelp->varp(); + if (VN_IS(varp->dtypep()->skipRefp(), ClassRefDType)) return; + if (VN_AS(varp->user2p(), NodeModule) != ownerClassp) return; if (!varp->user3()) varp->user3(true); - } + }); }); }); diff --git a/src/V3Sampled.cpp b/src/V3Sampled.cpp index 4fb45e772..841b32d55 100644 --- a/src/V3Sampled.cpp +++ b/src/V3Sampled.cpp @@ -51,8 +51,6 @@ class SampledVisitor final : public VNVisitor { AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; m_scopep->modp()->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; - newvarp->direction(VDirection::INPUT); // Inform V3Sched that it will be driven later - newvarp->primaryIO(true); newvarp->sampled(true); vscp->user1p(newvscp); m_scopep->addVarsp(newvscp); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 8adc0a52e..6078fbbf0 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -529,8 +529,46 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, + entry.m_memberp->name()); } + // Create the input change detect SenTrees. + // If there is a lot of combinationallogic hanging of the top level inputs, we can save + // a lot of work by only evaluating it if an input has actually changed. This in + // paticular helps hierarchical models partitioned across combinaitonal boundaries. + // The change detect itself should be fairly cheap otherwise so alway do it. + // For correctness, don't create a change detect for top level inputs also written + // by the design, as the change detect 'previous value' would get out of sync. + // Also omit a SenTree for types that don't have the required '!=' operator. + // Any signal that does not have an explicit change detect trigger will fall back to + // using the 'first iteration' trigger, same as if this optimization was disabled. + std::unordered_map inp2changedp; + std::vector icoChangeSenTreeps; + if (v3Global.opt.fIcoChangeDetect().isTrue()) { + FileLine* const flp = netlistp->fileline(); + AstScope* const scopep = netlistp->topScopep()->scopep(); + for (AstVarScope* vscp = scopep->varsp(); vscp; vscp = VN_AS(vscp->nextp(), VarScope)) { + // Only for top level ports, assume outputs don't change externally + if (!vscp->varp()->isPrimaryInish()) continue; + // Don't do if written by the design - wouldn't update the change detect 'prev' value + if (vscp->varp()->icoMaybeWritten()) continue; + // Don't do if forceable, as we can't see the actual value - this is belt and braces + if (vscp->varp()->isForced()) continue; + // Can't handle unpacked arrays (they have special types when primary input) + if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) continue; + // Similarly to arrays, can't handle SystemC types + if (vscp->varp()->isSc()) continue; + // Create a sen tree triggered when this input changes + AstSenTree*& senTreepr = inp2changedp[vscp]; + UASSERT_OBJ(!senTreepr, vscp, "Duplicate input change detect trigger"); + AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ}; + AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_CHANGED, refp}; + senTreepr = new AstSenTree{flp, senItemp}; + icoChangeSenTreeps.push_back(senTreepr); + } + } + V3Stats::addStat("Scheduling, 'ico' change detect triggers", icoChangeSenTreeps.size()); + // Gather the relevant sensitivity expressions and create the trigger kit - const auto& senTreeps = getSenTreesUsedBy({&logic}); + std::vector senTreeps = getSenTreesUsedBy({&logic}); + senTreeps.insert(senTreeps.end(), icoChangeSenTreeps.begin(), icoChangeSenTreeps.end()); const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, {}, senTreeps, "ico", extraTriggers, false, false); std::ignore = senExprBuilder.getAndClearResults(); @@ -543,14 +581,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, // Remap sensitivities remapSensitivities(logic, trigKit.mapVec()); + for (auto& pair : inp2changedp) pair.second = trigKit.mapVec().at(pair.second); // Create the inverse map from trigger ref AstSenTree to original AstSenTree V3Order::TrigToSenMap trigToSen; invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec()); - // The trigger top level inputs (first iteration) - AstSenTree* const inputChanged - = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); + // The 'first iteration' trigger for top level inputs - lazy constructed only if needed + AstSenTree* firstIterTriggerp = nullptr; // The DPI Export trigger AstSenTree* const dpiExportTriggered @@ -565,9 +603,19 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, netlistp, {&logic}, trigToSen, "ico", false, false, [&](const AstVarScope* vscp, std::vector& out) { AstVar* const varp = vscp->varp(); - if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) { - out.push_back(inputChanged); + // If it has an explicit change detect trigger, use that, + // otherwise fall back to using the 'first iteration' trigger + auto it = inp2changedp.find(vscp); + if (it != inp2changedp.end()) { + out.push_back(it->second); + } else if (varp->isPrimaryInish() || varp->isSigUserRWPublic() || varp->sampled()) { + if (!firstIterTriggerp) { + firstIterTriggerp + = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); + } + out.push_back(firstIterTriggerp); } + // Add other triggers if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered); if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) { const auto& ifaceTriggered @@ -593,8 +641,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, // Work statements: Invoke the 'ico' function util::callVoidFunc(icoFuncp)); - // Add the first iteration trigger to the trigger computation function - trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false); + // Add the first iteration trigger to the trigger computation function - if used + if (firstIterTriggerp) { + trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false); + } + + // Release temporary input change detect SenTrees + for (AstSenTree* const senTreep : icoChangeSenTreeps) senTreep->deleteTree(); + icoChangeSenTreeps.clear(); return icoLoop.stmtsp; } diff --git a/src/V3SchedAcyclic.cpp b/src/V3SchedAcyclic.cpp index 36a9cd096..ad8ba454e 100644 --- a/src/V3SchedAcyclic.cpp +++ b/src/V3SchedAcyclic.cpp @@ -351,8 +351,7 @@ std::string reportLoopVars(FileLine* /*warnFl*/, Graph* graphp, SchedAcyclicVarV if (splittable) { ss << V3Error::warnMore() - << "... Suggest add /*verilator split_var*/ or /*verilator " - "isolate_assignments*/ to appropriate variables above.\n"; + << "... Suggest add /*verilator split_var*/ to appropriate variables above.\n"; } V3Stats::addStat("Scheduling, split_var, candidates", splittable); return ss.str(); diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 35718b98a..3aa404018 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -150,8 +150,8 @@ public: : SchedReplicateVertex{graphp} , m_vscp{vscp} { // Top level inputs are - if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi() - || varp()->sensIfacep() || varp()->isVirtIface()) { + if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->sampled() + || varp()->isWrittenByDpi() || varp()->sensIfacep() || varp()->isVirtIface()) { addDrivingRegions(INPUT); } // Currently we always execute suspendable processes at the beginning of diff --git a/src/V3SchedVirtIface.cpp b/src/V3SchedVirtIface.cpp index 4dd95662d..41684cd2b 100644 --- a/src/V3SchedVirtIface.cpp +++ b/src/V3SchedVirtIface.cpp @@ -39,11 +39,18 @@ namespace { class VirtIfaceVisitor final : public VNVisitor { private: // STATE + using IfaceMember = std::pair; + using IfaceCallable = std::pair; + // Set of (iface, member) pairs written through VIF -- defines which members need triggers - std::set> m_vifWrittenMembers; + std::set m_vifWrittenMembers; // All candidate VarScopes of interface members (keyed by interface type + member name) - std::map, std::vector> - m_candidateVscps; + std::map> m_candidateVscps; + std::set> m_seenCandidateVscps; + // VarScope index and callable worklist for VIF method-body writes. + std::map> m_vscpsByVar; + std::vector m_reachableIfaceCallables; + std::set m_seenReachableIfaceCallables; VirtIfaceTriggers m_triggers; // METHODS @@ -70,12 +77,24 @@ private: } iterateChildren(nodep); } + void visit(AstCMethodCall* nodep) override { + if (const AstIfaceRefDType* const dtypep + = VN_CAST(nodep->fromp()->dtypep()->skipRefp(), IfaceRefDType)) { + if (VL_UNCOVERABLE(!dtypep->isVirtual())) { + // Concrete interface method calls are lowered before this pass. + } else { + addReachableIfaceCallable(dtypep->ifaceViaCellp(), nodep->funcp()); + } + } + iterateChildren(nodep); + } void visit(AstVarScope* nodep) override { // Collect candidate VarScopes. sensIfacep() is set on interface members // accessed via any MemberSel (virtual or non-virtual). - if (const AstIface* const ifacep = nodep->varp()->sensIfacep()) { - m_candidateVscps[{ifacep, nodep->varp()->name()}].push_back(nodep); - } + AstVar* const varp = nodep->varp(); + if (varp->isTemp()) return; + m_vscpsByVar[varp].push_back(nodep); + if (const AstIface* const ifacep = varp->sensIfacep()) addCandidateVscp(ifacep, nodep); } void visit(AstNodeProcedure* nodep) override { // Disable lifetime optimization for variables in AlwaysPost blocks @@ -87,6 +106,19 @@ private: } void visit(AstNode* nodep) override { iterateChildren(nodep); } + void addCandidateVscp(const AstIface* const ifacep, AstVarScope* const vscp) { + const IfaceMember member{ifacep, vscp->varp()->name()}; + if (m_seenCandidateVscps.emplace(member, vscp).second) + m_candidateVscps[member].push_back(vscp); + } + + void addReachableIfaceCallable(AstIface* const ifacep, AstCFunc* const funcp) { + const IfaceCallable callable{ifacep, funcp}; + if (m_seenReachableIfaceCallables.emplace(callable).second) { + m_reachableIfaceCallables.push_back(callable); + } + } + // Build final trigger list by intersecting VIF writes with candidate VarScopes void buildTriggers() { for (const auto& written : m_vifWrittenMembers) { @@ -103,6 +135,29 @@ public: // CONSTRUCTORS explicit VirtIfaceVisitor(AstNetlist* nodep) { iterate(nodep); + for (size_t i = 0; i < m_reachableIfaceCallables.size(); ++i) { + const IfaceCallable callable = m_reachableIfaceCallables[i]; + callable.second->foreach([this, callable](AstNodeExpr* const nodep) { + if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) { + // Only persistent interface storage is observable through a VIF read. + UASSERT_OBJ(refp->varScopep(), refp, "No var scope"); + AstVar* const varp = refp->varp(); + if (!refp->access().isWriteOrRW() || varp->isFuncLocal() || varp->isTemp() + || varp->isEvent() || !VN_IS(refp->varScopep()->scopep()->modp(), Iface)) { + return; + } + varp->sensIfacep(callable.first); + m_vifWrittenMembers.emplace(callable.first, varp->name()); + const auto it = m_vscpsByVar.find(varp); + UASSERT_OBJ(it != m_vscpsByVar.end(), varp, + "No VarScope for interface member"); + for (AstVarScope* const vscp : it->second) + addCandidateVscp(callable.first, vscp); + } else if (AstNodeCCall* const callp = VN_CAST(nodep, NodeCCall)) { + addReachableIfaceCallable(callable.first, callp->funcp()); + } + }); + } buildTriggers(); } ~VirtIfaceVisitor() override = default; diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h index 861f8a905..ac9abf196 100644 --- a/src/V3SenExprBuilder.h +++ b/src/V3SenExprBuilder.h @@ -78,7 +78,7 @@ private: // Check if expression contains a class member access that could be null // (e.g., accessing an event through a class reference that may not be initialized) static bool hasClassMemberAccess(const AstNode* const exprp) { - return exprp->exists([](const AstNode* const nodep) { + return exprp && exprp->exists([](const AstNode* const nodep) { if (const AstMemberSel* const mselp = VN_CAST(nodep, MemberSel)) { // Check if the base expression is a class reference return mselp->fromp()->dtypep() @@ -294,6 +294,8 @@ private: } case VEdgeType::ET_TRUE: // return {currp(), false}; + case VEdgeType::ET_INITIAL_NBA: // + return {new AstConst{flp, AstConst::BitFalse{}}, true}; default: // LCOV_EXCL_START senItemp->v3fatalSrc("Unknown edge type"); return {nullptr, false}; diff --git a/src/V3SplitAs.cpp b/src/V3SplitAs.cpp deleted file mode 100644 index 74b72a4dd..000000000 --- a/src/V3SplitAs.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Break always into separate statements to reduce temps -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* -// V3SplitAs's Transformations: -// -// Search each ALWAYS for a VARREF lvalue with a /*isolate_assignments*/ attribute -// If found, color statements with both, assignment to that varref, or other assignments. -// Replicate the Always, and remove mis-colored duplicate code. -// -//************************************************************************* - -#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT - -#include "V3SplitAs.h" - -#include "V3Stats.h" - -VL_DEFINE_DEBUG_FUNCTIONS; - -//###################################################################### -// Find all split variables in a block - -class SplitAsFindVisitor final : public VNVisitorConst { - // STATE - across all visitors - AstVarScope* m_splitVscp = nullptr; // Variable we want to split - - // METHODS - void visit(AstVarRef* nodep) override { - if (nodep->access().isWriteOrRW() && !m_splitVscp && nodep->varp()->attrIsolateAssign()) { - m_splitVscp = nodep->varScopep(); - } - } - void visit(AstExprStmt* nodep) override { - // A function call inside the splitting assignment - // We need to presume the whole call is preserved (if the upper statement is) - // This will break if the m_splitVscp is a "ref" argument to the function, - // but little we can do. - } - void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } - -public: - // CONSTRUCTORS - explicit SplitAsFindVisitor(AstAlways* nodep) { iterateConst(nodep); } - ~SplitAsFindVisitor() override = default; - // METHODS - AstVarScope* splitVscp() const { return m_splitVscp; } -}; - -//###################################################################### -// Remove nodes not containing proper references - -class SplitAsCleanVisitor final : public VNVisitor { - // STATE - across all visitors - const AstVarScope* const m_splitVscp; // Variable we want to split - const bool m_modeMatch; // Remove matching Vscp, else non-matching - // STATE - for current visit position (use VL_RESTORER) - bool m_keepStmt = false; // Current Statement must be preserved - bool m_matches = false; // Statement below has matching lvalue reference - - // METHODS - void visit(AstVarRef* nodep) override { - if (nodep->access().isWriteOrRW()) { - if (nodep->varScopep() == m_splitVscp) { - UINFO(6, " CL VAR " << nodep); - m_matches = true; - } - } - } - void visit(AstNodeStmt* nodep) override { - UINFO(6, " CL STMT " << nodep); - const bool oldKeep = m_keepStmt; - { - VL_RESTORER(m_matches); - m_matches = false; - m_keepStmt = false; - - iterateChildren(nodep); - - if (m_keepStmt || (m_modeMatch ? m_matches : !m_matches)) { - UINFO(6, " Keep STMT " << nodep); - m_keepStmt = true; - } else { - UINFO(6, " Delete STMT " << nodep); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - } - } - // If something below matches, the upper statement remains too. - m_keepStmt = oldKeep || m_keepStmt; - UINFO(9, " upKeep=" << m_keepStmt << " STMT " << nodep); - } - void visit(AstExprStmt* nodep) override { - // A function call inside the splitting assignment - // We need to presume the whole call is preserved (if the upper statement is) - // This will break if the m_splitVscp is a "ref" argument to the function, - // but little we can do. - } - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - SplitAsCleanVisitor(AstAlways* nodep, AstVarScope* vscp, bool modeMatch) - : m_splitVscp{vscp} - , m_modeMatch{modeMatch} { - iterate(nodep); - } - ~SplitAsCleanVisitor() override = default; -}; - -//###################################################################### -// SplitAs class functions - -class SplitAsVisitor final : public VNVisitor { - // NODE STATE - // AstAlways::user() -> bool. True if already processed - const VNUser1InUse m_inuser1; - - // STATE - across all visitors - VDouble0 m_statSplits; // Statistic tracking - - // METHODS - void splitAlways(AstAlways* nodep, AstVarScope* splitVscp) { - UINFOTREE(9, nodep, "", "in"); - // Duplicate it and link in - // Below cloneTree should perhaps be cloneTreePure, but given - // isolate_assignments is required to hit this code, we presume the user - // knows what they are asking for - AstAlways* const newp = nodep->cloneTree(false); - newp->user1(true); // So we don't clone it again - nodep->addNextHere(newp); - { // Delete stuff we don't want in old - const SplitAsCleanVisitor visitor{nodep, splitVscp, false}; - UINFOTREE(9, nodep, "", "out0"); - } - { // Delete stuff we don't want in new - const SplitAsCleanVisitor visitor{newp, splitVscp, true}; - UINFOTREE(9, newp, "", "out1"); - } - } - - void visit(AstAlways* nodep) override { - // Are there any lvalue references below this? - // There could be more than one. So, we process the first one found first. - const AstVarScope* lastSplitVscp = nullptr; - while (!nodep->user1()) { - // Find any splittable variables - const SplitAsFindVisitor visitor{nodep}; - AstVarScope* const splitVscp = visitor.splitVscp(); - // Now isolate the always - if (splitVscp) { - UINFO(3, "Split " << nodep); - UINFO(3, " For " << splitVscp); - // If we did this last time! Something's stuck! - UASSERT_OBJ(splitVscp != lastSplitVscp, nodep, - "Infinite loop in isolate_assignments removal for: " - << splitVscp->prettyNameQ()); - lastSplitVscp = splitVscp; - splitAlways(nodep, splitVscp); - ++m_statSplits; - } else { - nodep->user1(true); - } - } - } - - void visit(AstNodeExpr*) override {} // Accelerate - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - explicit SplitAsVisitor(AstNetlist* nodep) { iterate(nodep); } - ~SplitAsVisitor() override { - V3Stats::addStat("Optimizations, isolate_assignments blocks", m_statSplits); - } -}; - -//###################################################################### -// SplitAs class functions - -void V3SplitAs::splitAsAll(AstNetlist* nodep) { - UINFO(2, __FUNCTION__ << ":"); - { SplitAsVisitor{nodep}; } // Destruct before checking - V3Global::dumpCheckGlobalTree("splitas", 0, dumpTreeEitherLevel() >= 3); -} diff --git a/src/V3SplitVar.cpp b/src/V3SplitVar.cpp index 95bd05954..917123be6 100644 --- a/src/V3SplitVar.cpp +++ b/src/V3SplitVar.cpp @@ -477,7 +477,13 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl { UINFO(4, "Start checking " << nodep->prettyNameQ()); if (!VN_IS(nodep, Module)) { UINFO(4, "Skip " << nodep->prettyNameQ()); - nodep->foreach([this](AstVarXRef* const nodep) { handleVarXRef(nodep); }); + nodep->foreach([this](AstNodeVarRef* const nodep) { + if (AstVarXRef* const varXRefp = VN_CAST(nodep, VarXRef)) { + handleVarXRef(varXRefp); + } else if (m_modp) { + iterate(VN_AS(nodep, VarRef)); + } + }); return; } UASSERT_OBJ(!m_modp, m_modp, "Nested module declaration"); diff --git a/src/V3Stats.cpp b/src/V3Stats.cpp index e1cd949ae..a1ad5edb2 100644 --- a/src/V3Stats.cpp +++ b/src/V3Stats.cpp @@ -35,54 +35,63 @@ class StatsVisitor final : public VNVisitorConst { struct Counters final { // Nodes of given type std::array m_statTypeCount{}; - // Nodes of given type with given type immediate child - std::array, VNType::NUM_TYPES()> m_statAbove{}; // Prediction of given type std::array m_statPred{}; }; // STATE const bool m_fastOnly; // When true, consider only fast functions - const AstNodeExpr* m_parentExprp = nullptr; // Parent expression + bool m_empty = true; // Netlist is empty Counters m_counters; // The actual counts we will display Counters m_dumpster; // Alternate buffer to make discarding parts of the tree easier Counters* m_accump; // The currently active accumulator std::vector m_statVarWidths; // Variables of given width + std::vector m_constPoolConsts; // Constant pool constants of given width + // Constant pool tables of given width and depth + std::map, uint64_t> m_constPoolTables; std::vector> m_statVarWidthNames; // Var names of given width // METHODS void countThenIterateChildren(AstNode* nodep) { ++m_accump->m_statTypeCount[nodep->type()]; + if (nodep->type() != VNType::Netlist) m_empty = false; iterateChildrenConst(nodep); } // VISITORS void visit(AstVar* nodep) override { if (nodep->dtypep()) { - if (m_statVarWidths.size() <= static_cast(nodep->width())) { - m_statVarWidths.resize(nodep->width() + 5); - if (v3Global.opt.statsVars()) { // - m_statVarWidthNames.resize(nodep->width() + 5); + if (nodep->constPoolEntry()) { + // Count constant pool entries + if (AstUnpackArrayDType* const uatp = VN_CAST(nodep->dtypep(), UnpackArrayDType)) { + const int width = uatp->subDTypep()->width(); + const int depth = uatp->elementsConst(); + ++m_constPoolTables[{width, depth}]; + } else { + if (m_constPoolConsts.size() <= static_cast(nodep->width())) { + m_constPoolConsts.resize(nodep->width() + 5); + } + ++m_constPoolConsts.at(nodep->width()); + } + } else { + // Count proper variables + if (m_statVarWidths.size() <= static_cast(nodep->width())) { + m_statVarWidths.resize(nodep->width() + 5); + if (v3Global.opt.statsVars()) { // + m_statVarWidthNames.resize(nodep->width() + 5); + } + } + ++m_statVarWidths.at(nodep->width()); + if (v3Global.opt.statsVars()) { + ++m_statVarWidthNames.at(nodep->width())[nodep->prettyName()]; } - } - ++m_statVarWidths.at(nodep->width()); - if (v3Global.opt.statsVars()) { - ++m_statVarWidthNames.at(nodep->width())[nodep->prettyName()]; } } countThenIterateChildren(nodep); } - void visit(AstNodeExpr* nodep) override { - // Count expression combinations - if (m_parentExprp) ++m_accump->m_statAbove[m_parentExprp->type()][nodep->type()]; - VL_RESTORER(m_parentExprp); - m_parentExprp = nodep; - countThenIterateChildren(nodep); - } - void visit(AstNodeIf* nodep) override { // Track prediction ++m_accump->m_statPred[nodep->branchPred()]; @@ -104,6 +113,7 @@ public: , m_accump{fastOnly ? &m_dumpster : &m_counters} { UINFO(9, "Starting stats, fastOnly=" << fastOnly); iterateConst(nodep); + if (m_empty) return; // Shorthand const auto addStat = [&](const std::string& name, double count, unsigned precision = 0) { @@ -126,6 +136,26 @@ public: } } + // Constant pool constants + for (size_t i = 0; i < m_constPoolConsts.size(); ++i) { + const uint64_t count = m_constPoolConsts.at(i); + if (!count) continue; + std::stringstream ss; + ss << "Vars Const, width " << std::setw(5) << std::dec << i; + addStat(ss.str(), count); + } + + // Constant pool tables + for (const auto& it : m_constPoolTables) { + const int depth = it.first.second; + const int width = it.first.first; + const int count = it.second; + std::ostringstream ss; + ss << "Vars Table, width " << std::setw(5) << std::dec << width // + << " x " << std::setw(5) << std::dec << depth; + addStat(ss.str(), count); + } + // Node types (also total memory usage) const auto typeName = [](size_t t) { return std::string{VNType{static_cast(t)}.ascii()}; }; @@ -138,22 +168,13 @@ public: addStat("Node count, " + typeName(t), count); } } - addStat("Node memory TOTAL (MiB)", totalNodeMemoryUsage >> 20); + addStat("Node mem TOTAL (MiB)", totalNodeMemoryUsage >> 20); // Node Memory usage for (size_t t = 0; t < VNType::NUM_TYPES(); ++t) { if (const uint64_t count = m_counters.m_statTypeCount[t]) { const double share = 100.0 * count * typeSize(t) / totalNodeMemoryUsage; - addStat("Node memory share (%), " + typeName(t), share, 2); - } - } - - // Expression combinations - for (size_t t1 = 0; t1 < VNType::NUM_TYPES(); ++t1) { - for (size_t t2 = 0; t2 < VNType::NUM_TYPES(); ++t2) { - if (const uint64_t c = m_counters.m_statAbove[t1][t2]) { - addStat("Expr combination, " + typeName(t1) + " over " + typeName(t2), c); - } + addStat("Node mem %, " + typeName(t), share, 2); } } diff --git a/src/V3StatsReport.cpp b/src/V3StatsReport.cpp index 32556cb0a..1bf7c512d 100644 --- a/src/V3StatsReport.cpp +++ b/src/V3StatsReport.cpp @@ -119,11 +119,11 @@ class StatsReport final { // Header os << " Stat " << std::left << std::setw(maxWidth - 5 - 2) << ""; - for (const string& i : stages) os << " " << std::left << std::setw(9) << i; + for (const string& i : stages) os << " " << std::left << std::setw(9) << i; os << '\n'; os << " -------- " << std::left << std::setw(maxWidth - 5 - 2) << ""; for (auto it = stages.begin(); it != stages.end(); ++it) { - os << " " << std::left << std::setw(9) << "-------"; + os << " " << std::left << std::setw(9) << "-------"; } // os<name(); } while (col < stages.size() && stages.at(col) != repp->stage()) { - os << std::setw(11) << ""; + os << std::setw(10) << ""; col++; } repp->dump(os); @@ -191,7 +191,7 @@ StatsReport::StatColl StatsReport::s_allStats; // V3Statstic class void V3Statistic::dump(std::ofstream& os) const { - os << " " << std::right << std::fixed << std::setprecision(precision()) << std::setw(9) + os << " " << std::right << std::fixed << std::setprecision(precision()) << std::setw(9) << value(); } diff --git a/src/V3TSP.cpp b/src/V3TSP.cpp deleted file mode 100644 index 4244a5676..000000000 --- a/src/V3TSP.cpp +++ /dev/null @@ -1,701 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementation of Christofides algorithm to -// approximate the solution to the traveling salesman problem. -// -// ISSUES: This isn't exactly Christofides algorithm; see the TODO -// in perfectMatching(). True minimum-weight perfect matching -// would produce a better result. How much better is TBD. -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT - -#include "V3TSP.h" - -#include "V3File.h" -#include "V3Graph.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -VL_DEFINE_DEBUG_FUNCTIONS; - -//###################################################################### -// Support classes - -namespace V3TSP { -static uint32_t s_edgeIdNext = 0; - -static void selfTestStates(); -static void selfTestString(); -} // namespace V3TSP - -// Vertex that tracks a per-vertex key -template -class TspVertexTmpl final : public V3GraphVertex { - VL_RTTI_IMPL(TspVertexTmpl, V3GraphVertex) -private: - const T_Key m_key; - -public: - TspVertexTmpl(V3Graph* graphp, const T_Key& k) - : V3GraphVertex{graphp} - , m_key{k} {} - ~TspVertexTmpl() override = default; - const T_Key& key() const { return m_key; } - -private: - VL_UNCOPYABLE(TspVertexTmpl); -}; - -// TspGraphTmpl represents a complete graph, templatized to work with -// different T_Key types. -template -class TspGraphTmpl final : public V3Graph { -public: - // TYPES - using Vertex = TspVertexTmpl; - - enum VertexState : uint32_t { CLEAR = 0, MST_VISITED = 1, UNMATCHED_ODD = 2 }; - - // MEMBERS - std::unordered_map m_vertices; // T_Key to Vertex lookup map - - // CONSTRUCTORS - TspGraphTmpl() - : V3Graph{} {} - ~TspGraphTmpl() override = default; - - // METHODS - void addVertex(const T_Key& key) { - const bool newEntry = m_vertices.emplace(key, new Vertex{this, key}).second; - UASSERT(newEntry, "Vertex already exists with same key"); - } - - // For purposes of TSP, we are using non-directional graphs. - // Map that onto the normally-directional V3Graph by creating - // a matched pairs of opposite-directional edges to represent - // each non-directional edge: - void addEdge(const T_Key& from, const T_Key& to, int cost) { -#if VL_DEBUG // Hot, so only in debug - UASSERT(from != to, "Adding edge would form a loop"); - UASSERT(cost >= 0, "Negative weight edge"); -#endif - Vertex* const fp = findVertex(from); - Vertex* const tp = findVertex(to); - - // No need to dedup edges. - // The only time we may create duplicate edges is when - // combining the MST with the perfect-matched pairs, - // and in that case, we want to permit duplicate edges. - const uint32_t edgeId = ++V3TSP::s_edgeIdNext; - - // We want to be able to compare edges quickly for a total - // ordering, so pre-compute a sorting key and store it in - // the edge user field. We also want easy access to the 'id' - // which uniquely identifies a single bidir edge. Luckily we - // can do both efficiently. - // cppcheck-suppress badBitmaskCheck - const uint64_t userValue = (static_cast(cost) << 32) | edgeId; - (new V3GraphEdge{this, fp, tp, cost})->user(userValue); - (new V3GraphEdge{this, tp, fp, cost})->user(userValue); - } - - static uint32_t getEdgeId(const V3GraphEdge* edgep) { - return static_cast(edgep->user()); - } - - // cppcheck-suppress duplInheritedMember - bool empty() const { return m_vertices.empty(); } - - const std::list keysToVertexList(const std::vector& odds) { - std::list vertices; - for (unsigned i = 0; i < odds.size(); ++i) vertices.push_back(findVertex(odds.at(i))); - return vertices; - } - -private: - // We will keep sorted lists of edges as vectors - using EdgeList = std::vector; - - static bool edgeCmp(const V3GraphEdge* ap, const V3GraphEdge* bp) { - // We pre-computed these when adding the edge to sort first by cost, then by identity - return ap->user() > bp->user(); - } - - struct EdgeListCmp final { - bool operator()(const EdgeList* ap, const EdgeList* bp) const { - // Compare heads - return edgeCmp(bp->back(), ap->back()); - } - }; - - static Vertex* castVertexp(V3GraphVertex* vxp) { return static_cast(vxp); } - static const Vertex* castVertexp(const V3GraphVertex* vxp) { - return static_cast(vxp); - } - -public: - // From *this, populate *mstp with the minimum spanning tree. - // *mstp must be initially empty. - void makeMinSpanningTree(TspGraphTmpl* mstp) { - UASSERT(mstp->empty(), "Output graph must start empty"); - - // Use Prim's algorithm to efficiently construct the MST. - - uint32_t vertCount = 0; - for (V3GraphVertex& vtx : vertices()) { - mstp->addVertex(castVertexp(&vtx)->key()); - vertCount++; - } - - // Allocate storage for per vertex edge lists up front. - std::vector allocatedEdgeLists{vertCount}; - - // Index of vertex in visitation order (used for indexing allocatedEdgeLists) - uint32_t vertIdx = 0; - - // We keep pending edges as a sorted set of sorted vectors. This allows us to find the - // lowest cost edge quickly, while also reducing the cost of inserting batches of new - // edges, which is what we need in this algorithm. - std::set pendingEdgeListps; - - const auto visit = [&](V3GraphVertex* vtxp) { -#ifdef VL_DEBUG // Very hot, so only in debug - UASSERT(vtxp->user() == VertexState::CLEAR, "Vertex visited twice"); -#endif - // Mark vertex as visited - vtxp->user(VertexState::MST_VISITED); - // Allocate new edge list - EdgeList* const newEdgesp = &allocatedEdgeLists[vertIdx++]; - // Gather out edges of this vertex - for (V3GraphEdge& edge : vtxp->outEdges()) { - // Don't add edges leading to vertices we already visited. This is a highly - // connected graph, so this greatly reduces the cost of maintaining the pending - // set. - if (edge.top()->user() == VertexState::MST_VISITED) continue; - newEdgesp->push_back(&edge); - } - // If no relevant out edges, then we are done - if (newEdgesp->empty()) return; - // Sort new edge list - std::sort(newEdgesp->begin(), newEdgesp->end(), edgeCmp); - // Add edge list to pending set - pendingEdgeListps.insert(newEdgesp); - }; - - // To start, choose an arbitrary vertex and visit it. - visit(vertices().frontp()); - - // Repeatedly find the least costly edge in the pending set. - // If it connects to an unvisited node, visit that node and update - // the pending edge set. If it connects to an already visited node, - // discard it and repeat again. - while (!pendingEdgeListps.empty()) { - // Grab lowest cost edge list - auto it = pendingEdgeListps.begin(); - - // Grab lowest cost edge - EdgeList* const bestEdgeListp = *it; - const V3GraphEdge* const bestEdgep = bestEdgeListp->back(); - - // Remove the lowest cost edge list. We will remove its lowest cost element, and either - // we are done with (if it had a single element) it in which case it will be discarded, - // or the cost of the new head element might be different, so we will need to re-insert - // it in the right place. In either case, it needs to be removed. - pendingEdgeListps.erase(it); - - // If the lowest cost edge list is not a singleton list, then pop the lowest cost - // edge and re-insert the remaining edge list into the pending set. - if (bestEdgeListp->size() > 1) { - bestEdgeListp->pop_back(); - pendingEdgeListps.insert(bestEdgeListp); - } - - // Grab the target vertex - Vertex* const neighborp = castVertexp(bestEdgep->top()); - - // If the neighbour is not yet visited - if (neighborp->user() == VertexState::CLEAR) { - // Visit it - visit(neighborp); - - // Create the edge in our output MST graph - Vertex* const from_vertexp = castVertexp(bestEdgep->fromp()); - mstp->addEdge(from_vertexp->key(), neighborp->key(), bestEdgep->weight()); - -#if VL_DEBUG // Very hot loop, so only in debug - UASSERT(from_vertexp->user() == MST_VISITED, - "bestEdgep->fromp() should be already seen"); -#endif - } - } - - UASSERT(vertIdx == vertCount, "Should have visited all vertices"); - } - - // Populate *outp with a minimal perfect matching of *this. - // *outp must be initially empty. - void perfectMatching(const std::vector& oddKeys, TspGraphTmpl* outp) { - UASSERT(outp->empty(), "Output graph must start empty"); - - const std::list& odds = keysToVertexList(oddKeys); - UASSERT(odds.size() % 2 == 0, "number of odd-order nodes should be even"); - - for (Vertex* const vtxp : odds) { - outp->addVertex(vtxp->key()); - vtxp->user(VertexState::UNMATCHED_ODD); - } - - // TODO: The true Christofides algorithm calls for minimum-weight - // perfect matching. Instead, we have a simple greedy algorithm - // which might get close to the minimum, maybe, with luck? - // - // TODO: Revisit this. It's possible to compute the true minimum in - // N*N*log(N) time using variants of the Blossom algorithm. - // Implementing Blossom looks hard, maybe we can use an existing - // open source implementation -- for example the "LEMON" library - // which has a TSP solver. - - // ----- - - // Gather and sort all edges. We use a vector then sort, because this is faster than a - // sorted set. Reuse the comparator from Prim's routine (note it a 'greater', not a - // 'lesser' comparator). The logic is the same here. - // - // Note that there are two V3GraphEdge's representing a single bidir edge. While we could - // just add both to the pending list and get the same result, we will only add one (based - // on fast pointer comparison - this still yields deterministic results), in order to - // reduce the size of the working set. - std::vector pendingEdges; - - for (Vertex* const fromp : odds) { - for (V3GraphEdge& edge : fromp->outEdges()) { - Vertex* const top = castVertexp(edge.top()); - // There are two edges (in both directions) between these two vertices. Keep one. - if (fromp > top) continue; - // We only care about edges between the odd-order vertices - if (top->user() != VertexState::UNMATCHED_ODD) continue; - // Add to candidate list - pendingEdges.push_back(&edge); - } - } - - // Sort reverse iterators. This yields ascending order with a 'greater' comparator. - std::sort(pendingEdges.rbegin(), pendingEdges.rend(), edgeCmp); - - // Iterate over all edges, in order from low to high cost. - // For any edge whose ends are both odd-order vertices which - // haven't been matched yet, match them. - for (V3GraphEdge* const edgep : pendingEdges) { - Vertex* const fromp = castVertexp(edgep->fromp()); - Vertex* const top = castVertexp(edgep->top()); - if (fromp->user() == VertexState::UNMATCHED_ODD - && top->user() == VertexState::UNMATCHED_ODD) { - outp->addEdge(fromp->key(), top->key(), edgep->weight()); - fromp->user(VertexState::CLEAR); - top->user(VertexState::CLEAR); - } - } - } - - void combineGraph(const TspGraphTmpl& g) { - std::unordered_set edges_done; - for (const V3GraphVertex& vtx : g.vertices()) { - const Vertex* const fromp = castVertexp(&vtx); - for (const V3GraphEdge& edge : fromp->outEdges()) { - const Vertex* const top = castVertexp(edge.top()); - if (edges_done.insert(getEdgeId(&edge)).second) { - addEdge(fromp->key(), top->key(), edge.weight()); - } - } - } - } - - void findEulerTourRecurse(std::unordered_set* markedEdgesp, Vertex* startp, - std::vector* sortedOutp) { - Vertex* cur_vertexp = startp; - - // Go on a random tour. Fun! - std::vector tour; - do { - UINFO(6, "Adding " << cur_vertexp->key() << " to tour."); - tour.push_back(cur_vertexp); - - // Look for an arbitrary edge we've not yet marked - for (V3GraphEdge& edge : cur_vertexp->outEdges()) { - const uint32_t edgeId = getEdgeId(&edge); - if (markedEdgesp->end() == markedEdgesp->find(edgeId)) { - // This edge is not yet marked, so follow it. - markedEdgesp->insert(edgeId); - Vertex* const neighborp = castVertexp(edge.top()); - UINFO(6, "following edge " << edgeId << " from " << cur_vertexp->key() - << " to " << neighborp->key()); - cur_vertexp = neighborp; - goto found; - } - } - v3fatalSrc("No unmarked edges found in tour"); - found:; - } while (cur_vertexp != startp); - UINFO(6, "stopped, got back to start of tour @ " << cur_vertexp->key()); - - // Look for nodes on the tour that still have - // un-marked edges. If we find one, recurse. - for (Vertex* vxp : tour) { - bool recursed; - do { - recursed = false; - // Look for an arbitrary edge at vxp we've not yet marked - for (V3GraphEdge& edge : vxp->outEdges()) { - const uint32_t edgeId = getEdgeId(&edge); - if (markedEdgesp->end() == markedEdgesp->find(edgeId)) { - UINFO(6, "Recursing."); - findEulerTourRecurse(markedEdgesp, vxp, sortedOutp); - recursed = true; - goto recursed; - } - } - recursed:; - } while (recursed); - sortedOutp->push_back(vxp->key()); - } - - if (debug() >= 6) { - UINFO(0, "Tour was:"); - for (const Vertex* vxp : tour) std::cout << "- " << vxp->key() << '\n'; - std::cout << "-\n"; - } - } - - void dumpGraph(std::ostream& os, const string& nameComment) const { - // UINFO(0) as controlled by caller - os << "At " << nameComment << ", dumping graph. Keys:\n"; - for (const V3GraphVertex& vtx : vertices()) { - const Vertex* const tspvp = castVertexp(&vtx); - os << " " << tspvp->key() << '\n'; - for (const V3GraphEdge& edge : tspvp->outEdges()) { - const Vertex* const neighborp = castVertexp(edge.top()); - os << " has edge " << getEdgeId(&edge) << " to " << neighborp->key() << '\n'; - } - } - } - void dumpGraphFilePrefixed(const string& nameComment) const { - if (dumpLevel()) { - const string filename = v3Global.debugFilename(nameComment) + ".txt"; - const std::unique_ptr logp{V3File::new_ofstream(filename)}; - if (logp->fail()) v3fatal("Can't write file: " << filename); - dumpGraph(*logp, nameComment); - } - } - - void findEulerTour(std::vector* sortedOutp) { - UASSERT(sortedOutp->empty(), "Output graph must start empty"); - if (::dumpGraphLevel() >= 6) dumpDotFilePrefixed("findEulerTour"); - std::unordered_set markedEdges; - // Pick a start node - Vertex* const start_vertexp = castVertexp(vertices().frontp()); - findEulerTourRecurse(&markedEdges, start_vertexp, sortedOutp); - } - - std::vector getOddDegreeKeys() const { - std::vector result; - for (const V3GraphVertex& vtx : vertices()) { - const Vertex* const tspvp = castVertexp(&vtx); - const uint32_t degree = vtx.outEdges().size(); - if (degree & 1) result.push_back(tspvp->key()); - } - return result; - } - -private: - Vertex* findVertex(const T_Key& key) const { - const auto it = m_vertices.find(key); - UASSERT(it != m_vertices.end(), "Vertex not found"); - return it->second; - } - VL_UNCOPYABLE(TspGraphTmpl); -}; - -//###################################################################### -// Main algorithm - -void V3TSP::tspSort(const V3TSP::StateVec& states, V3TSP::StateVec* resultp) VL_MT_SAFE { - UASSERT(resultp->empty(), "Output graph must start empty"); - - // Make this TSP implementation work for graphs of size 0 or 1 - // which, unfortunately, is a special case as the following - // code assumes >= 2 nodes. - if (states.empty()) return; - if (states.size() == 1) { - resultp->push_back(*(states.begin())); - return; - } - - // Build the initial graph from the starting state set. - using Graph = TspGraphTmpl; - Graph graph; - for (const auto& state : states) graph.addVertex(state); - for (V3TSP::StateVec::const_iterator it = states.begin(); it != states.end(); ++it) { - for (V3TSP::StateVec::const_iterator jt = it; jt != states.end(); ++jt) { - if (it == jt) continue; - graph.addEdge(*it, *jt, (*it)->cost(*jt)); - } - } - - // Create the minimum spanning tree - Graph minGraph; - graph.makeMinSpanningTree(&minGraph); - if (dumpGraphLevel() >= 6) minGraph.dumpGraphFilePrefixed("minGraph"); - - const std::vector oddDegree = minGraph.getOddDegreeKeys(); - Graph matching; - graph.perfectMatching(oddDegree, &matching); - if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching"); - - // Adds edges to minGraph, the resulting graph will have even number of - // edge counts at every vertex: - minGraph.combineGraph(matching); - - V3TSP::StateVec prelim_result; - minGraph.findEulerTour(&prelim_result); - - UASSERT(prelim_result.size() >= states.size(), "Algorithm size error"); - - // Discard duplicate nodes that the Euler tour might contain. - { - std::unordered_set seen; - for (V3TSP::StateVec::iterator it = prelim_result.begin(); it != prelim_result.end(); - ++it) { - const TspStateBase* const elemp = *it; - const auto itFoundPair = seen.insert(elemp); - if (itFoundPair.second) resultp->push_back(elemp); - } - } - - UASSERT(resultp->size() == states.size(), "Algorithm size error"); - - // Find the most expensive arc and rotate the list so that the most - // expensive arc connects the last and first elements. (Since we're not - // modeling something that actually cycles back, we don't need to pay - // that cost at all.) - { - unsigned max_cost = 0; - unsigned max_cost_idx = 0; - for (unsigned i = 0; i < resultp->size(); ++i) { - const TspStateBase* const ap = (*resultp)[i]; - const TspStateBase* const bp - = (i + 1 == resultp->size()) ? (*resultp)[0] : (*resultp)[i + 1]; - const unsigned cost = ap->cost(bp); - if (cost > max_cost) { - max_cost = cost; - max_cost_idx = i; - } - } - - if (max_cost_idx == resultp->size() - 1) { - // List is already rotated for minimum cost. stop. - return; - } - - V3TSP::StateVec new_result; - unsigned i = max_cost_idx + 1; - UASSERT(i < resultp->size(), "Algorithm size error"); - while (i != max_cost_idx) { - new_result.push_back((*resultp)[i]); - i++; - if (i >= resultp->size()) i = 0; - } - new_result.push_back((*resultp)[i]); - - UASSERT(resultp->size() == new_result.size(), "Algorithm size error"); - *resultp = new_result; - } -} - -//###################################################################### -// Self Tests - -class TspTestState final : public V3TSP::TspStateBase { -public: - TspTestState(unsigned xpos, unsigned ypos) - : m_xpos{xpos} - , m_ypos{ypos} - , m_serial{++s_serialNext} {} - ~TspTestState() override = default; - int cost(const TspStateBase* otherp) const override VL_MT_SAFE { - return cost(dynamic_cast(otherp)); - } - static unsigned diff(unsigned a, unsigned b) VL_PURE { - if (a > b) return a - b; - return b - a; - } - int cost(const TspTestState* otherp) const VL_PURE { - // For test purposes, each TspTestState is merely a point - // on the Cartesian plane; cost is the linear distance - // between two points. - unsigned xabs; - unsigned yabs; - xabs = diff(otherp->m_xpos, m_xpos); - yabs = diff(otherp->m_ypos, m_ypos); - return std::lround(std::sqrt(xabs * xabs + yabs * yabs)); - } - unsigned xpos() const { return m_xpos; } - unsigned ypos() const { return m_ypos; } - - bool operator<(const TspStateBase& other) const override { - return operator<(dynamic_cast(other)); - } - bool operator<(const TspTestState& other) const { return m_serial < other.m_serial; } - -private: - const unsigned m_xpos; - const unsigned m_ypos; - const unsigned m_serial; - static unsigned s_serialNext; -}; - -unsigned TspTestState::s_serialNext = 0; - -void V3TSP::selfTestStates() { - // Linear test -- coords all along the x-axis - { - V3TSP::StateVec states; - const TspTestState s10{10, 0}; - const TspTestState s60{60, 0}; - const TspTestState s20{20, 0}; - const TspTestState s100{100, 0}; - const TspTestState s5{5, 0}; - states.push_back(&s10); - states.push_back(&s60); - states.push_back(&s20); - states.push_back(&s100); - states.push_back(&s5); - - V3TSP::StateVec result; - tspSort(states, &result); - - V3TSP::StateVec expect; - expect.push_back(&s100); - expect.push_back(&s60); - expect.push_back(&s20); - expect.push_back(&s10); - expect.push_back(&s5); - if (VL_UNCOVERABLE(expect != result)) { - for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) { - const TspTestState* const statep = dynamic_cast(*it); - cout << statep->xpos() << " "; - } - cout << endl; - v3fatalSrc("TSP linear self-test fail. Result (above) did not match expectation."); - } - } - - // Second test. Coords are distributed in 2D space. - // Test that tspSort() will rotate the list for minimum cost. - { - V3TSP::StateVec states; - const TspTestState a{0, 0}; - const TspTestState b{100, 0}; - const TspTestState c{200, 0}; - const TspTestState d{200, 100}; - const TspTestState e{150, 150}; - const TspTestState f{0, 150}; - const TspTestState g{0, 100}; - - states.push_back(&a); - states.push_back(&b); - states.push_back(&c); - states.push_back(&d); - states.push_back(&e); - states.push_back(&f); - states.push_back(&g); - - V3TSP::StateVec result; - tspSort(states, &result); - - V3TSP::StateVec expect; - expect.push_back(&f); - expect.push_back(&g); - expect.push_back(&a); - expect.push_back(&b); - expect.push_back(&c); - expect.push_back(&d); - expect.push_back(&e); - - if (VL_UNCOVERABLE(expect != result)) { - for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) { - const TspTestState* const statep = dynamic_cast(*it); - cout << statep->xpos() << "," << statep->ypos() << " "; - } - cout << endl; - v3fatalSrc( - "TSP 2d cycle=false self-test fail. Result (above) did not match expectation."); - } - } -} - -void V3TSP::selfTestString() { - using Graph = TspGraphTmpl; - Graph graph; - graph.addVertex("0"); - graph.addVertex("1"); - graph.addVertex("2"); - graph.addVertex("3"); - - graph.addEdge("0", "1", 3943); - graph.addEdge("0", "2", 3456); - graph.addEdge("0", "3", 4920); - graph.addEdge("1", "2", 2730); - graph.addEdge("1", "3", 8199); - graph.addEdge("2", "3", 4130); - - Graph minGraph; - graph.makeMinSpanningTree(&minGraph); - if (dumpGraphLevel() >= 6) minGraph.dumpGraphFilePrefixed("minGraph"); - - const std::vector oddDegree = minGraph.getOddDegreeKeys(); - Graph matching; - graph.perfectMatching(oddDegree, &matching); - if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching"); - - minGraph.combineGraph(matching); - - std::vector result; - minGraph.findEulerTour(&result); - - std::vector expect; - expect.emplace_back("0"); - expect.emplace_back("2"); - expect.emplace_back("1"); - expect.emplace_back("2"); - expect.emplace_back("3"); - - if (VL_UNCOVERABLE(expect != result)) { - for (const string& i : result) cout << i << " "; - cout << endl; - v3fatalSrc("TSP string self-test fail. Result (above) did not match expectation."); - } -} - -void V3TSP::selfTest() { - selfTestString(); - selfTestStates(); -} diff --git a/src/V3TSP.h b/src/V3TSP.h deleted file mode 100644 index 840f5fa96..000000000 --- a/src/V3TSP.h +++ /dev/null @@ -1,57 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementation of Christofides algorithm to -// approximate the solution to the traveling salesman problem. -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#ifndef VERILATOR_V3TSP_H_ -#define VERILATOR_V3TSP_H_ - -#include "config_build.h" -#include "verilatedos.h" - -#include "V3Error.h" - -#include - -namespace V3TSP { -// Perform a "Traveling Salesman Problem" optimizing sort -// on any type you like -- so long as inherits from TspStateBase. - -class TspStateBase VL_NOT_FINAL { -public: - // This is the cost function that the TSP sort will minimize. - // All costs in V3TSP are int, chosen to match the type of - // V3GraphEdge::weight() which will reflect each edge's cost. - virtual int cost(const TspStateBase* otherp) const VL_MT_SAFE = 0; - - // This operator< must place a meaningless, arbitrary, but - // stable order on all TspStateBase's. It's used only to - // key maps so that iteration is stable, without relying - // on pointer values that could lead to nondeterminism. - virtual bool operator<(const TspStateBase& otherp) const = 0; - - virtual ~TspStateBase() = default; -}; - -using StateVec = std::vector; - -// Given an unsorted set of TspState's, sort them to minimize -// the transition cost for walking the sorted list. -void tspSort(const StateVec& states, StateVec* resultp) VL_MT_SAFE; - -void selfTest() VL_MT_DISABLED; -} // namespace V3TSP - -#endif // Guard diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 5bae5d0aa..72e779d1e 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -248,9 +248,9 @@ private: UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked task"); TaskFTaskVertex* const taskVtxp = getFTaskVertex(nodep->taskp()); new TaskEdge{&m_callGraph, m_curVxp, taskVtxp}; - if (isVirtualIfaceMethodCall(nodep) && isIfaceFTaskScope(getScope(nodep->taskp()))) { - taskVtxp->needsNonInlineCFunc(true); - } + // Virtual-interface method calls dispatch through a runtime handle and + // must not be inlined. + if (isVirtualIfaceMethodCall(nodep)) taskVtxp->needsNonInlineCFunc(true); // Do we have to disable inlining the function? const V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp()); if (!taskVtxp->noInline()) { // Else short-circuit below @@ -306,10 +306,9 @@ private: } void visit(AstClass* nodep) override { // Move initial statements into the constructor - VL_RESTORER(m_initialps); + VL_RESTORER_CLEAR(m_initialps); VL_RESTORER(m_ctorp); VL_RESTORER(m_classp); - m_initialps.clear(); m_ctorp = nullptr; m_classp = nodep; { // Find m_initialps, m_ctor @@ -1638,6 +1637,7 @@ class TaskVisitor final : public VNVisitor { // Create cloned statements AstNode* beginp; AstCNew* cnewp = nullptr; + // getScope() is safe here: TaskStateVisitor stamped all FTask scopes before this pass. const bool virtualIfaceCall = TaskStateVisitor::isVirtualIfaceMethodCall(nodep) && TaskStateVisitor::isIfaceFTaskScope(m_statep->getScope(nodep->taskp())); diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index 2a7bb37e3..8cee034fa 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -1094,6 +1094,7 @@ class TraceVisitor final : public VNVisitor { // Create the trace registration function m_regFuncp = new AstCFunc{m_topScopep->fileline(), "trace_register", m_topScopep}; m_regFuncp->argTypes(v3Global.opt.traceClassBase() + "* tracep"); + m_regFuncp->entryPoint(true); m_regFuncp->isTrace(true); m_regFuncp->slow(true); m_regFuncp->isStatic(false); diff --git a/src/V3TraceDecl.cpp b/src/V3TraceDecl.cpp index 820a314c7..29c625f94 100644 --- a/src/V3TraceDecl.cpp +++ b/src/V3TraceDecl.cpp @@ -485,7 +485,7 @@ class TraceDeclVisitor final : public VNVisitor { AstNodeDType* const skipTypep = nodep->skipRefp(); // offset and direction args added in EmitCImp std::string callArgs{"tracep, \"" + VIdProtect::protect(m_traName) + "\""}; - VL_RESTORER(m_traName); + VL_RESTORER_COPY(m_traName); FileLine* const flp = skipTypep->fileline(); const DtypeFuncKey dtypeKey{skipTypep, m_traVscp->varp()->varType()}; @@ -530,7 +530,7 @@ class TraceDeclVisitor final : public VNVisitor { void declUnpackedArray(AstUnpackArrayDType* const nodep, bool newFunc) { string prefixName(newFunc ? "name" : m_traName); - VL_RESTORER(m_traName); + VL_RESTORER_COPY(m_traName); FileLine* const flp = nodep->fileline(); addToSubFunc(new AstTracePushPrefix{flp, prefixName, VTracePrefixType::ARRAY_UNPACKED, @@ -568,7 +568,7 @@ class TraceDeclVisitor final : public VNVisitor { string prefixName(newFunc ? "name" : m_traName); AstNodeDType* const subtypep = nodep->subDTypep()->skipRefToEnump(); - VL_RESTORER(m_traName); + VL_RESTORER_COPY(m_traName); FileLine* const flp = nodep->fileline(); addToSubFunc(new AstTracePushPrefix{flp, prefixName, VTracePrefixType::ARRAY_PACKED, @@ -918,7 +918,7 @@ class TraceDeclVisitor final : public VNVisitor { return; } - VL_RESTORER(m_traName); + VL_RESTORER_COPY(m_traName); FileLine* const flp = nodep->fileline(); int nMembers = 0; @@ -980,6 +980,7 @@ public: AstCFunc* rootFuncp = nullptr; if (!v3Global.opt.libCreate().empty()) { rootFuncp = newCFunc(flp, "trace_init_root"); + rootFuncp->entryPoint(true); for (size_t i = 0; i < m_topScopeRootFuncCount; ++i) { AstCCall* const callp = new AstCCall{flp, topScopeFuncps.at(i)}; callp->dtypeSetVoid(); @@ -1017,6 +1018,7 @@ public: // Set name of top level function AstCFunc* const topFuncp = m_topFuncps.front(); topFuncp->name("trace_init_top"); + topFuncp->entryPoint(true); if (rootFuncp && v3Global.opt.debugCheck()) checkCallsRecurse(rootFuncp); checkCalls(topFuncp); diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 6fa174ed0..226c5138e 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -352,7 +352,9 @@ class TristatePinVisitor final : public TristateBaseVisitor { TristateGraph& m_tgraph; const bool m_lvalue; // Flip to be an LVALUE // VISITORS - void visit(AstVarRef* nodep) override { + // AstNodeVarRef, not just AstVarRef: a cross-hierarchy pin expression into an + // interface (.pin(iface.net)) is an AstVarXRef and needs the same access flip. + void visit(AstNodeVarRef* nodep) override { UASSERT_OBJ(!nodep->access().isRW(), nodep, "Tristate unexpected on R/W access flip"); if (m_lvalue && !nodep->access().isWriteOrRW()) { UINFO(9, " Flip-to-LValue " << nodep); @@ -405,6 +407,11 @@ class TristateVisitor final : public TristateBaseVisitor { struct AuxAstVar final { AstPull* pullp = nullptr; // pullup/pulldown direction (whole variable) AstVar* outVarp = nullptr; // output __out var + bool ifaceTristate = false; // Interface var known to be tristate (set when the + // interface module is processed). Lets a module that + // drives this var across hierarchy with a plain (non-Z) + // assign be recognised as a tristate contributor even + // though that module's own graph has no Z on the net. std::unordered_map bitPulls; // Per-bit pull: bit_index -> direction (1=up, 0=down) }; @@ -439,6 +446,7 @@ class TristateVisitor final : public TristateBaseVisitor { int m_unique = 0; bool m_alhs = false; // On LHS of assignment bool m_inAlias = false; // Inside alias statement + bool m_processedIfaces = false; // Interface modules already processed (interfaces-first pass) VStrength m_currentStrength = VStrength::STRONG; // Current strength of assignment, // Used only on LHS of assignment const AstNode* m_logicp = nullptr; // Current logic being built @@ -755,6 +763,9 @@ class TristateVisitor final : public TristateBaseVisitor { } } } else if (isIfaceTri) { + // Mark here too, so a net made tristate only by an external 'z driver + // still captures a plain cross-hierarchy driver processed later. + m_varAux(invarp).ifaceTristate = true; // Interface tristate vars: drivers from different interface instances // (different VarXRef dotted paths) must be processed separately. // E.g. io_ifc.d and io_ifc_local.d both target the same AstVar d in @@ -780,7 +791,11 @@ class TristateVisitor final : public TristateBaseVisitor { } } else if (VN_IS(nodep, Iface) && !invarp->isIO()) { // Local driver in an interface module - use contribution mechanism - // so it can be combined with any external drivers later + // so it can be combined with any external drivers later. Record that + // this interface net is tristate, so a module that drives it across + // hierarchy with a plain (non-Z) assign is also routed through the + // contribution mechanism (its own graph has no Z to mark it tristate). + m_varAux(invarp).ifaceTristate = true; insertTristatesSignal(nodep, invarp, refsp, true, "", "", nullptr); } else { insertTristatesSignal(nodep, invarp, refsp, false, "", "", nullptr); @@ -2080,6 +2095,15 @@ class TristateVisitor final : public TristateBaseVisitor { if (m_graphing) { if (nodep->access().isWriteOrRW()) associateLogic(nodep, nodep->varp()); if (nodep->access().isReadOrRW()) associateLogic(nodep->varp(), nodep); + // Only interface tristate nets need this: a plain (non-Z) cross-hierarchy + // driver has no Z in its own module's graph, so mark the net tristate here to + // collect the driver as a contribution. The ifaceTristate flag is only set for + // interface nets; a non-interface cross-module tri driver does nothing here and + // is instead rejected (E_UNSUPPORTED) later in insertTristates. + if (nodep->access().isWriteOrRW() && VN_IS(nodep, VarXRef) + && m_varAux(nodep->varp()).ifaceTristate) { + m_tgraph.setTristate(nodep->varp()); + } } else { if (nodep->user2() & U2_NONGRAPH) return; // Processed nodep->user2Or(U2_NONGRAPH); @@ -2160,12 +2184,15 @@ class TristateVisitor final : public TristateBaseVisitor { } void visit(AstNodeModule* nodep) override { + // Interfaces are processed first in the constructor; skip the duplicate visit + // during the later iterateChildrenBackwardsConst() pass over all modules. + if (m_processedIfaces && VN_IS(nodep, Iface)) return; UINFO(8, dbgState() << nodep); VL_RESTORER(m_modp); VL_RESTORER(m_graphing); VL_RESTORER(m_unique); - VL_RESTORER(m_lhsmap); - VL_RESTORER(m_assigns); + VL_RESTORER_CLEAR(m_lhsmap); + VL_RESTORER_CLEAR(m_assigns); // Not preserved, needs pointer instead: TristateGraph origTgraph = m_tgraph; UASSERT_OBJ(m_tgraph.empty(), nodep, "Unsupported: NodeModule under NodeModule"); @@ -2174,8 +2201,6 @@ class TristateVisitor final : public TristateBaseVisitor { m_tgraph.clearAndCheck(); m_unique = 0; m_logicp = nullptr; - m_lhsmap.clear(); - m_assigns.clear(); m_modp = nodep; // Walk the graph, finding all variables and tristate constructs m_graphing = true; @@ -2296,6 +2321,18 @@ public: // CONSTRUCTORS explicit TristateVisitor(AstNetlist* netlistp) { m_tgraph.clearAndCheck(); + // Process interface modules first, so an interface tristate net is recorded + // (AuxAstVar::ifaceTristate) before any module that drives it across hierarchy is + // graphed. A module driving such a net with a plain (non-Z) assign has no Z in its + // own graph to mark the net tristate, so without this its driver would be dropped. + // Collect only the interfaces (reverse declaration order to match the pass below). + std::vector ifacesp; + for (AstNode* modp = netlistp->modulesp(); modp; modp = modp->nextp()) { + if (VN_IS(modp, Iface)) ifacesp.push_back(VN_AS(modp, NodeModule)); + } + for (auto it = ifacesp.rbegin(); it != ifacesp.rend(); ++it) iterate(*it); + m_processedIfaces = true; + // Then the rest in the historical order; interfaces are skipped (already done). iterateChildrenBackwardsConst(netlistp); // Combine interface tristate contributions after all modules processed diff --git a/src/V3Udp.cpp b/src/V3Udp.cpp index 7234061ed..2ee5908bb 100644 --- a/src/V3Udp.cpp +++ b/src/V3Udp.cpp @@ -56,14 +56,12 @@ class UdpVisitor final : public VNVisitor { VL_RESTORER(m_primp); VL_RESTORER(m_outputInitVerfp); VL_RESTORER(m_isFirstOutput); - VL_RESTORER(m_inputVars); - VL_RESTORER(m_outputVars); + VL_RESTORER_CLEAR(m_inputVars); + VL_RESTORER_CLEAR(m_outputVars); m_outputInitVerfp = nullptr; m_primp = nodep; m_isFirstOutput = false; iterateChildren(nodep); - m_inputVars.clear(); - m_outputVars.clear(); } void visit(AstVar* nodep) override { // Push the input and output vars for primitive. diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp index 727e97840..57e12e478 100644 --- a/src/V3Unknown.cpp +++ b/src/V3Unknown.cpp @@ -51,15 +51,14 @@ class UnknownVisitor final : public VNVisitor { static const std::string s_xrandPrefix; // STATE - across all visitors - VDouble0 m_statUnkVars; // Statistic tracking + VDouble0 m_statUnkVars; // Statistic of xrand variable created + VDouble0 m_statElses; // Statistic of else branches created for array selects V3UniqueNames m_lvboundNames; // For generating unique temporary variable names std::unique_ptr m_xrandNames; // For generating unique temporary variable names // STATE - for current visit position (use VL_RESTORER) AstNodeModule* m_modp = nullptr; // Current module AstNodeFTask* m_ftaskp = nullptr; // Current function/task - AstAssignDly* m_assigndlyp = nullptr; // Current assignment - AstNode* m_timingControlp = nullptr; // Current assignment's intra timing control bool m_constXCvt = false; // Convert X's bool m_allowXUnique = true; // Allow unique assignments @@ -69,38 +68,21 @@ class UnknownVisitor final : public VNVisitor { if (m_ftaskp) { varp->funcLocal(true); varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_ftaskp->stmtsp()->addHereThisAsNext(varp); + if (m_ftaskp->stmtsp()) + m_ftaskp->stmtsp()->addHereThisAsNext(varp); + else + m_ftaskp->addStmtsp(varp); } else { - m_modp->stmtsp()->addHereThisAsNext(varp); + if (m_modp->stmtsp()) + m_modp->stmtsp()->addHereThisAsNext(varp); + else + m_modp->addStmtsp(varp); } } void replaceBoundLvalue(AstNodeExpr* nodep, AstNodeExpr* condp) { // Spec says a out-of-range LHS SEL results in a NOP. - // This is a PITA. We could: - // 1. IF(...) around an ASSIGN, - // but that would break a "foo[TOO_BIG]=$fopen(...)". - // 2. Hack to extend the size of the output structure - // by one bit, and write to that temporary, but never read it. - // That makes there be two widths() and is likely a bug farm. - // 3. Make a special SEL to choose between the real lvalue - // and a temporary NOP register. - // 4. Assign to a temp, then IF that assignment. - // This is suspected to be nicest to later optimizations. - // 4 seems best but breaks later optimizations. 3 was tried, - // but makes a mess in the emitter as lvalue switching is needed. So 4. - // SEL(...) -> temp - // if (COND(LTE(bit<=maxlsb))) ASSIGN(SEL(...)),temp) - const bool needDly = (m_assigndlyp != nullptr); - if (m_assigndlyp) { - // Delayed assignments become normal assignments, - // then the temp created becomes the delayed assignment - AstNode* const newp = new AstAssign{m_assigndlyp->fileline(), - m_assigndlyp->lhsp()->unlinkFrBackWithNext(), - m_assigndlyp->rhsp()->unlinkFrBackWithNext()}; - m_assigndlyp->replaceWith(newp); - VL_DO_CLEAR(pushDeletep(m_assigndlyp), m_assigndlyp = nullptr); - } + // We wrap the expression into IF AstNodeExpr* prep = nodep; // Scan back to put the condlvalue above all selects (IE top of the lvalue) @@ -121,32 +103,35 @@ class UnknownVisitor final : public VNVisitor { // Already exists; rather than IF(a,... IF(b... optimize to IF(a&&b, // Saves us teaching V3Const how to optimize, and it won't be needed again. if (const AstIf* const ifp = VN_AS(prep->user2p(), If)) { - UASSERT_OBJ(!needDly, prep, "Should have already converted to non-delay"); VNRelinker replaceHandle; AstNodeExpr* const earliercondp = ifp->condp()->unlinkFrBack(&replaceHandle); AstNodeExpr* const newp = new AstLogAnd{condp->fileline(), condp, earliercondp}; UINFO(4, "Edit BOUNDLVALUE " << newp); replaceHandle.relink(newp); } else { - AstVar* const varp - = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), prep->dtypep()}; - addVar(varp); AstNode* stmtp = prep->backp(); // Grab above point before we replace 'prep' while (!VN_IS(stmtp, NodeStmt)) stmtp = stmtp->backp(); - - prep->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE}); - if (m_timingControlp) m_timingControlp->unlinkFrBack(); - AstIf* const newp = new AstIf{ - fl, condp, - (needDly - ? static_cast(new AstAssignDly{ - fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}) - : static_cast(new AstAssign{ - fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}))}; + VNRelinker replaceHandle; + AstNode* const origStmtp = stmtp->unlinkFrBack(&replaceHandle); + AstNode* elseStmtp = nullptr; + const bool hasSideEffects = origStmtp->exists( + [](AstNode* const np) { return !np->isPure() || np->isTimingControl(); }); + if (hasSideEffects) { + // Copy original statement and replace `prep` with reference to tmp var to make + // sure that side effect will take place + m_statElses++; + elseStmtp = origStmtp->cloneTree(false); + AstVar* const varp = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), + prep->dtypep()}; + addVar(varp); + AstNode* const prepCopyp = prep->clonep(); + prepCopyp->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE}); + pushDeletep(prepCopyp); + } + AstIf* const newp = new AstIf{fl, condp, origStmtp, elseStmtp}; + replaceHandle.relink(newp); newp->branchPred(VBranchPred::BP_LIKELY); newp->isBoundsCheck(true); - UINFOTREE(9, newp, "", "_new"); - stmtp->addNextHere(newp); prep->user2p(newp); // Save so we may LogAnd it next time } } @@ -208,23 +193,6 @@ class UnknownVisitor final : public VNVisitor { m_ftaskp = nodep; iterateChildren(nodep); } - void visit(AstAssignDly* nodep) override { - VL_RESTORER(m_assigndlyp); - VL_RESTORER(m_timingControlp); - m_assigndlyp = nodep; - m_timingControlp = nodep->timingControlp(); - VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - } - void visit(AstAssignW* nodep) override { - VL_RESTORER(m_timingControlp); - m_timingControlp = nodep->timingControlp(); - VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - } - void visit(AstNodeAssign* nodep) override { - VL_RESTORER(m_timingControlp); - m_timingControlp = nodep->timingControlp(); - iterateChildren(nodep); - } void visit(AstCaseItem* nodep) override { VL_RESTORER(m_constXCvt); m_constXCvt = false; // Avoid losing the X's in casex @@ -296,7 +264,7 @@ class UnknownVisitor final : public VNVisitor { } else { // X or Z's become mask, ala case statements. V3Number nummask{rhsp, rhsp->width()}; - nummask.opBitsNonX(VN_AS(rhsp, Const)->num()); + nummask.opBitsNonXZ(VN_AS(rhsp, Const)->num()); V3Number numval{rhsp, rhsp->width()}; numval.opBitsOne(VN_AS(rhsp, Const)->num()); AstNodeExpr* const and1p = new AstAnd{nodep->fileline(), lhsp, @@ -546,7 +514,11 @@ class UnknownVisitor final : public VNVisitor { } else if (nodeDtp->isString()) { xnum = V3Number{nodep, V3Number::String{}, ""}; } else { - xnum.setAllBitsX(); + if (nodeDtp->isFourstate()) { + xnum.setAllBitsX(); + } else { + xnum.setAllBits0(); + } } AstNode* const newp = new AstCond{nodep->fileline(), condp, nodep, new AstConst{nodep->fileline(), xnum}}; @@ -584,6 +556,7 @@ public: } ~UnknownVisitor() override { // V3Stats::addStat("Unknowns, variables created", m_statUnkVars); + V3Stats::addStat("Unknowns, else branches created", m_statElses); } }; diff --git a/src/V3Unroll.cpp b/src/V3Unroll.cpp index ce677324b..62cf42fa9 100644 --- a/src/V3Unroll.cpp +++ b/src/V3Unroll.cpp @@ -62,6 +62,8 @@ struct UnrollStats final { Stat m_nPragmaDisabled{"Pragma unroll_disable"}; Stat m_nUnrolledLoops{"Unrolled loops"}; Stat m_nUnrolledIters{"Unrolled iterations"}; + Stat m_bitScanLowered{"Lowered priority-encoder to mostsetbitp1"}; + Stat m_countOnesLowered{"Lowered count-set-bits to countones"}; }; //###################################################################### @@ -375,10 +377,12 @@ class UnrollOneVisitor final : VNVisitor { process(nodep); } void visit(AstJumpGo* nodep) override { + if (!m_ok) return; // Remove trailing dead code if (nodep->nextp()) pushDeletep(nodep->nextp()->unlinkFrBackWithNext()); } void visit(AstLoop* nodep) override { + if (!m_ok) return; m_bindings.checkpoint(); std::pair pair = UnrollOneVisitor::apply(m_stats, m_bindings, nodep); @@ -420,6 +424,157 @@ class UnrollAllVisitor final : VNVisitor { UnrollStats m_stats; // Statistic tracking UnrolllBindings m_bindings; // Variable bindings + // METHODS + // Peel value-preserving width casts (Extend/ExtendS, or a low-bits Sel with lsb 0) to the + // underlying VarRef. A Sel kept narrower than 'minWidth' is a lossy narrowing (idx[1:0]) + // and is rejected. + static AstVarRef* unwrapToVarRef(AstNodeExpr* nodep, int minWidth) { + while (true) { + if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) return refp; + if (AstExtend* const ep = VN_CAST(nodep, Extend)) { + nodep = ep->lhsp(); + } else if (AstExtendS* const ep = VN_CAST(nodep, ExtendS)) { + nodep = ep->lhsp(); + } else if (AstSel* const sp = VN_CAST(nodep, Sel)) { + const AstConst* const lsbp = VN_CAST(sp->lsbp(), Const); + if (!lsbp || lsbp->toUInt() != 0 || sp->width() < minWidth) return nullptr; + nodep = sp->fromp(); + } else { + return nullptr; + } + } + } + // True if 'nodep' is exactly '1 + var' for 'vscp' (V3Const puts the constant on the LHS). + // Passing the add's width as minWidth rejects a lossy increment like 32'(i[1:0]) + 1. + bool isVarPlus1(AstNode* nodep, const AstVarScope* vscp) { + AstAdd* const addp = VN_CAST(nodep, Add); + if (!addp || !addp->lhsp()->isOne()) return false; + const AstVarRef* const r = unwrapToVarRef(addp->rhsp(), addp->width()); + return r && r->varScopep() == vscp; + } + // Resize the 32-bit reduction to the accumulator width; truncating the low bits matches + // the original counted loop's wrap-around. + static AstNodeExpr* resizeToWidth(AstNodeExpr* exprp, const AstVarRef* targetRefp) { + const int width = targetRefp->width(); + if (width == 32) return exprp; + FileLine* const flp = exprp->fileline(); + if (width < 32) return new AstSel{flp, exprp, 0, width}; + AstExtend* const extp = new AstExtend{flp, exprp}; + extp->dtypeFrom(targetRefp); + return extp; + } + // Match a strict ascending loop bound 'idx < W'. V3Const canonicalizes this to the + // 'W > idx' form (Gt unsigned, GtS signed), so only that form is matched. + static bool ascendingBound(AstNodeExpr* condp, AstConst*& wp, AstVarRef*& idxRefp) { + if (!VN_IS(condp, Gt) && !VN_IS(condp, GtS)) return false; + AstNodeBiop* const bp = VN_AS(condp, NodeBiop); + wp = VN_CAST(bp->lhsp(), Const); + idxRefp = VN_CAST(bp->rhsp(), VarRef); + return wp && idxRefp && !wp->num().isFourState(); + } + // Recognize the redundant in-range guard Verilator auto-inserts for a select into a + // non-power-of-two vector. V3Const canonicalizes 'idx <= C' to '(C >= idx)' (Gte/GteS, + // const on the LHS), so only that form occurs; with C >= W-1 it is always true for idx + // in 0..W-1. + static bool isInRangeGuard(AstNodeExpr* condp, const AstVarScope* idxVscp, uint32_t width, + int addrBits) { + if (!VN_IS(condp, Gte) && !VN_IS(condp, GteS)) return false; + AstNodeBiop* const bp = VN_AS(condp, NodeBiop); + const AstConst* const cp = VN_CAST(bp->lhsp(), Const); + if (!cp || cp->num().isFourState() || cp->toUInt() < width - 1) return false; + const AstVarRef* const r = unwrapToVarRef(bp->rhsp(), addrBits); + return r && r->varScopep() == idxVscp; + } + // Recognize a single-bit scan loop over all W bits of 'vec' (idx 0..W-1, target + // pre-zeroed) and lower it to a bit-reduction primitive. Two idioms are matched: + // target = 0; idx = 0; + // loop { looptest(W > idx); if (...vec[idx]...) target = ; idx = idx + 1; } + // where, when W == width(vec): + // = idx + 1 => target = $mostsetbitp1(vec) (leading-one / bit-width) + // = target + 1 => target = $countones(vec) (population count) + bool tryLowerBitScanLoop(AstLoop* loopp) { + AstLoopTest* const testp = VN_CAST(loopp->stmtsp(), LoopTest); + if (!testp) return false; + AstIf* const ifp = VN_CAST(testp->nextp(), If); + if (!ifp) return false; + AstAssign* const incp = VN_CAST(ifp->nextp(), Assign); + if (!incp || incp->nextp()) return false; + AstConst* wp = nullptr; + AstVarRef* idxRefp = nullptr; + if (!ascendingBound(testp->condp(), wp, idxRefp)) return false; + AstVarScope* const idxVscp = idxRefp->varScopep(); + const uint32_t width = wp->toUInt(); + // Bits needed to address all W bits of 'vec' (clog2(W)); a narrower index is lossy. + const int addrBits = width <= 1 ? 1 : V3Number::log2b(width - 1) + 1; + const AstConst* const idxInitp = m_bindings.get(idxVscp); + if (!idxInitp || !idxInitp->isZero()) return false; + AstVarRef* const incLhsp = VN_CAST(incp->lhsp(), VarRef); + if (!incLhsp || incLhsp->varScopep() != idxVscp) return false; + if (!isVarPlus1(incp->rhsp(), idxVscp)) return false; + if (ifp->elsesp()) return false; + AstAssign* const thenp = VN_CAST(ifp->thensp(), Assign); + if (!thenp || thenp->nextp()) return false; + AstVarRef* const targetRefp = VN_CAST(thenp->lhsp(), VarRef); + if (!targetRefp) return false; + AstVarScope* const targetVscp = targetRefp->varScopep(); + if (targetVscp == idxVscp) return false; + const bool isLeadingOne = isVarPlus1(thenp->rhsp(), idxVscp); + const bool isCountOnes = !isLeadingOne && isVarPlus1(thenp->rhsp(), targetVscp); + if (!isLeadingOne && !isCountOnes) return false; + // If-cond is the 1-bit select 'vec[idx]', possibly wrapped in the redundant in-range + // guard Verilator auto-inserts (as 'guard && sel') for a non-power-of-two vector: + // '(idx <= W-1) && vec[idx]' (default / --x-assign 0; a LogAnd), or + // '(idx <= W-1) ? vec[idx] : ' (--x-assign unique; a Cond). + // The guard is always true for idx in 0..W-1, so peel it to reach the select. Any + // other compound condition (e.g. 'vec[idx] && en') leaves a non-select, rejected below. + AstNodeExpr* condp = ifp->condp(); + if (AstLogAnd* const andp = VN_CAST(condp, LogAnd)) { + if (isInRangeGuard(andp->lhsp(), idxVscp, width, addrBits)) condp = andp->rhsp(); + } else if (AstCond* const ternp = VN_CAST(condp, Cond)) { + if (isInRangeGuard(ternp->condp(), idxVscp, width, addrBits)) condp = ternp->thenp(); + } + AstSel* const selp = VN_CAST(condp, Sel); + if (!selp || selp->width() != 1) return false; + const AstVarRef* const fromp = VN_CAST(selp->fromp(), VarRef); + if (!fromp) return false; + const AstVarScope* const fromVscp = fromp->varScopep(); + if (fromVscp == idxVscp || fromVscp == targetVscp) return false; + AstNodeExpr* const vecExprp = selp->fromp(); + // Must scan all W bits of 'vec', indexed by exactly 'idx' (address kept >= clog2(W), + // so a lossy narrowing like vec[idx[2:0]] is rejected). + if (static_cast(width) != vecExprp->width()) return false; + const AstVarRef* const idxInSel = unwrapToVarRef(selp->lsbp(), addrBits); + if (!idxInSel || idxInSel->varScopep() != idxVscp) return false; + // 'target' must be const-0 immediately before the loop (collected in m_bindings), + // so that an all-zero 'vec' yields 0, matching $mostsetbitp1's definition. + const AstConst* const targetInitp = m_bindings.get(targetVscp); + if (!targetInitp || !targetInitp->isZero()) return false; + // Rewrite to 'target = (vec); idx = W'. The 'idx = W' store preserves the + // loop's exit value, so this is sound even if idx is read afterwards (else DCE drops it). + FileLine* const flp = loopp->fileline(); + AstNodeExpr* reducep; + if (isLeadingOne) { + reducep = new AstMostSetBitP1{flp, vecExprp->cloneTree(false)}; + } else { + AstCountOnes* const conep = new AstCountOnes{flp, vecExprp->cloneTree(false)}; + conep->dtypeSetInteger2State(); + reducep = conep; + } + reducep = resizeToWidth(reducep, targetRefp); + AstAssign* const newp = new AstAssign{flp, targetRefp->cloneTree(false), reducep}; + newp->addNext(new AstAssign{flp, incLhsp->cloneTree(false), wp->cloneTree(false)}); + loopp->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(loopp), loopp); + if (isLeadingOne) { + UINFO(4, "Lowered priority-encoder loop to $mostsetbitp1: " << newp); + ++m_stats.m_bitScanLowered; + } else { + UINFO(4, "Lowered count-set-bits loop to $countones: " << newp); + ++m_stats.m_countOnesLowered; + } + return true; + } + // VISIT void visit(AstLoop* nodep) override { // Gather variable bindings from the preceding statements @@ -448,6 +603,9 @@ class UnrollAllVisitor final : VNVisitor { m_bindings.set(lhsp->varScopep(), valp); } + // Recognize a bit counting loop and lower it to a builtin + if (v3Global.opt.fBitScanLoops() && tryLowerBitScanLoop(nodep)) return; + // Attempt to unroll this loop const std::pair pair = UnrollOneVisitor::apply(m_stats, m_bindings, nodep); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 861714e34..802fe155d 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -255,13 +255,6 @@ class WidthVisitor final : public VNVisitor { EXTEND_OFF // No extension }; - int widthUnpacked(const AstNodeDType* const dtypep) { - if (const AstUnpackArrayDType* const arrDtypep = VN_CAST(dtypep, UnpackArrayDType)) { - return arrDtypep->subDTypep()->width() * arrDtypep->arrayUnpackedElements(); - } - return dtypep->width(); - } - static void packIfUnpacked(AstNodeExpr* const nodep) { if (AstUnpackArrayDType* const unpackDTypep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) { const int elementsNum = unpackDTypep->arrayUnpackedElements(); @@ -274,6 +267,9 @@ class WidthVisitor final : public VNVisitor { nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)}); } } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } // When fromp() is a DType (e.g. unlinked RefDType), resolve through // the ref chain; when it's an expression, dtypep() is already resolved. static AstNodeDType* fromDTypep(AstNode* fromp) { @@ -735,6 +731,17 @@ class WidthVisitor final : public VNVisitor { iterateCheckBool(nodep, "default disable iff condition", nodep->condp(), BOTH); } void visit(AstDelay* nodep) override { + if (nodep->isCycleDelay() && m_underSExpr) { + // Fold parameterized SVA cycle-delay bounds + userIterateAndNext(nodep->lhsp(), WidthVP{SELF, BOTH}.p()); + V3Const::constifyParamsNoWarnEdit(nodep->lhsp()); + if (nodep->rhsp() && !nodep->isUnbounded()) { + // Fold parametrized SVA cycle-delay max bound + userIterateAndNext(nodep->rhsp(), WidthVP{SELF, BOTH}.p()); + V3Const::constifyParamsNoWarnEdit(nodep->rhsp()); + } + return; + } if (AstNodeExpr* const fallDelayp = nodep->fallDelay()) { iterateCheckDelay(nodep, "delay", nodep->lhsp(), BOTH); iterateCheckDelay(nodep, "delay", fallDelayp, BOTH); @@ -979,7 +986,10 @@ class WidthVisitor final : public VNVisitor { } const AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep()->skipRefToEnump(); if (VN_IS(lhsDtypep, DynArrayDType) || VN_IS(lhsDtypep, QueueDType) - || VN_IS(lhsDtypep, UnpackArrayDType)) { + || (VN_IS(lhsDtypep, UnpackArrayDType) && lhsDtypep->isStreamableFixedAggregate()) + || (VN_IS(lhsDtypep, NodeUOrStructDType) + && !VN_AS(lhsDtypep, NodeUOrStructDType)->packed() + && lhsDtypep->isStreamableFixedAggregate())) { nodep->dtypeSetStream(); } else if (lhsDtypep->isCompound()) { nodep->v3warn(E_UNSUPPORTED, @@ -1564,33 +1574,39 @@ class WidthVisitor final : public VNVisitor { } const bool loUnbounded = VN_IS(nodep->loBoundp(), Unbounded); const bool hiUnbounded = VN_IS(nodep->hiBoundp(), Unbounded); - if (loUnbounded || hiUnbounded) { - if (nodep->isStrong()) { - nodep->v3error("s_always range must be bounded (IEEE 1800-2023 16.12.11)"); - } else { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: unbounded always range (always [m:$])"); - } + // Strong always must be bounded (IEEE 1800-2023 16.12.11: "the range + // for a strong always shall be bounded"). Weak always [m:$] is legal: + // an unbounded upper bound imposes no end-of-trace obligation. + if (nodep->isStrong() && (loUnbounded || hiUnbounded)) { + AstNode* const boundp = loUnbounded ? nodep->loBoundp() : nodep->hiBoundp(); + boundp->v3error("s_always range must be bounded (IEEE 1800-2023 16.12.11)"); + nodep->dtypeSetBit(); + return; + } + if (loUnbounded) { + // Only the high bound may be $ (cycle_delay_const_range_expression). + nodep->loBoundp()->v3error("always range low bound must be a constant expression" + " (IEEE 1800-2023 16.12.11)"); nodep->dtypeSetBit(); return; } const AstConst* const loConstp = VN_CAST(nodep->loBoundp(), Const); const AstConst* const hiConstp = VN_CAST(nodep->hiBoundp(), Const); if (!loConstp) { - nodep->v3error("always range low bound must be a constant expression" - " (IEEE 1800-2023 16.12.11)"); + nodep->loBoundp()->v3error("always range low bound must be a constant expression" + " (IEEE 1800-2023 16.12.11)"); } - if (!hiConstp) { - nodep->v3error("always range high bound must be a constant expression" - " (IEEE 1800-2023 16.12.11)"); + if (!hiUnbounded && !hiConstp) { + nodep->hiBoundp()->v3error("always range high bound must be a constant expression" + " (IEEE 1800-2023 16.12.11)"); } if (loConstp && loConstp->toSInt() < 0) { - nodep->v3error("always range low bound must be non-negative" - " (IEEE 1800-2023 16.12.11)"); + nodep->loBoundp()->v3error("always range low bound must be non-negative" + " (IEEE 1800-2023 16.12.11)"); } - if (loConstp && hiConstp && hiConstp->toSInt() < loConstp->toSInt()) { - nodep->v3error("always range high bound must be >= low bound" - " (IEEE 1800-2023 16.12.11)"); + if (!hiUnbounded && loConstp && hiConstp && hiConstp->toSInt() < loConstp->toSInt()) { + nodep->hiBoundp()->v3error("always range high bound must be >= low bound" + " (IEEE 1800-2023 16.12.11)"); } bool hasPropertyOp = propp->isMultiCycleSva(); if (!hasPropertyOp) { @@ -1838,6 +1854,22 @@ class WidthVisitor final : public VNVisitor { nodep->dtypeSetBit(); } } + void visit(AstSClocked* nodep) override { + VL_RESTORER(m_underSExpr); + m_underSExpr = true; + m_hasSExpr = true; + assertAtExpr(nodep); + if (m_vup->prelim()) { + // Width each clock expression directly; the senitem chain is hoisted to + // the assertion clock by V3AssertNfa, where it is fully processed. + for (AstSenItem* senp = nodep->sensesp(); senp; + senp = VN_CAST(senp->nextp(), SenItem)) { + userIterateAndNext(senp->sensp(), WidthVP{SELF, BOTH}.p()); + } + iterateCheckBool(nodep, "exprp", nodep->exprp(), BOTH); + nodep->dtypeSetBit(); + } + } void visit(AstURandomRange* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { @@ -3074,6 +3106,7 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(nodep->dtypep(), nodep, "LHS var should be dtype completed"); } // UINFOTREE(9, nodep, "", "VRout"); + if (nodep->access().isWriteOrRW()) nodep->varp()->icoMaybeWritten(true); if (nodep->access().isWriteOrRW() && nodep->varp()->direction() == VDirection::CONSTREF) { nodep->v3error("Assigning to const ref variable: " << nodep->prettyNameQ()); } else if (nodep->access().isWriteOrRW() && nodep->varp()->isInput() @@ -3789,7 +3822,7 @@ class WidthVisitor final : public VNVisitor { if (!varp->didWidth()) userIterate(varp, nullptr); nodep->dtypep(foundp->dtypep()); nodep->varp(varp); - AstIface* const ifacep = adtypep->ifacep(); + AstIface* const ifacep = adtypep->ifaceViaCellp(); varp->sensIfacep(ifacep); nodep->didWidth(true); return; @@ -3830,7 +3863,25 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(viftopVarp, nodep, "No __Viftop variable for sub-interface cell"); if (!viftopVarp->didWidth()) userIterate(viftopVarp, nullptr); - nodep->dtypep(viftopVarp->dtypep()); + AstNodeDType* subDtypep = viftopVarp->dtypep(); + if (adtypep->isVirtual()) { + // A sub-interface selected through a virtual interface + // handle is itself a virtual reference (a runtime + // instance pointer), not a static instance. Mark the + // dtype virtual so a method call on it dispatches per + // instance instead of inlining to one fixed scope. + if (AstIfaceRefDType* const subRefp + = VN_CAST(subDtypep->skipRefp(), IfaceRefDType)) { + AstIfaceRefDType* const newDtypep = new AstIfaceRefDType{ + nodep->fileline(), subRefp->cellName(), subRefp->ifaceName()}; + newDtypep->ifacep(subRefp->ifacep()); + newDtypep->cellp(subRefp->cellp()); + newDtypep->isVirtual(true); + v3Global.rootp()->typeTablep()->addTypesp(newDtypep); + subDtypep = newDtypep; + } + } + nodep->dtypep(subDtypep); nodep->varp(viftopVarp); viftopVarp->sensIfacep(VN_AS(cellp->modp(), Iface)); nodep->didWidth(true); @@ -4781,7 +4832,9 @@ class WidthVisitor final : public VNVisitor { } } void methodCallIfaceRef(AstMethodCall* nodep, AstIfaceRefDType* adtypep) { - AstIface* const ifacep = adtypep->ifacep(); + // ifaceViaCellp() resolves the interface via cellp when ifacep is null, + // as for a sub-interface selected through a virtual interface handle. + AstIface* const ifacep = adtypep->ifaceViaCellp(); UINFO(5, __FUNCTION__ << ":" << nodep); if (AstNodeFTask* const ftaskp = VN_CAST(m_memberMap.findMember(ifacep, nodep->name()), NodeFTask)) { @@ -6295,14 +6348,14 @@ class WidthVisitor final : public VNVisitor { userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p()); // // UINFOTREE(1, nodep, "", "assign"); - AstNodeDType* const lhsDTypep + AstNodeDType* lhsDTypep = nodep->lhsp()->dtypep(); // Note we use rhsp for context determined // Check width of stream and wrap if needed if (AstNodeStream* const streamp = VN_CAST(nodep->rhsp(), NodeStream)) { AstNodeDType* const lhsDTypeSkippedRefp = lhsDTypep->skipRefp(); - const int lwidth = widthUnpacked(lhsDTypeSkippedRefp); - const int rwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); + const int lwidth = lhsDTypeSkippedRefp->widthStream(); + const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream(); if (lwidth != 0 && lwidth < rwidth) { nodep->v3widthWarn(lwidth, rwidth, "Target fixed size variable (" @@ -6314,16 +6367,18 @@ class WidthVisitor final : public VNVisitor { const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width(); UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS"); } - if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)) { + if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType) + || lowerAsFixedAggregate(lhsDTypeSkippedRefp)) { streamp->unlinkFrBack(); nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp, lhsDTypeSkippedRefp}); } } - if (const AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { + if (AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { const AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp(); - const int lwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); - const int rwidth = widthUnpacked(rhsDTypep); + AstNodeDType* const lhsStreamDTypep = streamp->lhsp()->dtypep()->skipRefp(); + const int lwidth = lhsStreamDTypep->widthStream(); + const int rwidth = rhsDTypep->widthStream(); if (rwidth != 0 && rwidth < lwidth) { nodep->v3widthWarn(lwidth, rwidth, "Stream target requires " @@ -6331,7 +6386,27 @@ class WidthVisitor final : public VNVisitor { << " bits, but source expression only provides " << rwidth << " bits (IEEE 1800-2023 11.4.14.3)"); } - if (VN_IS(rhsDTypep, UnpackArrayDType)) { + if (lowerAsFixedAggregate(lhsStreamDTypep)) { + AstNodeExpr* const streamExprp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const dstp = streamp->lhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); + if (VN_IS(streamp, StreamL)) { + streamp->lhsp(srcp); + streamp->dtypeSetLogicUnsized(srcp->width(), srcp->widthMin(), + VSigning::UNSIGNED); + srcp = streamExprp; + } else { + if (srcp->width() > lwidth) { + srcp = new AstSel{streamp->fileline(), srcp, srcp->width() - lwidth, + lwidth}; + } + VL_DO_DANGLING(pushDeletep(streamExprp), streamExprp); + } + nodep->lhsp(dstp); + nodep->rhsp(new AstCvtPackedToArray{srcp->fileline(), srcp, lhsStreamDTypep}); + nodep->dtypeFrom(dstp); + lhsDTypep = nodep->lhsp()->dtypep(); + } else if (VN_IS(rhsDTypep, UnpackArrayDType)) { AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); nodep->rhsp( new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()}); @@ -7788,7 +7863,7 @@ class WidthVisitor final : public VNVisitor { } void visit(AstNodeModule* nodep) override { assertAtStatement(nodep); - VL_RESTORER(m_insideTempNames); + VL_RESTORER_COPY(m_insideTempNames); if (AstClass* const classp = VN_CAST(nodep, Class)) { visitClass(classp); } else { @@ -9500,9 +9575,11 @@ class WidthVisitor final : public VNVisitor { // No width change on output;... // All below have bool or double outputs switch (nodep->type()) { case VNType::Eq: - case VNType::EqCase: newp = new AstEqN{fl, lhsp, rhsp}; break; + case VNType::EqCase: + case VNType::EqWild: newp = new AstEqN{fl, lhsp, rhsp}; break; case VNType::Neq: - case VNType::NeqCase: newp = new AstNeqN{fl, lhsp, rhsp}; break; + case VNType::NeqCase: + case VNType::NeqWild: newp = new AstNeqN{fl, lhsp, rhsp}; break; case VNType::Gt: case VNType::GtS: newp = new AstGtN{fl, lhsp, rhsp}; break; case VNType::Gte: @@ -9639,7 +9716,8 @@ class WidthVisitor final : public VNVisitor { continue; } else if (const AstNodeUOrStructDType* const adtypep = VN_CAST(dtypep, NodeUOrStructDType)) { - bits *= adtypep->width(); + bits *= adtypep->isStreamableFixedAggregate() ? adtypep->widthStream() + : adtypep->width(); break; } else if (const AstBasicDType* const adtypep = VN_CAST(dtypep, BasicDType)) { bits *= adtypep->width(); diff --git a/src/V3WidthCommit.cpp b/src/V3WidthCommit.cpp index 651bff963..900474423 100644 --- a/src/V3WidthCommit.cpp +++ b/src/V3WidthCommit.cpp @@ -47,7 +47,7 @@ class WidthCommitVisitor final : public VNVisitor { // STATE AstNodeFTask* m_ftaskp = nullptr; // Current function/task AstNodeModule* m_modp = nullptr; // Current module - std::string m_contNba; // In continuous- or non-blocking assignment + const char* m_contNbap = nullptr; // In continuous- or non-blocking assignment bool m_contReads = false; // Check read continuous automatic variables bool m_dynsizedelem = false; // Writing dynamically-sized array element, not the array itself VMemberMap m_memberMap; // Member names cached for fast lookup @@ -148,7 +148,7 @@ private: void varLifetimeCheck(AstNode* nodep, AstVar* varp) { // Skip if we are under a member select (lhs of a dot) // We don't care about lifetime of anything else than rhs of a dot - if (!m_underSel && !m_contNba.empty()) { + if (!m_underSel && m_contNbap) { std::string varType; const AstNodeDType* const varDtp = varp->dtypep()->skipRefp(); if (varp->lifetime().isAutomatic() && !VN_IS(varDtp, IfaceRefDType) @@ -162,7 +162,7 @@ private: if (!varType.empty()) { UINFO(1, " Related var dtype: " << varDtp); nodep->v3error(varType - << " variable not allowed in " << m_contNba + << " variable not allowed in " << m_contNbap << " assignment (IEEE 1800-2023 6.21): " << varp->prettyNameQ()); } } @@ -419,9 +419,9 @@ private: void visit(AstAssignCont* nodep) override { iterateAndNextNull(nodep->timingControlp()); { - VL_RESTORER(m_contNba); + VL_RESTORER(m_contNbap); VL_RESTORER(m_contReads); - m_contNba = "continuous"; + m_contNbap = "continuous"; m_contReads = true; iterateAndNextNull(nodep->lhsp()); iterateAndNextNull(nodep->rhsp()); @@ -432,9 +432,9 @@ private: iterateAndNextNull(nodep->timingControlp()); iterateAndNextNull(nodep->rhsp()); { - VL_RESTORER(m_contNba); + VL_RESTORER(m_contNbap); VL_RESTORER(m_contReads); - m_contNba = "nonblocking"; + m_contNbap = "nonblocking"; m_contReads = false; iterateAndNextNull(nodep->lhsp()); } @@ -444,9 +444,9 @@ private: iterateAndNextNull(nodep->timingControlp()); iterateAndNextNull(nodep->rhsp()); { - VL_RESTORER(m_contNba); + VL_RESTORER(m_contNbap); VL_RESTORER(m_contReads); - m_contNba = "continuous"; + m_contNbap = "continuous"; m_contReads = false; iterateAndNextNull(nodep->lhsp()); } diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 13bb95c6f..b694691dd 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -22,6 +22,7 @@ #include "V3AssertNfa.h" #include "V3AssertPre.h" #include "V3Ast.h" +#include "V3AstPatterns.h" #include "V3Begin.h" #include "V3Branch.h" #include "V3Broken.h" @@ -98,12 +99,10 @@ #include "V3Scoreboard.h" #include "V3Slice.h" #include "V3Split.h" -#include "V3SplitAs.h" #include "V3SplitVar.h" #include "V3Stats.h" #include "V3String.h" #include "V3Subst.h" -#include "V3TSP.h" #include "V3Table.h" #include "V3Task.h" #include "V3ThreadPool.h" @@ -261,6 +260,8 @@ static void process() { // Assertion insertion // After we've added block coverage, but before other nasty transforms + V3AssertCommon::collectDefaultDisable(v3Global.rootp()); + V3AssertCommon::lowerSequenceEvents(v3Global.rootp()); V3AssertNfa::assertNfaAll(v3Global.rootp()); // V3AssertProp removed: NFA subsumes multi-cycle property lowering. // Unsupported constructs fall through to V3AssertPre. @@ -421,7 +422,6 @@ static void process() { // Split single ALWAYS blocks into multiple blocks for better ordering chances if (v3Global.opt.fSplit()) V3Split::splitAll(v3Global.rootp()); - V3SplitAs::splitAsAll(v3Global.rootp()); // Create tracing sample points, before we start eliminating signals if (v3Global.opt.trace()) V3TraceDecl::traceDeclAll(v3Global.rootp()); @@ -543,6 +543,8 @@ static void process() { V3Const::constifyAll(v3Global.rootp()); V3Dead::deadifyAll(v3Global.rootp()); + if (v3Global.opt.dumpAstPatterns()) V3AstPatterns::dumpAll(v3Global.rootp(), "prec"); + // Here down, widthMin() is the Verilog width, and width() is the C++ width // Bits between widthMin() and width() are irrelevant, but may be non-zero. v3Global.widthMinUsage(VWidthMinUsage::VERILOG_WIDTH); @@ -584,8 +586,14 @@ static void process() { // Must be after all Sel/array index based optimizations V3Reloop::reloopAll(v3Global.rootp()); } + } - if (v3Global.opt.inlineCFuncs()) { + // These are no longer needed, remove references before CFunc inlining + v3Global.rootp()->evalp(nullptr); + v3Global.rootp()->evalNbap(nullptr); + + if (!v3Global.opt.lintOnly() && !v3Global.opt.serializeOnly()) { + if (v3Global.opt.fInlineCFuncs()) { // Inline small CFuncs to reduce function call overhead V3InlineCFuncs::inlineAll(v3Global.rootp()); } @@ -631,6 +639,8 @@ static void process() { } } + if (v3Global.opt.dumpAstPatterns()) V3AstPatterns::dumpAll(v3Global.rootp(), "emit"); + // Output the text if (!v3Global.opt.lintOnly() && !v3Global.opt.serializeOnly() && !v3Global.opt.dpiHdrOnly()) { @@ -734,7 +744,6 @@ static bool verilate(const string& argString) { VHashSha256::selfTest(); VSpellCheck::selfTest(); V3Graph::selfTest(); - V3TSP::selfTest(); V3ScoreboardBase::selfTest(); V3Order::selfTestParallel(); V3ExecGraph::selfTest(); @@ -808,7 +817,7 @@ static bool verilate(const string& argString) { V3Os::filesystemFlushBuildDir(v3Global.opt.makeDir()); if (v3Global.opt.hierTop()) V3Os::filesystemFlushBuildDir(v3Global.opt.hierTopDataDir()); if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "WroteAll"); - if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "WroteFast"); + if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "WroteFast", true); // Final writing shouldn't throw warnings, but... V3Error::abortIfWarnings(); diff --git a/src/VlcTop.cpp b/src/VlcTop.cpp index ccaec527e..de5160c2d 100644 --- a/src/VlcTop.cpp +++ b/src/VlcTop.cpp @@ -240,7 +240,7 @@ void VlcTop::writeInfo(const string& filename) { // FNF: // FNH: // Branches: - // BRDA:,,, + // BRDA:,,, // BRF: // BRH: // Line counts: @@ -273,8 +273,14 @@ void VlcTop::writeInfo(const string& filename) { for (const VlcPoint* point : infoPoints) { os << "BRDA:" << sc.lineno() << ","; os << "0,"; - os << point_num << ","; - os << point->count() << "\n"; + if (point->comment().empty()) { + os << point_num; + } else { + std::string comment(point->comment()); + std::replace(comment.begin(), comment.end(), ',', '_'); + os << comment; + } + os << "," << point->count() << "\n"; branchesHit += opt.countOk(point->count()); ++point_num; diff --git a/src/bisonpre b/src/bisonpre index 9be647f39..5db061a5a 100755 --- a/src/bisonpre +++ b/src/bisonpre @@ -145,6 +145,26 @@ def clean_output(filename: str, outname: str, is_output: bool, is_c: bool) -> No out.append(line) lines = out out = [] + # Code updates for the .c + if outname.endswith(".c"): + for line in lines: + modify = "YYSTACK_FREE (yyss);" in line and "if (yyss != yyssa)" in out[-1] + ifdefined = "#if defined __GNUC__ && ! defined __ICC\n" + endif = "#endif\n" + if modify: + out.insert(-1, ifdefined) + out.insert(-1, " _Pragma (\"GCC diagnostic push\")\n") + out.insert( + -1, + " _Pragma (\"GCC diagnostic ignored \\\"-Wfree-nonheap-object\\\"\")\n") + out.insert(-1, endif) + out.append(line) + if modify: + out.append(ifdefined) + out.append(" _Pragma (\"GCC diagnostic pop\")\n") + out.append(endif) + lines = out + out = [] with open(outname, "w", encoding="utf-8") as fh: tmpy = re.escape(tmp_prefix() + ".y") diff --git a/src/cppcheck-suppressions.txt b/src/cppcheck-suppressions.txt index 754ef3c4b..6d9ab321d 100644 --- a/src/cppcheck-suppressions.txt +++ b/src/cppcheck-suppressions.txt @@ -137,8 +137,6 @@ constParameterPointer:src/V3Tristate.cpp constParameterReference:src/V3Tristate.cpp constVariablePointer:src/V3Tristate.cpp constVariableReference:src/V3Tristate.cpp -constVariablePointer:src/V3TSP.cpp -constVariableReference:src/V3TSP.cpp constParameterPointer:src/V3Undriven.cpp constVariablePointer:src/V3Undriven.cpp constParameterPointer:src/V3Unroll.cpp diff --git a/src/verilog.y b/src/verilog.y index 663e8a206..db145ea8e 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3121,7 +3121,7 @@ sigAttr: | yVL_PUBLIC_FLAT { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT}; v3Global.dpi(true); } | yVL_PUBLIC_FLAT_RD { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RD}; v3Global.dpi(true); } | yVL_PUBLIC_FLAT_RW attr_event_controlE { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RW}; v3Global.dpi(true); DEL($2); } - | yVL_ISOLATE_ASSIGNMENTS { $$ = new AstAttrOf{$1, VAttrType::VAR_ISOLATE_ASSIGNMENTS}; } + | yVL_ISOLATE_ASSIGNMENTS { $$ = nullptr; /* Historical, now has no effect */ } | yVL_SC_BIGUINT { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BIGUINT}; } | yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; } | yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; } @@ -4657,12 +4657,12 @@ task_prototype: // ==IEEE: task_prototype function_declaration: // IEEE: function_declaration + function_body_declaration yFUNCTION dynamic_override_specifiersE lifetimeE funcId funcIsolateE tfGuts yENDFUNCTION endLabelE - { $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6); + { $$ = $4; $$->addStmtsp($6); $$->baseOverride($2); $$->lifetime($3); GRAMMARP->endLabel($8, $$, $8); } | yFUNCTION dynamic_override_specifiersE lifetimeE funcIdNew funcIsolateE tfNewGuts yENDFUNCTION endLabelE - { $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6); + { $$ = $4; $$->addStmtsp($6); $$->baseOverride($2); $$->lifetime($3); GRAMMARP->endLabel($8, $$, $8); } @@ -6497,14 +6497,34 @@ concurrent_assertion_statement: // ==IEEE: concurrent_assertion_stat | yCOVER yPROPERTY '(' property_spec ')' stmt { $$ = new AstCover{$1, $4, $6, VAssertType::CONCURRENT}; } // // IEEE: cover_sequence_statement + // // Reuses AstCover + AstPropSpec (same wrapper as + // // cover_property_statement above) and the isCoverSeq + // // flag drives V3AssertNfa to fire stmt per end-of-match + // // (IEEE 1800-2023 16.14.3), not per property success. | yCOVER ySEQUENCE '(' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $6); } - // // IEEE: yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt - // // sexpr already includes "clocking_event sexpr" + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), nullptr, nullptr, $4}, + $6, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } + | yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), $4, nullptr, $5}, + $7, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } | yCOVER ySEQUENCE '(' clocking_event yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $8, $10, $12);} + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), $4, $8, $10}, + $12, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } | yCOVER ySEQUENCE '(' yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($7, $9, $11); } + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$7->fileline(), nullptr, $7, $9}, + $11, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } // // IEEE: restrict_property_statement | yRESTRICT yPROPERTY '(' property_spec ')' ';' { $$ = new AstRestrict{$1, $4}; } @@ -6653,6 +6673,12 @@ sequence_declarationBody: // IEEE: part of sequence_declaration | assertion_variable_declarationList sexpr ';' { $$ = addNextNull($1, $2); } | sexpr { $$ = $1; } | sexpr ';' { $$ = $1; } + // // IEEE: clocking_event sequence_expr (16.7) + // // A leading clocking event on a named sequence body. + | '@' '(' event_expression ')' sexpr { $$ = new AstSClocked{$1, $3, $5}; } + | '@' '(' event_expression ')' sexpr ';' { $$ = new AstSClocked{$1, $3, $5}; } + | '@' senitemVar sexpr { $$ = new AstSClocked{$1, $2, $3}; } + | '@' senitemVar sexpr ';' { $$ = new AstSClocked{$1, $2, $3}; } ; property_spec: // IEEE: property_spec @@ -6664,14 +6690,8 @@ property_spec: // IEEE: property_spec { $$ = new AstPropSpec{$1, $3, nullptr, $5}; } | '@' senitemVar pexpr { $$ = new AstPropSpec{$1, $2, nullptr, $3}; } - // // Disable applied after the event occurs, - // // so no existing AST can represent this - | yDISABLE yIFF '(' expr ')' '@' '(' senitemEdge ')' pexpr - { $$ = new AstPropSpec{$1, $8, nullptr, new AstLogOr{$1, $4, $10}}; - BBUNSUP($1, "Unsupported: property '(disable iff (...) @ (...)'\n" - + $1->warnMore() - + "... Suggest use property '(@(...) disable iff (...))'"); } - //UNSUP remove above + | yDISABLE yIFF '(' expr ')' '@' '(' senitem ')' pexpr + { $$ = new AstPropSpec{$1, $8, $4, $10}; } | yDISABLE yIFF '(' expr ')' pexpr { $$ = new AstPropSpec{$4->fileline(), nullptr, $4, $6}; } | pexpr { $$ = new AstPropSpec{$1->fileline(), nullptr, nullptr, $1}; } ; @@ -7319,8 +7339,29 @@ cross_itemList: // IEEE: part of list_of_cross_items ; cross_item: // ==IEEE: cross_item - id/*cover_point_identifier*/ - { $$ = new AstCoverpointRef{$1, *$1}; } + // // IEEE: cover_point_identifier | variable_identifier - both are a + // // simple identifier. We parse idDotted (a plain hierarchical + // // reference a.b.c, with no bit/array selects) to also accept the + // // non-standard dotted form (e.g. 'cross a.b') that several + // // simulators support; the common simple-identifier case is detected + // // and handled exactly as before. + idDotted + { + if (AstParseRef* const refp = VN_CAST($1, ParseRef)) { + // Standard: simple cover_point_identifier / variable_identifier + $$ = new AstCoverpointRef{refp->fileline(), refp->name()}; + VL_DO_DANGLING(refp->deleteTree(), refp); + } else { + // Verilator extension beyond strict IEEE (cross_item is a simple + // identifier): some tools accept a hierarchical/dotted reference. + // Carry the reference expression (still an AstDot here) out of the + // parser unchanged; later stages resolve and, eventually, implement + // it as an implicit coverpoint. + $1->v3warn(NONSTD, "Non-standard hierarchical reference as a coverage " + "cross item (an implicit coverpoint)"); + $$ = new AstCoverpointRef{$1->fileline(), $1}; + } + } ; cross_body: // ==IEEE: cross_body @@ -8559,8 +8600,7 @@ vltVarAttrSpecE: ; vltVarAttrFront: - yVLT_ISOLATE_ASSIGNMENTS { $$ = VAttrType::VAR_ISOLATE_ASSIGNMENTS; } - | yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; } + yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; } | yVLT_PUBLIC { $$ = VAttrType::VAR_PUBLIC; v3Global.dpi(true); } | yVLT_PUBLIC_FLAT { $$ = VAttrType::VAR_PUBLIC_FLAT; v3Global.dpi(true); } | yVLT_PUBLIC_FLAT_RD { $$ = VAttrType::VAR_PUBLIC_FLAT_RD; v3Global.dpi(true); } @@ -8574,6 +8614,7 @@ vltVarAttrFront: vltVarAttrFrontDeprecated: yVLT_CLOCK_ENABLE { } | yVLT_CLOCKER { } + | yVLT_ISOLATE_ASSIGNMENTS { } | yVLT_NO_CLOCKER { } ; diff --git a/test_regress/AGENTS.md b/test_regress/AGENTS.md new file mode 100644 index 000000000..7d6b1b708 --- /dev/null +++ b/test_regress/AGENTS.md @@ -0,0 +1,134 @@ + + +# test_regress/ -- regression tests + +Covers `.v`/`.sv` sources, `.py` drivers, and `.out` golden files under +`test_regress/`. Read the repository-root [AGENTS.md](../AGENTS.md) first. This +file has two parts: **Orientation** explains how the harness runs a test; +**Before you open a PR** is the test-authoring checklist. + +______________________________________________________________________ + +# Orientation: how the harness works + +- **A test is a `.v`/`.sv` source plus a matching `.py` driver in `t/`.** The + driver calls `test.compile()`, `test.execute()`, and/or `test.lint()`. Without a + `.py` the source never runs and is dead code giving false coverage confidence. +- **Golden output lives in `.out` files**, compared via `expect_filename`. + Generate them with `HARNESS_UPDATE_GOLDEN=1 python3 t/.py` -- never + hand-write them; the harness normalizes paths, version markers, and line numbers + that hand edits get wrong. +- **Run one test from the repository root:** `test_regress/t/t_.py`. When + running from a checkout, set `VERILATOR_ROOT` to the checkout root first so the + harness compiles the generated C++ against the right headers. +- **`test.compile()` defaults are richer than they look:** the vlt driver + auto-injects `--exe` and a generated main, and `assert property` fires its + action blocks without `--assert` -- try the simple form before adding flags. +- **The `t_dist_*` tests enforce repository conventions** (file headers, sorted + lists, warning documentation, ASCII-only) -- run the relevant ones before + submitting. + +______________________________________________________________________ + +# Before you open a PR + +## Naming + +- Name tests `t_{category}_{description}` in snake_case; the first word groups the + category (`t_lint_unused_func_bad`, not `t_unused_func_lint_bad`) so related + tests are findable and runnable together. +- Use `_bad` when the code is illegal SystemVerilog, `_unsup` when it is legal but + unsupported, `_off` for disabled-behavior tests -- never `_fail`. +- Keep filenames under roughly 30-35 characters. + +## Files and drivers + +- Every `.v` needs a matching `.py` driver that actually calls `test.compile()` + and `test.execute()` (or `test.lint()` for static-analysis-only tests) -- a + driver that sets up but never runs hides coverage gaps silently. +- Copy the license header from an existing file: `.v` tests carry the CC0 notice, + `.py` drivers carry the standard driver header -- distribution tests check + headers on every committed file. +- Use `--binary` instead of `--exe --main --timing` -- it implies the others. +- Error tests use `test.compile(fails=True, expect_filename=test.golden_filename)` + (or `test.lint(...)`) and must not call `test.execute()`. +- Use `.out` golden files with `expect_filename` instead of inline `expect` + regex strings -- goldens are diffable and maintainable. +- Use `test.glob_one()`/`test.glob_some()` and `test.timeout()` instead of raw + `glob.glob()` and manual `time.time()` measurements. +- Coverage tests run `verilator_coverage` and verify individual bins and points, + not just aggregate percentages. +- Assert optimization stats with exact expected counts: + `test.file_grep(test.stats, r'Optimizations, ...\s+(\d+)', N)`. +- Add a `_protect_ids` variant when a feature emits user identifiers or filenames; + use conservative thread counts (2 or fewer) in multithreaded tests. +- Extend existing test files with related cases instead of creating many + single-purpose files; keep drivers minimal -- test logic belongs in the `.v`. + +## Golden .out files + +- Never hand-write or hand-edit `.out` goldens -- regenerate with + `HARNESS_UPDATE_GOLDEN=1`. +- When a feature lands, remove its now-supported entries from `t_*_unsup.v` / + `t_*_bad.v` in the same change and regenerate the goldens -- stale entries no + longer error and reviewers will flag them. + +## Verilog style + +- 2-space indentation, no tabs. + +- Declarations are flush-left with a single space between type and name; never + column-align: + + ```systemverilog + // WRONG: column-aligned + bit [63:0] crc = 64'h5aef0c8d_d70a4497; + int cyc = 0; + // RIGHT: flush-left + bit [63:0] crc = 64'h5aef0c8d_d70a4497; + int cyc = 0; + ``` + +- Run `nodist/verilog_format` on new `.v` files; wrap macro definitions in + `// verilog_format: off`/`on` so the formatter does not split them. + +- Use `$display("%0d", ...)` not `%d` -- avoids leading-space padding. + +- Wrap Verilator-specific test code (e.g. `$c`) in `` `ifdef VERILATOR ``. + +- Use inline `// verilator lint_off WARNCODE` only when that warning is itself + under test -- fix root causes otherwise. + +- Use only IEEE 1800-compliant constructs other simulators also accept -- tests + validate standard behavior, not Verilator's parser leniency. + +- Omit optional end labels on `endmodule`/`endclass`/`endtask`/`endfunction`. + +## Self-checking + +- Use the `checkh`/`checkd`/`checks` assertion macros, not manual + `if`/`$display`/`$stop` sequences. Note `checkh` prints with `%p`, which renders + integers as hex -- use `checkd` for integer comparisons. +- Use the `` `stop `` macro rather than direct `$stop`. +- Drive logic with runtime-varying inputs (counters, CRC/LFSR registers) so + constant folding cannot pre-evaluate the logic under test; check behavior across + multiple clock cycles, not just initial values. +- For a test whose pass/fail depends on varying or random values, loop enough + iterations that the values demonstrably differ and size the value space so a + failure is probable per run, then confirm the test fails on an un-fixed tree + before submitting. + +## Test design + +- Use non-power-of-2, non-word-aligned widths (7, 15, 31, 33, 63, 65, 95) -- + exposes masking and word-boundary bugs that 32/64/128 hide. +- Test both `[high:low]` and `[low:high]` orderings plus non-zero bounds like + `[3:1]`; use different ranges for each axis of multidimensional arrays. +- When adding type support, test all basic types (chandle, string, real) and + typedef-wrapped variants. +- Include the issue's own reproducer as a committed test, and verify it fails + without the fix. +- Assert non-blocking-assignment results in the cycle immediately after they take + effect, before later overwrites, using indices that change post-NBA. diff --git a/test_regress/t/t_altera_lpm.v b/test_regress/t/t_altera_lpm.v index 967cd1dea..19d445364 100644 --- a/test_regress/t/t_altera_lpm.v +++ b/test_regress/t/t_altera_lpm.v @@ -49,7 +49,7 @@ //See also: https://github.com/twosigma/verilator_support // verilog_format: off -// verilator lint_off COMBDLY,INITIALDLY,LATCH,MULTIDRIVEN,UNSIGNED,WIDTH +// verilator lint_off COMBDLY,LATCH,MULTIDRIVEN,UNSIGNED,WIDTH // BEGINNING OF MODULE `timescale 1 ps / 1 ps diff --git a/test_regress/t/t_array_packed_sysfunct.v b/test_regress/t/t_array_packed_sysfunct.v index e31d38929..c380fa898 100644 --- a/test_regress/t/t_array_packed_sysfunct.v +++ b/test_regress/t/t_array_packed_sysfunct.v @@ -33,9 +33,7 @@ module t ( initial begin `checkh($dimensions (array_unpk), 3); -`ifndef VCS `checkh($unpacked_dimensions (array_unpk), 2); // IEEE 2009 -`endif `checkh($bits (array_unpk), 2*2*2); `checkh($low (array_unpk), 2); `checkh($high (array_unpk), 3); diff --git a/test_regress/t/t_assert_always_unbounded.py b/test_regress/t/t_assert_always_unbounded.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_always_unbounded.v b/test_regress/t/t_assert_always_unbounded.v new file mode 100644 index 000000000..f408c5885 --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + int cyc = 0; + logic a_high = 1'b1; + logic a_low = 1'b0; + logic a_drop = 1'b1; + + // Weak always [m:$] is a pure safety property: it has no end-of-trace + // obligation, so it can only fail (else fires), once per failing tick. + int high_fail_q[$]; + int low0_fail_q[$]; + int low2_fail_q[$]; + int drop_fail_q[$]; + + // Constant-true input: never fails at any tick. + assert property (@(posedge clk) always [2:$] a_high) else high_fail_q.push_back(cyc); + + // Constant-false input, m=0: fails at every observed tick. + assert property (@(posedge clk) always [0:$] a_low) else low0_fail_q.push_back(cyc); + + // Constant-false input, m=2: fails at every tick once the window is live. + assert property (@(posedge clk) always [2:$] a_low) else low2_fail_q.push_back(cyc); + + // a_drop is high then drops at cyc 5 and stays low: deterministic single + // transition, so Verilator and others agree on the failing ticks exactly. + assert property (@(posedge clk) always [2:$] a_drop) else drop_fail_q.push_back(cyc); + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc >= 4) a_drop <= 1'b0; + if (cyc == 19) begin + // Counts pinned to Verilator (NFA per-cycle reject). For all-fail windows + // others are one lower (it does not fire the end-of-sim tick); see the sva + // lessons "multi-cycle end-of-simulation offset" note. + `checkd(high_fail_q.size(), 0); + `checkd(low0_fail_q.size(), 20); // All others: 19 + `checkd(low2_fail_q.size(), 18); // All others: 17 + `checkd(drop_fail_q[0], 5); // All others: 6; first fail tick: a_drop sampled low from cyc 5 + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_assert_always_unbounded_bad.out b/test_regress/t/t_assert_always_unbounded_bad.out new file mode 100644 index 000000000..155120065 --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded_bad.out @@ -0,0 +1,14 @@ +%Error: t/t_assert_always_unbounded_bad.v:13:35: s_always range must be bounded (IEEE 1800-2023 16.12.11) + : ... note: In instance 't' + 13 | assert property (@(posedge clk) s_always a); + | ^~~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_assert_always_unbounded_bad.v:14:47: s_always range must be bounded (IEEE 1800-2023 16.12.11) + : ... note: In instance 't' + 14 | assert property (@(posedge clk) s_always [2:$] a); + | ^ +%Error: t/t_assert_always_unbounded_bad.v:18:43: always range low bound must be a constant expression (IEEE 1800-2023 16.12.11) + : ... note: In instance 't' + 18 | assert property (@(posedge clk) always [$:5] a); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_assert_always_unbounded_bad.py b/test_regress/t/t_assert_always_unbounded_bad.py new file mode 100755 index 000000000..77a0ac64b --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(expect_filename=test.golden_filename, fails=True) + +test.passes() diff --git a/test_regress/t/t_assert_always_unbounded_bad.v b/test_regress/t/t_assert_always_unbounded_bad.v new file mode 100644 index 000000000..9103865ef --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t (input clk); + logic a; + + // A strong always must be bounded (IEEE 1800-2023 16.12.11): there is no bare + // s_always grammar production, and s_always [m:$] is the explicit "// Illegal" + // example (p5). Both forms are rejected. + assert property (@(posedge clk) s_always a); + assert property (@(posedge clk) s_always [2:$] a); + + // A weak always range may only place $ on the high bound; an unbounded low + // bound is not a legal cycle_delay_const_range_expression. + assert property (@(posedge clk) always [$:5] a); + +endmodule diff --git a/test_regress/t/t_assert_bang_in_seq.v b/test_regress/t/t_assert_bang_in_seq.v index b0c831229..784dd338a 100644 --- a/test_regress/t/t_assert_bang_in_seq.v +++ b/test_regress/t/t_assert_bang_in_seq.v @@ -66,12 +66,12 @@ module t ( end else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); - `checkd(count_fail1, 66); // Questa: 66 - `checkd(count_fail2, 69); // Questa: 69 - `checkd(count_fail3, 26); // Questa: 26 - `checkd(count_fail4, 66); // Questa: 66 - `checkd(count_fail5, 80); // Questa: 80 - `checkd(count_fail6, 27); // Questa: 27 + `checkd(count_fail1, 66); + `checkd(count_fail2, 69); + `checkd(count_fail3, 26); + `checkd(count_fail4, 66); + `checkd(count_fail5, 80); + `checkd(count_fail6, 27); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_assert_basic.v b/test_regress/t/t_assert_basic.v index 7d96ac19c..55360519d 100644 --- a/test_regress/t/t_assert_basic.v +++ b/test_regress/t/t_assert_basic.v @@ -13,6 +13,24 @@ module t ( integer cyc; initial cyc = 1; wire [7:0] cyc_copy = cyc[7:0]; + typedef enum logic { CAST_ONE = 1'b1 } cast_e; + cast_e cast_dst; + integer action_hits = 0; + + assert property (@(posedge clk) 1'b1) + if (cyc >= 0) action_hits++; + + assert property (@(posedge clk) 1'b1) + action_hits++; + else + action_hits--; + + cover property (@(posedge clk) 1'b1) + action_hits++; + + initial begin + $cast(cast_dst, 1); + end always @(negedge clk) begin AssertionFalse1 : assert (cyc < 100); @@ -26,6 +44,12 @@ module t ( if (cyc != 0) begin cyc <= cyc + 1; toggle <= !cyc[0]; + assert (cyc >= 0) + if (cyc >= 0) action_hits++; + assert (cyc >= 0) + action_hits++; + else + action_hits--; if (cyc == 7) assert (cyc[0] == cyc[1]); // bug743 if (cyc == 9) begin `ifdef FAILING_ASSERTIONS diff --git a/test_regress/t/t_assert_consec_rep.v b/test_regress/t/t_assert_consec_rep.v index 8a4d87040..206b49886 100644 --- a/test_regress/t/t_assert_consec_rep.v +++ b/test_regress/t/t_assert_consec_rep.v @@ -95,23 +95,23 @@ module t ( end else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); - `checkd(count_fail1, 5); // Questa: 5 - `checkd(count_fail2, 25); // Questa: 25 - `checkd(count_fail3, 9); // Questa: 9 - `checkd(count_fail4, 49); // Questa: 49 - `checkd(count_fail5, 0); // Questa: 0 - // NFA merge-node range [*M:N] over-counts rejects (Questa: 51); match + `checkd(count_fail1, 5); + `checkd(count_fail2, 25); // One other sim: 19 + `checkd(count_fail3, 9); + `checkd(count_fail4, 49); + `checkd(count_fail5, 0); + // NFA merge-node range [*M:N] over-counts rejects; match // detection is correct, only reject counting is imprecise - `checkd(count_fail6, 59); - `checkd(count_fail7, 51); // Questa: 51 - `checkd(count_fail8, 20); // Questa: 20 + `checkd(count_fail6, 59); // All other sims: 51 + `checkd(count_fail7, 51); + `checkd(count_fail8, 20); // IEEE 1800-2023 16.9.2 permits empty match of [*0]; NFA reports - // rejects on each tick while Questa suppresses (Questa: 20) - `checkd(count_fail9, 49); - `checkd(count_fail10, 59); // Questa: 59 + // rejects on each tick while others suppress + `checkd(count_fail9, 49); // Most others: 20, one other 49 + `checkd(count_fail10, 59); // a[*] ##1 b: NFA treats unbounded [*] as liveness (no reject); - // Questa treats as definite antecedent (Questa: 29) - `checkd(count_fail11, 0); + // Should be definite antecedent + `checkd(count_fail11, 0); // All other sims: 29 $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_assert_consec_rep_large.v b/test_regress/t/t_assert_consec_rep_large.v index 359537ce7..4df36f7bc 100644 --- a/test_regress/t/t_assert_consec_rep_large.v +++ b/test_regress/t/t_assert_consec_rep_large.v @@ -40,8 +40,8 @@ module t ( else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); `checkd(count_fail_257, 0); - // Questa: 31 -- pre-existing ~26.5% NFA reject gap on |-> ##1 [*N] - `checkd(count_fail_513, 23); + // Mismatch due to pre-existing ~26.5% NFA reject gap on |-> ##1 [*N] + `checkd(count_fail_513, 23); // All other sims: 31 $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_assert_ctl_arg.cpp b/test_regress/t/t_assert_ctl_arg.cpp index 7e386b5b0..684db6436 100644 --- a/test_regress/t/t_assert_ctl_arg.cpp +++ b/test_regress/t/t_assert_ctl_arg.cpp @@ -82,6 +82,61 @@ void verilatedTest() { contextp->assertOn(false); // Now everything is disabled TEST_CHECK_Z(contextp->assertOn()); + + // Unified runtime query getter + using Query = VerilatedAssertCtlQuery; + constexpr uint32_t LOCK = 1; + constexpr uint32_t UNLOCK = 2; + constexpr uint32_t ON = 3; + constexpr uint32_t OFF = 4; + constexpr uint32_t KILL = 5; + constexpr uint32_t PASS_ON = 6; + constexpr uint32_t PASS_OFF = 7; + constexpr uint32_t FAIL_ON = 8; + constexpr uint32_t FAIL_OFF = 9; + constexpr uint32_t NONVACUOUS_ON = 10; + constexpr uint32_t VACUOUS_OFF = 11; + constexpr uint32_t TYPE = 1; + constexpr uint32_t DIRECTIVE = 1; + + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, 0, DIRECTIVE)); + + contextp->assertCtl(LOCK, TYPE, DIRECTIVE); + contextp->assertCtl(ON, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(UNLOCK, TYPE, DIRECTIVE); + contextp->assertCtl(ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(OFF, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + + const uint32_t killBefore = contextp->assertCtlGet(Query::ASSERT_CTL_KILL, TYPE, DIRECTIVE); + contextp->assertCtl(KILL, TYPE, DIRECTIVE); + TEST_CHECK_EQ(contextp->assertCtlGet(Query::ASSERT_CTL_KILL, TYPE, DIRECTIVE), killBefore + 1); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + contextp->assertCtl(PASS_OFF, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + + contextp->assertCtl(NONVACUOUS_ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + contextp->assertCtl(PASS_ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + contextp->assertCtl(VACUOUS_OFF, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_FAIL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(FAIL_OFF, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_FAIL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(FAIL_ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_FAIL_ON, TYPE, DIRECTIVE)); } int main(int argc, char** argv) { verilatedTest(); diff --git a/test_regress/t/t_assert_ctl_arg_unsup.out b/test_regress/t/t_assert_ctl_arg_unsup.out index c9bad92c3..8325ccdf1 100644 --- a/test_regress/t/t_assert_ctl_arg_unsup.out +++ b/test_regress/t/t_assert_ctl_arg_unsup.out @@ -1,18 +1,18 @@ %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:15:5: Unsupported: assert control assertion_type : ... note: In instance 't' - 15 | $assertcontrol(OFF, EXPECT); + 15 | $assertcontrol(OFF, UNIQUE); | ^~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:16:5: Unsupported: assert control assertion_type : ... note: In instance 't' - 16 | $assertcontrol(OFF, UNIQUE); + 16 | $assertcontrol(OFF, UNIQUE0); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:17:5: Unsupported: assert control assertion_type : ... note: In instance 't' - 17 | $assertcontrol(OFF, UNIQUE0); + 17 | $assertcontrol(OFF, PRIORITY); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:18:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:18:5: Unsupported: non-const assert directive type expression : ... note: In instance 't' - 18 | $assertcontrol(OFF, PRIORITY); + 18 | $assertcontrol(OFF, 1, directive_type); | ^~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_arg_unsup.v b/test_regress/t/t_assert_ctl_arg_unsup.v index b041652f2..c82410860 100644 --- a/test_regress/t/t_assert_ctl_arg_unsup.v +++ b/test_regress/t/t_assert_ctl_arg_unsup.v @@ -6,15 +6,15 @@ module t; let OFF = 4; - let EXPECT = 16; let UNIQUE = 32; let UNIQUE0 = 64; let PRIORITY = 128; + logic directive_type = 1'b1; initial begin - $assertcontrol(OFF, EXPECT); $assertcontrol(OFF, UNIQUE); $assertcontrol(OFF, UNIQUE0); $assertcontrol(OFF, PRIORITY); + $assertcontrol(OFF, 1, directive_type); end endmodule diff --git a/test_regress/t/t_assert_ctl_concurrent.v b/test_regress/t/t_assert_ctl_concurrent.v index 3cdda2bc1..c7d62648a 100644 --- a/test_regress/t/t_assert_ctl_concurrent.v +++ b/test_regress/t/t_assert_ctl_concurrent.v @@ -7,32 +7,49 @@ module t; bit clock = 1'b0; - bit reset = 1'b0; + bit start = 1'b0; + bit done = 1'b0; + int concurrent_fails = 0; initial begin + $asserton; + + @(negedge clock); + start = 1'b1; + done = 1'b0; + + @(negedge clock); + start = 1'b0; $assertkill; + $asserton; - #10 reset = 1'b1; - $display("%t: deassert reset %d", $time, reset); + @(posedge clock); + #1; + if (concurrent_fails != 0) $stop; - #40 $asserton; + @(negedge clock); + start = 1'b1; + done = 1'b0; - reset = 1'b0; - $display("%t: deassert reset %d", $time, reset); + @(negedge clock); + start = 1'b0; - #200 $display("%t: finish", $time); + @(posedge clock); + #1; + if (concurrent_fails != 1) $stop; + + $assertcontrol(5, 1, 1); + $asserton; + + $display("%t: finish", $time); $write("*-* All Finished *-*\n"); $finish; - end - always #10 clock = ~clock; - reg r = 1'b0; - - always @(posedge clock) if (reset) r <= 1'b1; + always #5 clock = ~clock; assert_test : - assert property (@(posedge clock) (reset | r)) - else $error("%t: assertion triggered", $time); + assert property (@(posedge clock) start |-> ##1 done) + else concurrent_fails++; endmodule diff --git a/test_regress/t/t_assert_ctl_immediate.out b/test_regress/t/t_assert_ctl_immediate.out index a10fd21c4..58b76b5f2 100644 --- a/test_regress/t/t_assert_ctl_immediate.out +++ b/test_regress/t/t_assert_ctl_immediate.out @@ -1,6 +1,15 @@ -[0] %Error: t_assert_ctl_immediate.v:51: Assertion failed in top.t.module_with_assertctl: 'assert' failed. --Info: t/t_assert_ctl_immediate.v:51: Verilog $stop, ignored due to +verilator+error+limit -[0] %Error: t_assert_ctl_immediate.v:57: Assertion failed in top.t.module_with_assertctl: 'assert' failed. -[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. -[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:52: Assertion failed in top.t.module_with_assertctl: 'assert' failed. +-Info: t/t_assert_ctl_immediate.v:52: Verilog $stop, ignored due to +verilator+error+limit +[0] %Error: t_assert_ctl_immediate.v:58: Assertion failed in top.t.module_with_assertctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:159: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:132: Assertion failed in top.t.module_with_method_ctl.Ctl.check_positive: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:169: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:93: Assertion failed in top.t.module_with_method_ctl.iface.check_positive: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:180: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:186: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:195: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:199: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:203: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. *-* All Finished *-* diff --git a/test_regress/t/t_assert_ctl_immediate.v b/test_regress/t/t_assert_ctl_immediate.v index 6c5848aaf..cc9d22b1b 100644 --- a/test_regress/t/t_assert_ctl_immediate.v +++ b/test_regress/t/t_assert_ctl_immediate.v @@ -10,6 +10,7 @@ module t ( module_with_assert module_with_assert (clk); module_with_assertctl module_with_assertctl (clk); + module_with_method_ctl module_with_method_ctl (); always @(posedge clk) begin assert (0); @@ -63,3 +64,146 @@ module module_with_assertctl ( f_assert(); end endmodule + +// Assertion control invoked from class methods and interface functions, in a +// single sub-module with a single initial block so the golden output order is +// stable across --fno-inline (otherwise parallel sub-module initials are +// interleaved differently by the inliner). +// +// Covers: +// - class task $assertoff / $asserton +// - class static method $assertcontrol(Off=4 / On=3), IEEE 1800-2023 Table 20-5 +// - $assertkill from a class method +// - assert that lives inside a class method (obeys context-global gating) +// - interface function $assertoff / $asserton +// - assert inside an interface function +// - virtual interface dispatch to an assertion-control function +// - interface class (AstClass with isInterfaceClass) via a concrete impl +// - multiple instances of each thing: OFF via one instance, ON via another +// (assertion control is global per-context, IEEE 1800-2023 20.11) + +interface AssertCtlIf; + function void suppress(); + $assertoff; + endfunction + function void enable(); + $asserton; + endfunction + function void check_positive(int v); + assert (v > 0); + endfunction +endinterface + +// verilog_format: off (verible-verilog-format mangles `pure virtual function`) +interface class IAssertCtl; + pure virtual function void suppress(); + pure virtual function void enable(); +endclass +// verilog_format: on + +class IAssertCtlImpl implements IAssertCtl; + virtual function void suppress(); + $assertoff; + endfunction + virtual function void enable(); + $asserton; + endfunction +endclass + +module module_with_method_ctl; + class Ctl; + virtual AssertCtlIf vif; + static function void off_all(); + $assertcontrol(4); + endfunction + static function void on_all(); + $asserton; + endfunction + function void kill_all(); + $assertkill; + endfunction + function void inst_off(); + $assertoff; + endfunction + function void inst_on(); + $asserton; + endfunction + function void check_positive(int v); + assert (v > 0); + endfunction + function void vif_suppress(); + vif.suppress(); + endfunction + function void vif_enable(); + vif.enable(); + endfunction + endclass + + Ctl c; + Ctl c2; + AssertCtlIf iface (); + AssertCtlIf iface2 (); + IAssertCtlImpl impl; + IAssertCtlImpl impl2; + + initial begin + c = new; + c2 = new; + impl = new; + impl2 = new; + + // --- class method coverage --- + Ctl::off_all(); + assert (0); // gated via class static -> no fire + Ctl::on_all(); + assert (0); // fires + Ctl::off_all(); + c.check_positive(-1); // assert inside class method, gated -> no fire + Ctl::on_all(); + c.check_positive(-2); // assert inside class method, fires + + // --- interface function coverage --- + iface.suppress(); + assert (0); // gated via iface fn -> no fire + iface.enable(); + assert (0); // fires + iface.suppress(); + iface.check_positive(-1); // assert inside iface fn, gated -> no fire + iface.enable(); + iface.check_positive(-2); // assert inside iface fn, fires + + // --- virtual interface dispatch coverage --- + c.vif = iface; + c.vif_suppress(); + assert (0); // gated via virtual interface dispatch -> no fire + c.vif_enable(); + assert (0); // fires + + // --- interface class via concrete impl --- + impl.suppress(); + assert (0); // gated via interface-class impl -> no fire + impl.enable(); + assert (0); // fires + + // --- multiple instances: OFF via one instance, ON via another --- + // Assertion control is global per-context (IEEE 1800-2023 20.11, no scope + // list), so OFF issued via one instance gates every assertion and ON issued + // via a different instance re-enables them. + c.inst_off(); // class: OFF via c + assert (0); // gated -> no fire + c2.inst_on(); // class: ON via c2 + assert (0); // fires + iface.suppress(); // interface: OFF via iface + assert (0); // gated -> no fire + iface2.enable(); // interface: ON via iface2 + assert (0); // fires + impl.suppress(); // interface class: OFF via impl + assert (0); // gated -> no fire + impl2.enable(); // interface class: ON via impl2 + assert (0); // fires + + // --- $assertkill (last: terminal per IEEE 1800-2023 Table 20-5) --- + c.kill_all(); + assert (0); // killed -> no fire + end +endmodule diff --git a/test_regress/t/t_assert_ctl_lock.py b/test_regress/t/t_assert_ctl_lock.py new file mode 100755 index 000000000..fdd1c0d4d --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary --assert"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_ctl_lock.v b/test_regress/t/t_assert_ctl_lock.v new file mode 100644 index 000000000..c47e3b341 --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock.v @@ -0,0 +1,67 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`ifdef verilator + `define no_optimize(v) $c(v) +`else + `define no_optimize(v) (v) +`endif +// verilog_format: on + +module t; + logic clk = 0; + int imm_fails = 0, conc_fails = 0; + logic a = 1'b1; // antecedent always true + logic b = 1'b0; // consequent always false -> assertion always violates + + always #5 clk = ~clk; + + // Immediate assertion: assertion_type SIMPLE_IMMEDIATE (mask value 2). + always @(posedge clk) + ia : + assert (b) + else imm_fails = imm_fails + 1; + + // Concurrent assertion: assertion_type CONCURRENT (mask value 1). + ca : + assert property (@(posedge clk) a |-> b) + else conc_fails = conc_fails + 1; + + int ctl; + + // posedge clk at t = 5, 15, 25, 35, 45, 55, 65, 75, 85, 95. + initial begin + // Phase A (t=0..): everything on. edge@5 -> imm+1, conc+1. + #6; // t=6 + // Phase B: lock the on state, then $assertoff must be ignored while locked. + $assertcontrol(1); // Lock, all types + $assertoff; // ignored -> edges @15,@25 still fire both + #24; // t=30 + // Phase C: unlock, then turn off only SIMPLE_IMMEDIATE via assertion_type filter. + $assertcontrol(2); // Unlock + $assertcontrol(4, 2); // Off, assertion_type = SIMPLE_IMMEDIATE only + #20; // t=50: edges @35,@45 -> imm off, conc still on + // Phase D: non-constant control_type and assertion_type re-enable the immediate. + ctl = `no_optimize(3); // On + $assertcontrol(ctl, `no_optimize(2)); // non-const On, SIMPLE_IMMEDIATE + #20; // t=70: edges @55,@65 -> both on + // Phase E: off everything, then a non-const action-control code (no runtime effect). + $assertcontrol(4); // Off all + ctl = `no_optimize(7); // Pass_Off (IEEE 1800-2023 Table 20-5 action control) + $assertcontrol(ctl); // non-const action-control: evaluated at runtime, ignored + #30; // t=100: edges @75,@85,@95 -> none + $finish; + end + + final begin + `checkd(imm_fails, 5); + `checkd(conc_fails, 7); // Other sims: 7 or 1 + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_assert_ctl_lock_noinl.py b/test_regress/t/t_assert_ctl_lock_noinl.py new file mode 100755 index 000000000..83440d018 --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock_noinl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t_assert_ctl_lock.v" + +test.compile(verilator_flags2=["--binary --assert --fno-inline"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_ctl_pass_actions.py b/test_regress/t/t_assert_ctl_pass_actions.py new file mode 100755 index 000000000..fdd1c0d4d --- /dev/null +++ b/test_regress/t/t_assert_ctl_pass_actions.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary --assert"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_ctl_pass_actions.v b/test_regress/t/t_assert_ctl_pass_actions.v new file mode 100644 index 000000000..3a7065569 --- /dev/null +++ b/test_regress/t/t_assert_ctl_pass_actions.v @@ -0,0 +1,227 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", \ + `__FILE__, `__LINE__, (gotv), (expv), `"gotv`", `"expv`"); \ + `stop; \ +end while (0) +// verilog_format: on + +`ifdef VERILATOR +// The '$c(1)' is there to prevent inlining of the signal by V3Gate. +`define IMPURE_ONE ($c(1)) +`else +// Use standard $random. The chance of getting 2 consecutive zeroes is negligible. +`define IMPURE_ONE (|($random | $random)) +`endif + +interface AssertCtlIface; + bit run_initial = 0; + bit initial_done = 0; + int fails = 0; + + function void clear(); + fails = 0; + endfunction + function void assert_off(); + $assertcontrol(4, 2, 1); + endfunction + function void assert_on(); + $assertcontrol(3, 2, 1); + endfunction + function void fail_check(); + assert (0) `stop; + else fails++; + endfunction + function void run_checks(); + assert_off(); + fail_check(); + assert_on(); + fail_check(); + endfunction + + initial begin + wait (run_initial); + run_checks(); + initial_done = 1; + end +endinterface + +module t; + bit clk = 0; + int imm_passes = 0; + int imm_fails = 0; + int vacuous_passes = 0; + int nonvacuous_passes = 0; + int concurrent_fails = 0; + int class_fails = 0; + + class AssertCtlClass; + function void assert_off(); + $assertcontrol(4, 2, 1); + endfunction + function void assert_on(); + $assertcontrol(3, 2, 1); + endfunction + function void fail_check(); + assert (0) `stop; + else class_fails++; + endfunction + endclass + + AssertCtlClass assert_ctl_class; + AssertCtlIface assert_ctl_iface (); + virtual AssertCtlIface v_assert_ctl_iface = assert_ctl_iface; + + always #5 clk = !clk; + + default clocking @(posedge clk); + endclocking + + assert property (@(posedge clk) 1'b0 |-> ##1 1'b1) begin + vacuous_passes++; + end + else `stop; + + assert property (@(posedge clk) 1'b1 |-> ##1 1'b1) begin + nonvacuous_passes++; + end + else `stop; + + assert property (@(posedge clk) 1'b1 |-> ##1 1'b0) begin + end + else concurrent_fails++; + + task automatic tick_and_check(input int exp_vacuous, input int exp_nonvacuous, + input int exp_concurrent_fails); + @(posedge clk); + #2; + `checkd(vacuous_passes, exp_vacuous); + `checkd(nonvacuous_passes, exp_nonvacuous); + `checkd(concurrent_fails, exp_concurrent_fails); + endtask + + initial begin + assert_ctl_class = new; + + $assertcontrol(4, 16, 1); + $assertcontrol(5, 16, 1); + $assertcontrol(3 * `IMPURE_ONE, 2 * `IMPURE_ONE); + + $assertcontrol(3, 255, 7); + $assertcontrol(6, 255, 7); + $assertcontrol(8, 255, 7); + + assert (1) imm_passes++; + else `stop; + `checkd(imm_passes, 1); + + $assertcontrol(1, 2, 1); + $assertcontrol(4, 2, 1); + assert (1) imm_passes++; + else `stop; + `checkd(imm_passes, 2); + + $assertcontrol(2, 2, 1); + $assertcontrol(4, 2, 1); + assert (1) imm_passes++; + else `stop; + `checkd(imm_passes, 2); + + $assertcontrol(3, 2, 1); + + $assertcontrol(1, 2, 1); + $assertcontrol(7, 2, 1); + assert (1) imm_passes++; + else `stop; + `checkd(imm_passes, 3); + + $assertcontrol(2, 2, 1); + $assertcontrol(7, 2, 1); + assert (1) imm_passes++; + else `stop; + `checkd(imm_passes, 3); + + $assertcontrol(10, 2, 1); + assert (1) imm_passes++; + else `stop; + `checkd(imm_passes, 4); + + $assertcontrol(11, 2, 1); + assert (1) imm_passes++; + else `stop; + `checkd(imm_passes, 5); + + $assertcontrol(6, 2, 1); + + assert (0) `stop; + else imm_fails++; + `checkd(imm_fails, 1); + + $assertcontrol(1, 2, 1); + $assertcontrol(9, 2, 1); + assert (0) `stop; + else imm_fails++; + `checkd(imm_fails, 2); + + $assertcontrol(2, 2, 1); + $assertcontrol(9, 2, 1); + assert (0) `stop; + else imm_fails++; + `checkd(imm_fails, 2); + + $assertcontrol(8, 2, 1); + assert (0) `stop; + else imm_fails++; + `checkd(imm_fails, 3); + + assert_ctl_class.assert_off(); + assert_ctl_class.fail_check(); + `checkd(class_fails, 0); + + assert_ctl_class.assert_on(); + assert_ctl_class.fail_check(); + `checkd(class_fails, 1); + + assert_ctl_iface.run_initial = 1; + wait (assert_ctl_iface.initial_done); + `checkd(assert_ctl_iface.fails, 1); + + assert_ctl_iface.clear(); + assert_ctl_iface.run_checks(); + `checkd(assert_ctl_iface.fails, 1); + + assert_ctl_iface.clear(); + v_assert_ctl_iface.run_checks(); + `checkd(assert_ctl_iface.fails, 1); + + $assertcontrol(9, 1, 1); + + tick_and_check(1, 0, 0); + tick_and_check(2, 1, 0); + + $assertcontrol(11, 1, 1); + tick_and_check(2, 2, 0); + + $assertcontrol(7, 1, 1); + tick_and_check(2, 2, 0); + + $assertcontrol(10, 1, 1); + tick_and_check(2, 3, 0); + + $assertcontrol(6, 1, 1); + tick_and_check(3, 4, 0); + + $assertcontrol(8, 1, 1); + tick_and_check(4, 5, 1); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_assert_ctl_type_runtime_bad.out b/test_regress/t/t_assert_ctl_type_runtime_bad.out new file mode 100644 index 000000000..aaa9ff404 --- /dev/null +++ b/test_regress/t/t_assert_ctl_type_runtime_bad.out @@ -0,0 +1 @@ +%Warning: Bad $assertcontrol control_type '100' (IEEE 1800-2023 Table 20-5) diff --git a/test_regress/t/t_assert_ctl_type_runtime_bad.py b/test_regress/t/t_assert_ctl_type_runtime_bad.py new file mode 100755 index 000000000..84d6702d8 --- /dev/null +++ b/test_regress/t/t_assert_ctl_type_runtime_bad.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary', '--assert']) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_assert_ctl_type_runtime_bad.v b/test_regress/t/t_assert_ctl_type_runtime_bad.v new file mode 100644 index 000000000..ab48ce35f --- /dev/null +++ b/test_regress/t/t_assert_ctl_type_runtime_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +`ifdef VERILATOR +// The '$c1(1)' is there to prevent inlining of the signal by V3Gate +`define IMPURE_ONE ($c(1)) +`else +// Use standard $random (chaces of getting 2 consecutive zeroes is zero). +`define IMPURE_ONE (|($random | $random)) +`endif + +module t; + initial begin + $assertcontrol(100 * `IMPURE_ONE); + $finish; + end +endmodule diff --git a/test_regress/t/t_assert_ctl_unsup.out b/test_regress/t/t_assert_ctl_unsup.out index 26e32ff0d..d0aaa3d7e 100644 --- a/test_regress/t/t_assert_ctl_unsup.out +++ b/test_regress/t/t_assert_ctl_unsup.out @@ -1,123 +1,74 @@ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:25:5: Unsupported: non-constant assert assertion-type expression - : ... note: In instance 't.unsupported_ctl_type' - 25 | $assertcontrol(Lock, a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:23:5: Unsupported: $assertcontrol control_type '6' + 23 | $assertcontrol(PassOn); | ^~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:27:5: Unsupported: $assertcontrol control_type '2' - 27 | $assertcontrol(Unlock); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:29:5: Unsupported: $assertcontrol control_type '6' - 29 | $assertcontrol(PassOn); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 30 | $assertpasson; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:24:5: Unsupported: $assertcontrol control_type '6' + 24 | $assertpasson; | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 31 | $assertpasson(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:25:5: Unsupported: $assertcontrol control_type '6' + 25 | $assertpasson(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:32:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 32 | $assertpasson(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:26:5: Unsupported: $assertcontrol control_type '6' + 26 | $assertpasson(a, t); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:34:5: Unsupported: $assertcontrol control_type '7' - 34 | $assertcontrol(PassOff); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:28:5: Unsupported: $assertcontrol control_type '7' + 28 | $assertcontrol(PassOff); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 35 | $assertpassoff; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:29:5: Unsupported: $assertcontrol control_type '7' + 29 | $assertpassoff; | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 36 | $assertpassoff(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: $assertcontrol control_type '7' + 30 | $assertpassoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:37:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 37 | $assertpassoff(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: $assertcontrol control_type '7' + 31 | $assertpassoff(a, t); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:39:5: Unsupported: $assertcontrol control_type '8' - 39 | $assertcontrol(FailOn); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:33:5: Unsupported: $assertcontrol control_type '8' + 33 | $assertcontrol(FailOn); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 40 | $assertfailon; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:34:5: Unsupported: $assertcontrol control_type '8' + 34 | $assertfailon; | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 41 | $assertfailon(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: $assertcontrol control_type '8' + 35 | $assertfailon(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:42:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 42 | $assertfailon(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: $assertcontrol control_type '8' + 36 | $assertfailon(a, t); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:44:5: Unsupported: $assertcontrol control_type '9' - 44 | $assertcontrol(FailOff); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:38:5: Unsupported: $assertcontrol control_type '9' + 38 | $assertcontrol(FailOff); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 45 | $assertfailoff; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:39:5: Unsupported: $assertcontrol control_type '9' + 39 | $assertfailoff; | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 46 | $assertfailoff(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: $assertcontrol control_type '9' + 40 | $assertfailoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:47:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 47 | $assertfailoff(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: $assertcontrol control_type '9' + 41 | $assertfailoff(a, t); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:49:5: Unsupported: $assertcontrol control_type '10' - 49 | $assertcontrol(NonvacuousOn); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:43:5: Unsupported: $assertcontrol control_type '10' + 43 | $assertcontrol(NonvacuousOn); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 50 | $assertnonvacuouson; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:44:5: Unsupported: $assertcontrol control_type '10' + 44 | $assertnonvacuouson; | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 51 | $assertnonvacuouson(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: $assertcontrol control_type '10' + 45 | $assertnonvacuouson(a); | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:52:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 52 | $assertnonvacuouson(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: $assertcontrol control_type '10' + 46 | $assertnonvacuouson(a, t); | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:54:5: Unsupported: $assertcontrol control_type '11' - 54 | $assertcontrol(VacuousOff); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:48:5: Unsupported: $assertcontrol control_type '11' + 48 | $assertcontrol(VacuousOff); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 55 | $assertvacuousoff; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:49:5: Unsupported: $assertcontrol control_type '11' + 49 | $assertvacuousoff; | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:56:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 56 | $assertvacuousoff(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: $assertcontrol control_type '11' + 50 | $assertvacuousoff(a); | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:57:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 57 | $assertvacuousoff(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: $assertcontrol control_type '11' + 51 | $assertvacuousoff(a, t); | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:64:5: Unsupported: non-const assert control type expression - : ... note: In instance 't.unsupported_ctl_type_expr' - 64 | $assertcontrol(ctl_type); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:93:7: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_class' - 93 | $asserton; - | ^~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:99:7: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_class' - 99 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:172:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface' - 172 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:138:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface_class' - 138 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:145:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface_class' - 145 | $asserton; - | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_unsup.v b/test_regress/t/t_assert_ctl_unsup.v index 98afcad10..027b4e824 100644 --- a/test_regress/t/t_assert_ctl_unsup.v +++ b/test_regress/t/t_assert_ctl_unsup.v @@ -4,28 +4,22 @@ // SPDX-FileCopyrightText: 2024 Antmicro // SPDX-License-Identifier: CC0-1.0 -module t(input logic clk); - unsupported_ctl_type unsupported_ctl_type(clk ? 1 : 2); - unsupported_ctl_type_expr unsupported_ctl_type_expr(); - assert_class assert_class(); - assert_iface assert_iface(); - assert_iface_class assert_iface_class(); +module t ( + input logic clk +); + unsupported_ctl_type unsupported_ctl_type (clk ? 1 : 2); endmodule -module unsupported_ctl_type(input int a); +module unsupported_ctl_type ( + input int a +); initial begin - let Lock = 1; - let Unlock = 2; let PassOn = 6; let PassOff = 7; let FailOn = 8; let FailOff = 9; let NonvacuousOn = 10; let VacuousOff = 11; - $assertcontrol(Lock, a); - - $assertcontrol(Unlock); - $assertcontrol(PassOn); $assertpasson; $assertpasson(a); @@ -57,129 +51,3 @@ module unsupported_ctl_type(input int a); $assertvacuousoff(a, t); end endmodule - -module unsupported_ctl_type_expr; - int ctl_type = 1; - initial begin - $assertcontrol(ctl_type); - end -endmodule - -module assert_class; - virtual class AssertCtl; - pure virtual function void virtual_assert_ctl(); - endclass - - class AssertCls; - static function void static_function(); - assert(0); - endfunction - static task static_task(); - assert(0); - endtask - function void assert_function(); - assert(0); - endfunction - task assert_task(); - assert(0); - endtask - virtual function void virtual_assert(); - assert(0); - endfunction - endclass - - class AssertOn extends AssertCtl; - virtual function void virtual_assert_ctl(); - $asserton; - endfunction - endclass - - class AssertOff extends AssertCtl; - virtual function void virtual_assert_ctl(); - $assertoff; - endfunction - endclass - - AssertCls assertCls; - AssertOn assertOn; - AssertOff assertOff; - initial begin - $assertoff; - AssertCls::static_function(); - AssertCls::static_task(); - $asserton; - AssertCls::static_function(); - AssertCls::static_task(); - - assertCls = new; - assertOn = new; - assertOff = new; - - assertOff.virtual_assert_ctl(); - assertCls.assert_function(); - assertCls.assert_task(); - assertCls.virtual_assert(); - - assertOn.virtual_assert_ctl(); - assertCls.assert_function(); - assertCls.assert_task(); - assertCls.virtual_assert(); - assertOff.virtual_assert_ctl(); - assertCls.assert_function(); - end -endmodule - -interface Iface; - function void assert_func(); - assert(0); - endfunction - - function void assertoff_func(); - $assertoff; - endfunction - - initial begin - assertoff_func(); - assert(0); - assert_func(); - $asserton; - assert(0); - assert_func(); - end -endinterface - -module assert_iface; - Iface iface(); - virtual Iface vIface = iface; - initial begin - vIface.assert_func(); - vIface.assertoff_func(); - vIface.assert_func(); - - iface.assert_func(); - iface.assertoff_func(); - iface.assert_func(); - end -endmodule - -interface class IfaceClass; - pure virtual function void assertoff_func(); - pure virtual function void assert_func(); -endclass - -class IfaceClassImpl implements IfaceClass; - virtual function void assertoff_func(); - $assertoff; - endfunction - virtual function void assert_func(); - assert(0); - endfunction -endclass - -module assert_iface_class; - IfaceClassImpl ifaceClassImpl = new; - initial begin - ifaceClassImpl.assertoff_func(); - ifaceClassImpl.assert_func(); - end -endmodule diff --git a/test_regress/t/t_assert_disabled.py b/test_regress/t/t_assert_disabled.py index 3a4d31c50..390d36ef3 100755 --- a/test_regress/t/t_assert_disabled.py +++ b/test_regress/t/t_assert_disabled.py @@ -10,9 +10,9 @@ import vltest_bootstrap test.scenarios('simulator') -test.top_filename = "t/t_assert_on.v" +test.top_filename = "t/t_assert_disabled.v" -test.compile(verilator_flags2=['--no-assert']) +test.compile(verilator_flags2=['--no-assert', '--timing']) test.execute() diff --git a/test_regress/t/t_assert_disabled.v b/test_regress/t/t_assert_disabled.v new file mode 100644 index 000000000..b09690d85 --- /dev/null +++ b/test_regress/t/t_assert_disabled.v @@ -0,0 +1,32 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + integer action_hits = 0; + integer cyc = 0; + + assert property (@(posedge clk) ##1 1'b1) action_hits++; + else action_hits--; + + always @(posedge clk) begin + cyc++; + assert (0); + if (cyc == 4) begin + `checkd(action_hits, 0); + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_assert_goto_rep.v b/test_regress/t/t_assert_goto_rep.v index f926a3b5b..1a209444b 100644 --- a/test_regress/t/t_assert_goto_rep.v +++ b/test_regress/t/t_assert_goto_rep.v @@ -75,14 +75,14 @@ module t ( end else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); - `checkd(count_fail1, 20); // Questa: 20 - `checkd(count_fail2, 25); // Questa: 25 - `checkd(count_fail3, 19); // Questa: 19 - `checkd(count_fail4, 0); // Questa: 0 - `checkd(count_fail5, 20); // Questa: 20 - `checkd(count_fail6, 25); // Questa: 25 - `checkd(count_fail7, 20); // Questa: 20 - `checkd(count_fail8, 20); // Questa: 20 + `checkd(count_fail1, 20); + `checkd(count_fail2, 25); + `checkd(count_fail3, 19); + `checkd(count_fail4, 0); + `checkd(count_fail5, 20); + `checkd(count_fail6, 25); + `checkd(count_fail7, 20); + `checkd(count_fail8, 20); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_assert_iff_clk.py b/test_regress/t/t_assert_iff_clk.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_assert_iff_clk.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_iff_clk.v b/test_regress/t/t_assert_iff_clk.v new file mode 100644 index 000000000..73c8b82b5 --- /dev/null +++ b/test_regress/t/t_assert_iff_clk.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro Ltd +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + int cyc; + logic rst = 1'b1; + logic x = 1'b1; + int issue_fail; + int pre_fail; + int post_fail; + int pre_temporal_fail; + int post_temporal_fail; + + a_issue: assert property (disable iff(rst !== 1'b0) @(posedge clk) !x) + else issue_fail++; + + assert property (disable iff (cyc < 5) @(posedge clk) 0) + else pre_fail++; + + assert property (@(posedge clk) disable iff (cyc < 5) 0) + else post_fail++; + + assert property (disable iff (cyc < 5) @(posedge clk) 1 ##1 0) + else pre_temporal_fail++; + + assert property (@(posedge clk) disable iff (cyc < 5) 1 ##1 0) + else post_temporal_fail++; + + always @(negedge clk) begin + cyc <= cyc + 1; + rst <= cyc < 4; + x <= cyc < 4; + if (cyc == 12) begin + if (issue_fail != 0) $stop; + if (pre_fail != post_fail) $stop; + if (pre_temporal_fail != post_temporal_fail) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_assert_iff_clk_unsup.out b/test_regress/t/t_assert_iff_clk_unsup.out deleted file mode 100644 index 3ff98273e..000000000 --- a/test_regress/t/t_assert_iff_clk_unsup.out +++ /dev/null @@ -1,6 +0,0 @@ -%Error-UNSUPPORTED: t/t_assert_iff_clk_unsup.v:20:20: Unsupported: property '(disable iff (...) @ (...)' - : ... Suggest use property '(@(...) disable iff (...))' - 20 | assert property (disable iff (cyc < 5) @(posedge clk) cyc >= 5); - | ^~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_assert_iff_clk_unsup.v b/test_regress/t/t_assert_iff_clk_unsup.v deleted file mode 100644 index efe917a04..000000000 --- a/test_regress/t/t_assert_iff_clk_unsup.v +++ /dev/null @@ -1,22 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2022 Antmicro Ltd -// SPDX-License-Identifier: CC0-1.0 - -module t ( - input clk -); - - input clk; - int cyc = 0; - logic val = 0; - - always @(posedge clk) begin - cyc <= cyc + 1; - val = ~val; - end - - assert property (disable iff (cyc < 5) @(posedge clk) cyc >= 5); - -endmodule diff --git a/test_regress/t/t_assert_nonconsec_rep.v b/test_regress/t/t_assert_nonconsec_rep.v index 6d759b9c2..c69db8bf9 100644 --- a/test_regress/t/t_assert_nonconsec_rep.v +++ b/test_regress/t/t_assert_nonconsec_rep.v @@ -56,10 +56,10 @@ module t ( end else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); - `checkd(count_fail1, 34); // Questa: 29 - `checkd(count_fail2, 27); // Questa: 32 - `checkd(count_fail3, 25); // Questa: 29 - `checkd(count_fail4, 0); // Questa: 0 + `checkd(count_fail1, 34); // Other sims: 29, one other: 20 + `checkd(count_fail2, 27); // Other sims: 32, one other: 25 + `checkd(count_fail3, 25); // Other sims: 29, one other: 25 + `checkd(count_fail4, 0); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_assert_opt_check.py b/test_regress/t/t_assert_opt_check.py index b7652f1ab..0e970d46e 100755 --- a/test_regress/t/t_assert_opt_check.py +++ b/test_regress/t/t_assert_opt_check.py @@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--binary', '--stats']) test.execute(check_finished=True) -test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 2) -test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 11) +test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 4) +test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 42) test.passes() diff --git a/test_regress/t/t_assert_opt_check.v b/test_regress/t/t_assert_opt_check.v index ccfea327e..7d0f54537 100644 --- a/test_regress/t/t_assert_opt_check.v +++ b/test_regress/t/t_assert_opt_check.v @@ -58,4 +58,12 @@ module t; end end + // Should combine the 2 nested assertOn checks after hoisting + always @(posedge clk) begin + if (assertEnable) begin + // This is an 'assert' with another 'assert' in the fail branch + assert(cntB - 100 == cntA); else assert(cntB == cntA + 100); + end + end + endmodule diff --git a/test_regress/t/t_assert_procedural_gated.v b/test_regress/t/t_assert_procedural_gated.v index 9ed88121f..56e38ed41 100644 --- a/test_regress/t/t_assert_procedural_gated.v +++ b/test_regress/t/t_assert_procedural_gated.v @@ -57,9 +57,8 @@ module t ( end else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); - // Questa 2022.3 golden: count_gated=5, count_ref=12. - `checkd(count_gated, 5); - `checkd(count_ref, 12); + `checkd(count_gated, 5); // Other sims same, one other: 4 + `checkd(count_ref, 12); // Other sims same, one other: 10 $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_assert_seq_clocking.py b/test_regress/t/t_assert_seq_clocking.py new file mode 100755 index 000000000..23f04b54c --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_seq_clocking.v b/test_regress/t/t_assert_seq_clocking.v new file mode 100644 index 000000000..9c59d7287 --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking.v @@ -0,0 +1,52 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + int unsigned crc = 32'h1; + bit a, b; + int cyc = 0; + int fails_single = 0; + int fails_multi = 0; + + // verilog_format: off // verible does not support clocking events inside sequence declarations + sequence s_single; + @(posedge clk) a + endsequence + + sequence s_multi; + @(posedge clk) (a ##1 b); + endsequence + + sequence s_unused; + @(posedge clk) b; + endsequence + // verilog_format: on + + ap_single: assert property (s_single) else fails_single = fails_single + 1; + ap_multi: assert property (s_multi) else fails_multi = fails_multi + 1; + + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[30:0], crc[31] ^ crc[21] ^ crc[1] ^ crc[0]}; + a <= crc[0]; + b <= crc[1]; + if (cyc == 40) $finish; + end + + // Counts read in final (Postponed) to avoid same-timestep races. + final begin + `checkd(fails_single, 17); // Other sims: 0 + `checkd(fails_multi, 17); // Other sims: 0 + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_assert_seq_clocking_unsup.out b/test_regress/t/t_assert_seq_clocking_unsup.out new file mode 100644 index 000000000..ba12d4ad2 --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking_unsup.out @@ -0,0 +1,34 @@ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:15:5: Unsupported: multiclocked sequence or property + : ... note: In instance 't' + 15 | @(posedge clk) a; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:19:5: Unsupported: clocking event inside sequence expression + : ... note: In instance 't' + 19 | @(posedge clk) b; + | ^ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:23:5: Unsupported: non-edge clocking event on a sequence; use an edge such as @(posedge clk) + : ... note: In instance 't' + 23 | @clk a; + | ^ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:27:5: Unsupported: non-edge clocking event on a sequence; use an edge such as @(posedge clk) + : ... note: In instance 't' + 27 | @clk a + | ^ +%Error: t/t_assert_seq_clocking_unsup.v:32:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16) + : ... note: In instance 't' + : ... Suggest provide a clocking event, a default clocking, or a clocked procedural context + 32 | assert property (s_nest ##1 a); + | ^~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_assert_seq_clocking_unsup.v:33:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16) + : ... note: In instance 't' + : ... Suggest provide a clocking event, a default clocking, or a clocked procedural context + 33 | assert property (s_level); + | ^~~~~~ +%Error: t/t_assert_seq_clocking_unsup.v:34:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16) + : ... note: In instance 't' + : ... Suggest provide a clocking event, a default clocking, or a clocked procedural context + 34 | assert property (s_level2); + | ^~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_initial_dlyass_bad.py b/test_regress/t/t_assert_seq_clocking_unsup.py similarity index 84% rename from test_regress/t/t_initial_dlyass_bad.py rename to test_regress/t/t_assert_seq_clocking_unsup.py index f8feb4a77..38cf36b43 100755 --- a/test_regress/t/t_initial_dlyass_bad.py +++ b/test_regress/t/t_assert_seq_clocking_unsup.py @@ -4,13 +4,12 @@ # This program is free software; you can redistribute it and/or modify it # under the terms of either the GNU Lesser General Public License Version 3 # or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap test.scenarios('linter') -test.top_filename = "t/t_initial_dlyass.v" test.lint(fails=True, expect_filename=test.golden_filename) diff --git a/test_regress/t/t_assert_seq_clocking_unsup.v b/test_regress/t/t_assert_seq_clocking_unsup.v new file mode 100644 index 000000000..785c67000 --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking_unsup.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk, + input clk2 +); + logic a, b; + + // verilog_format: off + sequence s_multi; + @(posedge clk) a; + endsequence + + sequence s_nest; + @(posedge clk) b; + endsequence + + sequence s_level; + @clk a; + endsequence + + sequence s_level2; + @clk a + endsequence + // verilog_format: on + + assert property (@(posedge clk2) s_multi); + assert property (s_nest ##1 a); + assert property (s_level); + assert property (s_level2); + +endmodule diff --git a/test_regress/t/t_assert_seq_event.py b/test_regress/t/t_assert_seq_event.py new file mode 100755 index 000000000..e8cdbc78d --- /dev/null +++ b/test_regress/t/t_assert_seq_event.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_seq_event.v b/test_regress/t/t_assert_seq_event.v new file mode 100644 index 000000000..d82078cff --- /dev/null +++ b/test_regress/t/t_assert_seq_event.v @@ -0,0 +1,97 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + int unsigned crc = 32'h1; + bit a, b, c; + bit a1, a2, a3, b1; + int cyc; + int seq_hits, seq_hits2, ref_hits, one_hits, dc_hits; + int rng_hits, rng_ref; + + // verilog_format: off // verible does not support clocking events inside sequence declarations + sequence seq; + @(posedge clk) a ##1 b ##1 c; + endsequence + + sequence seq_one; + @(posedge clk) a; + endsequence + + sequence seq_rng; + @(posedge clk) a ##[1:3] b; + endsequence + // verilog_format: on + + // seq_dc inherits the default clocking; counts must match seq + default clocking @(posedge clk); + endclocking + + sequence seq_dc; + a ##1 b ##1 c; + endsequence + + // ref_hits and rng_ref are independent shift-register oracles; + // simultaneous end points resume a blocked waiter once + initial forever begin + @seq; + seq_hits = seq_hits + 1; + end + initial forever begin + @seq; + seq_hits2 = seq_hits2 + 1; + end + initial forever begin + @seq_one; + one_hits = one_hits + 1; + end + initial forever begin + @seq_dc; + dc_hits = dc_hits + 1; + end + initial forever begin + @seq_rng; + rng_hits = rng_hits + 1; + end + + always @(posedge clk) begin + if (a2 && b1 && c) ref_hits = ref_hits + 1; + if (b && (a1 || a2 || a3)) rng_ref = rng_ref + 1; + a3 <= a2; + a2 <= a1; + a1 <= a; + b1 <= b; + end + + // a/b/c bit spacing exceeds the ##2 window to decorrelate the LFSR taps + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[30:0], crc[31] ^ crc[21] ^ crc[1] ^ crc[0]}; + a <= crc[0]; + b <= crc[4]; + c <= crc[8]; + if (cyc == 60) $finish; + end + + // Counts read in final (Postponed) to avoid same-timestep races. + final begin + `checkd(seq_hits, 14); + `checkd(seq_hits2, 14); + `checkd(ref_hits, 14); + `checkd(one_hits, 30); + `checkd(dc_hits, 14); + `checkd(rng_hits, 26); + `checkd(rng_ref, 26); + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_assert_seq_event_bad.out b/test_regress/t/t_assert_seq_event_bad.out new file mode 100644 index 000000000..7e24b4b9f --- /dev/null +++ b/test_regress/t/t_assert_seq_event_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_assert_seq_event_bad.v:19:7: Arguments to a sequence used as an event control must be static (IEEE 1800-2023 9.4.2.4) + : ... note: In instance 't' + 19 | @(s_arg(x)); + | ^~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_assert_iff_clk_unsup.py b/test_regress/t/t_assert_seq_event_bad.py similarity index 86% rename from test_regress/t/t_assert_iff_clk_unsup.py rename to test_regress/t/t_assert_seq_event_bad.py index b5718946c..f051d14b9 100755 --- a/test_regress/t/t_assert_iff_clk_unsup.py +++ b/test_regress/t/t_assert_seq_event_bad.py @@ -4,13 +4,13 @@ # This program is free software; you can redistribute it and/or modify it # under the terms of either the GNU Lesser General Public License Version 3 # or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap test.scenarios('vlt') -test.lint(expect_filename=test.golden_filename, verilator_flags2=['--assert'], fails=True) +test.lint(expect_filename=test.golden_filename, verilator_flags2=['--timing'], fails=True) test.passes() diff --git a/test_regress/t/t_assert_seq_event_bad.v b/test_regress/t/t_assert_seq_event_bad.v new file mode 100644 index 000000000..d6621aa9d --- /dev/null +++ b/test_regress/t/t_assert_seq_event_bad.v @@ -0,0 +1,23 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + // verilog_format: off + sequence s_arg(x); + @(posedge clk) x; + endsequence + // verilog_format: on + + task automatic f; + bit x = 1; + @(s_arg(x)); + endtask + + initial f(); +endmodule diff --git a/test_regress/t/t_assert_seq_event_ctl.py b/test_regress/t/t_assert_seq_event_ctl.py new file mode 100755 index 000000000..e8cdbc78d --- /dev/null +++ b/test_regress/t/t_assert_seq_event_ctl.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_seq_event_ctl.v b/test_regress/t/t_assert_seq_event_ctl.v new file mode 100644 index 000000000..ce9931e4b --- /dev/null +++ b/test_regress/t/t_assert_seq_event_ctl.v @@ -0,0 +1,73 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + int unsigned crc = 32'h1; + bit a, b, rst; + bit a1; + int cyc; + int hits, ref_hits, one_hits, one_ref; + + // Neither the default disable iff nor $assertoff may suppress a sequence event control + default disable iff (rst); + + // verilog_format: off // verible does not support clocking events inside sequence declarations + sequence seq; + @(posedge clk) a ##1 b; + endsequence + + sequence seq_one; + @(posedge clk) 1; + endsequence + // verilog_format: on + + initial begin + $assertoff; + #300 $assertkill; + end + + initial forever begin + @seq; + hits = hits + 1; + end + initial forever begin + @seq_one; + one_hits = one_hits + 1; + end + + always @(posedge clk) begin + if (a1 && b) ref_hits = ref_hits + 1; + one_ref = one_ref + 1; + a1 <= a; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[30:0], crc[31] ^ crc[21] ^ crc[1] ^ crc[0]}; + a <= crc[0]; + b <= crc[4]; + rst <= crc[2]; + end + + always @(negedge clk) begin + if (cyc == 60) $finish; + end + + final begin + `checkd(hits, ref_hits); + `checkd(one_hits, one_ref); + `checkd(hits, 19); + `checkd(one_hits, 60); + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_assert_seq_event_unsup.out b/test_regress/t/t_assert_seq_event_unsup.out new file mode 100644 index 000000000..61d38b9f7 --- /dev/null +++ b/test_regress/t/t_assert_seq_event_unsup.out @@ -0,0 +1,13 @@ +%Error-UNSUPPORTED: t/t_assert_seq_event_unsup.v:18:5: Unsupported: non-edge clocking event on a sequence; use an edge such as @(posedge clk) + : ... note: In instance 't' + 18 | @(g) a ##1 b; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_assert_seq_event_unsup.v:28:30: Unsupported: sequence used as an event control with a sequence operand of 'or' + 28 | @(posedge clk) (a ##1 b) or (a ##2 b); + | ^~ +%Error-UNSUPPORTED: t/t_assert_seq_event_unsup.v:21:12: Unsupported: sequence referenced outside assertion property + : ... note: In instance 't' + 21 | sequence s_ref; + | ^~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_assert_seq_event_unsup.py b/test_regress/t/t_assert_seq_event_unsup.py new file mode 100755 index 000000000..f051d14b9 --- /dev/null +++ b/test_regress/t/t_assert_seq_event_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(expect_filename=test.golden_filename, verilator_flags2=['--timing'], fails=True) + +test.passes() diff --git a/test_regress/t/t_assert_seq_event_unsup.v b/test_regress/t/t_assert_seq_event_unsup.v new file mode 100644 index 000000000..eda3b0772 --- /dev/null +++ b/test_regress/t/t_assert_seq_event_unsup.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + bit a, b; + logic g = 0; + + default clocking @(posedge clk); + endclocking + + // verilog_format: off + sequence s_nonedge; + @(g) a ##1 b; + endsequence + + sequence s_ref; + @(posedge clk) a; + endsequence + + // Legal but its endpoint topology is not buildable, so the wait could never + // resume; rejected rather than silently ignored. + sequence s_or; + @(posedge clk) (a ##1 b) or (a ##2 b); + endsequence + // verilog_format: on + + // Legal: p is never asserted, so s_ref stays referenced outside any + // assertion property, which is not yet supported. + property p; + s_ref; + endproperty + + initial begin + @s_nonedge; + @s_or; + end +endmodule diff --git a/test_regress/t/t_assert_unclocked_bad.out b/test_regress/t/t_assert_unclocked_bad.out new file mode 100644 index 000000000..60590a943 --- /dev/null +++ b/test_regress/t/t_assert_unclocked_bad.out @@ -0,0 +1,22 @@ +%Error: t/t_assert_unclocked_bad.v:9:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16) + : ... note: In instance 't' + : ... Suggest provide a clocking event, a default clocking, or a clocked procedural context + 9 | assert property (a); + | ^~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_assert_unclocked_bad.v:10:22: Concurrent assertion has no clock (IEEE 1800-2023 16.16) + : ... note: In instance 't' + : ... Suggest provide a clocking event, a default clocking, or a clocked procedural context + 10 | assert property (a |=> b); + | ^~~ +%Error: t/t_assert_unclocked_bad.v:10:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16) + : ... note: In instance 't' + : ... Suggest provide a clocking event, a default clocking, or a clocked procedural context + 10 | assert property (a |=> b); + | ^~~~~~ +%Error: t/t_assert_unclocked_bad.v:11:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16) + : ... note: In instance 't' + : ... Suggest provide a clocking event, a default clocking, or a clocked procedural context + 11 | cover property (a); + | ^~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_assert_unclocked_bad.py b/test_regress/t/t_assert_unclocked_bad.py new file mode 100755 index 000000000..77a0ac64b --- /dev/null +++ b/test_regress/t/t_assert_unclocked_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(expect_filename=test.golden_filename, fails=True) + +test.passes() diff --git a/test_regress/t/t_assert_unclocked_bad.v b/test_regress/t/t_assert_unclocked_bad.v new file mode 100644 index 000000000..ae659cf7b --- /dev/null +++ b/test_regress/t/t_assert_unclocked_bad.v @@ -0,0 +1,12 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic a, b; + assert property (a); + assert property (a |=> b); + cover property (a); +endmodule diff --git a/test_regress/t/t_assertcontrol.py b/test_regress/t/t_assertcontrol.py new file mode 100755 index 000000000..fdd1c0d4d --- /dev/null +++ b/test_regress/t/t_assertcontrol.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary --assert"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assertcontrol.v b/test_regress/t/t_assertcontrol.v new file mode 100644 index 000000000..5088aa280 --- /dev/null +++ b/test_regress/t/t_assertcontrol.v @@ -0,0 +1,112 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", \ + `__FILE__, `__LINE__, (gotv), (expv), `"gotv`", `"expv`"); \ + `stop; \ +end while (0) +// verilog_format: on + +module t; + let LOCK = 1; + let UNLOCK = 2; + let ON = 3; + let OFF = 4; + let PASS_ON = 6; + let PASS_OFF = 7; + let FAIL_ON = 8; + let FAIL_OFF = 9; + + let SIMPLE_IMMEDIATE = 2; + let ASSERT = 1; + + int pass_count = 0; + int fail_count = 0; + int ctl_type = 0; + + task automatic run_pass(); + assert (1) begin + pass_count++; + end + else `stop; + endtask + + task automatic run_fail(); + assert (0) `stop; + else begin + fail_count++; + end + endtask + + initial begin + $assertcontrol(ON, 255, 7); + $assertcontrol(PASS_ON, 255, 7); + $assertcontrol(FAIL_ON, 255, 7); + + run_pass(); + `checkd(pass_count, 1); + + // Lock preserves the enabled checking state against OFF. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + ctl_type = OFF; + $assertcontrol(ctl_type, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 2); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(OFF, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 2); + + // Lock preserves the disabled checking state against ON. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(ON, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 2); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(ON, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 3); + + // Lock also preserves pass-action state. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(PASS_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 4); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(PASS_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 4); + + $assertcontrol(PASS_ON, SIMPLE_IMMEDIATE, ASSERT); + + run_fail(); + `checkd(fail_count, 1); + + // Lock also preserves fail-action state. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(FAIL_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_fail(); + `checkd(fail_count, 2); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(FAIL_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_fail(); + `checkd(fail_count, 2); + + $assertcontrol(FAIL_ON, SIMPLE_IMMEDIATE, ASSERT); + run_fail(); + `checkd(fail_count, 3); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_assertcontrol_noinl.py b/test_regress/t/t_assertcontrol_noinl.py new file mode 100755 index 000000000..cbc5575bf --- /dev/null +++ b/test_regress/t/t_assertcontrol_noinl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t_assertcontrol.v" + +test.compile(verilator_flags2=["--binary --assert --fno-inline"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_ast_dump_patterns.out b/test_regress/t/t_ast_dump_patterns.out new file mode 100644 index 000000000..4d2ea4bb2 --- /dev/null +++ b/test_regress/t/t_ast_dump_patterns.out @@ -0,0 +1,251 @@ +AST patterns with depth 1 + 126 (CONST #A):a/a + 54 (VARREF):a/b + 36 (CCAST (VARREF):a/b):a/b + 34 (AND (CONST #A):a/a _:a/1):a/1 + 29 (VARREF):a/a + 23 (CONST ZERO):a/a + 21 (VARREF):(w64)u[1:0] + 20 (CCAST _:a/1):a/1 + 18 (AND (CONST #A):a/a _:a/a):a/a + 18 (SHIFTR _:a/b (CONST #A):a/a):a/1 + 17 (VARREF):a/1 + 15 (SHIFTL _:a/1 (CONST #A):a/a):a/a + 14 (NEGATE _:a/1):a/a + 12 (AND (CONST #A):a/a _:a/b):a/b + 12 (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b + 12 (NOT _:a/b):a/b + 12 (VARREF):(w64)u[0:0] + 11 (OR _:a/a _:a/b):a/c + 9 (OR _:a/a _:a/1):a/b + 9 (VARREF):(G/str) + 8 (CCAST _:a/a):a/a + 8 (CCAST _:a/a):b/b + 8 (CRESET):a/a + 8 (NOT _:a/a):a/a + 8 (REDXOR _:a/b):a/1 + 7 (SHIFTL _:a/b (CONST #A):a/a):a/a + 6 (AND _:a/b _:a/b):a/b + 6 (REDXOR _:a/a):b/1 + 5 (CCAST _:a/a):b/1 + 5 (OR _:a/a _:a/a):a/a + 4 (ADD _:a/a (VARREF):a/a):a/a + 4 (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):a/a):b/b + 4 (CCAST (CONST #A):a/a):a/a + 4 (CCAST (VARREF):a/1):a/1 + 4 (CCAST _:a/1):b/1 + 4 (CONST #A):(G/str) + 4 (CONST #A):a/1 + 4 (NEGATE _:a/1):a/b + 4 (SHIFTL _:a/a (CONST #A):b/b):a/a + 3 (AND (VARREF):a/a (CONST #A):b/b):a/a + 3 (CONST ZERO):a/1 + 3 (CRESET):(w64)u[0:0] + 3 (CRESET):(w64)u[1:0] + 3 (NOT _:a/1):a/1 + 3 (OR (CONST #A):a/a _:a/a):a/a + 2 (CCALL [(VARREF):(w64)u[0:0], (VARREF):(w64)u[0:0]]):a/a + 2 (CCALL [(VARREF):(w64)u[0:0]]):a/1 + 2 (CCALL [(VARREF):(w64)u[1:0], (VARREF):(w64)u[1:0]]):a/a + 2 (CCALL [(VARREF):(w64)u[1:0]]):a/1 + 2 (CCALL []):a/1 + 2 (CRESET):(G/str) + 2 (GT (CONST #A):a/a (VARREF):a/a):a/1 + 2 (LT (CONST #A):a/a (VARREF):a/a):a/1 + 2 (NEQ _:a/b _:a/b):a/1 + 2 (OR _:a/a _:a/a):a/b + 2 (REDXOR (VARREF):a/b):a/1 + 2 (REDXOR _:a/a):a/1 + 2 (REDXOR _:a/b):c/1 + 2 (SHIFTR _:a/a (CONST #A):b/b):a/a + 1 (ARRAYSEL (VARREF):(w64)u[0:0] (VARREF):a/a):b/b + 1 (ARRAYSEL (VARREF):(w64)u[1:0] (CONST #A):a/a):b/b + 1 (ARRAYSEL (VARREF):(w64)u[1:0] (VARREF):a/a):b/b + 1 (CCAST _:a/1):b/b + 1 (CCAST _:a/b):a/b + 1 (CCAST _:a/b):c/c + 1 (CRESET):1/1 + 1 (NEQ _:a/b _:a/b):a/c + 1 (VARREF):1/1 + +AST patterns with depth 2 + 18 (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1 + 18 (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1 + 18 (SHIFTR (CCAST (VARREF):a/b):a/b (CONST #A):a/a):a/1 + 14 (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a + 12 (NOT (CCAST (VARREF):a/b):a/b):a/b + 10 (NEGATE (CCAST _:a/1):a/1):a/a + 8 (CCAST (CCAST _:a/a):a/a):b/b + 8 (CCAST (NOT _:a/a):a/a):a/a + 8 (NOT (NEGATE _:a/1):a/a):a/a + 8 (OR (AND (CONST #A):a/a _:a/a):a/a (AND (CONST #B):a/a _:a/1):a/1):a/b + 8 (REDXOR (AND (CONST #A):a/a _:a/b):a/b):a/1 + 6 (AND (CONST #A):a/a (AND _:a/b _:a/b):a/b):a/b + 6 (AND (NOT _:a/b):a/b (NOT _:a/b):a/b):a/b + 6 (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c + 6 (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a + 5 (AND (CONST #A):a/a (CCAST _:b/b):a/1):a/1 + 4 (ADD (CCAST (CONST #A):a/a):a/a (VARREF):a/a):a/a + 4 (AND (CONST #A):a/a (NEGATE _:a/1):a/b):a/b + 4 (AND (CONST #A):a/a (REDXOR _:a/b):a/1):a/1 + 4 (AND (CONST #A):a/a (REDXOR _:b/b):a/1):a/1 + 4 (CCAST (CCAST _:a/1):a/1):b/1 + 4 (NEGATE (CCAST _:a/1):a/1):a/b + 4 (NEGATE (CCAST _:a/1):b/1):b/b + 4 (REDXOR (NEGATE _:a/1):a/a):b/1 + 4 (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b + 4 (SHIFTL (REDXOR _:a/b):a/1 (CONST #A):a/a):a/a + 3 (AND (CONST #A):a/a (NOT _:a/1):a/1):a/1 + 3 (OR (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):b/b):a/a):a/a + 2 (AND (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):b/b):a/a):a/a + 2 (AND (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):b/b):a/a):a/a + 2 (AND (CONST #A):a/a (OR _:a/a _:a/a):a/b):a/b + 2 (CCAST (SHIFTR _:a/a (CONST #A):b/b):a/a):b/1 + 2 (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1 + 2 (NOT (CCAST _:a/1):a/1):a/1 + 2 (OR (OR _:a/a _:a/a):a/a (OR _:a/a _:a/b):a/c):a/d + 2 (OR (SHIFTL _:a/a (CONST #A):b/b):a/a (CCAST _:b/b):a/a):a/a + 2 (OR (SHIFTL _:a/a (CONST #A):b/b):a/a (CCAST _:b/b):a/a):a/c + 2 (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/c):a/d + 2 (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (SHIFTL _:a/b (CONST #B):a/a):a/a):a/a + 2 (REDXOR (AND (CONST #A):a/a _:a/b):a/b):c/1 + 2 (REDXOR (NEGATE _:a/1):a/a):a/1 + 2 (REDXOR (OR _:a/a _:a/a):a/a):b/1 + 2 (SHIFTL (CCAST (VARREF):a/1):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR (VARREF):a/b):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR _:a/a):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR _:a/a):b/1 (CONST #A):b/b):b/b + 2 (SHIFTL (REDXOR _:a/b):c/1 (CONST #A):c/c):c/c + 2 (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #A):a/a):b/b + 1 (CCAST (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):a/a):b/b):a/1 + 1 (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST #A):a/a):b/b):a/1 + 1 (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b):a/1 + 1 (CCAST (CCALL [(VARREF):(w64)u[0:0]]):a/1):a/1 + 1 (CCAST (CCALL [(VARREF):(w64)u[1:0]]):a/1):a/1 + 1 (CCAST (CCAST (VARREF):a/1):a/1):b/b + 1 (CCAST (CCAST _:a/b):a/b):c/c + 1 (CCAST (OR _:a/a _:a/b):a/c):a/c + 1 (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/c + 1 (NOT (CCAST (VARREF):a/1):a/1):a/1 + 1 (OR (AND (CONST #A):a/a _:a/a):a/a (CCAST _:b/1):a/a):a/a + 1 (OR (SHIFTL _:a/1 (CONST #A):a/a):a/a (NEQ _:a/b _:a/b):a/1):a/c + 1 (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/b):a/c + 1 (SHIFTL (NEQ _:a/b _:a/b):a/1 (CONST #A):a/a):a/a + 1 (SHIFTL (NEQ _:a/b _:a/b):a/c (CONST #A):a/a):a/a + +AST patterns with depth 3 + 18 (AND (CONST #A):a/a (SHIFTR (CCAST (VARREF):a/b):a/b (CONST #A):a/a):a/1):a/1 + 18 (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1 + 10 (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/a + 8 (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b + 8 (CCAST (NOT (NEGATE _:a/1):a/a):a/a):a/a + 8 (NOT (NEGATE (CCAST _:a/1):a/1):a/a):a/a + 6 (AND (CONST #A):a/a (AND (NOT _:a/b):a/b (NOT _:a/b):a/b):a/b):a/b + 6 (AND (NOT (CCAST (VARREF):a/b):a/b):a/b (NOT (CCAST (VARREF):a/b):a/b):a/b):a/b + 6 (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a _:a/a):a/a (AND (CONST #C):a/a _:a/1):a/1):a/b):a/c + 6 (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #B):a/a):a/a + 4 (AND (CONST #A):a/a (NEGATE (CCAST _:a/1):a/1):a/b):a/b + 4 (AND (CONST #A):a/a (REDXOR (AND (CONST #B):a/a _:a/b):a/b):a/1):a/1 + 4 (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/b):a/1 (CONST #B):a/a):a/a):a/a + 4 (CCAST (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):b/1 + 4 (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/b + 4 (NEGATE (CCAST (CCAST _:a/1):a/1):b/1):b/b + 4 (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR _:a/b):a/1):a/1):a/c + 4 (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR _:b/b):a/1):a/1):a/c + 4 (REDXOR (AND (CONST #A):a/a (AND _:a/b _:a/b):a/b):a/b):a/1 + 4 (REDXOR (AND (CONST #A):a/a (NEGATE _:a/1):a/b):a/b):a/1 + 4 (REDXOR (NEGATE (CCAST _:a/1):b/1):b/b):a/1 + 4 (SHIFTL (CCAST (CCAST _:a/a):a/a):b/b (CONST #A):a/a):b/b + 4 (SHIFTL (REDXOR (AND (CONST #A):a/a _:a/b):a/b):a/1 (CONST #B):a/a):a/a + 2 (AND (CONST #A):a/a (NOT (CCAST _:a/1):a/1):a/1):a/1 + 2 (AND (CONST #A):a/a (OR (SHIFTL _:a/a (CONST #B):b/b):a/a (CCAST _:b/b):a/a):a/c):a/c + 2 (AND (CONST #A):a/a (REDXOR (NEGATE _:b/1):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (OR _:b/b _:b/b):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (SHIFTL (CCAST (VARREF):a/1):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (VARREF):a/b):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/a):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/b):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/c):a/1 (CONST #B):a/a):a/a):a/a + 2 (CCAST (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #A):a/a):b/b):a/1 + 2 (OR (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (SHIFTL _:a/b (CONST #B):a/a):a/a):a/a (OR (SHIFTL _:a/b (CONST #C):a/a):a/a (OR _:a/a _:a/1):a/c):a/d):a/e + 2 (OR (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST _:a/a):a/a):b/b):b/b + 2 (OR (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST _:a/a):a/a):b/b):b/c + 2 (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a (OR (AND (CONST #A):a/a _:a/a):a/a (AND (CONST #B):a/a _:a/1):a/1):a/b):a/d + 2 (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a (SHIFTL (OR _:a/a _:a/b):a/c (CONST #B):a/a):a/a):a/a + 2 (REDXOR (AND (CONST #A):a/a (OR _:a/a _:a/a):a/b):a/b):c/1 + 2 (REDXOR (NEGATE (CCAST _:a/1):a/1):a/a):a/1 + 2 (REDXOR (OR (SHIFTL _:a/a (CONST #A):b/b):a/a (CCAST _:b/b):a/a):a/a):b/1 + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a _:a/b):a/b):c/1 (CONST #B):c/c):c/c + 2 (SHIFTL (REDXOR (NEGATE _:a/1):a/a):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR (NEGATE _:a/1):a/a):b/1 (CONST #A):b/b):b/b + 1 (AND (CONST #A):a/a (CCAST (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST #A):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (SHIFTR _:b/b (CONST #A):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (SHIFTR _:b/b (CONST #B):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (NOT (CCAST (VARREF):a/1):a/1):a/1):a/1 + 1 (CCAST (CCAST (OR _:a/a _:a/b):a/c):a/c):d/d + 1 (CCAST (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/b):a/c):a/c + 1 (NOT (CCAST (CCALL [(VARREF):(w64)u[0:0]]):a/1):a/1):a/1 + 1 (NOT (CCAST (CCALL [(VARREF):(w64)u[1:0]]):a/1):a/1):a/1 + 1 (OR (AND (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):b/b):a/a):a/a (CCAST (CCAST (VARREF):b/1):b/1):a/a):a/a + 1 (OR (SHIFTL (NEQ _:a/b _:a/b):a/1 (CONST #A):a/a):a/a (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1):a/c + 1 (OR (SHIFTL (NEQ _:a/b _:a/b):a/c (CONST #A):a/a):a/a (OR (SHIFTL _:a/1 (CONST #B):a/a):a/a (NEQ _:a/b _:a/b):a/1):a/c):a/b + 1 (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1 (CONST #A):a/a):a/a + 1 (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/c (CONST #A):a/a):a/a + +AST patterns with depth 4 + 18 (CCAST (AND (CONST #A):a/a (SHIFTR (CCAST (VARREF):a/b):a/b (CONST #A):a/a):a/1):a/1):a/1 + 10 (NEGATE (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1):a/a + 8 (CCAST (CCAST (NOT (NEGATE _:a/1):a/a):a/a):a/a):b/b + 8 (CCAST (NOT (NEGATE (CCAST _:a/1):a/1):a/a):a/a):a/a + 8 (NOT (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/a):a/a + 6 (AND (CONST #A):a/a (AND (NOT (CCAST (VARREF):a/b):a/b):a/b (NOT (CCAST (VARREF):a/b):a/b):a/b):a/b):a/b + 4 (AND (CONST #A):a/a (NEGATE (CCAST (AND (CONST #B):a/a _:a/1):a/1):a/1):a/b):a/b + 4 (AND (CONST #A):a/a (SHIFTL (REDXOR (AND (CONST #B):a/a _:a/b):a/b):a/1 (CONST #C):a/a):a/a):a/a + 4 (CCAST (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1):c/1 + 4 (NEGATE (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1):a/c + 4 (NEGATE (CCAST (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):b/1):b/b + 4 (REDXOR (AND (CONST #A):a/a (AND (NOT _:a/b):a/b (NOT _:a/b):a/b):a/b):a/b):a/1 + 4 (REDXOR (AND (CONST #A):a/a (NEGATE (CCAST _:a/1):a/1):a/b):a/b):a/1 + 4 (REDXOR (NEGATE (CCAST (CCAST _:a/1):a/1):b/1):b/b):a/1 + 4 (SHIFTL (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b (CONST #A):a/a):b/b + 4 (SHIFTL (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a _:a/a):a/a (AND (CONST #C):a/a _:a/1):a/1):a/b):a/c (CONST #D):a/a):a/a + 2 (AND (CONST #A):a/a (OR (SHIFTL (CCAST _:b/b):a/a (CONST #B):b/b):a/a (CCAST (CCAST _:b/b):b/b):a/a):a/c):a/c + 2 (AND (CONST #A):a/a (REDXOR (AND (CONST #B):a/a (AND _:a/b _:a/b):a/b):a/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (AND (CONST #B):a/a (NEGATE _:a/1):a/b):a/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (NEGATE (CCAST _:a/1):b/1):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (OR (SHIFTL _:b/b (CONST #B):a/a):b/b (CCAST _:a/a):b/b):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (AND (CONST #B):b/b _:b/c):b/c):a/1 (CONST #C):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (NEGATE _:a/1):a/a):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (NEGATE _:b/1):b/b):a/1 (CONST #B):a/a):a/a):a/a + 2 (OR (AND (CONST #A):a/a (SHIFTL (CCAST (VARREF):a/1):a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:a/b):a/1):a/1):a/c):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR (VARREF):a/b):a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:a/b):a/1):a/1):a/c):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/a):a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:b/b):a/1):a/1):a/c):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/b):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (AND (CONST #C):a/a _:a/b):a/b):a/1):a/1):a/c + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/b):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (AND (CONST #C):a/a _:a/c):a/c):a/1):a/1):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/b):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (OR _:b/b _:b/b):b/b):a/1):a/1):a/c + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/c):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (NEGATE _:b/1):b/b):a/1):a/1):a/d + 2 (OR (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a (SHIFTL (OR _:a/a _:a/b):a/c (CONST #B):a/a):a/a):a/a (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #C):a/a):a/a (OR (AND (CONST #C):a/a _:a/a):a/a (AND (CONST #D):a/a _:a/1):a/1):a/b):a/d):a/e + 2 (OR (SHIFTL (CCAST (CCAST _:a/a):a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b):b/b + 2 (OR (SHIFTL (CCAST (CCAST _:a/a):a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b):b/c + 2 (OR (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #B):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:d/d):a/1):a/1):a/b):a/e + 2 (OR (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #B):a/a):a/a (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #C):a/a):a/a):a/a + 2 (REDXOR (AND (CONST #A):a/a (OR (SHIFTL _:a/a (CONST #B):b/b):a/a (CCAST _:b/b):a/a):a/c):a/c):b/1 + 2 (REDXOR (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/a):a/1 + 2 (REDXOR (OR (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST _:a/a):a/a):b/b):b/b):a/1 + 2 (SHIFTL (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a _:a/a):a/a (AND (CONST #C):a/a _:a/1):a/1):a/b):a/c (CONST #B):a/a):a/a + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a (AND _:a/b _:a/b):a/b):a/b):a/1 (CONST #B):a/a):a/a + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a (NEGATE _:a/1):a/b):a/b):a/1 (CONST #B):a/a):a/a + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a (OR _:a/a _:a/a):a/b):a/b):c/1 (CONST #B):c/c):c/c + 2 (SHIFTL (REDXOR (NEGATE (CCAST _:a/1):a/1):a/a):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR (NEGATE (CCAST _:a/1):b/1):b/b):a/1 (CONST #A):a/a):a/a + 1 (AND (CONST #A):a/a (CCAST (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #A):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #B):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (NOT (CCAST (CCALL [(VARREF):(w64)u[0:0]]):a/1):a/1):a/1):a/1 + 1 (AND (CONST #A):a/a (NOT (CCAST (CCALL [(VARREF):(w64)u[1:0]]):a/1):a/1):a/1):a/1 + 1 (CCAST (CCAST (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/b):a/c):a/c):d/d + 1 (CCAST (OR (SHIFTL (NEQ _:a/b _:a/b):a/c (CONST #A):a/a):a/a (OR (SHIFTL _:a/1 (CONST #B):a/a):a/a (NEQ _:a/b _:a/b):a/1):a/c):a/b):a/b + 1 (OR (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1 (CONST #A):a/a):a/a (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1):a/c + 1 (OR (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/c (CONST #A):a/a):a/a (OR (SHIFTL (NEQ _:a/b _:a/b):a/1 (CONST #B):a/a):a/a (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1):a/c):a/b + diff --git a/test_regress/t/t_ast_dump_patterns.py b/test_regress/t/t_ast_dump_patterns.py new file mode 100755 index 000000000..d1bf14b33 --- /dev/null +++ b/test_regress/t/t_ast_dump_patterns.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--dump-ast-patterns", "--no-skip-identical"]) + +test.files_identical(test.obj_dir + "/" + test.vm_prefix + "__ast_patterns_emit.txt", + test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_ast_dump_patterns.v b/test_regress/t/t_ast_dump_patterns.v new file mode 100644 index 000000000..dc96474fb --- /dev/null +++ b/test_regress/t/t_ast_dump_patterns.v @@ -0,0 +1,26 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire [3:0] a, + input wire [3:0] b, + input wire [3:0] c, + output wire [10:0] o +); + wire [3:0] x = ~a & ~b; + wire [3:0] y = ~b & ~c; + wire [3:0] z = ~c & ~a; + wire [0:0] w1 = x[0]; + wire [7:0] w8 = {8{x[1]}}; + wire [15:0] w16 = {2{w8}}; + wire [31:0] w32 = {2{w16}}; + wire [63:0] w64a = {2{w32}}; + wire [63:0] w64b = {2{~w32}}; + wire [62:0] w63 = 63'({2{~w32}}); + wire [95:0] w96 = 96'(w64a); + + assign o = {^x, ^y, ^z, ^w1, ^w8, ^w16, ^w32, ^w64a, ^w64b, ^w63, ^w96}; +endmodule diff --git a/test_regress/t/t_bit_scan_loops.py b/test_regress/t/t_bit_scan_loops.py new file mode 100755 index 000000000..07b7818b1 --- /dev/null +++ b/test_regress/t/t_bit_scan_loops.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# --unroll-count 0 so the loops are recognized without relying on unrolling. +test.compile(verilator_flags2=['--stats', '--unroll-count', '0']) + +# The leading-one positives lower to $mostsetbitp1, the count-ones positive to +# $countones; the negatives are left as loops (a wrong lowering would raise a count). +test.file_grep(test.stats, + r'Optimizations, Loop unrolling, Lowered priority-encoder to mostsetbitp1\s+(\d+)', + 8) +test.file_grep(test.stats, + r'Optimizations, Loop unrolling, Lowered count-set-bits to countones\s+(\d+)', 1) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_bit_scan_loops.v b/test_regress/t/t_bit_scan_loops.v new file mode 100644 index 000000000..fa2c1df00 --- /dev/null +++ b/test_regress/t/t_bit_scan_loops.v @@ -0,0 +1,169 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Exercises the bit-counting loop idioms that V3Unroll lowers to builtins: +// leading-one for (b=0;b $mostsetbitp1(vec) +// count-ones for (b=0;b $countones(vec) +// Positives must lower (counted via --stats by the .py); negatives compute a +// different value than the builtin and so must be left as loops. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + // ---- positives: must lower ---- + logic [31:0] p32; + logic [5:0] n32; // I path, narrow target (select resize) + logic [47:0] p48; + logic [6:0] n48; // Q path + logic [79:0] p80; + logic [6:0] n80; // W path + logic [31:0] pu; + logic [5:0] nu; // unsigned loop index + logic [31:0] p32e; + logic [31:0] n32e; // 32-bit target (no resize) + logic [31:0] p32w; + logic [39:0] n40; // >32-bit target (extend resize) + logic [31:0] pc; + logic [5:0] nc; // count-ones -> $countones + logic [31:0] kvec; // const (set in initial) -> exercises $mostsetbitp1 fold + logic [5:0] kn; + initial kvec = 32'h0000_0100; + logic [31:0] kvec0; // const 0 -> $mostsetbitp1(0)=0 (covers the zero path) + logic [5:0] kn0; + initial kvec0 = 32'h0; + always_comb begin + n32 = 0; + for (int b = 0; b < 32; b++) if (p32[b]) n32 = 6'(b + 1); + end + always_comb begin + n48 = 0; + for (int b = 0; b < 48; b++) if (p48[b]) n48 = 7'(b + 1); + end + always_comb begin + n80 = 0; + for (int b = 0; b < 80; b++) if (p80[b]) n80 = 7'(b + 1); + end + always_comb begin + nu = 0; + for (int unsigned b = 0; b < 32; b++) if (pu[b]) nu = 6'(b + 1); + end + always_comb begin + n32e = 0; + for (int b = 0; b < 32; b++) if (p32e[b]) n32e = 32'(b + 1); + end + always_comb begin + n40 = 0; + for (int b = 0; b < 32; b++) if (p32w[b]) n40 = 40'(b + 1); + end + always_comb begin + nc = 0; + for (int b = 0; b < 32; b++) if (pc[b]) nc = nc + 1; + end + always_comb begin + kn = 0; + for (int b = 0; b < 32; b++) if (kvec[b]) kn = 6'(b + 1); + end + always_comb begin + kn0 = 0; + for (int b = 0; b < 32; b++) if (kvec0[b]) kn0 = 6'(b + 1); + end + + // ---- negatives: must NOT lower (each yields a different value than the builtin) ---- + logic [31:0] vn; // shared input, bits {2,4,5,7} + logic [31:0] vw; // has a set bit above the scan bound + logic [31:0] vt; // for the truncated-index case + logic en1; // runtime gate for the compound-condition case + logic [5:0] e_step2; + logic [6:0] e_start1; + logic [6:0] e_mul; + logic [5:0] e_off; + logic [5:0] e_noP1; + logic [5:0] e_narrow; + logic [5:0] e_comp; + logic [5:0] e_trunc; + always_comb begin + e_step2 = 0; + for (int b = 0; b < 32; b += 2) if (vn[b]) e_step2 = 6'(b + 1); + end + always_comb begin + e_start1 = 0; + for (int b = 1; b < 32; b++) if (vn[b]) e_start1 = 7'(b + 1); + end + always_comb begin + e_mul = 0; + for (int b = 0; b < 32; b++) if (vn[b]) e_mul = 7'(2 * b + 1); + end + always_comb begin + e_off = 0; + for (int b = 0; b < 31; b++) if (vn[b+1]) e_off = 6'(b + 1); + end + always_comb begin + e_noP1 = 0; + for (int b = 0; b < 32; b++) if (vn[b]) e_noP1 = 6'(b); + end + always_comb begin + e_narrow = 0; + for (int b = 0; b < 16; b++) if (vw[b]) e_narrow = 6'(b + 1); + end + always_comb begin + e_comp = 0; + for (int b = 0; b < 32; b++) if (vn[b] && en1) e_comp = 6'(b + 1); + end + // verilator lint_off WIDTHEXPAND + always_comb begin + e_trunc = 0; + for (int b = 0; b < 32; b++) if (vt[b[2:0]]) e_trunc = 6'(b + 1); + end + // verilator lint_on WIDTHEXPAND + + int cyc = 0; + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 0) begin + p32 <= 32'h8000_0000; + p48 <= 48'h0; + p48[47] <= 1'b1; + p80 <= 80'h0; + p80[79] <= 1'b1; + pu <= 32'h0001_0000; // bit 16 + p32e <= 32'h8000_0000; + p32w <= 32'h8000_0000; + pc <= 32'hf0f0_f0f0; // 16 ones + vn <= 32'h0000_00b4; // bits {2,4,5,7} + vw <= 32'h0010_0008; // bits {3,20} + vt <= 32'h0000_0080; // bit 7 + en1 <= 1'b0; // gate off -> compound loop yields 0 + end + else if (cyc == 1) begin + `checkh(n32, 6'd32); + `checkh(n48, 7'd48); + `checkh(n80, 7'd80); + `checkh(nu, 6'd17); // unsigned-index leading-one, bit 16 -> 17 + `checkh(n32e, 32'd32); + `checkh(n40, 40'd32); + `checkh(nc, 6'd16); // popcount(0xF0F0F0F0) + `checkh(kn, 6'd9); // mostsetbitp1(0x100), constant-folded + `checkh(kn0, 6'd0); // mostsetbitp1(0)=0, constant-folded (zero path) + // negatives, hand-computed for vn = 0xB4 (bits 2,4,5,7): + `checkh(e_step2, 6'd5); // highest even set bit (4) + 1 + `checkh(e_start1, 7'd8); // highest set bit in [1,32) (7) + 1 + `checkh(e_mul, 7'd15); // 2*7 + 1 + `checkh(e_off, 6'd7); // idx where vec[idx+1]; highest 6 -> 7 + `checkh(e_noP1, 6'd7); // highest set bit (7), no +1 + `checkh(e_narrow, 6'd4); // W=16 != width(vec): only low bits scanned (bit 3) + `checkh(e_comp, 6'd0); // && en1 (=0); a wrong lowering would give 8 + `checkh(e_trunc, 6'd32); // vt[b[2:0]] last hits b=31; a wrong lowering would give 8 + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_bit_scan_loops_off.py b/test_regress/t/t_bit_scan_loops_off.py new file mode 100755 index 000000000..cdf34ea55 --- /dev/null +++ b/test_regress/t/t_bit_scan_loops_off.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# Reuse the same design; only the optimization switch differs. +test.top_filename = "t/t_bit_scan_loops.v" + +test.compile(verilator_flags2=['--stats', '--unroll-count', '0', '-fno-bit-scan-loops']) + +# With the optimization disabled, nothing lowers. +test.file_grep(test.stats, r'Lowered priority-encoder to mostsetbitp1\s+([0-9])', 0) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_bit_scan_loops_xassign0.py b/test_regress/t/t_bit_scan_loops_xassign0.py new file mode 100755 index 000000000..b44c2cdf7 --- /dev/null +++ b/test_regress/t/t_bit_scan_loops_xassign0.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# Reuse the same design. '--x-assign 0' makes the auto-inserted out-of-range guard on a +# non-power-of-two bit-select a plain '(idx <= W-1) && vec[idx]' (AstLogAnd), rather than +# the ternary '(idx <= W-1) ? vec[idx] : ' (AstCond) produced under the driver's default +# '--x-assign unique'. This exercises the matcher's other guard-peel branch. +test.top_filename = "t/t_bit_scan_loops.v" + +test.compile(verilator_flags2=['--stats', '--unroll-count', '0', '--x-assign', '0']) + +# Same lowering counts as the default run -- only the guard shape differs, not the result. +test.file_grep(test.stats, + r'Optimizations, Loop unrolling, Lowered priority-encoder to mostsetbitp1\s+(\d+)', + 8) +test.file_grep(test.stats, + r'Optimizations, Loop unrolling, Lowered count-set-bits to countones\s+(\d+)', 1) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_case_call_count.py b/test_regress/t/t_case_call_count.py index 4116217e8..bf14a54dd 100755 --- a/test_regress/t/t_case_call_count.py +++ b/test_regress/t/t_case_call_count.py @@ -15,6 +15,7 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 2) +test.file_grep(test.stats, r'Assertions, lifted impure case expressions\s+(\d+)', 2) test.passes() diff --git a/test_regress/t/t_case_decoder.py b/test_regress/t/t_case_decoder.py new file mode 100755 index 000000000..4265c138b --- /dev/null +++ b/test_regress/t/t_case_decoder.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases decoder\s+(\d+)', 17) + +test.passes() diff --git a/test_regress/t/t_case_decoder.v b/test_regress/t/t_case_decoder.v new file mode 100644 index 000000000..495c19320 --- /dev/null +++ b/test_regress/t/t_case_decoder.v @@ -0,0 +1,544 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// Case statements that become a "decoder" (the selector is matched against a packed constant +// table at runtime), followed by cases that must not be converted to one. Each output is +// compared against an equivalent reference computed without a case statement, so the reference +// itself is never converted. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Accept A: a 31-bit (I) selector decoded into outputs of three widths (I/Q/W result). + wire [30:0] accept_a_in = 31'b1 << cyc[3:0]; + logic [5:0] accept_a_out_0, accept_a_ref_0; + logic [55:0] accept_a_out_1, accept_a_ref_1; + logic [142:0] accept_a_out_2, accept_a_ref_2; + always_comb begin + casez (accept_a_in) + 31'b???????_????????_????????_???????1: accept_a_out_0 = 6'd00; + 31'b???????_????????_????????_??????1?: accept_a_out_0 = 6'd01; + 31'b???????_????????_????????_?????1??: accept_a_out_0 = 6'd02; + 31'b???????_????????_????????_????1???: accept_a_out_0 = 6'd03; + 31'b???????_????????_????????_???1????: accept_a_out_0 = 6'd04; + 31'b???????_????????_????????_??1?????: accept_a_out_0 = 6'd05; + 31'b???????_????????_????????_?1??????: accept_a_out_0 = 6'd06; + 31'b???????_????????_????????_1???????: accept_a_out_0 = 6'd07; + 31'b???????_????????_???????1_????????: accept_a_out_0 = 6'd08; + 31'b???????_????????_??????1?_????????: accept_a_out_0 = 6'd09; + 31'b???????_????????_?????1??_????????: accept_a_out_0 = 6'd10; + 31'b???????_????????_????1???_????????: accept_a_out_0 = 6'd11; + 31'b???????_????????_???1????_????????: accept_a_out_0 = 6'd12; + 31'b???????_????????_??1?????_????????: accept_a_out_0 = 6'd13; + 31'b???????_????????_?1??????_????????: accept_a_out_0 = 6'd14; + 31'b???????_????????_1???????_????????: accept_a_out_0 = 6'd15; + 31'b???????_???????1_????????_????????: accept_a_out_0 = 6'd16; + 31'b???????_??????1?_????????_????????: accept_a_out_0 = 6'd17; + 31'b???????_?????1??_????????_????????: accept_a_out_0 = 6'd18; + 31'b???????_????1???_????????_????????: accept_a_out_0 = 6'd19; + 31'b???????_???1????_????????_????????: accept_a_out_0 = 6'd20; + 31'b???????_??1?????_????????_????????: accept_a_out_0 = 6'd21; + 31'b???????_?1??????_????????_????????: accept_a_out_0 = 6'd22; + 31'b???????_1???????_????????_????????: accept_a_out_0 = 6'd23; + 31'b??????1_????????_????????_????????: accept_a_out_0 = 6'd24; + 31'b?????1?_????????_????????_????????: accept_a_out_0 = 6'd25; + 31'b????1??_????????_????????_????????: accept_a_out_0 = 6'd26; + 31'b???1???_????????_????????_????????: accept_a_out_0 = 6'd27; + 31'b??1????_????????_????????_????????: accept_a_out_0 = 6'd28; + 31'b?1?????_????????_????????_????????: accept_a_out_0 = 6'd29; + 31'b1??????_????????_????????_????????: accept_a_out_0 = 6'd30; + default: accept_a_out_0 = '1; + endcase + casez (accept_a_in) + 31'b???????_????????_????????_???????1: accept_a_out_1 = 56'd0000; + 31'b???????_????????_????????_??????1?: accept_a_out_1 = 56'd0100; + 31'b???????_????????_????????_?????1??: accept_a_out_1 = 56'd0200; + 31'b???????_????????_????????_????1???: accept_a_out_1 = 56'd0300; + 31'b???????_????????_????????_???1????: accept_a_out_1 = 56'd0400; + 31'b???????_????????_????????_??1?????: accept_a_out_1 = 56'd0500; + 31'b???????_????????_????????_?1??????: accept_a_out_1 = 56'd0600; + 31'b???????_????????_????????_1???????: accept_a_out_1 = 56'd0700; + 31'b???????_????????_???????1_????????: accept_a_out_1 = 56'd0800; + 31'b???????_????????_??????1?_????????: accept_a_out_1 = 56'd0900; + 31'b???????_????????_?????1??_????????: accept_a_out_1 = 56'd1000; + 31'b???????_????????_????1???_????????: accept_a_out_1 = 56'd1100; + 31'b???????_????????_???1????_????????: accept_a_out_1 = 56'd1200; + 31'b???????_????????_??1?????_????????: accept_a_out_1 = 56'd1300; + 31'b???????_????????_?1??????_????????: accept_a_out_1 = 56'd1400; + 31'b???????_????????_1???????_????????: accept_a_out_1 = 56'd1500; + 31'b???????_???????1_????????_????????: accept_a_out_1 = 56'd1600; + 31'b???????_??????1?_????????_????????: accept_a_out_1 = 56'd1700; + 31'b???????_?????1??_????????_????????: accept_a_out_1 = 56'd1800; + 31'b???????_????1???_????????_????????: accept_a_out_1 = 56'd1900; + 31'b???????_???1????_????????_????????: accept_a_out_1 = 56'd2000; + 31'b???????_??1?????_????????_????????: accept_a_out_1 = 56'd2100; + 31'b???????_?1??????_????????_????????: accept_a_out_1 = 56'd2200; + 31'b???????_1???????_????????_????????: accept_a_out_1 = 56'd2300; + 31'b??????1_????????_????????_????????: accept_a_out_1 = 56'd2400; + 31'b?????1?_????????_????????_????????: accept_a_out_1 = 56'd2500; + 31'b????1??_????????_????????_????????: accept_a_out_1 = 56'd2600; + 31'b???1???_????????_????????_????????: accept_a_out_1 = 56'd2700; + 31'b??1????_????????_????????_????????: accept_a_out_1 = 56'd2800; + 31'b?1?????_????????_????????_????????: accept_a_out_1 = 56'd2900; + 31'b1??????_????????_????????_????????: accept_a_out_1 = 56'd3000; + default: accept_a_out_1 = '1; + endcase + casez (accept_a_in) + 31'b???????_????????_????????_???????1: accept_a_out_2 = 143'd0000000000; + 31'b???????_????????_????????_??????1?: accept_a_out_2 = 143'd0100000000; + 31'b???????_????????_????????_?????1??: accept_a_out_2 = 143'd0200000000; + 31'b???????_????????_????????_????1???: accept_a_out_2 = 143'd0300000000; + 31'b???????_????????_????????_???1????: accept_a_out_2 = 143'd0400000000; + 31'b???????_????????_????????_??1?????: accept_a_out_2 = 143'd0500000000; + 31'b???????_????????_????????_?1??????: accept_a_out_2 = 143'd0600000000; + 31'b???????_????????_????????_1???????: accept_a_out_2 = 143'd0700000000; + 31'b???????_????????_???????1_????????: accept_a_out_2 = 143'd0800000000; + 31'b???????_????????_??????1?_????????: accept_a_out_2 = 143'd0900000000; + 31'b???????_????????_?????1??_????????: accept_a_out_2 = 143'd1000000000; + 31'b???????_????????_????1???_????????: accept_a_out_2 = 143'd1100000000; + 31'b???????_????????_???1????_????????: accept_a_out_2 = 143'd1200000000; + 31'b???????_????????_??1?????_????????: accept_a_out_2 = 143'd1300000000; + 31'b???????_????????_?1??????_????????: accept_a_out_2 = 143'd1400000000; + 31'b???????_????????_1???????_????????: accept_a_out_2 = 143'd1500000000; + 31'b???????_???????1_????????_????????: accept_a_out_2 = 143'd1600000000; + 31'b???????_??????1?_????????_????????: accept_a_out_2 = 143'd1700000000; + 31'b???????_?????1??_????????_????????: accept_a_out_2 = 143'd1800000000; + 31'b???????_????1???_????????_????????: accept_a_out_2 = 143'd1900000000; + 31'b???????_???1????_????????_????????: accept_a_out_2 = 143'd2000000000; + 31'b???????_??1?????_????????_????????: accept_a_out_2 = 143'd2100000000; + 31'b???????_?1??????_????????_????????: accept_a_out_2 = 143'd2200000000; + 31'b???????_1???????_????????_????????: accept_a_out_2 = 143'd2300000000; + 31'b??????1_????????_????????_????????: accept_a_out_2 = 143'd2400000000; + 31'b?????1?_????????_????????_????????: accept_a_out_2 = 143'd2500000000; + 31'b????1??_????????_????????_????????: accept_a_out_2 = 143'd2600000000; + 31'b???1???_????????_????????_????????: accept_a_out_2 = 143'd2700000000; + 31'b??1????_????????_????????_????????: accept_a_out_2 = 143'd2800000000; + 31'b?1?????_????????_????????_????????: accept_a_out_2 = 143'd2900000000; + 31'b1??????_????????_????????_????????: accept_a_out_2 = 143'd3000000000; + default: accept_a_out_2 = '1; + endcase + end + assign accept_a_ref_0 = 6'(cyc[3:0]); + assign accept_a_ref_1 = 56'(cyc[3:0]) * 56'd100; + assign accept_a_ref_2 = 143'(cyc[3:0]) * 143'd100000000; + + // Accept B: a 40-bit (Q) selector decoded into outputs of three widths (I/Q/W result). + wire [39:0] accept_b_in = 40'b1 << cyc[5:1]; + logic [5:0] accept_b_out_0, accept_b_ref_0; + logic [55:0] accept_b_out_1, accept_b_ref_1; + logic [142:0] accept_b_out_2, accept_b_ref_2; + always_comb begin + casez (accept_b_in) + 40'b????????_????????_????????_????????_???????1: accept_b_out_0 = 6'd00; + 40'b????????_????????_????????_????????_??????1?: accept_b_out_0 = 6'd01; + 40'b????????_????????_????????_????????_?????1??: accept_b_out_0 = 6'd02; + 40'b????????_????????_????????_????????_????1???: accept_b_out_0 = 6'd03; + 40'b????????_????????_????????_????????_???1????: accept_b_out_0 = 6'd04; + 40'b????????_????????_????????_????????_??1?????: accept_b_out_0 = 6'd05; + 40'b????????_????????_????????_????????_?1??????: accept_b_out_0 = 6'd06; + 40'b????????_????????_????????_????????_1???????: accept_b_out_0 = 6'd07; + 40'b????????_????????_????????_???????1_????????: accept_b_out_0 = 6'd08; + 40'b????????_????????_????????_??????1?_????????: accept_b_out_0 = 6'd09; + 40'b????????_????????_????????_?????1??_????????: accept_b_out_0 = 6'd10; + 40'b????????_????????_????????_????1???_????????: accept_b_out_0 = 6'd11; + 40'b????????_????????_????????_???1????_????????: accept_b_out_0 = 6'd12; + 40'b????????_????????_????????_??1?????_????????: accept_b_out_0 = 6'd13; + 40'b????????_????????_????????_?1??????_????????: accept_b_out_0 = 6'd14; + 40'b????????_????????_????????_1???????_????????: accept_b_out_0 = 6'd15; + 40'b????????_????????_???????1_????????_????????: accept_b_out_0 = 6'd16; + 40'b????????_????????_??????1?_????????_????????: accept_b_out_0 = 6'd17; + 40'b????????_????????_?????1??_????????_????????: accept_b_out_0 = 6'd18; + 40'b????????_????????_????1???_????????_????????: accept_b_out_0 = 6'd19; + 40'b????????_????????_???1????_????????_????????: accept_b_out_0 = 6'd20; + 40'b????????_????????_??1?????_????????_????????: accept_b_out_0 = 6'd21; + 40'b????????_????????_?1??????_????????_????????: accept_b_out_0 = 6'd22; + 40'b????????_????????_1???????_????????_????????: accept_b_out_0 = 6'd23; + 40'b????????_???????1_????????_????????_????????: accept_b_out_0 = 6'd24; + 40'b????????_??????1?_????????_????????_????????: accept_b_out_0 = 6'd25; + 40'b????????_?????1??_????????_????????_????????: accept_b_out_0 = 6'd26; + 40'b????????_????1???_????????_????????_????????: accept_b_out_0 = 6'd27; + 40'b????????_???1????_????????_????????_????????: accept_b_out_0 = 6'd28; + 40'b????????_??1?????_????????_????????_????????: accept_b_out_0 = 6'd29; + 40'b????????_?1??????_????????_????????_????????: accept_b_out_0 = 6'd30; + 40'b????????_1???????_????????_????????_????????: accept_b_out_0 = 6'd31; + 40'b???????1_????????_????????_????????_????????: accept_b_out_0 = 6'd32; + 40'b??????1?_????????_????????_????????_????????: accept_b_out_0 = 6'd33; + 40'b?????1??_????????_????????_????????_????????: accept_b_out_0 = 6'd34; + 40'b????1???_????????_????????_????????_????????: accept_b_out_0 = 6'd35; + 40'b???1????_????????_????????_????????_????????: accept_b_out_0 = 6'd36; + 40'b??1?????_????????_????????_????????_????????: accept_b_out_0 = 6'd37; + 40'b?1??????_????????_????????_????????_????????: accept_b_out_0 = 6'd38; + 40'b1???????_????????_????????_????????_????????: accept_b_out_0 = 6'd39; + default: accept_b_out_0 = '1; + endcase + casez (accept_b_in) + 40'b????????_????????_????????_????????_???????1: accept_b_out_1 = 56'd0000; + 40'b????????_????????_????????_????????_??????1?: accept_b_out_1 = 56'd0100; + 40'b????????_????????_????????_????????_?????1??: accept_b_out_1 = 56'd0200; + 40'b????????_????????_????????_????????_????1???: accept_b_out_1 = 56'd0300; + 40'b????????_????????_????????_????????_???1????: accept_b_out_1 = 56'd0400; + 40'b????????_????????_????????_????????_??1?????: accept_b_out_1 = 56'd0500; + 40'b????????_????????_????????_????????_?1??????: accept_b_out_1 = 56'd0600; + 40'b????????_????????_????????_????????_1???????: accept_b_out_1 = 56'd0700; + 40'b????????_????????_????????_???????1_????????: accept_b_out_1 = 56'd0800; + 40'b????????_????????_????????_??????1?_????????: accept_b_out_1 = 56'd0900; + 40'b????????_????????_????????_?????1??_????????: accept_b_out_1 = 56'd1000; + 40'b????????_????????_????????_????1???_????????: accept_b_out_1 = 56'd1100; + 40'b????????_????????_????????_???1????_????????: accept_b_out_1 = 56'd1200; + 40'b????????_????????_????????_??1?????_????????: accept_b_out_1 = 56'd1300; + 40'b????????_????????_????????_?1??????_????????: accept_b_out_1 = 56'd1400; + 40'b????????_????????_????????_1???????_????????: accept_b_out_1 = 56'd1500; + 40'b????????_????????_???????1_????????_????????: accept_b_out_1 = 56'd1600; + 40'b????????_????????_??????1?_????????_????????: accept_b_out_1 = 56'd1700; + 40'b????????_????????_?????1??_????????_????????: accept_b_out_1 = 56'd1800; + 40'b????????_????????_????1???_????????_????????: accept_b_out_1 = 56'd1900; + 40'b????????_????????_???1????_????????_????????: accept_b_out_1 = 56'd2000; + 40'b????????_????????_??1?????_????????_????????: accept_b_out_1 = 56'd2100; + 40'b????????_????????_?1??????_????????_????????: accept_b_out_1 = 56'd2200; + 40'b????????_????????_1???????_????????_????????: accept_b_out_1 = 56'd2300; + 40'b????????_???????1_????????_????????_????????: accept_b_out_1 = 56'd2400; + 40'b????????_??????1?_????????_????????_????????: accept_b_out_1 = 56'd2500; + 40'b????????_?????1??_????????_????????_????????: accept_b_out_1 = 56'd2600; + 40'b????????_????1???_????????_????????_????????: accept_b_out_1 = 56'd2700; + 40'b????????_???1????_????????_????????_????????: accept_b_out_1 = 56'd2800; + 40'b????????_??1?????_????????_????????_????????: accept_b_out_1 = 56'd2900; + 40'b????????_?1??????_????????_????????_????????: accept_b_out_1 = 56'd3000; + 40'b????????_1???????_????????_????????_????????: accept_b_out_1 = 56'd3100; + 40'b???????1_????????_????????_????????_????????: accept_b_out_1 = 56'd3200; + 40'b??????1?_????????_????????_????????_????????: accept_b_out_1 = 56'd3300; + 40'b?????1??_????????_????????_????????_????????: accept_b_out_1 = 56'd3400; + 40'b????1???_????????_????????_????????_????????: accept_b_out_1 = 56'd3500; + 40'b???1????_????????_????????_????????_????????: accept_b_out_1 = 56'd3600; + 40'b??1?????_????????_????????_????????_????????: accept_b_out_1 = 56'd3700; + 40'b?1??????_????????_????????_????????_????????: accept_b_out_1 = 56'd3800; + 40'b1???????_????????_????????_????????_????????: accept_b_out_1 = 56'd3900; + default: accept_b_out_1 = '1; + endcase + casez (accept_b_in) + 40'b????????_????????_????????_????????_???????1: accept_b_out_2 = 143'd0000000000; + 40'b????????_????????_????????_????????_??????1?: accept_b_out_2 = 143'd0100000000; + 40'b????????_????????_????????_????????_?????1??: accept_b_out_2 = 143'd0200000000; + 40'b????????_????????_????????_????????_????1???: accept_b_out_2 = 143'd0300000000; + 40'b????????_????????_????????_????????_???1????: accept_b_out_2 = 143'd0400000000; + 40'b????????_????????_????????_????????_??1?????: accept_b_out_2 = 143'd0500000000; + 40'b????????_????????_????????_????????_?1??????: accept_b_out_2 = 143'd0600000000; + 40'b????????_????????_????????_????????_1???????: accept_b_out_2 = 143'd0700000000; + 40'b????????_????????_????????_???????1_????????: accept_b_out_2 = 143'd0800000000; + 40'b????????_????????_????????_??????1?_????????: accept_b_out_2 = 143'd0900000000; + 40'b????????_????????_????????_?????1??_????????: accept_b_out_2 = 143'd1000000000; + 40'b????????_????????_????????_????1???_????????: accept_b_out_2 = 143'd1100000000; + 40'b????????_????????_????????_???1????_????????: accept_b_out_2 = 143'd1200000000; + 40'b????????_????????_????????_??1?????_????????: accept_b_out_2 = 143'd1300000000; + 40'b????????_????????_????????_?1??????_????????: accept_b_out_2 = 143'd1400000000; + 40'b????????_????????_????????_1???????_????????: accept_b_out_2 = 143'd1500000000; + 40'b????????_????????_???????1_????????_????????: accept_b_out_2 = 143'd1600000000; + 40'b????????_????????_??????1?_????????_????????: accept_b_out_2 = 143'd1700000000; + 40'b????????_????????_?????1??_????????_????????: accept_b_out_2 = 143'd1800000000; + 40'b????????_????????_????1???_????????_????????: accept_b_out_2 = 143'd1900000000; + 40'b????????_????????_???1????_????????_????????: accept_b_out_2 = 143'd2000000000; + 40'b????????_????????_??1?????_????????_????????: accept_b_out_2 = 143'd2100000000; + 40'b????????_????????_?1??????_????????_????????: accept_b_out_2 = 143'd2200000000; + 40'b????????_????????_1???????_????????_????????: accept_b_out_2 = 143'd2300000000; + 40'b????????_???????1_????????_????????_????????: accept_b_out_2 = 143'd2400000000; + 40'b????????_??????1?_????????_????????_????????: accept_b_out_2 = 143'd2500000000; + 40'b????????_?????1??_????????_????????_????????: accept_b_out_2 = 143'd2600000000; + 40'b????????_????1???_????????_????????_????????: accept_b_out_2 = 143'd2700000000; + 40'b????????_???1????_????????_????????_????????: accept_b_out_2 = 143'd2800000000; + 40'b????????_??1?????_????????_????????_????????: accept_b_out_2 = 143'd2900000000; + 40'b????????_?1??????_????????_????????_????????: accept_b_out_2 = 143'd3000000000; + 40'b????????_1???????_????????_????????_????????: accept_b_out_2 = 143'd3100000000; + 40'b???????1_????????_????????_????????_????????: accept_b_out_2 = 143'd3200000000; + 40'b??????1?_????????_????????_????????_????????: accept_b_out_2 = 143'd3300000000; + 40'b?????1??_????????_????????_????????_????????: accept_b_out_2 = 143'd3400000000; + 40'b????1???_????????_????????_????????_????????: accept_b_out_2 = 143'd3500000000; + 40'b???1????_????????_????????_????????_????????: accept_b_out_2 = 143'd3600000000; + 40'b??1?????_????????_????????_????????_????????: accept_b_out_2 = 143'd3700000000; + 40'b?1??????_????????_????????_????????_????????: accept_b_out_2 = 143'd3800000000; + 40'b1???????_????????_????????_????????_????????: accept_b_out_2 = 143'd3900000000; + default: accept_b_out_2 = '1; + endcase + end + assign accept_b_ref_0 = 6'(cyc[5:1]); + assign accept_b_ref_1 = 56'(cyc[5:1]) * 56'd100; + assign accept_b_ref_2 = 143'(cyc[5:1]) * 143'd100000000; + + // Accept C: a 155-bit (W) selector decoded into outputs of three widths (I/Q/W result). + wire [154:0] accept_c_in = 155'b1 << cyc[5:0]; + logic [5:0] accept_c_out_0, accept_c_ref_0; + logic [55:0] accept_c_out_1, accept_c_ref_1; + logic [142:0] accept_c_out_2, accept_c_ref_2; + always_comb begin + casez (accept_c_in) + 155'b????????_????????_????????_????????_???????1: accept_c_out_0 = 6'd00; + 155'b????????_????????_????????_????????_??????1?: accept_c_out_0 = 6'd01; + 155'b????????_????????_????????_????????_?????1??: accept_c_out_0 = 6'd02; + 155'b????????_????????_????????_????????_????1???: accept_c_out_0 = 6'd03; + 155'b????????_????????_????????_????????_???1????: accept_c_out_0 = 6'd04; + 155'b????????_????????_????????_????????_??1?????: accept_c_out_0 = 6'd05; + 155'b????????_????????_????????_????????_?1??????: accept_c_out_0 = 6'd06; + 155'b????????_????????_????????_????????_1???????: accept_c_out_0 = 6'd07; + 155'b????????_????????_????????_???????1_????????: accept_c_out_0 = 6'd08; + 155'b????????_????????_????????_??????1?_????????: accept_c_out_0 = 6'd09; + 155'b????????_????????_????????_?????1??_????????: accept_c_out_0 = 6'd10; + default: accept_c_out_0 = '1; + endcase + casez (accept_c_in) + 155'b????????_????????_????????_????????_???????1: accept_c_out_1 = 56'd0000; + 155'b????????_????????_????????_????????_??????1?: accept_c_out_1 = 56'd0100; + 155'b????????_????????_????????_????????_?????1??: accept_c_out_1 = 56'd0200; + 155'b????????_????????_????????_????????_????1???: accept_c_out_1 = 56'd0300; + 155'b????????_????????_????????_????????_???1????: accept_c_out_1 = 56'd0400; + 155'b????????_????????_????????_????????_??1?????: accept_c_out_1 = 56'd0500; + 155'b????????_????????_????????_????????_?1??????: accept_c_out_1 = 56'd0600; + 155'b????????_????????_????????_????????_1???????: accept_c_out_1 = 56'd0700; + 155'b????????_????????_????????_???????1_????????: accept_c_out_1 = 56'd0800; + 155'b????????_????????_????????_??????1?_????????: accept_c_out_1 = 56'd0900; + 155'b????????_????????_????????_?????1??_????????: accept_c_out_1 = 56'd1000; + default: accept_c_out_1 = '1; + endcase + casez (accept_c_in) + 155'b????????_????????_????????_????????_???????1: accept_c_out_2 = 143'd0000000000; + 155'b????????_????????_????????_????????_??????1?: accept_c_out_2 = 143'd0100000000; + 155'b????????_????????_????????_????????_?????1??: accept_c_out_2 = 143'd0200000000; + 155'b????????_????????_????????_????????_????1???: accept_c_out_2 = 143'd0300000000; + 155'b????????_????????_????????_????????_???1????: accept_c_out_2 = 143'd0400000000; + 155'b????????_????????_????????_????????_??1?????: accept_c_out_2 = 143'd0500000000; + 155'b????????_????????_????????_????????_?1??????: accept_c_out_2 = 143'd0600000000; + 155'b????????_????????_????????_????????_1???????: accept_c_out_2 = 143'd0700000000; + 155'b????????_????????_????????_???????1_????????: accept_c_out_2 = 143'd0800000000; + 155'b????????_????????_????????_??????1?_????????: accept_c_out_2 = 143'd0900000000; + 155'b????????_????????_????????_?????1??_????????: accept_c_out_2 = 143'd1000000000; + default: accept_c_out_2 = '1; + endcase + end + assign accept_c_ref_0 = cyc[5:0] <= 6'd10 ? 6'(cyc[5:0]) : ~6'd0; + assign accept_c_ref_1 = cyc[5:0] <= 6'd10 ? 56'(cyc[5:0]) * 56'd100 : ~56'd0; + assign accept_c_ref_2 = cyc[5:0] <= 6'd10 ? 143'(cyc[5:0]) * 143'd100000000 : ~143'd0; + + // Accept D: the default value is set before the case, with no default item. + wire [39:0] accept_d_in = 40'b1 << cyc[4:0]; + logic [4:0] accept_d_out, accept_d_ref; + always_comb begin + accept_d_out = '1; + casez (accept_d_in) + 40'b????????_????????_????????_????????_???????1: accept_d_out = 5'd0; + 40'b????????_????????_????????_????????_??????1?: accept_d_out = 5'd1; + 40'b????????_????????_????????_????????_?????1??: accept_d_out = 5'd2; + 40'b????????_????????_????????_????????_????1???: accept_d_out = 5'd3; + endcase + end + assign accept_d_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; + + // Accept E: an empty default item. + wire [39:0] accept_e_in = 40'b1 << cyc[4:0]; + logic [4:0] accept_e_out, accept_e_ref; + always_comb begin + accept_e_out = '1; + casez (accept_e_in) + 40'b????????_????????_????????_????????_???????1: accept_e_out = 5'd0; + 40'b????????_????????_????????_????????_??????1?: accept_e_out = 5'd1; + 40'b????????_????????_????????_????????_?????1??: accept_e_out = 5'd2; + 40'b????????_????????_????????_????????_????1???: accept_e_out = 5'd3; + default: begin + end + endcase + end + assign accept_e_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; + + // Accept F: an item with an empty body (output keeps its pre-case default). + wire [39:0] accept_f_in = 40'b1 << cyc[4:0]; + logic [4:0] accept_f_out, accept_f_ref; + always_comb begin + accept_f_out = '1; + casez (accept_f_in) + 40'b????????_????????_????????_????????_???1????: begin + end + 40'b????????_????????_????????_????????_???????1: accept_f_out = 5'd0; + 40'b????????_????????_????????_????????_??????1?: accept_f_out = 5'd1; + 40'b????????_????????_????????_????????_?????1??: accept_f_out = 5'd2; + 40'b????????_????????_????????_????????_????1???: accept_f_out = 5'd3; + default: begin + end + endcase + end + assign accept_f_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; + + // Accept G: non-blocking assignments. + wire [23:0] accept_g_in = 24'b1 << cyc[4:0]; + logic [5:0] accept_g_out, accept_g_ref; + always_ff @(posedge clk) begin + accept_g_out <= '1; + casez (accept_g_in) + 24'b????????_????????_???????1: accept_g_out <= 6'd0; + 24'b????????_????????_??????1?: accept_g_out <= 6'd1; + 24'b????????_????????_?????1??: accept_g_out <= 6'd2; + 24'b????????_????????_????1???: accept_g_out <= 6'd3; + endcase + end + always_ff @(posedge clk) + accept_g_ref <= cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 + : cyc[4:0] == 5'd2 ? 6'd2 : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + + // Accept H: multiple outputs from one decoder, some items assigning only a subset. + wire [23:0] accept_h_in = 24'b1 << cyc[4:0]; + logic [5:0] accept_h_out_0, accept_h_ref_0; + logic [11:0] accept_h_out_1, accept_h_ref_1; + always_comb begin + accept_h_out_0 = '1; + accept_h_out_1 = '1; + casez (accept_h_in) + 24'b????????_????????_???????1: begin + accept_h_out_0 = 6'd0; + accept_h_out_1 = 12'h001; + end + 24'b????????_????????_??????1?: accept_h_out_0 = 6'd1; + 24'b????????_????????_?????1??: accept_h_out_1 = 12'h004; + 24'b????????_????????_????1???: begin + accept_h_out_0 = 6'd3; + accept_h_out_1 = 12'h008; + end + endcase + end + assign accept_h_ref_0 = cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 + : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + assign accept_h_ref_1 = cyc[4:0] == 5'd0 ? 12'h001 : cyc[4:0] == 5'd2 ? 12'h004 + : cyc[4:0] == 5'd3 ? 12'h008 : ~12'd0; + + // Accept I: multiple outputs from one decoder, non-blocking assignments. + wire [23:0] accept_i_in = 24'b1 << cyc[4:0]; + logic [5:0] accept_i_out_0, accept_i_ref_0; + logic [11:0] accept_i_out_1, accept_i_ref_1; + always_ff @(posedge clk) begin + accept_i_out_0 <= '1; + accept_i_out_1 <= '1; + casez (accept_i_in) + 24'b????????_????????_???????1: begin + accept_i_out_0 <= 6'd0; + accept_i_out_1 <= 12'h001; + end + 24'b????????_????????_??????1?: begin + accept_i_out_0 <= 6'd1; + accept_i_out_1 <= 12'h002; + end + 24'b????????_????????_?????1??: begin + accept_i_out_0 <= 6'd2; + accept_i_out_1 <= 12'h004; + end + 24'b????????_????????_????1???: begin + accept_i_out_0 <= 6'd3; + accept_i_out_1 <= 12'h008; + end + endcase + end + always_ff @(posedge clk) begin + accept_i_ref_0 <= cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 + : cyc[4:0] == 5'd2 ? 6'd2 : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + accept_i_ref_1 <= cyc[4:0] == 5'd0 ? 12'h001 : cyc[4:0] == 5'd1 ? 12'h002 + : cyc[4:0] == 5'd2 ? 12'h004 : cyc[4:0] == 5'd3 ? 12'h008 : ~12'd0; + end + + // Accept J: an item that can never match in 2-state (an X in casez) is skipped. + // verilator lint_off CASEWITHX + wire [23:0] accept_j_in = 24'b1 << cyc[4:0]; + logic [5:0] accept_j_out, accept_j_ref; + always_comb begin + accept_j_out = '1; + casez (accept_j_in) + 24'b????????_????????_???????x: accept_j_out = 6'd9; // X never matches in 2-state, skipped + 24'b????????_????????_??????1?: accept_j_out = 6'd1; + 24'b????????_????????_?????1??: accept_j_out = 6'd2; + 24'b????????_????????_????1???: accept_j_out = 6'd3; + endcase + end + // verilator lint_on CASEWITHX + assign accept_j_ref = cyc[4:0] == 5'd1 ? 6'd1 : cyc[4:0] == 5'd2 ? 6'd2 + : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + + // Accept K: Will constant fold after converting to decoder + wire [23:0] accept_k_in; + logic [5:0] accept_k_out, accept_k_ref; + always_ff @(posedge clk) begin + accept_k_out <= '1; + casez (accept_k_in) + 24'b????????_????????_???????1: accept_k_out <= 6'd0; + 24'b????????_????????_??????1?: accept_k_out <= 6'd1; + 24'b????????_????????_?????1??: accept_k_out <= 6'd2; + 24'b????????_????????_????1???: accept_k_out <= 6'd3; + endcase + end + always_ff @(posedge clk) accept_k_ref <= 6'd2; + assign accept_k_in = 24'b100; + + // The cases below are intentionally NOT converted to a decoder. + + // Reject A: too few conditions to be worth the indexed load. + wire [23:0] reject_a_in = 24'b1 << cyc[4:0]; + logic [5:0] reject_a_out, reject_a_ref; + always_comb begin + reject_a_out = '1; + casez (reject_a_in) + 24'b????????_????????_???????1: reject_a_out = 6'd0; + 24'b????????_????????_??????1?: reject_a_out = 6'd1; + endcase + end + assign reject_a_ref = cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 : ~6'd0; + + // Reject B: every condition contains an X, so none can ever match in 2-state. The case has no + // matchable branches, so it must not be treated as a decoder - it just keeps the + // pre-case default. + // verilator lint_off CASEWITHX + wire [23:0] reject_b_in = 24'b1 << cyc[4:0]; + logic [5:0] reject_b_out, reject_b_ref; + always_comb begin + reject_b_out = 6'h2a; + casez (reject_b_in) + 24'b????????_????????_???????x: reject_b_out = 6'd0; + 24'b????????_????????_??????x?: reject_b_out = 6'd1; + 24'b????????_????????_?????x??: reject_b_out = 6'd2; + endcase + end + // verilator lint_on CASEWITHX + assign reject_b_ref = 6'h2a; // No condition can ever match, so always the pre-default + + // Test driver/checker + always @(posedge clk) begin + `checkh(accept_a_out_0, accept_a_ref_0); + `checkh(accept_a_out_1, accept_a_ref_1); + `checkh(accept_a_out_2, accept_a_ref_2); + `checkh(accept_b_out_0, accept_b_ref_0); + `checkh(accept_b_out_1, accept_b_ref_1); + `checkh(accept_b_out_2, accept_b_ref_2); + `checkh(accept_c_out_0, accept_c_ref_0); + `checkh(accept_c_out_1, accept_c_ref_1); + `checkh(accept_c_out_2, accept_c_ref_2); + `checkh(accept_d_out, accept_d_ref); + `checkh(accept_e_out, accept_e_ref); + `checkh(accept_f_out, accept_f_ref); + `checkh(accept_g_out, accept_g_ref); + `checkh(accept_h_out_0, accept_h_ref_0); + `checkh(accept_h_out_1, accept_h_ref_1); + `checkh(accept_i_out_0, accept_i_ref_0); + `checkh(accept_i_out_1, accept_i_ref_1); + `checkh(accept_j_out, accept_j_ref); + `checkh(accept_k_out, accept_k_ref); + `checkh(reject_a_out, reject_a_ref); + `checkh(reject_b_out, reject_b_ref); + + cyc <= cyc + 32'd1; + if (cyc[16]) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_decoder_off.py b/test_regress/t/t_case_decoder_off.py new file mode 100755 index 000000000..abc61d879 --- /dev/null +++ b/test_regress/t/t_case_decoder_off.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_case_decoder.v" + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-case-decoder']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases decoder\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_enum_incomplete_bad.out b/test_regress/t/t_case_enum_incomplete_bad.out index 6fa08e122..6f3986c1d 100644 --- a/test_regress/t/t_case_enum_incomplete_bad.out +++ b/test_regress/t/t_case_enum_incomplete_bad.out @@ -1,4 +1,4 @@ -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_bad.v:18:12: Enum item 'S1' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_bad.v:18:12: Enum item(s) not covered by case: 'S1', 'S2' 18 | unique case (state) | ^~~~ ... For warning description see https://verilator.org/warn/CASEINCOMPLETE?v=latest diff --git a/test_regress/t/t_case_enum_incomplete_bad.v b/test_regress/t/t_case_enum_incomplete_bad.v index d5d4dabab..6f5dfd6c0 100644 --- a/test_regress/t/t_case_enum_incomplete_bad.v +++ b/test_regress/t/t_case_enum_incomplete_bad.v @@ -17,7 +17,6 @@ module t; unique case (state) S0: $stop; - S2: $stop; endcase end endmodule diff --git a/test_regress/t/t_case_enum_incomplete_wildcard_bad.out b/test_regress/t/t_case_enum_incomplete_wildcard_bad.out index 289933112..59e2f5b81 100644 --- a/test_regress/t/t_case_enum_incomplete_wildcard_bad.out +++ b/test_regress/t/t_case_enum_incomplete_wildcard_bad.out @@ -1,12 +1,12 @@ -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:26:12: Enum item 'S10' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:26:12: Enum item(s) not covered by case: 'S10', 'SX0' 26 | unique case (state) | ^~~~ ... For warning description see https://verilator.org/warn/CASEINCOMPLETE?v=latest ... Use "/* verilator lint_off CASEINCOMPLETE */" and lint_on around source to disable this message. -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:30:12: Enum item 'S00' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:30:12: Enum item(s) not covered by case: 'S00', 'SX0', 'S0X' 30 | unique case (state) | ^~~~ -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:35:12: Enum item 'S10' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:35:12: Enum item(s) not covered by case: 'S10', 'SX0' 35 | unique casez (state) | ^~~~~ %Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:40:5: Case values incompletely covered (example pattern 0x3) diff --git a/test_regress/t/t_case_huge.py b/test_regress/t/t_case_huge.py index 0ac31a2f8..57891d35b 100755 --- a/test_regress/t/t_case_huge.py +++ b/test_regress/t/t_case_huge.py @@ -16,12 +16,10 @@ test.compile(verilator_flags2=["--stats", "-fno-dfg"]) test.execute() -if test.vlt: - test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 11) - test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)', 8) - test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 10) -elif test.vltmt: - test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)', 9) - test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 10) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 8) +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 3) +test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)', 9 if test.vltmt else 8) test.passes() diff --git a/test_regress/t/t_case_huge_nocase.py b/test_regress/t/t_case_huge_nocase.py index 2b3aad742..011bbdc67 100755 --- a/test_regress/t/t_case_huge_nocase.py +++ b/test_regress/t/t_case_huge_nocase.py @@ -16,6 +16,8 @@ test.compile(verilator_flags2=["--stats -fno-case"]) test.execute() +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 0) test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_case_huge_nocase_tree.py b/test_regress/t/t_case_huge_nocase_tree.py new file mode 100755 index 000000000..2083db119 --- /dev/null +++ b/test_regress/t/t_case_huge_nocase_tree.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator_st') +test.top_filename = 't/t_case_huge.v' + +test.compile(verilator_flags2=["--stats -fno-case-tree"]) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 8) +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_inside_call_count.py b/test_regress/t/t_case_inside_call_count.py index 4116217e8..bf14a54dd 100755 --- a/test_regress/t/t_case_inside_call_count.py +++ b/test_regress/t/t_case_inside_call_count.py @@ -15,6 +15,7 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 2) +test.file_grep(test.stats, r'Assertions, lifted impure case expressions\s+(\d+)', 2) test.passes() diff --git a/test_regress/t/t_case_inside_with_x.py b/test_regress/t/t_case_inside_with_x.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_inside_with_x.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_case_inside_with_x.v b/test_regress/t/t_case_inside_with_x.v new file mode 100644 index 000000000..935d779e8 --- /dev/null +++ b/test_regress/t/t_case_inside_with_x.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module top; + bit clk = 1'b0; + always #1 clk = ~clk; + + logic [2:0] cyc = 3'd0; + int count = 0; + always @(posedge clk) begin + // verilator lint_off CASEWITHX + case (cyc) inside + 3'b000: begin + $display("case inside 000"); + ++count; + end + 3'b001: begin + $display("case inside 001"); + ++count; + end + // Should match z + 3'b01?: begin + $display("case inside 01?"); + ++count; + end + // Should match x + 3'b1xx: begin + $display("case inside 1xx"); + ++count; + end + endcase + // verilator lint_on CASEWITHX + cyc <= cyc + 3'd1; + if (&cyc) begin + `checkh(count, 8); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_priority_overlap.py b/test_regress/t/t_case_priority_overlap.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_case_priority_overlap.v b/test_regress/t/t_case_priority_overlap.v new file mode 100644 index 000000000..9b7e794d6 --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Regression: a `priority` case item whose later condition (INST_B) is fully +// subsumed by an earlier condition (INST_A) of the *same* item. Overlap within +// a single case item is legal, but this previously crashed V3Case with a null +// pointer dereference: the priority "case item ignored" CASEOVERLAP diagnostic +// dereferenced 'overlappedCondp', which is null when the only covering item is +// the item itself. Must compile cleanly and simulate correctly. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [1:0] in; + logic [1:0] out; + + always_comb begin + priority casez (in) + 2'b1?, // fully subsumes 2'b11 below on the same case clause + 2'b11: + out = 2'b10; + 2'b0?: out = 2'b01; + endcase + end + + initial begin + in = 2'b00; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b01; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b10; + @(posedge clk); + `checkh(out, 2'b10); + + in = 2'b11; + @(posedge clk); + `checkh(out, 2'b10); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_case_table_normal.py b/test_regress/t/t_case_table_normal.py new file mode 100755 index 000000000..25746e901 --- /dev/null +++ b/test_regress/t/t_case_table_normal.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 8) +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_table_normal.v b/test_regress/t/t_case_table_normal.v new file mode 100644 index 000000000..a7b6285f0 --- /dev/null +++ b/test_regress/t/t_case_table_normal.v @@ -0,0 +1,315 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// Case statements that become a "normal" (constant-pool) lookup table, followed by +// cases that must not be converted to one. Each output is compared against an +// equivalent reference computed without a case statement, so the reference itself is +// never tabled. Selectors are wide enough, with enough distinct values, that the +// branch lowering they replace is deep enough to make a table worthwhile. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Accept A: single output, blocking assignment, all selector values covered. + logic [15:0] accept_a_out, accept_a_ref; + always_comb + case (cyc[3:0]) + 4'd0: accept_a_out = 16'h1111; + 4'd1: accept_a_out = 16'h2222; + 4'd2: accept_a_out = 16'h4444; + 4'd3: accept_a_out = 16'h8888; + default: accept_a_out = 16'h0f0f; + endcase + assign accept_a_ref = (cyc[3:0] == 4'd0) ? 16'h1111 + : (cyc[3:0] == 4'd1) ? 16'h2222 + : (cyc[3:0] == 4'd2) ? 16'h4444 + : (cyc[3:0] == 4'd3) ? 16'h8888 : 16'h0f0f; + + // Accept B: single output, non-blocking assignment, with a default value set before + // the case and not all selector values covered. + logic [15:0] accept_b_out, accept_b_ref; + // verilator lint_off CASEINCOMPLETE + always_ff @(posedge clk) begin + accept_b_out <= 16'hffff; + case (cyc[3:0]) + 4'd0: accept_b_out <= 16'h0001; + 4'd1: accept_b_out <= 16'h0002; + 4'd2: accept_b_out <= 16'h0004; + 4'd3: accept_b_out <= 16'h0008; + endcase + end + // verilator lint_on CASEINCOMPLETE + always_ff @(posedge clk) + accept_b_ref <= (cyc[3:0] == 4'd0) ? 16'h0001 + : (cyc[3:0] == 4'd1) ? 16'h0002 + : (cyc[3:0] == 4'd2) ? 16'h0004 + : (cyc[3:0] == 4'd3) ? 16'h0008 : 16'hffff; + + // Accept C: three outputs, blocking assignment, with a default branch. + logic [11:0] accept_c_out_0, accept_c_ref_0; + logic [11:0] accept_c_out_1, accept_c_ref_1; + logic [11:0] accept_c_out_2, accept_c_ref_2; + always_comb + case (cyc[3:0]) + 4'd0: begin + accept_c_out_0 = 12'h001; + accept_c_out_1 = 12'h010; + accept_c_out_2 = 12'h100; + end + 4'd1: begin + accept_c_out_0 = 12'h002; + accept_c_out_1 = 12'h020; + accept_c_out_2 = 12'h200; + end + 4'd2: begin + accept_c_out_0 = 12'h004; + accept_c_out_1 = 12'h040; + accept_c_out_2 = 12'h400; + end + 4'd3: begin + accept_c_out_0 = 12'h008; + accept_c_out_1 = 12'h080; + accept_c_out_2 = 12'h800; + end + default: begin + accept_c_out_0 = 12'h000; + accept_c_out_1 = 12'h0ff; + accept_c_out_2 = 12'hfff; + end + endcase + assign accept_c_ref_0 = (cyc[3:0] == 4'd0) ? 12'h001 : (cyc[3:0] == 4'd1) ? 12'h002 + : (cyc[3:0] == 4'd2) ? 12'h004 : (cyc[3:0] == 4'd3) ? 12'h008 : 12'h000; + assign accept_c_ref_1 = (cyc[3:0] == 4'd0) ? 12'h010 : (cyc[3:0] == 4'd1) ? 12'h020 + : (cyc[3:0] == 4'd2) ? 12'h040 : (cyc[3:0] == 4'd3) ? 12'h080 : 12'h0ff; + assign accept_c_ref_2 = (cyc[3:0] == 4'd0) ? 12'h100 : (cyc[3:0] == 4'd1) ? 12'h200 + : (cyc[3:0] == 4'd2) ? 12'h400 : (cyc[3:0] == 4'd3) ? 12'h800 : 12'hfff; + + // Accept D: two outputs, non-blocking assignment, empty default branch, with default + // values set before the case. + logic [15:0] accept_d_out_0, accept_d_ref_0; + logic [15:0] accept_d_out_1, accept_d_ref_1; + always_ff @(posedge clk) begin + accept_d_out_0 <= 16'h0000; + accept_d_out_1 <= 16'hffff; + case (cyc[3:0]) + 4'd0: begin + accept_d_out_0 <= 16'h0001; + accept_d_out_1 <= 16'h0010; + end + 4'd1: begin + accept_d_out_0 <= 16'h0002; + accept_d_out_1 <= 16'h0020; + end + 4'd2: begin + accept_d_out_0 <= 16'h0004; + accept_d_out_1 <= 16'h0040; + end + 4'd3: begin + accept_d_out_0 <= 16'h0008; + accept_d_out_1 <= 16'h0080; + end + default: begin + end + endcase + end + always_ff @(posedge clk) begin + accept_d_ref_0 <= (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002 + : (cyc[3:0] == 4'd2) ? 16'h0004 : (cyc[3:0] == 4'd3) ? 16'h0008 : 16'h0000; + accept_d_ref_1 <= (cyc[3:0] == 4'd0) ? 16'h0010 : (cyc[3:0] == 4'd1) ? 16'h0020 + : (cyc[3:0] == 4'd2) ? 16'h0040 : (cyc[3:0] == 4'd3) ? 16'h0080 : 16'hffff; + end + + // Accept E: casez with don't-care bits. + logic [15:0] accept_e_out, accept_e_ref; + always_comb + casez (cyc[3:0]) + 4'b00??: accept_e_out = 16'haaaa; + 4'b01??: accept_e_out = 16'hbbbb; + 4'b10??: accept_e_out = 16'hcccc; + 4'b11??: accept_e_out = 16'hdddd; + endcase + assign accept_e_ref = (cyc[3:2] == 2'd0) ? 16'haaaa : (cyc[3:2] == 2'd1) ? 16'hbbbb + : (cyc[3:2] == 2'd2) ? 16'hcccc : 16'hdddd; + + // Accept F: an item that can never match, and an item listing multiple values. + logic [15:0] accept_f_out, accept_f_ref; + // verilator lint_off CASEWITHX + always_comb + casez (cyc[3:0]) + 4'bxxx0: accept_f_out = 16'h0000; // X can never match in 2-state + 4'b0001, 4'b0011, 4'b0101: accept_f_out = 16'h5555; // lists three values + default: accept_f_out = 16'h9999; + endcase + // verilator lint_on CASEWITHX + assign accept_f_ref = (cyc[3:0] == 4'd1 || cyc[3:0] == 4'd3 || cyc[3:0] == 4'd5) + ? 16'h5555 : 16'h9999; + + // Accept G: items assign different subsets of two outputs, with default values (and an + // unrelated output) set before the case. + logic [15:0] accept_g_out_0, accept_g_ref_0; + logic [15:0] accept_g_out_1, accept_g_ref_1; + logic [15:0] accept_g_out_2, accept_g_ref_2; + always_comb begin + accept_g_out_0 = 16'h0000; + accept_g_out_1 = 16'hffff; + accept_g_out_2 = 16'h3333; // not assigned in the case + case (cyc[3:0]) + 4'd0: accept_g_out_0 = 16'h0001; + 4'd1: accept_g_out_1 = 16'h0002; + 4'd2: begin + accept_g_out_0 = 16'h0004; + accept_g_out_1 = 16'h0008; + end + 4'd3: accept_g_out_0 = 16'h0010; + default: ; + endcase + end + assign accept_g_ref_0 = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd2) ? 16'h0004 + : (cyc[3:0] == 4'd3) ? 16'h0010 : 16'h0000; + assign accept_g_ref_1 = (cyc[3:0] == 4'd1) ? 16'h0002 : (cyc[3:0] == 4'd2) ? 16'h0008 : 16'hffff; + assign accept_g_ref_2 = 16'h3333; + + // Accept H: unique0 enum case; the selector may hold an out-of-range value. + typedef enum logic [3:0] { + NE0, + NE1, + NE2, + NE3, + NE4 + } ne_t; + ne_t accept_h_in; + assign accept_h_in = ne_t'(cyc[3:0]); + logic [15:0] accept_h_out, accept_h_ref; + always_comb begin + accept_h_out = 16'hffff; + unique0 case (accept_h_in) + NE0: accept_h_out = 16'h0001; + NE1: accept_h_out = 16'h0002; + NE2: accept_h_out = 16'h0003; + NE3: accept_h_out = 16'h0004; + NE4: accept_h_out = 16'h0005; + endcase + end + assign accept_h_ref = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002 + : (cyc[3:0] == 4'd2) ? 16'h0003 : (cyc[3:0] == 4'd3) ? 16'h0004 + : (cyc[3:0] == 4'd4) ? 16'h0005 : 16'hffff; + + // The cases below are intentionally NOT converted to a lookup table. + + // Reject A: too few distinct values, so the branch lowering is cheaper than a load. + logic [15:0] reject_a_out, reject_a_ref; + always_comb + case (cyc[3:0]) + 4'd0: reject_a_out = 16'h0001; + 4'd1: reject_a_out = 16'h0002; + default: reject_a_out = 16'h00ff; + endcase + assign reject_a_ref = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002 : 16'h00ff; + + // Reject B: a one-bit selector, too shallow to be worth a load. + logic [19:0] reject_b_out, reject_b_ref; + always_comb + case (cyc[0]) + 1'b0: reject_b_out = 20'h00001; + 1'b1: reject_b_out = 20'h00002; + default: reject_b_out = 20'h00000; + endcase + assign reject_b_ref = cyc[0] ? 20'h00002 : 20'h00001; + + // Reject C: a 12-bit selector, too wide to table. + logic [15:0] reject_c_out, reject_c_ref; + always_comb + case (cyc[11:0]) + 12'd0: reject_c_out = 16'h0001; + 12'd1: reject_c_out = 16'h0002; + 12'd2: reject_c_out = 16'h0004; + default: reject_c_out = 16'h0000; + endcase + assign reject_c_ref = (cyc[11:0] == 12'd0) ? 16'h0001 + : (cyc[11:0] == 12'd1) ? 16'h0002 + : (cyc[11:0] == 12'd2) ? 16'h0004 : 16'h0000; + + // Reject D: a 17-bit selector, too wide to table. + logic [16:0] reject_d_in; + assign reject_d_in = cyc[16:0]; + logic [15:0] reject_d_out, reject_d_ref; + // verilator lint_off CASEINCOMPLETE + always_comb begin + reject_d_out = 16'hbeef; + case (reject_d_in) + 17'd0: reject_d_out = 16'h0001; + 17'd1: reject_d_out = 16'h0002; + 17'd2: reject_d_out = 16'h0004; + endcase + end + // verilator lint_on CASEINCOMPLETE + assign reject_d_ref = (reject_d_in == 17'd0) ? 16'h0001 + : (reject_d_in == 17'd1) ? 16'h0002 + : (reject_d_in == 17'd2) ? 16'h0004 : 16'hbeef; + + // Reject E: a whole output and a sub-range of it assigned in different items. + logic [7:0] reject_e_out, reject_e_ref; + always_comb begin + reject_e_out = 8'h00; + reject_e_out[3:0] = 4'h0; + case (cyc[1:0]) + 2'b00: reject_e_out = 8'haa; // assigns the whole output + 2'b01: reject_e_out[3:0] = 4'h5; // assigns a sub-range of the same output + default: ; + endcase + end + assign reject_e_ref = (cyc[1:0] == 2'd0) ? 8'haa : (cyc[1:0] == 2'd1) ? 8'h05 : 8'h00; + + // Reject F: a sub-range's default value is overwritten by a later whole-output default + // before the case, so the sub-range's pre-case value is set elsewhere. + logic [31:0] reject_f_out, reject_f_ref; + always_comb begin + reject_f_out[15:0] = 16'h0005; // farther default for the sub-range + reject_f_out = 32'h0; // closer whole-output default overwrites the sub-range to 0 + case (cyc[1:0]) + 2'b00: reject_f_out[15:0] = 16'habcd; // only the sub-range is assigned in the case + default: ; + endcase + end + assign reject_f_ref = (cyc[1:0] == 2'd0) ? 32'h0000abcd : 32'h00000000; + + // Test driver/checker + always @(posedge clk) begin + `checkh(accept_a_out, accept_a_ref); + `checkh(accept_b_out, accept_b_ref); + `checkh(accept_c_out_0, accept_c_ref_0); + `checkh(accept_c_out_1, accept_c_ref_1); + `checkh(accept_c_out_2, accept_c_ref_2); + `checkh(accept_d_out_0, accept_d_ref_0); + `checkh(accept_d_out_1, accept_d_ref_1); + `checkh(accept_e_out, accept_e_ref); + `checkh(accept_f_out, accept_f_ref); + `checkh(accept_g_out_0, accept_g_ref_0); + `checkh(accept_g_out_1, accept_g_ref_1); + `checkh(accept_g_out_2, accept_g_ref_2); + `checkh(accept_h_out, accept_h_ref); + `checkh(reject_a_out, reject_a_ref); + `checkh(reject_b_out, reject_b_ref); + `checkh(reject_c_out, reject_c_ref); + `checkh(reject_d_out, reject_d_ref); + `checkh(reject_e_out, reject_e_ref); + `checkh(reject_f_out, reject_f_ref); + + cyc <= cyc + 32'd1; + if (cyc == 32'd32) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_table_normal_off.py b/test_regress/t/t_case_table_normal_off.py new file mode 100755 index 000000000..7497fbc92 --- /dev/null +++ b/test_regress/t/t_case_table_normal_off.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_case_table_normal.v" + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-case-table']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_table_tiny.py b/test_regress/t/t_case_table_tiny.py new file mode 100755 index 000000000..55d103058 --- /dev/null +++ b/test_regress/t/t_case_table_tiny.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 11) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_case_table_tiny.v b/test_regress/t/t_case_table_tiny.v new file mode 100644 index 000000000..ecf79db3d --- /dev/null +++ b/test_regress/t/t_case_table_tiny.v @@ -0,0 +1,391 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// Case statements that become a "tiny" lookup table, followed by cases that must +// not be converted to one. Each output is compared against an equivalent reference +// computed without a case statement, so the reference itself is never tabled. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Accept A: single output, blocking assignment, all selector values covered. + wire [2:0] accept_a_in = cyc[2:0]; + logic [3:0] accept_a_out, accept_a_ref; + always_comb + case (accept_a_in) + 3'd0: accept_a_out = 4'd3; + 3'd1: accept_a_out = 4'd4; + 3'd2: accept_a_out = 4'd5; + 3'd3: accept_a_out = 4'd6; + 3'd4: accept_a_out = 4'd7; + 3'd5: accept_a_out = 4'd8; + 3'd6: accept_a_out = 4'd9; + 3'd7: accept_a_out = 4'd10; + endcase + assign accept_a_ref = 4'd3 + {1'b0, accept_a_in}; + + // Accept B: single output, non-blocking assignment, with a default value set before + // the case and not all selector values covered. + logic [3:0] accept_b_out, accept_b_ref; + // verilator lint_off CASEINCOMPLETE + always_ff @(posedge clk) begin + accept_b_out <= 4'hf; + case (cyc[1:0]) + 2'b00: accept_b_out <= 4'h1; + 2'b01: accept_b_out <= 4'h2; + endcase + end + // verilator lint_on CASEINCOMPLETE + always_ff @(posedge clk) + accept_b_ref <= (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'hf; + + // Accept C: two outputs of different widths, blocking assignment, with a default branch. + logic [2:0] accept_c_out_0, accept_c_ref_0; + logic [3:0] accept_c_out_1, accept_c_ref_1; + always_comb + case (cyc[1:0]) + 2'b00: begin + accept_c_out_0 = 3'd1; + accept_c_out_1 = 4'd6; + end + 2'b01: begin + accept_c_out_0 = 3'd2; + accept_c_out_1 = 4'd5; + end + default: begin + accept_c_out_0 = 3'd0; + accept_c_out_1 = 4'd7; + end + endcase + assign accept_c_ref_0 = (cyc[1:0] == 2'b00) ? 3'd1 : (cyc[1:0] == 2'b01) ? 3'd2 : 3'd0; + assign accept_c_ref_1 = (cyc[1:0] == 2'b00) ? 4'd6 : (cyc[1:0] == 2'b01) ? 4'd5 : 4'd7; + + // Accept D: two outputs, non-blocking assignment, empty default branch, with default + // values set before the case. + logic [2:0] accept_d_out_0, accept_d_ref_0; + logic [2:0] accept_d_out_1, accept_d_ref_1; + always_ff @(posedge clk) begin + accept_d_out_0 <= 3'd0; + accept_d_out_1 <= 3'd7; + case (cyc[1:0]) + 2'b00: begin + accept_d_out_0 <= 3'd1; + accept_d_out_1 <= 3'd6; + end + 2'b01: begin + accept_d_out_0 <= 3'd2; + accept_d_out_1 <= 3'd5; + end + default: begin + end + endcase + end + always_ff @(posedge clk) begin + accept_d_ref_0 <= (cyc[1:0] == 2'b00) ? 3'd1 : (cyc[1:0] == 2'b01) ? 3'd2 : 3'd0; + accept_d_ref_1 <= (cyc[1:0] == 2'b00) ? 3'd6 : (cyc[1:0] == 2'b01) ? 3'd5 : 3'd7; + end + + // Accept E: casez with a don't-care bit. + logic [3:0] accept_e_out, accept_e_ref; + always_comb + casez (cyc[1:0]) + 2'b1?: accept_e_out = 4'ha; + 2'b0?: accept_e_out = 4'hb; + endcase + assign accept_e_ref = cyc[1] ? 4'ha : 4'hb; + + // Accept F: an item that can never match, and an item listing multiple values. + logic [3:0] accept_f_out, accept_f_ref; + // verilator lint_off CASEWITHX + always_comb + casez (cyc[1:0]) + 2'bx0: accept_f_out = 4'h0; // X can never match in 2-state + 2'b01, 2'b11: accept_f_out = 4'h5; // lists two values + default: accept_f_out = 4'h9; + endcase + // verilator lint_on CASEWITHX + assign accept_f_ref = (cyc[1:0] == 2'b01 || cyc[1:0] == 2'b11) ? 4'h5 : 4'h9; + + // Accept G: items assign different subsets of two outputs, with default values (and an + // unrelated output) set before the case. + logic [3:0] accept_g_out_0, accept_g_ref_0; + logic [3:0] accept_g_out_1, accept_g_ref_1; + logic [3:0] accept_g_out_2, accept_g_ref_2; + // verilator lint_off CASEINCOMPLETE + always_comb begin + accept_g_out_0 = 4'h0; + accept_g_out_1 = 4'hf; + accept_g_out_2 = 4'h3; // not assigned in the case + case (cyc[1:0]) + 2'b00: accept_g_out_0 = 4'h1; + 2'b01: accept_g_out_1 = 4'h2; + endcase + end + // verilator lint_on CASEINCOMPLETE + assign accept_g_ref_0 = (cyc[1:0] == 2'b00) ? 4'h1 : 4'h0; + assign accept_g_ref_1 = (cyc[1:0] == 2'b01) ? 4'h2 : 4'hf; + assign accept_g_ref_2 = 4'h3; + + // Accept H: single output, non-blocking assignment, all selector values covered. + logic [3:0] accept_h_out, accept_h_ref; + always_ff @(posedge clk) + case (cyc[1:0]) + 2'b00: accept_h_out <= 4'h1; + 2'b01: accept_h_out <= 4'h2; + 2'b10: accept_h_out <= 4'h4; + 2'b11: accept_h_out <= 4'h8; + endcase + always_ff @(posedge clk) accept_h_ref <= 4'h1 << cyc[1:0]; + + // Accept I: unique0 enum case; the selector may hold an out-of-range value. + typedef enum logic [1:0] { + E0, + E1, + E2 + } e_t; + e_t accept_i_in; + assign accept_i_in = e_t'(cyc[1:0]); + logic [3:0] accept_i_out, accept_i_ref; + always_comb begin + accept_i_out = 4'hf; + unique0 case (accept_i_in) + E0: accept_i_out = 4'h1; + E1: accept_i_out = 4'h2; + E2: accept_i_out = 4'h3; + endcase + end + assign accept_i_ref = (cyc[1:0] == 2'd0) ? 4'h1 + : (cyc[1:0] == 2'd1) ? 4'h2 + : (cyc[1:0] == 2'd2) ? 4'h3 : 4'hf; + + // Accept J: wide output, materialized as a normal (not tiny) lookup table. + logic [8:0] accept_j_out, accept_j_ref; + always_comb + case (cyc[3:0]) + 4'd0: accept_j_out = 9'h001; + 4'd1: accept_j_out = 9'h002; + 4'd2: accept_j_out = 9'h004; + 4'd3: accept_j_out = 9'h008; + default: accept_j_out = 9'h010; + endcase + assign accept_j_ref = (cyc[3:0] < 4'd4) ? (9'h1 << cyc[3:0]) : 9'h010; + + // Accept K: a non-constant assignment precedes the case. + logic [3:0] accept_k_out_0, accept_k_ref_0; + logic [3:0] accept_k_out_1, accept_k_ref_1; + always_comb begin + accept_k_out_1 = cyc[3:0] ^ 4'ha; // non-constant value + case (cyc[1:0]) + 2'b00: accept_k_out_0 = 4'h1; + 2'b01: accept_k_out_0 = 4'h2; + 2'b10: accept_k_out_0 = 4'h4; + 2'b11: accept_k_out_0 = 4'h8; + endcase + end + assign accept_k_ref_0 = 4'h1 << cyc[1:0]; + assign accept_k_ref_1 = cyc[3:0] ^ 4'ha; + + // Accept L: the same output is given a default value twice before the case. + logic [3:0] accept_l_out, accept_l_ref; + // verilator lint_off CASEINCOMPLETE + always_comb begin + accept_l_out = 4'h1; + accept_l_out = 4'h6; // assigned a second time before the case + case (cyc[1:0]) + 2'b00: accept_l_out = 4'h2; + 2'b01: accept_l_out = 4'h3; + endcase + end + // verilator lint_on CASEINCOMPLETE + assign accept_l_ref = (cyc[1:0] == 2'd0) ? 4'h2 : (cyc[1:0] == 2'd1) ? 4'h3 : 4'h6; + + // The cases below are intentionally NOT converted to a lookup table. + + // Reject A: an item whose body is not a simple assignment. + logic [3:0] reject_a_out, reject_a_ref; + always_comb begin + reject_a_out = 4'h0; + case (cyc[1:0]) + 2'b00: reject_a_out = 4'h1; + 2'b01: if (cyc[0]) reject_a_out = 4'h2; // not a simple assignment + default: reject_a_out = 4'h3; + endcase + end + assign reject_a_ref = (cyc[1:0] == 2'd0) ? 4'h1 : (cyc[1:0] == 2'd1) ? 4'h2 : 4'h3; + + // Reject B: an item assigns through a variable bit-select (the index is read). + logic [3:0] reject_b_out, reject_b_ref; + always_comb begin + reject_b_out = 4'h0; + case (cyc[1:0]) + 2'b00: reject_b_out[cyc[1:0]] = 1'b1; + default: reject_b_out = 4'h5; + endcase + end + assign reject_b_ref = (cyc[1:0] == 2'd0) ? 4'h1 : 4'h5; + + // Reject C: an item assigns the same output twice. + logic [3:0] reject_c_out, reject_c_ref; + always_comb begin + reject_c_out = 4'h0; + case (cyc[1:0]) + 2'b00: begin + reject_c_out = 4'h1; + reject_c_out = 4'h2; + end + default: reject_c_out = 4'h3; + endcase + end + assign reject_c_ref = (cyc[1:0] == 2'd0) ? 4'h2 : 4'h3; + + // Reject D: a non-constant case-item value. + logic [1:0] reject_d_in; + assign reject_d_in = cyc[1:0]; + logic [3:0] reject_d_out, reject_d_ref; + always_comb begin + reject_d_out = 4'h0; + case (cyc[1:0]) + reject_d_in: reject_d_out = 4'h7; // non-constant item value + default: reject_d_out = 4'h9; + endcase + end + assign reject_d_ref = 4'h7; // reject_d_in always equals the case expression + + // Reject E: all items are empty. + logic [3:0] reject_e_out, reject_e_ref; + always_comb begin + reject_e_out = 4'h7; + case (cyc[2:0]) + 3'd0: ; + 3'd1: ; + 3'd2: ; + 3'd3: ; + 3'd4: ; + 3'd5: ; + 3'd6: ; + 3'd7: ; + endcase + end + assign reject_e_ref = 4'h7; + + // Reject F: an item uses a delayed (intra-assignment) assignment. + logic [3:0] reject_f_out, reject_f_ref; + always_ff @(posedge clk) + case (cyc[1:0]) + 2'b00: reject_f_out <= #1 4'h1; // delayed assignment + default: reject_f_out <= 4'h2; + endcase + always_ff @(posedge clk) + if (cyc[1:0] == 2'b00) reject_f_ref <= #1 4'h1; + else reject_f_ref <= 4'h2; + + // Reject G: an output assigned with both blocking and non-blocking assignments. The three + // variants exercise the distinct ways the assignment kinds conflict. The deliberate + // mixing warnings are waived. + // verilator lint_off BLKANDNBLK + // verilator lint_off COMBDLY + // verilator lint_off CASEINCOMPLETE + // Variant 0: an item mixes a blocking and a non-blocking assignment to the same output. + logic [3:0] reject_g_out_0, reject_g_ref_0; + always_comb + case (cyc[1:0]) + 2'b00: reject_g_out_0 = 4'h1; + 2'b01: reject_g_out_0 <= 4'h2; + default: reject_g_out_0 = 4'h3; + endcase + assign reject_g_ref_0 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h3; + // Variant 1: blocking items, but the pre-case default is a non-blocking assignment. + logic [3:0] reject_g_out_1, reject_g_ref_1; + always_comb begin + reject_g_out_1 <= 4'h0; + case (cyc[1:0]) + 2'b00: reject_g_out_1 = 4'h1; + 2'b01: reject_g_out_1 = 4'h2; + endcase + end + assign reject_g_ref_1 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h0; + // Variant 2: non-blocking items, but the pre-case default is a blocking assignment. + logic [3:0] reject_g_out_2, reject_g_ref_2; + always_comb begin + reject_g_out_2 = 4'h0; + case (cyc[1:0]) + 2'b00: reject_g_out_2 <= 4'h1; + 2'b01: reject_g_out_2 <= 4'h2; + endcase + end + assign reject_g_ref_2 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h0; + // verilator lint_on CASEINCOMPLETE + // verilator lint_on COMBDLY + // verilator lint_on BLKANDNBLK + + // Reject H: items assign a real (non-packed) output. + real reject_h_out, reject_h_ref; + always_comb + case (cyc[1:0]) + 2'b00: reject_h_out = 1.5; + 2'b01: reject_h_out = 2.5; + default: reject_h_out = 9.0; + endcase + always_comb reject_h_ref = (cyc[1:0] == 2'b00) ? 1.5 : (cyc[1:0] == 2'b01) ? 2.5 : 9.0; + + // Reject I: items assign a string (non-packed) output. + string reject_i_out, reject_i_ref; + always_comb + case (cyc[1:0]) + 2'b00: reject_i_out = "zero"; + 2'b01: reject_i_out = "one"; + default: reject_i_out = "other"; + endcase + always_comb reject_i_ref = (cyc[1:0] == 2'b00) ? "zero" : (cyc[1:0] == 2'b01) ? "one" : "other"; + + // Test driver/checker + always @(posedge clk) begin + `checkh(accept_a_out, accept_a_ref); + `checkh(accept_b_out, accept_b_ref); + `checkh(accept_c_out_0, accept_c_ref_0); + `checkh(accept_c_out_1, accept_c_ref_1); + `checkh(accept_d_out_0, accept_d_ref_0); + `checkh(accept_d_out_1, accept_d_ref_1); + `checkh(accept_e_out, accept_e_ref); + `checkh(accept_f_out, accept_f_ref); + `checkh(accept_g_out_0, accept_g_ref_0); + `checkh(accept_g_out_1, accept_g_ref_1); + `checkh(accept_g_out_2, accept_g_ref_2); + `checkh(accept_h_out, accept_h_ref); + `checkh(accept_i_out, accept_i_ref); + `checkh(accept_j_out, accept_j_ref); + `checkh(accept_k_out_0, accept_k_ref_0); + `checkh(accept_k_out_1, accept_k_ref_1); + `checkh(accept_l_out, accept_l_ref); + `checkh(reject_a_out, reject_a_ref); + `checkh(reject_b_out, reject_b_ref); + `checkh(reject_c_out, reject_c_ref); + `checkh(reject_d_out, reject_d_ref); + `checkh(reject_e_out, reject_e_ref); + `checkh(reject_f_out, reject_f_ref); + `checkh(reject_g_out_0, reject_g_ref_0); + `checkh(reject_g_out_1, reject_g_ref_1); + `checkh(reject_g_out_2, reject_g_ref_2); + `checkr(reject_h_out, reject_h_ref); + `checks(reject_i_out, reject_i_ref); + + cyc <= cyc + 32'd1; + if (cyc == 32'd32) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_table_tiny_off.py b/test_regress/t/t_case_table_tiny_off.py new file mode 100755 index 000000000..8890dcd07 --- /dev/null +++ b/test_regress/t/t_case_table_tiny_off.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_case_table_tiny.v" + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-case-table']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_unique_overlap.py b/test_regress/t/t_case_unique_overlap.py index 8a938befd..f3945fcdd 100755 --- a/test_regress/t/t_case_unique_overlap.py +++ b/test_regress/t/t_case_unique_overlap.py @@ -11,8 +11,10 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=['--stats']) test.execute() +test.file_grep(test.stats, r'Optimizations, Cases proven assertions\s+(\d+)', 1) + test.passes() diff --git a/test_regress/t/t_class_cpp_overload.py b/test_regress/t/t_class_cpp_overload.py new file mode 100755 index 000000000..0f1f5d0a0 --- /dev/null +++ b/test_regress/t/t_class_cpp_overload.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '-Wno-WIDTHTRUNC']) + +test.passes() diff --git a/test_regress/t/t_class_cpp_overload.v b/test_regress/t/t_class_cpp_overload.v new file mode 100644 index 000000000..02f67f60f --- /dev/null +++ b/test_regress/t/t_class_cpp_overload.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +interface irq_if ( + input logic clk, + input logic resetn +); + logic irq; + logic te; + logic halted; + logic fault; + logic wfi; + clocking cb @(posedge clk); + default input #1step output #2ns; + output irq; + output te; + input halted; + input fault; + input wfi; + endclocking + modport DUT_IRQ_PORT(input clk, resetn, output halted, fault, wfi); +endinterface +class base_test_class; + function int foo(); + endfunction + virtual irq_if.DUT_IRQ_PORT irq_vif; + function new(string name); + endfunction + virtual function void build_phase(); + if (irq_vif == null) begin + if (foo()) $display(); + end + endfunction +endclass +module tb_top; +endmodule diff --git a/test_regress/t/t_assert_ctl_unsup.py b/test_regress/t/t_class_extends_param_scope.py similarity index 78% rename from test_regress/t/t_assert_ctl_unsup.py rename to test_regress/t/t_class_extends_param_scope.py index da00b062f..3cc73805c 100755 --- a/test_regress/t/t_assert_ctl_unsup.py +++ b/test_regress/t/t_class_extends_param_scope.py @@ -9,8 +9,10 @@ import vltest_bootstrap -test.scenarios('linter') +test.scenarios('simulator') -test.lint(verilator_flags2=['--assert'], fails=True, expect_filename=test.golden_filename) +test.compile() + +test.execute() test.passes() diff --git a/test_regress/t/t_class_extends_param_scope.v b/test_regress/t/t_class_extends_param_scope.v new file mode 100644 index 000000000..d60ac9381 --- /dev/null +++ b/test_regress/t/t_class_extends_param_scope.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Sergey Chusov +// SPDX-License-Identifier: CC0-1.0 + +// A factory-like static method, as used by UVM's type_id::create(). +class registry #(type T = int); + static function T create(); + T r = new; + return r; + endfunction +endclass + +class item; + int val; + typedef registry#(item) type_id; + function new(); + val = 42; + endfunction +endclass + +class seq_base #(type REQ = int, type RSP = REQ); + REQ req; +endclass + +// Non-parameterized class extending a parameterized base. +// REQ is a type parameter inherited from the (specialized) base class and is +// here used as a '::' class scope: REQ::type_id::create(). Resolving REQ in +// this position requires deferring until after V3Param has bound REQ to item. +class seq extends seq_base #(item); + function int make(); + REQ r; + r = REQ::type_id::create(); + return r.val; + endfunction +endclass + +module t; + seq s; + initial begin + s = new; + if (s.make() != 42) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_member_shadow_type.py b/test_regress/t/t_class_member_shadow_type.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_class_member_shadow_type.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_member_shadow_type.v b/test_regress/t/t_class_member_shadow_type.v new file mode 100644 index 000000000..3e56ba5cd --- /dev/null +++ b/test_regress/t/t_class_member_shadow_type.v @@ -0,0 +1,53 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// A data member/variable named identically to a type from an enclosing scope +// must resolve its type specifier against the enclosing-scope type, not against +// the just-declared variable (IEEE 1800-2023 6.18). This is the UVM RAL / +// peakrdl-regblock pattern: 'rand ;'. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Tom Jackson +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef logic [7:0] my_t; // type at $unit scope + +class C; + my_t my_t; // member named the same as its (outer-scope) type +endclass + +class D; + my_t a; // second use of the type name after the shadowing variable... + my_t my_t; // ... is also legal; both resolve to the $unit typedef +endclass + +// The exact RAL shape: class member named after its class type +class my_blk; + int x; +endclass + +class parent; + rand my_blk my_blk; +endclass + +module t; + my_t my_t; // also legal at module scope + + initial begin + static C c = new; + static parent p = new; + c.my_t = 8'hAB; + p.my_blk = new; + p.my_blk.x = 5; + my_t = 8'h12; + `checkh(c.my_t, 8'hAB); + `checkh(p.my_blk.x, 5); + `checkh(my_t, 8'h12); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_nested_split_var.py b/test_regress/t/t_class_nested_split_var.py new file mode 100755 index 000000000..c87a1e49f --- /dev/null +++ b/test_regress/t/t_class_nested_split_var.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_nested_split_var.v b/test_regress/t/t_class_nested_split_var.v new file mode 100644 index 000000000..c4124f775 --- /dev/null +++ b/test_regress/t/t_class_nested_split_var.v @@ -0,0 +1,40 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +`ifdef VERILATOR +// The '$c(1)' is there to prevent inlining of the signal by V3Gate. +`define IMPURE_ONE ($c(1)) +`else +// Use standard $random. The chance of getting 2 consecutive zeroes is negligible. +`define IMPURE_ONE (|($random | $random)) +`endif + +module t; + bit [2:0] y; + bit [2:0] z; + assign z[0] = 1'b1; + assign z[1] = !(y[0]); + assign z[2] = !(|y[1:0]); + class Foo; + bit foo; + + task run(); + foo = `IMPURE_ONE; + if (z !== 3'b001) begin + $error("Failed: got %0b, expected %0b", z, 3'b001); + end + if (foo != 1'b1) $stop; + endtask + endclass + Foo test; + initial begin + static Foo foo = new; + #10 y = 3'b111; + #1 foo.run(); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_config_libmap_inc.py b/test_regress/t/t_config_libmap_inc.py index 10db367a5..bbe991e93 100755 --- a/test_regress/t/t_config_libmap_inc.py +++ b/test_regress/t/t_config_libmap_inc.py @@ -9,7 +9,6 @@ import vltest_bootstrap -test.scenarios('simulator') test.scenarios('linter') test.lint(verilator_flags2=["-libmap t/t_config_libmap_inc.map"], diff --git a/test_regress/t/t_constraint_dist.v b/test_regress/t/t_constraint_dist.v index 7d27d8748..3b62845a3 100644 --- a/test_regress/t/t_constraint_dist.v +++ b/test_regress/t/t_constraint_dist.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2024 Antmicro Ltd +// SPDX-FileCopyrightText: 2026 Antmicro // SPDX-License-Identifier: CC0-1.0 `define check_rand(cl, field, cond) \ @@ -11,7 +11,7 @@ begin \ if (!bit'(cl.randomize())) $stop; \ prev_result = longint'(field); \ if (!(cond)) $stop; \ - repeat(9) begin \ + repeat(100) begin \ longint result; \ if (!bit'(cl.randomize())) $stop; \ result = longint'(field); \ @@ -33,8 +33,12 @@ class C; }; constraint distinside { z dist {que}; - w dist {arr}; - }; + w dist {arr}; }; endclass + +class DistNarrow; + rand bit [3:0] x; + constraint c1 { x dist {[4'd1:4'd9] := 1}; } + constraint c2 { x > 4'd5; } endclass module t; @@ -45,6 +49,11 @@ module t; `check_rand(c, c.y, 5 <= c.y && c.y <= 6); `check_rand(c, c.z, 3 <= c.z && c.z <= 5); `check_rand(c, c.w, 5 <= c.w && c.w <= 7); + begin + DistNarrow dn; + dn = new; + `check_rand(dn, dn.x, 6 <= dn.x && dn.x <= 9); + end $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_constraint_dist_foreach_if.py b/test_regress/t/t_constraint_dist_foreach_if.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_foreach_if.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_dist_foreach_if.v b/test_regress/t/t_constraint_dist_foreach_if.v new file mode 100644 index 000000000..5d6f1b2ec --- /dev/null +++ b/test_regress/t/t_constraint_dist_foreach_if.v @@ -0,0 +1,148 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// Test that dist constraints nested inside if / -> inside foreach produce +// values only within the declared distribution and cover all buckets. + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +// foreach (a[i]) if (gate) a[i] dist {...} +class ClsIf; + rand bit [3:0] a[4]; + bit gate; + constraint c { + foreach (a[i]) { + if (gate == 1'b1) { + a[i] dist { + 4'd0 := 3, + [4'd1 : 4'd4] := 1 + }; + } + } + } +endclass + +// foreach (a[i]) gate -> a[i] dist {...} +class ClsImpl; + rand bit [3:0] a[4]; + bit gate; + constraint c { + foreach (a[i]) { + gate -> + (a[i] dist { + 4'd0 := 3, + [4'd1 : 4'd4] := 1 + }); + } + } +endclass + +// foreach (a[i]) gateA -> (gateB -> a[i] dist {...}) -- doubly-nested implication +class ClsImplChained; + rand bit [3:0] a[4]; + bit gateA, gateB; + constraint c { + foreach (a[i]) { + gateA -> + (gateB -> (a[i] dist { + 4'd0 := 3, + [4'd1 : 4'd4] := 1 + })); + } + } +endclass + +module t; + initial begin + // Test if form + begin + static ClsIf obj = new(); + int seen_zero, seen_nonzero; + obj.gate = 1'b1; + seen_zero = 0; + seen_nonzero = 0; + repeat (100) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4) begin + $write("%%Error: %s:%0d: if: value out of dist range: %0d\n", `__FILE__, `__LINE__, + obj.a[i]); + $stop; + end + if (obj.a[i] == 0) seen_zero++; + else seen_nonzero++; + end + end + if (seen_zero == 0 || seen_nonzero == 0) begin + $write( + "%%Error: %s:%0d: dist inside foreach+if: not all buckets hit (zero=%0d nonzero=%0d)\n", + `__FILE__, `__LINE__, seen_zero, seen_nonzero); + $stop; + end + end + + // Test -> (implication) form + begin + static ClsImpl obj = new(); + int seen_zero, seen_nonzero; + obj.gate = 1'b1; + seen_zero = 0; + seen_nonzero = 0; + repeat (100) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4) begin + $write("%%Error: %s:%0d: ->: value out of dist range: %0d\n", `__FILE__, `__LINE__, + obj.a[i]); + $stop; + end + if (obj.a[i] == 0) seen_zero++; + else seen_nonzero++; + end + end + if (seen_zero == 0 || seen_nonzero == 0) begin + $write( + "%%Error: %s:%0d: dist inside foreach+->: not all buckets hit (zero=%0d nonzero=%0d)\n", + `__FILE__, `__LINE__, seen_zero, seen_nonzero); + $stop; + end + end + + // Test doubly-nested -> (chained implication) form + begin + static ClsImplChained obj = new(); + int seen_zero, seen_nonzero; + obj.gateA = 1'b1; + obj.gateB = 1'b1; + seen_zero = 0; + seen_nonzero = 0; + repeat (100) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4) begin + $write("%%Error: %s:%0d: ->->: value out of dist range: %0d\n", `__FILE__, `__LINE__, + obj.a[i]); + $stop; + end + if (obj.a[i] == 0) seen_zero++; + else seen_nonzero++; + end + end + if (seen_zero == 0 || seen_nonzero == 0) begin + $write( + "%%Error: %s:%0d: dist inside foreach+->->: not all buckets hit (zero=%0d nonzero=%0d)\n", + `__FILE__, `__LINE__, seen_zero, seen_nonzero); + $stop; + end + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_dist_range.py b/test_regress/t/t_constraint_dist_range.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_range.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_dist_range.v b/test_regress/t/t_constraint_dist_range.v new file mode 100644 index 000000000..88ffe3e0b --- /dev/null +++ b/test_regress/t/t_constraint_dist_range.v @@ -0,0 +1,198 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0); +`define check_tol(gotv,expv) `check_range((gotv), (expv)*(100-TOL_PCT)/100, (expv)*(100+TOL_PCT)/100) +// verilog_format: on + +// Scalar: uniform over [0:9] (10% each) +class DistScalarRange; + rand bit [3:0] x; + constraint c { + x dist { + [4'd0 : 4'd9] := 1 + }; + } +endclass + +// Array foreach: uniform over [0:4] (20% each per element) +class DistForeachUniform; + rand bit [2:0] a[5]; + constraint c { + foreach (a[i]) + a[i] dist {[3'd0 : 3'd4] := 1}; + } +endclass + +// Array foreach: mixed single + range +// a[i] dist {0 := 5, [1:9] := 1} total weight = 5+9 = 14 +// 0: 5/14 ~= 35.7%, 1..9: 1/14 ~= 7.1% each +class DistForeachMixed; + rand bit [3:0] a[5]; + constraint c { + foreach (a[i]) + a[i] dist { + 4'd0 := 5, + [4'd1 : 4'd9] := 1 + }; + } +endclass + +// Scalar signed int: uniform over negative range [-9:0] (10% each) +class DistNegRange; + rand int x; + constraint c { + x dist { + [-9 : 0] := 1 + }; + } +endclass + +// Non-constant unsigned range bounds: uniform over [lo_val:hi_val] = [2:7] (6 values) +class DistVarRangeUnsigned; + rand bit [3:0] x; + bit [3:0] lo_val = 4'd2, hi_val = 4'd7; + constraint c { + x dist { + [lo_val : hi_val] := 1 + }; + } +endclass + +// Mixed const/non-const bounds: lo is constant, hi is a variable [1:hi_val] = [1:7] +class DistMixedBounds; + rand bit [3:0] x; + bit [3:0] hi_val = 4'd7; + constraint c { + x dist { + [4'd1 : hi_val] := 1 + }; + } +endclass + +module t; + parameter int N = 2000; // randomize() calls per test + parameter int TOL_PCT = 30; // +-% tolerance on expected counts + + initial begin + + // --- T1: scalar uniform [0:9] --- + // 10 values, expected N/10 each + begin + automatic DistScalarRange obj = new(); + int cnt[10]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x > 4'd9) begin + $write("%%Error: x=%0d outside valid range [0:9]\n", obj.x); + `stop; + end + cnt[obj.x]++; + end + foreach (cnt[v]) `check_tol(cnt[v], N / 10) + end + + // --- T2: array foreach uniform [0:4], 5 elements * N calls --- + // total element samples = N*5; 5 values -> expected N each + begin + automatic DistForeachUniform obj = new(); + int cnt[5]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 3'd4) begin + $write("%%Error: a[%0d]=%0d outside valid range [0:4]\n", i, obj.a[i]); + `stop; + end + cnt[obj.a[i]]++; + end + end + foreach (cnt[v]) `check_tol(cnt[v], N) + end + + // --- T3: array foreach mixed {0:=5, [1:9]:=1}, 5 elements * N calls --- + // total element samples = N*5; total weight = 5+9 = 14 + // v=0: expected N*5*5/14 + // v=1..9: expected N*5*1/14 + begin + automatic DistForeachMixed obj = new(); + int cnt[10]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4'd9) begin + $write("%%Error: a[%0d]=%0d outside valid range [0:9]\n", i, obj.a[i]); + `stop; + end + cnt[obj.a[i]]++; + end + end + `check_tol(cnt[0], N * 5 * 5 / 14) + for (int v = 1; v <= 9; v++) `check_tol(cnt[v], N * 5 * 1 / 14) + end + + // --- T4: signed int, uniform over negative range [-9:0] --- + // 10 values, expected N/10 each + // cnt[v] = count of (x == v-9), so v=0 -> x=-9, v=9 -> x=0 + begin + automatic DistNegRange obj = new(); + int cnt[10]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x < -9 || obj.x > 0) begin + $write("%%Error: x=%0d outside valid range [-9:0]\n", obj.x); + `stop; + end + cnt[obj.x+9]++; + end + foreach (cnt[v]) `check_tol(cnt[v], N / 10) + end + + // --- T5: non-constant unsigned range bounds [lo_val:hi_val] = [2:7] --- + // 6 values, expected N/6 each + begin + automatic DistVarRangeUnsigned obj = new(); + int cnt[16]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x < 4'd2 || obj.x > 4'd7) begin + $write("%%Error: x=%0d outside valid range [2:7]\n", obj.x); + `stop; + end + cnt[obj.x]++; + end + for (int v = 2; v <= 7; v++) `check_tol(cnt[v], N / 6) + end + + // --- T6: mixed const/non-const bounds [4'd1:hi_val] = [1:7] --- + // 7 values, expected N/7 each + begin + automatic DistMixedBounds obj = new(); + int cnt[16]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x < 4'd1 || obj.x > 4'd7) begin + $write("%%Error: x=%0d outside valid range [1:7]\n", obj.x); + `stop; + end + cnt[obj.x]++; + end + for (int v = 1; v <= 7; v++) `check_tol(cnt[v], N / 7) + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_global_cls_arr.py b/test_regress/t/t_constraint_global_cls_arr.py new file mode 100755 index 000000000..8862c2c31 --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_global_cls_arr.v b/test_regress/t/t_constraint_global_cls_arr.v new file mode 100644 index 000000000..1c9c3d6d4 --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +class Foo; + rand int abcd; +endclass + +class Bar; + rand Foo foo_arr[]; + + function new(); + foo_arr = new[12]; + foreach (foo_arr[i]) foo_arr[i] = new; + endfunction + + constraint c { + foo_arr.size() == 10; + foreach (foo_arr[i]) foo_arr[i].abcd < 8; + } +endclass + +module t; + Bar bar; + initial begin + bar = new(); + bar.randomize(); + if (bar.foo_arr.size() != 10) $stop; + foreach (bar.foo_arr[i]) begin + if (bar.foo_arr[i] >= 8) $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.out b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.out new file mode 100644 index 000000000..9d049fc2e --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_constraint_global_cls_arr_2d_unsup.v:13:12: Unsupported: Nested array element access in global constraint + : ... note: In instance 't' + 13 | rand Foo foo_arr[$][]; + | ^~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.py b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.py new file mode 100755 index 000000000..4cebd5d8e --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v new file mode 100644 index 000000000..46be2a8f3 --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +class Foo; + rand int abcd; + constraint c {abcd >= 2;} +endclass + +class Bar; + rand Foo foo_arr[$][]; + + function new(); + for (int i = 0; i < 3; i++) foo_arr[i] = new[5]; + foreach (foo_arr[i, j]) foo_arr[i][j] = new; + endfunction + + constraint c { + foo_arr.size() == 10; + foreach (foo_arr[i, j]) foo_arr[i][j].abcd < 8; + } +endclass + +module t; + Bar bar; + initial begin + bar = new(); + void'(bar.randomize()); + if (bar.foo_arr.size() != 10) $stop; + foreach (bar.foo_arr[i, j]) begin + if (bar.foo_arr[i][j].abcd < 2 || bar.foo_arr[i][j].abcd >= 8) $stop; + end + for (int i = 3; i < 10; i++) begin + if (bar.foo_arr[i].size() != 0) $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_global_subobj.py b/test_regress/t/t_constraint_global_subobj.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_global_subobj.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_global_subobj.v b/test_regress/t/t_constraint_global_subobj.v new file mode 100644 index 000000000..333695f2d --- /dev/null +++ b/test_regress/t/t_constraint_global_subobj.v @@ -0,0 +1,178 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// Scenario 1 (issue #7833 literal): owner SA1 declares a global constraint on a +// sub-object's type but is NEVER randomized; a standalone randomize() of the +// holder must still randomize the nested fields. +class C1; + rand int x; + rand int y; +endclass + +class B1; + rand C1 c; + function new(); + c = new(); + endfunction +endclass + +class SA1; + rand B1 b; + constraint c_foo {b.c.x == 123;} +endclass + +// Scenario 2 (combined): the SAME design randomizes both the constraint owner +// and a standalone holder of the constrained type. +class C2; + rand int x; + rand int y; +endclass + +class B2; + rand C2 c; + function new(); + c = new(); + endfunction +endclass + +class SA2; + rand B2 b; + constraint c_foo {b.c.x == 123;} + function new(); + b = new(); + endfunction +endclass + +// Scenario 3 (multiple same-type sub-objects): two sub-objects of one type are +// each pinned by a global constraint while that type is also randomized +// standalone; the two share one underlying rand variable. +class C3; + rand int x; +endclass + +class M3; + rand C3 c1; + rand C3 c2; + constraint c { + c1.x == 11; + c2.x == 22; + } + function new(); + c1 = new(); + c2 = new(); + endfunction +endclass + +// Scenario 4 (global constraint owner with its own size-constrained array): the +// owner basic-randomizes first, the solver overrides last, and the size-only +// resize fallback still resizes from the solver-determined size. +class C4; + rand int x; +endclass + +class A4; + rand C4 c; + rand int arr[]; + constraint c_sub {c.x == 5;} + constraint c_size {arr.size() == 4;} + function new(); + c = new(); + endfunction +endclass + +module t_constraint_global_subobj; + B1 b1; + SA2 a2; + B2 b2; + M3 m3; + C3 s3; + A4 a4; + int prevx, prevy, p3; + bit varyx, varyy, vary3; + + initial begin + // Scenario 1: SA1 never randomized; standalone B1 must vary both fields. + b1 = new(); + varyx = 0; + varyy = 0; + if (b1.randomize() != 1) $stop; + prevx = b1.c.x; + prevy = b1.c.y; + for (int i = 0; i < 20; i++) begin + if (b1.randomize() != 1) $stop; + if (b1.c.x != prevx) varyx = 1; + if (b1.c.y != prevy) varyy = 1; + prevx = b1.c.x; + prevy = b1.c.y; + end + if (!varyx || !varyy) begin + $display("ERROR: standalone holder fields stuck (varyx=%0d varyy=%0d)", varyx, varyy); + $stop; + end + + // Scenario 2: randomize the owner (constraint applies) and a standalone + // holder (fields random) in the same design. + a2 = new(); + if (a2.randomize() != 1) $stop; + if (a2.b.c.x != 123) begin + $display("ERROR: owner constraint not applied, a2.b.c.x=%0d", a2.b.c.x); + $stop; + end + b2 = new(); + varyx = 0; + if (b2.randomize() != 1) $stop; + prevx = b2.c.x; + for (int i = 0; i < 20; i++) begin + if (b2.randomize() != 1) $stop; + if (b2.c.x != prevx) varyx = 1; + prevx = b2.c.x; + end + if (!varyx) begin + $display("ERROR: standalone holder x stuck while owner also randomized"); + $stop; + end + + // Scenario 3: two same-type sub-objects each pinned, plus standalone vary. + m3 = new(); + if (m3.randomize() != 1) $stop; + if (m3.c1.x != 11) begin + $display("ERROR: m3.c1.x should be 11, got %0d", m3.c1.x); + $stop; + end + if (m3.c2.x != 22) begin + $display("ERROR: m3.c2.x should be 22, got %0d", m3.c2.x); + $stop; + end + s3 = new(); + vary3 = 0; + if (s3.randomize() != 1) $stop; + p3 = s3.x; + for (int i = 0; i < 20; i++) begin + if (s3.randomize() != 1) $stop; + if (s3.x != p3) vary3 = 1; + p3 = s3.x; + end + if (!vary3) begin + $display("ERROR: standalone same-type x stuck"); + $stop; + end + + // Scenario 4: owner with a global constraint AND its own size constraint. + a4 = new(); + if (a4.randomize() != 1) $stop; + if (a4.c.x != 5) begin + $display("ERROR: a4.c.x should be 5, got %0d", a4.c.x); + $stop; + end + if (a4.arr.size() != 4) begin + $display("ERROR: a4.arr.size() should be 4, got %0d", a4.arr.size()); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_json_only.out b/test_regress/t/t_constraint_json_only.out index ce4b7e0d1..c18d8a989 100644 --- a/test_regress/t/t_constraint_json_only.out +++ b/test_regress/t/t_constraint_json_only.out @@ -29,7 +29,7 @@ {"type":"VAR","name":"state","addr":"(Z)","loc":"d,17:10,17:15","dtypep":"(M)","origName":"state","verilogName":"state","direction":"NONE","lifetime":"VAUTOMI","varType":"MEMBER","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"FUNC","name":"strings_equal","addr":"(AB)","loc":"d,61:16,61:29","dtypep":"(U)","method":true,"cname":"strings_equal", "fvarp": [ - {"type":"VAR","name":"strings_equal","addr":"(BB)","loc":"d,61:16,61:29","dtypep":"(U)","origName":"strings_equal","verilogName":"strings_equal","direction":"OUTPUT","noCReset":true,"isFuncReturn":true,"isFuncLocal":true,"lifetime":"VAUTOM","varType":"VAR","dtypeName":"bit","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} + {"type":"VAR","name":"strings_equal","addr":"(BB)","loc":"d,61:16,61:29","dtypep":"(U)","origName":"strings_equal","verilogName":"strings_equal","direction":"OUTPUT","noCReset":true,"icoMaybeWritten":true,"isFuncReturn":true,"isFuncLocal":true,"lifetime":"VAUTOM","varType":"VAR","dtypeName":"bit","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} ],"classOrPackagep": [], "stmtsp": [ {"type":"VAR","name":"a","addr":"(CB)","loc":"d,61:37,61:38","dtypep":"(M)","origName":"a","verilogName":"a","direction":"INPUT","isFuncLocal":true,"lifetime":"VAUTOMI","varType":"PORT","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, diff --git a/test_regress/t/t_constraint_redops.py b/test_regress/t/t_constraint_redops.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_redops.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_redops.v b/test_regress/t/t_constraint_redops.v new file mode 100644 index 000000000..5eff12a63 --- /dev/null +++ b/test_regress/t/t_constraint_redops.v @@ -0,0 +1,123 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 by Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// Test case for reducing and in constraint +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_rand(cl, field, gotv, expv, count) \ +begin \ + automatic longint prev_result; \ + automatic int ok; \ + if (!bit'(cl.randomize())) $stop; \ + prev_result = longint'(field); \ + `checkd(gotv, expv) \ + repeat(count) begin \ + longint result; \ + if (!bit'(cl.randomize())) $stop; \ + result = longint'(field); \ + `checkd(gotv, expv) \ + if (result != prev_result) ok = 1; \ + prev_result = result; \ + end \ + if (ok != 1) $stop; \ +end +// verilog_format: on + +class test_redops_bitfields #(RANDVAL_BITWIDTH=8); + rand bit [RANDVAL_BITWIDTH-1:0] rand_val; + rand bit redand; + rand bit redxor; + rand bit redor; + + constraint c { + redand == &rand_val; + } + + constraint d { + redxor == ^rand_val; + } + + constraint e { + redor == |rand_val; + } + + function bit calc_redand(); + bit result = 1'b1; + + foreach (rand_val[idx]) begin + result &= rand_val[idx]; + end + + return result; + endfunction + + function bit calc_redxor(); + bit result; + + foreach (rand_val[idx]) begin + result ^= rand_val[idx]; + end + + return result; + endfunction + + function bit calc_redor(); + bit result = 1'b0; + + foreach (rand_val[idx]) begin + result |= rand_val[idx]; + end + + return result; + endfunction + + function void verify(); + //`check_rand(this, this.rand_val, this.redand, this.calc_redand(), 20); + `check_rand(this, this.rand_val, this.redxor, this.calc_redxor(), 20); + //`check_rand(this, this.rand_val, this.redor, this.calc_redor(), 20); + endfunction +endclass + +module t; + test_redops_bitfields #(.RANDVAL_BITWIDTH(1)) redops_1bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(8)) redops_8bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(16)) redops_16bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(32)) redops_32bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(47)) redops_47bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(63)) redops_63bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(64)) redops_64bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(128)) redops_128bit; + + initial begin + redops_1bit = new(); + redops_1bit.verify(); + + redops_8bit = new(); + redops_8bit.verify(); + + redops_16bit = new(); + redops_16bit.verify(); + + redops_32bit = new(); + redops_32bit.verify(); + + redops_47bit = new(); + redops_47bit.verify(); + + redops_63bit = new(); + redops_63bit.verify(); + + redops_64bit = new(); + redops_64bit.verify(); + + redops_128bit = new(); + redops_128bit.verify(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_unsup.v b/test_regress/t/t_constraint_unsup.v index 4b1c95031..84f67e5ce 100644 --- a/test_regress/t/t_constraint_unsup.v +++ b/test_regress/t/t_constraint_unsup.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 class Packet; diff --git a/test_regress/t/t_cover_expr_fork.py b/test_regress/t/t_cover_expr_fork.py new file mode 100755 index 000000000..57b6e30b1 --- /dev/null +++ b/test_regress/t/t_cover_expr_fork.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--coverage-expr --binary']) + +test.execute(all_run_flags=[" +verilator+coverage+file+" + test.obj_dir + "/coverage.dat"]) + +test.passes() diff --git a/test_regress/t/t_cover_expr_fork.v b/test_regress/t/t_cover_expr_fork.v new file mode 100644 index 000000000..c111e5947 --- /dev/null +++ b/test_regress/t/t_cover_expr_fork.v @@ -0,0 +1,31 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + int cnt = 0; + task automatic myTask; + fork + begin + bit x; + if (!x) begin + cnt++; + end + if (!$onehot(x)) begin + cnt++; + end + end + join_none + endtask + + initial begin + myTask(); + #1; + if (cnt != 2) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_cover_fsm_concat_unsup.out b/test_regress/t/t_cover_fsm_concat_unsup.out new file mode 100644 index 000000000..85da4d440 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.out @@ -0,0 +1,6 @@ +%Warning-COVERIGN: t/t_cover_fsm_concat_unsup.v:12:17: Ignoring unsupported: FSM coverage with CONCAT + 12 | assign c = ({a, b} == 8'h00); + | ^ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_concat_unsup.py b/test_regress/t/t_cover_fsm_concat_unsup.py new file mode 100755 index 000000000..4a63bc600 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage concat as unsupported operation test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--coverage']) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_concat_unsup.v b/test_regress/t/t_cover_fsm_concat_unsup.v new file mode 100644 index 000000000..a7d9a1f84 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.v @@ -0,0 +1,18 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input logic [6:0] a, + input logic b, + output logic c +); + assign c = ({a, b} == 8'h00); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.out b/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.out index 7416b7aa4..8cf3712f6 100644 --- a/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.out +++ b/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.out @@ -1,33 +1,33 @@ -%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:26:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_then_u.state_q': assigned value 3 is not present in the declared enum - 26 | S0: state_d = sel ? 2'd3 : S1; +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:274:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_wide_direct_u.state_q': assigned value 40'hffffffffff is not present in the declared enum + 274 | S0: state_d = 40'hffff_ffff_ff; | ^ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:53:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_else_u.state_q': assigned value 3 is not present in the declared enum - 53 | S0: state_d = sel ? S1 : 2'd3; - | ^ -%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:79:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_direct_u.state_q': assigned value 3 is not present in the declared enum - 79 | S0: state_d = 2'd3; - | ^ -%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:126:15: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_reset_u.state_q': assigned value 3 is not present in the declared enum - 126 | state_q <= 2'd3; - | ^~ -%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:150:7: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_source_u.state_q': case item value 3 is not present in the declared enum - 150 | 2'd3: state_d = S0; - | ^~~~ -%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:175:5: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_source_u.state_q': case item value 3 is not present in the declared enum - 175 | if (state_q == 2'd3) state_d = S0; - | ^~ -%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:199:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_direct_target_u.state_q': assigned value 3 is not present in the declared enum - 199 | if (state_q == S0) state_d = 2'd3; +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:249:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_else_target_u.state_q': assigned value 3 is not present in the declared enum + 249 | if (state_q == S0) state_d = sel ? S1 : 2'd3; | ^ %Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:224:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_then_target_u.state_q': assigned value 3 is not present in the declared enum 224 | if (state_q == S0) state_d = sel ? 2'd3 : S1; | ^ -%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:249:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_else_target_u.state_q': assigned value 3 is not present in the declared enum - 249 | if (state_q == S0) state_d = sel ? S1 : 2'd3; +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:199:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_direct_target_u.state_q': assigned value 3 is not present in the declared enum + 199 | if (state_q == S0) state_d = 2'd3; | ^ -%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:274:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_wide_direct_u.state_q': assigned value 40'hffffffffff is not present in the declared enum - 274 | S0: state_d = 40'hffff_ffff_ff; +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:175:5: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_source_u.state_q': case item value 3 is not present in the declared enum + 175 | if (state_q == 2'd3) state_d = S0; + | ^~ +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:150:7: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_source_u.state_q': case item value 3 is not present in the declared enum + 150 | 2'd3: state_d = S0; + | ^~~~ +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:126:15: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_reset_u.state_q': assigned value 3 is not present in the declared enum + 126 | state_q <= 2'd3; + | ^~ +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:79:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_direct_u.state_q': assigned value 3 is not present in the declared enum + 79 | S0: state_d = 2'd3; + | ^ +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:53:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_else_u.state_q': assigned value 3 is not present in the declared enum + 53 | S0: state_d = sel ? S1 : 2'd3; + | ^ +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:26:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_then_u.state_q': assigned value 3 is not present in the declared enum + 26 | S0: state_d = sel ? 2'd3 : S1; | ^ %Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_plain_always_warn_multi_bad.out b/test_regress/t/t_cover_fsm_plain_always_warn_multi_bad.out index 064ef2ff1..976c00e07 100644 --- a/test_regress/t/t_cover_fsm_plain_always_warn_multi_bad.out +++ b/test_regress/t/t_cover_fsm_plain_always_warn_multi_bad.out @@ -1,12 +1,12 @@ -%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:25:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb - 25 | case (state_q) +%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:86:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb + 86 | case (state_q) | ^~~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. %Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:55:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb 55 | case (state_d) | ^~~~ -%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:86:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb - 86 | case (state_q) +%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:25:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb + 25 | case (state_q) | ^~~~ %Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_sel.out b/test_regress/t/t_cover_fsm_sel.out new file mode 100644 index 000000000..a87e0569d --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.out @@ -0,0 +1,97 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: Verilog Test module + // + // This file ONLY is placed under the Creative Commons Public Domain + // SPDX-FileCopyrightText: 2026 Antmicro + // SPDX-License-Identifier: CC0-1.0 + + package P; + typedef struct packed {logic [7:0] vs;} C; + typedef struct packed { + C a; + int b; + } B; + typedef struct packed {B a;} A; + endpackage + + module t ( +%000009 input clk + ); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + +%000001 logic rst; +%000001 logic start; + integer cyc; +%000001 state_t state /*verilator fsm_reset_arc*/; +%000002 P::A a; +%000001 logic done; + + logic [7:0] va[int]; + logic [7:0] va2d[int][int]; + +%000001 logic b; +%000001 logic c; +%000001 logic d; + + assign b = (a.a.a.vs == 8'h0); + assign c = (va[0] == 8'h0); + assign d = (va2d[0][0] == 8'h0); + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000009 always @(posedge clk) begin +%000009 cyc <= cyc + 1; +%000008 if (cyc == 1) rst <= 1'b0; +%000008 if (cyc == 2) start <= 1'b1; +%000008 if (cyc == 3) start <= 1'b0; +%000008 if (cyc == 8) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000009 always_ff @(posedge clk) begin +%000007 if (rst) begin +%000002 state <= S_IDLE; + end +%000007 else begin +%000007 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S_DONE->S_DONE] +%000003 // [fsm_arc t.state::S_IDLE->S_IDLE] +%000001 // [fsm_arc t.state::S_IDLE->S_RUN] +%000001 // [fsm_state t.state::S_DONE] +%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED *** +%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED *** +%000001 // [fsm_state t.state::S_RUN] +%000002 S_IDLE: +%000001 if (start) state <= S_RUN; +%000001 else state <= S_IDLE; +%000003 S_RUN: begin +%000003 a.a.a.vs <= a.a.a.vs + 1; +%000003 done <= (a.a.a.vs == 8'h1); +%000002 if (done) begin +%000001 state <= S_DONE; + end +%000002 else begin +%000002 state <= S_RUN; + end + end +%000002 S_DONE: state <= S_DONE; +%000000 default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_sel.py b/test_regress/t/t_cover_fsm_sel.py new file mode 100755 index 000000000..9d066e89e --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage array sel test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import os + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage"]) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel.v b/test_regress/t/t_cover_fsm_sel.v new file mode 100644 index 000000000..732a63553 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.v @@ -0,0 +1,86 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +package P; + typedef struct packed {logic [7:0] vs;} C; + typedef struct packed { + C a; + int b; + } B; + typedef struct packed {B a;} A; +endpackage + +module t ( + input clk +); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + + logic rst; + logic start; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + P::A a; + logic done; + + logic [7:0] va[int]; + logic [7:0] va2d[int][int]; + + logic b; + logic c; + logic d; + + assign b = (a.a.a.vs == 8'h0); + assign c = (va[0] == 8'h0); + assign d = (va2d[0][0] == 8'h0); + + initial begin + rst = 1'b1; + start = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) start <= 1'b1; + if (cyc == 3) start <= 1'b0; + if (cyc == 8) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S_IDLE; + end + else begin + case (state) + S_IDLE: + if (start) state <= S_RUN; + else state <= S_IDLE; + S_RUN: begin + a.a.a.vs <= a.a.a.vs + 1; + done <= (a.a.a.vs == 8'h1); + if (done) begin + state <= S_DONE; + end + else begin + state <= S_RUN; + end + end + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_sel_assign.out b/test_regress/t/t_cover_fsm_sel_assign.out new file mode 100644 index 000000000..1f0c3983e --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.out @@ -0,0 +1,90 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: Verilog Test module + // + // This file ONLY is placed under the Creative Commons Public Domain + // SPDX-FileCopyrightText: 2026 Antmicro + // SPDX-License-Identifier: CC0-1.0 + + module t #( + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 + ) ( +%000009 input clk + ); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + +%000001 logic rst; +%000001 logic start; + integer cyc; +%000001 state_t state /*verilator fsm_reset_arc*/; +%000001 logic [1:0] done_arr; + +%000001 logic [W-1:0] a; +%000000 logic [BW-1:0] b; + begin +%000001 logic [D-1:0][W-1:0] s; + begin +%000009 always_ff @(posedge clk) s[b] <= a; + end + end + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000009 always @(posedge clk) begin +%000009 cyc <= cyc + 1; +%000008 if (cyc == 1) rst <= 1'b0; +%000008 if (cyc == 2) start <= 1'b1; +%000008 if (cyc == 3) start <= 1'b0; +%000008 if (cyc == 4) a[0] = 1'b1; +%000008 if (cyc == 8) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000009 always_ff @(posedge clk) begin +%000007 if (rst) begin +%000002 state <= S_IDLE; + end +%000007 else begin +%000007 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S_DONE->S_DONE] +%000003 // [fsm_arc t.state::S_IDLE->S_IDLE] +%000001 // [fsm_arc t.state::S_IDLE->S_RUN] +%000001 // [fsm_state t.state::S_DONE] +%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED *** +%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED *** +%000001 // [fsm_state t.state::S_RUN] +%000002 S_IDLE: +%000001 if (start) state <= S_RUN; +%000001 else state <= S_IDLE; +%000003 S_RUN: begin + ; +%000003 done_arr[0] <= (a[0] == 1'b1); +%000002 if (done_arr[0]) begin +%000001 state <= S_DONE; + end +%000002 else begin +%000002 state <= S_RUN; + end + end +%000002 S_DONE: state <= S_DONE; +%000000 default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_sel_assign.py b/test_regress/t/t_cover_fsm_sel_assign.py new file mode 100755 index 000000000..728658c89 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage assignment test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import os + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage"]) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel_assign.v b/test_regress/t/t_cover_fsm_sel_assign.v new file mode 100644 index 000000000..9f3ecca24 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.v @@ -0,0 +1,79 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t #( + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 +) ( + input clk +); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + + logic rst; + logic start; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + logic [1:0] done_arr; + + logic [W-1:0] a; + logic [BW-1:0] b; + begin + logic [D-1:0][W-1:0] s; + begin + always_ff @(posedge clk) s[b] <= a; + end + end + + initial begin + rst = 1'b1; + start = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) start <= 1'b1; + if (cyc == 3) start <= 1'b0; + if (cyc == 4) a[0] = 1'b1; + if (cyc == 8) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S_IDLE; + end + else begin + case (state) + S_IDLE: + if (start) state <= S_RUN; + else state <= S_IDLE; + S_RUN: begin + ; + done_arr[0] <= (a[0] == 1'b1); + if (done_arr[0]) begin + state <= S_DONE; + end + else begin + state <= S_RUN; + end + end + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out new file mode 100644 index 000000000..4b76f7298 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out @@ -0,0 +1,11 @@ +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:19:16: Coverage ignored for type ASSOCARRAYDTYPE + : ... note: In instance 't' + 19 | input P::A a, + | ^ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:19:16: Coverage ignored for type WILDCARDARRAYDTYPE + : ... note: In instance 't' + 19 | input P::A a, + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py new file mode 100755 index 000000000..59e566c59 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage ignores associative array selection operators +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--coverage']) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v new file mode 100644 index 000000000..9be3e9a7a --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v @@ -0,0 +1,30 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +package P; + typedef struct { + logic [7:0] va[int]; + logic [7:0] vw[*]; + } C; + typedef struct { + C a; + int b; + } B; + typedef struct {B a;} A; +endpackage +module t ( + input P::A a, + output logic b, + output logic c +); + assign b = (a.a.a.va[0] == 8'h0); + assign c = (a.a.a.vw[0] == 8'h0); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_cover_line_cc.info.out b/test_regress/t/t_cover_line_cc.info.out index 6a04c28bf..fbba38865 100644 --- a/test_regress/t/t_cover_line_cc.info.out +++ b/test_regress/t/t_cover_line_cc.info.out @@ -4,32 +4,32 @@ DA:15,1 DA:18,1 DA:55,10 DA:56,10 -BRDA:56,0,0,10 -BRDA:56,0,1,0 +BRDA:56,0,if,10 +BRDA:56,0,else,0 DA:57,10 DA:58,10 DA:60,9 -BRDA:60,0,0,1 -BRDA:60,0,1,9 +BRDA:60,0,if,1 +BRDA:60,0,else,9 DA:61,9 -BRDA:61,0,0,1 -BRDA:61,0,1,9 +BRDA:61,0,if,1 +BRDA:61,0,else,9 DA:62,1 DA:63,1 DA:66,9 -BRDA:66,0,0,1 -BRDA:66,0,1,9 +BRDA:66,0,if,1 +BRDA:66,0,else,9 DA:67,9 -BRDA:67,0,0,1 -BRDA:67,0,1,9 +BRDA:67,0,if,1 +BRDA:67,0,else,9 DA:69,9 DA:70,9 DA:73,9 -BRDA:73,0,0,1 -BRDA:73,0,1,9 +BRDA:73,0,if,1 +BRDA:73,0,else,9 DA:74,9 -BRDA:74,0,0,1 -BRDA:74,0,1,9 +BRDA:74,0,if,1 +BRDA:74,0,else,9 DA:75,1 DA:76,1 DA:79,9 @@ -41,8 +41,8 @@ DA:87,1 DA:88,1 DA:89,1 DA:91,7 -BRDA:91,0,0,1 -BRDA:91,0,1,7 +BRDA:91,0,if,1 +BRDA:91,0,else,7 DA:92,1 DA:93,1 DA:96,7 @@ -52,67 +52,67 @@ DA:101,0 DA:102,0 DA:104,0 DA:105,10 -BRDA:105,0,0,0 -BRDA:105,0,1,10 +BRDA:105,0,block,0 +BRDA:105,0,block,10 DA:106,10 DA:107,10 -BRDA:107,0,0,0 -BRDA:107,0,1,10 +BRDA:107,0,block,0 +BRDA:107,0,block,10 DA:110,1 DA:111,1 DA:113,1 DA:115,1 DA:120,7 -BRDA:120,0,0,1 -BRDA:120,0,1,7 +BRDA:120,0,if,1 +BRDA:120,0,else,7 DA:121,1 DA:122,1 DA:127,1 DA:129,1 DA:140,20 DA:141,18 -BRDA:141,0,0,2 -BRDA:141,0,1,18 +BRDA:141,0,if,2 +BRDA:141,0,else,18 DA:142,2 DA:145,18 DA:164,20 DA:165,20 DA:166,20 -BRDA:166,0,0,0 -BRDA:166,0,1,20 +BRDA:166,0,if,0 +BRDA:166,0,else,20 DA:168,0 DA:170,18 -BRDA:170,0,0,2 -BRDA:170,0,1,18 +BRDA:170,0,if,2 +BRDA:170,0,else,18 DA:172,2 DA:174,18 DA:188,11 DA:189,11 DA:190,11 -BRDA:190,0,0,11 -BRDA:190,0,1,0 +BRDA:190,0,if,11 +BRDA:190,0,else,0 DA:191,11 DA:194,11 DA:195,11 -BRDA:195,0,0,11 -BRDA:195,0,1,0 +BRDA:195,0,if,11 +BRDA:195,0,else,0 DA:196,11 DA:199,11 DA:200,11 -BRDA:200,0,0,11 -BRDA:200,0,1,0 +BRDA:200,0,if,11 +BRDA:200,0,else,0 DA:201,11 DA:215,10 DA:216,10 DA:219,11 DA:221,11 DA:222,10 -BRDA:222,0,0,1 -BRDA:222,0,1,10 +BRDA:222,0,if,1 +BRDA:222,0,else,10 DA:223,1 DA:225,10 -BRDA:225,0,0,1 -BRDA:225,0,1,10 +BRDA:225,0,if,1 +BRDA:225,0,else,10 DA:226,1 DA:229,11 DA:230,11 @@ -121,12 +121,12 @@ DA:232,11 DA:233,11 DA:253,10 DA:254,9 -BRDA:254,0,0,1 -BRDA:254,0,1,9 +BRDA:254,0,if,1 +BRDA:254,0,else,9 DA:256,1 DA:257,1 -BRDA:257,0,0,0 -BRDA:257,0,1,1 +BRDA:257,0,if,0 +BRDA:257,0,else,1 DA:266,10 DA:267,10 DA:268,1 @@ -139,8 +139,8 @@ DA:277,10 DA:278,10 DA:288,0 DA:289,0 -BRDA:289,0,0,0 -BRDA:289,0,1,0 +BRDA:289,0,if,0 +BRDA:289,0,else,0 DA:290,0 DA:292,0 DA:293,0 @@ -157,66 +157,66 @@ DA:328,10 DA:329,10 DA:330,10 DA:333,31 -BRDA:333,0,0,0 -BRDA:333,0,1,31 +BRDA:333,0,cond_then,0 +BRDA:333,0,cond_else,31 DA:334,28 -BRDA:334,0,0,3 -BRDA:334,0,1,28 +BRDA:334,0,cond_then,3 +BRDA:334,0,cond_else,28 DA:335,1 -BRDA:335,0,0,1 -BRDA:335,0,1,0 +BRDA:335,0,cond_then,1 +BRDA:335,0,cond_else,0 DA:336,10 DA:337,10 -BRDA:337,0,0,10 -BRDA:337,0,1,3 -BRDA:337,0,2,7 +BRDA:337,0,block,10 +BRDA:337,0,cond_then,3 +BRDA:337,0,cond_else,7 DA:338,10 -BRDA:338,0,0,10 -BRDA:338,0,1,0 -BRDA:338,0,2,10 +BRDA:338,0,block,10 +BRDA:338,0,cond_then,0 +BRDA:338,0,cond_else,10 DA:340,19 -BRDA:340,0,0,12 -BRDA:340,0,1,19 -BRDA:340,0,2,7 -BRDA:340,0,3,5 +BRDA:340,0,cond_then,12 +BRDA:340,0,cond_else,19 +BRDA:340,0,cond_then,7 +BRDA:340,0,cond_else,5 DA:343,11 -BRDA:343,0,0,11 -BRDA:343,0,1,0 +BRDA:343,0,cond_then,11 +BRDA:343,0,cond_else,0 DA:349,22 -BRDA:349,0,0,20 -BRDA:349,0,1,22 +BRDA:349,0,cond_then,20 +BRDA:349,0,cond_else,22 DA:352,11 DA:353,10 -BRDA:353,0,0,0 -BRDA:353,0,1,1 -BRDA:353,0,2,1 -BRDA:353,0,3,10 +BRDA:353,0,cond_then,0 +BRDA:353,0,cond_else,1 +BRDA:353,0,if,1 +BRDA:353,0,else,10 DA:354,10 DA:356,11 -BRDA:356,0,0,11 -BRDA:356,0,1,1 -BRDA:356,0,2,10 +BRDA:356,0,block,11 +BRDA:356,0,cond_then,1 +BRDA:356,0,cond_else,10 DA:359,55 -BRDA:359,0,0,11 -BRDA:359,0,1,55 +BRDA:359,0,block,11 +BRDA:359,0,block,55 DA:360,55 DA:362,44 -BRDA:362,0,0,11 -BRDA:362,0,1,11 -BRDA:362,0,2,33 -BRDA:362,0,3,44 +BRDA:362,0,block,11 +BRDA:362,0,cond_then,11 +BRDA:362,0,cond_else,33 +BRDA:362,0,block,44 DA:363,44 DA:366,11 -BRDA:366,0,0,0 -BRDA:366,0,1,11 +BRDA:366,0,if,0 +BRDA:366,0,else,11 DA:367,11 DA:370,10 -BRDA:370,0,0,1 -BRDA:370,0,1,10 +BRDA:370,0,cond_then,1 +BRDA:370,0,cond_else,10 DA:373,10 DA:374,9 -BRDA:374,0,0,1 -BRDA:374,0,1,9 +BRDA:374,0,if,1 +BRDA:374,0,else,9 BRF:83 BRH:32 end_of_record diff --git a/test_regress/t/t_cover_property.py b/test_regress/t/t_cover_property.py new file mode 100755 index 000000000..ef1f5c1ed --- /dev/null +++ b/test_regress/t/t_cover_property.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage', '--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_cover_property.v b/test_regress/t/t_cover_property.v new file mode 100644 index 000000000..d68118d96 --- /dev/null +++ b/test_regress/t/t_cover_property.v @@ -0,0 +1,76 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + logic [63:0] crc = 64'h5aef0c8dd70a4497; + logic a, b; + int cyc = 0; + + int n_imp_no = 0; // cover property (a |=> b) -- non-overlapped implication + int n_imp_ov = 0; // cover property (a |-> b) -- overlapped implication + int n_seq = 0; // cover property (a ##1 b) -- identity with |=> + int n_seq0 = 0; // cover property (a ##0 b) -- identity with |-> + int n_bool = 0; // cover property (a) -- bare boolean baseline + int n_named = 0; // cover property (named pr) -- identity with |=> + + default clocking cb @(posedge clk); + endclocking + + assign a = crc[0]; + assign b = crc[5]; + + property pr; + a |=> b; + endproperty + + cp_imp_no : + cover property (a |=> b) n_imp_no++; + cp_imp_ov : + cover property (a |-> b) n_imp_ov++; + cp_seq : + cover property (a ##1 b) n_seq++; + cp_seq0 : + cover property (a ##0 b) n_seq0++; + cp_bool : + cover property (a) n_bool++; + cp_named : + cover property (pr) n_named++; + + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[1] ^ crc[0]}; + if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + final begin + // A cover of an implication counts only non-vacuous matches (IEEE + // 1800-2023 16.15.2): the antecedent must match. So it is identical to the + // corresponding sequence cover, not the vacuous implication value. + `checkd(n_imp_no, n_seq); // Other sims: pass, 73 + `checkd(n_imp_ov, n_seq0); // Other sims: pass, 45 + // A named-property cover lowers the same implication, so it also counts + // non-vacuously (regression guard for the property-inlining path). + `checkd(n_named, n_imp_no); + `checkd(n_imp_no, 28); + `checkd(n_imp_ov, 27); // Other sims: pass, 73 + `checkd(n_seq, 28); // Other sims: 45, 27 + `checkd(n_seq0, 27); + `checkd(n_bool, 55); // Other sims: pass, 25 + `checkd(n_named, 28); // Other sims: 73, 54, 54 + end + +endmodule diff --git a/test_regress/t/t_cover_sequence.py b/test_regress/t/t_cover_sequence.py new file mode 100755 index 000000000..23e13d99b --- /dev/null +++ b/test_regress/t/t_cover_sequence.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing', '--coverage-user']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_cover_sequence.v b/test_regress/t/t_cover_sequence.v new file mode 100644 index 000000000..79516a81c --- /dev/null +++ b/test_regress/t/t_cover_sequence.v @@ -0,0 +1,89 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + logic [63:0] crc = 64'h5aef0c8dd70a4497; + logic rst_n = 1'b0; + logic a, b, c, d, e; + int cyc = 0; + + int hit_simple = 0; + int hit_clocked = 0; + int hit_clocked_disable = 0; + int hit_default_disable = 0; + int hit_consrep_range = 0; + int hit_consrep_2 = 0; + int hit_consrep_3 = 0; + + default clocking cb @(posedge clk); + endclocking + + // Non-adjacent CRC bits to avoid LFSR shift correlation + assign a = crc[0]; + assign b = crc[5]; + assign c = crc[10]; + assign d = crc[15]; + assign e = crc[20]; + + // Form 1: cover sequence ( sexpr ) stmt + cover sequence (a | b | c | d | e) hit_simple++; + + // Form 2: cover sequence ( clocking_event sexpr ) stmt + cover sequence (@(posedge clk) (a | b | c | d | e) ##[1:3] b) hit_clocked++; + + // Form 3: cover sequence ( clocking_event disable iff (expr) sexpr ) stmt + cover sequence (@(posedge clk) disable iff (!rst_n) a ##1 b) hit_clocked_disable++; + + // Form 4: cover sequence ( disable iff (expr) sexpr ) stmt + cover sequence (disable iff (!rst_n) a ##1 c) hit_default_disable++; + + // Form 5: consecutive repetition, counted per end-of-match + cover sequence (a [* 2: 3]) hit_consrep_range++; + cover sequence (a [* 2]) hit_consrep_2++; + cover sequence (a [* 3]) hit_consrep_3++; + + always @(posedge clk) begin +`ifdef TEST_VERBOSE + $write("[%0t] cyc=%0d crc=%x a=%b b=%b c=%b d=%b e=%b\n", $time, cyc, crc, a, b, c, d, e); +`endif + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[1] ^ crc[0]}; + if (cyc == 2) rst_n <= 1'b1; + if (cyc == 99) begin + `checkh(crc, 64'h261a9f1371d7aadf); + $finish; + end + end + + // Read the counters in 'final', not the clocked block: a same-cycle read of a + // cover counter races the cover's increment under --threads (vltmt). Verilator + // counts one more end-of-match than others on some forms. + final begin +`ifdef TEST_VERBOSE + $write("simple=%0d clocked=%0d clk_dis=%0d def_dis=%0d range=%0d 2=%0d 3=%0d\n", hit_simple, + hit_clocked, hit_clocked_disable, hit_default_disable, hit_consrep_range, hit_consrep_2, + hit_consrep_3); +`endif + `checkd(hit_simple, 96); // Other sims: 5, 95 + `checkd(hit_clocked, 149); + `checkd(hit_clocked_disable, 27); + `checkd(hit_default_disable, 30); + `checkd(hit_consrep_2, 30); // Other sims: 29 + `checkd(hit_consrep_3, 14); // Other sims: 13 + // a[*2:3] == a[*2] or a[*3] (IEEE 1800-2023 16.9.2) + `checkd(hit_consrep_range, hit_consrep_2 + hit_consrep_3); + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_cover_sequence_unsup.out b/test_regress/t/t_cover_sequence_unsup.out new file mode 100644 index 000000000..47af703cb --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.out @@ -0,0 +1,18 @@ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:21:33: Ignoring unsupported: cover sequence with a sequence operand of 'or' + 21 | cover sequence ((a ##[1:3] b) or 1'b0); + | ^~ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:24:32: Ignoring unsupported: cover sequence with a sequence operand of 'or' + 24 | cover sequence ((a [* 1: 3]) or 1'b0); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:27:21: Ignoring unsupported: cover sequence with this ranged cycle delay + 27 | cover sequence (a ##[1:2] (b ##1 c)); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:30:21: Ignoring unsupported: cover sequence with this ranged cycle delay + 30 | cover sequence (a ##[1:300] b); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:33:21: Ignoring unsupported: cover sequence with a ranged goto repetition + 33 | cover sequence (a [-> 2: 3]); + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_sequence_first_match_unsup.py b/test_regress/t/t_cover_sequence_unsup.py similarity index 87% rename from test_regress/t/t_sequence_first_match_unsup.py rename to test_regress/t/t_cover_sequence_unsup.py index 235ad76f1..56c514bc5 100755 --- a/test_regress/t/t_sequence_first_match_unsup.py +++ b/test_regress/t/t_cover_sequence_unsup.py @@ -4,12 +4,12 @@ # This program is free software; you can redistribute it and/or modify it # under the terms of either the GNU Lesser General Public License Version 3 # or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap -test.scenarios('simulator') +test.scenarios('vlt') test.lint(expect_filename=test.golden_filename, verilator_flags2=['--assert --error-limit 1000'], diff --git a/test_regress/t/t_cover_sequence_unsup.v b/test_regress/t/t_cover_sequence_unsup.v new file mode 100644 index 000000000..b2770a2fa --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + logic a, b, c; + + default clocking cb @(posedge clk); + endclocking + + // cover sequence (IEEE 1800-2023 16.14.3) counts every end-of-match. The + // following forms put a sub-sequence where only its final end is forwarded, + // so they are ignored (COVERIGN) rather than under-counted. + + // Sequence operand of 'or' (ranged cycle delay). + cover sequence ((a ##[1:3] b) or 1'b0); + + // Sequence operand of 'or' (consecutive repetition). + cover sequence ((a [* 1: 3]) or 1'b0); + + // Ranged cycle delay before a multi-cycle sequence. + cover sequence (a ##[1:2] (b ##1 c)); + + // Ranged cycle delay wide enough to use the counter FSM. + cover sequence (a ##[1:300] b); + + // Ranged goto repetition (every M..N-th match is a separate end). + cover sequence (a [-> 2: 3]); + +endmodule diff --git a/test_regress/t/t_cover_toggle_min.info.out b/test_regress/t/t_cover_toggle_min.info.out index 975b502e0..6cdc747be 100644 --- a/test_regress/t/t_cover_toggle_min.info.out +++ b/test_regress/t/t_cover_toggle_min.info.out @@ -1,20 +1,20 @@ TN:verilator_coverage SF:t/t_cover_toggle_min.v DA:10,1 -BRDA:10,0,0,1 -BRDA:10,0,1,0 -BRDA:10,0,2,0 -BRDA:10,0,3,0 +BRDA:10,0,a[0]:0->1,1 +BRDA:10,0,a[0]:1->0,0 +BRDA:10,0,a[1]:0->1,0 +BRDA:10,0,a[1]:1->0,0 DA:11,1 -BRDA:11,0,0,0 -BRDA:11,0,1,0 -BRDA:11,0,2,1 -BRDA:11,0,3,0 +BRDA:11,0,b[0]:0->1,0 +BRDA:11,0,b[0]:1->0,0 +BRDA:11,0,b[1]:0->1,1 +BRDA:11,0,b[1]:1->0,0 DA:12,1 -BRDA:12,0,0,1 -BRDA:12,0,1,1 -BRDA:12,0,2,1 -BRDA:12,0,3,0 +BRDA:12,0,c[0]:0->1,1 +BRDA:12,0,c[0]:1->0,1 +BRDA:12,0,c[1]:0->1,1 +BRDA:12,0,c[1]:1->0,0 BRF:12 BRH:0 end_of_record diff --git a/test_regress/t/t_covergroup_array_bins.out b/test_regress/t/t_covergroup_array_bins.out index 4c0767563..903c2b307 100644 --- a/test_regress/t/t_covergroup_array_bins.out +++ b/test_regress/t/t_covergroup_array_bins.out @@ -1,4 +1,28 @@ cg.data.grouped: 2 -cg.data.values: 3 -cg2.cp.range_arr: 3 -cg3.cp.range_sized: 3 +cg.data.values[0]: 1 +cg.data.values[1]: 1 +cg.data.values[2]: 1 +cg2.cp.range_arr[0]: 1 +cg2.cp.range_arr[1]: 1 +cg2.cp.range_arr[2]: 1 +cg2.cp.range_arr[3]: 0 +cg3.cp.range_sized[0]: 1 +cg3.cp.range_sized[1]: 1 +cg3.cp.range_sized[2]: 1 +cg3.cp.range_sized[3]: 0 +cg4.cp.all_vals[0]: 1 +cg4.cp.all_vals[1]: 1 +cg4.cp.all_vals[2]: 1 +cg4.cp.all_vals[3]: 0 +cg5.cp.hi_vals[0]: 1 +cg5.cp.hi_vals[1]: 0 +cg6.cp.lo_open[0]: 1 +cg6.cp.lo_open[1]: 0 +cg7.cp.rev[0]: 1 +cg7.cp.rev[1]: 0 +cg8.cp.w[0]: 0 +cg8.cp.w[1]: 1 +cg9.__cross7.cumulative_x_lo [cross]: 1 +cg9.__cross7.ok_x_lo [cross]: 1 +cg9.cpA.ok: 1 +cg9.cpB.lo: 1 diff --git a/test_regress/t/t_covergroup_array_bins.v b/test_regress/t/t_covergroup_array_bins.v index 84033319d..209e1b549 100644 --- a/test_regress/t/t_covergroup_array_bins.v +++ b/test_regress/t/t_covergroup_array_bins.v @@ -13,6 +13,8 @@ module t; bit [7:0] data; + bit [1:0] sel; + bit [63:0] wide; covergroup cg; coverpoint data { @@ -38,14 +40,79 @@ module t; } endgroup + // cg4: array bins with '$' (open range) - '$' resolves to the coverpoint domain max. + // For 2-bit sel, {[0:$]} == {[0:3]}: one bin per value -> 4 bins (issue #7750). + covergroup cg4; + cp: coverpoint sel { + bins all_vals[] = {[0 : $]}; + } + endgroup + + // cg5: lower-open range {[lo:$]} == {[lo:maxVal]} -> bins for 2 and 3 + covergroup cg5; + cp: coverpoint sel { + bins hi_vals[] = {[2 : $]}; + } + endgroup + + // cg6: upper-open range {[$:hi]} == {[0:hi]} -> bins for 0 and 1 + covergroup cg6; + cp: coverpoint sel { + bins lo_open[] = {[$ : 1]}; + } + endgroup + + // cg7: a reversed range {[hi:lo]} (hi 2 bins total. + covergroup cg7; + cp: coverpoint data { + bins rev[] = {[3 : 1], 5, 7}; + } + endgroup + + // cg8: wide (>= 64-bit) coverpoint, exercising the 64-bit domain-max path + covergroup cg8; + cp: coverpoint wide { + bins w[] = {[0 : 1]}; + } + endgroup + + // cg9: two ranges that are each under COVER_BINS_LIMIT (1000) but whose + // cumulative size exceeds it. The first range populates the value list, the + // second trips the running-total guard -> COVERIGN, the whole bin is ignored. + // cpA is crossed, so it is non-convertible and routes through the legacy + // per-bin generateArrayBins() path (exercising its unsupported-bin guard). + covergroup cg9; + cpA: coverpoint wide { + bins cumulative[] = {[0 : 500], [0 : 500]}; + bins ok = {5}; + } + cpB: coverpoint sel { + bins lo = {1}; + } + cross cpA, cpB; + endgroup + initial begin cg cg_inst; cg2 cg2_inst; cg3 cg3_inst; + cg4 cg4_inst; + cg5 cg5_inst; + cg6 cg6_inst; + cg7 cg7_inst; + cg8 cg8_inst; + cg9 cg9_inst; cg_inst = new(); cg2_inst = new(); cg3_inst = new(); + cg4_inst = new(); + cg5_inst = new(); + cg6_inst = new(); + cg7_inst = new(); + cg8_inst = new(); + cg9_inst = new(); // Hit first array bin value (1) data = 1; @@ -94,6 +161,42 @@ module t; cg3_inst.sample(); `checkr(cg3_inst.get_inst_coverage(), 75.0); + // Hit cg4 '$' bins ([0:$] == [0:3], 4 bins): cover 3 of 4 + sel = 0; + cg4_inst.sample(); + `checkr(cg4_inst.get_inst_coverage(), 25.0); + sel = 1; + cg4_inst.sample(); + `checkr(cg4_inst.get_inst_coverage(), 50.0); + sel = 2; + cg4_inst.sample(); + `checkr(cg4_inst.get_inst_coverage(), 75.0); + + // Hit cg5 lower-open bins ([2:$] == [2:3], 2 bins): cover 1 of 2 + sel = 2; + cg5_inst.sample(); + `checkr(cg5_inst.get_inst_coverage(), 50.0); + + // Hit cg6 upper-open bins ([$:1] == [0:1], 2 bins): cover 1 of 2 + sel = 0; + cg6_inst.sample(); + `checkr(cg6_inst.get_inst_coverage(), 50.0); + + // Hit cg7 bins (reversed [3:1] -> no bins; 5 and 7 -> 2 bins): cover 1 of 2 + data = 5; + cg7_inst.sample(); + `checkr(cg7_inst.get_inst_coverage(), 50.0); + + // Hit cg8 wide bins ([0:1], 2 bins): cover 1 of 2 + wide = 1; + cg8_inst.sample(); + `checkr(cg8_inst.get_inst_coverage(), 50.0); + + // Exercise cg9 (crossed cpA with an ignored cumulative array bin, legacy path) + wide = 5; + sel = 1; + cg9_inst.sample(); + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_autobins_bad.out b/test_regress/t/t_covergroup_autobins_bad.out index bb8c92918..2e5bb49e5 100644 --- a/test_regress/t/t_covergroup_autobins_bad.out +++ b/test_regress/t/t_covergroup_autobins_bad.out @@ -1,76 +1,88 @@ -%Error: t/t_covergroup_autobins_bad.v:17:12: Automatic bins array size must be a constant +%Error: t/t_covergroup_autobins_bad.v:18:12: Automatic bins array size must be a constant : ... note: In instance 't' - 17 | bins auto[size_var]; + 18 | bins auto[size_var]; | ^~~~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_covergroup_autobins_bad.v:24:12: Automatic bins array size must be >= 1, got 0 +%Error: t/t_covergroup_autobins_bad.v:25:12: Automatic bins array size must be >= 1, got 0 : ... note: In instance 't' - 24 | bins auto[0]; + 25 | bins auto[0]; | ^~~~ -%Error: t/t_covergroup_autobins_bad.v:31:12: Automatic bins array size of 1001 exceeds limit of 1000 +%Error: t/t_covergroup_autobins_bad.v:32:12: Automatic bins array size of 1001 exceeds limit of 1000 : ... note: In instance 't' - 31 | bins auto[1001]; + 32 | bins auto[1001]; | ^~~~ -%Error: t/t_covergroup_autobins_bad.v:43:26: Non-constant expression in bin value list; values must be constants +%Error: t/t_covergroup_autobins_bad.v:44:26: Non-constant expression in bin value list; values must be constants : ... note: In instance 't' - 43 | ignore_bins ign = {size_var}; + 44 | ignore_bins ign = {size_var}; | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:44:32: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:45:32: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 44 | ignore_bins ign_range = {[0:size_var]}; + 45 | ignore_bins ign_range = {[0:size_var]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:38:12: Non-constant expression in array bins range; range bounds must be constants - : ... note: In instance 't' - 38 | bins b[] = {[size_var:size_var]}; - | ^ %Error: t/t_covergroup_autobins_bad.v:39:12: Non-constant expression in array bins range; range bounds must be constants : ... note: In instance 't' - 39 | bins b_mixed[] = {[0:size_var]}; + 39 | bins b[] = {[size_var:size_var]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:40:12: Non-constant expression in array bins range; range bounds must be constants + : ... note: In instance 't' + 40 | bins b_mixed[] = {[0:size_var]}; | ^~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:40:23: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:41:23: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 40 | bins b_range = {[size_var:4]}; + 41 | bins b_range = {[size_var:4]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:41:24: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:42:24: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 41 | bins b_range2 = {[0:size_var]}; + 42 | bins b_range2 = {[0:size_var]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:42:18: Non-constant expression in bin range; values must be constants +%Error: t/t_covergroup_autobins_bad.v:43:18: Non-constant expression in bin range; values must be constants : ... note: In instance 't' - 42 | bins b2 = {size_var}; + 43 | bins b2 = {size_var}; | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:43:26: Non-constant expression in bin range; values must be constants +%Error: t/t_covergroup_autobins_bad.v:44:26: Non-constant expression in bin range; values must be constants : ... note: In instance 't' - 43 | ignore_bins ign = {size_var}; + 44 | ignore_bins ign = {size_var}; | ^~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:51:25: Ignoring unsupported: non-constant 'option.at_least'; using default value +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:52:25: Ignoring unsupported: non-constant 'option.at_least'; using default value : ... note: In instance 't' - 51 | option.at_least = size_var; + 52 | option.at_least = size_var; | ^~~~~~~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Error: t/t_covergroup_autobins_bad.v:61:31: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:62:31: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 61 | ignore_bins ign_nclo = {[size_var:4]}; + 62 | ignore_bins ign_nclo = {[size_var:4]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:58:20: Four-state (x/z) value in bin range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:59:20: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 58 | bins b_xz = {[4'bxxxx:4'hF]}; + 59 | bins b_xz = {[4'bxxxx:4'hF]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:59:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants - : ... note: In instance 't' - 59 | ignore_bins ign_xz_lo = {[4'bxxxx:4'hF]}; - | ^ %Error: t/t_covergroup_autobins_bad.v:60:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 60 | ignore_bins ign_xz_hi = {[4'h0:4'bzzzz]}; + 60 | ignore_bins ign_xz_lo = {[4'bxxxx:4'hF]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:62:23: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:61:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 62 | bins b_nc_ub = {[size_var:$]}; - | ^ -%Error: t/t_covergroup_autobins_bad.v:63:23: Four-state (x/z) value in bin range bound; range bounds must be two-state constants + 61 | ignore_bins ign_xz_hi = {[4'h0:4'bzzzz]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:63:23: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 63 | bins b_xz_ub = {[4'bxxxx:$]}; + 63 | bins b_nc_ub = {[size_var:$]}; | ^ +%Error: t/t_covergroup_autobins_bad.v:64:23: Four-state (x/z) value in bin range bound; range bounds must be two-state constants + : ... note: In instance 't' + 64 | bins b_xz_ub = {[4'bxxxx:$]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:65:12: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants + : ... note: In instance 't' + 65 | bins b_xz_arr[] = {[4'bxxxx:4'hF]}; + | ^~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:66:12: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants + : ... note: In instance 't' + 66 | bins b_xz_arr_hi[] = {[4'h0:4'bzzzz]}; + | ^~~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:73:12: Unsupported: array 'bins' covering more than 1000 values (e.g. an open '[lo:$]' range over a wide coverpoint); bin ignored + : ... note: In instance 't' + 73 | bins b_huge[] = {[0:$]}; + | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_autobins_bad.v b/test_regress/t/t_covergroup_autobins_bad.v index 8a19c0c8e..db8a48fd5 100644 --- a/test_regress/t/t_covergroup_autobins_bad.v +++ b/test_regress/t/t_covergroup_autobins_bad.v @@ -10,6 +10,7 @@ module t; int size_var; logic [3:0] cp_expr; + logic [15:0] cp_wide; // Error: array size must be a constant covergroup cg1; @@ -61,6 +62,15 @@ module t; ignore_bins ign_nclo = {[size_var:4]}; // non-constant lower bound bins b_nc_ub = {[size_var:$]}; // non-constant lower bound, open-ended '$' upper bins b_xz_ub = {[4'bxxxx:$]}; // four-state lower bound, open-ended '$' upper + bins b_xz_arr[] = {[4'bxxxx:4'hF]}; // four-state lower bound (array-bins path) + bins b_xz_arr_hi[] = {[4'h0:4'bzzzz]}; // four-state upper bound (array-bins path) + } + endgroup + + // Warning (COVERIGN): array bins range exceeds COVER_BINS_LIMIT + covergroup cg6; + cp1: coverpoint cp_wide { + bins b_huge[] = {[0:$]}; // open '[lo:$]' over 16-bit coverpoint exceeds bin limit } endgroup @@ -70,6 +80,7 @@ module t; cg3 cg3_inst = new; cg4 cg4_inst = new; cg5 cg5_inst = new; + cg6 cg6_inst = new; initial $finish; endmodule diff --git a/test_regress/t/t_covergroup_clocked_sample.py b/test_regress/t/t_covergroup_clocked_sample.py index 9f6b5465d..be9b453d3 100755 --- a/test_regress/t/t_covergroup_clocked_sample.py +++ b/test_regress/t/t_covergroup_clocked_sample.py @@ -10,6 +10,7 @@ import vltest_bootstrap import coverage_covergroup_common -test.scenarios('vlt_all') +# Issue #7779 unstable with --vltmt +test.scenarios('vlt') coverage_covergroup_common.run(test) diff --git a/test_regress/t/t_covergroup_cross.out b/test_regress/t/t_covergroup_cross.out index 84d9104d6..abb035e5e 100644 --- a/test_regress/t/t_covergroup_cross.out +++ b/test_regress/t/t_covergroup_cross.out @@ -65,6 +65,15 @@ cg_at_least.cp_addr.addr0: 1 cg_at_least.cp_addr.addr1: 1 cg_at_least.cp_cmd.read: 1 cg_at_least.cp_cmd.write: 1 +cg_def_cross.axc.a0_x_read [cross]: 1 +cg_def_cross.axc.a0_x_write [cross]: 0 +cg_def_cross.axc.a1_x_read [cross]: 0 +cg_def_cross.axc.a1_x_write [cross]: 0 +cg_def_cross.cp_a.a0: 1 +cg_def_cross.cp_a.a1: 0 +cg_def_cross.cp_a.ad: 1 +cg_def_cross.cp_c.read: 1 +cg_def_cross.cp_c.write: 1 cg_goal.addr_cmd_goal.addr0_x_read [cross]: 1 cg_goal.addr_cmd_goal.addr0_x_write [cross]: 0 cg_goal.addr_cmd_goal.addr1_x_read [cross]: 0 @@ -82,6 +91,16 @@ cg_ignore.cross_ab.a0_x_read [cross]: 1 cg_ignore.cross_ab.a0_x_write [cross]: 1 cg_ignore.cross_ab.a1_x_read [cross]: 1 cg_ignore.cross_ab.a1_x_write [cross]: 1 +cg_mixed.ab.addr0_x_read [cross]: 1 +cg_mixed.ab.addr0_x_write [cross]: 1 +cg_mixed.ab.addr1_x_read [cross]: 1 +cg_mixed.ab.addr1_x_write [cross]: 1 +cg_mixed.cp_addr.addr0: 2 +cg_mixed.cp_addr.addr1: 2 +cg_mixed.cp_cmd.read: 2 +cg_mixed.cp_cmd.write: 2 +cg_mixed.cp_solo.debug: 2 +cg_mixed.cp_solo.normal: 2 cg_range.addr_cmd_range.hi_range_x_read [cross]: 1 cg_range.addr_cmd_range.hi_range_x_write [cross]: 1 cg_range.addr_cmd_range.lo_range_x_read [cross]: 1 @@ -90,10 +109,10 @@ cg_range.cp_addr.hi_range: 2 cg_range.cp_addr.lo_range: 2 cg_range.cp_cmd.read: 2 cg_range.cp_cmd.write: 2 -cg_unnamed_cross.__cross7.a0_x_read [cross]: 1 -cg_unnamed_cross.__cross7.a0_x_write [cross]: 0 -cg_unnamed_cross.__cross7.a1_x_read [cross]: 0 -cg_unnamed_cross.__cross7.a1_x_write [cross]: 1 +cg_unnamed_cross.__cross8.a0_x_read [cross]: 1 +cg_unnamed_cross.__cross8.a0_x_write [cross]: 0 +cg_unnamed_cross.__cross8.a1_x_read [cross]: 0 +cg_unnamed_cross.__cross8.a1_x_write [cross]: 1 cg_unnamed_cross.cp_a.a0: 1 cg_unnamed_cross.cp_a.a1: 1 cg_unnamed_cross.cp_c.read: 1 diff --git a/test_regress/t/t_covergroup_cross.v b/test_regress/t/t_covergroup_cross.v index 6245baa35..f0857bc01 100644 --- a/test_regress/t/t_covergroup_cross.v +++ b/test_regress/t/t_covergroup_cross.v @@ -17,6 +17,9 @@ module t; logic mode; logic parity; + typedef struct packed {logic m_p; logic h_mode;} cfg_t; + cfg_t s_cfg = '0; + // 2-way cross covergroup cg2; cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} @@ -90,6 +93,12 @@ module t; addr_cmd_unsup: cross cp_addr, cp_cmd{ option.per_instance = 1; // unsupported for cross - expect COVERIGN warning } + // Non-standard hierarchical reference as a cross item (an implicit coverpoint): + // accepted with NONSTD, but implicit coverpoints are unsupported so the whole + // cross is dropped (COVERIGN, suppressed here) - it contributes no bins. + /* verilator lint_off NONSTD */ + cross_hier: cross cp_addr, s_cfg.m_p; + /* verilator lint_on NONSTD */ endgroup // Covergroup with an unnamed cross - the cross is reported under the default name "cross" @@ -99,6 +108,23 @@ module t; cross cp_a, cp_c; // no label: reported under the default cross name endgroup + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted + // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + covergroup cg_mixed; + cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed + ab: cross cp_addr, cp_cmd; + endgroup + + // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the + // legacy default-bin codegen path that converted coverpoints bypass. + covergroup cg_def_cross; + cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} + cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} + axc: cross cp_a, cp_c; + endgroup + cg2 cg2_inst = new; cg_ignore cg_ignore_inst = new; cg_range cg_range_inst = new; @@ -109,6 +135,8 @@ module t; cg_goal cg_goal_inst = new; cg_unsup_cross_opt cg_unsup_cross_opt_inst = new; cg_unnamed_cross cg_unnamed_cross_inst = new; + cg_mixed cg_mixed_inst = new; + cg_def_cross cg_def_cross_inst = new; initial begin // Sample 2-way: hit all 4 combinations @@ -275,6 +303,23 @@ module t; cg_unnamed_cross_inst.sample(); // a1 x write `checkr(cg_unnamed_cross_inst.get_inst_coverage(), 75.0); + // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) + addr = 0; cmd = 0; mode = 0; + cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) + `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 + addr = 0; cmd = 1; mode = 1; + cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) + addr = 1; cmd = 0; mode = 0; + cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) + addr = 1; cmd = 1; mode = 1; + cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) + `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 + + // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) + mode = 1; + addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read + addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_sequence_intersect_len_warn.py b/test_regress/t/t_covergroup_cross_no_coverage.py similarity index 75% rename from test_regress/t/t_sequence_intersect_len_warn.py rename to test_regress/t/t_covergroup_cross_no_coverage.py index 58fc7aeba..699c061b8 100755 --- a/test_regress/t/t_sequence_intersect_len_warn.py +++ b/test_regress/t/t_covergroup_cross_no_coverage.py @@ -10,9 +10,11 @@ import vltest_bootstrap test.scenarios('vlt_all') +test.top_filename = 't/t_covergroup_cross.v' -test.compile(verilator_flags2=['--assert', '--timing', '--lint-only'], - fails=True, - expect_filename=test.golden_filename) +# runs without --coverage +test.compile(verilator_flags2=['--Wno-COVERIGN']) + +test.execute() test.passes() diff --git a/test_regress/t/t_covergroup_cross_opt_unsup.out b/test_regress/t/t_covergroup_cross_opt_unsup.out index d07db1513..e1e2c5621 100644 --- a/test_regress/t/t_covergroup_cross_opt_unsup.out +++ b/test_regress/t/t_covergroup_cross_opt_unsup.out @@ -1,3 +1,8 @@ +%Warning-NONSTD: t/t_covergroup_cross_opt_unsup.v:19:34: Non-standard hierarchical reference as a coverage cross item (an implicit coverpoint) + 19 | cross_hier: cross cp_a, s_cfg.m_p; + | ^ + ... For warning description see https://verilator.org/warn/NONSTD?v=latest + ... Use "/* verilator lint_off NONSTD */" and lint_on around source to disable this message. %Warning-COVERIGN: t/t_covergroup_cross_opt_unsup.v:13:7: Ignoring unsupported coverage cross option: 'per_instance' 13 | option.per_instance = 1; | ^~~~~~ @@ -7,4 +12,8 @@ : ... note: In instance 't' 15 | cross_implicit: cross cp_a, var_x; | ^~~~~ +%Warning-COVERIGN: t/t_covergroup_cross_opt_unsup.v:19:34: Unsupported: cross of hierarchical reference (implicit coverpoint) + : ... note: In instance 't' + 19 | cross_hier: cross cp_a, s_cfg.m_p; + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_cross_opt_unsup.v b/test_regress/t/t_covergroup_cross_opt_unsup.v index bb63a86be..720fbad6c 100644 --- a/test_regress/t/t_covergroup_cross_opt_unsup.v +++ b/test_regress/t/t_covergroup_cross_opt_unsup.v @@ -13,7 +13,13 @@ module t; option.per_instance = 1; // unsupported for cross; triggers COVERIGN } cross_implicit: cross cp_a, var_x; + // Non-standard hierarchical/dotted cross item: can only be a data reference + // (implicit coverpoint), never a coverpoint. Accepted with a NONSTD warning; + // implicit coverpoints are unsupported so the cross is dropped (COVERIGN). + cross_hier: cross cp_a, s_cfg.m_p; endgroup + typedef struct packed {logic m_p; logic h_mode;} cfg_t; + cfg_t s_cfg = '0; logic var_x = 1'b0; cg cg_i = new; initial begin diff --git a/test_regress/t/t_covergroup_default_bins.out b/test_regress/t/t_covergroup_default_bins.out index 9e4e7a1c6..e034545b8 100644 --- a/test_regress/t/t_covergroup_default_bins.out +++ b/test_regress/t/t_covergroup_default_bins.out @@ -1,11 +1,11 @@ cg.data.high: 1 cg.data.low: 1 -cg.data.other: 2 -cg2.cp_only_default.all: 4 +cg.data.other [default]: 2 +cg2.cp_only_default.all [default]: 4 cg3.data.bad [ignore]: 1 cg3.data.err [illegal]: 0 cg3.data.normal: 2 -cg3.data.other: 2 +cg3.data.other [default]: 2 cg4.cp_idx.auto_0: 1 cg4.cp_idx.auto_1: 1 cg4.cp_idx.auto_2: 1 diff --git a/test_regress/t/t_covergroup_default_bins.v b/test_regress/t/t_covergroup_default_bins.v index f4ba5148c..c97cf8f66 100644 --- a/test_regress/t/t_covergroup_default_bins.v +++ b/test_regress/t/t_covergroup_default_bins.v @@ -157,17 +157,17 @@ module t; // Sample cg3: verify ignore/illegal bins do not contribute to coverage data = 2; cg3_inst.sample(); // hits normal bin - `checkr(cg3_inst.get_inst_coverage(), 50.0); // 1/2 bins hit (normal) + `checkr(cg3_inst.get_inst_coverage(), 100.0); // 1/1: only 'normal' counts (default/ignore/illegal excluded, LRM 19.5) data = 7; cg3_inst.sample(); // hits normal bin again - `checkr(cg3_inst.get_inst_coverage(), 50.0); // no new bins + `checkr(cg3_inst.get_inst_coverage(), 100.0); // no new normal bins data = 255; - cg3_inst.sample(); // ignore_bins value; Verilator counts it toward default bin - `checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2: Verilator hits 'other' (default) even for ignore_bins + cg3_inst.sample(); // ignore_bins value; recorded but excluded from coverage + `checkr(cg3_inst.get_inst_coverage(), 100.0); // note: do not sample 254 (illegal_bins would cause runtime assertion) data = 100; - cg3_inst.sample(); // hits default (other) bin - `checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2 bins hit + cg3_inst.sample(); // hits default (other) bin; recorded but excluded from coverage + `checkr(cg3_inst.get_inst_coverage(), 100.0); // Sample cg4: auto-bins with one excluded value // idx=2 is in ignore_bins, so auto-bins cover 0, 1, 3 only (3 bins total) diff --git a/test_regress/t/t_covergroup_iff.out b/test_regress/t/t_covergroup_iff.out index 0e28a68cf..69a7711dd 100644 --- a/test_regress/t/t_covergroup_iff.out +++ b/test_regress/t/t_covergroup_iff.out @@ -2,9 +2,11 @@ cg_and.cp_concat.b00: 0 cg_and.cp_concat.b01: 1 cg_and.cp_concat.b10: 0 cg_and.cp_concat.b11: 0 -cg_array_iff.cp.arr: 1 +cg_array_iff.cp.arr[0]: 1 +cg_array_iff.cp.arr[1]: 0 +cg_array_iff.cp.arr[2]: 0 cg_bitw.cp.seven: 1 -cg_default_iff.cp.def: 1 +cg_default_iff.cp.def [default]: 1 cg_default_iff.cp.known: 1 cg_iff.cp_value.disabled_hi: 0 cg_iff.cp_value.disabled_lo: 0 diff --git a/test_regress/t/t_covergroup_iff.v b/test_regress/t/t_covergroup_iff.v index 1816bd327..8bf6e0016 100644 --- a/test_regress/t/t_covergroup_iff.v +++ b/test_regress/t/t_covergroup_iff.v @@ -129,7 +129,7 @@ module t; enable = 1; value = 10; cg2.sample(); // hits 'known' - `checkr(cg2.get_inst_coverage(), 50.0); + `checkr(cg2.get_inst_coverage(), 100.0); // 1/1: only 'known' counts (default excluded, LRM 19.5) value = 99; cg2.sample(); // hits 'def' (default) `checkr(cg2.get_inst_coverage(), 100.0); diff --git a/test_regress/t/t_covergroup_ignore_bins.out b/test_regress/t/t_covergroup_ignore_bins.out index bcd0b72e0..6a1cba188 100644 --- a/test_regress/t/t_covergroup_ignore_bins.out +++ b/test_regress/t/t_covergroup_ignore_bins.out @@ -1,5 +1,7 @@ -cg.data.arr [ignore]: 0 -cg.data.bad [illegal]: 0 +cg.data.arr[0] [ignore]: 0 +cg.data.arr[1] [ignore]: 0 +cg.data.bad[0] [illegal]: 0 +cg.data.bad[1] [illegal]: 0 cg.data.catch_all [ignore]: 0 cg.data.high: 1 cg.data.low: 1 diff --git a/test_regress/t/t_covergroup_illegal_bins.out b/test_regress/t/t_covergroup_illegal_bins.out index d2de75a65..798247e10 100644 --- a/test_regress/t/t_covergroup_illegal_bins.out +++ b/test_regress/t/t_covergroup_illegal_bins.out @@ -2,7 +2,9 @@ cg.data.forbidden [illegal]: 0 cg.data.high: 1 cg.data.low: 1 cg.data.mid: 1 -cg2.cp_arr.bad_arr [illegal]: 0 +cg2.cp_arr.bad_arr[0] [illegal]: 0 +cg2.cp_arr.bad_arr[1] [illegal]: 0 +cg2.cp_arr.bad_arr[2] [illegal]: 0 cg2.cp_arr.ok: 1 cg2.cp_arr.wlib [illegal]: 0 cg2.cp_trans.bad_2step [illegal]: 0 diff --git a/test_regress/t/t_covergroup_option_no_coverage.py b/test_regress/t/t_covergroup_option_no_coverage.py new file mode 100755 index 000000000..5f3bb6c6d --- /dev/null +++ b/test_regress/t/t_covergroup_option_no_coverage.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = 't/t_covergroup_option.v' + +# runs without --coverage +test.compile(verilator_flags2=['--Wno-COVERIGN']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_param_bins.v b/test_regress/t/t_covergroup_param_bins.v index ab57a4a62..ece6521cb 100644 --- a/test_regress/t/t_covergroup_param_bins.v +++ b/test_regress/t/t_covergroup_param_bins.v @@ -26,10 +26,10 @@ module t #( covergroup cg; cp: coverpoint value { bins negative = {[PMIN : -1]}; // parameter as range lower bound - bins zero = {0}; + bins zero = {0}; bins positive = {[1 : LMAX]}; // localparam as range upper bound - bins maxv = {LMAX}; // localparam as single value - bins minv = {PMIN}; // parameter as single value + bins maxv = {LMAX}; // localparam as single value + bins minv = {PMIN}; // parameter as single value } endgroup diff --git a/test_regress/t/t_covergroup_trans.out b/test_regress/t/t_covergroup_trans.out index f7621e74a..17e059ede 100644 --- a/test_regress/t/t_covergroup_trans.out +++ b/test_regress/t/t_covergroup_trans.out @@ -7,3 +7,5 @@ cg.cp_trans2.trans2: 1 cg.cp_trans2.trans3: 1 cg.cp_trans3.seq_a: 1 cg.cp_trans3.seq_b: 1 +cg_legacy.cp_mix.tr: 1 +cg_legacy.cp_mix.vals: 1 diff --git a/test_regress/t/t_covergroup_trans.v b/test_regress/t/t_covergroup_trans.v index dea93e6c5..f95561ed2 100644 --- a/test_regress/t/t_covergroup_trans.v +++ b/test_regress/t/t_covergroup_trans.v @@ -39,7 +39,17 @@ module t; } endgroup + // Non-convertible coverpoint (it has a transition bin) that also carries a value-array bin, + // so the legacy array codegen path (which converted coverpoints bypass) is still exercised. + covergroup cg_legacy; + cp_mix: coverpoint state { + bins tr = (0 => 1); + bins vals[] = {2, [4:5]}; // discrete + range elements (both legacy array paths) + } + endgroup + cg cg_inst = new; + cg_legacy cg_legacy_inst = new; initial begin // Drive sequence 0->1->2->3->4 which hits all bins @@ -56,6 +66,12 @@ module t; cg_inst.sample(); // 3=>4: seq_b done `checkr(cg_inst.get_inst_coverage(), 100.0); + // cg_legacy: exercise legacy array codegen (non-convertible coverpoint) + state = 0; cg_legacy_inst.sample(); + state = 1; cg_legacy_inst.sample(); // 0=>1: tr + state = 2; cg_legacy_inst.sample(); // vals + state = 3; cg_legacy_inst.sample(); // vals + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_cuse_struct_pkg.py b/test_regress/t/t_cuse_struct_pkg.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_cuse_struct_pkg.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_cuse_struct_pkg.v b/test_regress/t/t_cuse_struct_pkg.v new file mode 100644 index 000000000..2ff97c8b2 --- /dev/null +++ b/test_regress/t/t_cuse_struct_pkg.v @@ -0,0 +1,72 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +package pkg; + typedef struct {logic [7:0] value;} field_t; + typedef struct { + field_t f0; + field_t f1; + } reg_t; + typedef struct { + reg_t R0; + reg_t R1; + reg_t R2; + } hwif_t; +endpackage + +module t ( + input clk +); + + import pkg::*; + + hwif_t hwif_in; + hwif_t hwif_out; + hwif_t storage; + + integer cyc = 0; + + always_ff @(posedge clk) begin + storage.R0.f0.value <= hwif_in.R0.f0.value; + storage.R0.f1.value <= hwif_in.R0.f1.value; + storage.R1.f0.value <= hwif_in.R1.f0.value; + storage.R1.f1.value <= hwif_in.R1.f1.value; + storage.R2.f0.value <= hwif_in.R2.f0.value; + storage.R2.f1.value <= hwif_in.R2.f1.value; + end + + always_comb begin + hwif_out.R0.f0.value = storage.R0.f0.value; + hwif_out.R0.f1.value = storage.R0.f1.value; + hwif_out.R1.f0.value = storage.R1.f0.value; + hwif_out.R1.f1.value = storage.R1.f1.value; + hwif_out.R2.f0.value = storage.R2.f0.value; + hwif_out.R2.f1.value = storage.R2.f1.value; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 0) begin + hwif_in.R0.f0.value <= 8'h11; + hwif_in.R0.f1.value <= 8'h22; + hwif_in.R1.f0.value <= 8'h33; + hwif_in.R1.f1.value <= 8'h44; + hwif_in.R2.f0.value <= 8'h55; + hwif_in.R2.f1.value <= 8'h66; + end + else if (cyc == 3) begin + if (hwif_out.R0.f0.value !== 8'h11) $stop; + if (hwif_out.R0.f1.value !== 8'h22) $stop; + if (hwif_out.R1.f0.value !== 8'h33) $stop; + if (hwif_out.R1.f1.value !== 8'h44) $stop; + if (hwif_out.R2.f0.value !== 8'h55) $stop; + if (hwif_out.R2.f1.value !== 8'h66) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index b65ab991e..5fcf5c7d7 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -364,6 +364,16 @@ module Vt_debug_emitv_t; release sum; end end + sequence s_clocked; + @(posedge clk) in + endsequence + begin : assert_seq_clocked + assert property ( s_clocked() + ) begin + end + else begin + end + end property p; @(posedge clk) ##1 sum[0] endproperty @@ -651,6 +661,11 @@ module Vt_debug_emitv_t; $display("pass"); end end + begin : cover_sequence_concurrent + cover property (@(posedge clk) in##1 in + ) begin + end + end begin : assert_prop_always assert property (@(posedge clk) always ['sh0: 'sh3] in @@ -1303,6 +1318,21 @@ module Vt_debug_emitv_sub; endfunction real r; endmodule +module Vt_debug_emitv_seq_event; + input logic clk; + bit a; + bit b; + bit c; + sequence sq; + @(posedge clk) a##1 b##1 c + endsequence + initial begin + begin + + ???? // EVENTCONTROL + @( sq())end + end +endmodule package Vt_debug_emitv_p; logic pkgvar; endpackage diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index edc1ec87b..7e474c305 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -138,6 +138,7 @@ module t (/*AUTOARG*/ endfunction sub sub(.*); + seq_event seq_event(.*); initial begin int other; @@ -286,6 +287,14 @@ module t (/*AUTOARG*/ release sum; end + // verilog_format: off // verible does not support clocking events inside sequence declarations + sequence s_clocked; + @(posedge clk) in + endsequence + // verilog_format: on + + assert_seq_clocked: assert property (s_clocked); + property p; @(posedge clk) ##1 sum[0] endproperty @@ -343,6 +352,8 @@ module t (/*AUTOARG*/ cover_concurrent: cover property(prop); cover_concurrent_stmt: cover property(prop) $display("pass"); + cover_sequence_concurrent: cover sequence (@(posedge clk) in ##1 in); + assert_prop_always: assert property (@(posedge clk) always [0:3] in); assert_prop_s_always: assert property (@(posedge clk) s_always [1:2] in); assert_prop_overlap_impl: assert property (@(posedge clk) in |-> in); @@ -433,6 +444,16 @@ module sub(input logic clk); real r; endmodule +module seq_event(input logic clk); + bit a, b, c; + sequence sq; + @(posedge clk) a ##1 b ##1 c; + endsequence + initial begin + @sq; + end +endmodule + package p; logic pkgvar; endpackage diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.out b/test_regress/t/t_default_disable_iff_gen_multi_bad.out new file mode 100644 index 000000000..ed0df293e --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_default_disable_iff_gen_multi_bad.v:15:7: Only one 'default disable iff' allowed per generate block (IEEE 1800-2023 16.15) + : ... note: In instance 't' + 15 | default disable iff (cyc < 7); + | ^~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.py b/test_regress/t/t_default_disable_iff_gen_multi_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.v b/test_regress/t/t_default_disable_iff_gen_multi_bad.v new file mode 100644 index 000000000..61e69830c --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t(input clk); + int cyc; + + default disable iff (cyc < 3); + + generate + begin : g + default disable iff (cyc < 5); + default disable iff (cyc < 7); + + assert property (@(posedge clk) 0); + end + endgenerate +endmodule diff --git a/test_regress/t/t_default_disable_iff_scope.py b/test_regress/t/t_default_disable_iff_scope.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_default_disable_iff_scope.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_default_disable_iff_scope.v b/test_regress/t/t_default_disable_iff_scope.v new file mode 100644 index 000000000..6cdabba82 --- /dev/null +++ b/test_regress/t/t_default_disable_iff_scope.v @@ -0,0 +1,230 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + bit clk; + int cyc; + + always #1 clk = !clk; + + bit rst_mod; + int before_mod_false; + int before_mod_gen_false; + int before_gen_default_false; + int before_gen_child_default_false; + int prog_false_count; + + always_comb rst_mod = (cyc < 3); + + // The default declaration appears later in the module, but still applies here. + assert property (@(posedge clk) 0) + else before_mod_false++; + + generate + begin : g_before_mod_default + // Inherits the module default declaration that appears later in this module. + assert property (@(posedge clk) 0) + else before_mod_gen_false++; + end + + begin : g_before_local_default + // The generate-local default declaration appears later in the block. + assert property (@(posedge clk) 0) + else before_gen_default_false++; + + begin : g_child_inherit + // Inherits the generate-local default declaration that appears later. + assert property (@(posedge clk) 0) + else before_gen_child_default_false++; + end + default disable iff (cyc < 7); + end + endgenerate + + default disable iff (rst_mod); + + generate + begin : g_override + bit rst_gen; + int override_false; + + always_comb rst_gen = (cyc < 5); + + default disable iff (rst_gen); + + assert property (@(posedge clk) 0) + else override_false++; + end + + begin : g_inherit + bit rst_mod; + int inherit_false; + + always_comb rst_mod = (cyc < 8); + + // Inherits the module default, whose rst_mod was resolved in module scope. + assert property (@(posedge clk) 0) + else inherit_false++; + end + endgenerate + + if_scope u_if (.clk(clk), .cyc(cyc)); + p_scope u_prog (.clk(clk), .cyc(cyc), .false_count(prog_false_count)); + examples_with_default_count u_with (.clk(clk), .cyc(cyc)); + examples_without_default_count u_without (.clk(clk), .cyc(cyc)); + examples_with_default u_ieee_with (.a(1'b0), .b(1'b0), .clk(clk), .rst(1'b0), .rst1(1'b0)); + examples_without_default u_ieee_without (.a(1'b0), .b(1'b0), .clk(clk), .rst(1'b0)); + m_override u_m_override (.clk(clk), .cyc(cyc)); + + // The disable iff expression is unsampled, so same-edge updates race in MT simulation. + // Change clk on negedge while the properties are sampled on posedge to avoid races. + always @(negedge clk) begin + cyc++; + if (cyc == 12) begin + `checkd(before_mod_false, 9); + `checkd(before_mod_gen_false, 9); + `checkd(before_gen_default_false, 5); + `checkd(before_gen_child_default_false, 5); + `checkd(g_inherit.inherit_false, 9); + `checkd(g_override.override_false, 7); + `checkd(u_if.false_count, 6); + `checkd(u_if.g_inherit.false_count, 6); + `checkd(prog_false_count, 4); + `checkd(u_with.explicit_assert_false, 5); + `checkd(u_with.explicit_property_false, 5); + `checkd(u_with.inferred_default_false, 9); + `checkd(u_without.explicit_assert_false, 9); + `checkd(u_without.explicit_property_false, 9); + `checkd(u_m_override.false_count, 7); + `checkd(u_m_override.g_inherit_from_module.false_count, 7); + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module examples_with_default (input logic a, b, clk, rst, rst1); +default disable iff rst; +property p1; +disable iff (rst1) a |=> b; +endproperty +// Disable condition is rst1 - explicitly specified within a1 +a1 : assert property (@(posedge clk) disable iff (rst1) a |=> b); +// Disable condition is rst1 - explicitly specified within p1 +a2 : assert property (@(posedge clk) p1); +// Disable condition is rst - no explicit specification, inferred from +// default disable iff declaration +a3 : assert property (@(posedge clk) a |=> b); +// Disable condition is 1'b0. This is the only way to +// cancel the effect of default disable. +a4 : assert property (@(posedge clk) disable iff (1'b0) a |=> b); +endmodule + +module examples_without_default (input logic a, b, clk, rst); +property p2; +disable iff (rst) a |=> b; +endproperty +// Disable condition is rst - explicitly specified within a5 +a5 : assert property (@(posedge clk) disable iff (rst) a |=> b); +// Disable condition is rst - explicitly specified within p2 +a6 : assert property (@ (posedge clk) p2); +// No disable condition +a7 : assert property (@ (posedge clk) a |=> b); +endmodule + +module examples_with_default_count(input bit clk, input int cyc); + int explicit_assert_false; + int explicit_property_false; + int inferred_default_false; + + default disable iff (cyc < 3); + + property p1; + disable iff (cyc < 7) 0; + endproperty + + // Disable condition is explicit in the assertion. + assert property (@(posedge clk) disable iff (cyc < 7) 0) + else explicit_assert_false++; + + // Disable condition is explicit in the property. + assert property (@(posedge clk) p1) + else explicit_property_false++; + + // Disable condition is inferred from the default. + assert property (@(posedge clk) 0) + else inferred_default_false++; + +endmodule + +module examples_without_default_count(input bit clk, input int cyc); + int explicit_assert_false; + int explicit_property_false; + + property p2; + disable iff (cyc < 3) 0; + endproperty + + // Disable condition is explicit in the assertion. + assert property (@(posedge clk) disable iff (cyc < 3) 0) + else explicit_assert_false++; + + // Disable condition is explicit in the property. + assert property (@(posedge clk) p2) + else explicit_property_false++; + +endmodule + +module m_override(input bit clk, input int cyc); + bit rst2; + int false_count; + + always_comb rst2 = (cyc < 5); + default disable iff (rst2); + + assert property (@(posedge clk) 0) + else false_count++; + + generate + begin : g_inherit_from_module + int false_count; + assert property (@(posedge clk) 0) + else false_count++; + end + endgenerate +endmodule + +interface if_scope(input bit clk, input int cyc); + bit rst_if; + int false_count; + + always_comb rst_if = (cyc < 6); + default disable iff (rst_if); + + assert property (@(posedge clk) 0) + else false_count++; + + generate + begin : g_inherit + int false_count; + assert property (@(posedge clk) 0) + else false_count++; + end + endgenerate +endinterface + +program p_scope(input bit clk, input int cyc, + output int false_count); + default disable iff (cyc < 8); + + assert property (@(posedge clk) 0) + else false_count++; +endprogram diff --git a/test_regress/t/t_dfg_break_cycles.py b/test_regress/t/t_dfg_break_cycles.py index 2c35f08f5..217863f38 100755 --- a/test_regress/t/t_dfg_break_cycles.py +++ b/test_regress/t/t_dfg_break_cycles.py @@ -17,6 +17,8 @@ test.sim_time = 2000000 if not os.path.exists(test.root + "/.git"): test.skip("Not in a git repository") +test.top_filename = "t/t_dfg_break_cycles.v" + # Read expected source lines hit expectedLines = set() @@ -65,7 +67,8 @@ with open(rdFile, 'r', encoding="utf8") as rdFh, \ test.compile(verilator_flags2=[ "--stats", "--build", - "-fno-dfg-break-cycles", + "-fno-dfg" if test.name == "t_dfg_break_cycles" else "-fno-dfg-break-cycles", + "-fno-gate", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_ref", "--prefix", "Vref", @@ -73,8 +76,9 @@ test.compile(verilator_flags2=[ ]) # yapf:disable # Check we got the expected number of circular logic warnings -test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt", - r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles) +if test.name == "t_dfg_break_cycles": + test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt", + r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles) # Compile optimized - also builds executable test.compile(verilator_flags2=[ @@ -82,15 +86,17 @@ test.compile(verilator_flags2=[ "--build", "--exe", "-fno-const-before-dfg", + "-fno-gate", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_opt", "--prefix", "Vopt", "-Werror-UNOPTFLAT", "--dumpi-V3DfgBreakCycles", "9", # To fill code coverage + "--debugi-V3DfgBreakCycles", "9", # To fill code coverage "--debug", "--debugi", "0", "--dumpi-tree", "0", "-CFLAGS \"-I .. -I ../obj_ref\"", "../obj_ref/Vref__ALL.a", - "../../t/" + test.name + ".cpp" + "../../t/t_dfg_break_cycles.cpp" ]) # yapf:disable # Execute test to check equivalence diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index 5de4c628b..6114336fe 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -124,6 +124,9 @@ module t ( assign SHIFTRS_VARIABLE_4_B = signed'({4'b1111, SHIFTRS_VARIABLE_4_A[1]}) >>> rand_b[0]; assign SHIFTRS_VARIABLE_4_A = rand_a[3:0] ^ SHIFTRS_VARIABLE_4_B[4:1]; + `signal(SHIFTRS_VARIABLE_5, 2); // UNOPTFLAT + assign SHIFTRS_VARIABLE_5 = signed'({rand_a[0], SHIFTRS_VARIABLE_5[1]}) >>> rand_b[0]; + `signal(SHIFTR, 14); // UNOPTFLAT assign SHIFTR = { SHIFTR[6:5], // 13:12 @@ -201,6 +204,16 @@ module t ( assign REPLICATE_4_B = {2{REPLICATE_4_A[1:0]}}; assign REPLICATE_4_A = {REPLICATE_4_B[2:1], rand_a[1:0]}; + function automatic logic [1:0] span_repl(input logic [1:0] x); + logic [3:0] r; + r = {2{x}}; + return r[2:1]; + endfunction + wire logic [3:0] replicate_5_int; // UNOPTFLAT + assign replicate_5_int = {span_repl(replicate_5_int[1:0]), rand_a[1:0]}; + `signal(REPLICATE_5, 4); + assign REPLICATE_5 = replicate_5_int; + `signal(PARTIAL, 4); // UNOPTFLAT assign PARTIAL[0] = rand_a[0]; // PARTIAL[1] intentionally unconnected @@ -219,14 +232,14 @@ module t ( `signal(ARRAY_1, 3); // UNOPTFLAT assign ARRAY_1 = array_1[0]; - wire [2:0] array_2a [2]; - wire [2:0] array_2b [2]; + wire [2:0] array_2a [2]; // UNOPTFLAT + wire [2:0] array_2b [2]; // UNOPTFLAT assign array_2a[0][0] = rand_a[0]; assign array_2a[0][1] = array_2b[1][0]; assign array_2a[0][2] = array_2b[1][1]; assign array_2a[1] = array_2a[0]; assign array_2b = array_2a; - `signal(ARRAY_2, 3); // UNOPTFLAT + `signal(ARRAY_2, 3); assign ARRAY_2 = array_2a[0]; wire [2:0] array_3 [2]; @@ -348,6 +361,19 @@ module t ( assign ALWAYS_2 = always_2; // verilator lint_on ALWCOMBORDER + // verilator lint_off ALWCOMBORDER + logic [4:0] always_3; + always_comb begin + always_3[4] = always_3[0]; + always_3[0] = rand_a[0]; + always_3[2] = rand_a[2]; + always_3[3] = |always_3[2:1]; + end + assign always_3[1] = always_3[0]; + `signal(ALWAYS_3, 5); // UNOPTFLAT + assign ALWAYS_3 = always_3; + // verilator lint_on ALWCOMBORDER + logic [31:0] array_4[3]; // UNOPTFLAT // Input assign array_4[0] = rand_a[31:0]; @@ -436,4 +462,86 @@ module t ( assign VOLATILE_ARRAY_IN_CYCLE_1 = volatile_array_in_cycle_1a[1]; // verilator lint_on + ////////////////////////////////////////////////////////////////////////// + // Match masked + ////////////////////////////////////////////////////////////////////////// + + logic [63:0] match_masked; // UNOPTFLAT + always_comb begin + casez (rand_a[31:0]) + 32'b????????_????????_????????_???????1 : match_masked[31:0] = 32'd00; + 32'b????????_????????_????????_??????1? : match_masked[31:0] = 32'd01; + 32'b????????_????????_????????_?????1?? : match_masked[31:0] = 32'd02; + 32'b????????_????????_????????_????1??? : match_masked[31:0] = 32'd03; + 32'b????????_????????_????????_???1???? : match_masked[31:0] = 32'd04; + 32'b????????_????????_????????_??1????? : match_masked[31:0] = 32'd05; + 32'b????????_????????_????????_?1?????? : match_masked[31:0] = 32'd06; + 32'b????????_????????_????????_1??????? : match_masked[31:0] = 32'd07; + 32'b????????_????????_???????1_???????? : match_masked[31:0] = 32'd08; + 32'b????????_????????_??????1?_???????? : match_masked[31:0] = 32'd09; + 32'b????????_????????_?????1??_???????? : match_masked[31:0] = 32'd10; + 32'b????????_????????_????1???_???????? : match_masked[31:0] = 32'd11; + 32'b????????_????????_???1????_???????? : match_masked[31:0] = 32'd12; + 32'b????????_????????_??1?????_???????? : match_masked[31:0] = 32'd13; + 32'b????????_????????_?1??????_???????? : match_masked[31:0] = 32'd14; + 32'b????????_????????_1???????_???????? : match_masked[31:0] = 32'd15; + 32'b????????_???????1_????????_???????? : match_masked[31:0] = 32'd16; + 32'b????????_??????1?_????????_???????? : match_masked[31:0] = 32'd17; + 32'b????????_?????1??_????????_???????? : match_masked[31:0] = 32'd18; + 32'b????????_????1???_????????_???????? : match_masked[31:0] = 32'd19; + 32'b????????_???1????_????????_???????? : match_masked[31:0] = 32'd20; + 32'b????????_??1?????_????????_???????? : match_masked[31:0] = 32'd21; + 32'b????????_?1??????_????????_???????? : match_masked[31:0] = 32'd22; + 32'b????????_1???????_????????_???????? : match_masked[31:0] = 32'd23; + 32'b???????1_????????_????????_???????? : match_masked[31:0] = 32'd24; + 32'b??????1?_????????_????????_???????? : match_masked[31:0] = 32'd25; + 32'b?????1??_????????_????????_???????? : match_masked[31:0] = 32'd26; + 32'b????1???_????????_????????_???????? : match_masked[31:0] = 32'd27; + 32'b???1????_????????_????????_???????? : match_masked[31:0] = 32'd28; + 32'b??1?????_????????_????????_???????? : match_masked[31:0] = 32'd29; + 32'b?1??????_????????_????????_???????? : match_masked[31:0] = 32'd30; + 32'b1???????_????????_????????_???????? : match_masked[31:0] = 32'd31; + default : match_masked[31:0] = '1; + endcase + end + always_comb begin + casez (match_masked[31:0]) + 32'd00 : match_masked[63:32] = 32'b00000000_00000000_00000000_00000001; + 32'd01 : match_masked[63:32] = 32'b00000000_00000000_00000000_00000010; + 32'd02 : match_masked[63:32] = 32'b00000000_00000000_00000000_00000100; + 32'd03 : match_masked[63:32] = 32'b00000000_00000000_00000000_00001000; + 32'd04 : match_masked[63:32] = 32'b00000000_00000000_00000000_00010000; + 32'd05 : match_masked[63:32] = 32'b00000000_00000000_00000000_00100000; + 32'd06 : match_masked[63:32] = 32'b00000000_00000000_00000000_01000000; + 32'd07 : match_masked[63:32] = 32'b00000000_00000000_00000000_10000000; + 32'd08 : match_masked[63:32] = 32'b00000000_00000000_00000001_00000000; + 32'd09 : match_masked[63:32] = 32'b00000000_00000000_00000010_00000000; + 32'd10 : match_masked[63:32] = 32'b00000000_00000000_00000100_00000000; + 32'd11 : match_masked[63:32] = 32'b00000000_00000000_00001000_00000000; + 32'd12 : match_masked[63:32] = 32'b00000000_00000000_00010000_00000000; + 32'd13 : match_masked[63:32] = 32'b00000000_00000000_00100000_00000000; + 32'd14 : match_masked[63:32] = 32'b00000000_00000000_01000000_00000000; + 32'd15 : match_masked[63:32] = 32'b00000000_00000000_10000000_00000000; + 32'd16 : match_masked[63:32] = 32'b00000000_00000001_00000000_00000000; + 32'd17 : match_masked[63:32] = 32'b00000000_00000010_00000000_00000000; + 32'd18 : match_masked[63:32] = 32'b00000000_00000100_00000000_00000000; + 32'd19 : match_masked[63:32] = 32'b00000000_00001000_00000000_00000000; + 32'd20 : match_masked[63:32] = 32'b00000000_00010000_00000000_00000000; + 32'd21 : match_masked[63:32] = 32'b00000000_00100000_00000000_00000000; + 32'd22 : match_masked[63:32] = 32'b00000000_01000000_00000000_00000000; + 32'd23 : match_masked[63:32] = 32'b00000000_10000000_00000000_00000000; + 32'd24 : match_masked[63:32] = 32'b00000001_00000000_00000000_00000000; + 32'd25 : match_masked[63:32] = 32'b00000010_00000000_00000000_00000000; + 32'd26 : match_masked[63:32] = 32'b00000100_00000000_00000000_00000000; + 32'd27 : match_masked[63:32] = 32'b00001000_00000000_00000000_00000000; + 32'd28 : match_masked[63:32] = 32'b00010000_00000000_00000000_00000000; + 32'd29 : match_masked[63:32] = 32'b00100000_00000000_00000000_00000000; + 32'd30 : match_masked[63:32] = 32'b01000000_00000000_00000000_00000000; + 32'd31 : match_masked[63:32] = 32'b10000000_00000000_00000000_00000000; + default: match_masked[63:32] = 32'b00000000_00000000_00000000_00000000; + endcase + end + `signal(MATCH_MASKED, 64); + assign MATCH_MASKED = match_masked; + endmodule diff --git a/test_regress/t/t_dfg_break_cycles_off.py b/test_regress/t/t_dfg_break_cycles_off.py new file mode 100755 index 000000000..bf6af753c --- /dev/null +++ b/test_regress/t/t_dfg_break_cycles_off.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +import runpy + +test.scenarios('vlt_all') + +runpy.run_path("t/t_dfg_break_cycles.py", globals()) diff --git a/test_regress/t/t_dfg_constpool_unused.py b/test_regress/t/t_dfg_constpool_unused.py new file mode 100755 index 000000000..d8194a13e --- /dev/null +++ b/test_regress/t/t_dfg_constpool_unused.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, DFG, Peephole, remove var\s+(\d+)', 2) +test.file_grep_not(test.stats, r'ConstPool, Constants emitted') # Removed by V3Dead later + +test.passes() diff --git a/test_regress/t/t_dfg_constpool_unused.v b/test_regress/t/t_dfg_constpool_unused.v new file mode 100644 index 000000000..339346ffa --- /dev/null +++ b/test_regress/t/t_dfg_constpool_unused.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Converted to case table in const pool, but proven unused by Dfg + logic [15:0] out; + always_comb begin + case (cyc[3:0]) + 4'd0: out = 16'h1111; + 4'd1: out = 16'h2222; + 4'd2: out = 16'h4444; + 4'd3: out = 16'h8888; + default: out = 16'h0f0f; + endcase + end + + // Complicated way to write constant 0 that only Dfg can decipher + wire [63:0] convoluted_zero = (({64{cyc[0]}} & ~{64{cyc[0]}})); + + wire logic [15:0] zero = &convoluted_zero ? out : 16'd0; + + // Test driver/checker + always @(posedge clk) begin + `checkh(zero, 16'd0); + cyc <= cyc + 32'd1; + if (cyc == 32'd32) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_dfg_oob_array_access.py b/test_regress/t/t_dfg_oob_array_access.py new file mode 100755 index 000000000..b7bc5e342 --- /dev/null +++ b/test_regress/t/t_dfg_oob_array_access.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["-runtime-debug"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_dfg_oob_array_access.v b/test_regress/t/t_dfg_oob_array_access.v new file mode 100644 index 000000000..39b3f4d17 --- /dev/null +++ b/test_regress/t/t_dfg_oob_array_access.v @@ -0,0 +1,27 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + + +module t ( + input clk +); + wire mem_wire; + bit [15:0] idx = 65535; + bit mem_reg[0:34000]; + assign mem_wire = mem_reg[idx]; + + always @(posedge clk) begin + if (idx < 65533) begin + $display("oob_val %d", mem_wire); + $write("*-* All Finished *-*\n"); + $finish; + end + else begin + idx <= idx - 1; + mem_reg[idx] <= 0; + end + end +endmodule diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 3c9ce7ae8..3e393a297 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -50,6 +50,9 @@ module t ( assign unitArrayParts[0][1] = rand_a[1]; assign unitArrayParts[0][9] = rand_a[9]; + // Complicated way to write constant 0 that only Dfg can decipher + wire [63:0] convoluted_zero = (({64{rand_a[0]}} & ~{64{rand_a[0]}})); + `signal(FOLD_UNARY_LogNot, !const_a[0]); `signal(FOLD_UNARY_Negate, -const_a); `signal(FOLD_UNARY_Not, ~const_a); @@ -133,6 +136,13 @@ module t ( `signal(FOLD_SEL, const_a[3:1]); + int fold_arraysel_table; + ffs ffs_a(convoluted_zero[0] ? 20'hff: 20'd2, fold_arraysel_table); + int fold_matchmasked; + ffs ffs_b(convoluted_zero[1] ? 20'hff: 20'd7, fold_matchmasked); + `signal(FOLD_ARRAYSEL_TABLE, fold_arraysel_table); + `signal(FOLD_MATCHMASKED, fold_matchmasked); + `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, rand_a + ~rand_a); `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rand_b + rand_a); @@ -283,10 +293,20 @@ module t ( `signal(REPLACE_COND_CONST_ZERO_ONAE, rand_a[0] ? 80'b0 : -80'b1); `signal(REPLACE_COND_CAT_LHS_CONST_ONE_ZERO, rand_a[0] ? {8'b1, rand_b[0]} : {8'b0, rand_b[1]}); `signal(REPLACE_COND_CAT_LHS_CONST_ZERO_ONE, rand_a[0] ? {8'b0, rand_b[0]} : {8'b1, rand_b[1]}); - `signal(REPLACE_COND_SAME_CAT_LHS, rand_a[0] ? {8'd0, rand_b[0]} : {8'd0, rand_b[1]}); - `signal(REPLACE_COND_SAME_CAT_RHS, rand_a[0] ? {rand_b[0], 8'd0} : {rand_b[1], 8'd0}); `signal(REPLACE_COND_SAM_COND_THEN, rand_a[0] ? (rand_a[0] ? rand_b[1:0] : rand_b[3:2]) : rand_b[5:4]); `signal(REPLACE_COND_SAM_COND_ELSE, rand_a[0] ? rand_b[1:0] : (rand_a[0] ? rand_b[3:2] : rand_b[5:4])); + + `signal(REPLACE_COND_COMMON_MSBS_A, rand_a[0] ? {8'd0, rand_b[0]} : {8'd0, rand_b[1]}); + `signal(REPLACE_COND_COMMON_MSBS_B, rand_a[0] ? {8'hf0, rand_b[1:0]} : {9'h1e2, rand_b[1]}); + `signal(REPLACE_COND_COMMON_MSBS_C, rand_a[0] ? {rand_a[63 -: 3] , rand_b[0]} : {rand_a[63 -: 2], rand_b[2:1]}); + `signal(REPLACE_COND_COMMON_LSBS_A, rand_a[0] ? {rand_b[0], 8'd0} : {rand_b[1], 8'd0}); + `signal(REPLACE_COND_COMMON_LSBS_B, rand_a[0] ? {rand_b[2:1], 8'h0f} : {rand_b[1], 9'h08f}); + `signal(REPLACE_COND_COMMON_LSBS_C, rand_a[0] ? {rand_b[0], rand_a[3:0]} : {rand_b[1:0], rand_a[2:0]}); + wire [5:0] tmp_REPLACE_COND_COMMON_LSBS_D = rand_b[5:0]; + wire [5:0] tmp_REPLACE_COND_COMMON_MSBS_D = rand_b[63:58]; + `signal(REPLACE_COND_COMMON_LSBS_D, rand_a[0] ? rand_b[4:0] : tmp_REPLACE_COND_COMMON_LSBS_D[4:0]); + `signal(REPLACE_COND_COMMON_MSBS_D, rand_a[0] ? rand_b[63:59] : tmp_REPLACE_COND_COMMON_MSBS_D[5:1]); + `signal(REMOVE_SHIFTL_ZERO, rand_a << 0); `signal(REPLACE_SHIFTL_OVER, rand_a << 64); `signal(REPLACE_SHIFTL_SEL, rand_a[27:0] << 4); @@ -352,7 +372,6 @@ module t ( `signal(REMOVE_EQ_BIT_1, 1'b1 == rand_a[0]); `signal(REMOVE_NEQ_BIT_0, 1'b0 != rand_a[0]); `signal(REPLACE_NEQ_BIT_1, 1'b1 != rand_a[0]); - `signal(REPLACE_COND_INSERT, rand_a[0] ? {rand_b[63:40], {1'd0, rand_b[38:0]}} : rand_b); `signal(REPLACE_REP_REP, {2{({3{rand_a[0]}})}}); // Operators that should work wiht mismatched widths @@ -418,3 +437,35 @@ module t ( assign zero = '0; assign ones = '1; endmodule + +module ffs( + input logic [19:0] i, + output int o +); + // V3Table will convert this + always_comb begin + casez (i) + 20'b1???????????????????: o = 19; + 20'b?1??????????????????: o = 18; + 20'b??1?????????????????: o = 17; + 20'b???1????????????????: o = 16; + 20'b????1???????????????: o = 15; + 20'b?????1??????????????: o = 14; + 20'b??????1?????????????: o = 13; + 20'b???????1????????????: o = 12; + 20'b????????1???????????: o = 11; + 20'b?????????1??????????: o = 10; + 20'b??????????1?????????: o = 9; + 20'b???????????1????????: o = 8; + 20'b????????????1???????: o = 7; + 20'b?????????????1??????: o = 6; + 20'b??????????????1?????: o = 5; + 20'b???????????????1????: o = 4; + 20'b????????????????1???: o = 3; + 20'b?????????????????1??: o = 2; + 20'b??????????????????1?: o = 1; + 20'b???????????????????1: o = 0; + default: o = 32'hffffffff; + endcase + end +endmodule diff --git a/test_regress/t/t_disable_fork_nested.out b/test_regress/t/t_disable_fork_nested.out new file mode 100644 index 000000000..dce504813 --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.out @@ -0,0 +1,3 @@ +Fast clock (200ns half-period): o_counter=7 +Slow clock (5400ns half-period): o_counter=3 +*-* All Finished *-* diff --git a/test_regress/t/t_disable_fork_nested.py b/test_regress/t/t_disable_fork_nested.py new file mode 100755 index 000000000..b716266af --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary", "-Wno-ZERODLY"]) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_disable_fork_nested.v b/test_regress/t/t_disable_fork_nested.v new file mode 100644 index 000000000..de1b2526a --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.v @@ -0,0 +1,89 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Clock-period detector exercising a named-block `disable` of a block whose +// body contains a nested fork..join. Disabling such a block from a sibling +// process must release the enclosing fork..join so the surrounding `always` +// block keeps iterating. + +module disable_fork ( + input logic i_clk, + output logic [2:0] o_counter +); + time delay1 = 500ns; // min period + time delay2 = 3333ns; // max period + + logic clk_re = 1'b0; // rising edge of the clock + logic [2:0] counter = 3'b000; + + always begin + fork + begin : check1 + #delay1; + #1 disable check2; + fork + begin : check3 + #(delay2 - delay1); + clk_re <= 1'b0; + #1 disable check4; + if (counter < 3'b111) counter <= counter + 3'b001; + end + begin : check4 + @(posedge i_clk); + clk_re <= 1'b1; + counter <= 3'b000; + #1 disable check3; + end + join + end + begin : check2 + @(posedge i_clk); + clk_re <= 1'b0; + #1 disable check1; + if (counter < 3'b111) counter <= counter + 3'b001; + end + join + end + + assign o_counter = counter; +endmodule + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk; + logic [2:0] counter; + + task clk_cycle(input time half_period); + clk = 1'b1; + #half_period; + clk = 1'b0; + #half_period; + endtask : clk_cycle + + initial begin + // Fast clock (period below delay1): every edge arrives before the + // min-period timeout, so the counter saturates at its max. + repeat (100) clk_cycle(200ns); + $display("Fast clock (200ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h7); + // Slow clock (period above delay2): the nested fork path runs, which + // only works if disabling check1 releases the inner fork..join. + repeat (100) clk_cycle(5400ns); + $display("Slow clock (5400ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h3); + $write("*-* All Finished *-*\n"); + $finish; + end + + disable_fork a_inst ( + .i_clk(clk), + .o_counter(counter) + ); +endmodule diff --git a/test_regress/t/t_display_merge.py b/test_regress/t/t_display_merge.py index 999f03507..205550676 100755 --- a/test_regress/t/t_display_merge.py +++ b/test_regress/t/t_display_merge.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator_st') -test.compile(verilator_flags2=["--stats", "--inline-cfuncs", "0"]) +test.compile(verilator_flags2=["--stats", "-fno-inline-cfuncs"]) test.execute(expect_filename=test.golden_filename) diff --git a/test_regress/t/t_dpi_2exparg_bad.out b/test_regress/t/t_dpi_2exparg_bad.out index e908a6f57..812511578 100644 --- a/test_regress/t/t_dpi_2exparg_bad.out +++ b/test_regress/t/t_dpi_2exparg_bad.out @@ -10,12 +10,12 @@ | ^ ... For warning description see https://verilator.org/warn/WIDTHTRUNC?v=latest ... Use "/* verilator lint_off WIDTHTRUNC */" and lint_on around source to disable this message. -%Error: t/t_dpi_2exparg_bad.v:21:8: Duplicate declaration of DPI function with different signature: 'dpix_twice' - 21 | task dpix_twice(input int i, output [63:0] o); - | ^~~~~~~~~~ - : ... New signature: void dpix_twice (int, svLogicVecVal* /* logic[63:0] */ ) - t/t_dpi_2exparg_bad.v:12:8: ... Original signature: void dpix_twice (int, svLogicVecVal* /* logic[2:0] */ ) +%Error: t/t_dpi_2exparg_bad.v:12:8: Duplicate declaration of DPI function with different signature: 'dpix_twice' 12 | task dpix_twice(input int i, output [2:0] o); + | ^~~~~~~~~~ + : ... New signature: void dpix_twice (int, svLogicVecVal* /* logic[2:0] */ ) + t/t_dpi_2exparg_bad.v:21:8: ... Original signature: void dpix_twice (int, svLogicVecVal* /* logic[63:0] */ ) + 21 | task dpix_twice(input int i, output [63:0] o); | ^~~~~~~~~~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. %Error: Exiting due to diff --git a/test_regress/t/t_eq_wild.v b/test_regress/t/t_eq_wild.v index c34463bd1..100726f23 100644 --- a/test_regress/t/t_eq_wild.v +++ b/test_regress/t/t_eq_wild.v @@ -4,7 +4,16 @@ // SPDX-FileCopyrightText: 2026 Antmicro // SPDX-License-Identifier: CC0-1.0 +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + module t; + localparam string AB = "AB"; + + bit r; + initial begin if (('bxz10 ==? 'bxxx0) !== 1) $stop; if (('bxz10 ==? 'bxxx1) !== 0) $stop; @@ -12,6 +21,16 @@ module t; if (('bxz10 !=? 'bxxx1) !== 1) $stop; if (('bxz10 !=? 'bxxx0) !== 0) $stop; if (('bxz10 !=? 'b1xx0) !== 'x) $stop; + + r = (AB ==? "AA"); + `checkd(r, 0); + r = (AB ==? "AB"); + `checkd(r, 1); + r = (AB !=? "AB"); + `checkd(r, 0); + + // real/shortreal is illegal so not tested here + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_finaldly_bad.out b/test_regress/t/t_finaldly_bad.out new file mode 100644 index 000000000..01c50dacb --- /dev/null +++ b/test_regress/t/t_finaldly_bad.out @@ -0,0 +1,5 @@ +%Error-FINALDLY: t/t_finaldly_bad.v:9:13: Non-blocking assignment '<=' in final block + 9 | final foo <= 1; + | ^~ + ... For error description see https://verilator.org/warn/FINALDLY?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_finaldly_bad.py b/test_regress/t/t_finaldly_bad.py new file mode 100755 index 000000000..09d3c8e81 --- /dev/null +++ b/test_regress/t/t_finaldly_bad.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.compile(fails=True, expect_filename=test.golden_filename) + +test.extract(in_filename=test.top_filename, + out_filename=test.root + "/docs/gen/ex_FINALDLY_faulty.rst", + lines="8-9") + +test.extract(in_filename=test.golden_filename, + out_filename=test.root + "/docs/gen/ex_FINALDLY_msg.rst", + lines="1-3") + +test.passes() diff --git a/test_regress/t/t_finaldly_bad.v b/test_regress/t/t_finaldly_bad.v new file mode 100644 index 000000000..81b5223ff --- /dev/null +++ b/test_regress/t/t_finaldly_bad.v @@ -0,0 +1,10 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + bit foo; + final foo <= 1; // <--- Error +endmodule diff --git a/test_regress/t/t_flag_csplit_eval.py b/test_regress/t/t_flag_csplit_eval.py index 1b88de3bb..8311f5d9b 100755 --- a/test_regress/t/t_flag_csplit_eval.py +++ b/test_regress/t/t_flag_csplit_eval.py @@ -23,7 +23,7 @@ def check_evals(): test.error("Too few _eval functions found: " + str(got)) -test.compile(v_flags2=["--output-split 1 --output-split-cfuncs 20"], +test.compile(v_flags2=["--output-split 1 --output-split-cfuncs 20 -fno-inline-cfuncs"], verilator_make_gmake=False) # Slow to compile, so skip it) check_evals() diff --git a/test_regress/t/t_flag_expand_limit.py b/test_regress/t/t_flag_expand_limit.py index ec0cc54a6..d9b19b528 100755 --- a/test_regress/t/t_flag_expand_limit.py +++ b/test_regress/t/t_flag_expand_limit.py @@ -13,6 +13,6 @@ test.scenarios('vlt') test.compile(verilator_flags2=['--expand-limit 1 --stats -fno-dfg']) -test.file_grep(test.stats, r'Optimizations, expand limited\s+(\d+)', 7) +test.file_grep(test.stats, r'Optimizations, expand limited\s+(\d+)', 29) test.passes() diff --git a/test_regress/t/t_flag_main_vpi.cpp b/test_regress/t/t_flag_main_vpi.cpp new file mode 100644 index 000000000..1be31452a --- /dev/null +++ b/test_regress/t/t_flag_main_vpi.cpp @@ -0,0 +1,94 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: VPI test library for t_flag_main_vpi +// +// Loaded at runtime via +verilator+vpi+ to verify that --binary --vpi +// correctly loads shared libraries and invokes vlog_startup_routines[] (or a +// named bootstrap). The design drives its own clock; this library only +// observes 'count' via a cbValueChange callback and calls $finish after +// MAX_TICKS edges -- so a successful $finish proves the library was loaded +// and is able to register callbacks and reach signals by name. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "vpi_user.h" + +#include +#include + +// Number of count increments to observe before calling $finish +static const int MAX_TICKS = 10; +static vpiHandle s_count_handle = nullptr; + +static PLI_INT32 count_change_cb(p_cb_data /*cb_data*/) { + if (!s_count_handle) return 0; + s_vpi_value val; + val.format = vpiIntVal; + vpi_get_value(s_count_handle, &val); + if (val.value.integer >= MAX_TICKS) { + vpi_printf(const_cast("*-* All Finished *-*\n")); + vpi_control(vpiFinish, 0); + } + return 0; +} + +static PLI_INT32 start_of_sim_cb(p_cb_data /*cb_data*/) { + s_count_handle = vpi_handle_by_name(const_cast("t.count"), nullptr); + if (!s_count_handle) { + vpi_printf(const_cast("ERROR: cannot find t.count\n")); + vpi_control(vpiFinish, 1); + return 0; + } + // Observe count: fire a callback whenever it changes + t_cb_data cb_data; + s_vpi_time t; + s_vpi_value val; + t.type = vpiSuppressTime; + val.format = vpiSuppressVal; + cb_data.reason = cbValueChange; + cb_data.cb_rtn = count_change_cb; + cb_data.obj = s_count_handle; + cb_data.time = &t; + cb_data.value = &val; + cb_data.user_data = nullptr; + vpi_register_cb(&cb_data); + return 0; +} + +static PLI_INT32 end_of_sim_cb(p_cb_data /*cb_data*/) { + vpi_printf(const_cast("VPI end of simulation\n")); + return 0; +} + +static void register_callbacks() { + // cbStartOfSimulation + t_cb_data cb_data; + s_vpi_time t; + t.type = vpiSuppressTime; + cb_data.reason = cbStartOfSimulation; + cb_data.cb_rtn = start_of_sim_cb; + cb_data.obj = nullptr; + cb_data.time = &t; + cb_data.value = nullptr; + cb_data.user_data = nullptr; + vpi_register_cb(&cb_data); + + // cbEndOfSimulation + cb_data.reason = cbEndOfSimulation; + cb_data.cb_rtn = end_of_sim_cb; + vpi_register_cb(&cb_data); +} + +// IEEE 1800 section 37: vlog_startup_routines[] -- null-terminated array of startup functions +extern "C" { +void (*vlog_startup_routines[])() = {register_callbacks, nullptr}; + +// Named bootstrap entrypoint -- used when library is loaded as :my_vpi_bootstrap +void my_vpi_bootstrap() { register_callbacks(); } +} diff --git a/test_regress/t/t_flag_main_vpi.out b/test_regress/t/t_flag_main_vpi.out new file mode 100644 index 000000000..6a9a7f37b --- /dev/null +++ b/test_regress/t/t_flag_main_vpi.out @@ -0,0 +1,2 @@ +*-* All Finished *-* +VPI end of simulation diff --git a/test_regress/t/t_flag_main_vpi.py b/test_regress/t/t_flag_main_vpi.py new file mode 100755 index 000000000..695b70e76 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# Compile with --binary --vpi to exercise the VPI-aware generated main. +# Also compile a VPI shared library to be loaded at runtime via +verilator+vpi+. +test.compile(make_pli=True, verilator_flags2=["--binary --vpi --public-flat-rw"]) + +# Run the generated binary; load the VPI library via the +verilator+vpi+ plusarg. +# The VPI library's output (observed 'count' reaching MAX_TICKS, then end-of-sim) is +# checked against the golden .out file. +# Also pass a non-VPI plusarg (skipped by the loader's prefix check) and a bare +# +verilator+vpi+ with an empty payload (skipped by the empty-arg check), so both +# loader-skip branches are exercised alongside the real library load. +test.execute(all_run_flags=[ + "+othertest", "+verilator+vpi+", "+verilator+vpi+" + test.obj_dir + "/libvpi.so" +], + check_finished=True, + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_flag_main_vpi.v b/test_regress/t/t_flag_main_vpi.v new file mode 100644 index 000000000..6ac03e26e --- /dev/null +++ b/test_regress/t/t_flag_main_vpi.v @@ -0,0 +1,28 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Test for --binary --vpi runtime library loading. The design provides its +// own clock (so the simulation has Verilog event activity); the VPI library +// (t_flag_main_vpi.cpp), loaded at runtime via +verilator+vpi+, observes +// 'count' via a cbValueChange callback and calls $finish after MAX_TICKS +// edges. Signals are public so the library can reach them by name +// (requires --public-flat-rw). +module t; + + reg clk /*verilator public_flat_rw*/; + reg [31:0] count /*verilator public_flat_rw*/; + + initial begin + clk = 0; + count = 0; + end + + // Self-driving clock: the design itself keeps the simulation alive + always #5 clk = ~clk; + + always @(posedge clk) count <= count + 1; + +endmodule diff --git a/test_regress/t/t_flag_main_vpi_badentry.out b/test_regress/t/t_flag_main_vpi_badentry.out new file mode 100644 index 000000000..6283fa03f --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_badentry.out @@ -0,0 +1,2 @@ +%Error: Cannot find VPI bootstrap 'no_such_fn' in: obj_vlt/t_flag_main_vpi_badentry/libvpi.so +Aborting... diff --git a/test_regress/t/t_flag_main_vpi_badentry.py b/test_regress/t/t_flag_main_vpi_badentry.py new file mode 100755 index 000000000..15398a3e5 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_badentry.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# A valid library loaded with a : entry that does not exist must +# fail with a clear error (the missing-named-bootstrap branch of the loader). +test.top_filename = 't/t_flag_main_vpi.v' +test.pli_filename = 't/t_flag_main_vpi.cpp' + +test.compile(make_pli=True, verilator_flags2=["--binary --vpi --public-flat-rw"]) + +test.execute(fails=True, + all_run_flags=["+verilator+vpi+" + test.obj_dir + "/libvpi.so:no_such_fn"], + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_flag_main_vpi_badlib.out b/test_regress/t/t_flag_main_vpi_badlib.out new file mode 100644 index 000000000..752708e9e --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_badlib.out @@ -0,0 +1,2 @@ +%Error: Cannot load VPI library: obj_vlt/t_flag_main_vpi_badlib/nonexistent.so +Aborting... diff --git a/test_regress/t/t_flag_main_vpi_badlib.py b/test_regress/t/t_flag_main_vpi_badlib.py new file mode 100755 index 000000000..2ac211d49 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_badlib.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# +verilator+vpi+ pointing at a non-existent library must fail with a clear +# error (the dlopen-failure branch of the runtime loader). +test.top_filename = 't/t_flag_main_vpi.v' + +test.compile(verilator_flags2=["--binary --vpi --public-flat-rw"]) + +# The fatal names the (stable, relative) library path; the platform-specific dlerror() +# detail is emitted on a "- " line, which golden comparison strips, so the .out is portable. +test.execute(fails=True, + all_run_flags=["+verilator+vpi+" + test.obj_dir + "/nonexistent.so"], + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_flag_main_vpi_bootstrap.out b/test_regress/t/t_flag_main_vpi_bootstrap.out new file mode 100644 index 000000000..6a9a7f37b --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_bootstrap.out @@ -0,0 +1,2 @@ +*-* All Finished *-* +VPI end of simulation diff --git a/test_regress/t/t_flag_main_vpi_bootstrap.py b/test_regress/t/t_flag_main_vpi_bootstrap.py new file mode 100755 index 000000000..4c5c21e70 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_bootstrap.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# Same design and VPI library as t_flag_main_vpi, but loaded via the +# +verilator+vpi+: named-bootstrap syntax instead of vlog_startup_routines[]. +test.top_filename = 't/t_flag_main_vpi.v' +test.pli_filename = 't/t_flag_main_vpi.cpp' + +test.compile(make_pli=True, verilator_flags2=["--binary --vpi --public-flat-rw"]) + +test.execute(all_run_flags=["+verilator+vpi+" + test.obj_dir + "/libvpi.so:my_vpi_bootstrap"], + check_finished=True, + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_flag_main_vpi_lib2.cpp b/test_regress/t/t_flag_main_vpi_lib2.cpp new file mode 100644 index 000000000..8f5bba74b --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_lib2.cpp @@ -0,0 +1,25 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Second VPI test library for t_flag_main_vpi_multi +// +// A second, independent VPI library loaded alongside t_flag_main_vpi.cpp via a +// repeated +verilator+vpi+ argument, to verify multiple libraries are loaded. +// Its startup routine prints a marker proving it was loaded; it does not drive +// or finish the simulation (the first library does that). +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "vpi_user.h" + +static void lib2_startup() { vpi_printf(const_cast("second VPI library loaded\n")); } + +// IEEE 1800 section 37: vlog_startup_routines[] -- null-terminated array of startup functions +extern "C" { +void (*vlog_startup_routines[])() = {lib2_startup, nullptr}; +} diff --git a/test_regress/t/t_flag_main_vpi_multi.out b/test_regress/t/t_flag_main_vpi_multi.out new file mode 100644 index 000000000..04f3f5e6d --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_multi.out @@ -0,0 +1,3 @@ +second VPI library loaded +*-* All Finished *-* +VPI end of simulation diff --git a/test_regress/t/t_flag_main_vpi_multi.py b/test_regress/t/t_flag_main_vpi_multi.py new file mode 100755 index 000000000..af3e301ee --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_multi.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import os +import platform +import vltest_bootstrap + +test.scenarios('vlt') + +# Two +verilator+vpi+ arguments load two independent libraries: the first +# (t_flag_main_vpi.cpp) observes the design and calls $finish; the second +# (t_flag_main_vpi_lib2.cpp) prints a marker proving it too was loaded. +test.top_filename = 't/t_flag_main_vpi.v' +test.pli_filename = 't/t_flag_main_vpi.cpp' + +test.compile(make_pli=True, verilator_flags2=["--binary --vpi --public-flat-rw"]) + +# Build the second VPI library (make_pli only builds libvpi.so), mirroring the +# driver's own pli flags. +root = os.environ['VERILATOR_ROOT'] +pli2_cmd = [ + os.environ['CXX'], "-I" + root + "/include/vltstd", "-I" + root + "/include", "-fPIC", + "-shared" +] +pli2_cmd += (["-Wl,-undefined,dynamic_lookup"] if platform.system() == 'Darwin' else ["-rdynamic"]) +pli2_cmd += ["-o", test.obj_dir + "/libvpi2.so", "t/t_flag_main_vpi_lib2.cpp"] +test.run(logfile=test.obj_dir + "/pli2_compile.log", cmd=pli2_cmd) + +test.execute(all_run_flags=[ + "+verilator+vpi+" + test.obj_dir + "/libvpi.so", + "+verilator+vpi+" + test.obj_dir + "/libvpi2.so" +], + check_finished=True, + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_flag_main_vpi_noarray.cpp b/test_regress/t/t_flag_main_vpi_noarray.cpp new file mode 100644 index 000000000..02568d282 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_noarray.cpp @@ -0,0 +1,21 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: VPI test library lacking vlog_startup_routines +// +// Loaded via +verilator+vpi+ with no : entry, to exercise +// the loader's error path when a library defines neither a named bootstrap +// nor the vlog_startup_routines[] array. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2025 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "vpi_user.h" + +// Intentionally no vlog_startup_routines and no bootstrap; just some symbol so +// the shared object is non-empty and loads successfully. +extern "C" void t_flag_main_vpi_noarray_present() {} diff --git a/test_regress/t/t_flag_main_vpi_noarray.out b/test_regress/t/t_flag_main_vpi_noarray.out new file mode 100644 index 000000000..a7f54a6af --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_noarray.out @@ -0,0 +1,2 @@ +%Error: Cannot find 'vlog_startup_routines' in: obj_vlt/t_flag_main_vpi_noarray/libvpi.so +Aborting... diff --git a/test_regress/t/t_flag_main_vpi_noarray.py b/test_regress/t/t_flag_main_vpi_noarray.py new file mode 100755 index 000000000..9dd2ac853 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_noarray.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# A library loaded with no : entry that lacks vlog_startup_routines +# must fail with a clear error (the missing-array branch of the loader). +test.top_filename = 't/t_flag_main_vpi.v' +test.pli_filename = 't/t_flag_main_vpi_noarray.cpp' + +test.compile(make_pli=True, verilator_flags2=["--binary --vpi --public-flat-rw"]) + +test.execute(fails=True, + all_run_flags=["+verilator+vpi+" + test.obj_dir + "/libvpi.so"], + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_flag_main_vpi_noexe.py b/test_regress/t/t_flag_main_vpi_noexe.py new file mode 100755 index 000000000..1f347105b --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_noexe.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# --vpi without --exe (no generated main, library-only output): the Makefile +# must still report VM_VPI = 1, but must NOT add the runtime-VPI link flags since +# there is no executable to export VPI symbols from. Exercises the exe()==false +# branch of the VPI link-flag gate in V3EmitMk. +test.top_filename = 't/t_flag_main_vpi.v' + +test.compile(make_main=False, verilator_make_gmake=False, verilator_flags2=["--vpi --timing"]) + +test.file_grep(test.obj_dir + "/V" + test.name + "_classes.mk", r'VM_VPI = 1') +# Without --exe there is no executable to export symbols from or to dlopen into, +# so the runtime-VPI link flags (CFG_LDFLAGS_DYNAMIC/CFG_LDLIBS_DYNAMIC, probed at +# configure time) must not be referenced in the generated Makefile. +mk = test.obj_dir + "/V" + test.name + ".mk" +test.file_grep_not(mk, r'CFG_LDFLAGS_DYNAMIC') +test.file_grep_not(mk, r'CFG_LDLIBS_DYNAMIC') + +test.passes() diff --git a/test_regress/t/t_flag_main_vpi_nowarn.out b/test_regress/t/t_flag_main_vpi_nowarn.out new file mode 100644 index 000000000..e51042279 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_nowarn.out @@ -0,0 +1,3 @@ +%Warning: COMMAND_LINE:0: +verilator+vpi+ ignored: simulation was not compiled with --vpi '+verilator+vpi+/nonexistent.so' +[0] Hello +*-* All Finished *-* diff --git a/test_regress/t/t_flag_main_vpi_nowarn.py b/test_regress/t/t_flag_main_vpi_nowarn.py new file mode 100755 index 000000000..ab077f4f5 --- /dev/null +++ b/test_regress/t/t_flag_main_vpi_nowarn.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +# Compile with --binary but WITHOUT --vpi. +# Passing +verilator+vpi+ at runtime should emit a warning, not load anything. +test.compile(top_filename='t/t_flag_main.v', verilator_flags2=["--binary"]) + +# Without --vpi there is no loader, so the plusarg is ignored with a warning (checked via +# the golden .out). The plusarg value is fixed, so the warning text is portable. +test.execute(all_run_flags=["+verilator+vpi+/nonexistent.so"], + check_finished=True, + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_force_unpacked_bitsel.py b/test_regress/t/t_force_unpacked_bitsel.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_force_unpacked_bitsel.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_force_unpacked_bitsel.v b/test_regress/t/t_force_unpacked_bitsel.v new file mode 100644 index 000000000..402d15f83 --- /dev/null +++ b/test_regress/t/t_force_unpacked_bitsel.v @@ -0,0 +1,133 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Nikolai Kumar +// SPDX-License-Identifier: CC0-1.0 + +`define checkh(g, + e) do if ((g) !==(e)) begin $write("%%Error: %s:%0d: got=%b exp=%b\n", `__FILE__,`__LINE__, (g),(e)); $stop; end while(0) + + +module t ( + input clk +); + int cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + logic [4:1] var_arr[2]; + /* verilator lint_off ASCRANGE */ + logic [1:4] var_arr_a[2]; + /* verilator lint_on ASCRANGE */ + logic [72:1] wide_arr[2]; + + always @(posedge clk) begin + var_arr[0] <= 4'b0101; + var_arr[1] <= (cyc <= 3) ? 4'b1111 : (cyc <= 7) ? 4'b0000 : 4'b0001; + var_arr_a[0] <= 4'b1010; + var_arr_a[1] <= 4'b0000; + wide_arr[0] <= '0; + wide_arr[1] <= 72'hAB_CDEF0123_456789AB; + end + + always @(posedge clk) begin + if (cyc == 2) force var_arr[1][1] = 1'b0; + if (cyc == 6) force var_arr[1][4] = 1'b1; + if (cyc == 8) release var_arr[1][1]; + if (cyc == 10) force var_arr[1] = 4'b1010; + if (cyc == 12) release var_arr[1]; + + if (cyc == 2) force wide_arr[1][36:5] = 32'hffff_ffff; + if (cyc == 4) release wide_arr[1]; + + if (cyc == 2) force var_arr_a[1][2:4] = 3'b111; + if (cyc == 4) force var_arr_a[1][3] = 1'b0; + if (cyc == 6) release var_arr_a[1]; + if (cyc == 7) force var_arr_a[1][3:4] = 2'b11; + if (cyc == 9) force var_arr_a[1][2:3] = 2'b00; + if (cyc == 11) release var_arr_a[1]; + if (cyc == 12) force var_arr_a[1][1:2] = 2'b11; + if (cyc == 14) force var_arr_a[1][2:3] = 2'b00; + if (cyc == 16) release var_arr_a[1]; + if (cyc == 17) force var_arr_a[1][2:3] = 2'b11; + if (cyc == 19) force var_arr_a[1][1:3] = 3'b000; + if (cyc == 21) release var_arr_a[1]; + + if (cyc == 14) force var_arr[1][1] = 1'b0; + if (cyc == 14) force var_arr[0][1] = 1'b0; + if (cyc == 16) release var_arr[1][1]; + if (cyc == 16) release var_arr[0][1]; + if (cyc == 18) force var_arr[0] = 4'b1010; + if (cyc == 20) force var_arr[0][1] = 1'b1; + if (cyc == 22) release var_arr[0]; + end + + always @(posedge clk) + case (cyc) + 1: begin + `checkh(var_arr[0], 4'b0101); + `checkh(var_arr[1], 4'b1111); + end + 3: begin + `checkh(var_arr[1], 4'b1110); + `checkh(wide_arr[1][36:5], 32'hffff_ffff); + `checkh(wide_arr[1][4:1], 4'hB); + `checkh(wide_arr[1][72:37], 36'hABCDEF012); + `checkh(var_arr_a[1], 4'b0111); + `checkh(var_arr_a[0], 4'b1010); + end + 5: begin + `checkh(var_arr[1], 4'b0000); + `checkh(wide_arr[1], 72'hAB_CDEF0123_456789AB); + `checkh(var_arr_a[1], 4'b0101); + end + 7: begin + `checkh(var_arr[1], 4'b1000); + end + 8: begin + `checkh(var_arr_a[1], 4'b0011); + end + 9: begin + `checkh(var_arr[1], 4'b1001); + end + 10: begin + `checkh(var_arr_a[1], 4'b0001); + end + 11: begin + `checkh(var_arr[1], 4'b1010); + `checkh(var_arr[0], 4'b0101); + end + 13: begin + `checkh(var_arr[1], 4'b0001); + `checkh(var_arr_a[1], 4'b1100); + end + 15: begin + `checkh(var_arr_a[1], 4'b1000); + `checkh(var_arr[1], 4'b0000); + `checkh(var_arr[0], 4'b0100); + end + 17: begin + `checkh(var_arr[1], 4'b0001); + `checkh(var_arr[0], 4'b0101); + end + 18: begin + `checkh(var_arr_a[1], 4'b0110); + end + 19: begin + `checkh(var_arr[0], 4'b1010); + end + 20: begin + `checkh(var_arr_a[1], 4'b0000); + end + 21: begin + `checkh(var_arr[0], 4'b1011); + end + 22: begin + `checkh(var_arr_a[1], 4'b0000); + end + 23: begin + `checkh(var_arr[0], 4'b0101); + $write("*-* All Finished *-*\n"); + $finish; + end + endcase +endmodule diff --git a/test_regress/t/t_forceable_unpacked.v b/test_regress/t/t_forceable_unpacked.v index 5e0048563..3b3cd23a0 100644 --- a/test_regress/t/t_forceable_unpacked.v +++ b/test_regress/t/t_forceable_unpacked.v @@ -5,7 +5,8 @@ // SPDX-License-Identifier: CC0-1.0 // verilog_format: off -`define checkh(g,e) do if ((g) !==(e)) begin $write("%%Error: %s:%0d: got=%x exp=%x\n", `__FILE__,`__LINE__, (g),(e)); $stop; end while(0) +`define stop $stop +`define checkh(g,e) do if ((g) !==(e)) begin $write("%%Error: %s:%0d: got=%x exp=%x\n", `__FILE__,`__LINE__, (g),(e)); `stop; end while(0) `ifdef CMT `define FORCEABLE /*verilator forceable*/ diff --git a/test_regress/t/t_fork_dynscope_out.py b/test_regress/t/t_fork_dynscope_out.py index f478cb764..7ded63f3a 100755 --- a/test_regress/t/t_fork_dynscope_out.py +++ b/test_regress/t/t_fork_dynscope_out.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--binary -Wno-INITIALDLY"]) +test.compile(verilator_flags2=["--binary"]) test.execute() diff --git a/test_regress/t/t_fsm_duplicate.py b/test_regress/t/t_fsm_duplicate.py new file mode 100755 index 000000000..7f4205522 --- /dev/null +++ b/test_regress/t/t_fsm_duplicate.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM no duplicate variables test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage -Wno-PINMISSING"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fsm_duplicate.v b/test_regress/t/t_fsm_duplicate.v new file mode 100644 index 000000000..c0edc5755 --- /dev/null +++ b/test_regress/t/t_fsm_duplicate.v @@ -0,0 +1,157 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module rr #( +) ( + input logic clk, + input logic rst, + input logic [7:0] data, + input logic data_q +); + logic a; + logic [15:0] dcnt; + typedef enum logic [7:0] { + S0, + S1, + S2, + S3 + } state_t; + state_t state_d, state_q; + always_ff @(posedge clk or negedge rst) if (!rst) state_q <= S0; + always_ff @(posedge clk) + unique case (state_q) + S1: if (a) dcnt[7:0] <= data; + S2: if (a) dcnt[15:8] <= data; + S3: if (data_q) dcnt <= dcnt - 1; + default: dcnt <= dcnt; + endcase +endmodule +module re #( +) ( + input logic clk, + input logic rst, + output logic o, + input unused0, /* block optimizations */ + input unused1, + input unused2, + input unused3, + input unused4, + input unused5, + input unused6, + input unused7, + input unused8, + input unused9, + input unused10, + input unused11, + input unused12, + input unused13, + input unused14, + input unused15, + input unused16, + input unused17, + input unused18, + input unused19, + input unused20, + input unused21, + input unused22, + input unused23, + input unused24, + input unused25, + input unused26, + input unused27, + input unused28, + input unused29, + input unused30, + input unused31, + input unused32, + input unused33, + input unused34, + input unused35, + input unused36, + input unused37, + input unused38, + input unused39, + input unused40 +); + logic [15:0] dcnt; + typedef enum logic [7:0] { + S0, + S1 + } state_t; + state_t state_d, state_q; + always_ff @(posedge clk or negedge rst) if (!rst) state_q <= S0; + always_ff @(posedge clk) + unique case (state_q) + S1: o <= dcnt[0]; + default: o <= '0; + endcase + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule +module rh #( +) ( + input logic clk +); + logic [7:0] a; + logic b; + logic c; + logic d; + logic rst; + rr xrr ( + .clk, + .rst(rst), + .data(a), + .data_q(b & c) + ); + re xre ( + .clk, + .rst(rst), + .o(d) + ); +endmodule +module U #( +) ( + input clk, + input rst +); + rh xrh (.clk(clk)); +endmodule +module C #( +) ( + input clk, + input rst +); + U U ( + .clk, + .rst + ); +endmodule +module A #() (); + logic clk; + logic rst; + C c0 ( + .clk, + .rst + ); + C c1 ( + .clk, + .rst + ); +endmodule +module B #() (); + logic clk; + logic rst; + C xC ( + .clk, + .rst + ); +endmodule +module t #() (); + B b (); + A a (); +endmodule diff --git a/test_regress/t/t_fsmmulti_combo_multi_warn_bad.out b/test_regress/t/t_fsmmulti_combo_multi_warn_bad.out index 8985f2c91..367f70f55 100644 --- a/test_regress/t/t_fsmmulti_combo_multi_warn_bad.out +++ b/test_regress/t/t_fsmmulti_combo_multi_warn_bad.out @@ -1,29 +1,3 @@ -%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:36:21: FSM coverage: multiple supported transition candidates found in the same combinational always block. Only the first candidate will be instrumented. - 36 | B0: state_b_d = B1; - | ^ - t/t_fsmmulti_combo_multi_warn_bad.v:32:21: ... Location of first supported candidate for 't.same_u.state_a_q' - 32 | A0: state_a_d = A1; - | ^ - ... For warning description see https://verilator.org/warn/FSMMULTI?v=latest - ... Use "/* verilator lint_off FSMMULTI */" and lint_on around source to disable this message. -%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:73:19: FSM coverage: multiple supported transition candidates found for the same FSM in combinational always blocks. Only the first candidate will be instrumented. - 73 | S0: state_d = S1; - | ^ - t/t_fsmmulti_combo_multi_warn_bad.v:65:19: ... Location of first supported candidate for 't.split_u.state_q' - 65 | S0: state_d = S1; - | ^ -%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:107:5: FSM coverage: multiple supported transition candidates found in the same combinational always block. Only the first candidate will be instrumented. - 107 | if (state_b_q == B0) state_b_d = B1; - | ^~ - t/t_fsmmulti_combo_multi_warn_bad.v:105:5: ... Location of first supported candidate for 't.same_if_u.state_a_q' - 105 | if (state_a_q == A0) state_a_d = A1; - | ^~ -%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:140:5: FSM coverage: multiple supported transition candidates found for the same FSM in combinational always blocks. Only the first candidate will be instrumented. - 140 | if (state_q == S0) state_d = S1; - | ^~ - t/t_fsmmulti_combo_multi_warn_bad.v:134:5: ... Location of first supported candidate for 't.split_if_u.state_q' - 134 | if (state_q == S0) state_d = S1; - | ^~ %Warning-COVERIGN: t/t_fsmmulti_combo_multi_warn_bad.v:165:5: Ignoring unsupported: FSM coverage on multiple supported if-chain statements found in the same combinational always block. Only the first candidate will be instrumented. 165 | if (state_q == S1) state_d = S0; | ^~ @@ -32,4 +6,30 @@ | ^~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:140:5: FSM coverage: multiple supported transition candidates found for the same FSM in combinational always blocks. Only the first candidate will be instrumented. + 140 | if (state_q == S0) state_d = S1; + | ^~ + t/t_fsmmulti_combo_multi_warn_bad.v:134:5: ... Location of first supported candidate for 't.split_if_u.state_q' + 134 | if (state_q == S0) state_d = S1; + | ^~ + ... For warning description see https://verilator.org/warn/FSMMULTI?v=latest + ... Use "/* verilator lint_off FSMMULTI */" and lint_on around source to disable this message. +%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:107:5: FSM coverage: multiple supported transition candidates found in the same combinational always block. Only the first candidate will be instrumented. + 107 | if (state_b_q == B0) state_b_d = B1; + | ^~ + t/t_fsmmulti_combo_multi_warn_bad.v:105:5: ... Location of first supported candidate for 't.same_if_u.state_a_q' + 105 | if (state_a_q == A0) state_a_d = A1; + | ^~ +%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:73:19: FSM coverage: multiple supported transition candidates found for the same FSM in combinational always blocks. Only the first candidate will be instrumented. + 73 | S0: state_d = S1; + | ^ + t/t_fsmmulti_combo_multi_warn_bad.v:65:19: ... Location of first supported candidate for 't.split_u.state_q' + 65 | S0: state_d = S1; + | ^ +%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:36:21: FSM coverage: multiple supported transition candidates found in the same combinational always block. Only the first candidate will be instrumented. + 36 | B0: state_b_d = B1; + | ^ + t/t_fsmmulti_combo_multi_warn_bad.v:32:21: ... Location of first supported candidate for 't.same_u.state_a_q' + 32 | A0: state_a_d = A1; + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_gate_chained.py b/test_regress/t/t_gate_chained.py index 7fa18e3a3..ddf8bef62 100755 --- a/test_regress/t/t_gate_chained.py +++ b/test_regress/t/t_gate_chained.py @@ -48,6 +48,6 @@ test.compile( test.execute() # Must be <<9000 above to prove this worked -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 8550) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 8550) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_exclude_multiple.py b/test_regress/t/t_gate_inline_wide_exclude_multiple.py index 76a8991cf..2bf5b1241 100755 --- a/test_regress/t/t_gate_inline_wide_exclude_multiple.py +++ b/test_regress/t/t_gate_inline_wide_exclude_multiple.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 2) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 4) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py index 8c99e4b33..15370ae7c 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-dfg']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_const.py b/test_regress/t/t_gate_inline_wide_noexclude_const.py index 5102631f7..cb6511e21 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_const.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_const.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 2) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v b/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v deleted file mode 100644 index c70022d24..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v +++ /dev/null @@ -1,34 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -localparam N = 256; // Wider than expand limit. - -module t ( - input wire [N-1:0] i, - output wire [N-1:0] o -); - - // Do not exclude from inlining wides referenced in different scope. - wire [N-1:0] wide = N ~^ i; - - sub sub ( - i, - wide, - o - ); -endmodule - -module sub ( - input wire [N-1:0] i, - input wire [N-1:0] wide, - output logic [N-1:0] o -); - initial begin - for (integer n = 0; n < N; ++n) begin - o[n] = i[N-1-n] | wide[N-1-n]; - end - end -endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_sel.py b/test_regress/t/t_gate_inline_wide_noexclude_sel.py index aa65429ab..41d13c55d 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_sel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_sel.py @@ -13,8 +13,8 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-var-split']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 1) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 1) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v b/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v deleted file mode 100644 index 1b1231c4e..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v +++ /dev/null @@ -1,21 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -localparam N = 65; // Wide but narrower than expand limit - -module t ( - input wire [N-1:0] i, - output wire [N-1:0] o -); - - // Do not exclude from inlining wides small enough to be handled by - // V3Expand. - wire [65:0] wide_small = N << i * i / N; - - for (genvar n = 0; n < N; ++n) begin - assign o[n] = i[n] ^ wide_small[n]; - end -endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_varref.py b/test_regress/t/t_gate_inline_wide_noexclude_varref.py index 1d91e6493..282e88b84 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_varref.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_varref.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gen_for.v b/test_regress/t/t_gen_for.v index 47d7ba1a7..006b9f83a 100644 --- a/test_regress/t/t_gen_for.v +++ b/test_regress/t/t_gen_for.v @@ -97,8 +97,6 @@ module paramed ( /*AUTOARG*/ // No else endgenerate -`ifndef NC // for(genvar) unsupported -`ifndef ATSIM // for(genvar) unsupported generate // Empty loop body, local genvar for (genvar j = 0; j < 3; j = j + 1) begin @@ -107,8 +105,6 @@ module paramed ( /*AUTOARG*/ for (genvar j = 0; j < 5; j = j + 1) begin end endgenerate -`endif -`endif generate endgenerate diff --git a/test_regress/t/t_gen_intdot.v b/test_regress/t/t_gen_intdot.v index d00c7028a..248616e8e 100644 --- a/test_regress/t/t_gen_intdot.v +++ b/test_regress/t/t_gen_intdot.v @@ -10,30 +10,34 @@ module t ( input clk ); - integer cyc = 0; + integer cyc = 0; - wire out; - reg in; + wire out; + reg in; - Genit g (.clk(clk), .value(in), .result(out)); + Genit g ( + .clk(clk), + .value(in), + .result(out) + ); - always @ (posedge clk) begin + always @(posedge clk) begin //$write("[%0t] cyc==%0d %x %x\n", $time, cyc, in, out); cyc <= cyc + 1; - if (cyc==0) begin + if (cyc == 0) begin // Setup in <= 1'b1; end - else if (cyc==1) begin + else if (cyc == 1) begin in <= 1'b0; end - else if (cyc==2) begin + else if (cyc == 2) begin if (out != 1'b1) $stop; end - else if (cyc==3) begin + else if (cyc == 3) begin if (out != 1'b0) $stop; end - else if (cyc==9) begin + else if (cyc == 9) begin $write("*-* All Finished *-*\n"); $finish; end @@ -41,7 +45,11 @@ module t ( endmodule -module Generate (clk, value, result); +module Generate ( + clk, + value, + result +); input clk; input value; output result; @@ -50,63 +58,73 @@ module Generate (clk, value, result); assign result = Internal; - always @(posedge clk) - Internal <= value; + always @(posedge clk) Internal <= value; endmodule -module Checker (clk, value); +module Checker ( + clk, + value +); input clk, value; always @(posedge clk) begin - $write ("[%0t] value=%h\n", $time, value); + $write("[%0t] value=%h\n", $time, value); end endmodule -module Test (clk, value, result); +module Test ( + clk, + value, + result +); input clk; input value; output result; - Generate gen (clk, value, result); - Checker chk (clk, gen.Internal); + Generate gen ( + clk, + value, + result + ); + Checker chk ( + clk, + gen.Internal + ); endmodule -module Genit (clk, value, result); +module Genit ( + clk, + value, + result +); input clk; input value; output result; -`ifndef ATSIM // else unsupported - `ifndef NC // else unsupported - `ifndef IVERILOG // else unsupported - `define WITH_FOR_GENVAR - `endif - `endif -`endif - -`define WITH_GENERATE + `define WITH_GENERATE `ifdef WITH_GENERATE - `ifndef WITH_FOR_GENVAR genvar i; - `endif generate - for ( - `ifdef WITH_FOR_GENVAR - genvar - `endif - i = 0; i < 1; i = i + 1) - begin : foo - Test tt (clk, value, result); - end + for (genvar i = 0; i < 1; i = i + 1) begin : foo + Test tt ( + clk, + value, + result + ); + end endgenerate `else - Test tt (clk, value, result); + Test tt ( + clk, + value, + result + ); `endif wire Result2 = t.g.foo[0].tt.gen.Internal; // Works - Do not change! - always @ (posedge clk) begin + always @(posedge clk) begin $write("[%0t] Result2 = %x\n", $time, Result2); end diff --git a/test_regress/t/t_gen_upscope.out b/test_regress/t/t_gen_upscope.out index 3765ed4ca..bb8b8e714 100644 --- a/test_regress/t/t_gen_upscope.out +++ b/test_regress/t/t_gen_upscope.out @@ -1,12 +1,12 @@ -created tag with scope = top.t.tag -created tag with scope = top.t.b.gen[0].tag created tag with scope = top.t.b.gen[1].tag +created tag with scope = top.t.b.gen[0].tag +created tag with scope = top.t.tag mod a has scope = top.t mod a has tag = top.t.tag mod b has scope = top.t.b mod b has tag = top.t.tag -mod c has scope = top.t.b.gen[0].c -mod c has tag = top.t.b.gen[0].tag mod c has scope = top.t.b.gen[1].c mod c has tag = top.t.b.gen[1].tag +mod c has scope = top.t.b.gen[0].c +mod c has tag = top.t.b.gen[0].tag *-* All Finished *-* diff --git a/test_regress/t/t_genfor_signed.out b/test_regress/t/t_genfor_signed.out index 7f921d5a9..b99a494fc 100644 --- a/test_regress/t/t_genfor_signed.out +++ b/test_regress/t/t_genfor_signed.out @@ -1,9 +1,9 @@ -top.t.u_sub1.unnamedblk1 1..1 i=1 +top.t.SUB_PIPE[0].u_sub.unnamedblk1 1..0 i=1 +top.t.SUB_PIPE[0].u_sub.unnamedblk1 1..0 i=0 top.t.u_sub0.unnamedblk1 1..0 i=1 top.t.u_sub0.unnamedblk1 1..0 i=0 top.t.SUB_PIPE[-1].u_sub.unnamedblk1 1..-1 i=1 top.t.SUB_PIPE[-1].u_sub.unnamedblk1 1..-1 i=0 top.t.SUB_PIPE[-1].u_sub.unnamedblk1 1..-1 i=-1 -top.t.SUB_PIPE[0].u_sub.unnamedblk1 1..0 i=1 -top.t.SUB_PIPE[0].u_sub.unnamedblk1 1..0 i=0 +top.t.u_sub1.unnamedblk1 1..1 i=1 *-* All Finished *-* diff --git a/test_regress/t/t_hier_block_chained.py b/test_regress/t/t_hier_block_chained.py index fb17db8e6..41e89faab 100755 --- a/test_regress/t/t_hier_block_chained.py +++ b/test_regress/t/t_hier_block_chained.py @@ -31,9 +31,9 @@ test.compile(v_flags2=[ if test.vltmt: test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule count\s+(\d+)', 3) + r'Optimizations, Thread schedule count\s+(\d+)', 4) test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule total tasks\s+(\d+)', 5) + r'Optimizations, Thread schedule total tasks\s+(\d+)', 6) test.execute() diff --git a/test_regress/t/t_hier_block_perf.py b/test_regress/t/t_hier_block_perf.py index d91e3475e..ea6d40c8b 100755 --- a/test_regress/t/t_hier_block_perf.py +++ b/test_regress/t/t_hier_block_perf.py @@ -35,9 +35,9 @@ test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "_ if test.vltmt: test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule count\s+(\d+)', 1) + r'Optimizations, Thread schedule count\s+(\d+)', 2) test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule total tasks\s+(\d+)', 2) + r'Optimizations, Thread schedule total tasks\s+(\d+)', 3) test.execute(all_run_flags=[ "+verilator+prof+exec+start+2", diff --git a/test_regress/t/t_initial_dlyass.py b/test_regress/t/t_initial_dlyass.py index 99d3b795b..3cc73805c 100755 --- a/test_regress/t/t_initial_dlyass.py +++ b/test_regress/t/t_initial_dlyass.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=['-Wno-INITIALDLY']) +test.compile() test.execute() diff --git a/test_regress/t/t_initial_dlyass2.py b/test_regress/t/t_initial_dlyass2.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_initial_dlyass2.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_initial_dlyass2.v b/test_regress/t/t_initial_dlyass2.v new file mode 100644 index 000000000..02e25a323 --- /dev/null +++ b/test_regress/t/t_initial_dlyass2.v @@ -0,0 +1,50 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + + reg clk = 1; + reg [7:0] d = 0; + reg [7:0] q = 0; + + // Clock generation + always #0.5 clk = ~clk; + + // Input signal generation + initial begin + // verilator lint_off INITIALDLY + d <= 0; + repeat (5) @(posedge clk); + d <= 1; + @(posedge clk); + d <= 2; + @(posedge clk); + d <= 3; + @(posedge clk); + d <= 4; + @(posedge clk); + d <= 0; + repeat (5) @(posedge clk); + $finish; + end + + // Unit under test (flip-flop) + always @(posedge clk) q <= d; + + always @(negedge clk) begin + $display("[%0t] d=%x q=%x", $time, d, q); + if (d == 1) `checkd(q, 0); + if (d == 2) `checkd(q, 1); + if (d == 3) `checkd(q, 2); + if (d == 4) `checkd(q, 3); + end + +endmodule diff --git a/test_regress/t/t_initial_dlyass_bad.out b/test_regress/t/t_initial_dlyass_bad.out deleted file mode 100644 index 54d3da81c..000000000 --- a/test_regress/t/t_initial_dlyass_bad.out +++ /dev/null @@ -1,11 +0,0 @@ -%Warning-INITIALDLY: t/t_initial_dlyass.v:17:7: Non-blocking assignment '<=' in initial/final block - : ... This will be executed as a blocking assignment '='! - 17 | a <= 22; - | ^~ - ... For warning description see https://verilator.org/warn/INITIALDLY?v=latest - ... Use "/* verilator lint_off INITIALDLY */" and lint_on around source to disable this message. -%Warning-INITIALDLY: t/t_initial_dlyass.v:18:7: Non-blocking assignment '<=' in initial/final block - : ... This will be executed as a blocking assignment '='! - 18 | b <= 33; - | ^~ -%Error: Exiting due to diff --git a/test_regress/t/t_inst_signed.v b/test_regress/t/t_inst_signed.v index bd46592dd..bfb24f007 100644 --- a/test_regress/t/t_inst_signed.v +++ b/test_regress/t/t_inst_signed.v @@ -34,9 +34,9 @@ module t ( if (sgn_wide[2:0] != 3'sh7) $stop; if (unsgn_wide[2:0] != 3'h7) $stop; // Simulators differ here. - if (sgn_wide !== 8'sbzzzzz111 // z-extension - NC - && sgn_wide !== 8'sb11111111) - $stop; // sign extension - VCS + if (sgn_wide !== 8'sbzzzzz111 // z-extension - some others + && sgn_wide !== 8'sb11111111) // sign extension - some others + $stop; if (unsgn_wide !== 8'sbzzzzz111 && unsgn_wide !== 8'sb00000111) $stop; cyc <= cyc + 1; if (cyc == 3) begin diff --git a/test_regress/t/t_inst_tree_inl0_pub1.py b/test_regress/t/t_inst_tree_inl0_pub1.py index 31d27a095..be831d2c5 100755 --- a/test_regress/t/t_inst_tree_inl0_pub1.py +++ b/test_regress/t/t_inst_tree_inl0_pub1.py @@ -14,8 +14,8 @@ test.top_filename = "t/t_inst_tree.v" default_vltmt_threads = test.get_default_vltmt_threads test.compile( - # Disable --inline-cfuncs so functions exist to be combined - verilator_flags2=['--stats', '--inline-cfuncs', '0', test.t_dir + "/" + test.name + ".vlt"], + # Disable CFunc inlining so functions exist to be combined + verilator_flags2=['--stats', '-fno-inline-cfuncs', test.t_dir + "/" + test.name + ".vlt"], # Force 3 threads even if we have fewer cores threads=(default_vltmt_threads if test.vltmt else 1)) diff --git a/test_regress/t/t_inst_tree_inl1_pub0.py b/test_regress/t/t_inst_tree_inl1_pub0.py index 443c7b9b1..036210aaa 100755 --- a/test_regress/t/t_inst_tree_inl1_pub0.py +++ b/test_regress/t/t_inst_tree_inl1_pub0.py @@ -20,11 +20,11 @@ test.compile( if test.vlt_all: test.file_grep( out_filename, - r'{"type":"VAR","name":"t.u.u0.u0.z1",.*"loc":"\w,70:[^"]*",.*"origName":"z1",.*"dtypeName":"logic"' + r'{"type":"VAR","name":"t.u.u1.u0.z1",.*"loc":"\w,70:[^"]*",.*"origName":"z1",.*"dtypeName":"logic"' ) test.file_grep( out_filename, - r'{"type":"VAR","name":"t.u.u0.u1.z1",.*"loc":"\w,70:[^"]*",.*"origName":"z1",.*"dtypeName":"logic"' + r'{"type":"VAR","name":"t.u.u1.u1.z1",.*"loc":"\w,70:[^"]*",.*"origName":"z1",.*"dtypeName":"logic"' ) test.file_grep( out_filename, diff --git a/test_regress/t/t_interface2.v b/test_regress/t/t_interface2.v index e11f71b1f..52c3ac79e 100644 --- a/test_regress/t/t_interface2.v +++ b/test_regress/t/t_interface2.v @@ -12,7 +12,7 @@ module t ( counter_io c1_data(); counter_io c2_data(); - //counter_io c3_data; // IEEE illegal, and VCS doesn't allow non-() as it does with cells + // counter_io c3_data; // IEEE illegal counter_io c3_data(); counter_ansi c1 (.clkm(clk), diff --git a/test_regress/t/t_interface_array_nocolon.v b/test_regress/t/t_interface_array_nocolon.v index a5af54af4..c8015b005 100644 --- a/test_regress/t/t_interface_array_nocolon.v +++ b/test_regress/t/t_interface_array_nocolon.v @@ -37,7 +37,6 @@ module t; initial begin // Check numbering with 0 first - // NC has a bug here if (foos[0].x !== 1'b1) $stop; if (foos[1].x !== 1'b1) $stop; if (foos[2].x !== 1'b0) $stop; diff --git a/test_regress/t/t_interface_inout_tristate.py b/test_regress/t/t_interface_inout_tristate.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_interface_inout_tristate.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_inout_tristate.v b/test_regress/t/t_interface_inout_tristate.v new file mode 100644 index 000000000..36b20fea2 --- /dev/null +++ b/test_regress/t/t_interface_inout_tristate.v @@ -0,0 +1,70 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`ifdef verilator + `define no_optimize(v) $c(v) +`else + `define no_optimize(v) (v) +`endif +// verilog_format: on + +// verilator lint_off MULTIDRIVEN + +interface ifc; + // Tristate net resolved across an inout port connected hierarchically to + // this interface signal (iface.data <-> dut inout port). + wire [7:0] data; +endinterface + +module dut ( + inout wire [7:0] data, + input bit oe, + input logic [7:0] val +); + // The inout port drives the interface net when enabled, releases it (Z) otherwise. + assign data = oe ? val : 8'hzz; +endmodule + +module t; + ifc ifc0 (); + bit oe, top_oe; + logic [7:0] val, topval; + + // A second driver from the top, so resolution is observable from both sides. + assign ifc0.data = top_oe ? topval : 8'hzz; + + dut u ( + .data(ifc0.data), + .oe(oe), + .val(val) + ); + + initial begin + // dut drives the interface net through the inout port. + oe = `no_optimize(1'b1); + val = `no_optimize(8'hA5); + top_oe = `no_optimize(1'b0); + topval = `no_optimize(8'h00); + #1; + `checkh(ifc0.data, 8'hA5); + // top drives, dut releases. + oe = `no_optimize(1'b0); + top_oe = `no_optimize(1'b1); + topval = `no_optimize(8'h3C); + #1; + `checkh(ifc0.data, 8'h3C); + // Neither drives: net floats to Z. + oe = `no_optimize(1'b0); + top_oe = `no_optimize(1'b0); + #1; + `checkh(ifc0.data, 8'hzz); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_param_class_bits.py b/test_regress/t/t_interface_param_class_bits.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_interface_param_class_bits.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_param_class_bits.v b/test_regress/t/t_interface_param_class_bits.v new file mode 100644 index 000000000..35f80c334 --- /dev/null +++ b/test_regress/t/t_interface_param_class_bits.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// A class type parameter whose default reads $bits of a sibling type +// parameter must not freeze that sibling at its default width when other +// parameters of the interface are overridden (#7711). +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +class cls #( + int width +); +endclass + +interface ifc #( + parameter int width = 8, + parameter type dtype = logic [width-1:0], + parameter type cparam = cls#($bits(dtype)) +); + dtype data; +endinterface + +module t; + // width is overridden, dtype keeps its default logic[width-1:0], and the + // class type parameter is overridden. dtype must follow width (1 bit). + ifc #( + .width(1), + .cparam(cls #(1)) + ) inst1 (); + // Same interface left at its default width (8 bits) must still work. + ifc inst8 (); + + always_comb inst1.data = 1'b0; + + initial begin + if ($bits(inst1.data) != 1) begin + $write("%%Error: $bits(inst1.data)=%0d exp=1\n", $bits(inst1.data)); + $stop; + end + if ($bits(inst8.data) != 8) begin + $write("%%Error: $bits(inst8.data)=%0d exp=8\n", $bits(inst8.data)); + $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_tristate_plain_xhier.py b/test_regress/t/t_interface_tristate_plain_xhier.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_interface_tristate_plain_xhier.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_tristate_plain_xhier.v b/test_regress/t/t_interface_tristate_plain_xhier.v new file mode 100644 index 000000000..d7c7d6ca6 --- /dev/null +++ b/test_regress/t/t_interface_tristate_plain_xhier.v @@ -0,0 +1,104 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`ifdef verilator + `define no_optimize(v) $c(v) +`else + `define no_optimize(v) (v) +`endif +// verilog_format: on + +// verilator lint_off MULTIDRIVEN + +interface ifc; + wire [1:0] w; + // Self-contained interface-internal tristate driver: 'z while en==0, so any + // external driver must win the net resolution. no_optimize keeps the driver + // values from being constant-folded, so the resolution runs at run time. + logic en; + logic [1:0] wint; + assign en = `no_optimize(1'b0); + assign wint = `no_optimize(2'b11); + assign w = en ? wint : 2'bzz; + // Read the resolved net from inside the interface (a consumer's view), so the + // check sees the true resolution, not the driving module's own local copy. + function automatic logic [1:0] get_w(); + return w; + endfunction +endinterface + +// Interface net made tristate only by an external 'z driver (no internal driver). +interface ifc_tri; + tri [1:0] w; +endinterface + +module driver ( + ifc io, + input logic [1:0] din +); + // Plain (non-Z) cross-hierarchy driver of an interface tristate net. + // This module's own tristate graph has no 'z on io.w, so before the fix this + // contribution was silently dropped and io.w resolved to the interface-internal + // 'z (== 0) only. + assign io.w = din; +endmodule + +module driver_tri ( + ifc_tri io, + input logic [1:0] din +); + assign io.w = din; +endmodule + +module zdriver ( + ifc_tri io +); + assign io.w = 2'bzz; +endmodule + +module t; + // u_a, u_b: interface nets driven plainly from the top module (depth-0 xref). + // u_c: interface net driven plainly from a child module (depth-1 xref). + ifc u_a (); + ifc u_b (); + ifc u_c (); + // u_e: net is tristate only via zdriver's external 'z; the plain driver must + // still win (the interface itself has no internal driver). + ifc_tri u_e (); + + logic [1:0] va, vb, vc, ve; + assign va = `no_optimize(2'b01); + assign vb = `no_optimize(2'b10); + assign vc = `no_optimize(2'b11); + assign ve = `no_optimize(2'b01); + + assign u_a.w = va; // plain top-level driver + assign u_b.w = vb; // plain top-level driver + driver u_drv ( + .io(u_c), + .din(vc) + ); // plain driver in a child module + driver_tri u_pdrv ( + .io(u_e), + .din(ve) + ); // plain driver of an externally-tristated net + zdriver u_zdrv (.io(u_e)); // external 'z driver + + initial begin + #1; + // The plain external driver must win over the interface-internal 'z. + `checkh(u_a.get_w(), 2'b01); + `checkh(u_b.get_w(), 2'b10); + `checkh(u_c.get_w(), 2'b11); + // The plain driver must win over an external-only 'z (no internal driver). + `checkh(u_e.w, 2'b01); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_tristate_plain_xhier_noinl.py b/test_regress/t/t_interface_tristate_plain_xhier_noinl.py new file mode 100755 index 000000000..12aff0c40 --- /dev/null +++ b/test_regress/t/t_interface_tristate_plain_xhier_noinl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t_interface_tristate_plain_xhier.v" + +test.compile(timing_loop=True, verilator_flags2=['--timing', '-fno-inline']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_func_module_call.py b/test_regress/t/t_interface_virtual_func_module_call.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_module_call.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_func_module_call.v b/test_regress/t/t_interface_virtual_func_module_call.v new file mode 100644 index 000000000..7489bead4 --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_module_call.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +interface iface; + int cnt = 0; + function void bump(); + cnt++; + endfunction +endinterface + +module t; + iface theIf (); + virtual iface vif; + initial begin + vif = theIf; + vif.bump(); + if (theIf.cnt !== 1) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_virtual_sub_iface_method.py b/test_regress/t/t_interface_virtual_sub_iface_method.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_interface_virtual_sub_iface_method.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_sub_iface_method.v b/test_regress/t/t_interface_virtual_sub_iface_method.v new file mode 100644 index 000000000..d5d9a59fe --- /dev/null +++ b/test_regress/t/t_interface_virtual_sub_iface_method.v @@ -0,0 +1,72 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +interface inner_if; + logic [7:0] cnt = 0; + function automatic void bump(); + cnt = cnt + 1; + endfunction + function automatic logic [7:0] get(); + return cnt; + endfunction + task automatic addn(input logic [7:0] n); + cnt = cnt + n; + endtask +endinterface + +interface mid_if; + inner_if sub (); +endinterface + +interface data_if; + logic [7:0] val = 0; +endinterface + +interface outer_if; + inner_if a (); + inner_if b (); + mid_if m (); + data_if d (); +endinterface + +class driver_c; + virtual outer_if vif; + task run(); + vif.a.bump(); // sibling a: void function, +1 + vif.b.bump(); // sibling b: void function, +1 + vif.b.addn(8'd5); // sibling b: task with arg, +5 + vif.m.sub.bump(); // two-level nesting, +1 + vif.d.val = 8'd99; // sub-interface variable write via vif (no method) + endtask + function logic [7:0] read_a(); + return vif.a.get(); // function returning value via vif sub-interface + endfunction +endclass + +module t; + outer_if oif (); + driver_c drv; + initial begin + drv = new(); + drv.vif = oif; + drv.run(); + // Per-instance dispatch: a, b and m.sub are distinct instances. + `checkd(oif.a.cnt, 8'd1) + `checkd(oif.b.cnt, 8'd6) + `checkd(oif.m.sub.cnt, 8'd1) + `checkd(drv.read_a(), 8'd1) + `checkd(oif.d.val, 8'd99) + // Direct (non-virtual) sub-interface method call: covers the non-virtual dtype path. + oif.a.bump(); + `checkd(oif.a.cnt, 8'd2) + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_json_only_begin_hier.out b/test_regress/t/t_json_only_begin_hier.out index 8e779675c..68cc2bd11 100644 --- a/test_regress/t/t_json_only_begin_hier.out +++ b/test_regress/t/t_json_only_begin_hier.out @@ -2,7 +2,7 @@ "modulesp": [ {"type":"MODULE","name":"test","addr":"(E)","loc":"d,21:8,21:12","origName":"test","verilogName":"test","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"N","addr":"(F)","loc":"d,22:10,22:11","dtypep":"(G)","origName":"N","verilogName":"N","direction":"NONE","isUsedLoopIdx":true,"lifetime":"VSTATICI","varType":"GENVAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"N","addr":"(F)","loc":"d,22:10,22:11","dtypep":"(G)","origName":"N","verilogName":"N","direction":"NONE","isUsedLoopIdx":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"GENVAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"GENBLOCK","name":"FOR_GENERATE","addr":"(H)","loc":"d,24:5,24:8","implied":true,"genforp": [],"itemsp": []}, {"type":"GENBLOCK","name":"FOR_GENERATE[0]","addr":"(I)","loc":"d,25:14,25:24","genforp": [], "itemsp": [ diff --git a/test_regress/t/t_json_only_debugcheck.py b/test_regress/t/t_json_only_debugcheck.py index 3137b8632..aeef0a17b 100755 --- a/test_regress/t/t_json_only_debugcheck.py +++ b/test_regress/t/t_json_only_debugcheck.py @@ -16,7 +16,7 @@ test.top_filename = "t/t_enum_type_methods.v" out_filename = test.obj_dir + "/V" + test.name + ".tree.json" test.compile(verilator_flags2=[ - '--no-std', '--debug-check', '--no-json-edit-nums', '--flatten', '--inline-cfuncs', '0' + '--no-std', '--debug-check', '--no-json-edit-nums', '--flatten', '-fno-inline-cfuncs' ], verilator_make_gmake=False, make_top_shell=False, diff --git a/test_regress/t/t_json_only_first.out b/test_regress/t/t_json_only_first.out index f32e2e41d..0f60e9c25 100644 --- a/test_regress/t/t_json_only_first.out +++ b/test_regress/t/t_json_only_first.out @@ -2,13 +2,13 @@ "modulesp": [ {"type":"MODULE","name":"t","addr":"(E)","loc":"d,7:8,7:9","origName":"t","verilogName":"t","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"q","addr":"(F)","loc":"d,16:21,16:22","dtypep":"(G)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(F)","loc":"d,16:21,16:22","dtypep":"(G)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(H)","loc":"d,14:9,14:12","dtypep":"(I)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(J)","loc":"d,15:15,15:16","dtypep":"(G)","origName":"d","verilogName":"d","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"between","addr":"(K)","loc":"d,18:15,18:22","dtypep":"(G)","origName":"between","verilogName":"between","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"direct_named","addr":"(L)","loc":"d,19:9,19:21","dtypep":"(I)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"computed_named","addr":"(M)","loc":"d,20:9,20:23","dtypep":"(I)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"anonymous_expr","addr":"(N)","loc":"d,21:9,21:23","dtypep":"(I)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"between","addr":"(K)","loc":"d,18:15,18:22","dtypep":"(G)","origName":"between","verilogName":"between","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"direct_named","addr":"(L)","loc":"d,19:9,19:21","dtypep":"(I)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"computed_named","addr":"(M)","loc":"d,20:9,20:23","dtypep":"(I)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"anonymous_expr","addr":"(N)","loc":"d,21:9,21:23","dtypep":"(I)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"S_IDLE","addr":"(O)","loc":"d,23:26,23:32","dtypep":"(P)","origName":"S_IDLE","verilogName":"S_IDLE","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"2'h0","addr":"(Q)","loc":"d,23:35,23:40","dtypep":"(R)"} @@ -122,7 +122,7 @@ "stmtsp": [ {"type":"VAR","name":"clk","addr":"(QC)","loc":"d,62:11,62:14","dtypep":"(I)","origName":"clk","verilogName":"clk","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(KC)","loc":"d,63:17,63:18","dtypep":"(G)","origName":"d","verilogName":"d","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"q","addr":"(NC)","loc":"d,64:23,64:24","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(NC)","loc":"d,64:23,64:24","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(SC)","loc":"d,67:12,67:13","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(TC)","loc":"d,67:12,67:13","dtypep":"(G)", @@ -142,7 +142,7 @@ ],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(CC)","loc":"d,50:11,50:14","dtypep":"(I)","origName":"clk","verilogName":"clk","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(FC)","loc":"d,51:23,51:24","dtypep":"(G)","origName":"d","verilogName":"d","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"q","addr":"(ZB)","loc":"d,52:30,52:31","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(ZB)","loc":"d,52:30,52:31","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"IGNORED","addr":"(AD)","loc":"d,55:14,55:21","dtypep":"(XC)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"32'sh1","addr":"(BD)","loc":"d,55:24,55:25","dtypep":"(ZC)"} diff --git a/test_regress/t/t_json_only_flat.out b/test_regress/t/t_json_only_flat.out index 727724001..6b3d6ca6c 100644 --- a/test_regress/t/t_json_only_flat.out +++ b/test_regress/t/t_json_only_flat.out @@ -2,16 +2,16 @@ "modulesp": [ {"type":"MODULE","name":"$root","addr":"(F)","loc":"d,7:8,7:9","origName":"$root","verilogName":"$root","level":1,"modPublic":true,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"q","addr":"(G)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(G)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(I)","loc":"d,14:9,14:12","dtypep":"(J)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(K)","loc":"d,15:15,15:16","dtypep":"(H)","origName":"d","verilogName":"d","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.q","addr":"(L)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.q","addr":"(L)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.clk","addr":"(M)","loc":"d,14:9,14:12","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.d","addr":"(N)","loc":"d,15:15,15:16","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.between","addr":"(O)","loc":"d,18:15,18:22","dtypep":"(H)","origName":"between","verilogName":"between","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.direct_named","addr":"(P)","loc":"d,19:9,19:21","dtypep":"(J)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.computed_named","addr":"(Q)","loc":"d,20:9,20:23","dtypep":"(J)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.anonymous_expr","addr":"(R)","loc":"d,21:9,21:23","dtypep":"(J)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.between","addr":"(O)","loc":"d,18:15,18:22","dtypep":"(H)","origName":"between","verilogName":"between","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.direct_named","addr":"(P)","loc":"d,19:9,19:21","dtypep":"(J)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.computed_named","addr":"(Q)","loc":"d,20:9,20:23","dtypep":"(J)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.anonymous_expr","addr":"(R)","loc":"d,21:9,21:23","dtypep":"(J)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.S_IDLE","addr":"(S)","loc":"d,23:26,23:32","dtypep":"(T)","origName":"S_IDLE","verilogName":"S_IDLE","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"2'h0","addr":"(U)","loc":"d,23:35,23:40","dtypep":"(V)"} @@ -24,20 +24,20 @@ "valuep": [ {"type":"CONST","name":"2'h2","addr":"(Z)","loc":"d,25:43,25:44","dtypep":"(T)"} ],"attrsp": []}, - {"type":"VAR","name":"t.cell1.WIDTH","addr":"(AB)","loc":"d,48:15,48:20","dtypep":"(BB)","origName":"WIDTH","verilogName":"WIDTH","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"GPARAM","dtypeName":"logic","isGParam":true,"isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], + {"type":"VAR","name":"t.cell2.clk","addr":"(AB)","loc":"d,62:11,62:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell2.d","addr":"(BB)","loc":"d,63:17,63:18","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell2.q","addr":"(CB)","loc":"d,64:23,64:24","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell1.WIDTH","addr":"(DB)","loc":"d,48:15,48:20","dtypep":"(EB)","origName":"WIDTH","verilogName":"WIDTH","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"GPARAM","dtypeName":"logic","isGParam":true,"isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ - {"type":"CONST","name":"32'sh4","addr":"(CB)","loc":"d,32:14,32:15","dtypep":"(DB)"} + {"type":"CONST","name":"32'sh4","addr":"(FB)","loc":"d,32:14,32:15","dtypep":"(GB)"} ],"attrsp": []}, - {"type":"VAR","name":"t.cell1.clk","addr":"(EB)","loc":"d,50:11,50:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell1.d","addr":"(FB)","loc":"d,51:23,51:24","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell1.q","addr":"(GB)","loc":"d,52:30,52:31","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell1.IGNORED","addr":"(HB)","loc":"d,55:14,55:21","dtypep":"(BB)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], + {"type":"VAR","name":"t.cell1.clk","addr":"(HB)","loc":"d,50:11,50:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell1.d","addr":"(IB)","loc":"d,51:23,51:24","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell1.q","addr":"(JB)","loc":"d,52:30,52:31","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell1.IGNORED","addr":"(KB)","loc":"d,55:14,55:21","dtypep":"(EB)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ - {"type":"CONST","name":"32'sh1","addr":"(IB)","loc":"d,55:24,55:25","dtypep":"(DB)"} + {"type":"CONST","name":"32'sh1","addr":"(LB)","loc":"d,55:24,55:25","dtypep":"(GB)"} ],"attrsp": []}, - {"type":"VAR","name":"t.cell2.clk","addr":"(JB)","loc":"d,62:11,62:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell2.d","addr":"(KB)","loc":"d,63:17,63:18","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell2.q","addr":"(LB)","loc":"d,64:23,64:24","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"TOPSCOPE","name":"","addr":"(E)","loc":"d,7:8,7:9","senTreesp": [], "scopep": [ {"type":"SCOPE","name":"TOP","addr":"(MB)","loc":"d,7:8,7:9","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(F)", @@ -85,74 +85,74 @@ {"type":"VARSCOPE","name":"t.S_IDLE","addr":"(JC)","loc":"d,23:26,23:32","dtypep":"(T)","isTrace":true,"scopep":"(MB)","varp":"(S)"}, {"type":"VARSCOPE","name":"t.S_FETCH","addr":"(KC)","loc":"d,24:26,24:33","dtypep":"(T)","isTrace":true,"scopep":"(MB)","varp":"(W)"}, {"type":"VARSCOPE","name":"t.S_EXEC","addr":"(LC)","loc":"d,25:26,25:32","dtypep":"(T)","isTrace":true,"scopep":"(MB)","varp":"(Y)"}, - {"type":"VARSCOPE","name":"t.cell1.WIDTH","addr":"(MC)","loc":"d,48:15,48:20","dtypep":"(BB)","isTrace":true,"scopep":"(MB)","varp":"(AB)"}, - {"type":"VARSCOPE","name":"t.cell1.clk","addr":"(NC)","loc":"d,50:11,50:14","dtypep":"(J)","isTrace":true,"scopep":"(MB)","varp":"(EB)"}, - {"type":"ALWAYS","name":"","addr":"(OC)","loc":"d,50:11,50:14","keyword":"cont_assign","sentreep": [], + {"type":"VARSCOPE","name":"t.cell2.clk","addr":"(MC)","loc":"d,62:11,62:14","dtypep":"(J)","isTrace":true,"scopep":"(MB)","varp":"(AB)"}, + {"type":"ALWAYS","name":"","addr":"(NC)","loc":"d,62:11,62:14","keyword":"cont_assign","sentreep": [], "stmtsp": [ - {"type":"ASSIGNW","name":"","addr":"(PC)","loc":"d,50:11,50:14","dtypep":"(J)", + {"type":"ASSIGNW","name":"","addr":"(OC)","loc":"d,62:11,62:14","dtypep":"(J)", "rhsp": [ - {"type":"VARREF","name":"clk","addr":"(QC)","loc":"d,50:11,50:14","dtypep":"(J)","access":"RD","varp":"(I)","varScopep":"(OB)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"clk","addr":"(PC)","loc":"d,62:11,62:14","dtypep":"(J)","access":"RD","varp":"(I)","varScopep":"(OB)","classOrPackagep":"UNLINKED"} ], "lhsp": [ - {"type":"VARREF","name":"t.cell1.clk","addr":"(RC)","loc":"d,50:11,50:14","dtypep":"(J)","access":"WR","varp":"(EB)","varScopep":"(NC)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"t.cell2.clk","addr":"(QC)","loc":"d,62:11,62:14","dtypep":"(J)","access":"WR","varp":"(AB)","varScopep":"(MC)","classOrPackagep":"UNLINKED"} ],"timingControlp": [],"strengthSpecp": []} ]}, - {"type":"VARSCOPE","name":"t.cell1.d","addr":"(SC)","loc":"d,51:23,51:24","dtypep":"(H)","isTrace":true,"scopep":"(MB)","varp":"(FB)"}, - {"type":"ALWAYS","name":"","addr":"(TC)","loc":"d,51:23,51:24","keyword":"cont_assign","sentreep": [], + {"type":"VARSCOPE","name":"t.cell2.d","addr":"(RC)","loc":"d,63:17,63:18","dtypep":"(H)","isTrace":true,"scopep":"(MB)","varp":"(BB)"}, + {"type":"ALWAYS","name":"","addr":"(SC)","loc":"d,63:17,63:18","keyword":"cont_assign","sentreep": [], "stmtsp": [ - {"type":"ASSIGNW","name":"","addr":"(UC)","loc":"d,51:23,51:24","dtypep":"(H)", + {"type":"ASSIGNW","name":"","addr":"(TC)","loc":"d,63:17,63:18","dtypep":"(H)", "rhsp": [ - {"type":"VARREF","name":"d","addr":"(VC)","loc":"d,51:23,51:24","dtypep":"(H)","access":"RD","varp":"(K)","varScopep":"(PB)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"t.between","addr":"(UC)","loc":"d,63:17,63:18","dtypep":"(H)","access":"RD","varp":"(O)","varScopep":"(FC)","classOrPackagep":"UNLINKED"} ], "lhsp": [ - {"type":"VARREF","name":"t.cell1.d","addr":"(WC)","loc":"d,51:23,51:24","dtypep":"(H)","access":"WR","varp":"(FB)","varScopep":"(SC)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"t.cell2.d","addr":"(VC)","loc":"d,63:17,63:18","dtypep":"(H)","access":"WR","varp":"(BB)","varScopep":"(RC)","classOrPackagep":"UNLINKED"} ],"timingControlp": [],"strengthSpecp": []} ]}, - {"type":"VARSCOPE","name":"t.cell1.q","addr":"(XC)","loc":"d,52:30,52:31","dtypep":"(H)","isTrace":true,"scopep":"(MB)","varp":"(GB)"}, - {"type":"ALWAYS","name":"","addr":"(YC)","loc":"d,52:30,52:31","keyword":"cont_assign","sentreep": [], + {"type":"VARSCOPE","name":"t.cell2.q","addr":"(WC)","loc":"d,64:23,64:24","dtypep":"(H)","isTrace":true,"scopep":"(MB)","varp":"(CB)"}, + {"type":"ALWAYS","name":"","addr":"(XC)","loc":"d,64:23,64:24","keyword":"cont_assign","sentreep": [], "stmtsp": [ - {"type":"ASSIGNW","name":"","addr":"(ZC)","loc":"d,52:30,52:31","dtypep":"(H)", + {"type":"ASSIGNW","name":"","addr":"(YC)","loc":"d,64:23,64:24","dtypep":"(H)", "rhsp": [ - {"type":"VARREF","name":"t.between","addr":"(AD)","loc":"d,52:30,52:31","dtypep":"(H)","access":"RD","varp":"(O)","varScopep":"(FC)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"q","addr":"(ZC)","loc":"d,64:23,64:24","dtypep":"(H)","access":"RD","varp":"(G)","varScopep":"(NB)","classOrPackagep":"UNLINKED"} ], "lhsp": [ - {"type":"VARREF","name":"t.cell1.q","addr":"(BD)","loc":"d,52:30,52:31","dtypep":"(H)","access":"WR","varp":"(GB)","varScopep":"(XC)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"t.cell2.q","addr":"(AD)","loc":"d,64:23,64:24","dtypep":"(H)","access":"WR","varp":"(CB)","varScopep":"(WC)","classOrPackagep":"UNLINKED"} ],"timingControlp": [],"strengthSpecp": []} ]}, - {"type":"VARSCOPE","name":"t.cell1.IGNORED","addr":"(CD)","loc":"d,55:14,55:21","dtypep":"(BB)","isTrace":true,"scopep":"(MB)","varp":"(HB)"}, - {"type":"VARSCOPE","name":"t.cell2.clk","addr":"(DD)","loc":"d,62:11,62:14","dtypep":"(J)","isTrace":true,"scopep":"(MB)","varp":"(JB)"}, - {"type":"ALWAYS","name":"","addr":"(ED)","loc":"d,62:11,62:14","keyword":"cont_assign","sentreep": [], + {"type":"VARSCOPE","name":"t.cell1.WIDTH","addr":"(BD)","loc":"d,48:15,48:20","dtypep":"(EB)","isTrace":true,"scopep":"(MB)","varp":"(DB)"}, + {"type":"VARSCOPE","name":"t.cell1.clk","addr":"(CD)","loc":"d,50:11,50:14","dtypep":"(J)","isTrace":true,"scopep":"(MB)","varp":"(HB)"}, + {"type":"ALWAYS","name":"","addr":"(DD)","loc":"d,50:11,50:14","keyword":"cont_assign","sentreep": [], "stmtsp": [ - {"type":"ASSIGNW","name":"","addr":"(FD)","loc":"d,62:11,62:14","dtypep":"(J)", + {"type":"ASSIGNW","name":"","addr":"(ED)","loc":"d,50:11,50:14","dtypep":"(J)", "rhsp": [ - {"type":"VARREF","name":"clk","addr":"(GD)","loc":"d,62:11,62:14","dtypep":"(J)","access":"RD","varp":"(I)","varScopep":"(OB)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"clk","addr":"(FD)","loc":"d,50:11,50:14","dtypep":"(J)","access":"RD","varp":"(I)","varScopep":"(OB)","classOrPackagep":"UNLINKED"} ], "lhsp": [ - {"type":"VARREF","name":"t.cell2.clk","addr":"(HD)","loc":"d,62:11,62:14","dtypep":"(J)","access":"WR","varp":"(JB)","varScopep":"(DD)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"t.cell1.clk","addr":"(GD)","loc":"d,50:11,50:14","dtypep":"(J)","access":"WR","varp":"(HB)","varScopep":"(CD)","classOrPackagep":"UNLINKED"} ],"timingControlp": [],"strengthSpecp": []} ]}, - {"type":"VARSCOPE","name":"t.cell2.d","addr":"(ID)","loc":"d,63:17,63:18","dtypep":"(H)","isTrace":true,"scopep":"(MB)","varp":"(KB)"}, - {"type":"ALWAYS","name":"","addr":"(JD)","loc":"d,63:17,63:18","keyword":"cont_assign","sentreep": [], + {"type":"VARSCOPE","name":"t.cell1.d","addr":"(HD)","loc":"d,51:23,51:24","dtypep":"(H)","isTrace":true,"scopep":"(MB)","varp":"(IB)"}, + {"type":"ALWAYS","name":"","addr":"(ID)","loc":"d,51:23,51:24","keyword":"cont_assign","sentreep": [], "stmtsp": [ - {"type":"ASSIGNW","name":"","addr":"(KD)","loc":"d,63:17,63:18","dtypep":"(H)", + {"type":"ASSIGNW","name":"","addr":"(JD)","loc":"d,51:23,51:24","dtypep":"(H)", "rhsp": [ - {"type":"VARREF","name":"t.between","addr":"(LD)","loc":"d,63:17,63:18","dtypep":"(H)","access":"RD","varp":"(O)","varScopep":"(FC)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"d","addr":"(KD)","loc":"d,51:23,51:24","dtypep":"(H)","access":"RD","varp":"(K)","varScopep":"(PB)","classOrPackagep":"UNLINKED"} ], "lhsp": [ - {"type":"VARREF","name":"t.cell2.d","addr":"(MD)","loc":"d,63:17,63:18","dtypep":"(H)","access":"WR","varp":"(KB)","varScopep":"(ID)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"t.cell1.d","addr":"(LD)","loc":"d,51:23,51:24","dtypep":"(H)","access":"WR","varp":"(IB)","varScopep":"(HD)","classOrPackagep":"UNLINKED"} ],"timingControlp": [],"strengthSpecp": []} ]}, - {"type":"VARSCOPE","name":"t.cell2.q","addr":"(ND)","loc":"d,64:23,64:24","dtypep":"(H)","isTrace":true,"scopep":"(MB)","varp":"(LB)"}, - {"type":"ALWAYS","name":"","addr":"(OD)","loc":"d,64:23,64:24","keyword":"cont_assign","sentreep": [], + {"type":"VARSCOPE","name":"t.cell1.q","addr":"(MD)","loc":"d,52:30,52:31","dtypep":"(H)","isTrace":true,"scopep":"(MB)","varp":"(JB)"}, + {"type":"ALWAYS","name":"","addr":"(ND)","loc":"d,52:30,52:31","keyword":"cont_assign","sentreep": [], "stmtsp": [ - {"type":"ASSIGNW","name":"","addr":"(PD)","loc":"d,64:23,64:24","dtypep":"(H)", + {"type":"ASSIGNW","name":"","addr":"(OD)","loc":"d,52:30,52:31","dtypep":"(H)", "rhsp": [ - {"type":"VARREF","name":"q","addr":"(QD)","loc":"d,64:23,64:24","dtypep":"(H)","access":"RD","varp":"(G)","varScopep":"(NB)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"t.between","addr":"(PD)","loc":"d,52:30,52:31","dtypep":"(H)","access":"RD","varp":"(O)","varScopep":"(FC)","classOrPackagep":"UNLINKED"} ], "lhsp": [ - {"type":"VARREF","name":"t.cell2.q","addr":"(RD)","loc":"d,64:23,64:24","dtypep":"(H)","access":"WR","varp":"(LB)","varScopep":"(ND)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"t.cell1.q","addr":"(QD)","loc":"d,52:30,52:31","dtypep":"(H)","access":"WR","varp":"(JB)","varScopep":"(MD)","classOrPackagep":"UNLINKED"} ],"timingControlp": [],"strengthSpecp": []} - ]} + ]}, + {"type":"VARSCOPE","name":"t.cell1.IGNORED","addr":"(RD)","loc":"d,55:14,55:21","dtypep":"(EB)","isTrace":true,"scopep":"(MB)","varp":"(KB)"} ], "blocksp": [ {"type":"ALWAYS","name":"","addr":"(SD)","loc":"d,27:23,27:24","keyword":"cont_assign","sentreep": [], @@ -221,34 +221,34 @@ {"type":"VARREF","name":"t.anonymous_expr","addr":"(RE)","loc":"d,29:10,29:24","dtypep":"(J)","access":"WR","varp":"(R)","varScopep":"(IC)","classOrPackagep":"UNLINKED"} ],"timingControlp": [],"strengthSpecp": []} ]}, - {"type":"ALWAYS","name":"","addr":"(SE)","loc":"d,57:3,57:9","keyword":"always", + {"type":"ALWAYS","name":"","addr":"(SE)","loc":"d,67:12,67:13","keyword":"cont_assign","sentreep": [], + "stmtsp": [ + {"type":"ASSIGNW","name":"","addr":"(TE)","loc":"d,67:12,67:13","dtypep":"(H)", + "rhsp": [ + {"type":"VARREF","name":"t.between","addr":"(UE)","loc":"d,67:14,67:15","dtypep":"(H)","access":"RD","varp":"(O)","varScopep":"(FC)","classOrPackagep":"UNLINKED"} + ], + "lhsp": [ + {"type":"VARREF","name":"q","addr":"(VE)","loc":"d,67:10,67:11","dtypep":"(H)","access":"WR","varp":"(G)","varScopep":"(NB)","classOrPackagep":"UNLINKED"} + ],"timingControlp": [],"strengthSpecp": []} + ]}, + {"type":"ALWAYS","name":"","addr":"(WE)","loc":"d,57:3,57:9","keyword":"always", "sentreep": [ - {"type":"SENTREE","name":"","addr":"(TE)","loc":"d,57:10,57:11", + {"type":"SENTREE","name":"","addr":"(XE)","loc":"d,57:10,57:11", "sensesp": [ - {"type":"SENITEM","name":"","addr":"(UE)","loc":"d,57:12,57:19","edgeType":"POS", + {"type":"SENITEM","name":"","addr":"(YE)","loc":"d,57:12,57:19","edgeType":"POS", "sensp": [ - {"type":"VARREF","name":"clk","addr":"(VE)","loc":"d,57:20,57:23","dtypep":"(J)","access":"RD","varp":"(I)","varScopep":"(OB)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"clk","addr":"(ZE)","loc":"d,57:20,57:23","dtypep":"(J)","access":"RD","varp":"(I)","varScopep":"(OB)","classOrPackagep":"UNLINKED"} ],"condp": []} ]} ], "stmtsp": [ - {"type":"ASSIGNDLY","name":"","addr":"(WE)","loc":"d,57:27,57:29","dtypep":"(H)", + {"type":"ASSIGNDLY","name":"","addr":"(AF)","loc":"d,57:27,57:29","dtypep":"(H)", "rhsp": [ - {"type":"VARREF","name":"d","addr":"(XE)","loc":"d,57:30,57:31","dtypep":"(H)","access":"RD","varp":"(K)","varScopep":"(PB)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"d","addr":"(BF)","loc":"d,57:30,57:31","dtypep":"(H)","access":"RD","varp":"(K)","varScopep":"(PB)","classOrPackagep":"UNLINKED"} ], "lhsp": [ - {"type":"VARREF","name":"t.between","addr":"(YE)","loc":"d,57:25,57:26","dtypep":"(H)","access":"WR","varp":"(O)","varScopep":"(FC)","classOrPackagep":"UNLINKED"} + {"type":"VARREF","name":"t.between","addr":"(CF)","loc":"d,57:25,57:26","dtypep":"(H)","access":"WR","varp":"(O)","varScopep":"(FC)","classOrPackagep":"UNLINKED"} ],"timingControlp": []} - ]}, - {"type":"ALWAYS","name":"","addr":"(ZE)","loc":"d,67:12,67:13","keyword":"cont_assign","sentreep": [], - "stmtsp": [ - {"type":"ASSIGNW","name":"","addr":"(AF)","loc":"d,67:12,67:13","dtypep":"(H)", - "rhsp": [ - {"type":"VARREF","name":"t.between","addr":"(BF)","loc":"d,67:14,67:15","dtypep":"(H)","access":"RD","varp":"(O)","varScopep":"(FC)","classOrPackagep":"UNLINKED"} - ], - "lhsp": [ - {"type":"VARREF","name":"q","addr":"(CF)","loc":"d,67:10,67:11","dtypep":"(H)","access":"WR","varp":"(G)","varScopep":"(NB)","classOrPackagep":"UNLINKED"} - ],"timingControlp": [],"strengthSpecp": []} ]} ],"inlinesp": []} ]} @@ -260,11 +260,11 @@ {"type":"BASICDTYPE","name":"bit","addr":"(V)","loc":"d,23:35,23:40","dtypep":"(V)","keyword":"bit","range":"1:0","generic":true,"rangep": []}, {"type":"BASICDTYPE","name":"logic","addr":"(J)","loc":"d,27:32,27:34","dtypep":"(J)","keyword":"logic","generic":true,"rangep": []}, {"type":"BASICDTYPE","name":"logic","addr":"(T)","loc":"d,23:14,23:19","dtypep":"(T)","keyword":"logic","range":"1:0","generic":true,"rangep": []}, - {"type":"BASICDTYPE","name":"logic","addr":"(BB)","loc":"d,48:15,48:20","dtypep":"(BB)","keyword":"logic","range":"31:0","generic":true,"signed":true,"rangep": []}, + {"type":"BASICDTYPE","name":"logic","addr":"(EB)","loc":"d,48:15,48:20","dtypep":"(EB)","keyword":"logic","range":"31:0","generic":true,"signed":true,"rangep": []}, {"type":"BASICDTYPE","name":"logic","addr":"(H)","loc":"d,16:15,16:16","dtypep":"(H)","keyword":"logic","range":"3:0","generic":true,"rangep": []}, {"type":"BASICDTYPE","name":"logic","addr":"(AE)","loc":"d,27:26,27:27","dtypep":"(AE)","keyword":"logic","range":"1:0","generic":true,"signed":true,"rangep": []}, {"type":"BASICDTYPE","name":"bit","addr":"(VD)","loc":"d,27:32,27:34","dtypep":"(VD)","keyword":"bit","generic":true,"rangep": []}, - {"type":"BASICDTYPE","name":"bit","addr":"(DB)","loc":"d,29:48,29:49","dtypep":"(DB)","keyword":"bit","range":"31:0","generic":true,"signed":true,"rangep": []} + {"type":"BASICDTYPE","name":"bit","addr":"(GB)","loc":"d,29:48,29:49","dtypep":"(GB)","keyword":"bit","range":"31:0","generic":true,"signed":true,"rangep": []} ]}, {"type":"CONSTPOOL","name":"","addr":"(D)","loc":"a,0:0,0:0", "modulep": [ diff --git a/test_regress/t/t_json_only_flat_vlvbound.out b/test_regress/t/t_json_only_flat_vlvbound.out index ff23ac385..1725da9ef 100644 --- a/test_regress/t/t_json_only_flat_vlvbound.out +++ b/test_regress/t/t_json_only_flat_vlvbound.out @@ -4,12 +4,12 @@ "stmtsp": [ {"type":"VAR","name":"i_a","addr":"(G)","loc":"d,8:24,8:27","dtypep":"(H)","origName":"i_a","verilogName":"i_a","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"i_b","addr":"(I)","loc":"d,9:24,9:27","dtypep":"(H)","origName":"i_b","verilogName":"i_b","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"o_a","addr":"(J)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"o_b","addr":"(L)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"o_a","addr":"(J)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"o_b","addr":"(L)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"vlvbound_test.i_a","addr":"(M)","loc":"d,8:24,8:27","dtypep":"(H)","origName":"i_a","verilogName":"i_a","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"vlvbound_test.i_b","addr":"(N)","loc":"d,9:24,9:27","dtypep":"(H)","origName":"i_b","verilogName":"i_b","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"vlvbound_test.o_a","addr":"(O)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"vlvbound_test.o_b","addr":"(P)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"vlvbound_test.o_a","addr":"(O)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"vlvbound_test.o_b","addr":"(P)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"TOPSCOPE","name":"","addr":"(E)","loc":"d,7:8,7:21","senTreesp": [], "scopep": [ {"type":"SCOPE","name":"TOP","addr":"(Q)","loc":"d,7:8,7:21","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(F)", diff --git a/test_regress/t/t_json_only_primary_io.out b/test_regress/t/t_json_only_primary_io.out index 896aa716b..a611f6917 100644 --- a/test_regress/t/t_json_only_primary_io.out +++ b/test_regress/t/t_json_only_primary_io.out @@ -5,8 +5,8 @@ {"type":"VAR","name":"clk","addr":"(F)","loc":"d,13:9,13:12","dtypep":"(G)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a1","addr":"(H)","loc":"d,14:9,14:11","dtypep":"(G)","origName":"a1","verilogName":"a1","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a2","addr":"(I)","loc":"d,15:9,15:11","dtypep":"(G)","origName":"a2","verilogName":"a2","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"ready","addr":"(J)","loc":"d,16:10,16:15","dtypep":"(G)","origName":"ready","verilogName":"ready","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"ready_reg","addr":"(K)","loc":"d,18:8,18:17","dtypep":"(G)","origName":"ready_reg","verilogName":"ready_reg","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"ready","addr":"(J)","loc":"d,16:10,16:15","dtypep":"(G)","origName":"ready","verilogName":"ready","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"ready_reg","addr":"(K)","loc":"d,18:8,18:17","dtypep":"(G)","origName":"ready_reg","verilogName":"ready_reg","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"CELL","name":"and_cell","addr":"(L)","loc":"d,20:11,20:19","origName":"and_cell","verilogName":"and_cell","modp":"(M)", "pinsp": [ {"type":"PIN","name":"a1","addr":"(N)","loc":"d,21:8,21:10","svDotName":true,"modVarp":"(O)","modPTypep":"UNLINKED", @@ -37,7 +37,7 @@ "stmtsp": [ {"type":"VAR","name":"a1","addr":"(O)","loc":"d,30:16,30:18","dtypep":"(G)","origName":"a1","verilogName":"a1","direction":"INPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a2","addr":"(R)","loc":"d,31:16,31:18","dtypep":"(G)","origName":"a2","verilogName":"a2","direction":"INPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"zn","addr":"(U)","loc":"d,32:17,32:19","dtypep":"(G)","origName":"zn","verilogName":"zn","direction":"OUTPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"zn","addr":"(U)","loc":"d,32:17,32:19","dtypep":"(G)","origName":"zn","verilogName":"zn","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(AB)","loc":"d,34:13,34:14","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(BB)","loc":"d,34:13,34:14","dtypep":"(G)", diff --git a/test_regress/t/t_json_only_tag.out b/test_regress/t/t_json_only_tag.out index c0d396689..c7e9999c6 100644 --- a/test_regress/t/t_json_only_tag.out +++ b/test_regress/t/t_json_only_tag.out @@ -9,7 +9,7 @@ {"type":"CELL","name":"itop","addr":"(L)","loc":"d,29:7,29:11","origName":"itop","verilogName":"itop","modp":"(M)","pinsp": [],"paramsp": [],"rangep": [],"intfRefsp": []}, {"type":"VAR","name":"itop","addr":"(N)","loc":"d,29:7,29:11","dtypep":"(O)","origName":"itop__Viftop","verilogName":"itop__Viftop","direction":"NONE","lifetime":"VSTATICI","varType":"IFACEREF","dtypeName":"","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"this_struct","addr":"(P)","loc":"d,31:13,31:24","dtypep":"(Q)","origName":"this_struct","verilogName":"this_struct","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"dotted","addr":"(R)","loc":"d,33:15,33:21","dtypep":"(S)","origName":"dotted","verilogName":"dotted","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"dotted","addr":"(R)","loc":"d,33:15,33:21","dtypep":"(S)","origName":"dotted","verilogName":"dotted","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(T)","loc":"d,33:22,33:23","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(U)","loc":"d,33:22,33:23","dtypep":"(S)", diff --git a/test_regress/t/t_lint_historical.v b/test_regress/t/t_lint_historical.v index 014c059a9..d92949dc9 100644 --- a/test_regress/t/t_lint_historical.v +++ b/test_regress/t/t_lint_historical.v @@ -40,6 +40,7 @@ module t; // verilator lint_off ENUMITEMWIDTH // verilator lint_off ENUMVALUE // verilator lint_off EOFNEWLINE + // verilator lint_off FINALDLY // verilator lint_off FUNCTIMECTL // verilator lint_off GENCLK // verilator lint_off GENUNNAMED diff --git a/test_regress/t/t_lint_unusedloop_removed_bad.out b/test_regress/t/t_lint_unusedloop_removed_bad.out index 792568e78..16b88ada9 100644 --- a/test_regress/t/t_lint_unusedloop_removed_bad.out +++ b/test_regress/t/t_lint_unusedloop_removed_bad.out @@ -21,12 +21,6 @@ %Warning-UNUSEDLOOP: t/t_lint_unusedloop_removed_bad.v:280:5: Loop is not used and will be optimized out 280 | while (m_2_ticked); | ^~~~~ -%Warning-UNUSEDLOOP: t/t_lint_unusedloop_removed_bad.v:114:5: Loop condition is always false - 114 | while(always_zero < 0) begin - | ^~~~~ -%Warning-UNUSEDLOOP: t/t_lint_unusedloop_removed_bad.v:171:5: Loop condition is always false - 171 | while(always_false) begin - | ^~~~~ %Warning-UNUSEDLOOP: t/t_lint_unusedloop_removed_bad.v:181:5: Loop condition is always false 181 | while(always_zero) begin | ^~~~~ @@ -36,4 +30,10 @@ %Warning-UNUSEDLOOP: t/t_lint_unusedloop_removed_bad.v:190:5: Loop condition is always false 190 | for (int i = 0; i < always_zero; i++) | ^~~ +%Warning-UNUSEDLOOP: t/t_lint_unusedloop_removed_bad.v:171:5: Loop condition is always false + 171 | while(always_false) begin + | ^~~~~ +%Warning-UNUSEDLOOP: t/t_lint_unusedloop_removed_bad.v:114:5: Loop condition is always false + 114 | while(always_zero < 0) begin + | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_math_pow.v b/test_regress/t/t_math_pow.v index 72a617697..35aab1cb3 100644 --- a/test_regress/t/t_math_pow.v +++ b/test_regress/t/t_math_pow.v @@ -244,9 +244,7 @@ module t ( `checkh(67'h0 ** 21'h0, 67'h1); `checkh(67'sh0 ** 21'sh0, 67'sh1); `checkh(67'h10 ** 21'h0, 67'h1); -`ifndef VCS `checkh(61'h7ab3811219 ** 21'ha6e30, 61'h01ea58c703687e81); -`endif if (cyc==0) begin end else if (cyc==1) begin a <= 67'h0; b <= 67'h0; end else if (cyc==2) begin a <= 67'h0; b <= 67'h3; end diff --git a/test_regress/t/t_math_pow3.v b/test_regress/t/t_math_pow3.v index b591618b3..f0c18f8b6 100644 --- a/test_regress/t/t_math_pow3.v +++ b/test_regress/t/t_math_pow3.v @@ -16,22 +16,21 @@ module t; // verilog_format: off initial begin - // NC=67b6cfc1b29a21 VCS=c1b29a20(wrong) IV=67b6cfc1b29a21 Verilator=67b6cfc1b29a21 $display("15 ** 14 = %0x expect 67b6cfc1b29a21", 64'b1111 ** 64'b1110); - // NC=1 VCS=0 IV=0 Verilator=1 (wrong,fixed) $display("15 **-4'sd2 = %0x expect 0 (per IEEE negative power)", ((-4'd1 ** -4'sd2))); - // NC=1 VCS=0 IV=67b6cfc1b29a21(wrong) Verilator=1 $display("15 ** 14 = %0x expect 1 (LSB 4-bits of 67b6cfc1b29a21)", ((-4'd1 ** -4'd2))); - // NC=1 VCS=0 IV=67b6cfc1b29a21(wrong) Verilator=1 $display("15 ** 14 = %0x expect 1 (LSB 4-bits of 67b6cfc1b29a21)", ((4'd15 ** 4'd14))); - // NC=8765432187654321 VCS=8765432187654000(wrong) IV=8765432187654321 Verilator=8765432187654321 $display("64'big ** 1 = %0x expect %0x", 64'h8765432187654321 ** 1, 64'h8765432187654321); $display("\n"); `checkh( (64'b1111 ** 64'b1110), 64'h67b6cfc1b29a21); +`ifndef NC `checkh( (-4'd1 ** -4'sd2), 4'h0); //bug730 +`endif +`ifndef VCS `checkh( (-4'd1 ** -4'd2), 4'h1); `checkh( (4'd15 ** 4'd14), 4'h1); +`endif `checkh( (64'h8765432187654321 ** 4'h1), 64'h8765432187654321); `checkh((-8'sh3 ** 8'h3) , 8'he5 ); // a**b (-27) @@ -70,13 +69,23 @@ module t; `checkh(( 8'h3 ** -8'sh0), 8'h1 ); // a**0 always 1 `checkh(( 8'sh3 ** -8'sh0), 8'h1 ); // a**0 always 1 - `checkh((-8'sh3 ** -8'sh3), 8'h0 ); // 0 (a<-1) // NCVERILOG bug +`ifndef NC + `checkh((-8'sh3 ** -8'sh3), 8'h0 ); // 0 (a<-1) +`endif +`ifndef IVERILOG +`ifndef QUESTA +`ifndef VCS `checkh((-8'sh1 ** -8'sh2), 8'h1 ); // -1^odd=-1, -1^even=1 `checkh((-8'sh1 ** -8'sh3), 8'hff); // -1^odd=-1, -1^even=1 -// `checkh(( 8'h0 ** -8'sh3), 8'hx ); // x // NCVERILOG bug +`endif +`endif +`endif +// `checkh(( 8'h0 ** -8'sh3), 8'hx ); // x `checkh(( 8'h1 ** -8'sh3), 8'h1 ); // 1**b always 1 - `checkh(( 8'h3 ** -8'sh3), 8'h0 ); // 0 // NCVERILOG bug - `checkh(( 8'sh3 ** -8'sh3), 8'h0 ); // 0 // NCVERILOG bug +`ifndef NC + `checkh(( 8'h3 ** -8'sh3), 8'h0 ); // 0 + `checkh(( 8'sh3 ** -8'sh3), 8'h0 ); // 0 +`endif if (fail) $stop; diff --git a/test_regress/t/t_math_signed4.v b/test_regress/t/t_math_signed4.v index bb5d83602..05b7e6f7c 100644 --- a/test_regress/t/t_math_signed4.v +++ b/test_regress/t/t_math_signed4.v @@ -97,11 +97,7 @@ module t; // bug754 w5_u = 4'sb0010 << -2'sd1; // << 3 -`ifdef VCS - `checkh(w5_u, 5'b00000); // VCS E-2014.03 bug -`else - `checkh(w5_u, 5'b10000); // VCS E-2014.03 bug -`endif + `checkh(w5_u, 5'b10000); w5_u = 4'sb1000 << 0; // Sign extends `checkh(w5_u, 5'b11000); diff --git a/test_regress/t/t_math_signed5.v b/test_regress/t/t_math_signed5.v index b87b854f2..aa9a68d79 100644 --- a/test_regress/t/t_math_signed5.v +++ b/test_regress/t/t_math_signed5.v @@ -47,11 +47,7 @@ module t ( /*AUTOARG*/ initial begin // verilator lint_off STMTDLY #1; -`ifdef VCS // I-2014.03 - `checkh({a, b, c, d, e, f, g}, 7'b1101111); -`else `checkh({a, b, c, d, e, f, g}, 7'b1101011); -`endif //====================================================================== @@ -76,20 +72,11 @@ module t ( /*AUTOARG*/ w4_u = ((5'b0 == (5'sb11111 >>> 3'd7))); // Exp 0 Vlt 0 `checkh(w4_u, 4'b0001); w4_u = ((5'b01111 == (5'sb11111 / 5'sd2))); // Strength-reduces to >>> -`ifdef VCS // I-2014.03 - `checkh(w4_u, 4'b0000); // Wrong, gets 5'b0==..., unsigned does not propagate -`else - `checkh(w4_u, 4'b0001); // NC-Verilog, Modelsim, XSim, ... -`endif + `checkh(w4_u, 4'b0001); - // Does == sign propagate from lhs to rhs? Yes, but not in VCS + // Does == sign propagate from lhs to rhs? w4_u = ((5'b01010 == (5'sb11111 / 5'sd3))); // Exp 0 Vlt 0 // Must be signed result (-1/3) to make this result zero -`ifdef VCS // I-2014.03 - `checkh(w4_u, 4'b0000); // Wrong, gets 5'b0==..., unsigned does not propagate - // Somewhat questionable, as spec says division signed depends on only LHS and RHS, however differs from others -`else - `checkh(w4_u, 4'b0001); // NC-Verilog, Modelsim, XSim, ... -`endif + `checkh(w4_u, 4'b0001); w4_u = (1'b0+(5'sb11111 >>> 3'd7)); // Exp 00000 Vlt 000000 Actually the signedness of result does NOT matter `checkh(w4_u, 4'b0000); diff --git a/test_regress/t/t_math_svl2.v b/test_regress/t/t_math_svl2.v index b6049d112..1d62f7006 100644 --- a/test_regress/t/t_math_svl2.v +++ b/test_regress/t/t_math_svl2.v @@ -19,15 +19,11 @@ module t ( if ('1 !== {66{1'b1}}) $stop; if ('x !== {66{1'bx}}) $stop; if ('z !== {66{1'bz}}) $stop; -`ifndef NC // NC-Verilog 5.50-s09 chokes on this test if ("\v" != 8'd11) $stop; if ("\f" != 8'd12) $stop; if ("\a" != 8'd7) $stop; if ("\x9a" != 8'h9a) $stop; if ("\xf1" != 8'hf1) $stop; -`endif - end - if (cyc == 8) begin end if (cyc == 9) begin $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_modport_export_task.v b/test_regress/t/t_modport_export_task.v index 848c05ccf..0016f7a3d 100644 --- a/test_regress/t/t_modport_export_task.v +++ b/test_regress/t/t_modport_export_task.v @@ -5,7 +5,8 @@ // SPDX-License-Identifier: CC0-1.0 // verilog_format: off -`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0) +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) // verilog_format: on interface bus_if; diff --git a/test_regress/t/t_oob_2state_array.py b/test_regress/t/t_oob_2state_array.py new file mode 100755 index 000000000..7b63fce7f --- /dev/null +++ b/test_regress/t/t_oob_2state_array.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +# x-assign shouldn't affect 2-state oob read +test.compile(verilator_flags2=["--binary --x-assign 1"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_oob_2state_array.v b/test_regress/t/t_oob_2state_array.v new file mode 100644 index 000000000..ac59838cb --- /dev/null +++ b/test_regress/t/t_oob_2state_array.v @@ -0,0 +1,29 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #1 clk = ~clk; + + int idx = 0; + bit a[0:2] = {1, 1, 1}; + + always @(posedge clk) begin + if (idx == 4) begin + $write("*-* All Finished *-*\n"); + $finish; + end + else begin + idx <= idx + 1; + `checkh(a[idx], idx <= 2 ? 1 : 0); + end + end +endmodule diff --git a/test_regress/t/t_opt_balance_cats.py b/test_regress/t/t_opt_balance_cats.py index 577c19985..d5e863848 100755 --- a/test_regress/t/t_opt_balance_cats.py +++ b/test_regress/t/t_opt_balance_cats.py @@ -14,7 +14,7 @@ test.scenarios('vlt') test.compile( verilator_flags2=["--stats", "--build", "--gate-stmts", "10000", "--expand-limit", "128"]) -test.file_grep(test.stats, r'Optimizations, FuncOpt concat trees balanced\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, FuncOpt concat trees balanced\s+(\d+)', 3) test.file_grep(test.stats, r'Optimizations, FuncOpt concat splits\s+(\d+)', 67) test.passes() diff --git a/test_regress/t/t_opt_const.py b/test_regress/t/t_opt_const.py index 9a49ab61d..fa2e1cfee 100755 --- a/test_regress/t/t_opt_const.py +++ b/test_regress/t/t_opt_const.py @@ -16,7 +16,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "-fno-dfg", "--stats", test. test.execute() if test.vlt: - test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 48) + test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 46) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_opt_const_dfg.py b/test_regress/t/t_opt_const_dfg.py index 6ad2d3549..41f6a2976 100755 --- a/test_regress/t/t_opt_const_dfg.py +++ b/test_regress/t/t_opt_const_dfg.py @@ -18,7 +18,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "--stats", test.pli_filename test.execute() if test.vlt: - test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 38) + test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 40) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_opt_constpool_recache.py b/test_regress/t/t_opt_constpool_recache.py new file mode 100755 index 000000000..ac30d3a16 --- /dev/null +++ b/test_regress/t/t_opt_constpool_recache.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 1) +test.file_grep(test.stats, r'ConstPool, Constants emitted\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_opt_constpool_recache.v b/test_regress/t/t_opt_constpool_recache.v new file mode 100644 index 000000000..af77d7409 --- /dev/null +++ b/test_regress/t/t_opt_constpool_recache.v @@ -0,0 +1,75 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + logic [3:0] idx; + assign idx = cyc[3:0]; + + // V3Case lowers this to a 512-bit constant-pool lookup table before V3Dead + // calls AstConstPool::rebuildVarScopesAndCache(). + logic [31:0] case_word; + always_comb + case (idx) + 4'h0: case_word = 32'h00000000; + 4'h1: case_word = 32'h00000001; + 4'h2: case_word = 32'h00000002; + 4'h3: case_word = 32'h00000003; + 4'h4: case_word = 32'h00000004; + 4'h5: case_word = 32'h00000005; + 4'h6: case_word = 32'h00000006; + 4'h7: case_word = 32'h00000007; + 4'h8: case_word = 32'h00000008; + 4'h9: case_word = 32'h00000009; + 4'ha: case_word = 32'h0000000a; + 4'hb: case_word = 32'h0000000b; + 4'hc: case_word = 32'h0000000c; + 4'hd: case_word = 32'h0000000d; + 4'he: case_word = 32'h0000000e; + default: case_word = 32'h0000000f; + endcase + + localparam logic [511:0] TABLE = { + 32'h0000000f, + 32'h0000000e, + 32'h0000000d, + 32'h0000000c, + 32'h0000000b, + 32'h0000000a, + 32'h00000009, + 32'h00000008, + 32'h00000007, + 32'h00000006, + 32'h00000005, + 32'h00000004, + 32'h00000003, + 32'h00000002, + 32'h00000001, + 32'h00000000 + }; + + // V3Premit extracts this matching wide constant after V3Dead recached the + // const-pool contents created by V3Case. + logic [31:0] static_word; + assign static_word = TABLE[{idx, 5'b0}+:32]; + + always @(posedge clk) begin + `checkh(case_word, static_word); + cyc <= cyc + 32'd1; + if (cyc == 32'd32) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_dedupe_clk_gate.py b/test_regress/t/t_opt_dedupe_clk_gate.py index 02778fcec..0fbd122ba 100755 --- a/test_regress/t/t_opt_dedupe_clk_gate.py +++ b/test_regress/t/t_opt_dedupe_clk_gate.py @@ -18,7 +18,7 @@ test.compile(verilator_flags2=["--no-json-edit-nums", "--stats"]) if test.vlt_all: test.file_grep( out_filename, - r'{"type":"VAR","name":"t.f0.clock_gate.clken_latched","addr":"[^"]*","loc":"\w,44:[^"]*","dtypep":"\(\w+\)",.*"origName":"clken_latched",.*"isLatched":true,.*"dtypeName":"logic"' + r'{"type":"VAR","name":"t.f1.clock_gate.clken_latched","addr":"[^"]*","loc":"\w,44:[^"]*","dtypep":"\(\w+\)",.*"origName":"clken_latched",.*"isLatched":true,.*"dtypeName":"logic"' ) test.file_grep(test.stats, r'Optimizations, Gate sigs deduped\s+(\d+)', 2) diff --git a/test_regress/t/t_opt_inline_cfuncs.py b/test_regress/t/t_opt_inline_cfuncs.py index 7981f48c3..1821f5ec5 100755 --- a/test_regress/t/t_opt_inline_cfuncs.py +++ b/test_regress/t/t_opt_inline_cfuncs.py @@ -9,17 +9,17 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('vlt_all') -# Use --output-split-cfuncs to create small functions that can be inlined -# Also test --inline-cfuncs-product option test.compile(verilator_flags2=[ - "--stats", "--binary", "--output-split-cfuncs", "1", "--inline-cfuncs-product", "200" + "--stats", "--binary", "--inline-cfuncs-product", "200", "--dumpi-V3InlineCFuncs", "9" ]) -# Verify inlining happened with exact count -test.file_grep(test.stats, r'Optimizations, Inlined CFuncs\s+(\d+)', 39) - test.execute() +if test.vlt: + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 7) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 7) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 7) + test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_args.py b/test_regress/t/t_opt_inline_cfuncs_args.py new file mode 100755 index 000000000..05fb055dd --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_args.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=["--stats", "--binary"]) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]') + +test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_args.v b/test_regress/t/t_opt_inline_cfuncs_args.v new file mode 100644 index 000000000..74c049939 --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_args.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire clk +); + + integer cyc = 0; + reg [31:0] acc; + + task automatic add_pair(input [31:0] a, input [31:0] b, inout [31:0] sum); + // verilator no_inline_task + sum = sum + a + b; + endtask + + always @(posedge clk) begin + cyc <= cyc + 1; + acc = 0; + add_pair(cyc[31:0], 32'd1, acc); // + cyc + 1 + add_pair(32'd1000, cyc[31:0], acc); // + 1000 + cyc + // acc = (cyc + 1) + (1000 + cyc) = 2*cyc + 1001 + if (cyc > 1) begin + if (acc !== (2 * cyc[31:0] + 32'd1001)) begin + $write("%%Error: cyc=%0d acc=%0d expected %0d\n", cyc, acc, 2 * cyc + 1001); + $stop; + end + end + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_inline_cfuncs_dup.py b/test_regress/t/t_opt_inline_cfuncs_dup.py new file mode 100755 index 000000000..05fb055dd --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_dup.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=["--stats", "--binary"]) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]') + +test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_dup.v b/test_regress/t/t_opt_inline_cfuncs_dup.v new file mode 100644 index 000000000..2d93e20fe --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_dup.v @@ -0,0 +1,30 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire clk +); + + integer cyc = 0; + + task automatic tick(); + // verilator no_inline_task + automatic time t = $time; + $display("TICK: %0t", t); + endtask + + always @(posedge clk) begin + cyc <= cyc + 1; + tick(); + tick(); + tick(); + tick(); + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_inline_cfuncs_off.py b/test_regress/t/t_opt_inline_cfuncs_off.py index 8b045e8ca..7e7361d8c 100755 --- a/test_regress/t/t_opt_inline_cfuncs_off.py +++ b/test_regress/t/t_opt_inline_cfuncs_off.py @@ -9,15 +9,13 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('vlt_all') test.top_filename = "t/t_opt_inline_cfuncs.v" -# Disable inlining with --inline-cfuncs 0 -test.compile(verilator_flags2=["--stats", "--binary", "--inline-cfuncs", "0"]) - -# Verify inlining did NOT happen (stat doesn't exist when pass is skipped) -test.file_grep_not(test.stats, r'Optimizations, Inlined CFuncs\s+[1-9]') +test.compile(verilator_flags2=["--stats", "--binary", "-fno-inline-cfuncs"]) test.execute() +test.file_grep_not(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]') + test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_threshold.py b/test_regress/t/t_opt_inline_cfuncs_threshold.py index 677dee894..c7c6713b4 100755 --- a/test_regress/t/t_opt_inline_cfuncs_threshold.py +++ b/test_regress/t/t_opt_inline_cfuncs_threshold.py @@ -9,17 +9,16 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('vlt_all') -# Use thresholds that guarantee rejection to test the "return false" path in isInlineable() -# --inline-cfuncs 1: pass still runs (not skipped) -# --inline-cfuncs-product 0: guarantees all functions rejected (node_count * call_count > 0 always) test.compile(verilator_flags2=[ - "--stats", "--binary", "--inline-cfuncs", "1", "--inline-cfuncs-product", "0" + "--stats", "--binary", "--inline-cfuncs", "0", "--inline-cfuncs-product", "0" ]) -test.file_grep(test.stats, r'Optimizations, Inlined CFuncs\s+(\d+)', 0) - test.execute() +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 0) + test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_trace.py b/test_regress/t/t_opt_inline_cfuncs_trace.py new file mode 100755 index 000000000..515aa4510 --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_trace.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') +test.top_filename = "t/t_opt_inline_cfuncs.v" + +test.compile(verilator_flags2=["--stats", "--binary", "--trace", "--inline-cfuncs-product", "200"]) + +if test.vlt: + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 8) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 7) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 9) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_opt_table_enum.py b/test_regress/t/t_opt_table_enum.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_enum.py +++ b/test_regress/t/t_opt_table_enum.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_packed_array.py b/test_regress/t/t_opt_table_packed_array.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_packed_array.py +++ b/test_regress/t/t_opt_table_packed_array.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_real.py b/test_regress/t/t_opt_table_real.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_real.py +++ b/test_regress/t/t_opt_table_real.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_same.py b/test_regress/t/t_opt_table_same.py index 2cee4586a..8f09062b6 100755 --- a/test_regress/t/t_opt_table_same.py +++ b/test_regress/t/t_opt_table_same.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 2) diff --git a/test_regress/t/t_opt_table_signed.py b/test_regress/t/t_opt_table_signed.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_signed.py +++ b/test_regress/t/t_opt_table_signed.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_string.py b/test_regress/t/t_opt_table_string.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_string.py +++ b/test_regress/t/t_opt_table_string.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_struct.py b/test_regress/t/t_opt_table_struct.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_struct.py +++ b/test_regress/t/t_opt_table_struct.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_param_first.v b/test_regress/t/t_param_first.v index eea84e2c3..6ded92a93 100644 --- a/test_regress/t/t_param_first.v +++ b/test_regress/t/t_param_first.v @@ -63,12 +63,11 @@ module t ( parameter THREE_2WIDE = 2'b11; parameter ALSO_THREE_WIDE = THREE_BITS_WIDE; parameter THREEPP_32_WIDE = 2 * 8 * 2 + 3; - parameter THREEPP_3_WIDE = 3'd4 * 3'd4 * 3'd2 + 3'd3; // Yes folks VCS says 3 bits wide + parameter THREEPP_3_WIDE = 3'd4 * 3'd4 * 3'd2 + 3'd3; // Width propagation doesn't care about LHS vs RHS // But the width of a RHS/LHS on a upper node does affect lower nodes; // Thus must double-descend in width analysis. - // VCS 7.0.1 is broken on this test! parameter T10 = (3'h7 + 3'h7) + 4'h0; //initial if (T10!==4'd14) $stop; parameter T11 = 4'h0 + (3'h7 + 3'h7); //initial if (T11!==4'd14) $stop; diff --git a/test_regress/t/t_procedure_always_nba.py b/test_regress/t/t_procedure_always_nba.py new file mode 100755 index 000000000..a68ed3ea9 --- /dev/null +++ b/test_regress/t/t_procedure_always_nba.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.top_filename = "t/t_procedure_nba.v" + +test.compile(verilator_flags2=["--binary", "--stats", "-DPROCEDURE=always"]) + +test.file_grep(test.stats, r'Scheduling, \'act\' extra triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' pre triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' sense triggers\s+(\d+)', 3) +test.file_grep(test.stats, r'Procedures needing initial NBA trigger\s+(\d+)', 100) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_procedure_initial_nba.py b/test_regress/t/t_procedure_initial_nba.py new file mode 100755 index 000000000..528290717 --- /dev/null +++ b/test_regress/t/t_procedure_initial_nba.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.top_filename = "t/t_procedure_nba.v" + +test.compile(verilator_flags2=["--binary", "--stats", "-DPROCEDURE=initial"]) + +test.file_grep(test.stats, r'Scheduling, \'act\' extra triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' pre triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' sense triggers\s+(\d+)', 3) +test.file_grep(test.stats, r'Procedures needing initial NBA trigger\s+(\d+)', 100) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_procedure_nba.v b/test_regress/t/t_procedure_nba.v new file mode 100644 index 000000000..a5cbf19a5 --- /dev/null +++ b/test_regress/t/t_procedure_nba.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk; + initial clk = 0; + always #5 clk = ~clk; + bit [99:0][2:0] foo; + bit bar; + + always @(posedge clk) begin + bar <= ~bar; + #1; + end + + genvar i; + for (i = 0; i < 100; i=i+1) + `PROCEDURE begin + foo[i] <= 3; + if (foo[i] !== 0) $stop; + @(posedge clk); + if (foo[i] !== 3) $stop; + foo[i] = 2; + if (foo[i] !== 2) $stop; + #1; + if (foo[i] !== 2) $stop; + foo[i] = 0; + end + + initial #100 begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_prop_always.v b/test_regress/t/t_prop_always.v index 3f54b1023..f389a3a8c 100644 --- a/test_regress/t/t_prop_always.v +++ b/test_regress/t/t_prop_always.v @@ -24,7 +24,6 @@ module t ( // For "always [m:n] P" the action runs at cyc=K+n on success and at the // detected-violation cyc on failure -- both deterministic given the inputs. int high_bounded_pass_q[$]; - int high_sbounded_pass_q[$]; int high_degenerate_pass_q[$]; int low_bounded_fail_q[$]; int low_degenerate_fail_q[$]; @@ -39,19 +38,14 @@ module t ( // Bounded weak always over constant-true input. assert property (@(posedge clk) always [0:3] a_high) high_bounded_pass_q.push_back(cyc); - // Bounded strong s_always over constant-true input. - assert property (@(posedge clk) s_always [1:2] a_high) high_sbounded_pass_q.push_back(cyc); - // Degenerate [0:0]: equivalent to immediate sample. assert property (@(posedge clk) always [0:0] a_high) high_degenerate_pass_q.push_back(cyc); // Constant-false: every attempt fails. assert property (@(posedge clk) always [0:3] a_low) - ; else low_bounded_fail_q.push_back(cyc); assert property (@(posedge clk) always [0:0] a_low) - ; else low_degenerate_fail_q.push_back(cyc); // CRC-driven random input: window [cyc..cyc+3] of a_rand. @@ -80,25 +74,21 @@ module t ( crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; if (cyc == 19) begin // Constant-true window [0:3]: K=0..16 succeed at cyc K+3 = 3..19. - `checkd(high_bounded_pass_q.size(), 17); - `checkd(high_bounded_pass_q[0], 3); + `checkd(high_bounded_pass_q.size(), 17); // Other sims: 16 + `checkd(high_bounded_pass_q[0], 3); // Other sims: 4 `checkd(high_bounded_pass_q[$], 19); - // Strong [1:2]: K=0..17 succeed at cyc K+2 = 2..19. - `checkd(high_sbounded_pass_q.size(), 18); - `checkd(high_sbounded_pass_q[0], 2); - `checkd(high_sbounded_pass_q[$], 19); // Degenerate [0:0]: K=0..19 succeed at cyc K = 0..19. - `checkd(high_degenerate_pass_q.size(), 20); - `checkd(high_degenerate_pass_q[0], 0); + `checkd(high_degenerate_pass_q.size(), 20); // Other sims: 19 + `checkd(high_degenerate_pass_q[0], 0); // Other sims: 0, 1 `checkd(high_degenerate_pass_q[$], 19); // Constant-false: every attempt fails immediately. - `checkd(low_bounded_fail_q.size(), 20); - `checkd(low_degenerate_fail_q.size(), 20); - // CRC + disable streams: counts pinned (cross-checked against Questa). + `checkd(low_bounded_fail_q.size(), 20); // Other sims: 19 + `checkd(low_degenerate_fail_q.size(), 20); // Other sims: 19 + // CRC + disable streams `checkd(rand_bounded_pass_q.size(), 0); - `checkd(rand_bounded_fail_q.size(), 20); + `checkd(rand_bounded_fail_q.size(), 20); // Other sims: 19, 11 `checkd(disable_bounded_pass_q.size(), 0); - `checkd(disable_bounded_fail_q.size(), 13); + `checkd(disable_bounded_fail_q.size(), 13); // Other sims: 5, 6 $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_prop_always_bad.out b/test_regress/t/t_prop_always_bad.out index 4e06a8feb..62d3d66a8 100644 --- a/test_regress/t/t_prop_always_bad.out +++ b/test_regress/t/t_prop_always_bad.out @@ -1,34 +1,34 @@ -%Error: t/t_prop_always_bad.v:13:20: always range low bound must be non-negative (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:13:28: always range low bound must be non-negative (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 13 | assert property (always [-1:3] a); - | ^~~~~~ + | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_prop_always_bad.v:14:20: always range high bound must be >= low bound (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:14:30: always range high bound must be >= low bound (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 14 | assert property (always [5:2] a); - | ^~~~~~ + | ^ %Error: t/t_prop_always_bad.v:15:28: Expecting expression to be constant, but variable isn't const: 'x' : ... note: In instance 't' 15 | assert property (always [x:3] a); | ^ -%Error: t/t_prop_always_bad.v:15:20: always range low bound must be a constant expression (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:15:28: always range low bound must be a constant expression (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 15 | assert property (always [x:3] a); - | ^~~~~~ + | ^ %Error: t/t_prop_always_bad.v:16:30: Expecting expression to be constant, but variable isn't const: 'x' : ... note: In instance 't' 16 | assert property (always [1:x] a); | ^ -%Error: t/t_prop_always_bad.v:16:20: always range high bound must be a constant expression (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:16:30: always range high bound must be a constant expression (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 16 | assert property (always [1:x] a); - | ^~~~~~ + | ^ %Error: t/t_prop_always_bad.v:17:20: s_always range must be bounded (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 17 | assert property (s_always a); | ^~~~~~~~ -%Error: t/t_prop_always_bad.v:18:20: s_always range must be bounded (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:18:32: s_always range must be bounded (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 18 | assert property (s_always [1:$] a); - | ^~~~~~~~ + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_prop_always_unsup.out b/test_regress/t/t_prop_always_unsup.out index 52bd14918..3bc897181 100644 --- a/test_regress/t/t_prop_always_unsup.out +++ b/test_regress/t/t_prop_always_unsup.out @@ -1,18 +1,14 @@ -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:12:35: Unsupported: unbounded always range (always [m:$]) +%Error-UNSUPPORTED: t/t_prop_always_unsup.v:12:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) : ... note: In instance 't' - 12 | assert property (@(posedge clk) always [2:$] a); + 12 | assert property (@(posedge clk) always [0:3] (a |-> b)); | ^~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:15:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) +%Error-UNSUPPORTED: t/t_prop_always_unsup.v:13:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) : ... note: In instance 't' - 15 | assert property (@(posedge clk) always [0:3] (a |-> b)); + 13 | assert property (@(posedge clk) always [0:3] (a |=> b)); | ^~~~~~ -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:16:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) +%Error-UNSUPPORTED: t/t_prop_always_unsup.v:14:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) : ... note: In instance 't' - 16 | assert property (@(posedge clk) always [0:3] (a |=> b)); - | ^~~~~~ -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:17:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) - : ... note: In instance 't' - 17 | assert property (@(posedge clk) always [0:3] (a ##1 b)); + 14 | assert property (@(posedge clk) always [0:3] (a ##1 b)); | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_prop_always_unsup.v b/test_regress/t/t_prop_always_unsup.v index 7013c9fff..998516d7d 100644 --- a/test_regress/t/t_prop_always_unsup.v +++ b/test_regress/t/t_prop_always_unsup.v @@ -8,9 +8,6 @@ module t (input clk); logic a; logic b; - // IEEE-legal but engine has no sim-end liveness. - assert property (@(posedge clk) always [2:$] a); - // Nested sequence/property operators inside bounded always. assert property (@(posedge clk) always [0:3] (a |-> b)); assert property (@(posedge clk) always [0:3] (a |=> b)); diff --git a/test_regress/t/t_prop_always_wide.v b/test_regress/t/t_prop_always_wide.v index a0e297293..742e37d9a 100644 --- a/test_regress/t/t_prop_always_wide.v +++ b/test_regress/t/t_prop_always_wide.v @@ -15,17 +15,13 @@ module t ( int cyc = 0; logic a_high = 1'b1, b_high = 1'b1, c_high = 1'b1; - int wide_pass_q[$], wide_strong_pass_q[$]; + int wide_pass_q[$]; // Wide range with multi-operand pure propp -- exercises the shared // $sampled(propp) hoist path; pre-fix would clone propp 33 times. assert property (@(posedge clk) always[1: 33] (a_high && b_high && c_high)) wide_pass_q.push_back(cyc); - // Wide strong s_always over the same expression (in-window matches weak). - assert property (@(posedge clk) s_always[1: 33] (a_high && b_high && c_high)) - wide_strong_pass_q.push_back(cyc); - always @(posedge clk) begin cyc <= cyc + 1; if (cyc == 49) begin @@ -33,9 +29,6 @@ module t ( `checkd(wide_pass_q.size(), 17); `checkd(wide_pass_q[0], 33); `checkd(wide_pass_q[$], 49); - `checkd(wide_strong_pass_q.size(), 17); - `checkd(wide_strong_pass_q[0], 33); - `checkd(wide_strong_pass_q[$], 49); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_prop_followed_by.v b/test_regress/t/t_prop_followed_by.v index 0fea33f5b..6cc11045a 100644 --- a/test_regress/t/t_prop_followed_by.v +++ b/test_regress/t/t_prop_followed_by.v @@ -4,11 +4,10 @@ // SPDX-FileCopyrightText: 2026 PlanV GmbH // SPDX-License-Identifier: CC0-1.0 -`define checkd(gotv, expv) \ - do if ((gotv) !== (expv)) begin \ - $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__, `__LINE__, (gotv), (expv)); \ - $stop; \ - end while (0); +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on module t ( input clk @@ -25,9 +24,12 @@ module t ( integer impl_f = 0; integer nimp_f = 0; integer wide_f = 0; + integer action_hits = 0; // Smoke: trivially-true forms must compile and never fail. assert property (@(posedge clk) 1'b1 #-# 1'b1); + assert property (@(posedge clk) 1'b1 #-# 1'b1) + action_hits++; assert property (@(posedge clk) 0 |-> (0 #-# 0)); assert property (@(posedge clk) 0 |-> (0 #=# 0)); @@ -51,13 +53,11 @@ module t ( cyc <= cyc + 1; crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; if (cyc == 32) begin - // Counts are deterministic for this CRC seed. Questa reference run - // (IEEE 1800-2023 16.12.9) reports ovl=28, novl=19, impl=9, nimp=0; the // ovl/novl deltas vs Verilator are 1-cycle preponed-sampling differences. $display("ovl=%0d novl=%0d impl=%0d nimp=%0d wide=%0d", ovl_f, novl_f, impl_f, nimp_f, wide_f); - `checkd(ovl_f, 29); - `checkd(novl_f, 20); + `checkd(ovl_f, 29); // Other sims: 28, one other sim: 5 + `checkd(novl_f, 20); // Other sims: 19 `checkd(impl_f, 9); `checkd(nimp_f, 0); `checkd(wide_f, 0); diff --git a/test_regress/t/t_prop_s_always_liveness.out b/test_regress/t/t_prop_s_always_liveness.out new file mode 100644 index 000000000..8a1f69c16 --- /dev/null +++ b/test_regress/t/t_prop_s_always_liveness.out @@ -0,0 +1,4 @@ +*-* All Finished *-* +[115] %Error: t_prop_s_always_liveness.v:25: Assertion failed in top.t +%Error: t/t_prop_s_always_liveness.v:25: Verilog $stop +Aborting... diff --git a/test_regress/t/t_prop_s_always_liveness.py b/test_regress/t/t_prop_s_always_liveness.py new file mode 100755 index 000000000..e6fa1c51f --- /dev/null +++ b/test_regress/t/t_prop_s_always_liveness.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--assert']) +test.execute(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_prop_s_always_liveness.v b/test_regress/t/t_prop_s_always_liveness.v new file mode 100644 index 000000000..3251b7754 --- /dev/null +++ b/test_regress/t/t_prop_s_always_liveness.v @@ -0,0 +1,43 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + int cyc = 0; + logic a_high = 1'b1; + logic a_low = 1'b0; + + int low_s_fail_q[$]; + int low_w_fail_q[$]; + + // The youngest [2:5] windows are still open at $finish, so strong s_always + // reports a liveness failure even with a_high always 1; weak always does not. + assert property (@(posedge clk) s_always [2:5] a_high); + assert property (@(posedge clk) always [2:5] a_high); + + assert property (@(posedge clk) s_always [2:5] a_low) + else low_s_fail_q.push_back(cyc); + assert property (@(posedge clk) always [2:5] a_low) + else low_w_fail_q.push_back(cyc); + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 10) begin + `checkd(low_s_fail_q.size(), low_w_fail_q.size()); + `checkd(low_w_fail_q.size(), 9); + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_property_accept_reject_on.v b/test_regress/t/t_property_accept_reject_on.v index 020e754f2..080af892c 100644 --- a/test_regress/t/t_property_accept_reject_on.v +++ b/test_regress/t/t_property_accept_reject_on.v @@ -94,16 +94,16 @@ module t ( end else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); - `checkd(count_fail1, 29); // Questa: 14 - `checkd(count_fail2, 65); // Questa: 64 - `checkd(count_fail3, 29); // Questa: 14 - `checkd(count_fail4, 65); // Questa: 64 - `checkd(count_fail5, 46); // Questa: 31 - `checkd(count_fail6, 65); // Questa: 59 - `checkd(count_fail7, 29); // Questa: 14 - `checkd(count_fail8, 14); // Questa: 10 - `checkd(count_fail9, 29); // Questa: 14 - `checkd(count_fail10, 29); // Questa: 14 + `checkd(count_fail1, 28); // Other sims: 14, one other: 15 + `checkd(count_fail2, 64); // One other sim: 66 + `checkd(count_fail3, 28); // Other sims: 14 + `checkd(count_fail4, 64); + `checkd(count_fail5, 45); // Other sims: 31, one other: 32 + `checkd(count_fail6, 64); // Other sims: 59, one other: 60 + `checkd(count_fail7, 28); // Other sims: 14, one other: 15 + `checkd(count_fail8, 13); // Other sims: 10 + `checkd(count_fail9, 28); // Other sims: 14, one other: 15 + `checkd(count_fail10, 28); // Other sims: 14 $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_property_disable_iff_held.py b/test_regress/t/t_property_disable_iff_held.py new file mode 100755 index 000000000..8c5881f1b --- /dev/null +++ b/test_regress/t/t_property_disable_iff_held.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert --timing --coverage']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_property_disable_iff_held.v b/test_regress/t/t_property_disable_iff_held.v new file mode 100644 index 000000000..37fa06461 --- /dev/null +++ b/test_regress/t/t_property_disable_iff_held.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +// IEEE 1800-2023 16.12: a disable iff condition held continuously true must +// disable every attempt of a multi-cycle property (verilator/verilator#7792). +// en_held is a plain non-$sampled, non-constant signal held 1, so it exercises +// the NFA disable-counter path. The held assert/cover must never fire; the +// `disable iff (1'b0)` controls prove the same assert/cover do fire when enabled. + +module t ( + input clk +); + int cyc = 0; + reg [63:0] crc = 64'h5aef0c8d_d70a4497; + wire a = crc[0]; + wire b = crc[4]; + + bit en_held = 1'b1; + + int n_held_assert = 0; + int n_held_cover = 0; + int n_ctrl_assert = 0; + int n_ctrl_cover = 0; + + // Held-true disable: assert + cover must be fully suppressed. + assert property (@(posedge clk) disable iff (en_held) (a ##1 b)) + else n_held_assert <= n_held_assert + 1; + cover property (@(posedge clk) disable iff (en_held) (a ##1 b)) + n_held_cover <= n_held_cover + 1; + + // Enabled control (disable iff 1'b0): same assert + cover must fire. + assert property (@(posedge clk) disable iff (1'b0) (a ##1 b)) + else n_ctrl_assert <= n_ctrl_assert + 1; + cover property (@(posedge clk) disable iff (1'b0) (a ##1 b)) + n_ctrl_cover <= n_ctrl_cover + 1; + + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; + if (cyc == 99) begin + `checkd(n_held_assert, 0); + `checkd(n_held_cover, 0); + `checkd(n_ctrl_assert, 58); + `checkd(n_ctrl_cover, 26); // Others: 26, One other: 0 + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_property_pexpr_unsup.out b/test_regress/t/t_property_pexpr_unsup.out deleted file mode 100644 index bae43d802..000000000 --- a/test_regress/t/t_property_pexpr_unsup.out +++ /dev/null @@ -1,26 +0,0 @@ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:115:21: Unsupported: Unclocked assertion - : ... note: In instance 't' - 115 | assert property ((s_eventually a) implies (s_eventually a)); - | ^~~~~~~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:115:46: Unsupported: Unclocked assertion - : ... note: In instance 't' - 115 | assert property ((s_eventually a) implies (s_eventually a)); - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:115:3: Unsupported: Unclocked assertion - : ... note: In instance 't' - 115 | assert property ((s_eventually a) implies (s_eventually a)); - | ^~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:117:21: Unsupported: Unclocked assertion - : ... note: In instance 't' - 117 | assert property ((s_eventually a) iff (s_eventually a)); - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:117:42: Unsupported: Unclocked assertion - : ... note: In instance 't' - 117 | assert property ((s_eventually a) iff (s_eventually a)); - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:117:3: Unsupported: Unclocked assertion - : ... note: In instance 't' - 117 | assert property ((s_eventually a) iff (s_eventually a)); - | ^~~~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_property_pexpr_unsup.py b/test_regress/t/t_property_pexpr_unsup.py deleted file mode 100755 index 9768aebfc..000000000 --- a/test_regress/t/t_property_pexpr_unsup.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -# DESCRIPTION: Verilator: Verilog Test driver/expect definition -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of either the GNU Lesser General Public License Version 3 -# or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -import vltest_bootstrap - -test.scenarios('vlt') - -test.compile(expect_filename=test.golden_filename, - verilator_flags2=['--timing', '--error-limit 1000'], - fails=test.vlt_all) - -test.passes() diff --git a/test_regress/t/t_property_s_eventually_iface_param.py b/test_regress/t/t_property_s_eventually_iface_param.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface_param.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_property_s_eventually_iface_param.v b/test_regress/t/t_property_s_eventually_iface_param.v new file mode 100644 index 000000000..6a76feabf --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface_param.v @@ -0,0 +1,40 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +interface iface_if #( + parameter int W = 8 +) ( + input bit clk +); + logic [W-1:0] sig = 0; + int passed = 0; + assert property (@(posedge clk) s_eventually (sig == 1)) passed++; +endinterface + +module t; + bit clk = 0; + initial forever #1 clk = ~clk; + int cyc = 0; + + // Two distinct specializations: V3Param clones the interface into two + // modules, each with its own s_eventually tracking. The generated final + // block must stay per-module. + iface_if #(.W(4)) a (.clk(clk)); + iface_if #(.W(8)) b (.clk(clk)); + + always @(posedge clk) begin + ++cyc; + if (cyc == 2) begin + a.sig <= 1; + b.sig <= 1; + end + if (cyc == 5) begin + if (a.passed == 0 || b.passed == 0) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_property_s_eventually_unsup.out b/test_regress/t/t_property_s_eventually_unsup.out index 6a9be1c3a..e4355f13f 100644 --- a/test_regress/t/t_property_s_eventually_unsup.out +++ b/test_regress/t/t_property_s_eventually_unsup.out @@ -1,12 +1,8 @@ -%Error-UNSUPPORTED: t/t_property_s_eventually_unsup.v:14:20: Unsupported: Unclocked assertion +%Error-UNSUPPORTED: t/t_property_s_eventually_unsup.v:14:35: Unsupported: cycle delay in s_eventually : ... note: In instance 't' - 14 | assert property (s_eventually ##1 1); - | ^~~~~~~~~~~~ + 14 | assert property (@(posedge clk) s_eventually ##1 1); + | ^~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_property_s_eventually_unsup.v:14:3: Unsupported: Unclocked assertion - : ... note: In instance 't' - 14 | assert property (s_eventually ##1 1); - | ^~~~~~ %Error-UNSUPPORTED: t/t_property_s_eventually_unsup.v:15:35: Unsupported: cycle delay in s_eventually : ... note: In instance 't' 15 | assert property (@(negedge clk) s_eventually ##1 1); diff --git a/test_regress/t/t_property_s_eventually_unsup.v b/test_regress/t/t_property_s_eventually_unsup.v index 9338a0f4d..02a22a127 100644 --- a/test_regress/t/t_property_s_eventually_unsup.v +++ b/test_regress/t/t_property_s_eventually_unsup.v @@ -11,7 +11,7 @@ module t; localparam MAX = 3; integer cyc = 1; - assert property (s_eventually ##1 1); + assert property (@(posedge clk) s_eventually ##1 1); assert property (@(negedge clk) s_eventually ##1 1); always @(posedge clk) begin diff --git a/test_regress/t/t_property_sexpr_cov.v b/test_regress/t/t_property_sexpr_cov.v index 59fa3ddc9..ef58a1fd8 100644 --- a/test_regress/t/t_property_sexpr_cov.v +++ b/test_regress/t/t_property_sexpr_cov.v @@ -52,4 +52,12 @@ module t ( /*AUTOARG*/ cover property (@(posedge clk) ##3 val[0] && val[1]) $display("[%0t] concurrent cover, fileline:%0d", $time, `__LINE__); + + integer action_hits = 0; + + assert property (@(posedge clk) ##1 1'b1) + action_hits++; + + assert property (@(posedge clk) (val[0] ##1 val[1]) |-> 1'b1) + action_hits++; endmodule diff --git a/test_regress/t/t_property_sexpr_multi.v b/test_regress/t/t_property_sexpr_multi.v index 9a99739e5..ff126810f 100644 --- a/test_regress/t/t_property_sexpr_multi.v +++ b/test_regress/t/t_property_sexpr_multi.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 // verilog_format: off diff --git a/test_regress/t/t_property_sexpr_range_delay.v b/test_regress/t/t_property_sexpr_range_delay.v index 5594b6a54..85ad2dcc8 100644 --- a/test_regress/t/t_property_sexpr_range_delay.v +++ b/test_regress/t/t_property_sexpr_range_delay.v @@ -13,6 +13,8 @@ module t ( input clk ); + parameter P = 1; + integer cyc = 0; reg [63:0] crc = '0; reg [63:0] sum = '0; @@ -30,6 +32,7 @@ module t ( int count_fail2 = 0; int count_fail3 = 0; int count_fail4 = 0; + int count_fail5 = 0; always_ff @(posedge clk) begin `ifdef TEST_VERBOSE @@ -49,13 +52,11 @@ module t ( else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); `checkh(sum, 64'h38c614665c6b71ad); - // count_fail1 overcounts Questa by 1: NFA per-cycle reject merging - // OR's requiredStep-fail and terminal-fail into one signal; Questa - // resolves the same overlap as a single per-attempt miss. - `checkd(count_fail1, 25); // Questa: 24 - `checkd(count_fail2, 50); // Questa: 50 - `checkd(count_fail3, 24); // Questa: 24 - `checkd(count_fail4, 1); // Questa: 1 + `checkd(count_fail1, 24); + `checkd(count_fail2, 50); + `checkd(count_fail3, 24); + `checkd(count_fail4, 1); + `checkd(count_fail5, 1); $write("*-* All Finished *-*\n"); $finish; end @@ -65,6 +66,11 @@ module t ( assert property (@(posedge clk) disable iff (cyc < 2) a |-> ##[1:3] (a | b | c | d | e)); + // Parameterized range bound + assert property (@(posedge clk) disable iff (cyc < 2) + a |-> ##[P:P+3] (a | b | c | d | e)); + assert property (@(posedge clk) ##[P:P+3] 1); + // ##[2:4] range delay assert property (@(posedge clk) disable iff (cyc < 2) b |-> ##[2:4] (a | b | c | d | e)); @@ -131,4 +137,25 @@ module t ( assert property (@(posedge clk) disable iff (cyc < 2) a |-> ##[+] b ##1 (a | b | c | d | e)); + // Finite range delay with a multi-cycle RHS must not reject on an earlier + // candidate when a later candidate in the same window matches. + assert property (@(posedge clk) + cyc == 10 |-> ##[2:4] ((cyc == 12 || cyc == 13) ##1 cyc == 14)); + + // Same shape, but every RHS candidate in the range window rejects, so the + // assertion attempt itself must reject once. + assert property (@(posedge clk) + cyc == 20 |-> ##[2:4] ((cyc == 22 || cyc == 23) ##1 cyc == 25)) + else count_fail5 <= count_fail5 + 1; + + // Variable-length nested RHS, then another finite range below + // the liveness scope. + assert property (@(posedge clk) + cyc == 30 |-> ##[1:2] ((cyc == 31) ##[1:2] ((cyc == 32) ##1 1'b1))); + + // Finite range whose RHS ends in an NFA state, not a final + // boolean condition. + assert property (@(posedge clk) + cyc == 40 |-> ##[1:2] (##1 (((cyc == 43) ##1 1'b1) or ((cyc == 43) ##1 1'b1)))); + endmodule diff --git a/test_regress/t/t_property_until_implication.v b/test_regress/t/t_property_until_implication.v index 6023ac501..2f345677f 100644 --- a/test_regress/t/t_property_until_implication.v +++ b/test_regress/t/t_property_until_implication.v @@ -50,12 +50,12 @@ module t ( crc <= 64'h5aef0c8d_d70a4497; end else if (cyc == 99) begin - // Counts reflect NFA per-cycle reject aggregation, not Questa's + // Counts reflect NFA per-cycle reject aggregation, not some other sim's // per-attempt action_block firing; the two differ by a small constant // (see PR description for the model gap). Test is a regression for // "no internal error on `until` as |=> consequent" (issue #7548). - `checkd(fail_nonoverlap, 7); - `checkd(fail_overlap, 22); + `checkd(fail_nonoverlap, 7); // Other sims: 8, one other: 7 + `checkd(fail_overlap, 22); // Other sims: 24, one other: 22 $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_protect_ids_key.out b/test_regress/t/t_protect_ids_key.out index 110f826c1..a9a6d264e 100644 --- a/test_regress/t/t_protect_ids_key.out +++ b/test_regress/t/t_protect_ids_key.out @@ -16,13 +16,11 @@ - - - + @@ -36,13 +34,10 @@ - - - diff --git a/test_regress/t/t_queue_array.py b/test_regress/t/t_queue_array.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_queue_array.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_queue_array.v b/test_regress/t/t_queue_array.v new file mode 100644 index 000000000..c729a4654 --- /dev/null +++ b/test_regress/t/t_queue_array.v @@ -0,0 +1,87 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + + int normal_queue[$]; + int array_of_queues[3][$]; + int aa_of_queues[int][$]; + + function void test_normal_queue(); + $display("[%0t] %m: Testing single queue (int normal_queue [$])", $realtime); + `checkd(normal_queue.size(), 0); + repeat (4) normal_queue.push_back($urandom); + `checkd(normal_queue.size(), 4); + repeat (4) void'(normal_queue.pop_front()); + `checkd(normal_queue.size(), 0); + endfunction + + function void test_array_of_queues(); + $display("[%0t] %m: Testing array of queues (int array_of_queues [3][$])", $realtime); + array_of_queues[0] = {}; + array_of_queues[1] = {}; + array_of_queues[2] = {}; + + for (int i = 0; i < 3; i++) begin + `checkd(array_of_queues[i].size(), 0); + end + + for (int i = 0; i < 3; i++) begin + for (int j = 0; j < 4; j++) begin : push_4_items + array_of_queues[i].push_back($urandom); + $display("[%0t] %m: array_of_queues, pushed item to queue %0d: [0]=%p [1]=%p [2]=%p", + $realtime, i, array_of_queues[0], array_of_queues[1], array_of_queues[2]); + `checkd(array_of_queues[i].size(), j + 1); + end + end + + for (int i = 0; i < 3; i++) begin : pop_4_items_from_each + repeat (4) void'(array_of_queues[i].pop_front()); + `checkd(array_of_queues[i].size(), 0); + end + endfunction + + function void test_aa_of_queues(); + $display("[%0t] %m: Testing associative-array of queues (int aa_of_queues [int][$])", + $realtime); + + aa_of_queues[0] = {}; + aa_of_queues[1] = {}; + aa_of_queues[2] = {}; + + for (int i = 0; i < 3; i++) begin + `checkd(aa_of_queues[i].size(), 0); + end + + for (int i = 0; i < 3; i++) begin + for (int j = 0; j < 4; j++) begin : push_4_items + aa_of_queues[i].push_back($urandom); + $display("[%0t] %m: aa_of_queues, pushed item to queue %0d: [0]=%p [1]=%p [2]=%p", + $realtime, i, aa_of_queues[0], aa_of_queues[1], aa_of_queues[2]); + `checkd(aa_of_queues[i].size(), j + 1); + end + end + + for (int i = 0; i < 3; i++) begin + repeat (4) void'(aa_of_queues[i].pop_front()); + `checkd(aa_of_queues[i].size(), 0); + end + endfunction + + + initial begin + test_normal_queue(); + test_aa_of_queues(); + test_array_of_queues(); + $finish; + end + +endmodule diff --git a/test_regress/t/t_randomize_prepost_with_baseref.py b/test_regress/t/t_randomize_prepost_with_baseref.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_prepost_with_baseref.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_randomize_prepost_with_baseref.v b/test_regress/t/t_randomize_prepost_with_baseref.v new file mode 100644 index 000000000..1bfe4ca64 --- /dev/null +++ b/test_regress/t/t_randomize_prepost_with_baseref.v @@ -0,0 +1,94 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +class Base; + rand bit [7:0] a; + bit [7:0] m_pre; + bit [7:0] m_post; +endclass + +class Derived extends Base; + function void pre_randomize; + `checkd(m_pre, 8'd0); + m_pre = 8'd10; + endfunction + function void post_randomize; + `checkd(m_pre, 8'd10); + m_post = a + 8'd1; + endfunction +endclass + +class Base2; + rand bit [7:0] b; + bit [7:0] bp; + bit [7:0] bq; + function void pre_randomize; + bp = 8'd1; + endfunction + function void post_randomize; + bq = b; + endfunction +endclass + +class Derived2 extends Base2; + bit [7:0] dp; + bit [7:0] dq; + function void pre_randomize; + dp = 8'd2; + super.pre_randomize(); + endfunction + function void post_randomize; + dq = b + 8'd1; + super.post_randomize(); + endfunction +endclass + +module t; + initial begin + Base b; + Derived d; + Base2 b2; + Derived2 d2; + int ok; + + // Plain randomize through a base handle already dispatches pre/post + d = new; + b = d; + ok = b.randomize(); + `checkd(ok, 1); + `checkd(d.m_pre, 8'd10); + `checkd(d.m_post, d.a + 8'd1); + + // randomize() with through a base handle whose static type lacks pre/post + d = new; + b = d; + ok = b.randomize() with {a == 8'h3c;}; + `checkd(ok, 1); + `checkd(b.a, 8'h3c); + `checkd(d.m_pre, 8'd10); + `checkd(d.m_post, 8'h3d); + + // randomize() with through a base handle that DOES define pre/post, + // overridden by the derived class with super chaining + d2 = new; + b2 = d2; + ok = b2.randomize() with {b == 8'h11;}; + `checkd(ok, 1); + `checkd(d2.b, 8'h11); + `checkd(d2.dp, 8'd2); + `checkd(d2.bp, 8'd1); + `checkd(d2.dq, 8'h12); + `checkd(d2.bq, 8'h11); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_randsequence.v b/test_regress/t/t_randsequence.v index 739178d02..7d00d9e15 100644 --- a/test_regress/t/t_randsequence.v +++ b/test_regress/t/t_randsequence.v @@ -7,8 +7,9 @@ // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // verilog_format: off -`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); -`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); $stop; end while(0); +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0); `define check_within_30_percent(gotv,val) `check_range((gotv), (val) * 70 / 100, (val) * 130 / 100) // verilog_format: on diff --git a/test_regress/t/t_randsequence_func.v b/test_regress/t/t_randsequence_func.v index ac06220bc..2374251af 100644 --- a/test_regress/t/t_randsequence_func.v +++ b/test_regress/t/t_randsequence_func.v @@ -7,7 +7,8 @@ // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // verilog_format: off -`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); // verilog_format: on module t; diff --git a/test_regress/t/t_randsequence_randjoin.v b/test_regress/t/t_randsequence_randjoin.v index 47bd297c2..d0d2febcd 100644 --- a/test_regress/t/t_randsequence_randjoin.v +++ b/test_regress/t/t_randsequence_randjoin.v @@ -7,8 +7,9 @@ // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // verilog_format: off -`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); -`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); $stop; end while(0); +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0); `define check_within_30_percent(gotv,val) `check_range((gotv), (val) * 70 / 100, (val) * 130 / 100) // verilog_format: on diff --git a/test_regress/t/t_randsequence_svtests.v b/test_regress/t/t_randsequence_svtests.v index dd087d63b..b4c549148 100644 --- a/test_regress/t/t_randsequence_svtests.v +++ b/test_regress/t/t_randsequence_svtests.v @@ -4,7 +4,8 @@ // SPDX-License-Identifier: ISC // verilog_format: off -`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); // verilog_format: on module t; diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp b/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp new file mode 100644 index 000000000..27e7a3d39 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp @@ -0,0 +1,45 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +#include + +#include +#include + +#include VM_PREFIX_INCLUDE + +int main(int argc, char** argv) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->threads(1); + contextp->commandArgs(argc, argv); + + const std::unique_ptr topp{new VM_PREFIX{contextp.get(), "top"}}; + topp->clk = 0; + topp->i = 0; + topp->eval(); + + while ((contextp->time() < 10000) && !contextp->gotFinish()) { + contextp->timeInc(1); + topp->clk = !topp->clk; + // Always set to the same constant value, so change detection will think it's not changing + topp->i = 500000; + topp->eval(); + if (topp->o != topp->i + 10) { + const std::string msg = "%Error: incorrect output, got: " + std::to_string(topp->o) + + " expected: " + std::to_string(topp->i + 10); + vl_fatal(__FILE__, __LINE__, "main", msg.c_str()); + break; + } + } + + if (!contextp->gotFinish()) { + vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish"); + } + topp->final(); + return 0; +} diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.py b/test_regress/t/t_sched_ico_change_detect_input_assigned.py new file mode 100755 index 000000000..c837fa2b9 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp" + ]) + +test.execute() + +# There should be a change detect for 'clk', but not for 'i' +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.v b/test_regress/t/t_sched_ico_change_detect_input_assigned.v new file mode 100644 index 000000000..9a4f79b0b --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + clk, + i, + o, + cyc +); + + input clk, i; + output o, cyc; + + logic clk; + int i; // Primary input that the design also drives + int o; + int cyc = 0; + + // Logic dependent on primary input 'i' + always_comb o = i + 10; + + always @(posedge clk) begin + cyc <= cyc + 1; + // On even cycles, assign 'i' + if (cyc % 2 == 0) i = cyc + 1000; + if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py new file mode 100755 index 000000000..b98a8e1b6 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "-fno-ico-change-detect" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 0) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py new file mode 100755 index 000000000..cd22fef43 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "--vpi" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 0) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py new file mode 100755 index 000000000..afd44e7ce --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "-fico-change-detect", "--vpi" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_select_bound4.py b/test_regress/t/t_select_bound4.py new file mode 100755 index 000000000..00e3aae74 --- /dev/null +++ b/test_regress/t/t_select_bound4.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_select_bound4.v b/test_regress/t/t_select_bound4.v new file mode 100644 index 000000000..d09a59a52 --- /dev/null +++ b/test_regress/t/t_select_bound4.v @@ -0,0 +1,29 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module t; + int q[2][$]; + + task automatic pop_q(input int qid, input int expected); + int actual; + actual = q[qid].pop_front(); + if (qid < 2 && actual !== expected) $stop; + endtask + + initial begin + for (int i = 0; i < 4; i++) begin + q[i].push_back(i); + end + + for (int i = 0; i < 4; i++) begin + pop_q(i, i); + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_select_bound_side_effect.py b/test_regress/t/t_select_bound_side_effect.py new file mode 100755 index 000000000..d696b53d9 --- /dev/null +++ b/test_regress/t/t_select_bound_side_effect.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_select_bound_side_effect.v b/test_regress/t/t_select_bound_side_effect.v new file mode 100644 index 000000000..b6a182d04 --- /dev/null +++ b/test_regress/t/t_select_bound_side_effect.v @@ -0,0 +1,74 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + int arr[5]; + int x = 0; + int y = 0; + int z = 0; + + task automatic incr(input int i, input int expected); + arr[i] = x++; + if (i < 5) `checkh(arr[i], expected); + endtask + + function int get_y; + y++; + return y; + endfunction + + task automatic assign_side_effect(input int i, input int expected); + arr[i] = get_y(); + if (i < 5) `checkh(arr[i], expected); + endtask + + task automatic add_z(inout int a); + a += z; + z++; + endtask + + task automatic assign_side_effect_inout(input int i, input int expected); + if (i < 5) arr[i] = 1; + add_z(arr[i]); + if (i < 5) `checkh(arr[i], expected); + endtask + + initial begin + incr(0, 0); + incr(7, 0); + incr(4, 2); + + assign_side_effect(3, 1); + assign_side_effect(8, 0); + assign_side_effect(9, 0); + assign_side_effect(3, 4); + + assign_side_effect_inout(3, 1); + assign_side_effect_inout(4, 2); + assign_side_effect_inout(5, 0); + assign_side_effect_inout(1, 4); + + y = 0; + for (int i = 0; i < 10; i++) begin + arr[get_y()] = i; + if (y < 5) `checkh(arr[y], i); + `checkh(y, 2 * i + 1); + arr[get_y()%(i+1)] = i; + if (y % (i + 1) < 5) `checkh(arr[y%(i+1)], i); + `checkh(y, 2 * (i + 1)); + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py b/test_regress/t/t_select_bound_timing_intra.py similarity index 71% rename from test_regress/t/t_gate_inline_wide_noexclude_small_wide.py rename to test_regress/t/t_select_bound_timing_intra.py index c20d324db..4fae43d09 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py +++ b/test_regress/t/t_select_bound_timing_intra.py @@ -9,10 +9,12 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('simulator') -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) +test.compile(verilator_flags2=["--binary", "--stats"]) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_select_bound_timing_intra.v b/test_regress/t/t_select_bound_timing_intra.v new file mode 100644 index 000000000..31d30c48c --- /dev/null +++ b/test_regress/t/t_select_bound_timing_intra.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + int arr[5]; + + task automatic intra(input int i); + time t = $time; + arr[i] = #1 i; + #1; + if (i < 5) `checkh(arr[i], i); + `checkh($time, t + 2); + endtask + + initial begin + intra(0); + intra(7); + intra(4); + intra(1); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_sequence_first_match_unsup.out b/test_regress/t/t_sequence_first_match_unsup.out deleted file mode 100644 index e4e3a5d37..000000000 --- a/test_regress/t/t_sequence_first_match_unsup.out +++ /dev/null @@ -1,47 +0,0 @@ -%Error: t/t_sequence_first_match_unsup.v:51:34: Usage of cycle delays requires default clocking (IEEE 1800-2023 14.11) - : ... note: In instance 'main' - 51 | initial p0 : assert property ((##1 1) or(##2 1) |-> x == 1); - | ^~ - ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_sequence_first_match_unsup.v:51:44: Usage of cycle delays requires default clocking (IEEE 1800-2023 14.11) - : ... note: In instance 'main' - 51 | initial p0 : assert property ((##1 1) or(##2 1) |-> x == 1); - | ^~ -%Error-UNSUPPORTED: t/t_sequence_first_match_unsup.v:51:16: Unsupported: Unclocked assertion - : ... note: In instance 'main' - 51 | initial p0 : assert property ((##1 1) or(##2 1) |-> x == 1); - | ^~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: t/t_sequence_first_match_unsup.v:54:47: Usage of cycle delays requires default clocking (IEEE 1800-2023 14.11) - : ... note: In instance 'main' - 54 | initial p1 : assert property (first_match ((##1 1) or(##2 1)) |-> x == 1); - | ^~ -%Error: t/t_sequence_first_match_unsup.v:54:57: Usage of cycle delays requires default clocking (IEEE 1800-2023 14.11) - : ... note: In instance 'main' - 54 | initial p1 : assert property (first_match ((##1 1) or(##2 1)) |-> x == 1); - | ^~ -%Error-UNSUPPORTED: t/t_sequence_first_match_unsup.v:54:16: Unsupported: Unclocked assertion - : ... note: In instance 'main' - 54 | initial p1 : assert property (first_match ((##1 1) or(##2 1)) |-> x == 1); - | ^~~~~~ -%Error: t/t_sequence_first_match_unsup.v:57:38: Usage of cycle delays requires default clocking (IEEE 1800-2023 14.11) - : ... note: In instance 'main' - 57 | initial p2 : assert property (1 or ##1 1 |-> x == 0); - | ^~ -%Error-UNSUPPORTED: t/t_sequence_first_match_unsup.v:57:16: Unsupported: Unclocked assertion - : ... note: In instance 'main' - 57 | initial p2 : assert property (1 or ##1 1 |-> x == 0); - | ^~~~~~ -%Error: t/t_sequence_first_match_unsup.v:60:51: Usage of cycle delays requires default clocking (IEEE 1800-2023 14.11) - : ... note: In instance 'main' - 60 | initial p3 : assert property (first_match (1 or ##1 1) |-> x == 0); - | ^~ -%Error-UNSUPPORTED: t/t_sequence_first_match_unsup.v:60:16: Unsupported: Unclocked assertion - : ... note: In instance 'main' - 60 | initial p3 : assert property (first_match (1 or ##1 1) |-> x == 0); - | ^~~~~~ -%Error: Internal Error: t/t_sequence_first_match_unsup.v:51:34: ../V3Ast.cpp:#: AstSExpr must have non-nullptr delayp() - : ... note: In instance 'main' - 51 | initial p0 : assert property ((##1 1) or(##2 1) |-> x == 1); - | ^~ - ... This fatal error may be caused by the earlier error(s); resolve those first. diff --git a/test_regress/t/t_sequence_first_match_unsup.v b/test_regress/t/t_sequence_first_match_unsup.v deleted file mode 100644 index 5f4d08838..000000000 --- a/test_regress/t/t_sequence_first_match_unsup.v +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2001-2020 Daniel Kroening, Edmund Clarke -// SPDX-License-Identifier: BSD-3-Clause -// -// (C) 2001-2020, Daniel Kroening, Edmund Clarke, -// Computer Science Department, University of Oxford -// Computer Science Department, Carnegie Mellon University -// -// All rights reserved. Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// 1. Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the University nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// -// You can contact the author at: -// - homepage : https://www.cprover.org/ebmc/ -// - source repository : https://github.com/diffblue/hw-cbmc - -module main ( - input clk -); - - reg [31:0] x = 0; - - always @(posedge clk) x <= x + 1; - - // Starting from a particular state, - // first_match yields the sequence that _ends_ first. - - // fails - initial p0 : assert property ((##1 1) or(##2 1) |-> x == 1); - - // passes - initial p1 : assert property (first_match ((##1 1) or(##2 1)) |-> x == 1); - - // fails - initial p2 : assert property (1 or ##1 1 |-> x == 0); - - // passes - initial p3 : assert property (first_match (1 or ##1 1) |-> x == 0); - -endmodule diff --git a/test_regress/t/t_sequence_intersect.v b/test_regress/t/t_sequence_intersect.v index 1e1b754b5..4e278a177 100644 --- a/test_regress/t/t_sequence_intersect.v +++ b/test_regress/t/t_sequence_intersect.v @@ -79,6 +79,10 @@ module t ( assert property (@(posedge clk) (1'b1 ##1 1'b1) intersect (1'b1 ##1 1'b1)); + // Leading-delay operands (no offset-0 check): conjoin first offset > 0. + assert property (@(posedge clk) + (##2 1'b1) intersect (##2 1'b1)); + // Intersect with `throughout` on one side: exercises fixedLength's // SThroughout branch (recurses into rhs to compute the length). cover property (@(posedge clk) diff --git a/test_regress/t/t_sequence_intersect_len_warn.out b/test_regress/t/t_sequence_intersect_len_warn.out deleted file mode 100644 index f8239fae1..000000000 --- a/test_regress/t/t_sequence_intersect_len_warn.out +++ /dev/null @@ -1,10 +0,0 @@ -%Error: t/t_sequence_intersect_len_warn.v:16:17: Intersect sequence length mismatch: left 1 cycles, right 3 cycles (IEEE 1800-2023 16.9.6) - : ... note: In instance 't' - 16 | (a ##1 b) intersect (c ##3 d)); - | ^~~~~~~~~ - ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_sequence_intersect_len_warn.v:20:17: Intersect sequence length mismatch: left 3 cycles, right 1 cycles (IEEE 1800-2023 16.9.6) - : ... note: In instance 't' - 20 | (a ##3 b) intersect (c ##1 d)); - | ^~~~~~~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_sequence_intersect_len_warn.v b/test_regress/t/t_sequence_intersect_len_warn.v deleted file mode 100644 index e544c8926..000000000 --- a/test_regress/t/t_sequence_intersect_len_warn.v +++ /dev/null @@ -1,22 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH -// SPDX-License-Identifier: CC0-1.0 - -// verilog_format: off -// verilog_lint: off -// verilog_format: on - -module t (input clk); - logic a, b, c, d; - - // LHS length 2, RHS length 4 -- WIDTHTRUNC (left < right) - assert property (@(posedge clk) - (a ##1 b) intersect (c ##3 d)); - - // LHS length 4, RHS length 2 -- WIDTHEXPAND (left > right) - assert property (@(posedge clk) - (a ##3 b) intersect (c ##1 d)); - -endmodule diff --git a/test_regress/t/t_sequence_intersect_nevermatch.py b/test_regress/t/t_sequence_intersect_nevermatch.py new file mode 100755 index 000000000..ddef50cab --- /dev/null +++ b/test_regress/t/t_sequence_intersect_nevermatch.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--assert --timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_sequence_intersect_nevermatch.v b/test_regress/t/t_sequence_intersect_nevermatch.v new file mode 100644 index 000000000..38abde53a --- /dev/null +++ b/test_regress/t/t_sequence_intersect_nevermatch.v @@ -0,0 +1,59 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + integer cyc = 0; + reg [63:0] crc = 64'h5aef0c8d_d70a4497; + + wire a = crc[0]; + wire b = crc[1]; + wire c = crc[2]; + wire d = crc[3]; + + int f_fix = 0; + int f_dis = 0; + + always_ff @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; + if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + default clocking @(posedge clk); + endclocking + + // An intersect whose operands share no common length compiles and never + // matches (IEEE 1800-2023 16.9.6 requires a window of equal length). Each + // form is the antecedent of `|-> 1'b0`: it stays vacuous while it never + // matches, so the else fires once per match -- pinned to 0. A spurious match + // would both fail the assertion and bump the counter. + + // Unequal fixed lengths: {2} vs {3}. + ap_fix : + assert property (disable iff (cyc < 2) ((a ##2 b) intersect (c ##3 d)) |-> 1'b0) + else f_fix <= f_fix + 1; + + // Disjoint ranges: {1,2} vs {4,5}. + ap_dis : + assert property (disable iff (cyc < 2) ((a ##[1:2] b) intersect (c ##[4:5] d)) |-> 1'b0) + else f_dis <= f_dis + 1; + + final begin + // TODO need better non-zero test + `checkd(f_fix, 0); + `checkd(f_dis, 0); + end +endmodule diff --git a/test_regress/t/t_sequence_intersect_range_unsup.out b/test_regress/t/t_sequence_intersect_range_unsup.out index 3d71a9f29..8788a6735 100644 --- a/test_regress/t/t_sequence_intersect_range_unsup.out +++ b/test_regress/t/t_sequence_intersect_range_unsup.out @@ -1,14 +1,18 @@ -%Error: t/t_sequence_intersect_range_unsup.v:12:10: Usage of cycle delays requires default clocking (IEEE 1800-2023 14.11) - : ... note: In instance 't' - 12 | (a ##[1:5] b) intersect (c ##2 d)); - | ^~ - ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_sequence_intersect_range_unsup.v:12:34: Usage of cycle delays requires default clocking (IEEE 1800-2023 14.11) - : ... note: In instance 't' - 12 | (a ##[1:5] b) intersect (c ##2 d)); - | ^~ -%Error: Internal Error: t/t_sequence_intersect_range_unsup.v:12:10: ../V3Ast.cpp:#: AstSExpr must have non-nullptr delayp() - : ... note: In instance 't' - 12 | (a ##[1:5] b) intersect (c ##2 d)); - | ^~ - ... This fatal error may be caused by the earlier error(s); resolve those first. +%Error-UNSUPPORTED: t/t_sequence_intersect_range_unsup.v:16:44: Unsupported: intersect with this variable-length operand + : ... note: In instance 't' + 16 | assert property ((a ##[1:3] b ##[1:2] c) intersect (d ##2 e)); + | ^~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_sequence_intersect_range_unsup.v:19:45: Unsupported: intersect operand is not a plain boolean sequence + : ... note: In instance 't' + 19 | assert property ((a throughout (b ##1 c)) intersect (d ##[0:2] e)); + | ^~~~~~~~~ +%Error-UNSUPPORTED: t/t_sequence_intersect_range_unsup.v:22:42: Unsupported: intersect of two sequences that each vary in length over a range with internal structure + : ... note: In instance 't' + 22 | assert property ((a ##[1:3] (b ##1 c)) intersect (d ##[2:4] e)); + | ^~~~~~~~~ +%Error-UNSUPPORTED: t/t_sequence_intersect_range_unsup.v:25:42: Unsupported: intersect of two sequences that each vary in length over a range with internal structure + : ... note: In instance 't' + 25 | assert property ((a ##2 (b ##[1:3] c)) intersect (d ##[3:5] e)); + | ^~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_sequence_intersect_range_unsup.v b/test_regress/t/t_sequence_intersect_range_unsup.v index 837f9b146..567496a47 100644 --- a/test_regress/t/t_sequence_intersect_range_unsup.v +++ b/test_regress/t/t_sequence_intersect_range_unsup.v @@ -4,11 +4,24 @@ // SPDX-FileCopyrightText: 2026 PlanV GmbH // SPDX-License-Identifier: CC0-1.0 -module t (input clk); - logic a, b, c, d; +module t ( + input clk +); + logic a, b, c, d, e; - // Range delay in intersect operand is unsupported - assert property (@(posedge clk) - (a ##[1:5] b) intersect (c ##2 d)); + default clocking @(posedge clk); + endclocking + + // Two ranged cycle delays in one intersect operand is unsupported + assert property ((a ##[1:3] b ##[1:2] c) intersect (d ##2 e)); + + // Single common length, but an operand is not a plain boolean sequence + assert property ((a throughout (b ##1 c)) intersect (d ##[0:2] e)); + + // Both operands vary over a range and carry internal structure + assert property ((a ##[1:3] (b ##1 c)) intersect (d ##[2:4] e)); + + // Operand top-level delay fixed but length varies via a nested range + assert property ((a ##2 (b ##[1:3] c)) intersect (d ##[3:5] e)); endmodule diff --git a/test_regress/t/t_sequence_intersect_varlen.py b/test_regress/t/t_sequence_intersect_varlen.py new file mode 100755 index 000000000..ddef50cab --- /dev/null +++ b/test_regress/t/t_sequence_intersect_varlen.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--assert --timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_sequence_intersect_varlen.v b/test_regress/t/t_sequence_intersect_varlen.v new file mode 100644 index 000000000..72eaa8601 --- /dev/null +++ b/test_regress/t/t_sequence_intersect_varlen.v @@ -0,0 +1,72 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + integer cyc = 0; + reg [63:0] crc = 64'h5aef0c8d_d70a4497; + + wire a = crc[0]; + wire b = crc[1]; + wire c = crc[2]; + wire d = crc[3]; + wire e = crc[4]; + + int f_var = 0; + int f_ieee = 0; + int f_collapse = 0; + int f_over = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; + if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + default clocking @(posedge clk); + endclocking + + // Both operands vary in length: lhs in [1,3], rhs in [2,5], common [2,3]. + // Variable-length intersect (IEEE 1800-2023 16.9.6): both match the same + // window with equal start and end, length chosen from each operand's range. + ap_var : + assert property (disable iff (cyc < 2) (a & c) |-> ((a ##[1:3] b) intersect (c ##[2:5] d))) + else f_var <= f_var + 1; + + // IEEE 16.9.6 canonical: one variable + one fixed operand, common length {4}. + ap_ieee : + assert property (disable iff (cyc < 2) a |-> ((a ##[1:5] b) intersect (c ##2 d ##2 e))) + else f_ieee <= f_ieee + 1; + + // Common length collapses to {0}: (a ##[0:3] b) intersect c == a & b & c, + // so this implication never fails (exercises the bare-boolean lowering path). + ap_collapse : + assert property (disable iff (cyc < 2) (a & b & c) |-> ((a ##[0:3] b) intersect c)) + else f_collapse <= f_collapse + 1; + + // Equal fixed length, one operand carrying an internal check: lowered through + // the per-cycle conjoin, not the done-latch combiner (which conflates + // concurrent attempts and over-accepts). + ap_over : + assert property (disable iff (cyc < 2) (a ##4 b) intersect (c ##2 d ##2 e)) + else f_over <= f_over + 1; + + final begin + `checkd(f_var, 7); + `checkd(f_ieee, 41); + `checkd(f_collapse, 0); + `checkd(f_over, 84); + end +endmodule diff --git a/test_regress/t/t_sequence_ref_bad.out b/test_regress/t/t_sequence_ref_bad.out new file mode 100644 index 000000000..afe2fe2f7 --- /dev/null +++ b/test_regress/t/t_sequence_ref_bad.out @@ -0,0 +1,7 @@ +%Error: t/t_sequence_ref_bad.v:14:6: Concurrent assertion has no clock (IEEE 1800-2023 16.16) + : ... note: In instance 't' + : ... Suggest provide a clocking event, a default clocking, or a clocked procedural context + 14 | @s_one; + | ^~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_sequence_ref_unsup.py b/test_regress/t/t_sequence_ref_bad.py similarity index 100% rename from test_regress/t/t_sequence_ref_unsup.py rename to test_regress/t/t_sequence_ref_bad.py diff --git a/test_regress/t/t_sequence_ref_unsup.v b/test_regress/t/t_sequence_ref_bad.v similarity index 100% rename from test_regress/t/t_sequence_ref_unsup.v rename to test_regress/t/t_sequence_ref_bad.v diff --git a/test_regress/t/t_sequence_ref_unsup.out b/test_regress/t/t_sequence_ref_unsup.out deleted file mode 100644 index 41fb50aad..000000000 --- a/test_regress/t/t_sequence_ref_unsup.out +++ /dev/null @@ -1,6 +0,0 @@ -%Error-UNSUPPORTED: t/t_sequence_ref_unsup.v:9:12: Unsupported: sequence referenced outside assertion property - : ... note: In instance 't' - 9 | sequence s_one; - | ^~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_sequence_sexpr_throughout.v b/test_regress/t/t_sequence_sexpr_throughout.v index a2b17ef46..8b9fe3cfa 100644 --- a/test_regress/t/t_sequence_sexpr_throughout.v +++ b/test_regress/t/t_sequence_sexpr_throughout.v @@ -74,7 +74,7 @@ module t ( // by the throughout-drop check. cover property (@(posedge clk) a throughout (b ##1 c)); - always_ff @(posedge clk) begin + always @(posedge clk) begin `ifdef TEST_VERBOSE $write("[%0t] cyc==%0d crc=%x cond=%b a=%b b=%b c=%b\n", $time, cyc, crc, cond, a, b, c); @@ -85,15 +85,15 @@ module t ( crc <= 64'h5aef0c8d_d70a4497; end else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); - `checkd(count_fail1, 28); // Questa: 28 - `checkd(count_fail2, 33); // Questa: 33 - `checkd(count_fail3, 31); // Questa: 31 - `checkd(count_fail4, 35); // Questa: 35 + `checkd(count_fail1, 28); + `checkd(count_fail2, 33); + `checkd(count_fail3, 31); + `checkd(count_fail4, 35); // count_fail5: NFA undercounts by 12; throughout+temporal-and first-step // rejection is a known limitation of the SAnd combiner architecture // (propagating isTopLevelStep causes double-counting; fix is future work). - `checkd(count_fail5, 25); // Questa: 36 - `checkd(count_fail6, 33); // Questa: 33 + `checkd(count_fail5, 25); // All other sims: 36 + `checkd(count_fail6, 33); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_sequence_sexpr_unsup.out b/test_regress/t/t_sequence_sexpr_unsup.out index 975a06491..9bf5bf9ea 100644 --- a/test_regress/t/t_sequence_sexpr_unsup.out +++ b/test_regress/t/t_sequence_sexpr_unsup.out @@ -8,15 +8,4 @@ %Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:99:5: Unsupported: first_match with sequence_match_items 99 | first_match (a, res0 = 1, res1 = 2); | ^~~~~~~~~~~ -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:102:9: Ignoring unsupported: cover sequence - 102 | cover sequence (s_a) $display(""); - | ^~~~~~~~ - ... For warning description see https://verilator.org/warn/COVERIGN?v=latest - ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:103:9: Ignoring unsupported: cover sequence - 103 | cover sequence (@(posedge a) disable iff (b) s_a) $display(""); - | ^~~~~~~~ -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:104:9: Ignoring unsupported: cover sequence - 104 | cover sequence (disable iff (b) s_a) $display(""); - | ^~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_sequence_within.v b/test_regress/t/t_sequence_within.v index a8ec28b25..e8d73ad61 100644 --- a/test_regress/t/t_sequence_within.v +++ b/test_regress/t/t_sequence_within.v @@ -11,9 +11,6 @@ // verilog_format: on // IEEE 1800-2023 16.9.10: seq1 within seq2 -// CRC-driven random stimulus. Each property has a counter; at cyc==99 we -// `checkd` against Verilator's actual count and record the Questa golden -// value in a trailing comment for cross-simulator reference. module t ( input clk @@ -91,7 +88,9 @@ module t ( (a ##3 b) intersect ((c ##1 d) within (a ##3 b))) count_p10 <= count_p10 + 1; - always_ff @(posedge clk) begin + initial $assertvacuousoff; + + always @(posedge clk) begin cyc <= cyc + 1; crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; @@ -101,30 +100,20 @@ module t ( else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); // p1/p2/p5 use |->; the NFA currently fires the pass action on - // vacuous passes too, so counts are inflated vs. Questa. Pre-existing + // vacuous passes too, so counts are inflated vs. others. Pre-existing // engine-wide behavior, not within-specific. - `checkd(count_p1, 89); // Questa: 23 - `checkd(count_p2, 89); // Questa: 44 - `checkd(count_p3, 26); // Questa: 20 - `checkd(count_p4, 24); // Questa: 22 - `checkd(count_p5, 89); // Questa: 26 - `checkd(count_p6, 21); // Questa: 16 - `checkd(count_p7, 15); // Questa: 9 - `checkd(count_p8, 15); // Questa: 4 - `checkd(count_p9, 17); // Questa: 10 - `checkd(count_p10, 24); // Questa: 15 + `checkd(count_p1, 23); // Other sims: 23, or 16 + `checkd(count_p2, 44); // Other sims: 44, or 21 + `checkd(count_p3, 25); // Other sims: 20 + `checkd(count_p4, 23); // Other sims: 22 + `checkd(count_p5, 26); + `checkd(count_p6, 21); // Other sims: 16 + `checkd(count_p7, 15); // Other sims: 9 + `checkd(count_p8, 15); // Other sims: 4 + `checkd(count_p9, 15); // Other sims: 10 + `checkd(count_p10, 23); // Other sims: 15 $write("*-* All Finished *-*\n"); $finish; end end endmodule - -// Harness for stand-alone simulators (e.g. QuestaSim). Verilator uses -// test_regress's built-in clock shell and ignores this module. -`ifndef VERILATOR -module wrap; - logic clk = 0; - always #5 clk = ~clk; - t inst (.clk(clk)); -endmodule -`endif diff --git a/test_regress/t/t_stream_queue.py b/test_regress/t/t_stream_queue.py index 3a46a7545..f7785cc52 100755 --- a/test_regress/t/t_stream_queue.py +++ b/test_regress/t/t_stream_queue.py @@ -9,7 +9,8 @@ import vltest_bootstrap -test.scenarios('simulator') +# Issue #7780 unstable with --vltmt +test.scenarios('simulator_st') test.compile(verilator_flags2=["--timing"]) diff --git a/test_regress/t/t_stream_unpacked_struct.py b/test_regress/t/t_stream_unpacked_struct.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct.v b/test_regress/t/t_stream_unpacked_struct.v new file mode 100644 index 000000000..6b0c6f2d6 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.v @@ -0,0 +1,378 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// Ref. to IEEE 1800-2023 11.4.14, A.8.1 + +module t; + + `define checkh(gotv, expv) \ + do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__, `__LINE__, (gotv), (expv)); \ + $stop; \ + end while (0); + + typedef struct packed { + logic [3:0] hi; + logic [3:0] lo; + } packed_pair_t; + + typedef union packed { + logic [7:0] byte_data; + packed_pair_t pair; + } packed_union_t; + + typedef struct { + byte a; + logic [3:0] b; + packed_pair_t p; + } simple_t; + + typedef struct { + byte prefix; + byte data[2]; + packed_pair_t p; + } array_t; + + typedef struct { + simple_t inner; + byte tail[2]; + } nested_t; + + typedef struct { + byte prefix; + simple_t items[2]; + byte suffix; + } struct_array_t; + + typedef struct { + byte data[1:0]; + } descending_array_t; + + typedef struct { + byte matrix[2][3]; + } matrix_array_t; + + typedef struct { + logic [1:0][3:0] data[2]; + } mixed_array_t; + + typedef enum logic [2:0] { + E0 = 3'd0, + E5 = 3'd5 + } enum_t; + + typedef struct { + enum_t e; + logic [4:0] x; + } enum_struct_t; + + typedef struct { + byte tag; + real r; + realtime rt; + real ra[2]; + } real_struct_t; + + typedef struct { + packed_union_t u; + byte tail; + } packed_union_struct_t; + + localparam int WORD_WIDTH = 16; + + typedef union packed { + struct packed { + logic [15:0] temp_1; + logic [15:0] temp_2; + logic [15:0] temp_3; + } regs; + logic [($bits(regs) / WORD_WIDTH)-1:0][WORD_WIDTH-1:0] words; + } issue_4521_union_t; + + simple_t simple; + simple_t simple_out; + simple_t simple_cont_out; + array_t arrayed; + array_t arrayed_out; + nested_t nested; + nested_t nested_out; + struct_array_t struct_array; + struct_array_t struct_array_out; + descending_array_t descending; + descending_array_t descending_out; + matrix_array_t matrix_array; + matrix_array_t matrix_array_out; + mixed_array_t mixed_array; + mixed_array_t mixed_array_out; + simple_t simple_array[2]; + simple_t simple_array_out[2]; + enum_struct_t enum_struct; + enum_struct_t enum_struct_out; + real_struct_t real_struct; + real_struct_t real_struct_out; + packed_union_struct_t packed_union_struct; + packed_union_struct_t packed_union_struct_out; + issue_4521_union_t issue_4521_union; + + logic [19:0] simple_bits; + logic [31:0] wide_simple_bits; + logic [31:0] array_bits; + logic [35:0] nested_bits; + logic [55:0] struct_array_bits; + logic [15:0] descending_bits; + logic [47:0] matrix_array_bits; + logic [15:0] mixed_array_bits; + logic [39:0] simple_array_bits; + logic [31:0] byteswapped_bits; + logic [7:0] enum_bits; + logic [263:0] real_bits; + logic [15:0] packed_union_bits; + logic [11:0] narrow_bits; + logic [19:0] simple_streaml_src; + logic [$bits(simple_t)-1:0] simple_bits_from_bits; + byte byte_array_out[2]; + + assign {>>{simple_cont_out}} = 20'habcde; + + initial begin + byteswapped_bits = {<<8{32'h11223344}}; + `checkh(byteswapped_bits, 32'h44332211); + byteswapped_bits = {<>{simple_bits}} = 20'h13579; + `checkh(simple_bits, 20'h13579); + {<<4{simple_bits}} = 20'h12345; + `checkh(simple_bits, 20'h54321); + + simple = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + `checkh($bits(simple_t), 20); + `checkh($bits(array_t), 32); + `checkh($bits(nested_t), 36); + `checkh($bits(struct_array_t), 56); + `checkh($bits(simple_array), 40); + simple_bits_from_bits = {>>{simple}}; + `checkh(simple_bits_from_bits, 20'h12abc); + simple_bits = {>>{simple}}; + `checkh(simple_bits, 20'h12abc); + /* verilator lint_off WIDTHEXPAND */ + wide_simple_bits = {>>{simple}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(wide_simple_bits, 32'h12abc000); + simple_bits = {<<4{simple}}; + `checkh(simple_bits, 20'hcba21); + + {>>{simple_out}} = 20'h345de; + `checkh(simple_out.a, 8'h34); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'hde); + `checkh(simple_cont_out.a, 8'hab); + `checkh(simple_cont_out.b, 4'hc); + `checkh(simple_cont_out.p, 8'hde); + + {<<4{simple_out}} = 20'h789ab; + `checkh(simple_out.a, 8'hba); + `checkh(simple_out.b, 4'h9); + `checkh(simple_out.p, 8'h87); + + simple_out = {>>{20'hfedcb}}; + `checkh(simple_out.a, 8'hfe); + `checkh(simple_out.b, 4'hd); + `checkh(simple_out.p, 8'hcb); + + simple_out = {>>{simple}}; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + {>>{simple_out}} = simple; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + if ($test$plusargs("t_stream_unpacked_struct_alt")) begin + narrow_bits = 12'h123; + end + else begin + narrow_bits = 12'habd; + end + /* verilator lint_off WIDTHEXPAND */ + simple_bits = {>>{narrow_bits}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(simple_bits, {narrow_bits, 8'h00}); + + simple_out = {>>{narrow_bits}}; + `checkh(simple_out.a, narrow_bits[11:4]); + `checkh(simple_out.b, narrow_bits[3:0]); + `checkh(simple_out.p, 8'h00); + + {>>{simple_out}} = 24'habcdef; + `checkh(simple_out.a, 8'hab); + `checkh(simple_out.b, 4'hc); + `checkh(simple_out.p, 8'hde); + + simple_out = {<<4{20'h13579}}; + `checkh(simple_out.a, 8'h97); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'h31); + + simple_streaml_src = 20'h2468a; + simple_out = {<<4{simple_streaml_src}}; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<4{simple_out}} = simple_streaml_src; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<8{byte_array_out}} = 16'h1234; + `checkh(byte_array_out[0], 8'h34); + `checkh(byte_array_out[1], 8'h12); + + arrayed = '{8'h11, '{8'h22, 8'h33}, '{4'h4, 4'h5}}; + array_bits = {>>{arrayed}}; + `checkh(array_bits, 32'h11223345); + array_bits = {<<8{arrayed}}; + `checkh(array_bits, 32'h45332211); + + {>>{arrayed_out}} = 32'haabbccdd; + `checkh(arrayed_out.prefix, 8'haa); + `checkh(arrayed_out.data[0], 8'hbb); + `checkh(arrayed_out.data[1], 8'hcc); + `checkh(arrayed_out.p, 8'hdd); + + nested = '{'{8'h12, 4'ha, '{4'hb, 4'hc}}, '{8'h34, 8'h56}}; + nested_bits = {>>{nested}}; + `checkh(nested_bits, 36'h12abc3456); + + {>>{nested_out}} = 36'h6543210fe; + `checkh(nested_out.inner.a, 8'h65); + `checkh(nested_out.inner.b, 4'h4); + `checkh(nested_out.inner.p, 8'h32); + `checkh(nested_out.tail[0], 8'h10); + `checkh(nested_out.tail[1], 8'hfe); + + struct_array.prefix = 8'haa; + struct_array.items[0] = '{8'h12, 4'h3, '{4'h4, 4'h5}}; + struct_array.items[1] = '{8'h67, 4'h8, '{4'h9, 4'ha}}; + struct_array.suffix = 8'hbb; + struct_array_bits = {>>{struct_array}}; + `checkh(struct_array_bits, 56'haa123456789abb); + + {>>{struct_array_out}} = 56'hccdef0123456dd; + `checkh(struct_array_out.prefix, 8'hcc); + `checkh(struct_array_out.items[0].a, 8'hde); + `checkh(struct_array_out.items[0].b, 4'hf); + `checkh(struct_array_out.items[0].p, 8'h01); + `checkh(struct_array_out.items[1].a, 8'h23); + `checkh(struct_array_out.items[1].b, 4'h4); + `checkh(struct_array_out.items[1].p, 8'h56); + `checkh(struct_array_out.suffix, 8'hdd); + + descending = '{data: '{8'hcc, 8'hdd}}; + descending_bits = {>>{descending}}; + `checkh(descending_bits, 16'hccdd); + + {>>{descending_out}} = 16'h1234; + `checkh(descending_out.data[1], 8'h12); + `checkh(descending_out.data[0], 8'h34); + + matrix_array.matrix[0][0] = 8'h11; + matrix_array.matrix[0][1] = 8'h22; + matrix_array.matrix[0][2] = 8'h33; + matrix_array.matrix[1][0] = 8'h44; + matrix_array.matrix[1][1] = 8'h55; + matrix_array.matrix[1][2] = 8'h66; + matrix_array_bits = {>>{matrix_array}}; + `checkh(matrix_array_bits, 48'h112233445566); + + {>>{matrix_array_out}} = 48'haabbccddeeff; + `checkh(matrix_array_out.matrix[0][0], 8'haa); + `checkh(matrix_array_out.matrix[0][1], 8'hbb); + `checkh(matrix_array_out.matrix[0][2], 8'hcc); + `checkh(matrix_array_out.matrix[1][0], 8'hdd); + `checkh(matrix_array_out.matrix[1][1], 8'hee); + `checkh(matrix_array_out.matrix[1][2], 8'hff); + + mixed_array.data[0] = 8'hab; + mixed_array.data[1] = 8'hcd; + mixed_array_bits = {>>{mixed_array}}; + `checkh(mixed_array_bits, 16'habcd); + + {>>{mixed_array_out}} = 16'h1234; + `checkh(mixed_array_out.data[0], 8'h12); + `checkh(mixed_array_out.data[0][1], 4'h1); + `checkh(mixed_array_out.data[0][0], 4'h2); + `checkh(mixed_array_out.data[1], 8'h34); + `checkh(mixed_array_out.data[1][1], 4'h3); + `checkh(mixed_array_out.data[1][0], 4'h4); + + simple_array[0] = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + simple_array[1] = '{8'h34, 4'hd, '{4'he, 4'hf}}; + simple_array_bits = {>>{simple_array}}; + `checkh(simple_array_bits, 40'h12abc34def); + + {>>{simple_array_out}} = 40'h567899abcd; + `checkh(simple_array_out[0].a, 8'h56); + `checkh(simple_array_out[0].b, 4'h7); + `checkh(simple_array_out[0].p, 8'h89); + `checkh(simple_array_out[1].a, 8'h9a); + `checkh(simple_array_out[1].b, 4'hb); + `checkh(simple_array_out[1].p, 8'hcd); + + enum_struct = '{E5, 5'ha}; + enum_bits = {>>{enum_struct}}; + `checkh(enum_bits, 8'haa); + + {>>{enum_struct_out}} = 8'h71; + `checkh(enum_struct_out.e, 3'h3); + `checkh(enum_struct_out.x, 5'h11); + + real_struct.tag = 8'h42; + real_struct.r = 1.0; + real_struct.rt = 3.0; + real_struct.ra[0] = 4.0; + real_struct.ra[1] = 5.0; + real_bits = {>>{real_struct}}; + `checkh(real_bits, + {8'h42, $realtobits(1.0), $realtobits(3.0), $realtobits(4.0), $realtobits(5.0)}); + + {>>{real_struct_out}} + = {8'h99, $realtobits(2.0), $realtobits(6.0), $realtobits(7.0), $realtobits(8.0)}; + `checkh(real_struct_out.tag, 8'h99); + `checkh($realtobits(real_struct_out.r), $realtobits(2.0)); + `checkh($realtobits(real_struct_out.rt), $realtobits(6.0)); + `checkh($realtobits(real_struct_out.ra[0]), $realtobits(7.0)); + `checkh($realtobits(real_struct_out.ra[1]), $realtobits(8.0)); + + packed_union_struct.u.byte_data = 8'hbe; + packed_union_struct.tail = 8'hef; + packed_union_bits = {>>{packed_union_struct}}; + `checkh(packed_union_bits, 16'hbeef); + + {>>{packed_union_struct_out}} = 16'hcafe; + `checkh(packed_union_struct_out.u.byte_data, 8'hca); + `checkh(packed_union_struct_out.tail, 8'hfe); + + `checkh($bits(issue_4521_union_t), 48); + `checkh($bits(issue_4521_union.regs), 48); + `checkh($bits(issue_4521_union.words), 48); + issue_4521_union.regs.temp_1 = 16'h1234; + issue_4521_union.regs.temp_2 = 16'h5678; + issue_4521_union.regs.temp_3 = 16'h9abc; + `checkh(issue_4521_union.words[2], 16'h1234); + `checkh(issue_4521_union.words[1], 16'h5678); + `checkh(issue_4521_union.words[0], 16'h9abc); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_stream_unpacked_struct_bad.out b/test_regress/t/t_stream_unpacked_struct_bad.out new file mode 100644 index 000000000..9955e9208 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.out @@ -0,0 +1,30 @@ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:54:12: Unsupported: Stream operation on a variable of a type 'struct{}t.queue_struct_t' + : ... note: In instance 't' + 54 | out = {>>{queue_struct}}; + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:55:12: Unsupported: Stream operation on a variable of a type 'struct{}t.dynamic_struct_t' + : ... note: In instance 't' + 55 | out = {>>{dynamic_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:56:12: Unsupported: Stream operation on a variable of a type 'struct{}t.associative_struct_t' + : ... note: In instance 't' + 56 | out = {>>{associative_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:57:12: Unsupported: Stream operation on a variable of a type 'struct{}t.union_struct_t' + : ... note: In instance 't' + 57 | out = {>>{union_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:58:12: Unsupported: Stream operation on a variable of a type 'struct{}t.string_struct_t' + : ... note: In instance 't' + 58 | out = {>>{string_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:59:12: Unsupported: Stream operation on a variable of a type 'struct{}t.chandle_struct_t' + : ... note: In instance 't' + 59 | out = {>>{chandle_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:60:12: Unsupported: Stream operation on a variable of a type 'struct{}t.event_struct_t' + : ... note: In instance 't' + 60 | out = {>>{event_struct}}; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_stream_unpacked_struct_bad.py b/test_regress/t/t_stream_unpacked_struct_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct_bad.v b/test_regress/t/t_stream_unpacked_struct_bad.v new file mode 100644 index 000000000..2c1e00b97 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.v @@ -0,0 +1,63 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module t; + + typedef struct { + byte bytes[$]; + } queue_struct_t; + + typedef struct { + byte bytes[]; + } dynamic_struct_t; + + typedef struct { + byte bytes[int]; + } associative_struct_t; + + typedef union { + byte a; + logic [15:0] b; + } unpacked_union_t; + + typedef struct { + unpacked_union_t u; + } union_struct_t; + + typedef struct { + string s; + } string_struct_t; + + typedef struct { + chandle c; + } chandle_struct_t; + + typedef struct { + event e; + } event_struct_t; + + logic [255:0] out; + queue_struct_t queue_struct; + dynamic_struct_t dynamic_struct; + associative_struct_t associative_struct; + union_struct_t union_struct; + string_struct_t string_struct; + chandle_struct_t chandle_struct; + event_struct_t event_struct; + + initial begin + out = {>>{queue_struct}}; + out = {>>{dynamic_struct}}; + out = {>>{associative_struct}}; + out = {>>{union_struct}}; + out = {>>{string_struct}}; + out = {>>{chandle_struct}}; + out = {>>{event_struct}}; + end + +endmodule diff --git a/test_regress/t/t_struct_type_bad.out b/test_regress/t/t_struct_type_bad.out index f57c1dd0c..2085d6ecd 100644 --- a/test_regress/t/t_struct_type_bad.out +++ b/test_regress/t/t_struct_type_bad.out @@ -2,4 +2,11 @@ 13 | i badi; | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_struct_type_bad.v:18:19: Reference to 'later' before declaration (IEEE 1800-2023 6.18) + : ... Suggest move the declaration before the reference + 18 | logic [($bits(later) / 8)-1:0][7:0] bad_forward; + | ^~~~~ + t/t_struct_type_bad.v:19:18: ... Location of original declaration + 19 | logic [15:0] later; + | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_struct_type_bad.v b/test_regress/t/t_struct_type_bad.v index 622f2e8e8..1d977d7b7 100644 --- a/test_regress/t/t_struct_type_bad.v +++ b/test_regress/t/t_struct_type_bad.v @@ -13,4 +13,10 @@ module t; i badi; // Bad } struct_t; + typedef union packed { + // Bad + logic [($bits(later) / 8)-1:0][7:0] bad_forward; + logic [15:0] later; + } forward_member_t; + endmodule diff --git a/test_regress/t/t_sv_cpu.py b/test_regress/t/t_sv_cpu.py index dcc41dab2..0eb062f2a 100755 --- a/test_regress/t/t_sv_cpu.py +++ b/test_regress/t/t_sv_cpu.py @@ -14,23 +14,22 @@ test.scenarios('simulator') # 22-Mar-2012: Modifications for this test contributed by Jeremy Bennett, # Embecosm. -test.compile( - # Taken from the original VCS command line. - v_flags2=[ - "t/t_sv_cpu_code/timescale.sv", "t/t_sv_cpu_code/program_h.sv", - "t/t_sv_cpu_code/pads_h.sv", "t/t_sv_cpu_code/ports_h.sv", "t/t_sv_cpu_code/pinout_h.sv", - "t/t_sv_cpu_code/genbus_if.sv", "t/t_sv_cpu_code/pads_if.sv", "t/t_sv_cpu_code/adrdec.sv", - "t/t_sv_cpu_code/pad_gpio.sv", "t/t_sv_cpu_code/pad_vdd.sv", "t/t_sv_cpu_code/pad_gnd.sv", - "t/t_sv_cpu_code/pads.sv", "t/t_sv_cpu_code/ports.sv", "t/t_sv_cpu_code/ac_dig.sv", - "t/t_sv_cpu_code/ac_ana.sv", "t/t_sv_cpu_code/ac.sv", "t/t_sv_cpu_code/cpu.sv", - "t/t_sv_cpu_code/chip.sv" - ], - vcs_flags2=["-R -sverilog +memcbk -y t/t_sv_cpu_code +libext+.sv+ +incdir+t/t_sv_cpu_code"], - verilator_flags2=[ - "-y t/t_sv_cpu_code +libext+.sv+ +incdir+t/t_sv_cpu_code --top-module t", - "--timescale-override 1ns/1ps" - ], - iv_flags2=["-yt/t_sv_cpu_code -It/t_sv_cpu_code -Y.sv"]) +test.compile(v_flags2=[ + "t/t_sv_cpu_code/timescale.sv", "t/t_sv_cpu_code/program_h.sv", "t/t_sv_cpu_code/pads_h.sv", + "t/t_sv_cpu_code/ports_h.sv", "t/t_sv_cpu_code/pinout_h.sv", "t/t_sv_cpu_code/genbus_if.sv", + "t/t_sv_cpu_code/pads_if.sv", "t/t_sv_cpu_code/adrdec.sv", "t/t_sv_cpu_code/pad_gpio.sv", + "t/t_sv_cpu_code/pad_vdd.sv", "t/t_sv_cpu_code/pad_gnd.sv", "t/t_sv_cpu_code/pads.sv", + "t/t_sv_cpu_code/ports.sv", "t/t_sv_cpu_code/ac_dig.sv", "t/t_sv_cpu_code/ac_ana.sv", + "t/t_sv_cpu_code/ac.sv", "t/t_sv_cpu_code/cpu.sv", "t/t_sv_cpu_code/chip.sv" +], + vcs_flags2=[ + "-R -sverilog +memcbk -y t/t_sv_cpu_code +libext+.sv+ +incdir+t/t_sv_cpu_code" + ], + verilator_flags2=[ + "-y t/t_sv_cpu_code +libext+.sv+ +incdir+t/t_sv_cpu_code --top-module t", + "--timescale-override 1ns/1ps" + ], + iv_flags2=["-yt/t_sv_cpu_code -It/t_sv_cpu_code -Y.sv"]) test.execute() diff --git a/test_regress/t/t_sys_file_scan_delay.dat b/test_regress/t/t_sys_file_scan_delay.dat new file mode 100644 index 000000000..b85d75e37 --- /dev/null +++ b/test_regress/t/t_sys_file_scan_delay.dat @@ -0,0 +1,4 @@ +00000010 +00000011 + +00000012 diff --git a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py b/test_regress/t/t_sys_file_scan_delay.py similarity index 71% rename from test_regress/t/t_gate_inline_wide_noexclude_other_scope.py rename to test_regress/t/t_sys_file_scan_delay.py index c20d324db..3cc73805c 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py +++ b/test_regress/t/t_sys_file_scan_delay.py @@ -9,10 +9,10 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('simulator') -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) +test.compile() -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) +test.execute() test.passes() diff --git a/test_regress/t/t_sys_file_scan_delay.v b/test_regress/t/t_sys_file_scan_delay.v new file mode 100644 index 000000000..efb4e4659 --- /dev/null +++ b/test_regress/t/t_sys_file_scan_delay.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2024 by David Harris. +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + + int fd, code; + string line; + int siglines; + + localparam SIGNATURESIZE = 5000000; + logic [31:0] sig32[0:SIGNATURESIZE]; + logic [31:0] parsed; + string signame; + + initial begin + signame = "t/t_sys_file_scan_delay.dat"; + + fd = $fopen(signame, "r"); + siglines = 0; + if (fd == 0) $display("Unable to read %s", signame); + else begin + $display("Read %s", signame); + while (!$feof( + fd + )) begin + code = $fgets(line, fd); + if (code != 0) begin + if (line.len() > 1) begin + if ($sscanf(line, "%x", sig32[siglines]) != 0) begin + $display("sig32[%1d] = %x line: ", siglines, sig32[siglines], line); + siglines = siglines + 1; // increment if line is not blank + end + end + end + end + $fclose(fd); + end + + `checkh(sig32[0], 32'h10); + `checkh(sig32[1], 32'h11); + `checkh(sig32[2], 32'h12); + $display("*-* All Finished *-*"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_tagged_case.out b/test_regress/t/t_tagged_case.out index e7965a85d..edec504d1 100644 --- a/test_regress/t/t_tagged_case.out +++ b/test_regress/t/t_tagged_case.out @@ -11,142 +11,142 @@ %Error-UNSUPPORTED: t/t_tagged_case.v:65:5: Unsupported: void (for tagged unions) 65 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:71:5: Unsupported: void (for tagged unions) - 71 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_case.v:73:5: Unsupported: void (for tagged unions) + 73 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:80:5: Unsupported: void (for tagged unions) - 80 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_case.v:82:5: Unsupported: void (for tagged unions) + 82 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:87:5: Unsupported: void (for tagged unions) - 87 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_case.v:89:5: Unsupported: void (for tagged unions) + 89 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:93:5: Unsupported: void (for tagged unions) - 93 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_case.v:95:5: Unsupported: void (for tagged unions) + 95 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:102:5: Unsupported: void (for tagged unions) - 102 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_case.v:104:5: Unsupported: void (for tagged unions) + 104 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:126:9: Unsupported: tagged union - 126 | v = tagged Invalid; +%Error-UNSUPPORTED: t/t_tagged_case.v:128:9: Unsupported: tagged union + 128 | v = tagged Invalid; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:128:5: Unsupported: case matches (for tagged union) - 128 | case (v) matches +%Error-UNSUPPORTED: t/t_tagged_case.v:130:5: Unsupported: case matches (for tagged union) + 130 | case (v) matches | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:129:7: Unsupported: tagged union - 129 | tagged Invalid : result = 1; +%Error-UNSUPPORTED: t/t_tagged_case.v:131:7: Unsupported: tagged union + 131 | tagged Invalid : result = 1; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:130:7: Unsupported: tagged pattern - 130 | tagged Valid .n : result = n; +%Error-UNSUPPORTED: t/t_tagged_case.v:132:7: Unsupported: tagged pattern + 132 | tagged Valid .n : result = n; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:130:20: Unsupported: pattern variable - 130 | tagged Valid .n : result = n; +%Error-UNSUPPORTED: t/t_tagged_case.v:132:20: Unsupported: pattern variable + 132 | tagged Valid .n : result = n; | ^ -%Error-UNSUPPORTED: t/t_tagged_case.v:135:9: Unsupported: tagged union - 135 | v = tagged Valid (123); +%Error-UNSUPPORTED: t/t_tagged_case.v:137:9: Unsupported: tagged union + 137 | v = tagged Valid (123); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:137:5: Unsupported: case matches (for tagged union) - 137 | case (v) matches +%Error-UNSUPPORTED: t/t_tagged_case.v:139:5: Unsupported: case matches (for tagged union) + 139 | case (v) matches | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:138:7: Unsupported: tagged union - 138 | tagged Invalid : result = -1; +%Error-UNSUPPORTED: t/t_tagged_case.v:140:7: Unsupported: tagged union + 140 | tagged Invalid : result = -1; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:139:7: Unsupported: tagged pattern - 139 | tagged Valid .n : result = n; +%Error-UNSUPPORTED: t/t_tagged_case.v:141:7: Unsupported: tagged pattern + 141 | tagged Valid .n : result = n; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:139:20: Unsupported: pattern variable - 139 | tagged Valid .n : result = n; +%Error-UNSUPPORTED: t/t_tagged_case.v:141:20: Unsupported: pattern variable + 141 | tagged Valid .n : result = n; | ^ -%Error-UNSUPPORTED: t/t_tagged_case.v:144:10: Unsupported: tagged union - 144 | wt = tagged Wide60 (60'hFEDCBA987654321); +%Error-UNSUPPORTED: t/t_tagged_case.v:146:10: Unsupported: tagged union + 146 | wt = tagged Wide60 (60'hFEDCBA987654321); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:146:5: Unsupported: case matches (for tagged union) - 146 | case (wt) matches +%Error-UNSUPPORTED: t/t_tagged_case.v:148:5: Unsupported: case matches (for tagged union) + 148 | case (wt) matches | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:147:7: Unsupported: tagged union - 147 | tagged Invalid : wide60_result = 0; +%Error-UNSUPPORTED: t/t_tagged_case.v:149:7: Unsupported: tagged union + 149 | tagged Invalid : wide60_result = 0; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:148:7: Unsupported: tagged pattern - 148 | tagged Wide60 .w : wide60_result = w; +%Error-UNSUPPORTED: t/t_tagged_case.v:150:7: Unsupported: tagged pattern + 150 | tagged Wide60 .w : wide60_result = w; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:148:21: Unsupported: pattern variable - 148 | tagged Wide60 .w : wide60_result = w; +%Error-UNSUPPORTED: t/t_tagged_case.v:150:21: Unsupported: pattern variable + 150 | tagged Wide60 .w : wide60_result = w; | ^ -%Error-UNSUPPORTED: t/t_tagged_case.v:154:10: Unsupported: tagged union - 154 | wt = tagged Wide90 (90'hDE_ADBEEFCA_FEBABE12_3456); +%Error-UNSUPPORTED: t/t_tagged_case.v:158:10: Unsupported: tagged union + 158 | wt = tagged Wide90 (90'hDE_ADBEEFCA_FEBABE12_3456); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:156:5: Unsupported: case matches (for tagged union) - 156 | case (wt) matches +%Error-UNSUPPORTED: t/t_tagged_case.v:160:5: Unsupported: case matches (for tagged union) + 160 | case (wt) matches | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:157:7: Unsupported: tagged union - 157 | tagged Invalid : wide90_result = 0; +%Error-UNSUPPORTED: t/t_tagged_case.v:161:7: Unsupported: tagged union + 161 | tagged Invalid : wide90_result = 0; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:158:7: Unsupported: tagged pattern - 158 | tagged Wide90 .w : wide90_result = w; +%Error-UNSUPPORTED: t/t_tagged_case.v:162:7: Unsupported: tagged pattern + 162 | tagged Wide90 .w : wide90_result = w; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:158:21: Unsupported: pattern variable - 158 | tagged Wide90 .w : wide90_result = w; +%Error-UNSUPPORTED: t/t_tagged_case.v:162:21: Unsupported: pattern variable + 162 | tagged Wide90 .w : wide90_result = w; | ^ -%Error-UNSUPPORTED: t/t_tagged_case.v:164:10: Unsupported: tagged union - 164 | wt = tagged Byte8NonZeroLSB (8'hA5); +%Error-UNSUPPORTED: t/t_tagged_case.v:170:10: Unsupported: tagged union + 170 | wt = tagged Byte8NonZeroLSB (8'hA5); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:166:5: Unsupported: case matches (for tagged union) - 166 | case (wt) matches +%Error-UNSUPPORTED: t/t_tagged_case.v:172:5: Unsupported: case matches (for tagged union) + 172 | case (wt) matches | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:167:7: Unsupported: tagged pattern - 167 | tagged Byte8NonZeroLSB .b : result = b; +%Error-UNSUPPORTED: t/t_tagged_case.v:173:7: Unsupported: tagged pattern + 173 | tagged Byte8NonZeroLSB .b : result = b; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:167:30: Unsupported: pattern variable - 167 | tagged Byte8NonZeroLSB .b : result = b; +%Error-UNSUPPORTED: t/t_tagged_case.v:173:30: Unsupported: pattern variable + 173 | tagged Byte8NonZeroLSB .b : result = b; | ^ -%Error-UNSUPPORTED: t/t_tagged_case.v:173:10: Unsupported: tagged union - 173 | wt = tagged Word32LittleEndian (32'hDEADBEEF); +%Error-UNSUPPORTED: t/t_tagged_case.v:179:10: Unsupported: tagged union + 179 | wt = tagged Word32LittleEndian (32'hDEADBEEF); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:175:5: Unsupported: case matches (for tagged union) - 175 | case (wt) matches +%Error-UNSUPPORTED: t/t_tagged_case.v:181:5: Unsupported: case matches (for tagged union) + 181 | case (wt) matches | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:176:7: Unsupported: tagged pattern - 176 | tagged Word32LittleEndian .w : result = w; +%Error-UNSUPPORTED: t/t_tagged_case.v:182:7: Unsupported: tagged pattern + 182 | tagged Word32LittleEndian .w : result = w; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:176:33: Unsupported: pattern variable - 176 | tagged Word32LittleEndian .w : result = w; +%Error-UNSUPPORTED: t/t_tagged_case.v:182:33: Unsupported: pattern variable + 182 | tagged Word32LittleEndian .w : result = w; | ^ -%Error-UNSUPPORTED: t/t_tagged_case.v:182:10: Unsupported: tagged union - 182 | at = tagged Arr '{10, 20, 30, 40}; +%Error-UNSUPPORTED: t/t_tagged_case.v:188:10: Unsupported: tagged union + 188 | at = tagged Arr '{10, 20, 30, 40}; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:184:5: Unsupported: case matches (for tagged union) - 184 | case (at) matches +%Error-UNSUPPORTED: t/t_tagged_case.v:190:5: Unsupported: case matches (for tagged union) + 190 | case (at) matches | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:185:7: Unsupported: tagged union - 185 | tagged Invalid : result = -1; +%Error-UNSUPPORTED: t/t_tagged_case.v:191:7: Unsupported: tagged union + 191 | tagged Invalid : result = -1; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:186:7: Unsupported: tagged pattern - 186 | tagged Scalar .s : result = s; +%Error-UNSUPPORTED: t/t_tagged_case.v:192:7: Unsupported: tagged pattern + 192 | tagged Scalar .s : result = s; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:186:21: Unsupported: pattern variable - 186 | tagged Scalar .s : result = s; +%Error-UNSUPPORTED: t/t_tagged_case.v:192:21: Unsupported: pattern variable + 192 | tagged Scalar .s : result = s; | ^ -%Error-UNSUPPORTED: t/t_tagged_case.v:187:7: Unsupported: tagged pattern - 187 | tagged Arr .a : result = a[0] + a[1] + a[2] + a[3]; +%Error-UNSUPPORTED: t/t_tagged_case.v:193:7: Unsupported: tagged pattern + 193 | tagged Arr .a : result = a[0] + a[1] + a[2] + a[3]; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:187:18: Unsupported: pattern variable - 187 | tagged Arr .a : result = a[0] + a[1] + a[2] + a[3]; +%Error-UNSUPPORTED: t/t_tagged_case.v:193:18: Unsupported: pattern variable + 193 | tagged Arr .a : result = a[0] + a[1] + a[2] + a[3]; | ^ -%Error-UNSUPPORTED: t/t_tagged_case.v:192:13: Unsupported: tagged union - 192 | instr = tagged Jmp (tagged JmpC '{2'd1, 10'd256}); +%Error-UNSUPPORTED: t/t_tagged_case.v:198:13: Unsupported: tagged union + 198 | instr = tagged Jmp (tagged JmpC '{2'd1, 10'd256}); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:192:25: Unsupported: tagged union - 192 | instr = tagged Jmp (tagged JmpC '{2'd1, 10'd256}); +%Error-UNSUPPORTED: t/t_tagged_case.v:198:25: Unsupported: tagged union + 198 | instr = tagged Jmp (tagged JmpC '{2'd1, 10'd256}); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:194:5: Unsupported: case matches (for tagged union) - 194 | case (instr) matches +%Error-UNSUPPORTED: t/t_tagged_case.v:200:5: Unsupported: case matches (for tagged union) + 200 | case (instr) matches | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:195:7: Unsupported: tagged pattern - 195 | tagged Add .* : result = -1; +%Error-UNSUPPORTED: t/t_tagged_case.v:201:7: Unsupported: tagged pattern + 201 | tagged Add .* : result = -1; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_case.v:195:18: Unsupported: pattern wildcard - 195 | tagged Add .* : result = -1; +%Error-UNSUPPORTED: t/t_tagged_case.v:201:18: Unsupported: pattern wildcard + 201 | tagged Add .* : result = -1; | ^~ -%Error-UNSUPPORTED: t/t_tagged_case.v:196:7: Unsupported: tagged union - 196 | tagged Jmp (tagged JmpU .a) : result = a; +%Error-UNSUPPORTED: t/t_tagged_case.v:202:7: Unsupported: tagged union + 202 | tagged Jmp (tagged JmpU .a) : result = a; | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_tagged_case.v b/test_regress/t/t_tagged_case.v index a2ebf02fc..ebf466c59 100644 --- a/test_regress/t/t_tagged_case.v +++ b/test_regress/t/t_tagged_case.v @@ -63,7 +63,9 @@ module t; // Tagged union with chandle member typedef union tagged { void Invalid; +`ifndef QUESTA chandle Handle; +`endif } ChandleType; // Tagged union with class reference member @@ -148,7 +150,9 @@ module t; tagged Wide60 .w : wide60_result = w; default : wide60_result = 0; endcase +`ifndef QUESTA `checkh(wide60_result, 60'hFEDCBA987654321); +`endif // Test 4: Wide type case matching - 90-bit wt = tagged Wide90 (90'hDE_ADBEEFCA_FEBABE12_3456); @@ -158,7 +162,9 @@ module t; tagged Wide90 .w : wide90_result = w; default : wide90_result = 0; endcase +`ifndef QUESTA `checkh(wide90_result, 90'hDE_ADBEEFCA_FEBABE12_3456); +`endif // Test 5: Non-zero LSB case match wt = tagged Byte8NonZeroLSB (8'hA5); @@ -203,7 +209,9 @@ module t; result = 0; case (cht) matches tagged Invalid : result = 1; +`ifndef QUESTA tagged Handle .* : result = 2; // Wildcard - can't bind chandle +`endif endcase `checkh(result, 1); @@ -213,9 +221,9 @@ module t; result = 0; case (clt) matches tagged Invalid : result = -1; - tagged Obj .o : result = o.value; + tagged Obj : result = 2; endcase - `checkh(result, 42); + `checkh(result, 2); // Test 11: Real member case matching rt = tagged Invalid; diff --git a/test_regress/t/t_tagged_if.out b/test_regress/t/t_tagged_if.out index 31b7cc0bb..db7e619f6 100644 --- a/test_regress/t/t_tagged_if.out +++ b/test_regress/t/t_tagged_if.out @@ -1,14 +1,14 @@ -%Error-UNSUPPORTED: t/t_tagged_if.v:218:37: Unsupported: &&& expression - 218 | if (instr matches tagged Jmp .j &&& +%Error-UNSUPPORTED: t/t_tagged_if.v:226:37: Unsupported: &&& expression + 226 | if (instr matches tagged Jmp .j &&& | ^~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_tagged_if.v:228:35: Unsupported: &&& expression - 228 | if (v matches tagged Valid .n &&& (n > 50)) +%Error-UNSUPPORTED: t/t_tagged_if.v:236:35: Unsupported: &&& expression + 236 | if (v matches tagged Valid .n &&& (n > 50)) | ^~~ -%Error-UNSUPPORTED: t/t_tagged_if.v:237:35: Unsupported: &&& expression - 237 | if (v matches tagged Valid .n &&& (n > 50)) +%Error-UNSUPPORTED: t/t_tagged_if.v:245:35: Unsupported: &&& expression + 245 | if (v matches tagged Valid .n &&& (n > 50)) | ^~~ -%Error-UNSUPPORTED: t/t_tagged_if.v:266:37: Unsupported: &&& expression - 266 | if (instr matches tagged Jmp .j &&& +%Error-UNSUPPORTED: t/t_tagged_if.v:274:37: Unsupported: &&& expression + 274 | if (instr matches tagged Jmp .j &&& | ^~~ %Error: Exiting due to diff --git a/test_regress/t/t_tagged_if.v b/test_regress/t/t_tagged_if.v index 46be66135..269219f9d 100644 --- a/test_regress/t/t_tagged_if.v +++ b/test_regress/t/t_tagged_if.v @@ -61,10 +61,12 @@ module t; } Instr; // Tagged union with chandle member +`ifndef QUESTA typedef union tagged { void Invalid; chandle Handle; } ChandleType; +`endif // Tagged union with class reference member typedef union tagged { @@ -108,7 +110,9 @@ module t; WideType wt; ArrayType at; Instr instr; +`ifndef QUESTA ChandleType cht; +`endif ClassType clt; TestClass obj; RealType rt; @@ -156,7 +160,9 @@ module t; wide60_result = w; else wide60_result = 0; +`ifndef QUESTA `checkh(wide60_result, 60'hFEDCBA987654321); +`endif // Test 5: Wide type if matching - 90-bit wt = tagged Wide90 (90'hDE_ADBEEFCA_FEBABE12_3456); @@ -165,7 +171,9 @@ module t; wide90_result = w; else wide90_result = 0; +`ifndef QUESTA `checkh(wide90_result, 90'hDE_ADBEEFCA_FEBABE12_3456); +`endif // Test 6: Non-zero LSB if match wt = tagged Byte8NonZeroLSB (8'hA5); @@ -291,6 +299,7 @@ module t; result = 2; `checkh(result, 1); +`ifndef QUESTA // Test 19: Chandle member if matching cht = tagged Invalid; result = 0; @@ -307,6 +316,7 @@ module t; else result = 2; `checkh(result, 1); +`endif // Test 20: Class reference member if matching obj = new(42); @@ -320,8 +330,8 @@ module t; clt = tagged Obj (obj); result = 0; - if (clt matches tagged Obj .o) - result = o.value; + if (clt matches tagged Obj) + result = 42; else result = -1; `checkh(result, 42); diff --git a/test_regress/t/t_tagged_union.out b/test_regress/t/t_tagged_union.out index ab08889dc..d31a3fdfa 100644 --- a/test_regress/t/t_tagged_union.out +++ b/test_regress/t/t_tagged_union.out @@ -8,145 +8,145 @@ %Error-UNSUPPORTED: t/t_tagged_union.v:57:5: Unsupported: void (for tagged unions) 57 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:79:5: Unsupported: void (for tagged unions) - 79 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_union.v:80:5: Unsupported: void (for tagged unions) + 80 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:85:5: Unsupported: void (for tagged unions) - 85 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_union.v:87:5: Unsupported: void (for tagged unions) + 87 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:94:5: Unsupported: void (for tagged unions) - 94 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_union.v:96:5: Unsupported: void (for tagged unions) + 96 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:101:5: Unsupported: void (for tagged unions) - 101 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_union.v:103:5: Unsupported: void (for tagged unions) + 103 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:107:5: Unsupported: void (for tagged unions) - 107 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_union.v:109:5: Unsupported: void (for tagged unions) + 109 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:116:5: Unsupported: void (for tagged unions) - 116 | void Invalid; +%Error-UNSUPPORTED: t/t_tagged_union.v:118:5: Unsupported: void (for tagged unions) + 118 | void Invalid; | ^~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:137:11: Unsupported: tagged union - 137 | vi1 = tagged Invalid; - | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:138:11: Unsupported: tagged union - 138 | vi2 = tagged Invalid; - | ^~~~~~ %Error-UNSUPPORTED: t/t_tagged_union.v:141:11: Unsupported: tagged union - 141 | vi1 = tagged Valid (42); + 141 | vi1 = tagged Invalid; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:144:11: Unsupported: tagged union - 144 | vi2 = tagged Valid (23 + 34); +%Error-UNSUPPORTED: t/t_tagged_union.v:142:11: Unsupported: tagged union + 142 | vi2 = tagged Invalid; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:148:10: Unsupported: tagged union - 148 | mt = tagged Invalid; +%Error-UNSUPPORTED: t/t_tagged_union.v:145:11: Unsupported: tagged union + 145 | vi1 = tagged Valid (42); + | ^~~~~~ +%Error-UNSUPPORTED: t/t_tagged_union.v:148:11: Unsupported: tagged union + 148 | vi2 = tagged Valid (23 + 34); + | ^~~~~~ +%Error-UNSUPPORTED: t/t_tagged_union.v:152:10: Unsupported: tagged union + 152 | mt = tagged Invalid; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:150:10: Unsupported: tagged union - 150 | mt = tagged IntVal (32'h12345678); +%Error-UNSUPPORTED: t/t_tagged_union.v:154:10: Unsupported: tagged union + 154 | mt = tagged IntVal (32'h12345678); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:153:10: Unsupported: tagged union - 153 | mt = tagged ShortVal (16'hABCD); +%Error-UNSUPPORTED: t/t_tagged_union.v:157:10: Unsupported: tagged union + 157 | mt = tagged ShortVal (16'hABCD); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:156:10: Unsupported: tagged union - 156 | mt = tagged ByteVal (8'h5A); - | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:159:10: Unsupported: tagged union - 159 | mt = tagged BitVal (1'b1); +%Error-UNSUPPORTED: t/t_tagged_union.v:160:10: Unsupported: tagged union + 160 | mt = tagged ByteVal (8'h5A); | ^~~~~~ %Error-UNSUPPORTED: t/t_tagged_union.v:163:10: Unsupported: tagged union - 163 | mt = tagged Byte8NonZeroLSB (8'hA5); + 163 | mt = tagged BitVal (1'b1); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:166:10: Unsupported: tagged union - 166 | mt = tagged Word16NonZeroLSB (16'h1234); +%Error-UNSUPPORTED: t/t_tagged_union.v:167:10: Unsupported: tagged union + 167 | mt = tagged Byte8NonZeroLSB (8'hA5); | ^~~~~~ %Error-UNSUPPORTED: t/t_tagged_union.v:170:10: Unsupported: tagged union - 170 | mt = tagged Word32LittleEndian (32'hDEADBEEF); + 170 | mt = tagged Word16NonZeroLSB (16'h1234); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:173:10: Unsupported: tagged union - 173 | mt = tagged Word16LittleEndian (16'hCAFE); +%Error-UNSUPPORTED: t/t_tagged_union.v:174:10: Unsupported: tagged union + 174 | mt = tagged Word32LittleEndian (32'hDEADBEEF); | ^~~~~~ %Error-UNSUPPORTED: t/t_tagged_union.v:177:10: Unsupported: tagged union - 177 | mt = tagged Wide60 (60'hFEDCBA987654321); + 177 | mt = tagged Word16LittleEndian (16'hCAFE); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:180:10: Unsupported: tagged union - 180 | mt = tagged Wide60NonZeroLSB (60'h123456789ABCDEF); +%Error-UNSUPPORTED: t/t_tagged_union.v:181:10: Unsupported: tagged union + 181 | mt = tagged Wide60 (60'hFEDCBA987654321); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:183:10: Unsupported: tagged union - 183 | mt = tagged Wide60LittleEndian (60'hABCDEF012345678); +%Error-UNSUPPORTED: t/t_tagged_union.v:184:10: Unsupported: tagged union + 184 | mt = tagged Wide60NonZeroLSB (60'h123456789ABCDEF); | ^~~~~~ %Error-UNSUPPORTED: t/t_tagged_union.v:187:10: Unsupported: tagged union - 187 | mt = tagged Wide90 (90'hFF_FFFFFFFF_FFFFFFFF_FFFF); + 187 | mt = tagged Wide60LittleEndian (60'hABCDEF012345678); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:190:10: Unsupported: tagged union - 190 | mt = tagged Wide90NonZeroLSB (90'hDE_ADBEEFCA_FEBABE12_3456); +%Error-UNSUPPORTED: t/t_tagged_union.v:191:10: Unsupported: tagged union + 191 | mt = tagged Wide90 (90'hFF_FFFFFFFF_FFFFFFFF_FFFF); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:193:10: Unsupported: tagged union - 193 | mt = tagged Wide90LittleEndian (90'h11_11111122_22222233_3333); +%Error-UNSUPPORTED: t/t_tagged_union.v:194:10: Unsupported: tagged union + 194 | mt = tagged Wide90NonZeroLSB (90'hDE_ADBEEFCA_FEBABE12_3456); | ^~~~~~ %Error-UNSUPPORTED: t/t_tagged_union.v:197:10: Unsupported: tagged union - 197 | at = tagged Invalid; + 197 | mt = tagged Wide90LittleEndian (90'h11_11111122_22222233_3333); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:199:10: Unsupported: tagged union - 199 | at = tagged Scalar (999); +%Error-UNSUPPORTED: t/t_tagged_union.v:201:10: Unsupported: tagged union + 201 | at = tagged Invalid; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:202:10: Unsupported: tagged union - 202 | at = tagged UnpackedArr '{100, 200, 300, 400}; +%Error-UNSUPPORTED: t/t_tagged_union.v:203:10: Unsupported: tagged union + 203 | at = tagged Scalar (999); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:208:10: Unsupported: tagged union - 208 | at = tagged UnpackedArr2D '{'{32'hA, 32'hB, 32'hC}, '{32'hD, 32'hE, 32'hF}}; +%Error-UNSUPPORTED: t/t_tagged_union.v:206:10: Unsupported: tagged union + 206 | at = tagged UnpackedArr '{100, 200, 300, 400}; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:217:13: Unsupported: tagged union - 217 | instr = tagged Add '{5'd1, 5'd2, 5'd3}; +%Error-UNSUPPORTED: t/t_tagged_union.v:212:10: Unsupported: tagged union + 212 | at = tagged UnpackedArr2D '{'{32'hA, 32'hB, 32'hC}, '{32'hD, 32'hE, 32'hF}}; + | ^~~~~~ +%Error-UNSUPPORTED: t/t_tagged_union.v:221:13: Unsupported: tagged union + 221 | instr = tagged Add '{5'd1, 5'd2, 5'd3}; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:223:13: Unsupported: tagged union - 223 | instr = tagged Add '{reg2:5'd10, regd:5'd20, reg1:5'd5}; +%Error-UNSUPPORTED: t/t_tagged_union.v:227:13: Unsupported: tagged union + 227 | instr = tagged Add '{reg2:5'd10, regd:5'd20, reg1:5'd5}; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:229:13: Unsupported: tagged union - 229 | instr = tagged Jmp (tagged JmpU 10'd512); - | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:229:25: Unsupported: tagged union - 229 | instr = tagged Jmp (tagged JmpU 10'd512); - | ^~~~~~ %Error-UNSUPPORTED: t/t_tagged_union.v:233:13: Unsupported: tagged union - 233 | instr = tagged Jmp (tagged JmpC '{2'd1, 10'd256}); + 233 | instr = tagged Jmp (tagged JmpU 10'd512); | ^~~~~~ %Error-UNSUPPORTED: t/t_tagged_union.v:233:25: Unsupported: tagged union - 233 | instr = tagged Jmp (tagged JmpC '{2'd1, 10'd256}); + 233 | instr = tagged Jmp (tagged JmpU 10'd512); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:238:13: Unsupported: tagged union - 238 | instr = tagged Jmp (tagged JmpC '{cc:2'd3, addr:10'd100}); +%Error-UNSUPPORTED: t/t_tagged_union.v:237:13: Unsupported: tagged union + 237 | instr = tagged Jmp (tagged JmpC '{2'd1, 10'd256}); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:238:25: Unsupported: tagged union - 238 | instr = tagged Jmp (tagged JmpC '{cc:2'd3, addr:10'd100}); +%Error-UNSUPPORTED: t/t_tagged_union.v:237:25: Unsupported: tagged union + 237 | instr = tagged Jmp (tagged JmpC '{2'd1, 10'd256}); + | ^~~~~~ +%Error-UNSUPPORTED: t/t_tagged_union.v:242:13: Unsupported: tagged union + 242 | instr = tagged Jmp (tagged JmpC '{cc:2'd3, addr:10'd100}); + | ^~~~~~ +%Error-UNSUPPORTED: t/t_tagged_union.v:242:25: Unsupported: tagged union + 242 | instr = tagged Jmp (tagged JmpC '{cc:2'd3, addr:10'd100}); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:243:11: Unsupported: tagged union - 243 | cht = tagged Invalid; - | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:244:11: Unsupported: tagged union - 244 | cht = tagged Handle (null); - | ^~~~~~ %Error-UNSUPPORTED: t/t_tagged_union.v:248:11: Unsupported: tagged union - 248 | clt = tagged Invalid; + 248 | cht = tagged Invalid; | ^~~~~~ %Error-UNSUPPORTED: t/t_tagged_union.v:249:11: Unsupported: tagged union - 249 | clt = tagged Obj (obj); + 249 | cht = tagged Handle (null); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:253:10: Unsupported: tagged union - 253 | rt = tagged Invalid; +%Error-UNSUPPORTED: t/t_tagged_union.v:254:11: Unsupported: tagged union + 254 | clt = tagged Invalid; + | ^~~~~~ +%Error-UNSUPPORTED: t/t_tagged_union.v:255:11: Unsupported: tagged union + 255 | clt = tagged Obj (obj); + | ^~~~~~ +%Error-UNSUPPORTED: t/t_tagged_union.v:259:10: Unsupported: tagged union + 259 | rt = tagged Invalid; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:254:10: Unsupported: tagged union - 254 | rt = tagged RealVal (3.14159); +%Error-UNSUPPORTED: t/t_tagged_union.v:260:10: Unsupported: tagged union + 260 | rt = tagged RealVal (3.14159); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:258:10: Unsupported: tagged union - 258 | rt = tagged ShortRealVal (2.5); +%Error-UNSUPPORTED: t/t_tagged_union.v:264:10: Unsupported: tagged union + 264 | rt = tagged ShortRealVal (2.5); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:262:10: Unsupported: tagged union - 262 | st = tagged Invalid; +%Error-UNSUPPORTED: t/t_tagged_union.v:268:10: Unsupported: tagged union + 268 | st = tagged Invalid; | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:263:10: Unsupported: tagged union - 263 | st = tagged StrVal ("hello"); +%Error-UNSUPPORTED: t/t_tagged_union.v:269:10: Unsupported: tagged union + 269 | st = tagged StrVal ("hello"); | ^~~~~~ -%Error-UNSUPPORTED: t/t_tagged_union.v:267:10: Unsupported: tagged union - 267 | et = tagged Invalid; +%Error-UNSUPPORTED: t/t_tagged_union.v:273:10: Unsupported: tagged union + 273 | et = tagged Invalid; | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_tagged_union.v b/test_regress/t/t_tagged_union.v index 59642f503..4ce7fe866 100644 --- a/test_regress/t/t_tagged_union.v +++ b/test_regress/t/t_tagged_union.v @@ -75,10 +75,12 @@ module t; } Instr; // Tagged union with chandle member +`ifndef QUESTA typedef union tagged { void Invalid; chandle Handle; } ChandleType; +`endif // Tagged union with class reference member typedef union tagged { @@ -122,7 +124,9 @@ module t; MultiType mt; ArrayType at; Instr instr; +`ifndef QUESTA ChandleType cht; +`endif ClassType clt; TestClass obj; RealType rt; @@ -239,9 +243,11 @@ module t; `checkh(instr.Jmp.JmpC.cc, 2'd3); `checkh(instr.Jmp.JmpC.addr, 10'd100); +`ifndef QUESTA // Test 13: Chandle member cht = tagged Invalid; cht = tagged Handle (null); +`endif // Test 14: Class reference member obj = new(42); diff --git a/test_regress/t/t_timing_debug1.py b/test_regress/t/t_timing_debug1.py index 94534429f..a8648a97a 100755 --- a/test_regress/t/t_timing_debug1.py +++ b/test_regress/t/t_timing_debug1.py @@ -13,7 +13,7 @@ test.scenarios('vlt_all') test.top_filename = "t/t_timing_sched.v" test.compile( - verilator_flags2=["--binary", "--timing", "--inline-cfuncs", "0", "-CFLAGS", "-DVL_DEBUG"]) + verilator_flags2=["--binary", "--timing", "-fno-inline-cfuncs", "-CFLAGS", "-DVL_DEBUG"]) test.execute(all_run_flags=["+verilator+debug"]) diff --git a/test_regress/t/t_timing_debug2.py b/test_regress/t/t_timing_debug2.py index 18a53b76f..e5bb01e3c 100755 --- a/test_regress/t/t_timing_debug2.py +++ b/test_regress/t/t_timing_debug2.py @@ -12,8 +12,8 @@ import vltest_bootstrap test.scenarios('vlt_all') test.top_filename = "t/t_timing_class.v" -# Disable --inline-cfuncs so debug traces show all function entries -test.compile(verilator_flags2=["--exe --main --timing --inline-cfuncs 0"]) +# Disable CFunc inlining so debug traces show all function entries +test.compile(verilator_flags2=["--exe --main --timing -fno-inline-cfuncs"]) test.execute(all_run_flags=["+verilator+debug"]) diff --git a/test_regress/t/t_timing_eval_act.out b/test_regress/t/t_timing_eval_act.out index 1d71dde4f..46417dac7 100644 --- a/test_regress/t/t_timing_eval_act.out +++ b/test_regress/t/t_timing_eval_act.out @@ -4,6 +4,7 @@ -V{t#,#}+ Vt_timing_eval_act___024root___eval_debug_assertions -V{t#,#}+ Initial -V{t#,#}+ Vt_timing_eval_act___024root___eval_static +-V{t#,#}+ Vt_timing_eval_act___024root___eval_static__TOP -V{t#,#}+ Vt_timing_eval_act___024root___timing_ready -V{t#,#}+ Vt_timing_eval_act___024root___eval_initial -V{t#,#}+ Vt_timing_eval_act___024root___eval_initial__TOP__Vtiming__0 diff --git a/test_regress/t/t_timing_eval_act.py b/test_regress/t/t_timing_eval_act.py index 787745ab3..46dae2791 100755 --- a/test_regress/t/t_timing_eval_act.py +++ b/test_regress/t/t_timing_eval_act.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('vlt') -test.compile(verilator_flags2=["--binary", "--runtime-debug"]) +test.compile(verilator_flags2=["--binary", "--runtime-debug", "-fno-inline-cfuncs"]) test.file_grep( test.obj_dir + "/" + test.vm_prefix + "___024root__0.cpp", r'void\s+' + test.vm_prefix + diff --git a/test_regress/t/t_unique0_case.py b/test_regress/t/t_unique0_case.py new file mode 100755 index 000000000..3626b338d --- /dev/null +++ b/test_regress/t/t_unique0_case.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios("simulator") + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r"Assertions, lifted impure case expressions\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_unique0_case.v b/test_regress/t/t_unique0_case.v new file mode 100644 index 000000000..ed3f92535 --- /dev/null +++ b/test_regress/t/t_unique0_case.v @@ -0,0 +1,38 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + int x; + int y; + int z; + initial begin + y = 0; + x = 0; + z = 0; + unique0 case (increment_x()) + 1: begin + y = 1; + end + default: begin + z = 1; + end + endcase + `checkh(x, 32'h1); + `checkh(y, 32'h1); + `checkh(z, 32'h0); + $finish; + end + + function automatic integer increment_x(); + x = x + 1; + return x; + endfunction +endmodule diff --git a/test_regress/t/t_unopt_combo_isolate.py b/test_regress/t/t_unopt_combo_isolate.py index f7b4618d3..3ffd80545 100755 --- a/test_regress/t/t_unopt_combo_isolate.py +++ b/test_regress/t/t_unopt_combo_isolate.py @@ -9,37 +9,14 @@ import vltest_bootstrap -test.scenarios('simulator') +# Note: This test is historical for the isolate_assignments attribute, which +# was deprecated and has no effect today. This test ensures it still parses +# in SystemVerilog and Verilator control files for backward compatibility. + +test.scenarios('vlt') test.top_filename = "t/t_unopt_combo.v" -out_filename = test.obj_dir + "/V" + test.name + ".tree.json" - -test.compile(verilator_flags2=[ - "--no-json-edit-nums", "+define+ISOLATE", "--stats", "-fno-dfg", "-fno-lift-expr" -]) - -if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+4') - test.file_grep( - out_filename, - r'{"type":"VAR","name":"t.b",.*"loc":"\w,23:[^"]*",.*"origName":"b",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__Vfuncout",.*"loc":"\w,99:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__t_crc",.*"loc":"\w,100:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_crc",.*"loc":"\w,112:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_c",.*"loc":"\w,113:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_c",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) +test.compile(verilator_flags2=["+define+ISOLATE"]) test.execute() diff --git a/test_regress/t/t_unopt_combo_isolate_vlt.py b/test_regress/t/t_unopt_combo_isolate_vlt.py index 35e0cc8d2..1a041dac0 100755 --- a/test_regress/t/t_unopt_combo_isolate_vlt.py +++ b/test_regress/t/t_unopt_combo_isolate_vlt.py @@ -9,38 +9,14 @@ import vltest_bootstrap -test.scenarios('simulator') +# Note: This test is historical for the isolate_assignments attribute, which +# was deprecated and has no effect today. This test ensures it still parses +# in SystemVerilog and Verilator control files for backward compatibility. + +test.scenarios('vlt') test.top_filename = "t/t_unopt_combo.v" -out_filename = test.obj_dir + "/V" + test.name + ".tree.json" - -test.compile(verilator_flags2=[ - "--no-json-edit-nums", "--stats", test.t_dir + - "/t_unopt_combo_isolate.vlt", "-fno-dfg", "-fno-lift-expr" -]) - -if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+4') - test.file_grep( - out_filename, - r'{"type":"VAR","name":"t.b",.*"loc":"\w,23:[^"]*",.*"origName":"b",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__Vfuncout",.*"loc":"\w,104:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__t_crc",.*"loc":"\w,105:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_crc",.*"loc":"\w,115:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_c",.*"loc":"\w,116:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_c",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) +test.compile(verilator_flags2=[test.t_dir + "/t_unopt_combo_isolate.vlt"]) test.execute() diff --git a/test_regress/t/t_unoptflat_simple_2_bad.out b/test_regress/t/t_unoptflat_simple_2_bad.out index 8588ac55c..4c8793b04 100644 --- a/test_regress/t/t_unoptflat_simple_2_bad.out +++ b/test_regress/t/t_unoptflat_simple_2_bad.out @@ -10,5 +10,5 @@ t/t_unoptflat_simple_2.v:16:14: t.x, width 3, circular fanout 2, can split_var ... Candidates with the highest fanout: t/t_unoptflat_simple_2.v:16:14: t.x, width 3, circular fanout 2, can split_var - ... Suggest add /*verilator split_var*/ or /*verilator isolate_assignments*/ to appropriate variables above. + ... Suggest add /*verilator split_var*/ to appropriate variables above. %Error: Exiting due to diff --git a/test_regress/t/t_unoptflat_simple_3_bad.out b/test_regress/t/t_unoptflat_simple_3_bad.out index f5ef439ae..7e31eb84d 100644 --- a/test_regress/t/t_unoptflat_simple_3_bad.out +++ b/test_regress/t/t_unoptflat_simple_3_bad.out @@ -4,8 +4,8 @@ ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message. t/t_unoptflat_simple_3.v:16:14: Example path: t.x - t/t_unoptflat_simple_3.v:58:18: Example path: ASSIGNW - t/t_unoptflat_simple_3.v:56:21: Example path: t.__Vcellout__test1i__xvecout - t/t_unoptflat_simple_3.v:21:8: Example path: ASSIGNW + t/t_unoptflat_simple_3.v:76:18: Example path: ASSIGNW + t/t_unoptflat_simple_3.v:74:21: Example path: t.__Vcellout__test2i__xvecout + t/t_unoptflat_simple_3.v:27:8: Example path: ASSIGNW t/t_unoptflat_simple_3.v:16:14: Example path: t.x %Error: Exiting due to diff --git a/test_regress/t/t_verilated_debug.out b/test_regress/t/t_verilated_debug.out index 1d7867bab..7d98054ea 100644 --- a/test_regress/t/t_verilated_debug.out +++ b/test_regress/t/t_verilated_debug.out @@ -37,6 +37,7 @@ internalsDump: -V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba -V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act -V{t#,#}+ Vt_verilated_debug___024root___eval_nba +-V{t#,#}+ Vt_verilated_debug___024root___nba_sequent__TOP__0 *-* All Finished *-* -V{t#,#}+ Vt_verilated_debug___024root___trigger_clear__act -V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act diff --git a/test_regress/t/t_verilated_debug.py b/test_regress/t/t_verilated_debug.py index dbc82d1c1..e880f1866 100755 --- a/test_regress/t/t_verilated_debug.py +++ b/test_regress/t/t_verilated_debug.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('vlt_all') test.verilated_debug = True -test.compile(verilator_flags2=[]) +test.compile(verilator_flags2=['-fno-inline-cfuncs']) test.execute() diff --git a/test_regress/t/t_virtual_interface_method_sched.py b/test_regress/t/t_virtual_interface_method_sched.py new file mode 100755 index 000000000..6ac2815da --- /dev/null +++ b/test_regress/t/t_virtual_interface_method_sched.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_virtual_interface_method_sched.v b/test_regress/t/t_virtual_interface_method_sched.v new file mode 100644 index 000000000..b6586f268 --- /dev/null +++ b/test_regress/t/t_virtual_interface_method_sched.v @@ -0,0 +1,85 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +class msg; + string context_name; + + function void set_context(string name); + context_name = name; + endfunction +endclass + +package helper_pkg; + int package_counter; + + function void bump(); + package_counter++; + endfunction +endpackage + +interface intf ( + output logic a, + output logic b +); + msg m; + event e; + + task go(); + helper_pkg::package_counter++; + go_helper(); + go_helper(); + endtask + + task go_helper(); + // verilator no_inline_task + m.set_context("go_helper"); + helper_pkg::bump(); + ->e; + a <= 1; + endtask +endinterface + +class driver; + virtual intf vif; + + function new(virtual intf vif); + this.vif = vif; + endfunction + + task go(); + vif.go(); + endtask +endclass + +module t; + wire a; + wire b; + virtual intf vif; + + intf i ( + a, + b + ); + + initial begin + driver d; + vif = t.i; + t.i.m = new; + d = new(t.i); + d.go(); + end + + always @(posedge a) begin + vif.b <= 1; + end + + always @(*) begin + if (a && b) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_vlcov_covergroup.annotate.out b/test_regress/t/t_vlcov_covergroup.annotate.out index 436973cb7..16b86a66e 100644 --- a/test_regress/t/t_vlcov_covergroup.annotate.out +++ b/test_regress/t/t_vlcov_covergroup.annotate.out @@ -14,9 +14,9 @@ module t; %000001 logic [1:0] addr; --000001 point: type=toggle comment=addr[0]:0->1 hier=top.t +-000000 point: type=toggle comment=addr[0]:0->1 hier=top.t -000000 point: type=toggle comment=addr[0]:1->0 hier=top.t --000000 point: type=toggle comment=addr[1]:0->1 hier=top.t +-000001 point: type=toggle comment=addr[1]:0->1 hier=top.t -000000 point: type=toggle comment=addr[1]:1->0 hier=top.t %000001 logic cmd; -000001 point: type=toggle comment=cmd:0->1 hier=top.t @@ -28,6 +28,14 @@ -000001 point: type=toggle comment=parity:0->1 hier=top.t -000000 point: type=toggle comment=parity:1->0 hier=top.t + typedef struct packed {logic m_p; logic h_mode;} cfg_t; +%000001 cfg_t s_cfg = '0; +-000001 point: type=line comment=block hier=top.t +-000000 point: type=toggle comment=s_cfg.h_mode:0->1 hier=top.t +-000000 point: type=toggle comment=s_cfg.h_mode:1->0 hier=top.t +-000000 point: type=toggle comment=s_cfg.m_p:0->1 hier=top.t +-000000 point: type=toggle comment=s_cfg.m_p:1->0 hier=top.t + // 2-way cross covergroup cg2; %000002 cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} @@ -257,6 +265,12 @@ // cross: [addr1, write] option.per_instance = 1; // unsupported for cross - expect COVERIGN warning } + // Non-standard hierarchical reference as a cross item (an implicit coverpoint): + // accepted with NONSTD, but implicit coverpoints are unsupported so the whole + // cross is dropped (COVERIGN, suppressed here) - it contributes no bins. + /* verilator lint_off NONSTD */ + cross_hier: cross cp_addr, s_cfg.m_p; + /* verilator lint_on NONSTD */ endgroup // Covergroup with an unnamed cross - the cross is reported under the default name "cross" @@ -268,13 +282,57 @@ -000001 point: type=covergroup comment= hier=cg_unnamed_cross.cp_c.read -000001 point: type=covergroup comment= hier=cg_unnamed_cross.cp_c.write %000001 cross cp_a, cp_c; // no label: reported under the default cross name --000001 point: type=covergroup comment= hier=cg_unnamed_cross.__cross7.a0_x_read +-000001 point: type=covergroup comment= hier=cg_unnamed_cross.__cross8.a0_x_read // cross: [a0, read] --000000 point: type=covergroup comment= hier=cg_unnamed_cross.__cross7.a0_x_write +-000000 point: type=covergroup comment= hier=cg_unnamed_cross.__cross8.a0_x_write // cross: [a0, write] --000000 point: type=covergroup comment= hier=cg_unnamed_cross.__cross7.a1_x_read +-000000 point: type=covergroup comment= hier=cg_unnamed_cross.__cross8.a1_x_read // cross: [a1, read] --000001 point: type=covergroup comment= hier=cg_unnamed_cross.__cross7.a1_x_write +-000001 point: type=covergroup comment= hier=cg_unnamed_cross.__cross8.a1_x_write + // cross: [a1, write] + endgroup + + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted + // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + covergroup cg_mixed; +%000002 cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} +-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr0 +-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr1 +%000002 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.write +%000002 cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed +-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.normal +-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.debug +%000001 ab: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_read + // cross: [addr0, read] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_write + // cross: [addr0, write] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_read + // cross: [addr1, read] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_write + // cross: [addr1, write] + endgroup + + // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the + // legacy default-bin codegen path that converted coverpoints bypass. + covergroup cg_def_cross; +%000001 cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.a0 +-000000 point: type=covergroup comment= hier=cg_def_cross.cp_a.a1 +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.ad +%000001 cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.read +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.write +%000001 axc: cross cp_a, cp_c; +-000001 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_read + // cross: [a0, read] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_write + // cross: [a0, write] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_read + // cross: [a1, read] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_write // cross: [a1, write] endgroup @@ -298,6 +356,10 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_unnamed_cross cg_unnamed_cross_inst = new; -000001 point: type=line comment=block hier=top.t +%000001 cg_mixed cg_mixed_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_def_cross cg_def_cross_inst = new; +-000001 point: type=line comment=block hier=top.t %000001 initial begin -000001 point: type=line comment=block hier=top.t @@ -641,6 +703,40 @@ -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t + // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) +%000001 addr = 0; cmd = 0; mode = 0; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t +%000001 addr = 0; cmd = 1; mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 0; mode = 0; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 1; mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) +%000001 mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write +-000001 point: type=line comment=block hier=top.t + %000001 $write("*-* All Finished *-*\n"); -000001 point: type=line comment=block hier=top.t %000001 $finish; diff --git a/test_regress/t/t_vlcov_data_e.dat b/test_regress/t/t_vlcov_data_e.dat index 32c705c59..114d03f03 100644 --- a/test_regress/t/t_vlcov_data_e.dat +++ b/test_regress/t/t_vlcov_data_e.dat @@ -224,3 +224,4 @@ C 'ft/t_cover_line.vl83n10tlinepagev_line/toelsifS83-85htop.t' 1 C 'ft/t_cover_line.vl87n15tlinepagev_line/toelsifS87-89htop.t' 1 C 'ft/t_cover_line.vl91n15tlinepagev_line/toifS91-93htop.t' 1 C 'ft/t_cover_line.vl91n16tlinepagev_line/toelseS96-97htop.t' 7 +C 'ft/t_cover_line.vl91n16tlinepagev_line/totest,testS91-91htop.t' 5 diff --git a/test_regress/t/t_vlcov_hier_report.out b/test_regress/t/t_vlcov_hier_report.out index bc70aa8a1..1c37b8e6a 100644 --- a/test_regress/t/t_vlcov_hier_report.out +++ b/test_regress/t/t_vlcov_hier_report.out @@ -1,6 +1,6 @@ $ verilator_coverage --report summary t/t_vlcov_data_e.dat Coverage Summary: - line : 88.6% ( 39/ 44) + line : 88.9% ( 40/ 45) toggle : 33.3% ( 35/105) branch : 78.1% ( 50/ 64) expr : 66.7% ( 8/ 12) diff --git a/test_regress/t/t_vlcov_opt_branch.info.out b/test_regress/t/t_vlcov_opt_branch.info.out index 45d5b4627..f7850b8c9 100644 --- a/test_regress/t/t_vlcov_opt_branch.info.out +++ b/test_regress/t/t_vlcov_opt_branch.info.out @@ -1,32 +1,32 @@ TN:verilator_coverage SF:t/t_cover_line.v DA:56,10 -BRDA:56,0,0,10 -BRDA:56,0,1,0 +BRDA:56,0,if,10 +BRDA:56,0,else,0 DA:57,10 DA:58,10 DA:60,9 -BRDA:60,0,0,1 -BRDA:60,0,1,9 +BRDA:60,0,if,1 +BRDA:60,0,else,9 DA:61,9 -BRDA:61,0,0,1 -BRDA:61,0,1,9 +BRDA:61,0,if,1 +BRDA:61,0,else,9 DA:62,1 DA:63,1 DA:66,9 -BRDA:66,0,0,1 -BRDA:66,0,1,9 +BRDA:66,0,if,1 +BRDA:66,0,else,9 DA:67,9 -BRDA:67,0,0,1 -BRDA:67,0,1,9 +BRDA:67,0,if,1 +BRDA:67,0,else,9 DA:69,9 DA:70,9 DA:73,9 -BRDA:73,0,0,1 -BRDA:73,0,1,9 +BRDA:73,0,if,1 +BRDA:73,0,else,9 DA:74,9 -BRDA:74,0,0,1 -BRDA:74,0,1,9 +BRDA:74,0,if,1 +BRDA:74,0,else,9 DA:75,1 DA:76,1 DA:79,9 @@ -34,93 +34,93 @@ DA:80,9 DA:105,10 DA:106,10 DA:120,7 -BRDA:120,0,0,1 -BRDA:120,0,1,7 +BRDA:120,0,if,1 +BRDA:120,0,else,7 DA:121,1 DA:122,1 DA:141,18 -BRDA:141,0,0,2 -BRDA:141,0,1,18 +BRDA:141,0,if,2 +BRDA:141,0,else,18 DA:142,2 DA:166,20 -BRDA:166,0,0,0 -BRDA:166,0,1,20 +BRDA:166,0,if,0 +BRDA:166,0,else,20 DA:168,0 DA:170,18 -BRDA:170,0,0,2 -BRDA:170,0,1,18 +BRDA:170,0,if,2 +BRDA:170,0,else,18 DA:172,2 DA:190,1 -BRDA:190,0,0,1 -BRDA:190,0,1,0 +BRDA:190,0,if,1 +BRDA:190,0,else,0 DA:191,1 DA:195,11 -BRDA:195,0,0,11 -BRDA:195,0,1,0 +BRDA:195,0,if,11 +BRDA:195,0,else,0 DA:196,11 DA:200,11 -BRDA:200,0,0,11 -BRDA:200,0,1,0 +BRDA:200,0,if,11 +BRDA:200,0,else,0 DA:201,11 DA:222,10 -BRDA:222,0,0,1 -BRDA:222,0,1,10 +BRDA:222,0,if,1 +BRDA:222,0,else,10 DA:223,1 DA:225,10 -BRDA:225,0,0,1 -BRDA:225,0,1,10 +BRDA:225,0,if,1 +BRDA:225,0,else,10 DA:226,1 DA:253,9 -BRDA:253,0,0,1 -BRDA:253,0,1,9 +BRDA:253,0,if,1 +BRDA:253,0,else,9 DA:255,1 DA:256,1 -BRDA:256,0,0,0 -BRDA:256,0,1,1 +BRDA:256,0,if,0 +BRDA:256,0,else,1 DA:288,0 -BRDA:288,0,0,0 -BRDA:288,0,1,0 +BRDA:288,0,if,0 +BRDA:288,0,else,0 DA:289,0 DA:291,0 DA:292,0 DA:327,31 -BRDA:327,0,0,0 -BRDA:327,0,1,31 +BRDA:327,0,cond_then,0 +BRDA:327,0,cond_else,31 DA:328,28 -BRDA:328,0,0,3 -BRDA:328,0,1,28 +BRDA:328,0,cond_then,3 +BRDA:328,0,cond_else,28 DA:329,21 -BRDA:329,0,0,21 -BRDA:329,0,1,0 +BRDA:329,0,cond_then,21 +BRDA:329,0,cond_else,0 DA:331,7 -BRDA:331,0,0,3 -BRDA:331,0,1,7 +BRDA:331,0,cond_then,3 +BRDA:331,0,cond_else,7 DA:332,10 -BRDA:332,0,0,0 -BRDA:332,0,1,10 +BRDA:332,0,cond_then,0 +BRDA:332,0,cond_else,10 DA:334,19 -BRDA:334,0,0,12 -BRDA:334,0,1,19 -BRDA:334,0,2,7 -BRDA:334,0,3,5 +BRDA:334,0,cond_then,12 +BRDA:334,0,cond_else,19 +BRDA:334,0,cond_then,7 +BRDA:334,0,cond_else,5 DA:337,11 -BRDA:337,0,0,11 -BRDA:337,0,1,0 +BRDA:337,0,cond_then,11 +BRDA:337,0,cond_else,0 DA:343,11 -BRDA:343,0,0,10 -BRDA:343,0,1,11 +BRDA:343,0,cond_then,10 +BRDA:343,0,cond_else,11 DA:347,10 -BRDA:347,0,0,0 -BRDA:347,0,1,1 -BRDA:347,0,2,1 -BRDA:347,0,3,10 +BRDA:347,0,cond_then,0 +BRDA:347,0,cond_else,1 +BRDA:347,0,if,1 +BRDA:347,0,else,10 DA:348,10 DA:350,10 -BRDA:350,0,0,1 -BRDA:350,0,1,10 +BRDA:350,0,cond_then,1 +BRDA:350,0,cond_else,10 DA:360,11 -BRDA:360,0,0,0 -BRDA:360,0,1,11 +BRDA:360,0,if,0 +BRDA:360,0,else,11 DA:361,11 BRF:64 BRH:20 diff --git a/test_regress/t/t_vlcov_opt_expr.info.out b/test_regress/t/t_vlcov_opt_expr.info.out index 33c3c1c89..c83249652 100644 --- a/test_regress/t/t_vlcov_opt_expr.info.out +++ b/test_regress/t/t_vlcov_opt_expr.info.out @@ -1,23 +1,23 @@ TN:verilator_coverage SF:t/t_cover_line.v DA:331,7 -BRDA:331,0,0,7 -BRDA:331,0,1,3 +BRDA:331,0,(((cyc %25 32'sh3) == 32'sh0)==0) => 0,7 +BRDA:331,0,(((cyc %25 32'sh3) == 32'sh0)==1) => 1,3 DA:347,1 -BRDA:347,0,0,1 -BRDA:347,0,1,0 +BRDA:347,0,((cyc > 32'sh5)==0) => 0,1 +BRDA:347,0,((cyc > 32'sh5)==1) => 1,0 DA:350,10 -BRDA:350,0,0,10 -BRDA:350,0,1,1 +BRDA:350,0,((cyc == 32'sh2)==0) => 0,10 +BRDA:350,0,((cyc == 32'sh2)==1) => 1,1 DA:353,11 -BRDA:353,0,0,11 -BRDA:353,0,1,0 +BRDA:353,0,((i < 32'sh5)==0) => 0,11 +BRDA:353,0,((i < 32'sh5)==1) => 1,0 DA:356,11 -BRDA:356,0,0,0 -BRDA:356,0,1,11 +BRDA:356,0,((i > 32'sh4)==0) => 0,0 +BRDA:356,0,((i > 32'sh4)==1) => 1,11 DA:360,11 -BRDA:360,0,0,11 -BRDA:360,0,1,0 +BRDA:360,0,(k==0) => 0,11 +BRDA:360,0,(k==1) => 1,0 BRF:12 BRH:4 end_of_record diff --git a/test_regress/t/t_vlcov_opt_line.info.out b/test_regress/t/t_vlcov_opt_line.info.out index eaba1df5d..c744548c6 100644 --- a/test_regress/t/t_vlcov_opt_line.info.out +++ b/test_regress/t/t_vlcov_opt_line.info.out @@ -10,8 +10,9 @@ DA:87,1 DA:88,1 DA:89,1 DA:91,7 -BRDA:91,0,0,1 -BRDA:91,0,1,7 +BRDA:91,0,if,1 +BRDA:91,0,else,7 +BRDA:91,0,test_test,5 DA:92,1 DA:93,1 DA:96,7 @@ -75,13 +76,13 @@ DA:332,10 DA:346,11 DA:350,11 DA:353,55 -BRDA:353,0,0,11 -BRDA:353,0,1,55 +BRDA:353,0,block,11 +BRDA:353,0,block,55 DA:354,55 DA:356,44 -BRDA:356,0,0,11 -BRDA:356,0,1,44 +BRDA:356,0,block,11 +BRDA:356,0,block,44 DA:357,44 -BRF:6 +BRF:7 BRH:4 end_of_record diff --git a/test_regress/t/t_vlcov_opt_toggle.info.out b/test_regress/t/t_vlcov_opt_toggle.info.out index 7740e44dc..e44fbbbbe 100644 --- a/test_regress/t/t_vlcov_opt_toggle.info.out +++ b/test_regress/t/t_vlcov_opt_toggle.info.out @@ -3,14 +3,14 @@ SF:t/t_cover_line.v DA:12,19 DA:14,2 DA:20,11 -BRDA:20,0,0,11 -BRDA:20,0,1,5 -BRDA:20,0,2,2 -BRDA:20,0,3,1 -BRDA:20,0,4,0 -BRDA:20,0,5,0 -BRDA:20,0,6,0 -BRDA:20,0,7,0 +BRDA:20,0,cyc_copy[0],11 +BRDA:20,0,cyc_copy[1],5 +BRDA:20,0,cyc_copy[2],2 +BRDA:20,0,cyc_copy[3],1 +BRDA:20,0,cyc_copy[4],0 +BRDA:20,0,cyc_copy[5],0 +BRDA:20,0,cyc_copy[6],0 +BRDA:20,0,cyc_copy[7],0 DA:138,38 DA:139,4 DA:159,38 @@ -21,96 +21,96 @@ DA:241,19 DA:242,2 DA:261,19 DA:262,10 -BRDA:262,0,0,10 -BRDA:262,0,1,5 -BRDA:262,0,2,2 -BRDA:262,0,3,1 +BRDA:262,0,cyc4[0],10 +BRDA:262,0,cyc4[1],5 +BRDA:262,0,cyc4[2],2 +BRDA:262,0,cyc4[3],1 DA:309,19 -BRDA:309,0,0,19 -BRDA:309,0,1,11 -BRDA:309,0,2,0 -BRDA:309,0,3,0 -BRDA:309,0,4,0 -BRDA:309,0,5,0 -BRDA:309,0,6,0 -BRDA:309,0,7,0 -BRDA:309,0,8,0 -BRDA:309,0,9,0 -BRDA:309,0,10,0 -BRDA:309,0,11,0 -BRDA:309,0,12,5 -BRDA:309,0,13,0 -BRDA:309,0,14,0 -BRDA:309,0,15,0 -BRDA:309,0,16,0 -BRDA:309,0,17,0 -BRDA:309,0,18,0 -BRDA:309,0,19,0 -BRDA:309,0,20,0 -BRDA:309,0,21,0 -BRDA:309,0,22,0 -BRDA:309,0,23,2 -BRDA:309,0,24,0 -BRDA:309,0,25,0 -BRDA:309,0,26,1 -BRDA:309,0,27,0 -BRDA:309,0,28,0 -BRDA:309,0,29,0 -BRDA:309,0,30,0 -BRDA:309,0,31,0 -BRDA:309,0,32,0 +BRDA:309,0,clk,19 +BRDA:309,0,cyc[0],11 +BRDA:309,0,cyc[10],0 +BRDA:309,0,cyc[11],0 +BRDA:309,0,cyc[12],0 +BRDA:309,0,cyc[13],0 +BRDA:309,0,cyc[14],0 +BRDA:309,0,cyc[15],0 +BRDA:309,0,cyc[16],0 +BRDA:309,0,cyc[17],0 +BRDA:309,0,cyc[18],0 +BRDA:309,0,cyc[19],0 +BRDA:309,0,cyc[1],5 +BRDA:309,0,cyc[20],0 +BRDA:309,0,cyc[21],0 +BRDA:309,0,cyc[22],0 +BRDA:309,0,cyc[23],0 +BRDA:309,0,cyc[24],0 +BRDA:309,0,cyc[25],0 +BRDA:309,0,cyc[26],0 +BRDA:309,0,cyc[27],0 +BRDA:309,0,cyc[28],0 +BRDA:309,0,cyc[29],0 +BRDA:309,0,cyc[2],2 +BRDA:309,0,cyc[30],0 +BRDA:309,0,cyc[31],0 +BRDA:309,0,cyc[3],1 +BRDA:309,0,cyc[4],0 +BRDA:309,0,cyc[5],0 +BRDA:309,0,cyc[6],0 +BRDA:309,0,cyc[7],0 +BRDA:309,0,cyc[8],0 +BRDA:309,0,cyc[9],0 DA:310,19 -BRDA:310,0,0,0 -BRDA:310,0,1,2 -BRDA:310,0,2,19 -BRDA:310,0,3,6 -BRDA:310,0,4,7 -BRDA:310,0,5,1 -BRDA:310,0,6,19 -BRDA:310,0,7,3 -BRDA:310,0,8,0 -BRDA:310,0,9,0 -BRDA:310,0,10,0 +BRDA:310,0,a,0 +BRDA:310,0,b,2 +BRDA:310,0,c,19 +BRDA:310,0,d,6 +BRDA:310,0,e,7 +BRDA:310,0,f,1 +BRDA:310,0,g,19 +BRDA:310,0,h,3 +BRDA:310,0,k,0 +BRDA:310,0,l,0 +BRDA:310,0,m,0 DA:311,1 -BRDA:311,0,0,1 -BRDA:311,0,1,1 -BRDA:311,0,2,0 -BRDA:311,0,3,0 -BRDA:311,0,4,0 -BRDA:311,0,5,0 +BRDA:311,0,tab[0],1 +BRDA:311,0,tab[1],1 +BRDA:311,0,tab[2],0 +BRDA:311,0,tab[3],0 +BRDA:311,0,tab[4],0 +BRDA:311,0,tab[5],0 DA:313,2 -BRDA:313,0,0,0 -BRDA:313,0,1,2 -BRDA:313,0,2,0 -BRDA:313,0,3,0 -BRDA:313,0,4,0 -BRDA:313,0,5,0 -BRDA:313,0,6,0 -BRDA:313,0,7,0 -BRDA:313,0,8,2 -BRDA:313,0,9,0 -BRDA:313,0,10,0 -BRDA:313,0,11,0 -BRDA:313,0,12,0 -BRDA:313,0,13,0 -BRDA:313,0,14,0 -BRDA:313,0,15,0 -BRDA:313,0,16,0 -BRDA:313,0,17,0 -BRDA:313,0,18,0 -BRDA:313,0,19,0 -BRDA:313,0,20,0 -BRDA:313,0,21,0 -BRDA:313,0,22,0 -BRDA:313,0,23,0 -BRDA:313,0,24,0 -BRDA:313,0,25,0 -BRDA:313,0,26,0 -BRDA:313,0,27,0 -BRDA:313,0,28,0 -BRDA:313,0,29,0 -BRDA:313,0,30,0 -BRDA:313,0,31,0 +BRDA:313,0,data[0][0][0],0 +BRDA:313,0,data[0][0][1],2 +BRDA:313,0,data[0][0][2],0 +BRDA:313,0,data[0][0][3],0 +BRDA:313,0,data[0][0][4],0 +BRDA:313,0,data[0][0][5],0 +BRDA:313,0,data[0][0][6],0 +BRDA:313,0,data[0][0][7],0 +BRDA:313,0,data[0][1][0],2 +BRDA:313,0,data[0][1][1],0 +BRDA:313,0,data[0][1][2],0 +BRDA:313,0,data[0][1][3],0 +BRDA:313,0,data[0][1][4],0 +BRDA:313,0,data[0][1][5],0 +BRDA:313,0,data[0][1][6],0 +BRDA:313,0,data[0][1][7],0 +BRDA:313,0,data[1][0][0],0 +BRDA:313,0,data[1][0][1],0 +BRDA:313,0,data[1][0][2],0 +BRDA:313,0,data[1][0][3],0 +BRDA:313,0,data[1][0][4],0 +BRDA:313,0,data[1][0][5],0 +BRDA:313,0,data[1][0][6],0 +BRDA:313,0,data[1][0][7],0 +BRDA:313,0,data[1][1][0],0 +BRDA:313,0,data[1][1][1],0 +BRDA:313,0,data[1][1][2],0 +BRDA:313,0,data[1][1][3],0 +BRDA:313,0,data[1][1][4],0 +BRDA:313,0,data[1][1][5],0 +BRDA:313,0,data[1][1][6],0 +BRDA:313,0,data[1][1][7],0 BRF:94 BRH:6 end_of_record diff --git a/test_regress/t/t_vlcov_opt_user.info.out b/test_regress/t/t_vlcov_opt_user.info.out index e1b47061b..45fd8205a 100644 --- a/test_regress/t/t_vlcov_opt_user.info.out +++ b/test_regress/t/t_vlcov_opt_user.info.out @@ -1,180 +1,180 @@ TN:verilator_coverage SF:t/t_assert_ctl_arg.v DA:49,1 -BRDA:49,0,0,1 -BRDA:49,0,1,1 -BRDA:49,0,2,0 -BRDA:49,0,3,0 -BRDA:49,0,4,0 -BRDA:49,0,5,0 +BRDA:49,0,cover_simple_immediate_49,1 +BRDA:49,0,cover_simple_immediate_stmt_49,1 +BRDA:49,0,cover_final_deferred_immediate_49,0 +BRDA:49,0,cover_observed_deferred_immediate_49,0 +BRDA:49,0,cover_final_deferred_immediate_stmt_49,0 +BRDA:49,0,cover_observed_deferred_immediate_stmt_49,0 DA:51,0 -BRDA:51,0,0,0 -BRDA:51,0,1,0 -BRDA:51,0,2,0 -BRDA:51,0,3,0 -BRDA:51,0,4,0 -BRDA:51,0,5,0 +BRDA:51,0,cover_simple_immediate_51,0 +BRDA:51,0,cover_simple_immediate_stmt_51,0 +BRDA:51,0,cover_final_deferred_immediate_51,0 +BRDA:51,0,cover_observed_deferred_immediate_51,0 +BRDA:51,0,cover_final_deferred_immediate_stmt_51,0 +BRDA:51,0,cover_observed_deferred_immediate_stmt_51,0 DA:56,1 -BRDA:56,0,0,0 -BRDA:56,0,1,0 -BRDA:56,0,2,0 -BRDA:56,0,3,1 -BRDA:56,0,4,0 -BRDA:56,0,5,1 +BRDA:56,0,cover_simple_immediate_56,0 +BRDA:56,0,cover_simple_immediate_stmt_56,0 +BRDA:56,0,cover_final_deferred_immediate_56,0 +BRDA:56,0,cover_observed_deferred_immediate_56,1 +BRDA:56,0,cover_final_deferred_immediate_stmt_56,0 +BRDA:56,0,cover_observed_deferred_immediate_stmt_56,1 DA:58,0 -BRDA:58,0,0,0 -BRDA:58,0,1,0 -BRDA:58,0,2,0 -BRDA:58,0,3,0 -BRDA:58,0,4,0 -BRDA:58,0,5,0 +BRDA:58,0,cover_simple_immediate_58,0 +BRDA:58,0,cover_simple_immediate_stmt_58,0 +BRDA:58,0,cover_final_deferred_immediate_58,0 +BRDA:58,0,cover_observed_deferred_immediate_58,0 +BRDA:58,0,cover_final_deferred_immediate_stmt_58,0 +BRDA:58,0,cover_observed_deferred_immediate_stmt_58,0 DA:63,1 -BRDA:63,0,0,0 -BRDA:63,0,1,0 -BRDA:63,0,2,1 -BRDA:63,0,3,0 -BRDA:63,0,4,1 -BRDA:63,0,5,0 +BRDA:63,0,cover_simple_immediate_63,0 +BRDA:63,0,cover_simple_immediate_stmt_63,0 +BRDA:63,0,cover_final_deferred_immediate_63,1 +BRDA:63,0,cover_observed_deferred_immediate_63,0 +BRDA:63,0,cover_final_deferred_immediate_stmt_63,1 +BRDA:63,0,cover_observed_deferred_immediate_stmt_63,0 DA:65,0 -BRDA:65,0,0,0 -BRDA:65,0,1,0 -BRDA:65,0,2,0 -BRDA:65,0,3,0 -BRDA:65,0,4,0 -BRDA:65,0,5,0 +BRDA:65,0,cover_simple_immediate_65,0 +BRDA:65,0,cover_simple_immediate_stmt_65,0 +BRDA:65,0,cover_final_deferred_immediate_65,0 +BRDA:65,0,cover_observed_deferred_immediate_65,0 +BRDA:65,0,cover_final_deferred_immediate_stmt_65,0 +BRDA:65,0,cover_observed_deferred_immediate_stmt_65,0 DA:69,0 -BRDA:69,0,0,0 -BRDA:69,0,1,0 -BRDA:69,0,2,0 -BRDA:69,0,3,0 -BRDA:69,0,4,0 -BRDA:69,0,5,0 +BRDA:69,0,cover_simple_immediate_69,0 +BRDA:69,0,cover_simple_immediate_stmt_69,0 +BRDA:69,0,cover_final_deferred_immediate_69,0 +BRDA:69,0,cover_observed_deferred_immediate_69,0 +BRDA:69,0,cover_final_deferred_immediate_stmt_69,0 +BRDA:69,0,cover_observed_deferred_immediate_stmt_69,0 DA:71,1 -BRDA:71,0,0,1 -BRDA:71,0,1,1 -BRDA:71,0,2,1 -BRDA:71,0,3,1 -BRDA:71,0,4,1 -BRDA:71,0,5,1 +BRDA:71,0,cover_simple_immediate_71,1 +BRDA:71,0,cover_simple_immediate_stmt_71,1 +BRDA:71,0,cover_final_deferred_immediate_71,1 +BRDA:71,0,cover_observed_deferred_immediate_71,1 +BRDA:71,0,cover_final_deferred_immediate_stmt_71,1 +BRDA:71,0,cover_observed_deferred_immediate_stmt_71,1 DA:73,0 -BRDA:73,0,0,0 -BRDA:73,0,1,0 -BRDA:73,0,2,0 -BRDA:73,0,3,0 -BRDA:73,0,4,0 -BRDA:73,0,5,0 +BRDA:73,0,cover_simple_immediate_73,0 +BRDA:73,0,cover_simple_immediate_stmt_73,0 +BRDA:73,0,cover_final_deferred_immediate_73,0 +BRDA:73,0,cover_observed_deferred_immediate_73,0 +BRDA:73,0,cover_final_deferred_immediate_stmt_73,0 +BRDA:73,0,cover_observed_deferred_immediate_stmt_73,0 DA:76,1 -BRDA:76,0,0,1 -BRDA:76,0,1,1 -BRDA:76,0,2,0 -BRDA:76,0,3,1 -BRDA:76,0,4,0 -BRDA:76,0,5,1 +BRDA:76,0,cover_simple_immediate_76,1 +BRDA:76,0,cover_simple_immediate_stmt_76,1 +BRDA:76,0,cover_final_deferred_immediate_76,0 +BRDA:76,0,cover_observed_deferred_immediate_76,1 +BRDA:76,0,cover_final_deferred_immediate_stmt_76,0 +BRDA:76,0,cover_observed_deferred_immediate_stmt_76,1 DA:78,1 -BRDA:78,0,0,1 -BRDA:78,0,1,1 -BRDA:78,0,2,1 -BRDA:78,0,3,1 -BRDA:78,0,4,1 -BRDA:78,0,5,1 +BRDA:78,0,cover_simple_immediate_78,1 +BRDA:78,0,cover_simple_immediate_stmt_78,1 +BRDA:78,0,cover_final_deferred_immediate_78,1 +BRDA:78,0,cover_observed_deferred_immediate_78,1 +BRDA:78,0,cover_final_deferred_immediate_stmt_78,1 +BRDA:78,0,cover_observed_deferred_immediate_stmt_78,1 DA:80,1 -BRDA:80,0,0,1 -BRDA:80,0,1,1 -BRDA:80,0,2,0 -BRDA:80,0,3,0 -BRDA:80,0,4,0 -BRDA:80,0,5,0 +BRDA:80,0,cover_simple_immediate_80,1 +BRDA:80,0,cover_simple_immediate_stmt_80,1 +BRDA:80,0,cover_final_deferred_immediate_80,0 +BRDA:80,0,cover_observed_deferred_immediate_80,0 +BRDA:80,0,cover_final_deferred_immediate_stmt_80,0 +BRDA:80,0,cover_observed_deferred_immediate_stmt_80,0 DA:82,1 -BRDA:82,0,0,1 -BRDA:82,0,1,1 -BRDA:82,0,2,0 -BRDA:82,0,3,0 -BRDA:82,0,4,0 -BRDA:82,0,5,0 +BRDA:82,0,cover_simple_immediate_82,1 +BRDA:82,0,cover_simple_immediate_stmt_82,1 +BRDA:82,0,cover_final_deferred_immediate_82,0 +BRDA:82,0,cover_observed_deferred_immediate_82,0 +BRDA:82,0,cover_final_deferred_immediate_stmt_82,0 +BRDA:82,0,cover_observed_deferred_immediate_stmt_82,0 DA:84,0 -BRDA:84,0,0,0 -BRDA:84,0,1,0 -BRDA:84,0,2,0 -BRDA:84,0,3,0 -BRDA:84,0,4,0 -BRDA:84,0,5,0 +BRDA:84,0,cover_simple_immediate_84,0 +BRDA:84,0,cover_simple_immediate_stmt_84,0 +BRDA:84,0,cover_final_deferred_immediate_84,0 +BRDA:84,0,cover_observed_deferred_immediate_84,0 +BRDA:84,0,cover_final_deferred_immediate_stmt_84,0 +BRDA:84,0,cover_observed_deferred_immediate_stmt_84,0 DA:86,1 -BRDA:86,0,0,1 -BRDA:86,0,1,1 -BRDA:86,0,2,0 -BRDA:86,0,3,0 -BRDA:86,0,4,0 -BRDA:86,0,5,0 +BRDA:86,0,cover_simple_immediate_86,1 +BRDA:86,0,cover_simple_immediate_stmt_86,1 +BRDA:86,0,cover_final_deferred_immediate_86,0 +BRDA:86,0,cover_observed_deferred_immediate_86,0 +BRDA:86,0,cover_final_deferred_immediate_stmt_86,0 +BRDA:86,0,cover_observed_deferred_immediate_stmt_86,0 DA:88,0 -BRDA:88,0,0,0 -BRDA:88,0,1,0 -BRDA:88,0,2,0 -BRDA:88,0,3,0 -BRDA:88,0,4,0 -BRDA:88,0,5,0 +BRDA:88,0,cover_simple_immediate_88,0 +BRDA:88,0,cover_simple_immediate_stmt_88,0 +BRDA:88,0,cover_final_deferred_immediate_88,0 +BRDA:88,0,cover_observed_deferred_immediate_88,0 +BRDA:88,0,cover_final_deferred_immediate_stmt_88,0 +BRDA:88,0,cover_observed_deferred_immediate_stmt_88,0 DA:90,1 -BRDA:90,0,0,1 -BRDA:90,0,1,1 -BRDA:90,0,2,1 -BRDA:90,0,3,1 -BRDA:90,0,4,1 -BRDA:90,0,5,1 +BRDA:90,0,cover_simple_immediate_90,1 +BRDA:90,0,cover_simple_immediate_stmt_90,1 +BRDA:90,0,cover_final_deferred_immediate_90,1 +BRDA:90,0,cover_observed_deferred_immediate_90,1 +BRDA:90,0,cover_final_deferred_immediate_stmt_90,1 +BRDA:90,0,cover_observed_deferred_immediate_stmt_90,1 DA:92,0 -BRDA:92,0,0,0 -BRDA:92,0,1,0 -BRDA:92,0,2,0 -BRDA:92,0,3,0 -BRDA:92,0,4,0 -BRDA:92,0,5,0 +BRDA:92,0,cover_simple_immediate_92,0 +BRDA:92,0,cover_simple_immediate_stmt_92,0 +BRDA:92,0,cover_final_deferred_immediate_92,0 +BRDA:92,0,cover_observed_deferred_immediate_92,0 +BRDA:92,0,cover_final_deferred_immediate_stmt_92,0 +BRDA:92,0,cover_observed_deferred_immediate_stmt_92,0 DA:97,0 -BRDA:97,0,0,0 -BRDA:97,0,1,0 -BRDA:97,0,2,0 -BRDA:97,0,3,0 -BRDA:97,0,4,0 -BRDA:97,0,5,0 +BRDA:97,0,cover_simple_immediate_97,0 +BRDA:97,0,cover_simple_immediate_stmt_97,0 +BRDA:97,0,cover_final_deferred_immediate_97,0 +BRDA:97,0,cover_observed_deferred_immediate_97,0 +BRDA:97,0,cover_final_deferred_immediate_stmt_97,0 +BRDA:97,0,cover_observed_deferred_immediate_stmt_97,0 DA:100,1 -BRDA:100,0,0,1 -BRDA:100,0,1,1 -BRDA:100,0,2,1 -BRDA:100,0,3,1 -BRDA:100,0,4,1 -BRDA:100,0,5,1 +BRDA:100,0,cover_simple_immediate_100,1 +BRDA:100,0,cover_simple_immediate_stmt_100,1 +BRDA:100,0,cover_final_deferred_immediate_100,1 +BRDA:100,0,cover_observed_deferred_immediate_100,1 +BRDA:100,0,cover_final_deferred_immediate_stmt_100,1 +BRDA:100,0,cover_observed_deferred_immediate_stmt_100,1 DA:103,0 -BRDA:103,0,0,0 -BRDA:103,0,1,0 -BRDA:103,0,2,0 -BRDA:103,0,3,0 -BRDA:103,0,4,0 -BRDA:103,0,5,0 +BRDA:103,0,cover_simple_immediate_103,0 +BRDA:103,0,cover_simple_immediate_stmt_103,0 +BRDA:103,0,cover_final_deferred_immediate_103,0 +BRDA:103,0,cover_observed_deferred_immediate_103,0 +BRDA:103,0,cover_final_deferred_immediate_stmt_103,0 +BRDA:103,0,cover_observed_deferred_immediate_stmt_103,0 DA:106,1 -BRDA:106,0,0,1 -BRDA:106,0,1,1 -BRDA:106,0,2,1 -BRDA:106,0,3,1 -BRDA:106,0,4,1 -BRDA:106,0,5,1 +BRDA:106,0,cover_simple_immediate_106,1 +BRDA:106,0,cover_simple_immediate_stmt_106,1 +BRDA:106,0,cover_final_deferred_immediate_106,1 +BRDA:106,0,cover_observed_deferred_immediate_106,1 +BRDA:106,0,cover_final_deferred_immediate_stmt_106,1 +BRDA:106,0,cover_observed_deferred_immediate_stmt_106,1 DA:108,1 -BRDA:108,0,0,1 -BRDA:108,0,1,1 -BRDA:108,0,2,1 -BRDA:108,0,3,1 -BRDA:108,0,4,1 -BRDA:108,0,5,1 +BRDA:108,0,cover_simple_immediate_108,1 +BRDA:108,0,cover_simple_immediate_stmt_108,1 +BRDA:108,0,cover_final_deferred_immediate_108,1 +BRDA:108,0,cover_observed_deferred_immediate_108,1 +BRDA:108,0,cover_final_deferred_immediate_stmt_108,1 +BRDA:108,0,cover_observed_deferred_immediate_stmt_108,1 DA:110,0 -BRDA:110,0,0,0 -BRDA:110,0,1,0 -BRDA:110,0,2,0 -BRDA:110,0,3,0 -BRDA:110,0,4,0 -BRDA:110,0,5,0 +BRDA:110,0,cover_simple_immediate_110,0 +BRDA:110,0,cover_simple_immediate_stmt_110,0 +BRDA:110,0,cover_final_deferred_immediate_110,0 +BRDA:110,0,cover_observed_deferred_immediate_110,0 +BRDA:110,0,cover_final_deferred_immediate_stmt_110,0 +BRDA:110,0,cover_observed_deferred_immediate_stmt_110,0 DA:112,1 -BRDA:112,0,0,1 -BRDA:112,0,1,1 -BRDA:112,0,2,1 -BRDA:112,0,3,0 -BRDA:112,0,4,1 -BRDA:112,0,5,0 +BRDA:112,0,cover_simple_immediate_112,1 +BRDA:112,0,cover_simple_immediate_stmt_112,1 +BRDA:112,0,cover_final_deferred_immediate_112,1 +BRDA:112,0,cover_observed_deferred_immediate_112,0 +BRDA:112,0,cover_final_deferred_immediate_stmt_112,1 +BRDA:112,0,cover_observed_deferred_immediate_stmt_112,0 DA:192,0 DA:193,0 BRF:150 diff --git a/test_regress/t/t_vlcov_opt_wild.info.out b/test_regress/t/t_vlcov_opt_wild.info.out index efcf737f6..8d8af4f6f 100644 --- a/test_regress/t/t_vlcov_opt_wild.info.out +++ b/test_regress/t/t_vlcov_opt_wild.info.out @@ -5,42 +5,42 @@ DA:14,2 DA:15,1 DA:18,1 DA:20,11 -BRDA:20,0,0,11 -BRDA:20,0,1,5 -BRDA:20,0,2,2 -BRDA:20,0,3,1 -BRDA:20,0,4,0 -BRDA:20,0,5,0 -BRDA:20,0,6,0 -BRDA:20,0,7,0 +BRDA:20,0,cyc_copy[0],11 +BRDA:20,0,cyc_copy[1],5 +BRDA:20,0,cyc_copy[2],2 +BRDA:20,0,cyc_copy[3],1 +BRDA:20,0,cyc_copy[4],0 +BRDA:20,0,cyc_copy[5],0 +BRDA:20,0,cyc_copy[6],0 +BRDA:20,0,cyc_copy[7],0 DA:55,10 DA:56,10 -BRDA:56,0,0,10 -BRDA:56,0,1,0 +BRDA:56,0,if,10 +BRDA:56,0,else,0 DA:57,10 DA:58,10 DA:60,9 -BRDA:60,0,0,1 -BRDA:60,0,1,9 +BRDA:60,0,if,1 +BRDA:60,0,else,9 DA:61,9 -BRDA:61,0,0,1 -BRDA:61,0,1,9 +BRDA:61,0,if,1 +BRDA:61,0,else,9 DA:62,1 DA:63,1 DA:66,9 -BRDA:66,0,0,1 -BRDA:66,0,1,9 +BRDA:66,0,if,1 +BRDA:66,0,else,9 DA:67,9 -BRDA:67,0,0,1 -BRDA:67,0,1,9 +BRDA:67,0,if,1 +BRDA:67,0,else,9 DA:69,9 DA:70,9 DA:73,9 -BRDA:73,0,0,1 -BRDA:73,0,1,9 +BRDA:73,0,if,1 +BRDA:73,0,else,9 DA:74,9 -BRDA:74,0,0,1 -BRDA:74,0,1,9 +BRDA:74,0,if,1 +BRDA:74,0,else,9 DA:75,1 DA:76,1 DA:79,9 @@ -52,8 +52,9 @@ DA:87,1 DA:88,1 DA:89,1 DA:91,7 -BRDA:91,0,0,1 -BRDA:91,0,1,7 +BRDA:91,0,if,1 +BRDA:91,0,else,7 +BRDA:91,0,test_test,5 DA:92,1 DA:93,1 DA:96,7 @@ -63,19 +64,19 @@ DA:101,0 DA:102,0 DA:104,0 DA:105,10 -BRDA:105,0,0,0 -BRDA:105,0,1,10 +BRDA:105,0,block,0 +BRDA:105,0,if,10 DA:106,10 -BRDA:106,0,0,0 -BRDA:106,0,1,10 +BRDA:106,0,block,0 +BRDA:106,0,if,10 DA:107,0 DA:110,1 DA:111,1 DA:113,1 DA:115,1 DA:120,7 -BRDA:120,0,0,1 -BRDA:120,0,1,7 +BRDA:120,0,if,1 +BRDA:120,0,else,7 DA:121,1 DA:122,1 DA:127,1 @@ -84,8 +85,8 @@ DA:138,38 DA:139,4 DA:140,20 DA:141,18 -BRDA:141,0,0,2 -BRDA:141,0,1,18 +BRDA:141,0,if,2 +BRDA:141,0,else,18 DA:142,2 DA:145,18 DA:159,38 @@ -93,29 +94,29 @@ DA:160,4 DA:164,20 DA:165,20 DA:166,20 -BRDA:166,0,0,0 -BRDA:166,0,1,20 +BRDA:166,0,if,0 +BRDA:166,0,else,20 DA:168,0 DA:170,18 -BRDA:170,0,0,2 -BRDA:170,0,1,18 +BRDA:170,0,if,2 +BRDA:170,0,else,18 DA:172,2 DA:174,18 DA:188,1 DA:189,1 DA:190,1 -BRDA:190,0,0,1 -BRDA:190,0,1,0 +BRDA:190,0,if,1 +BRDA:190,0,else,0 DA:191,1 DA:194,11 DA:195,11 -BRDA:195,0,0,11 -BRDA:195,0,1,0 +BRDA:195,0,if,11 +BRDA:195,0,else,0 DA:196,11 DA:199,11 DA:200,11 -BRDA:200,0,0,11 -BRDA:200,0,1,0 +BRDA:200,0,if,11 +BRDA:200,0,else,0 DA:201,11 DA:210,19 DA:211,2 @@ -124,12 +125,12 @@ DA:216,10 DA:219,11 DA:221,11 DA:222,10 -BRDA:222,0,0,1 -BRDA:222,0,1,10 +BRDA:222,0,if,1 +BRDA:222,0,else,10 DA:223,1 DA:225,10 -BRDA:225,0,0,1 -BRDA:225,0,1,10 +BRDA:225,0,if,1 +BRDA:225,0,else,10 DA:226,1 DA:229,11 DA:230,1 @@ -139,18 +140,18 @@ DA:241,19 DA:242,2 DA:252,10 DA:253,9 -BRDA:253,0,0,1 -BRDA:253,0,1,9 +BRDA:253,0,if,1 +BRDA:253,0,else,9 DA:255,1 DA:256,1 -BRDA:256,0,0,0 -BRDA:256,0,1,1 +BRDA:256,0,if,0 +BRDA:256,0,else,1 DA:261,19 DA:262,10 -BRDA:262,0,0,10 -BRDA:262,0,1,5 -BRDA:262,0,2,2 -BRDA:262,0,3,1 +BRDA:262,0,cyc4[0],10 +BRDA:262,0,cyc4[1],5 +BRDA:262,0,cyc4[2],2 +BRDA:262,0,cyc4[3],1 DA:265,10 DA:266,10 DA:267,1 @@ -163,8 +164,8 @@ DA:276,10 DA:277,10 DA:287,0 DA:288,0 -BRDA:288,0,0,0 -BRDA:288,0,1,0 +BRDA:288,0,if,0 +BRDA:288,0,else,0 DA:289,0 DA:291,0 DA:292,0 @@ -174,91 +175,91 @@ DA:303,1 DA:304,20 DA:305,20 DA:309,19 -BRDA:309,0,0,19 -BRDA:309,0,1,11 -BRDA:309,0,2,0 -BRDA:309,0,3,0 -BRDA:309,0,4,0 -BRDA:309,0,5,0 -BRDA:309,0,6,0 -BRDA:309,0,7,0 -BRDA:309,0,8,0 -BRDA:309,0,9,0 -BRDA:309,0,10,0 -BRDA:309,0,11,0 -BRDA:309,0,12,5 -BRDA:309,0,13,0 -BRDA:309,0,14,0 -BRDA:309,0,15,0 -BRDA:309,0,16,0 -BRDA:309,0,17,0 -BRDA:309,0,18,0 -BRDA:309,0,19,0 -BRDA:309,0,20,0 -BRDA:309,0,21,0 -BRDA:309,0,22,0 -BRDA:309,0,23,2 -BRDA:309,0,24,0 -BRDA:309,0,25,0 -BRDA:309,0,26,1 -BRDA:309,0,27,0 -BRDA:309,0,28,0 -BRDA:309,0,29,0 -BRDA:309,0,30,0 -BRDA:309,0,31,0 -BRDA:309,0,32,0 +BRDA:309,0,clk,19 +BRDA:309,0,cyc[0],11 +BRDA:309,0,cyc[10],0 +BRDA:309,0,cyc[11],0 +BRDA:309,0,cyc[12],0 +BRDA:309,0,cyc[13],0 +BRDA:309,0,cyc[14],0 +BRDA:309,0,cyc[15],0 +BRDA:309,0,cyc[16],0 +BRDA:309,0,cyc[17],0 +BRDA:309,0,cyc[18],0 +BRDA:309,0,cyc[19],0 +BRDA:309,0,cyc[1],5 +BRDA:309,0,cyc[20],0 +BRDA:309,0,cyc[21],0 +BRDA:309,0,cyc[22],0 +BRDA:309,0,cyc[23],0 +BRDA:309,0,cyc[24],0 +BRDA:309,0,cyc[25],0 +BRDA:309,0,cyc[26],0 +BRDA:309,0,cyc[27],0 +BRDA:309,0,cyc[28],0 +BRDA:309,0,cyc[29],0 +BRDA:309,0,cyc[2],2 +BRDA:309,0,cyc[30],0 +BRDA:309,0,cyc[31],0 +BRDA:309,0,cyc[3],1 +BRDA:309,0,cyc[4],0 +BRDA:309,0,cyc[5],0 +BRDA:309,0,cyc[6],0 +BRDA:309,0,cyc[7],0 +BRDA:309,0,cyc[8],0 +BRDA:309,0,cyc[9],0 DA:310,19 -BRDA:310,0,0,0 -BRDA:310,0,1,2 -BRDA:310,0,2,19 -BRDA:310,0,3,6 -BRDA:310,0,4,7 -BRDA:310,0,5,1 -BRDA:310,0,6,19 -BRDA:310,0,7,3 -BRDA:310,0,8,0 -BRDA:310,0,9,0 -BRDA:310,0,10,0 +BRDA:310,0,a,0 +BRDA:310,0,b,2 +BRDA:310,0,c,19 +BRDA:310,0,d,6 +BRDA:310,0,e,7 +BRDA:310,0,f,1 +BRDA:310,0,g,19 +BRDA:310,0,h,3 +BRDA:310,0,k,0 +BRDA:310,0,l,0 +BRDA:310,0,m,0 DA:311,1 -BRDA:311,0,0,1 -BRDA:311,0,1,1 -BRDA:311,0,2,0 -BRDA:311,0,3,0 -BRDA:311,0,4,0 -BRDA:311,0,5,0 +BRDA:311,0,tab[0],1 +BRDA:311,0,tab[1],1 +BRDA:311,0,tab[2],0 +BRDA:311,0,tab[3],0 +BRDA:311,0,tab[4],0 +BRDA:311,0,tab[5],0 DA:313,2 -BRDA:313,0,0,0 -BRDA:313,0,1,2 -BRDA:313,0,2,0 -BRDA:313,0,3,0 -BRDA:313,0,4,0 -BRDA:313,0,5,0 -BRDA:313,0,6,0 -BRDA:313,0,7,0 -BRDA:313,0,8,2 -BRDA:313,0,9,0 -BRDA:313,0,10,0 -BRDA:313,0,11,0 -BRDA:313,0,12,0 -BRDA:313,0,13,0 -BRDA:313,0,14,0 -BRDA:313,0,15,0 -BRDA:313,0,16,0 -BRDA:313,0,17,0 -BRDA:313,0,18,0 -BRDA:313,0,19,0 -BRDA:313,0,20,0 -BRDA:313,0,21,0 -BRDA:313,0,22,0 -BRDA:313,0,23,0 -BRDA:313,0,24,0 -BRDA:313,0,25,0 -BRDA:313,0,26,0 -BRDA:313,0,27,0 -BRDA:313,0,28,0 -BRDA:313,0,29,0 -BRDA:313,0,30,0 -BRDA:313,0,31,0 +BRDA:313,0,data[0][0][0],0 +BRDA:313,0,data[0][0][1],2 +BRDA:313,0,data[0][0][2],0 +BRDA:313,0,data[0][0][3],0 +BRDA:313,0,data[0][0][4],0 +BRDA:313,0,data[0][0][5],0 +BRDA:313,0,data[0][0][6],0 +BRDA:313,0,data[0][0][7],0 +BRDA:313,0,data[0][1][0],2 +BRDA:313,0,data[0][1][1],0 +BRDA:313,0,data[0][1][2],0 +BRDA:313,0,data[0][1][3],0 +BRDA:313,0,data[0][1][4],0 +BRDA:313,0,data[0][1][5],0 +BRDA:313,0,data[0][1][6],0 +BRDA:313,0,data[0][1][7],0 +BRDA:313,0,data[1][0][0],0 +BRDA:313,0,data[1][0][1],0 +BRDA:313,0,data[1][0][2],0 +BRDA:313,0,data[1][0][3],0 +BRDA:313,0,data[1][0][4],0 +BRDA:313,0,data[1][0][5],0 +BRDA:313,0,data[1][0][6],0 +BRDA:313,0,data[1][0][7],0 +BRDA:313,0,data[1][1][0],0 +BRDA:313,0,data[1][1][1],0 +BRDA:313,0,data[1][1][2],0 +BRDA:313,0,data[1][1][3],0 +BRDA:313,0,data[1][1][4],0 +BRDA:313,0,data[1][1][5],0 +BRDA:313,0,data[1][1][6],0 +BRDA:313,0,data[1][1][7],0 DA:314,1 DA:317,21 DA:318,21 @@ -266,69 +267,69 @@ DA:319,21 DA:322,10 DA:324,10 DA:327,31 -BRDA:327,0,0,0 -BRDA:327,0,1,31 +BRDA:327,0,cond_then,0 +BRDA:327,0,cond_else,31 DA:328,28 -BRDA:328,0,0,3 -BRDA:328,0,1,28 +BRDA:328,0,cond_then,3 +BRDA:328,0,cond_else,28 DA:329,21 -BRDA:329,0,0,21 -BRDA:329,0,1,0 +BRDA:329,0,cond_then,21 +BRDA:329,0,cond_else,0 DA:330,10 DA:331,10 -BRDA:331,0,0,10 -BRDA:331,0,1,7 -BRDA:331,0,2,3 -BRDA:331,0,3,3 -BRDA:331,0,4,7 +BRDA:331,0,block,10 +BRDA:331,0,(((cyc %25 32'sh3) == 32'sh0)==0) => 0,7 +BRDA:331,0,(((cyc %25 32'sh3) == 32'sh0)==1) => 1,3 +BRDA:331,0,cond_then,3 +BRDA:331,0,cond_else,7 DA:332,10 -BRDA:332,0,0,10 -BRDA:332,0,1,0 -BRDA:332,0,2,10 +BRDA:332,0,block,10 +BRDA:332,0,cond_then,0 +BRDA:332,0,cond_else,10 DA:334,19 -BRDA:334,0,0,12 -BRDA:334,0,1,19 -BRDA:334,0,2,7 -BRDA:334,0,3,5 +BRDA:334,0,cond_then,12 +BRDA:334,0,cond_else,19 +BRDA:334,0,cond_then,7 +BRDA:334,0,cond_else,5 DA:337,11 -BRDA:337,0,0,11 -BRDA:337,0,1,0 +BRDA:337,0,cond_then,11 +BRDA:337,0,cond_else,0 DA:343,11 -BRDA:343,0,0,10 -BRDA:343,0,1,11 +BRDA:343,0,cond_then,10 +BRDA:343,0,cond_else,11 DA:346,11 DA:347,10 -BRDA:347,0,0,1 -BRDA:347,0,1,0 -BRDA:347,0,2,0 -BRDA:347,0,3,1 -BRDA:347,0,4,1 -BRDA:347,0,5,10 +BRDA:347,0,((cyc > 32'sh5)==0) => 0,1 +BRDA:347,0,((cyc > 32'sh5)==1) => 1,0 +BRDA:347,0,cond_then,0 +BRDA:347,0,cond_else,1 +BRDA:347,0,if,1 +BRDA:347,0,else,10 DA:348,10 DA:350,11 -BRDA:350,0,0,11 -BRDA:350,0,1,10 -BRDA:350,0,2,1 -BRDA:350,0,3,1 -BRDA:350,0,4,10 +BRDA:350,0,block,11 +BRDA:350,0,((cyc == 32'sh2)==0) => 0,10 +BRDA:350,0,((cyc == 32'sh2)==1) => 1,1 +BRDA:350,0,cond_then,1 +BRDA:350,0,cond_else,10 DA:353,55 -BRDA:353,0,0,11 -BRDA:353,0,1,11 -BRDA:353,0,2,0 -BRDA:353,0,3,55 +BRDA:353,0,block,11 +BRDA:353,0,((i < 32'sh5)==0) => 0,11 +BRDA:353,0,((i < 32'sh5)==1) => 1,0 +BRDA:353,0,block,55 DA:354,55 DA:356,44 -BRDA:356,0,0,11 -BRDA:356,0,1,0 -BRDA:356,0,2,11 -BRDA:356,0,3,44 +BRDA:356,0,block,11 +BRDA:356,0,((i > 32'sh4)==0) => 0,0 +BRDA:356,0,((i > 32'sh4)==1) => 1,11 +BRDA:356,0,block,44 DA:357,44 DA:360,11 -BRDA:360,0,0,11 -BRDA:360,0,1,0 -BRDA:360,0,2,0 -BRDA:360,0,3,11 +BRDA:360,0,(k==0) => 0,11 +BRDA:360,0,(k==1) => 1,0 +BRDA:360,0,if,0 +BRDA:360,0,else,11 DA:361,11 -BRF:183 +BRF:184 BRH:39 end_of_record diff --git a/test_regress/t/t_vlcov_summary_typed.out b/test_regress/t/t_vlcov_summary_typed.out index e4b3d14f6..f9700d96d 100644 --- a/test_regress/t/t_vlcov_summary_typed.out +++ b/test_regress/t/t_vlcov_summary_typed.out @@ -1,5 +1,5 @@ Coverage Summary: - line : 88.6% ( 39/ 44) + line : 88.9% ( 40/ 45) toggle : 33.3% ( 35/105) branch : 78.1% ( 50/ 64) expr : 66.7% ( 8/ 12) diff --git a/test_regress/t/t_vlt_syntax_bad.out b/test_regress/t/t_vlt_syntax_bad.out index f1ad706be..65a3941ea 100644 --- a/test_regress/t/t_vlt_syntax_bad.out +++ b/test_regress/t/t_vlt_syntax_bad.out @@ -2,28 +2,25 @@ 9 | public -module "t" @(posedge clk) | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_vlt_syntax_bad.vlt:11:1: isolate_assignments only applies to signals or functions/tasks - 11 | isolate_assignments -module "t" - | ^~~~~~~~~~~~~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:13:1: Argument -match only supported for lint_off - 13 | tracing_off --file "*" -match "nothing" +%Error: t/t_vlt_syntax_bad.vlt:11:1: Argument -match only supported for lint_off + 11 | tracing_off --file "*" -match "nothing" | ^~~~~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:13:1: Argument -scope only supported for tracing_on/off + 13 | lint_off --rule UNOPTFLAT -scope "top*" + | ^~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:14:1: Argument -scope only supported for tracing_on/off_off + 14 | lint_off --rule UNOPTFLAT -scope "top*" -levels 0 + | ^~~~~~~~ %Error: t/t_vlt_syntax_bad.vlt:15:1: Argument -scope only supported for tracing_on/off - 15 | lint_off --rule UNOPTFLAT -scope "top*" - | ^~~~~~~~ + 15 | lint_on --rule UNOPTFLAT -scope "top*" + | ^~~~~~~ %Error: t/t_vlt_syntax_bad.vlt:16:1: Argument -scope only supported for tracing_on/off_off - 16 | lint_off --rule UNOPTFLAT -scope "top*" -levels 0 - | ^~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:17:1: Argument -scope only supported for tracing_on/off - 17 | lint_on --rule UNOPTFLAT -scope "top*" + 16 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 | ^~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:18:1: Argument -scope only supported for tracing_on/off_off - 18 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 - | ^~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:20:1: forceable missing -module - 20 | forceable -module "" -var "net_*" +%Error: t/t_vlt_syntax_bad.vlt:18:1: forceable missing -module + 18 | forceable -module "" -var "net_*" | ^~~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:22:1: missing -var - 22 | forceable -module "top" -var "" +%Error: t/t_vlt_syntax_bad.vlt:20:1: missing -var + 20 | forceable -module "top" -var "" | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_vlt_syntax_bad.vlt b/test_regress/t/t_vlt_syntax_bad.vlt index 2ebcefa7c..0a5cdbce0 100644 --- a/test_regress/t/t_vlt_syntax_bad.vlt +++ b/test_regress/t/t_vlt_syntax_bad.vlt @@ -7,8 +7,6 @@ `verilator_config public -module "t" @(posedge clk) -// only signals/functions/tasks -isolate_assignments -module "t" // -match not supported tracing_off --file "*" -match "nothing" // -scope not supported diff --git a/test_regress/t/t_vpi_force.v b/test_regress/t/t_vpi_force.v index ae4913926..fcfa1b5fb 100644 --- a/test_regress/t/t_vpi_force.v +++ b/test_regress/t/t_vpi_force.v @@ -175,8 +175,7 @@ typedef enum byte { // - Continuous assignments do not work // - vpi_handle_by_multi_index is not implemented // - vpi_handle_by_name and vpi_handle_by_index fail with negative indices - // Hence, these signals are excluded from testing with Icarus. Recommend - // Xcelium for cross-checking results. + // Hence, these signals are excluded from testing with Icarus. `ifndef IVERILOG // Force the entire packed array (no partial forcing possible, because // partial indexing only works for bits, not dimension slices) @@ -2422,10 +2421,8 @@ $dumpfile(`STRINGIFY(`TEST_DUMPFILE)); svReleaseValues(); #8 svCheckValuesReleased(); - // Icarus does not support forcing single bits through VPI -`ifndef IVERILOG - // Xcelium supports forcing single bits through VPI, but crashes on some signals -`ifndef XRUN +`ifndef IVERILOG // Does not support forcing single bits through VPI +`ifndef XRUN // Supports forcing single bits through VPI, but crashes on some signals // Force single bit through VPI, release through VPI if (`verbose) $display("*** Forcing single bit through VPI, releasing through VPI ***"); diff --git a/test_regress/t/t_vpi_var.cpp b/test_regress/t/t_vpi_var.cpp index 3afaf92ca..7c0eac4f4 100644 --- a/test_regress/t/t_vpi_var.cpp +++ b/test_regress/t/t_vpi_var.cpp @@ -38,6 +38,8 @@ #endif +#include + #ifdef VERILATOR #include "verilated.h" #endif @@ -258,6 +260,626 @@ int _mon_check_big() { return 0; } +int _mon_check_unpacked_struct_members() { + const char* p; + PLI_INT32 d; + t_vpi_value tmpValue; + + // unpacked struct port members + tmpValue.format = vpiIntVal; + { + TestVpiHandle vh101 = VPI_HANDLE("unpacked_struct_port"); + CHECK_RESULT_NZ(vh101); + d = vpi_get(vpiType, vh101); + CHECK_RESULT(d, vpiStructVar); + CHECK_RESULT(vpi_get(vpiSize, vh101), 0); + CHECK_RESULT(vpi_get(vpiPacked, vh101), 0); + CHECK_RESULT_Z(vpi_handle(vpiLeftRange, vh101)); + p = vpi_get_str(vpiType, vh101); + CHECK_RESULT_CSTR(p, "vpiStructVar"); + + TestVpiHandle vh102 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh102); + CHECK_RESULT(vpi_get(vpiType, vh102), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh102), 7); + p = vpi_get_str(vpiName, vh102); + CHECK_RESULT_CSTR(p, "member_a"); + p = vpi_get_str(vpiFullName, vh102); + CHECK_RESULT_CSTR(p, "t.unpacked_struct_port.member_a"); + CHECK_RESULT_NZ(vpi_handle(vpiLeftRange, vh102)); + CHECK_RESULT_Z(vpi_iterate(vpiMember, vh102)); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"member_a", vh102)); + + TestVpiHandle vh103 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_b", NULL); + CHECK_RESULT_NZ(vh103); + CHECK_RESULT(vpi_get(vpiType, vh103), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh103), 1); + + TestVpiHandle vh104 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh101); + CHECK_RESULT_NZ(vh104); + CHECK_RESULT(vpi_get(vpiType, vh104), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh104), 16); + + CHECK_RESULT_Z(vpi_iterate(vpiMember, nullptr)); + TestVpiHandle vh105 = vpi_iterate(vpiMember, vh101); + CHECK_RESULT_NZ(vh105); + CHECK_RESULT(vpi_get(vpiType, vh105), vpiIterator); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh105)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh105.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x35; + vpi_put_value(vh102, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh102, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x35); + + putValue.value.integer = 0x4321; + vpi_put_value(vh104, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh104, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x4321); + + // Members resolve even without the toplevel scope prefix + TestVpiHandle vh106 + = vpi_handle_by_name((PLI_BYTE8*)"unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh106); + CHECK_RESULT(vpi_get(vpiType, vh106), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh106), 7); + vpi_get_value(vh106, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x35); + + TestVpiHandle vh107 + = vpi_handle_by_name((PLI_BYTE8*)"$root.t.unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh107); + CHECK_RESULT(vpi_get(vpiType, vh107), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh107), 7); + + TestVpiHandle vh108 = VPI_HANDLE(""); + CHECK_RESULT_NZ(vh108); + TestVpiHandle vh109 + = vpi_handle_by_name((PLI_BYTE8*)"unpacked_struct_port.member_b", vh108); + CHECK_RESULT_NZ(vh109); + CHECK_RESULT(vpi_get(vpiType, vh109), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh109), 1); + + TestVpiHandle vh100 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_c[7:0]", NULL); + CHECK_RESULT_NZ(vh100); + CHECK_RESULT(vpi_get(vpiType, vh100), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh100), 8); + + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port[7:0]", NULL)); + + CHECK_RESULT_Z( + vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.no_such_member", NULL)); + } + + // Synthetic member vars preserve type-derived flags from the member dtype + { + TestVpiHandle vh114 = VPI_HANDLE("member_flags_struct_signal"); + CHECK_RESULT_NZ(vh114); + CHECK_RESULT(vpi_get(vpiType, vh114), vpiStructVar); + + TestVpiHandle vh115 = vpi_handle_by_name((PLI_BYTE8*)"unsigned_member", vh114); + CHECK_RESULT_NZ(vh115); + CHECK_RESULT(vpi_get(vpiType, vh115), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh115), 7); + CHECK_RESULT(vpi_get(vpiSigned, vh115), 0); + + TestVpiHandle vh116 = vpi_handle_by_name((PLI_BYTE8*)"signed_member", vh114); + CHECK_RESULT_NZ(vh116); + CHECK_RESULT(vpi_get(vpiType, vh116), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh116), 7); + CHECK_RESULT(vpi_get(vpiSigned, vh116), 1); + + TestVpiHandle vh117 = vpi_handle_by_name((PLI_BYTE8*)"bit_member", vh114); + CHECK_RESULT_NZ(vh117); + CHECK_RESULT(vpi_get(vpiType, vh117), vpiBitVar); + CHECK_RESULT(vpi_get(vpiSize, vh117), 1); + CHECK_RESULT(vpi_get(vpiSigned, vh117), 0); + } + + // unpacked union port members + { + TestVpiHandle vh110 = VPI_HANDLE("unpacked_union_port"); + CHECK_RESULT_NZ(vh110); + CHECK_RESULT(vpi_get(vpiType, vh110), vpiUnionVar); + CHECK_RESULT(vpi_get(vpiSize, vh110), 0); + CHECK_RESULT(vpi_get(vpiPacked, vh110), 0); + p = vpi_get_str(vpiType, vh110); + CHECK_RESULT_CSTR(p, "vpiUnionVar"); + + TestVpiHandle vh111 = vpi_handle_by_name((PLI_BYTE8*)"union_byte0", vh110); + CHECK_RESULT_NZ(vh111); + CHECK_RESULT(vpi_get(vpiType, vh111), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh111), 8); + + TestVpiHandle vh112 = vpi_iterate(vpiMember, vh110); + CHECK_RESULT_NZ(vh112); + std::set unionMembers; + while (TestVpiHandle member = vpi_scan(vh112)) { + unionMembers.insert(vpi_get_str(vpiName, member)); + } + vh112.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(unionMembers.count("union_byte0"), 1); + CHECK_RESULT(unionMembers.count("union_byte1"), 1); + } + + // nested unpacked struct port members + { + TestVpiHandle vh120 = VPI_HANDLE("nested_struct_port"); + CHECK_RESULT_NZ(vh120); + CHECK_RESULT(vpi_get(vpiType, vh120), vpiStructVar); + + // The nested struct member is itself a struct + TestVpiHandle vh121 = vpi_handle_by_name((PLI_BYTE8*)"outer_inner", vh120); + CHECK_RESULT_NZ(vh121); + CHECK_RESULT(vpi_get(vpiType, vh121), vpiStructVar); + CHECK_RESULT(vpi_get(vpiSize, vh121), 0); + + // Leaf reachable via the full hierarchical name + TestVpiHandle vh122 + = vpi_handle_by_name((PLI_BYTE8*)"t.nested_struct_port.outer_inner.inner_x", NULL); + CHECK_RESULT_NZ(vh122); + CHECK_RESULT(vpi_get(vpiType, vh122), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh122), 4); + + // Leaf reachable relative to the intermediate struct handle + TestVpiHandle vh123 = vpi_handle_by_name((PLI_BYTE8*)"inner_y", vh121); + CHECK_RESULT_NZ(vh123); + CHECK_RESULT(vpi_get(vpiType, vh123), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh123), 4); + + // Iterating the outer struct yields direct members only, not grandchildren + TestVpiHandle vh124 = vpi_iterate(vpiMember, vh120); + CHECK_RESULT_NZ(vh124); + std::set nestedMembers; + while (TestVpiHandle member = vpi_scan(vh124)) { + nestedMembers.insert(vpi_get_str(vpiName, member)); + } + vh124.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(nestedMembers.count("outer_a"), 1); + CHECK_RESULT(nestedMembers.count("outer_inner"), 1); + CHECK_RESULT(nestedMembers.count("inner_x"), 0); + + TestVpiHandle vh125 = vpi_handle_by_name((PLI_BYTE8*)"t.sub.subsig1", NULL); + CHECK_RESULT_NZ(vh125); + CHECK_RESULT(vpi_get(vpiType, vh125), vpiReg); + + // Write through a leaf of the nested struct + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xa; + vpi_put_value(vh122, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh122, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xa); + } + + // unpacked struct port with multidimensional packed-array members + { + TestVpiHandle vh130 = VPI_HANDLE("struct_with_packed_arrays_port"); + CHECK_RESULT_NZ(vh130); + CHECK_RESULT(vpi_get(vpiType, vh130), vpiStructVar); + + TestVpiHandle vh131 = vpi_handle_by_name((PLI_BYTE8*)"packed_matrix", vh130); + CHECK_RESULT_NZ(vh131); + CHECK_RESULT(vpi_get(vpiType, vh131), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh131), 512); + + TestVpiHandle vh132 = vpi_handle_by_index(vh131, 2); + CHECK_RESULT_NZ(vh132); + CHECK_RESULT(vpi_get(vpiSize, vh132), 32); + + TestVpiHandle vh133 = vpi_handle_by_index(vh132, 2); + CHECK_RESULT_NZ(vh133); + CHECK_RESULT(vpi_get(vpiSize, vh133), 8); + + TestVpiHandle vh134 = vpi_handle_by_name((PLI_BYTE8*)"reverse_matrix", vh130); + CHECK_RESULT_NZ(vh134); + CHECK_RESULT(vpi_get(vpiType, vh134), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh134), 128); + + TestVpiHandle vh135 = vpi_handle_by_index(vh134, -2); + CHECK_RESULT_NZ(vh135); + CHECK_RESULT(vpi_get(vpiSize, vh135), 8); + } + + // port array of unpacked structs + { + TestVpiHandle vh140 = VPI_HANDLE("struct_array_port"); + CHECK_RESULT_NZ(vh140); + CHECK_RESULT(vpi_get(vpiType, vh140), vpiRegArray); + + TestVpiHandle vh141 = vpi_handle_by_index(vh140, 0); + CHECK_RESULT_NZ(vh141); + CHECK_RESULT(vpi_get(vpiType, vh141), vpiStructVar); + + TestVpiHandle vh142 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh141); + CHECK_RESULT_NZ(vh142); + CHECK_RESULT(vpi_get(vpiType, vh142), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh142), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x6789; + vpi_put_value(vh142, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh142, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6789); + + TestVpiHandle vh144 = vpi_handle_by_index(vh140, 1); + CHECK_RESULT_NZ(vh144); + TestVpiHandle vh145 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh144); + CHECK_RESULT_NZ(vh145); + putValue.value.integer = 0x2468; + vpi_put_value(vh145, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh145, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x2468); + vpi_get_value(vh142, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6789); + + TestVpiHandle vh146 + = vpi_handle_by_name((PLI_BYTE8*)"struct_array_port[1].member_c", nullptr); + CHECK_RESULT_NZ(vh146); + vpi_get_value(vh146, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x2468); + + TestVpiHandle vh143 = vpi_iterate(vpiMember, vh141); + CHECK_RESULT_NZ(vh143); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh143)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh143.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + } + + // multidimensional port array of unpacked structs + { + TestVpiHandle vh150 = VPI_HANDLE("struct_matrix_port"); + CHECK_RESULT_NZ(vh150); + CHECK_RESULT(vpi_get(vpiType, vh150), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh150), 6); + + TestVpiHandle vh151 = vpi_handle_by_index(vh150, 1); + CHECK_RESULT_NZ(vh151); + CHECK_RESULT(vpi_get(vpiType, vh151), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh151), 3); + + TestVpiHandle vh152 = vpi_handle_by_index(vh151, 2); + CHECK_RESULT_NZ(vh152); + CHECK_RESULT(vpi_get(vpiType, vh152), vpiStructVar); + + TestVpiHandle vh153 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh152); + CHECK_RESULT_NZ(vh153); + CHECK_RESULT(vpi_get(vpiType, vh153), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh153), 16); + p = vpi_get_str(vpiName, vh153); + CHECK_RESULT_CSTR(p, "member_c"); + p = vpi_get_str(vpiFullName, vh153); + { + const std::string expectedFullName + = std::string{vpi_get_str(vpiFullName, vh152)} + ".member_c"; + CHECK_RESULT_CSTR(p, expectedFullName.c_str()); + } + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x1357; + vpi_put_value(vh153, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh153, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x1357); + + TestVpiHandle vh154 + = vpi_handle_by_name((PLI_BYTE8*)"struct_matrix_port[1][2].member_c", nullptr); + CHECK_RESULT_NZ(vh154); + vpi_get_value(vh154, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x1357); + } + + // unpacked struct net port members + { + TestVpiHandle vh190 = VPI_HANDLE("wire_unpacked_struct_port"); + CHECK_RESULT_NZ(vh190); + CHECK_RESULT(vpi_get(vpiType, vh190), vpiStructNet); + CHECK_RESULT(vpi_get(vpiPacked, vh190), 0); + p = vpi_get_str(vpiType, vh190); + CHECK_RESULT_CSTR(p, "vpiStructNet"); + + TestVpiHandle vh191 = vpi_handle_by_name((PLI_BYTE8*)"member_a", vh190); + CHECK_RESULT_NZ(vh191); + CHECK_RESULT(vpi_get(vpiType, vh191), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh191), 7); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x25; + vpi_put_value(vh191, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh191, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x25); + + TestVpiHandle vh192 = vpi_iterate(vpiMember, vh190); + CHECK_RESULT_NZ(vh192); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh192)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh192.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + } + + // net array of unpacked structs + { + TestVpiHandle vh193 = VPI_HANDLE("wire_struct_array_port"); + CHECK_RESULT_NZ(vh193); + CHECK_RESULT(vpi_get(vpiType, vh193), vpiNetArray); + + TestVpiHandle vh194 = vpi_handle_by_index(vh193, 0); + CHECK_RESULT_NZ(vh194); + CHECK_RESULT(vpi_get(vpiType, vh194), vpiStructNet); + CHECK_RESULT(vpi_get(vpiPacked, vh194), 0); + + TestVpiHandle vh195 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh194); + CHECK_RESULT_NZ(vh195); + CHECK_RESULT(vpi_get(vpiType, vh195), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh195), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x579b; + vpi_put_value(vh195, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh195, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x579b); + } + + // non-port array of unpacked structs + { + TestVpiHandle vh160 = VPI_HANDLE("struct_array_signal"); + CHECK_RESULT_NZ(vh160); + CHECK_RESULT(vpi_get(vpiType, vh160), vpiRegArray); + + TestVpiHandle vh161 = vpi_handle_by_index(vh160, 1); + CHECK_RESULT_NZ(vh161); + CHECK_RESULT(vpi_get(vpiType, vh161), vpiStructVar); + + TestVpiHandle vh162 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh161); + CHECK_RESULT_NZ(vh162); + CHECK_RESULT(vpi_get(vpiType, vh162), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh162), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xace0; + vpi_put_value(vh162, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh163 = vpi_handle_by_name( + const_cast(TestSimulator::rooted("struct_array_signal[1].member_c")), + nullptr); + CHECK_RESULT_NZ(vh163); + vpi_get_value(vh163, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xace0); + } + + // array of unpacked structs with unpacked-array members + { + TestVpiHandle vh170 = VPI_HANDLE("parent_struct_array"); + CHECK_RESULT_NZ(vh170); + CHECK_RESULT(vpi_get(vpiType, vh170), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh170), 2); + + TestVpiHandle vh171 = vpi_handle_by_index(vh170, 0); + CHECK_RESULT_NZ(vh171); + CHECK_RESULT(vpi_get(vpiType, vh171), vpiStructVar); + + TestVpiHandle vh172 = vpi_handle_by_index(vh170, 1); + CHECK_RESULT_NZ(vh172); + CHECK_RESULT(vpi_get(vpiType, vh172), vpiStructVar); + + TestVpiHandle vh173 = vpi_handle_by_name((PLI_BYTE8*)"tail_array", vh171); + CHECK_RESULT_NZ(vh173); + CHECK_RESULT(vpi_get(vpiType, vh173), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh173), 4); + + TestVpiHandle vh174 = vpi_handle_by_index(vh173, 3); + CHECK_RESULT_NZ(vh174); + CHECK_RESULT(vpi_get(vpiType, vh174), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh174), 8); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xaa; + vpi_put_value(vh174, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh17a = vpi_handle_by_name((PLI_BYTE8*)"trailing_children", vh171); + CHECK_RESULT_NZ(vh17a); + CHECK_RESULT(vpi_get(vpiType, vh17a), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh17a), 2); + + TestVpiHandle vh17b = vpi_handle_by_index(vh17a, 2); + CHECK_RESULT_NZ(vh17b); + CHECK_RESULT(vpi_get(vpiType, vh17b), vpiStructVar); + + TestVpiHandle vh17c = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh17b); + CHECK_RESULT_NZ(vh17c); + CHECK_RESULT(vpi_get(vpiType, vh17c), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh17c), 8); + putValue.value.integer = 0x11; + vpi_put_value(vh17c, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh175 = vpi_handle_by_name((PLI_BYTE8*)"scalar", vh172); + CHECK_RESULT_NZ(vh175); + CHECK_RESULT(vpi_get(vpiType, vh175), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh175), 16); + putValue.value.integer = 0x55cc; + vpi_put_value(vh175, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh175, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x55cc); + + vpi_get_value(vh174, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xaa); + vpi_get_value(vh17c, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x11); + + TestVpiHandle vh176 = vpi_handle_by_name((PLI_BYTE8*)"children", vh172); + CHECK_RESULT_NZ(vh176); + CHECK_RESULT(vpi_get(vpiType, vh176), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh176), 2); + + TestVpiHandle vh177 = vpi_handle_by_index(vh176, 3); + CHECK_RESULT_NZ(vh177); + CHECK_RESULT(vpi_get(vpiType, vh177), vpiStructVar); + + TestVpiHandle vh178 = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh177); + CHECK_RESULT_NZ(vh178); + CHECK_RESULT(vpi_get(vpiType, vh178), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh178), 8); + putValue.value.integer = 0x5a; + vpi_put_value(vh178, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh178, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x5a); + + TestVpiHandle vh179 + = vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].children[3].child_leaf")), + nullptr); + CHECK_RESULT_NZ(vh179); + vpi_get_value(vh179, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x5a); + + TestVpiHandle vh17d = vpi_handle_by_name((PLI_BYTE8*)"trailing_children", vh172); + CHECK_RESULT_NZ(vh17d); + TestVpiHandle vh17e = vpi_handle_by_index(vh17d, 2); + CHECK_RESULT_NZ(vh17e); + TestVpiHandle vh17f = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh17e); + CHECK_RESULT_NZ(vh17f); + putValue.value.integer = 0x6b; + vpi_put_value(vh17f, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh17f, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6b); + + TestVpiHandle vh17g + = vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].trailing_children[2].child_leaf")), + nullptr); + CHECK_RESULT_NZ(vh17g); + vpi_get_value(vh17g, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6b); + + CHECK_RESULT_Z(vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].children[3:2].child_leaf")), + nullptr)); + } + + // array of unpacked structs with members requiring different C++ alignments + { + TestVpiHandle vh180 = VPI_HANDLE("aligned_struct_array"); + CHECK_RESULT_NZ(vh180); + CHECK_RESULT(vpi_get(vpiType, vh180), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh180), 2); + + TestVpiHandle vh181 = vpi_handle_by_index(vh180, 1); + CHECK_RESULT_NZ(vh181); + CHECK_RESULT(vpi_get(vpiType, vh181), vpiStructVar); + + TestVpiHandle vh182 = vpi_handle_by_name((PLI_BYTE8*)"word_member", vh181); + CHECK_RESULT_NZ(vh182); + CHECK_RESULT(vpi_get(vpiType, vh182), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh182), 32); + + TestVpiHandle vh183 = vpi_handle_by_name((PLI_BYTE8*)"quad_member", vh181); + CHECK_RESULT_NZ(vh183); + CHECK_RESULT(vpi_get(vpiType, vh183), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh183), 64); + + TestVpiHandle vh184 = vpi_handle_by_name((PLI_BYTE8*)"real_member", vh181); + CHECK_RESULT_NZ(vh184); + CHECK_RESULT(vpi_get(vpiType, vh184), vpiRealVar); + + TestVpiHandle vh185 = vpi_handle_by_name((PLI_BYTE8*)"string_member", vh181); + CHECK_RESULT_NZ(vh185); + CHECK_RESULT(vpi_get(vpiType, vh185), vpiStringVar); + + TestVpiHandle vh186 = vpi_handle_by_name((PLI_BYTE8*)"nested_member", vh181); + CHECK_RESULT_NZ(vh186); + CHECK_RESULT(vpi_get(vpiType, vh186), vpiStructVar); + + TestVpiHandle vh187 = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh186); + CHECK_RESULT_NZ(vh187); + CHECK_RESULT(vpi_get(vpiType, vh187), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh187), 8); + } + + // Array stride for a struct whose C++ size needs tail padding after a nested struct + { + TestVpiHandle vh188a = VPI_HANDLE("alignment_stride_array"); + CHECK_RESULT_NZ(vh188a); + CHECK_RESULT(vpi_get(vpiType, vh188a), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh188a), 2); + + TestVpiHandle vh188b = vpi_handle_by_index(vh188a, 1); + CHECK_RESULT_NZ(vh188b); + CHECK_RESULT(vpi_get(vpiType, vh188b), vpiStructVar); + + TestVpiHandle vh188c = vpi_handle_by_name((PLI_BYTE8*)"tail", vh188b); + CHECK_RESULT_NZ(vh188c); + CHECK_RESULT(vpi_get(vpiType, vh188c), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh188c), 8); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xc3; + vpi_put_value(vh188c, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh188c, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xc3); + } + + TestVpiHandle vh188 = VPI_HANDLE("nested_struct_port"); + CHECK_RESULT_NZ(vh188); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"outer_inner.no_such", vh188)); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.\\no.such escaped.member", nullptr)); + + // Unpacked struct member with an escaped identifier containing a dot + { + TestVpiHandle vh200 = VPI_HANDLE("escaped_member_struct_signal"); + CHECK_RESULT_NZ(vh200); + CHECK_RESULT(vpi_get(vpiType, vh200), vpiStructVar); + + TestVpiHandle vh201 = vpi_handle_by_name((PLI_BYTE8*)"\\a.b ", vh200); + CHECK_RESULT_NZ(vh201); + CHECK_RESULT(vpi_get(vpiType, vh201), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh201), 8); + + TestVpiHandle vh202 + = vpi_handle_by_name((PLI_BYTE8*)"t.escaped_member_struct_signal.\\a.b ", nullptr); + CHECK_RESULT_NZ(vh202); + CHECK_RESULT(vpi_get(vpiType, vh202), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh202), 8); + + TestVpiHandle vh203 = vpi_iterate(vpiMember, vh200); + CHECK_RESULT_NZ(vh203); + std::set escapedMembers; + while (TestVpiHandle member = vpi_scan(vh203)) { + escapedMembers.insert(vpi_get_str(vpiName, member)); + } + vh203.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(escapedMembers.count("\\a.b "), 1); + CHECK_RESULT(escapedMembers.count("plain_after"), 1); + } + + return 0; +} + int _mon_check_var() { TestVpiHandle vh1 = VPI_HANDLE("onebit"); CHECK_RESULT_NZ(vh1); @@ -1543,6 +2165,9 @@ extern "C" int mon_check() { printf("-mon_check()\n"); #endif +#if !defined(T_VPI_VAR2) && !defined(T_VPI_VAR3) + if (int status = _mon_check_unpacked_struct_members()) return status; +#endif if (int status = _mon_check_mcd()) return status; if (int status = _mon_check_callbacks()) return status; if (int status = _mon_check_value_callbacks()) return status; @@ -1607,6 +2232,7 @@ int main(int argc, char** argv) { uint64_t sim_time = 1100; contextp->debug(0); contextp->commandArgs(argc, argv); + VerilatedVpi::selfTest(); const std::unique_ptr topp{new VM_PREFIX{contextp.get(), // Note null name - we're flattening it out diff --git a/test_regress/t/t_vpi_var.v b/test_regress/t/t_vpi_var.v index e115a1c4d..ea66b0712 100644 --- a/test_regress/t/t_vpi_var.v +++ b/test_regress/t/t_vpi_var.v @@ -16,7 +16,9 @@ module t (/*AUTOARG*/ // Outputs x, // Inputs - clk, a + clk, a, unpacked_struct_port, unpacked_union_port, nested_struct_port, + struct_array_port, struct_matrix_port, struct_with_packed_arrays_port, + wire_unpacked_struct_port, wire_struct_array_port ); `ifdef VERILATOR @@ -54,6 +56,99 @@ extern "C" int mon_check(); // verilator lint_on ASCRANGE reg unpacked_only[7:0]; + typedef struct { + logic [6:0] member_a; + logic member_b; + logic [15:0] member_c; + } unpacked_struct_t; + + input unpacked_struct_t unpacked_struct_port /*verilator public_flat_rw*/; + input wire unpacked_struct_t wire_unpacked_struct_port /*verilator public_flat_rw*/; + + typedef union { + logic [7:0] union_byte0; + logic [7:0] union_byte1; + } unpacked_union_t; + + input unpacked_union_t unpacked_union_port /*verilator public_flat_rw*/; + + typedef struct { + logic [3:0] inner_x; + logic [3:0] inner_y; + } inner_struct_t; + + typedef struct { + logic [7:0] outer_a; + inner_struct_t outer_inner; + } nested_struct_t; + + input nested_struct_t nested_struct_port /*verilator public_flat_rw*/; + + input unpacked_struct_t struct_array_port [1:0] /*verilator public_flat_rw*/; + input unpacked_struct_t struct_matrix_port [1:0][2:0] /*verilator public_flat_rw*/; + input wire unpacked_struct_t wire_struct_array_port [1:0] /*verilator public_flat_rw*/; + unpacked_struct_t struct_array_signal [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [6:0] unsigned_member; + logic signed [6:0] signed_member; + bit bit_member; + } member_flags_struct_t; + + member_flags_struct_t member_flags_struct_signal /*verilator public_flat_rw*/; + + typedef struct { + logic [7:0] child_leaf; + } child_struct_t; + + typedef struct { + logic [15:0] scalar; + child_struct_t children [3:2]; + logic [7:0] tail_array [2:5]; + child_struct_t trailing_children [3:2]; + } parent_struct_t; + + parent_struct_t parent_struct_array [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [7:0] \a.b ; + logic [7:0] plain_after; + } escaped_member_struct_t; + + escaped_member_struct_t escaped_member_struct_signal /*verilator public_flat_rw*/; + + typedef struct { + logic [31:0] word_member; + logic [63:0] quad_member; + real real_member; + string string_member; + child_struct_t nested_member; + } aligned_struct_t; + + aligned_struct_t aligned_struct_array [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [63:0] quad_leaf; + logic [7:0] byte_tail; + } stride_inner_struct_t; + + typedef struct { + logic [7:0] lead; + stride_inner_struct_t nested; + logic [7:0] tail; + } stride_outer_struct_t; + + stride_outer_struct_t alignment_stride_array [1:0] /*verilator public_flat_rw*/; + + // verilator lint_off ASCRANGE + typedef struct { + logic [0:15][0:3][7:0] packed_matrix; + logic [8:-7][3:-4] reverse_matrix; + } struct_with_packed_arrays_t; + // verilator lint_on ASCRANGE + + input struct_with_packed_arrays_t struct_with_packed_arrays_port /*verilator public_flat_rw*/; + reg [7:0] text_byte /*verilator public_flat_rw @(posedge clk) */; reg [15:0] text_half /*verilator public_flat_rw @(posedge clk) */; reg [31:0] text_word /*verilator public_flat_rw @(posedge clk) */; @@ -166,6 +261,7 @@ extern "C" int mon_check(); if (text != "lorem ipsum") $stop; if (str1 != "something a lot longer than hello") $stop; if (real1 > 123456.7895 || real1 < 123456.7885 ) $stop; + if (alignment_stride_array[1].tail != 8'hc3) $stop; end always @(posedge clk) begin diff --git a/test_regress/t/t_wrapper_context__top0.dat.out b/test_regress/t/t_wrapper_context__top0.dat.out index 07d4800da..ae165bca9 100644 --- a/test_regress/t/t_wrapper_context__top0.dat.out +++ b/test_regress/t/t_wrapper_context__top0.dat.out @@ -139,12 +139,12 @@ C 'ft/t_wrapper_context.vl21n3tlinepagev_line/topoblockS21-24,28,3 C 'ft/t_wrapper_context.vl35n3tlinepagev_line/topoblockS35htop0.top' 11 C 'ft/t_wrapper_context.vl36n5tbranchpagev_branch/topoifS36htop0.top' 1 C 'ft/t_wrapper_context.vl36n6tbranchpagev_branch/topoelseS37htop0.top' 10 -C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop0.top' 34 +C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop0.top' 13 C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop0.top' 0 -C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop0.top' 34 +C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop0.top' 13 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop0.top' 0 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==1 && stop==1) => 1htop0.top' 0 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo(stop==0) => 0htop0.top' 0 C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop0.top' 0 C 'ft/t_wrapper_context.vl48n7tbranchpagev_branch/topoifS48-50htop0.top' 1 -C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop0.top' 33 +C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop0.top' 12 diff --git a/test_regress/t/t_wrapper_context__top1.dat.out b/test_regress/t/t_wrapper_context__top1.dat.out index 8ef66f3d0..073c8c4b5 100644 --- a/test_regress/t/t_wrapper_context__top1.dat.out +++ b/test_regress/t/t_wrapper_context__top1.dat.out @@ -139,12 +139,12 @@ C 'ft/t_wrapper_context.vl21n3tlinepagev_line/topoblockS21-24,28,3 C 'ft/t_wrapper_context.vl35n3tlinepagev_line/topoblockS35htop1.top' 6 C 'ft/t_wrapper_context.vl36n5tbranchpagev_branch/topoifS36htop1.top' 1 C 'ft/t_wrapper_context.vl36n6tbranchpagev_branch/topoelseS37htop1.top' 5 -C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop1.top' 19 -C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop1.top' 19 +C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop1.top' 8 +C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop1.top' 8 C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop1.top' 0 -C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop1.top' 18 +C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop1.top' 7 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==1 && stop==1) => 1htop1.top' 1 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo(stop==0) => 0htop1.top' 0 -C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop1.top' 18 +C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop1.top' 7 C 'ft/t_wrapper_context.vl48n7tbranchpagev_branch/topoifS48-50htop1.top' 0 C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop1.top' 0 diff --git a/test_regress/t/t_x_rand_mt_stability.out b/test_regress/t/t_x_rand_mt_stability.out index 30d041b29..cf0f962d9 100644 --- a/test_regress/t/t_x_rand_mt_stability.out +++ b/test_regress/t/t_x_rand_mt_stability.out @@ -3,8 +3,8 @@ x_assigned (initial) = 0x00000000 uninitialized2 = 0xdf5768de big = 0x355d75711c3bf0ca3e65805db585c43beae34b336b9dbe44a2d040cbe101a665 random_init = 0x5fa24450 -top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 +top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d rand = 0x5fa24450 diff --git a/test_regress/t/t_x_rand_mt_stability_add.out b/test_regress/t/t_x_rand_mt_stability_add.out index 30d041b29..cf0f962d9 100644 --- a/test_regress/t/t_x_rand_mt_stability_add.out +++ b/test_regress/t/t_x_rand_mt_stability_add.out @@ -3,8 +3,8 @@ x_assigned (initial) = 0x00000000 uninitialized2 = 0xdf5768de big = 0x355d75711c3bf0ca3e65805db585c43beae34b336b9dbe44a2d040cbe101a665 random_init = 0x5fa24450 -top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 +top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d rand = 0x5fa24450 diff --git a/test_regress/t/t_x_rand_mt_stability_add_trace.out b/test_regress/t/t_x_rand_mt_stability_add_trace.out index 30d041b29..cf0f962d9 100644 --- a/test_regress/t/t_x_rand_mt_stability_add_trace.out +++ b/test_regress/t/t_x_rand_mt_stability_add_trace.out @@ -3,8 +3,8 @@ x_assigned (initial) = 0x00000000 uninitialized2 = 0xdf5768de big = 0x355d75711c3bf0ca3e65805db585c43beae34b336b9dbe44a2d040cbe101a665 random_init = 0x5fa24450 -top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 +top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d rand = 0x5fa24450 diff --git a/test_regress/t/t_x_rand_mt_stability_trace.out b/test_regress/t/t_x_rand_mt_stability_trace.out index 30d041b29..cf0f962d9 100644 --- a/test_regress/t/t_x_rand_mt_stability_trace.out +++ b/test_regress/t/t_x_rand_mt_stability_trace.out @@ -3,8 +3,8 @@ x_assigned (initial) = 0x00000000 uninitialized2 = 0xdf5768de big = 0x355d75711c3bf0ca3e65805db585c43beae34b336b9dbe44a2d040cbe101a665 random_init = 0x5fa24450 -top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 +top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d rand = 0x5fa24450 diff --git a/test_regress/t/t_x_rand_mt_stability_zeros.out b/test_regress/t/t_x_rand_mt_stability_zeros.out index 2b3a3a882..eaac43644 100644 --- a/test_regress/t/t_x_rand_mt_stability_zeros.out +++ b/test_regress/t/t_x_rand_mt_stability_zeros.out @@ -3,8 +3,8 @@ x_assigned (initial) = 0x00000000 uninitialized2 = 0x00000000 big = 0x0000000000000000000000000000000000000000000000000000000000000000 random_init = 0x5fa24450 -top.t.the_sub_yes_inline_1 no_init 0x0 top.t.the_sub_yes_inline_2 no_init 0x0 +top.t.the_sub_yes_inline_1 no_init 0x0 top.t.the_sub_no_inline_1 no_init 0x0 top.t.the_sub_no_inline_2 no_init 0x0 rand = 0x5fa24450 diff --git a/test_regress/t/t_x_rand_stability.out b/test_regress/t/t_x_rand_stability.out index 1db7b9396..eaa53dd1f 100644 --- a/test_regress/t/t_x_rand_stability.out +++ b/test_regress/t/t_x_rand_stability.out @@ -3,8 +3,8 @@ x_assigned (initial) = 0x00000000 uninitialized2 = 0xdf5768de big = 0x355d75711c3bf0ca3e65805db585c43beae34b336b9dbe44a2d040cbe101a665 random_init = 0x5fa24450 -top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 +top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d rand = 0x24800459 diff --git a/test_regress/t/t_x_rand_stability_add.out b/test_regress/t/t_x_rand_stability_add.out index 1db7b9396..eaa53dd1f 100644 --- a/test_regress/t/t_x_rand_stability_add.out +++ b/test_regress/t/t_x_rand_stability_add.out @@ -3,8 +3,8 @@ x_assigned (initial) = 0x00000000 uninitialized2 = 0xdf5768de big = 0x355d75711c3bf0ca3e65805db585c43beae34b336b9dbe44a2d040cbe101a665 random_init = 0x5fa24450 -top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 +top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d rand = 0x24800459 diff --git a/test_regress/t/t_x_rand_stability_add_trace.out b/test_regress/t/t_x_rand_stability_add_trace.out index 1db7b9396..eaa53dd1f 100644 --- a/test_regress/t/t_x_rand_stability_add_trace.out +++ b/test_regress/t/t_x_rand_stability_add_trace.out @@ -3,8 +3,8 @@ x_assigned (initial) = 0x00000000 uninitialized2 = 0xdf5768de big = 0x355d75711c3bf0ca3e65805db585c43beae34b336b9dbe44a2d040cbe101a665 random_init = 0x5fa24450 -top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 +top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d rand = 0x24800459 diff --git a/test_regress/t/t_x_rand_stability_trace.out b/test_regress/t/t_x_rand_stability_trace.out index 1db7b9396..eaa53dd1f 100644 --- a/test_regress/t/t_x_rand_stability_trace.out +++ b/test_regress/t/t_x_rand_stability_trace.out @@ -3,8 +3,8 @@ x_assigned (initial) = 0x00000000 uninitialized2 = 0xdf5768de big = 0x355d75711c3bf0ca3e65805db585c43beae34b336b9dbe44a2d040cbe101a665 random_init = 0x5fa24450 -top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 +top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d rand = 0x24800459 diff --git a/test_regress/t/t_x_rand_stability_zeros.out b/test_regress/t/t_x_rand_stability_zeros.out index f0fc3e106..c6dc6b6ab 100644 --- a/test_regress/t/t_x_rand_stability_zeros.out +++ b/test_regress/t/t_x_rand_stability_zeros.out @@ -3,8 +3,8 @@ x_assigned (initial) = 0x00000000 uninitialized2 = 0x00000000 big = 0x0000000000000000000000000000000000000000000000000000000000000000 random_init = 0x5fa24450 -top.t.the_sub_yes_inline_1 no_init 0x0 top.t.the_sub_yes_inline_2 no_init 0x0 +top.t.the_sub_yes_inline_1 no_init 0x0 top.t.the_sub_no_inline_1 no_init 0x0 top.t.the_sub_no_inline_2 no_init 0x0 rand = 0x24800459