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 7817a3bf6..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: @@ -29,272 +30,141 @@ concurrency: jobs: build-2604-gcc: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + name: Build | 26.04 | gcc uses: ./.github/workflows/reusable-build.yml with: + cc: gcc + runs-on: ubuntu-26.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-26.04, cc: gcc, asan: 0} build-2604-clang: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + 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 }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-26.04, cc: clang, asan: 1} build-2404-gcc: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + 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: 0} 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-osx-gcc: - 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: 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo - - name: Cache $CCACHE_DIR + - 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - path: ${{ github.workspace }}/repo/verilator.zip - name: verilator-win.zip test-2604-gcc: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + 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 }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} + cc: gcc + runs-on: ubuntu-26.04 suite: ${{ matrix.suite }} - dev-gcov: 0 strategy: fail-fast: false matrix: - include: - # Ubuntu 26.04 gcc - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-0} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-1} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-2} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-3} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-0} - - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-1} - - {os: ubuntu-26.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-2604-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + 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 }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} + cc: clang + runs-on: ubuntu-26.04 suite: ${{ matrix.suite }} - dev-gcov: 0 strategy: fail-fast: false matrix: - include: - # Ubuntu 26.04 clang - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-0} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-1} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-2} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-3} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-0} - - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-1} - - {os: ubuntu-26.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-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} + suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2] lint-py: name: Lint Python diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index cc6b463b0..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: @@ -121,7 +119,7 @@ jobs: - 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 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f41c513a5..a059439e2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -54,7 +54,7 @@ jobs: - name: Docker meta id: docker_meta - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # 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@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 with: buildkitd-flags: --debug - name: Login to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # 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@f9f3042f7e2789586610d6e8b85c8f03e5195baf # 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 5a604fa73..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@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 59048157c..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,6 +37,10 @@ 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: @@ -58,9 +65,16 @@ 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 + 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: diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 87a4052d7..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,26 +58,28 @@ 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@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 + if: ${{ env.CCACHE_DISABLE != '1' }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache @@ -75,25 +87,43 @@ jobs: 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 + - 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 f37027aa7..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 @@ -31,19 +23,18 @@ jobs: 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 b38ed62cf..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@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 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 ab66647db..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: @@ -82,14 +82,6 @@ jobs: working-directory: rtlmeter run: make venv - - name: Use saved ccache - if: ${{ env.CCACHE_DISABLE == 0 }} - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # 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 ######################################################################## @@ -101,7 +93,7 @@ jobs: - 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 @@ -164,7 +156,7 @@ jobs: - 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 diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index ffec26b84..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,15 +51,8 @@ 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 @@ -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@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # 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 }} @@ -104,6 +116,16 @@ jobs: 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 1848d4ec8..c0f7506cd 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -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: 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/configure.ac b/configure.ac index 6376593ad..893bf91d0 100644 --- a/configure.ac +++ b/configure.ac @@ -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) diff --git a/python-dev-requirements.txt b/python-dev-requirements.txt index 3e33332cc..e90349d2c 100644 --- a/python-dev-requirements.txt +++ b/python-dev-requirements.txt @@ -33,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/V3Assert.cpp b/src/V3Assert.cpp index ee18b24b4..cbf462260 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -90,6 +90,58 @@ 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) { @@ -97,6 +149,11 @@ void V3AssertCommon::collectDefaultDisable(AstNetlist* 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, @@ -546,14 +603,19 @@ class AssertVisitor final : public VNVisitor { bool selfDestruct = false; bool passspGated = false; - if (const AstCover* const snodep = VN_CAST(nodep, Cover)) { + 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) { @@ -597,7 +659,8 @@ class AssertVisitor final : public VNVisitor { 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)) { + if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr) + && !seqEvent) { passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(), /*vacuous=*/false); } @@ -607,7 +670,7 @@ class AssertVisitor final : public VNVisitor { 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); @@ -1153,12 +1216,11 @@ 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); } diff --git a/src/V3Assert.h b/src/V3Assert.h index f144688b6..3bda2528e 100644 --- a/src/V3Assert.h +++ b/src/V3Assert.h @@ -27,6 +27,7 @@ class V3AssertCommon final { public: static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED; + static void lowerSequenceEvents(AstNetlist* nodep) VL_MT_DISABLED; }; class V3Assert final { diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index 04d7fb7fc..149885634 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -291,6 +291,8 @@ class SvaNfaBuilder final { // 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; @@ -298,6 +300,15 @@ class SvaNfaBuilder final { 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; // AND all throughout conditions (supports nesting) @@ -465,8 +476,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))}; @@ -578,8 +589,7 @@ class SvaNfaBuilder final { // 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))) { - flp->v3warn(COVERIGN, - "Ignoring unsupported: cover sequence with this ranged cycle delay"); + warnEndpointUnsupported(flp, "this ranged cycle delay"); outErrorEmitted = true; return false; } @@ -885,8 +895,7 @@ class SvaNfaBuilder final { // 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) { - flp->v3warn(COVERIGN, - "Ignoring unsupported: cover sequence with a ranged goto repetition"); + warnEndpointUnsupported(flp, "a ranged goto repetition"); return BuildResult::failWithError(); } @@ -950,8 +959,7 @@ class SvaNfaBuilder final { // 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)) { - flp->v3warn(COVERIGN, - "Ignoring unsupported: cover sequence with a sequence operand of 'or'"); + warnEndpointUnsupported(flp, "a sequence operand of 'or'"); return BuildResult::failWithError(); } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); @@ -1446,11 +1454,12 @@ class SvaNfaBuilder final { public: SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames, - bool isCoverSeq = false) + bool isCoverSeq = false, bool isSeqEvent = false) : m_graph{graph} , m_modp{modp} , m_propTempNames{propTempNames} - , m_isCoverSeq{isCoverSeq} {} + , m_isCoverSeq{isCoverSeq} + , m_isSeqEvent{isSeqEvent} {} // Reset scope between antecedent and consequent: liveness must not leak. void resetScope() { @@ -3054,11 +3063,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()) { @@ -3070,12 +3085,9 @@ class AssertNfaVisitor final : public VNVisitor { if (!senTreep) return; FileLine* const flp = assertp->fileline(); - const bool isCover = VN_IS(assertp, Cover); - AstCover* const coverp = VN_CAST(assertp, Cover); - const bool isCoverSeq = coverp && coverp->isCoverSeq(); SvaGraph graph; - SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq}; + 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); @@ -3117,13 +3129,16 @@ class AssertNfaVisitor final : public VNVisitor { std::vector perMidSrcs; AstNodeExpr* const alwaysTriggerp - = assertOnCond(flp, assertp->userType(), assertp->directive()); + = 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, - assertp->userType(), assertp->directive()); + isSeqEvent ? VAssertType{VAssertType::INTERNAL} : assertp->userType(), + isSeqEvent ? VAssertDirectiveType{VAssertDirectiveType::INTERNAL} + : assertp->directive()); AstSenTree* const perSrcSenTreep = (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr; diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index f45df83f3..d9332ff59 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -1466,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(); diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 25de3ad08..fbbceaec7 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -1612,6 +1612,8 @@ 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, @@ -1622,6 +1624,8 @@ public: 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 aba8a9d0f..26126e5fd 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2241,9 +2241,11 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const { 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 { diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 2ac71c770..5cce373b0 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -137,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 { @@ -163,14 +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_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); @@ -191,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()); @@ -227,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; @@ -261,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); 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/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/V3Coverage.cpp b/src/V3Coverage.cpp index 9c19319d8..9db38cb6e 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -279,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 @@ -294,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 @@ -356,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); } @@ -762,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(); } @@ -776,7 +776,7 @@ 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; @@ -874,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 b1890e001..f243b0c33 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -1727,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/V3DfgBreakCycles.cpp b/src/V3DfgBreakCycles.cpp index 4bab8858d..c1e3b28c5 100644 --- a/src/V3DfgBreakCycles.cpp +++ b/src/V3DfgBreakCycles.cpp @@ -173,6 +173,7 @@ class TraceDriver final : public DfgVisitor { 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 @@ -228,8 +229,7 @@ class TraceDriver final : public DfgVisitor { 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"); @@ -271,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; @@ -285,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, @@ -388,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 @@ -425,46 +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 { - // From a variable - const DfgVarArray* varp = vtxp->fromp()->cast(); - if (!varp) return; - - // If index is not constant, independence was proven only if the 'fromp' is - // independent, so no need to trace that - if (!vtxp->bitp()->is()) { - 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); + // 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; } - // Trace the relevant driver based on the static index - const DfgConst* const idxp = vtxp->bitp()->as(); - 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"); - } - // 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 { @@ -710,7 +758,7 @@ class TraceDriver final : public DfgVisitor { DfgSel* const selp = make(vtxp, m_msb - m_lsb + 1); selp->fromp(resp); selp->lsb(m_lsb); - SET_RESULT(resp); + SET_RESULT(selp); } #undef SET_RESULT @@ -741,6 +789,142 @@ public: } }; +// 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 { @@ -751,7 +935,7 @@ class IndependentBits final : public DfgVisitor { // STATE 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 @@ -761,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; } @@ -784,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)) @@ -810,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; @@ -827,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 @@ -844,81 +1031,62 @@ class IndependentBits final : public DfgVisitor { propagateFromDriver(m, srcp); } - void visit(DfgArraySel* vtxp) override { - // From a variable - const DfgVarArray* varp = vtxp->fromp()->cast(); - if (!varp) return; + void visit(DfgVertexSplice* vtxp) override { + propagateFromDriver(MASK(vtxp), vtxp); // Needed to continue traversal + } - // If index is not constant, independent only if the variable index - // is indenpendent and the array is independent. We don't track arrays, - // so we will assume an array is only independent if it has no drivers - // in the graph. TODO: could check all drivers. - if (!vtxp->bitp()->is()) { - if (MASK(vtxp->bitp()).isEqAllOnes() && !varp->srcp() && !varp->defaultp()) { - MASK(vtxp).setAllBits1(); - } + void visit(DfgUnitArray* vtxp) override { MASK(vtxp).sub().at(0) = MASK(vtxp->srcp()); } + + void visit(DfgArraySel* vtxp) override { + 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; } - // Trace the relevant driver based on the static index - const DfgConst* const idxp = vtxp->bitp()->as(); - // 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; - } - // 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'); } @@ -928,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 { @@ -1004,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; @@ -1020,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); } } @@ -1036,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; @@ -1048,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); } } @@ -1064,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; @@ -1076,23 +1231,21 @@ 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()).isEqAllOnes() && MASK(vtxp->matchp()).isEqAllOnes()) { // - MASK(vtxp).setAllBits1(); - } + if (MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->matchp()).isOnes()) { MASK(vtxp).setOnes(); } } #undef MASK @@ -1102,11 +1255,6 @@ class IndependentBits final : public DfgVisitor { vtx.foreachSink([&](DfgVertex& sink) { // Ignore if sink is not part of an SCC, already has all bits marked independent if (!m_sccInfo.get(sink)) return false; - // If a vertex is not handled directly, recursively enqueue its sinks instead - if (!handledDirectly(sink)) { - enqueueSinks(sink); - return false; - } // Otherwise just enqueue it VertexState& state = m_vtxp2State.at(&sink); if (!state.m_isOnWorkList) { @@ -1169,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_sccInfo.get(*vtxp)) continue; - mask(*vtxp).setAllBits1(); + mask(*vtxp).setOnes(); } for (DfgVertex* const vtxp : rpoEnumeration) { - if (!handledDirectly(*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 @@ -1191,13 +1336,11 @@ class IndependentBits final : public DfgVisitor { m_vtxp2State.at(currp).m_isOnWorkList = false; // Should not enqueue vertices that are not in an SCC UASSERT_OBJ(m_sccInfo.get(*currp), currp, "Vertex should be in an SCC"); - // Should only enqueue packed vertices - UASSERT_OBJ(handledDirectly(*currp), currp, "Vertex should be handled directly"); // 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()); @@ -1205,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"); } } } @@ -1228,8 +1368,8 @@ 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 SccInfo& sccInfo) { + static std::unordered_map apply(DfgGraph& dfg, + const SccInfo& sccInfo) { return std::move(IndependentBits{dfg, sccInfo}.m_vtxp2Mask); } }; @@ -1239,21 +1379,21 @@ class FixUp final { SccInfo& m_sccInfo; // The SccInfo instance TraceDriver m_traceDriver{m_dfg, m_sccInfo}; // The independent bits map - const std::unordered_map m_independentBits + const std::unordered_map m_independentBits = IndependentBits::apply(m_dfg, m_sccInfo); - size_t m_nImprovements = 0; // Number of improvements mde + 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; @@ -1306,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 @@ -1328,14 +1466,14 @@ 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}; diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index afca00589..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; diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp index c0f5bdc99..a23b19c4b 100644 --- a/src/V3DfgSynthesize.cpp +++ b/src/V3DfgSynthesize.cpp @@ -2005,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); @@ -2016,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 d932de1a3..dd51a58a9 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -512,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 @@ -538,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/V3EmitV.cpp b/src/V3EmitV.cpp index db0798e38..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); 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/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/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/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 33e2d38b5..0bb78f8e8 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1259,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); @@ -1335,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); @@ -1384,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); @@ -3584,7 +3584,7 @@ class LinkDotResolveVisitor final : public VNVisitor { 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); @@ -3592,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); @@ -3805,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); @@ -3845,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"); @@ -4753,14 +4749,12 @@ 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::") { @@ -4834,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); } @@ -5054,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); { @@ -5509,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)); @@ -5524,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)); @@ -5643,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; @@ -5813,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); @@ -6169,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; @@ -6249,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/V3LinkParse.cpp b/src/V3LinkParse.cpp index 5403d77c2..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); @@ -763,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); @@ -780,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); @@ -797,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/V3Param.cpp b/src/V3Param.cpp index 0cf109211..72c33a6e7 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -2571,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); } } @@ -3241,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/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/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index 5e9e427cd..b39818b36 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -26,6 +26,7 @@ #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT +#include "V3AstUserAllocator.h" #include "V3EmitCBase.h" #include "V3Sched.h" @@ -179,7 +180,8 @@ AstCCall* TimingKit::createReady(AstNetlist* const netlistp) { class AwaitVisitor final : public VNVisitor { // NODE STATE - // AstSenTree::user1() -> bool. Set true if the sentree has been visited. + // AstSenTree::user1() -> bool. Set true if the sentree has been visited. + // AstCFunc::user1() -> AstUser1Allocator. See alocator below const VNUser1InUse m_inuser1; // STATE @@ -193,6 +195,34 @@ class AwaitVisitor final : public VNVisitor { std::set m_processDomains; // Sentrees from the current process // Variables written by suspendable processes std::vector m_writtenBySuspendable; + struct CFuncCache final { + std::set m_processDomains; // What shall be added to m_processDomains + std::vector + m_writtenBySuspendable; // What shall be added to m_writtenBySuspendable + enum State : uint8_t { + UNINITIALIZED = 0, // Not initialized members are empty + VISITING, // Visiting - needed for breaking recursion + INITIALIZED, // Members contains correct values + } m_state // Current state of Cache + = UNINITIALIZED; + }; + // Caches how visiting the function with given value of m_gatherVars changes + // m_processDomains and m_writtenBySuspendable - only accessed from visit(AstCFunc* nodep) + AstUser1Allocator> m_cfuncsCache; + // Count uses of not inlined writes to signals in suspendables + VDouble0 m_notInlinedWritesInSuspendableUsage; + // Set containing information whether an AstCFunc was hit (called) recursively + // This is needed to know whether cache is complete. E.g.: + // A->B->C->B + // Callstack: + // visit(A) -> Ok go to successors + // visit(B) -> Ok go to successors + // visit(C) -> Ok go to successors + // visit(B) -> Already visiting B + // visit(C) -> Cache must not be created since it is not complete - A was not visited + // visit(B) -> Cache may be created since all successors have been transitionally visited + // visit(A) -> Cache may be created since all successors have been transitionally visited + std::array, 2> m_hitVisiting; // METHODS // Add arguments to a resume() call based on arguments in the suspending call @@ -267,6 +297,7 @@ class AwaitVisitor final : public VNVisitor { if (!sentreep->user1SetOnce()) createResumeActive(nodep); if (m_inProcess) m_processDomains.insert(sentreep); } + iterateChildren(nodep); } void visit(AstNodeVarRef* nodep) override { if (m_gatherVars && nodep->access().isWriteOrRW() && !nodep->varp()->ignoreSchedWrite() @@ -274,6 +305,59 @@ class AwaitVisitor final : public VNVisitor { m_writtenBySuspendable.push_back(nodep->varScopep()); } } + void visit(AstNodeCCall* const nodep) override { + iterateChildren(nodep); + // We need to visit bodies of non-inlined functions + iterate(nodep->funcp()); + } + void visit(AstCFunc* nodep) override { + UASSERT_OBJ(!m_gatherVars || m_inProcess, nodep, + "Variables shall be gathered only inside processes"); + // Cache key does not depends on a m_inProcess variable since it is forced to be true + const size_t cacheKey = static_cast(m_gatherVars); + CFuncCache& value = m_cfuncsCache(nodep)[cacheKey]; + { + VL_RESTORER(m_inProcess); + // Force m_inProcess since visiting a tree with !m_inProcess does not change the + // m_writtenBySuspendable and m_processDomains (so it would be a bit of a dry run). + // However, we must visit children since there might be an AstCAwait which is modified + // in its visit() but since the function does not depend on a state of m_inProcess (nor + // anything that may be affected by changing the state of m_inProcess) we may force the + // variable, visit children and gather cache for m_inProcess && !m_gatherVars + m_inProcess = true; + switch (value.m_state) { + case CFuncCache::UNINITIALIZED: { + // Save current state + VL_RESTORER_CLEAR(m_processDomains); + VL_RESTORER_CLEAR(m_writtenBySuspendable); + + // Visit + value.m_state = CFuncCache::VISITING; + iterateChildren(nodep); + m_hitVisiting[cacheKey].erase(nodep); + value.m_state = m_hitVisiting[cacheKey].empty() ? CFuncCache::INITIALIZED + : CFuncCache::UNINITIALIZED; + + // Save a cache + std::swap(m_processDomains, value.m_processDomains); + std::swap(m_writtenBySuspendable, value.m_writtenBySuspendable); + } break; + case CFuncCache::VISITING: + m_hitVisiting[cacheKey].insert(nodep); + return; // Break recursion + case CFuncCache::INITIALIZED: break; + } + } + if (m_inProcess) { + // Add cached values to the visitor state if the m_inProcess was initially true - when + // it is false none of these variable shall be modified + m_writtenBySuspendable.insert(m_writtenBySuspendable.end(), + value.m_writtenBySuspendable.begin(), + value.m_writtenBySuspendable.end()); + m_notInlinedWritesInSuspendableUsage += value.m_writtenBySuspendable.size(); + m_processDomains.insert(value.m_processDomains.begin(), value.m_processDomains.end()); + } + } void visit(AstExprStmt* nodep) override { iterateChildren(nodep); } //-------------------- @@ -289,7 +373,10 @@ public: , m_externalDomains{externalDomains} { iterate(nodep); } - ~AwaitVisitor() override = default; + ~AwaitVisitor() override { + V3Stats::addStat("Scheduling, count of non-inlined signal writes in suspendables", + m_notInlinedWritesInSuspendableUsage); + } }; TimingKit prepareTiming(AstNetlist* const netlistp) { diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 0db8d04d3..72e779d1e 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -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 diff --git a/src/V3TraceDecl.cpp b/src/V3TraceDecl.cpp index 1a1ad6b89..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; diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 5bc72076b..226c5138e 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -2191,8 +2191,8 @@ class TristateVisitor final : public TristateBaseVisitor { 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"); @@ -2201,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; 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/V3Width.cpp b/src/V3Width.cpp index 86d688cc7..802fe155d 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -731,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); @@ -7852,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 { @@ -9564,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: 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 b1f0a9af0..b694691dd 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -261,6 +261,7 @@ 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. 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_seq_event_bad.py b/test_regress/t/t_assert_seq_event_bad.py new file mode 100755 index 000000000..f051d14b9 --- /dev/null +++ b/test_regress/t/t_assert_seq_event_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, 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_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 8c0ad2245..5fcf5c7d7 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -1318,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 813d02ea9..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; @@ -443,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_dfg_break_cycles.py b/test_regress/t/t_dfg_break_cycles.py index ec6c6b339..217863f38 100755 --- a/test_regress/t/t_dfg_break_cycles.py +++ b/test_regress/t/t_dfg_break_cycles.py @@ -92,6 +92,7 @@ test.compile(verilator_flags2=[ "--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", diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index b2124b1d6..6114336fe 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -361,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]; 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_property_sexpr_range_delay.v b/test_regress/t/t_property_sexpr_range_delay.v index cd3037d03..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; @@ -64,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)); diff --git a/test_regress/t/t_sched_noninlined_func_suspendable.py b/test_regress/t/t_sched_noninlined_func_suspendable.py new file mode 100755 index 000000000..b6c85ee2d --- /dev/null +++ b/test_regress/t/t_sched_noninlined_func_suspendable.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') + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-dfg']) + +test.execute() + +test.file_grep(test.stats, + r'Scheduling, count of non-inlined signal writes in suspendables\s+(\d+)', 7) + +test.passes() diff --git a/test_regress/t/t_sched_noninlined_func_suspendable.v b/test_regress/t/t_sched_noninlined_func_suspendable.v new file mode 100644 index 000000000..a7e9c8f0d --- /dev/null +++ b/test_regress/t/t_sched_noninlined_func_suspendable.v @@ -0,0 +1,93 @@ +// 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: off + +package pkg; + bit [2:0] y3; +endpackage + +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]); + + bit [2:0] y2; + bit [2:0] z2; + assign z2[0] = 1'b1; + assign z2[1] = !(y2[0]); + assign z2[2] = !(|y2[1:0]); + + import pkg::y3; + bit [2:0] z3; + assign z3[0] = 1'b1; + assign z3[1] = !(y3[0]); + assign z3[2] = !(|y3[1:0]); + + bit [2:0] y4; + bit [2:0] z4; + assign z4[0] = 1'b1; + assign z4[1] = !(y4[0]); + assign z4[2] = !(|y4[1:0]); + class Foo; + function automatic int bar(); + // verilator no_inline_task + y2 = 3'b111; + y3 = 3'b111; + return 1; + endfunction + task run(); + y = 3'b111; + #1; + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + endtask + task a(bit x = 0); + // verilator no_inline_task + y4 = ~y4; + #1; + if (!x) b(!x); + endtask + task b(bit x = 0); + // verilator no_inline_task + if (!x) a(!x); + endtask + endclass + initial begin + static Foo foo = new; + #1; + `checkh(z, 3'b111); + `checkh(z2, 3'b111); + `checkh(z3, 3'b111); + `checkh(z4, 3'b111); + void'(foo.bar()); + #1; + `checkh(z, 3'b111); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + foo.run(); + foo.a(); + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b001); + foo.b(); + `checkh(z, 3'b001); + `checkh(z2, 3'b001); + `checkh(z3, 3'b001); + `checkh(z4, 3'b111); + $write("*-* All Finished *-*\n"); + $finish; + 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_timing_debug2.out b/test_regress/t/t_timing_debug2.out index bff344460..48f633e08 100644 --- a/test_regress/t/t_timing_debug2.out +++ b/test_regress/t/t_timing_debug2.out @@ -78,7 +78,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -110,7 +110,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -171,7 +171,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -247,7 +247,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -278,7 +278,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -339,7 +339,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -463,7 +463,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -498,7 +498,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -581,7 +581,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -676,7 +676,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -711,7 +711,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -794,7 +794,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -831,7 +831,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#} 'act' region trigger index 0 is active: @([event] t.ec.e) --V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -906,7 +906,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -936,7 +936,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -994,7 +994,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1060,7 +1060,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1125,7 +1125,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1152,7 +1152,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1215,7 +1215,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1280,7 +1280,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1320,7 +1320,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate()) +-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1371,7 +1371,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1427,7 +1427,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1486,7 +1486,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1541,7 +1541,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1596,7 +1596,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume @@ -1649,7 +1649,7 @@ -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act --V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec -V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act -V{t#,#}+ Vt_timing_debug2___024root___timing_resume