diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 772fc1c95..d0dfeccb7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -28,6 +28,38 @@ concurrency: jobs: + build-2604-gcc: + name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + uses: ./.github/workflows/reusable-build.yml + with: + sha: ${{ github.sha }} + os: ${{ matrix.os }} + os-name: 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' || '' }} + uses: ./.github/workflows/reusable-build.yml + with: + 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' || '' }} uses: ./.github/workflows/reusable-build.yml @@ -58,7 +90,7 @@ jobs: fail-fast: false matrix: include: - - {os: ubuntu-24.04, cc: clang, asan: 1} + - {os: ubuntu-24.04, cc: clang, asan: 0} build-2204-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} @@ -76,22 +108,6 @@ jobs: include: - {os: ubuntu-22.04, cc: gcc, asan: 0} - build-2204-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: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-22.04, cc: clang, asan: 0} - build-osx-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} uses: ./.github/workflows/reusable-build.yml @@ -138,11 +154,11 @@ jobs: CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_LIMIT_MULTIPLE: 0.95 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo - name: Cache $CCACHE_DIR - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: ${{ env.CCACHE_DIR }} key: msbuild-msvc-cmake @@ -155,11 +171,59 @@ jobs: - name: Zip up repository run: Compress-Archive -LiteralPath install -DestinationPath verilator.zip - name: Upload zip archive - uses: actions/upload-artifact@v7 + 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 }} + 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 }} + 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} + + test-2604-clang: + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ 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 }} + 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} + test-2404-gcc: name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} needs: build-2404-gcc @@ -232,55 +296,6 @@ jobs: - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-1} - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-2} - test-2204-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} - needs: build-2204-clang - uses: ./.github/workflows/reusable-test.yml - with: - archive: ${{ needs.build-2204-clang.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} - suite: ${{ matrix.suite }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - # Ubuntu 22.04 clang, also test relocation - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-0} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-1} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-2} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-3} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-0} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-1} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-2} - lint-py: name: Lint Python uses: ./.github/workflows/reusable-lint-py.yml - - passed: - name: Test suite passed - if: always() - needs: - - build-2404-gcc - - build-2404-clang - - build-2204-gcc - - build-2204-clang - - build-osx-gcc - - build-osx-clang - - build-windows - - test-2404-gcc - - test-2404-clang - - test-2204-gcc - - test-2204-clang - - lint-py - - runs-on: ubuntu-24.04 - - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/contributor.yml b/.github/workflows/contributor.yml index 02549bf76..4fef0fd66 100644 --- a/.github/workflows/contributor.yml +++ b/.github/workflows/contributor.yml @@ -16,5 +16,5 @@ jobs: name: "'docs/CONTRIBUTORS' was signed" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - run: test_regress/t/t_dist_contributors.py diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 439143c85..cc6b463b0 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -74,10 +74,10 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Download code coverage data - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: code-coverage-* path: obj_coverage @@ -90,7 +90,7 @@ jobs: find obj_coverage -type f | paste -sd, | sed "s/^/files=/" >> "$GITHUB_OUTPUT" - name: Upload to codecov.io - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7 with: disable_file_fixes: true disable_search: true @@ -114,7 +114,7 @@ jobs: sudo apt install lcov - name: Download repository archive - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ needs.build.outputs.archive }} path: ${{ github.workspace }} @@ -125,7 +125,7 @@ jobs: ls -lsha - name: Download code coverage data - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: code-coverage-* path: repo/obj_coverage @@ -172,14 +172,14 @@ jobs: fi - name: Upload report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: repo/obj_coverage name: coverage-report - name: Upload notification if: ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: repo/notification name: pr-notification @@ -195,9 +195,9 @@ jobs: # Creating issues requires elevated privilege - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 278a8aeb9..f41c513a5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Extract context variables run: | @@ -54,7 +54,7 @@ jobs: - name: Docker meta id: docker_meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6 with: images: | ${{ vars.DOCKER_HUB_NAMESPACE }}/${{ env.image_name }} @@ -64,21 +64,21 @@ jobs: type=raw,value=latest,enable=${{ inputs.add_latest_tag == true }} - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 with: buildkitd-flags: --debug - name: Login to Docker Hub - uses: docker/login-action@v4 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 with: username: ${{ secrets.DOCKER_HUB_USER }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Build and Push to Docker - uses: docker/build-push-action@v7 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7 if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' with: context: ${{ env.build_context }} diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index eefc544a1..5a604fa73 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -21,7 +21,7 @@ jobs: CI_COMMIT: ${{ github.sha }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Install packages for build diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 4d1523ac2..59048157c 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -38,7 +38,7 @@ jobs: pr-run-ids: ${{ steps.build.outputs.pr-run-ids }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Build pages id: build env: @@ -48,7 +48,7 @@ jobs: ls -lsha tree -L 3 pages - name: Upload pages artifact - uses: actions/upload-pages-artifact@v5 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 with: path: pages @@ -61,7 +61,7 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages - uses: actions/deploy-pages@v5 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 notify: name: Notify @@ -70,13 +70,13 @@ jobs: if: ${{ github.repository == 'verilator/verilator' }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 # Use the Verilator CI app to post the comment - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} permission-actions: write permission-pull-requests: write diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 7138a7cce..5d053cadb 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -61,14 +61,14 @@ jobs: CCACHE_MAXSIZE: 1000M # Per build matrix entry (* 5 = 5000M in total) steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo ref: ${{ inputs.sha }} fetch-depth: ${{ inputs.dev-gcov && '0' || '1' }} # Coverage flow needs full history - name: Cache $CCACHE_DIR - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache with: @@ -93,7 +93,7 @@ jobs: echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT" - name: Upload repository archive - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ github.workspace }}/${{ steps.create-archive.outputs.archive }} name: ${{ steps.create-archive.outputs.archive }} diff --git a/.github/workflows/reusable-lint-py.yml b/.github/workflows/reusable-lint-py.yml index f4ad9b6e8..f37027aa7 100644 --- a/.github/workflows/reusable-lint-py.yml +++ b/.github/workflows/reusable-lint-py.yml @@ -27,7 +27,7 @@ jobs: name: Sub-lint | Python steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo diff --git a/.github/workflows/reusable-rtlmeter-build.yml b/.github/workflows/reusable-rtlmeter-build.yml index 321033a72..cdfee423a 100644 --- a/.github/workflows/reusable-rtlmeter-build.yml +++ b/.github/workflows/reusable-rtlmeter-build.yml @@ -50,7 +50,7 @@ jobs: sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev - name: Use saved ccache - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: ccache key: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }}-${{ github.run_attempt }} @@ -60,7 +60,7 @@ jobs: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }} - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo ref: ${{ inputs.sha }} @@ -89,7 +89,7 @@ jobs: echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT - name: Upload Verilator installation archive - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ steps.create-archive.outputs.archive }} name: ${{ steps.create-archive.outputs.archive }} diff --git a/.github/workflows/reusable-rtlmeter-run.yml b/.github/workflows/reusable-rtlmeter-run.yml index ebbaffef8..81aaad405 100644 --- a/.github/workflows/reusable-rtlmeter-run.yml +++ b/.github/workflows/reusable-rtlmeter-run.yml @@ -73,7 +73,7 @@ jobs: sudo apt install ccache mold libfl-dev libjemalloc-dev libsystemc-dev - name: Checkout RTLMeter - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -84,7 +84,7 @@ jobs: - name: Use saved ccache if: ${{ env.CCACHE_DISABLE == 0 }} - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # 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 }} @@ -95,7 +95,7 @@ jobs: ######################################################################## - name: Download Verilator installation archive - new - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.verilator-archive-new }} @@ -135,7 +135,7 @@ jobs: ./rtlmeter report --steps '*' --metrics '*' ../results-${{ steps.results.outputs.hash }}.json - name: Upload results - new - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: results-${{ steps.results.outputs.hash }}.json name: rtlmeter-${{ inputs.tag }}-results-${{ steps.results.outputs.hash }} @@ -157,7 +157,7 @@ jobs: - name: Download Verilator installation archive - old if: ${{ inputs.verilator-archive-old != '' }} - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.verilator-archive-old }} @@ -198,7 +198,7 @@ jobs: - name: Upload results - old if: ${{ inputs.verilator-archive-old != '' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: reference-${{ steps.results.outputs.hash }}.json name: rtlmeter-${{ inputs.tag }}-reference-${{ steps.results.outputs.hash }} diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index 7f363f2d9..efc9c50fe 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -55,7 +55,7 @@ jobs: steps: - name: Download repository archive - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.archive }} path: ${{ github.workspace }} @@ -67,7 +67,7 @@ jobs: ls -lsha - name: Cache $CCACHE_DIR - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache2 with: @@ -99,7 +99,7 @@ jobs: - name: Upload code coverage data if: ${{ inputs.dev-gcov }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ github.workspace }}/repo/obj_coverage/verilator-${{ inputs.suite }}.info name: code-coverage-${{ inputs.suite }} diff --git a/.github/workflows/rtlmeter.yml b/.github/workflows/rtlmeter.yml index d2880b476..1848d4ec8 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -45,7 +45,7 @@ jobs: cases: ${{ steps.cases.outputs.cases }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Startup id: start @@ -208,7 +208,7 @@ jobs: run: echo "tags=$(jq -r 'keys | map(sub("^run-"; "")) | join(" ")' <<< '${{ toJSON(needs) }}')" >> "$GITHUB_OUTPUT" - name: Checkout RTLMeter - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -232,7 +232,7 @@ jobs: ./rtlmeter collate ../all-results-$tag/*.json > ../all-results-$tag.json done - name: Upload combined results - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: all-results-*.json name: all-results @@ -259,7 +259,7 @@ jobs: done - name: Upload reference results if: ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: all-reference-*.json name: all-reference @@ -278,27 +278,27 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Download combined results - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: all-results path: results - name: Upload published results - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: results/*.json name: published-results # Pushing to verilator/verilator-rtlmeter-results requires elevated permissions - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator-rtlmeter-results permission-contents: write - name: Checkout verilator-rtlmeter-results - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/verilator-rtlmeter-results" token: ${{ steps.generate-token.outputs.token }} @@ -331,7 +331,7 @@ jobs: actions: read steps: - name: Checkout RTLMeter - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -341,7 +341,7 @@ jobs: run: make venv - name: Checkout Verilator - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: verilator @@ -367,13 +367,13 @@ jobs: echo ${{ github.event.number }} > ../notification-artifact/pr-number.txt - name: Upload report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: report-artifact name: rtlmeter-report - name: Upload notification - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: notification-artifact name: pr-notification @@ -392,9 +392,9 @@ jobs: # Creating issues requires elevated privilege - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..2f5f06eda --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,142 @@ + + +# Verilator Guidelines for AI Coding Agents + +These files are the general layer an agent loads first -- nearest file wins, so +you read this repository-root file plus the one for the directory you are +editing. They stay deliberately high-level: where to start, how the tree is laid +out, and the conventions reviewers otherwise enforce by hand. They are an index, +not the architecture reference -- for depth (how a pass works internally, the +algorithms, node lifetime) they point you to `docs/internals.rst`. When the +guidance here is not enough, that is where to look next. + +This file has two parts. **Orientation** gets you productive in the codebase from +a cold start. **Before you open a PR** is the checklist of conventions reviewers +otherwise have to enforce by hand -- read it before submitting any change. + +Then read the directory guide for the area you are editing: + +- [src/AGENTS.md](src/AGENTS.md) -- compiler C++ sources: AST, visitors, passes, parser, style +- [include/AGENTS.md](include/AGENTS.md) -- runtime library (`verilated*`): C++14, MT-safety, fixed-width types +- [test_regress/AGENTS.md](test_regress/AGENTS.md) -- regression tests: harness, drivers, golden files +- [docs/AGENTS.md](docs/AGENTS.md) -- documentation (`*.rst`) + +______________________________________________________________________ + +# Orientation + +## What Verilator is + +Verilator is a *compiler*, not an interpreter. It translates synthesizable (and +much behavioral) SystemVerilog into a cycle-accurate C++ model that you then +compile and run. Almost every decision is made at compile ("verilation") time; +the generated C++ just advances state each evaluation. Optimize for verilation- +time work over runtime work. + +## The pipeline is the spine + +A run is an ordered sequence of passes over one shared AST (abstract syntax +tree). In source order: + +| Stage | What it does | Key files | +|---|---|---| +| Preprocess + parse | Lex and parse text into a raw AST -- builds nodes only, no semantic checks | `verilog.l`, `verilog.y` | +| Link / elaborate | Resolve names, scopes, parameters; instantiate the hierarchy | `V3LinkParse`, `V3LinkDot`, `V3Param` | +| Width / type | Assign and check data types and bit widths | `V3Width` | +| Transform / optimize / schedule | Constant fold, lower language features, schedule events | `V3Const`, `V3Randomize`, `V3Assert*`, `V3Sched`, `V3Timing`, `V3Dfg` | +| Emit | Lower the final AST to generated C++ | `V3EmitC*` | +| Runtime | Library the generated model links against | `include/verilated*` | + +This table is the map; `docs/internals.rst` has the detail behind each stage. + +## Where to make a change + +Map the symptom to the pass that owns it, then start by reading that pass's +top-of-file comment. + +| Symptom or feature area | Start in | +|---|---| +| Type/width error, "what type is this", implicit conversion | `V3Width` | +| Name/scope/parameter resolution ("Can't find...", hierarchy) | `V3LinkDot`, `V3Param` | +| `randomize` / `constraint` / `rand` / `randc` | `V3Randomize` | +| `assert` / `property` / `sequence` / `cover` | `V3Assert`, `V3AssertPre`, `V3AssertNfa` | +| `fork` / timing / `#delay` / NBA / event scheduling | `V3Sched`, `V3Timing`, `V3Fork` | +| Syntax wrongly accepted or rejected | `verilog.y`, `verilog.l` | +| Wrong generated C++ | `V3EmitC*` | +| Runtime model behavior | `include/verilated*` | + +## Build and run a test + +- Build in the source tree: `autoconf && ./configure && make -j8`. Configure with + `--enable-ccwarn` so a new compiler warning stops the build. +- Run one test from the repository root: `test_regress/t/t_.py`. +- Run the full regression with `make test`. The complete suite requires + configuring with `--enable-longtests` (works on every OS, including macOS). + +______________________________________________________________________ + +# Before you open a PR + +## Scope and process + +- [ ] Searched open PRs and issues -- duplicating in-flight work wastes review time. +- [ ] Fixed the general root cause, not just the reported case -- if it also + affects other modules/classes/interfaces, cover them or expect rejection. +- [ ] PR is single-purpose. Refactors, drive-by fixes found along the way, and new + features each go in separate PRs; land standalone cleanups first. +- [ ] Every bug fix has a test that fails *without* the fix; include the issue's + own reproducer when possible. +- [ ] New code aims for 100% line coverage; branch coverage far below line coverage + signals guards callers never violate -- justify or remove them. +- [ ] Ran `make format` (clang-format), `make cppcheck`, and `make lint-py`; + self-reviewed the diff for leftover debug code, stale comments, and + copy-paste errors. +- [ ] Ran the full regression on at least one OS before submitting. Partial runs + are fine during development, but the submitted PR is expected to pass every + test. +- [ ] Did not edit `docs/CONTRIBUTORS` (humans only) or `Changes` (maintainer + updates it near release). + +## Pick the right diagnostic (and its required test) + +The API you choose determines which test must accompany the change. + +| API | Output | Meaning | Required test | +|---|---|---|---| +| `v3error("...")` | `%Error:` | User wrote invalid SystemVerilog | `t_*_bad*.v` + `.out` golden | +| `v3error("Unsupported: ...")` | `%Error-UNSUPPORTED:` | Legal SV that Verilator does not yet support | `t_*_unsup*.v` + `.out` golden | +| `v3warn(CODE, "...")` | `%Warning-CODE:` | Legal but suspicious code | warning test + `.out` golden | +| `v3fatalSrc("...")` | `%Error: Internal Error` | Should-never-happen internal assertion | none -- not user-triggerable | + +- Every `v3error`/`v3warn` needs a test in `test_regress/t/` -- enforced by the + warn-coverage distribution test. `v3fatalSrc` is exempt. +- Reserve "Unsupported:" for not-yet-implemented features, never for user mistakes. +- When an error enforces a spec-defined restriction, cite the clause + (`IEEE 1800-2023 11.4.7`) so it is verifiable. Update `docs/guide/warnings.rst` + when adding or changing a warning. +- On error paths, clean up or replace invalid AST (e.g. `AstConst::BitFalse`) so + later passes do not crash after the error. + +## Cross-cutting code rules + +- [ ] No non-ASCII characters in C++ sources or headers: write `--` (two ASCII + hyphens) rather than a Unicode em-dash, and a plain `'` rather than a smart + quote. At write time, not when CI complains. +- [ ] Lists stay sorted: lexer/parser tokens, option declarations, enum values, + configure feature lists, documented option lists. +- [ ] `bin/` scripts are Python (distributed cross-platform); `nodist/` may use + bash and platform-specific code (developer-only, not packaged). +- [ ] Runtime code in `include/` targets C++14 (`--no-timing` builds must work); + C++20 only in timing code paths. +- [ ] In `include/` public headers, prefix public classes with `Verilated`/`Vl` + and document the API with `///` comments. +- [ ] A new code pattern is applied globally or not at all -- no one-off + convention in a single file. + +## Commits + +- Subject line is short and imperative and conventionally ends with the PR number: + `Support property case (#7721)`. A body is optional and common for non-trivial + changes. diff --git a/Changes b/Changes index fcb769eae..438a8c265 100644 --- a/Changes +++ b/Changes @@ -15,7 +15,7 @@ Verilator 5.049 devel **Important:** -* Support covergroups, coverpoints, and bins (#784) (#7117). [Matthew Ballance] +* Support covergroups, coverpoints, and bins (#784) (#7117) (#7728). [Matthew Ballance] * Support new FST writer API (#6871) (#6992). [Yu-Sheng Lin] Use of FST may requiring installing liblz4 and/or liblz4-dev packages, see docs/install.rst. @@ -27,9 +27,12 @@ Verilator 5.049 devel * Add `--coverage-per-instance` (#7636). [Yogish Sekhar] * Add NOTREDOP error on reduction and negation operators (#7417) (#7623) (#7624). * Add hierarchy-aware reporting to `verilator_coverage` (#7657). [Yogish Sekhar] +* Add FINALDLY error (#7754). [Igor Zaworski, Antmicro Ltd.] +* Deprecate isolate_assignments attribute (#7774) (#7144). [Geza Lore, Testorrent USA, Inc.] * Improve `--coverage-fsm` (#7490) (#7529) (#7561) (#7573) (#7619). [Yogish Sekhar] * Change `+verilator+seed` to default to 1, and 0 to randomly select (#7325) (#7516). [Miguel] * Change JSON to include parameter constant mnemonics for FSM Coverage (#7531). [Yogish Sekhar] +* Support assert property 'default disable iff` (#4848) (#7723). [Artur Bieniek, Antmicro Ltd.] * Support printing enum names for %p and %s (#5523) (#7338 repair) (#7521) (#7527). [Nick Brereton] * Support weak `until` / `until_with` property operators (#7290) (#7548) (#7685). [Yilou Wang] * Support `s_eventually` (#7291) (#7508). [Bartłomiej Chmiel, Antmicro Ltd.] @@ -50,6 +53,7 @@ Verilator 5.049 devel * Support procedural concurrent assertions with inferred clock (#7581). [Yilou Wang] * Support calling interface functions without parens (#7584). [Krzysztof Bieganski, Antmicro Ltd.] * Support streaming on queues (#7597). [Benjamin Collier, Secturion Systems, Inc.] +* Support clocking event on a sequence declaration body (#7598) (#7793). [Yilou Wang] * Support generic interface arrays (#7604). [Krzysztof Bieganski, Antmicro Ltd.] * Support FSM detection in primitive wrappers (#7607). [Yogish Sekhar] * Support busses with mix of pullup/pulldown (#7632). [Lucas Amaral] @@ -59,6 +63,18 @@ Verilator 5.049 devel * Support if/if-else in properties (#7692). [Artur Bieniek, Antmicro Ltd.] * Support process::self().srand() (#7695). [Igor Zaworski, Antmicro Ltd.] * Support MacOS lldb (#7697). [Tracy Narine] +* Support assoc array methods with wide value types (#7680). [pawelktk] +* Support property case (#7682) (#7721). [Artur Bieniek, Antmicro Ltd.] +* Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.] +* Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance] +* Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.] +* Support NBAs in initial blocks (#7754). [Igor Zaworski, Antmicro Ltd.] +* Support assertion control system tasks in classes and interfaces (#7761). [Yilou Wang] +* Support cover sequence statement (#7764). [Yilou Wang] +* Support unpacked struct stream (#7767). [Nick Brereton] +* Support $assertcontrol control_type from lock to kill (#7788). [Yilou Wang] +* Support unbounded always [m:$] and strong s_always liveness (#7798). [Yilou Wang] +* Support method calls on a sub-interface via a virtual interface (#7800). [Yilou Wang] * Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.] * Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.] * Optimize forced signal handling (#7554 partial) (#7572) (#7594) (#7596). [Krzysztof Bieganski, Artur Bieniek, Antmicro Ltd.] @@ -71,9 +87,22 @@ Verilator 5.049 devel * Optimize runtime assertOn() checks (#7707). [Geza Lore, Testorrent USA, Inc.] * Optimize $countones and $onehot in DFG. [Geza Lore, Testorrent USA, Inc.] * Optimize procedural loop unrolling. [Geza Lore, Testorrent USA, Inc.] +* Optimize V3Gate inlining heuristic (#7716). [Geza Lore, Testorrent USA, Inc.] +* Optimize reset in DFG (#7737). [Geza Lore, Testorrent USA, Inc.] +* Optimize DFG with relaxed live variable analysis (#7739). [Geza Lore, Testorrent USA, Inc.] +* Optimize conditional patterns sharing common MBSs/LSBs in DfgPeephole (#7760). [Geza Lore, Testorrent USA, Inc.] +* Optimize bit select removal earlier in DFG (#7762). [Geza Lore, Testorrent USA, Inc.] +* Optimize away proven redundant case statement assertions (#7771). [Geza Lore, Testorrent USA, Inc.] +* Optimize table lookups in DFG (#7772). [Geza Lore, Testorrent USA, Inc.] +* Optimize input combinational logic by change detection (#7784). [Geza Lore, Testorrent USA, Inc.] +* Optimize decoder case statements into lookup tables (#7795). [Geza Lore, Testorrent USA, Inc.] +* Optimize wide decoder case statements into decoder expressions (#7804). [Geza Lore, Testorrent USA, Inc.] +* Optimize DFG cycle breaking to do less work (#7210). [Geza Lore, Testorrent USA, Inc.] * Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal] * Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.] +* Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli] * Fix segmentation fault when using --trace with --lib-create (#7299) (#7518). [anonkey] +* Fix cross-hierarchy tristate drivers of interface nets (#7339) (#7801). [Yilou Wang] * Fix destructive event state before dynamic waits (#7340). [Nick Brereton] * Fix ALWCOMBORDER on variable ordering (#7350) (#7608). [Cookie] * Fix false MULTIDRIVEN warning on always_ff variables (#7351) (#7621) (#7672). @@ -128,13 +157,33 @@ Verilator 5.049 devel * Fix loss of events due to bit shift (#7670). [Artur Bieniek, Antmicro Ltd.] * Fix parameter read through locally-declared interface instance (#7679). [Nick Brereton] * Fix skipping nulls in $sscanf (#7689). +* Fix bounds checks in expressions with read/write references (#7694). [Ryszard Rozak, Antmicro Ltd.] * Fix (const) ref default task argument handling (#7698). [Nick Brereton] * Fix `ref` argument type check for packed arrays with differing range directions (#7700). [Nick Brereton] * Fix ignoring not-found modules with encoded names (#7706). [Igor Zaworski, Antmicro Ltd.] * Fix MULTIDRIVEN in generates (#7709). [Todd Strader] +* Fix parameter pollution when using class parameters (#7711) (#7763). [em2machine] * Fix Makefile action to not write to ${srcdir} (#7715). [Larry Doolittle] * Fix splitting functions containing fork logic (#7717). [Mateusz Gancarz, Antmicro Ltd.] * Fix optimizations of assignments with timing controls (#7718). [Ryszard Rozak, Antmicro Ltd.] +* Fix s_eventually on interface (#7731) (#7733). [Marco Bartoli] +* Fix parameter values in coverage bins widths (#7732) (#7734). [Marco Bartoli] +* Fix configure fall back on dynamic malloc libraries (#7736). [Geza Lore, Testorrent USA, Inc.] +* Fix crash on overlapping priority case. [Geza Lore, Testorrent USA, Inc.] +* Fix s_eventually in parameterized interfaces (#7741). [Nick Brereton] +* Fix dpi export pointers (#7742) (#7751). [Yilin Li] +* Fix force on unpacked bit select (#7744) (#7745). [Nikolai Kumar] +* Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.] +* Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang] +* Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.] +* Fix not failing assertion when RHS of a range window rejects once (#7773). [Artur Bieniek, Antmicro Ltd.] +* Fix $fflush and autoflush with --threads (#7782). +* Fix out-of-bounds read value for 2-state types (#7785). [Jakub Michalski] +* Fix `cover property` of an implication counting vacuous matches (#7789). [Yilou Wang] +* Fix randomization of dynamic arrays of objects (#7790). [Ryszard Rozak, Antmicro Ltd.] +* Fix `$bits` on unpacked structs (#4521) (#7796). [Nick Brereton] +* Fix randomize() with skipping derived pre/post_randomize (#7799). [Yilou Wang] +* Fix assertion when loop unrolling failed (#7810). [Geza Lore, Testorrent USA, Inc.] Verilator 5.048 2026-04-26 diff --git a/Makefile.in b/Makefile.in index c2b60d004..80db572be 100644 --- a/Makefile.in +++ b/Makefile.in @@ -495,6 +495,11 @@ MAKE_FILES = \ src/Makefile*.in \ test_regress/Makefile* \ +# Markdown +MD_FILES = \ + *.md \ + */*.md \ + # Perl programs PERL_PROGRAMS = \ bin/redirect \ @@ -541,6 +546,13 @@ PY_FILES = \ # Python files, test_regress tests PY_TEST_FILES = test_regress/t/*.py +# reStructuredText Sphinx files +RST_FILES = \ + *.rst \ + */*.rst \ + ci/docker/*/*.rst \ + docs/guide/*.rst \ + # YAML files YAML_FILES = \ .*.yaml \ @@ -553,7 +565,7 @@ YAML_FILES = \ # Format format: - $(MAKE) -j 5 format-c format-cmake format-exec format-py format-yaml + $(MAKE) -j 5 format-c format-cmake format-exec format-md format-py format-rst format-yaml BEAUTYSH = beautysh BEAUTYSH_FLAGS = --indent-size 2 @@ -590,6 +602,13 @@ format-make mbake: $(MBAKE) --version $(MBAKE) $(MBAKE_FLAGS) $(MAKE_FILES) +MDFORMAT = mdformat +MDFORMAT_FLAGS = + +format-md: + $(MDFORMAT) --version + $(MDFORMAT) $(MDFORMAT_FLAGS) $(MD_FILES) + YAPF = yapf YAPF_FLAGS = -i --parallel @@ -597,6 +616,13 @@ format-py yapf: $(YAPF) --version $(YAPF) $(YAPF_FLAGS) $(PY_FILES) +DOCSTRFMT = docstrfmt +DOCSTRFMT_FLAGS = --line-length 75 --indent-width 3 --keep-blanks --ordered-marker "\#" --preserve-adornments --no-center-section-titles + +format-rst: + $(DOCSTRFMT) --version + $(DOCSTRFMT) $(DOCSTRFMT_FLAGS) $(RST_FILES) + YAMLFIX = YAMLFIX_WHITELINES=1 YAMLFIX_LINE_LENGTH=200 YAMLFIX_preserve_quotes=true yamlfix YAMLFIX_FLAGS = diff --git a/README.rst b/README.rst index bec2182ad..d51837df2 100644 --- a/README.rst +++ b/README.rst @@ -1,58 +1,63 @@ -.. Github doesn't render images unless absolute URL -.. Do not know of a conditional tag, "only: github" nor "github display" works -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + Github doesn't render images unless absolute URL + Do not know of a conditional tag, "only: github" nor "github display" works + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 |badge1| |badge2| |badge3| |badge4| |badge5| |badge7| |badge8| .. |badge1| image:: https://img.shields.io/badge/Website-Verilator.org-181717.svg :target: https://verilator.org + .. |badge2| image:: https://img.shields.io/badge/License-LGPL%20v3-blue.svg :target: https://www.gnu.org/licenses/lgpl-3.0 + .. |badge3| image:: https://img.shields.io/badge/License-Artistic%202.0-0298c3.svg :target: https://opensource.org/licenses/Artistic-2.0 + .. |badge4| image:: https://repology.org/badge/tiny-repos/verilator.svg?header=distro%20packages :target: https://repology.org/project/verilator/versions + .. |badge5| image:: https://img.shields.io/docker/pulls/verilator/verilator :target: https://hub.docker.com/r/verilator/verilator + .. |badge7| image:: https://github.com/verilator/verilator/workflows/build/badge.svg :target: https://github.com/verilator/verilator/actions?query=workflow%3Abuild + .. |badge8| image:: https://img.shields.io/github/actions/workflow/status/verilator/verilator/rtlmeter.yml?branch=master&event=schedule&label=benchmarks :target: https://verilator.github.io/verilator-rtlmeter-results - Welcome to Verilator ==================== .. list-table:: - * - **Welcome to Verilator, the fastest Verilog/SystemVerilog simulator.** - * Accepts Verilog or SystemVerilog - * Performs lint code-quality checks - * Compiles into multithreaded C++, or SystemC - * Creates JSON to front-end your own tools + - - **Welcome to Verilator, the fastest Verilog/SystemVerilog simulator.** + - Accepts Verilog or SystemVerilog + - Performs lint code-quality checks + - Compiles into multithreaded C++, or SystemC + - Creates JSON to front-end your own tools - |Logo| - * - |verilator multithreaded performance| + - - |verilator multithreaded performance| - **Fast** - * Outperforms many closed-source commercial simulators - * Single- and multithreaded output models - * - **Widely Used** - * Wide industry and academic deployment - * Out-of-the-box support from Arm and RISC-V vendor IP - * Over 700 contributors + - Outperforms many closed-source commercial simulators + - Single- and multithreaded output models + - - **Widely Used** + - Wide industry and academic deployment + - Out-of-the-box support from Arm and RISC-V vendor IP + - Over 700 contributors - |verilator usage| - * - |verilator community| + - - |verilator community| - **Community Driven & Openly Licensed** - * Guided by the `CHIPS Alliance`_ and `Linux Foundation`_ - * Open, and free as in both speech and beer - * More simulation for your verification budget - * - **Commercial Support Available** - * Commercial support contracts - * Design support contracts - * Enhancement contracts + - Guided by the `CHIPS Alliance`_ and `Linux Foundation`_ + - Open, and free as in both speech and beer + - More simulation for your verification budget + - - **Commercial Support Available** + - Commercial support contracts + - Design support contracts + - Enhancement contracts - |verilator support| - What Verilator Does =================== @@ -77,7 +82,6 @@ SDF annotation, or mixed-signal simulation. However, if you are looking for a path to migrate SystemVerilog to C++/SystemC, or want high-speed simulation, Verilator is the tool for you. - Performance =========== @@ -96,7 +100,6 @@ Mentor ModelSim/Questa, Synopsys VCS, VTOC, and Pragmatic CVer/CVC). But, Verilator is open-sourced, so you can spend on computes rather than licenses. Thus, Verilator gives you the best simulation cycles/dollar. - Installation & Documentation ============================ @@ -115,7 +118,6 @@ For more information: - `Verilator issues `_ - Support ======= @@ -132,7 +134,6 @@ Verilator also supports and encourages commercial support models and organizations; please see `Verilator Commercial Support `_. - Related Projects ================ @@ -149,7 +150,6 @@ Related Projects - `Surfer `_ - Web or offline waveform viewer for Verilator traces. - Open License ============ @@ -161,10 +161,17 @@ the terms of either the GNU Lesser General Public License Version 3 or the Perl Artistic License Version 2.0. See the documentation for more details. .. _chips alliance: https://chipsalliance.org + .. _icarus verilog: https://steveicarus.github.io/iverilog + .. _linux foundation: https://www.linuxfoundation.org + .. |Logo| image:: https://www.veripool.org/img/verilator_256_200_min.png + .. |verilator multithreaded performance| image:: https://www.veripool.org/img/verilator_multithreaded_performance_bg-min.png + .. |verilator usage| image:: https://www.veripool.org/img/verilator_usage_400x200-min.png + .. |verilator community| image:: https://www.veripool.org/img/verilator_community_400x125-min.png + .. |verilator support| image:: https://www.veripool.org/img/verilator_support_400x125-min.png diff --git a/bin/verilator b/bin/verilator index 10176f45c..57fd60507 100755 --- a/bin/verilator +++ b/bin/verilator @@ -438,6 +438,7 @@ detailed descriptions of these arguments. --diagnostics-sarif-output Set SARIF diagnostics output file --dpi-hdr-only Only produce the DPI header file --dump- Enable dumping everything in source file + --dump-ast-patterns Enable dumping Ast pattern statistics --dump-defines Show preprocessor defines with -E --dump-dfg Enable dumping DfgGraphs to .dot files --dump-dfg-patterns Enable dumping Dfg pattern statistics diff --git a/ci/docker/buildenv/README.rst b/ci/docker/buildenv/README.rst index 146725466..e1648420d 100644 --- a/ci/docker/buildenv/README.rst +++ b/ci/docker/buildenv/README.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _verilator build docker container: diff --git a/ci/docker/run/README.rst b/ci/docker/run/README.rst index 1ef7f9fb4..4feaf0804 100644 --- a/ci/docker/run/README.rst +++ b/ci/docker/run/README.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Verilator Executable Docker Container ===================================== diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 000000000..1c0524c78 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,38 @@ + + +# docs/ Guidelines -- Verilator documentation (\*.rst) + +When to check: before editing anything under `docs/`. +Read the repository-root [AGENTS.md](../AGENTS.md) first for process and cross-cutting rules. + +## Writing rules + +- Rewrap paragraphs after editing -- keep consistent line length in `*.rst` files. + +- Document only stable, implemented features -- never planned or in-development capabilities; prevents user confusion and support burden. + +- Explain WHAT and WHEN, not HOW -- conceptual purpose and practical use cases over implementation mechanics; describe behavior ("optimized as pure", not "treated as pure") and clarify ambiguous referents ("in the internals of Verilator"). + +- Keep terminology consistent -- one term per concept; update docs when renaming code constructs; spell out full terms, avoiding abbreviations like "sim"/"sims". + +- Use "how many" for countable nouns (threads, tasks, workers) and "how much" for uncountable quantities. + +- Mark internal or experimental features "for internal use only" -- prevents user dependence and forced deprecation cycles later. + +- Use specific IEEE references: `IEEE {number}-{year}` plus the section (e.g. `Annex I`) -- a vague "IEEE spec requires" is unverifiable. + +- Document all flags with descriptions, not just syntax. + +## reStructuredText mechanics + +- Use the `:vlopt:` role for Verilator option references -- makes cross-references clickable and consistent. + +- Escape angle brackets (`\<`, `\>`) in link targets -- prevents broken links to command-line options. + +- Generate documentation examples with `test.extract` from `test_regress` test files -- examples stay synced with actually tested behavior. + +## Hard constraint + +- Never edit `docs/CONTRIBUTORS` -- only humans may, after reading and agreeing to its statement; remind the user instead. diff --git a/docs/CONTRIBUTING.rst b/docs/CONTRIBUTING.rst index 6567d22b4..bec0684c6 100644 --- a/docs/CONTRIBUTING.rst +++ b/docs/CONTRIBUTING.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Contributing to Verilator ========================= diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index ef3746a76..44f7ecb2c 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -9,6 +9,7 @@ Please see the Verilator manual for 200+ additional contributors. Thanks to all. 404allen404 Adam Bagley +Adam Kostrzewski Adrian Sampson Adrien Le Masle أحمد المحمودي (Ahmed El-Mahmoudy) @@ -158,6 +159,7 @@ Kefa Chen Keith Colbert Kevin Kiningham Kevin Nygaard +Kornel Uriasz Kritik Bhimani Krzysztof Bieganski Krzysztof Boronski @@ -250,6 +252,7 @@ Rowan Goemans Rupert Swarbrick Ryan Ziegler Ryszard Rozak +Saksham Gupta Samuel Riedel Sean Cross Sebastien Van Cauwenberghe @@ -276,6 +279,7 @@ Tobias Jensen Tobias Rosenkranz Tobias Wölfel Todd Strader +Tom Jackson Tom Manner Tomasz Gorochowik Topa Topino @@ -326,3 +330,4 @@ Yogish Sekhar Zubin Jain Muzaffer Kal Yilin Li +Shashvat Prabhu diff --git a/docs/README.rst b/docs/README.rst index 5ace9194a..706619968 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Verilator Documentation ======================= diff --git a/docs/gen/ex_FINALDLY_faulty.rst b/docs/gen/ex_FINALDLY_faulty.rst new file mode 100644 index 000000000..78c456ad4 --- /dev/null +++ b/docs/gen/ex_FINALDLY_faulty.rst @@ -0,0 +1,7 @@ +.. comment: generated by t_finaldly_bad +.. code-block:: sv + :linenos: + :emphasize-lines: 2 + + bit foo; + final foo <= 1; // <--- Error diff --git a/docs/gen/ex_FINALDLY_msg.rst b/docs/gen/ex_FINALDLY_msg.rst new file mode 100644 index 000000000..4178e2669 --- /dev/null +++ b/docs/gen/ex_FINALDLY_msg.rst @@ -0,0 +1,6 @@ +.. comment: generated by t_finaldly_bad +.. code-block:: + + %Error-FINALDLY: example.v:1:13 Non-blocking assignment '<=' in final block + 9 | final foo <= 1; + | ^~ diff --git a/docs/guide/changes.rst b/docs/guide/changes.rst index 09264212b..743fc77b9 100644 --- a/docs/guide/changes.rst +++ b/docs/guide/changes.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 **************** Revision History diff --git a/docs/guide/connecting.rst b/docs/guide/connecting.rst index 65640aee9..1d714ce4e 100644 --- a/docs/guide/connecting.rst +++ b/docs/guide/connecting.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _connecting: @@ -28,13 +29,13 @@ The generated model class file manages all internal state required by the model, and exposes the following interface that allows interaction with the model: -* Top level IO ports are exposed as references to the appropriate internal +- Top level IO ports are exposed as references to the appropriate internal equivalents. -* Public top level module instances are exposed as pointers to allow access +- Public top level module instances are exposed as pointers to allow access to ``/* verilator public */`` items. -* The root of the design hierarchy (as in SystemVerilog ``$root``) is +- The root of the design hierarchy (as in SystemVerilog ``$root``) is exposed via the ``rootp`` member pointer to allow access to model internals, including ``/* verilator public_flat */`` items. @@ -58,22 +59,22 @@ This means that user code that accesses internal signals in the model (likely including ``/* verilator public_flat */`` signals, as they are often inlined into the root scope) will need to be updated as follows: -* No change required for accessing top level IO signals. These are directly +- No change required for accessing top level IO signals. These are directly accessible in the model class via references. -* No change required for accessing ``/* verilator public */`` items. These +- No change required for accessing ``/* verilator public */`` items. These are directly accessible via sub-module pointers in the model class. -* Accessing any other internal members, including - ``/* verilator public_flat */`` items requires the following changes: +- Accessing any other internal members, including ``/* verilator + public_flat */`` items requires the following changes: - * Additionally include :file:`{prefix}___024root.h`. This header defines + - Additionally include :file:`{prefix}___024root.h`. This header defines type of the ``rootp`` pointer within the model class. Note the ``__024`` substring is the Verilator escape sequence for the ``$`` character, i.e.: ``rootp`` points to the Verilated SystemVerilog ``$root`` scope. - * Replace ``modelp->internal->member`` references with + - Replace ``modelp->internal->member`` references with ``modelp->rootp->internal->member`` references, which contain one additional indirection via the ``rootp`` pointer. @@ -178,7 +179,7 @@ DPI Example In the SYSTEMC example above, if you wanted to import C++ functions into Verilog, put in our.v: -.. code-block:: +.. code-block:: sv import "DPI-C" function int add (input int a, input int b); @@ -212,7 +213,7 @@ function name for the import, but note it must be escaped. .. code-block:: sv - export "DPI-C" function integer \$myRand; + import "DPI-C" function integer \$myRand; initial $display("myRand=%d", $myRand()); @@ -502,9 +503,9 @@ described above is just a wrapper which calls these two functions. 3. If using delays and :vlopt:`--timing`, there are two additional methods the user should call: - * ``designp->eventsPending()``, which returns ``true`` if there are any + - ``designp->eventsPending()``, which returns ``true`` if there are any delayed events pending, - * ``designp->nextTimeSlot()``, which returns the simulation time of the + - ``designp->nextTimeSlot()``, which returns the simulation time of the next delayed event. This method can only be called if ``designp->eventsPending()`` returned ``true``. diff --git a/docs/guide/contributing.rst b/docs/guide/contributing.rst index 9975c7512..8f834d5a9 100644 --- a/docs/guide/contributing.rst +++ b/docs/guide/contributing.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ******************************* Contributing and Reporting Bugs @@ -86,7 +87,8 @@ Please refer to `sv-bugpoint README `_ for more information on how to use `sv-bugpoint`. -.. Contributing -.. ============ +.. + Contributing + ============ .. include:: ../CONTRIBUTING.rst diff --git a/docs/guide/contributors.rst b/docs/guide/contributors.rst index 782a79865..37d7ef9f1 100644 --- a/docs/guide/contributors.rst +++ b/docs/guide/contributors.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ************************ Contributors and Origins @@ -29,10 +30,10 @@ Alliance `_, and `Antmicro Ltd Previous major corporate sponsors of Verilator, by providing significant contributions of time or funds include: Antmicro Ltd., Atmel Corporation, Compaq Corporation, Digital Equipment Corporation, Embecosm Ltd., Fractile -Ltd., Hicamp Systems, Intel Corporation, Marvell Inc., Mindspeed Technologies -Inc., MicroTune Inc., picoChip Designs Ltd., Sun Microsystems Inc., Nauticus -Networks Inc., SiCortex Inc, Shunyao CAD, Tenstorrent USA, Inc. and Western -Digital Inc. +Ltd., Hicamp Systems, Intel Corporation, Marvell Inc., Mindspeed +Technologies Inc., MicroTune Inc., picoChip Designs Ltd., Sun Microsystems +Inc., Nauticus Networks Inc., SiCortex Inc, Shunyao CAD, Tenstorrent USA, +Inc. and Western Digital Inc. The contributors of major functionality are: Jeremy Bennett, Krzysztof Bieganski, Byron Bradley, Lane Brooks, John Coiner, Duane Galbi, Arkadiusz @@ -142,18 +143,18 @@ Krzysztof Obłonczek, Danny Oler, Andreas Olofsson, Baltazar Ortiz, Aleksander Osman, Don Owen, Tim Paine, Deepa Palaniappan, James Pallister, Vassilis Papaefstathiou, Sanggyu Park, Brad Parker, Risto Pejašinović, Seth Pellegrino, Joel Peltonen, Morten Borup Petersen, Dan Petrisko, Thanh Tung -Pham, Wesley Piard, Maciej Piechotka, David Pierce, Cody Piersall, -T. Platz, Michael Platzer, Dominic Plunkett, Nolan Poe, Tuomas Poikela, -George Polack, David Poole, Michael Popoloski, Roman Popov, Aylon Chaim -Porat, Oron Port, Rich Porter, Rick Porter, Stefan Post, Niranjan Prabhu, -Damien Pretet, Harald Pretl, Bill Pringlemeir, Usha Priyadharshini, Mark -Jackson Pulver, Prateek Puri, Nikolay Puzanov, Han Qi, Jiacheng Qian, -Marshal Qiao, Raynard Qiao, Yujia Qiao, Jasen Qin, Frank Qiu, Nandu Raj, -Kamil Rakoczy, Danilo Ramos, Drew Ranck, Chris Randall, Anton Rapp, Josh -Redford, Odd Magne Reitan, Frédéric Requin, Wajahat Riaz, Dustin Richmond, -Samuel Riedel, Alberto Del Rio, Eric Rippey, Narcis Rodas, Oleg Rodionov, -Ludwig Rogiers, Paul Rolfe, Michail Rontionov, Arjen Roodselaar, Arthur -Rosa, Tobias Rosenkranz, Yernagula Roshit, Diego Roux, Ryszard Rozak, Dan +Pham, Wesley Piard, Maciej Piechotka, David Pierce, Cody Piersall, T. +Platz, Michael Platzer, Dominic Plunkett, Nolan Poe, Tuomas Poikela, George +Polack, David Poole, Michael Popoloski, Roman Popov, Aylon Chaim Porat, +Oron Port, Rich Porter, Rick Porter, Stefan Post, Niranjan Prabhu, Damien +Pretet, Harald Pretl, Bill Pringlemeir, Usha Priyadharshini, Mark Jackson +Pulver, Prateek Puri, Nikolay Puzanov, Han Qi, Jiacheng Qian, Marshal Qiao, +Raynard Qiao, Yujia Qiao, Jasen Qin, Frank Qiu, Nandu Raj, Kamil Rakoczy, +Danilo Ramos, Drew Ranck, Chris Randall, Anton Rapp, Josh Redford, Odd +Magne Reitan, Frédéric Requin, Wajahat Riaz, Dustin Richmond, Samuel +Riedel, Alberto Del Rio, Eric Rippey, Narcis Rodas, Oleg Rodionov, Ludwig +Rogiers, Paul Rolfe, Michail Rontionov, Arjen Roodselaar, Arthur Rosa, +Tobias Rosenkranz, Yernagula Roshit, Diego Roux, Ryszard Rozak, Dan Ruelas-Petrisko, Luca Rufer, Huang Rui, Graham Rushton, Jan Egil Ruud, Denis Rystsov, Pawel Sagan, Robert Sammelson, Adrian Sampson, John Sanguinetti, Josep Sans, Dave Sargeant, Luca Sasselli, Philippe Sauter, diff --git a/docs/guide/control.rst b/docs/guide/control.rst index a01396040..8553de997 100644 --- a/docs/guide/control.rst +++ b/docs/guide/control.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _verilator control files: @@ -187,6 +188,10 @@ The grammar of control commands is as follows: .. option:: isolate_assignments -module "" [-task ""] -var "" + Deprecated and has no effect (ignored). + + In versions before 5.050: + Used to indicate that the assignments to this signal in any blocks should be isolated into new blocks. Same as :option:`/*verilator&32;isolate_assignments*/` metacomment. diff --git a/docs/guide/copyright.rst b/docs/guide/copyright.rst index 75b00d4ab..b5a59e1fe 100644 --- a/docs/guide/copyright.rst +++ b/docs/guide/copyright.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ********* Copyright diff --git a/docs/guide/deprecations.rst b/docs/guide/deprecations.rst index 75cad0492..7d69ab1c2 100644 --- a/docs/guide/deprecations.rst +++ b/docs/guide/deprecations.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Deprecations ============ diff --git a/docs/guide/environment.rst b/docs/guide/environment.rst index 1b3fd5ed4..a66ebe755 100644 --- a/docs/guide/environment.rst +++ b/docs/guide/environment.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _environment: diff --git a/docs/guide/example_binary.rst b/docs/guide/example_binary.rst index fd36fe8ce..c02469907 100644 --- a/docs/guide/example_binary.rst +++ b/docs/guide/example_binary.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _example create-binary execution: diff --git a/docs/guide/example_cc.rst b/docs/guide/example_cc.rst index 2a8384e85..d50caaa1d 100644 --- a/docs/guide/example_cc.rst +++ b/docs/guide/example_cc.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _example c++ execution: diff --git a/docs/guide/example_common_install.rst b/docs/guide/example_common_install.rst index 0e38fa6ff..1c963f39d 100644 --- a/docs/guide/example_common_install.rst +++ b/docs/guide/example_common_install.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 First you need Verilator installed, see :ref:`Installation`. In brief, if you installed Verilator using the package manager of your operating system, diff --git a/docs/guide/example_dist.rst b/docs/guide/example_dist.rst index f3e1765f5..8427394c0 100644 --- a/docs/guide/example_dist.rst +++ b/docs/guide/example_dist.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _examples in the distribution: diff --git a/docs/guide/example_sc.rst b/docs/guide/example_sc.rst index 349645080..06ec49070 100644 --- a/docs/guide/example_sc.rst +++ b/docs/guide/example_sc.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _example systemc execution: diff --git a/docs/guide/examples.rst b/docs/guide/examples.rst index 5e0b2e260..35fead694 100644 --- a/docs/guide/examples.rst +++ b/docs/guide/examples.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _examples: @@ -9,10 +10,10 @@ Examples This section covers the following examples: -* :ref:`Example Create-Binary Execution` -* :ref:`Example C++ Execution` -* :ref:`Example SystemC Execution` -* :ref:`Examples in the Distribution` +- :ref:`Example Create-Binary Execution` +- :ref:`Example C++ Execution` +- :ref:`Example SystemC Execution` +- :ref:`Examples in the Distribution` .. toctree:: :maxdepth: 1 diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index 14fecf63e..ac2b73f16 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _simulation runtime arguments: @@ -50,8 +51,8 @@ Options: .. option:: +verilator+log+file+ - Log all stdout and stderr to the specified output filename. If not specified - the normal stdout/stderr streams are used. + Log all stdout and stderr to the specified output filename. If not specified + the normal stdout/stderr streams are used. .. option:: +verilator+noassert diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 705bf3f71..952500400 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 =================== verilator Arguments @@ -490,6 +491,10 @@ Summary: Rarely needed - for developer use. Enable all dumping in the given source file at level 3. +.. option:: --dump-ast-patterns + + Rarely needed. Enable dumping AstNodeExpr pattern statistics. + .. option:: --dump-defines With :vlopt:`-E`, suppress normal output, and instead print a list of @@ -659,6 +664,22 @@ Summary: .. option:: -fno-case + Rarely needed. Disable all case statement optimizations. + + Alias for all other `-fno-case-*` options. + +.. option:: -fno-case-decoder + + Rarely needed. Disable converting case statements into decoder tables. + +.. option:: -fno-case-table + + Rarely needed. Disable converting case statements into table lookups. + +.. option:: -fno-case-tree + + Rarely needed. Disable converting case statements into bit-wise branch trees. + .. option:: -fno-combine .. option:: -fno-const @@ -737,12 +758,41 @@ Summary: this is not recommended as may cause additional warnings and ordering issues. +.. option:: -fno-ico-change-detect + + Rarely needed. Disable input change detection in the input combinational + ('ico') region. With change detection enabled (the default, unless + :vlopt:`--vpi` is passed), the input combinational logic is evaluated only + when a top level input has actually changed, rather than unconditionally on + the first scheduling iteration. + + The change detection logic assumes a top level input only ever changes + externally between evaluations. The optimization is automatically disabled + for top level input signals that are written within the design. Accesses via + the VPI cannot be analyzed at compile time, therefore :vlopt:`--vpi` + disables this optimization for all inputs; it may be turned back on by + explicitly passing :vlopt:`-fico-change-detect <-fno-ico-change-detect>`. + .. option:: -fno-inline + Rarely needed. Disable module inlining. + +.. option:: -fno-inline-cfuncs + + Rarely needed. Disable inlining of small generated C++ functions into their + callers. + + This optimization is automatically disabled when :vlopt:`--prof-cfuncs` is + used. + .. option:: -fno-inline-funcs + Rarely needed. Disable inlining of SystemVerilog functions and tasks. + .. option:: -fno-inline-funcs-eager + Rarely needed. Disable eager inlining of SystemVerilog functions and tasks. + .. option:: -fno-life .. option:: -fno-life-post @@ -944,27 +994,21 @@ Summary: .. option:: --inline-cfuncs - Inline small C++ function (internal AstCFunc) calls directly into their - callers when the function has at most nodes. This reduces - function call overhead when :vlopt:`--output-split-cfuncs` places - functions in separate compilation units that the C++ compiler cannot - inline. + Tune the inlining of small generated C++ function. Functions no bigger than + nodes will be inlined if possible. The default is 20. - Set to 0 to disable this optimization. The default is 20. - - This optimization is automatically disabled when :vlopt:`--prof-cfuncs` - or :vlopt:`--trace` is used. + See also :vlopt:`--inline-cfuncs-product` and :vlopt:`-fno-inline-cfuncs`. .. option:: --inline-cfuncs-product - Tune the inlining of C++ function (internal AstCFunc) calls for larger - functions. When a function is too large to always inline (exceeds - :vlopt:`--inline-cfuncs` threshold), it may still be inlined if the - function size multiplied by the number of call sites is at most . + Tune the inlining of small generated C++ function. If a function's node + count multiplied by the number of calls is not bigger than , the + function will be inlined if possible. - This allows functions that are called only once or twice to be inlined - even if they exceed the small function threshold. Set to 0 to only inline - functions below the :vlopt:`--inline-cfuncs` threshold. The default is 200. + This allows functions that are called only once or twice to be inlined even + if they exceed the small function threshold. The default is 200. + + See also :vlopt:`--inline-cfuncs` and :vlopt:`-fno-inline-cfuncs`. .. option:: --inline-mult diff --git a/docs/guide/exe_verilator_coverage.rst b/docs/guide/exe_verilator_coverage.rst index b5f0598c2..388f67cfb 100644 --- a/docs/guide/exe_verilator_coverage.rst +++ b/docs/guide/exe_verilator_coverage.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 verilator_coverage ================== diff --git a/docs/guide/exe_verilator_gantt.rst b/docs/guide/exe_verilator_gantt.rst index 5b45969f9..b9944575c 100644 --- a/docs/guide/exe_verilator_gantt.rst +++ b/docs/guide/exe_verilator_gantt.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 verilator_gantt =============== diff --git a/docs/guide/exe_verilator_profcfunc.rst b/docs/guide/exe_verilator_profcfunc.rst index c79f86436..a29a5a34c 100644 --- a/docs/guide/exe_verilator_profcfunc.rst +++ b/docs/guide/exe_verilator_profcfunc.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 verilator_profcfunc =================== diff --git a/docs/guide/executables.rst b/docs/guide/executables.rst index ac4e3f85e..7d86091b5 100644 --- a/docs/guide/executables.rst +++ b/docs/guide/executables.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ********************************* Executable and Argument Reference diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index 0e944860e..8e8d5d183 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 =================== Language Extensions @@ -341,6 +342,10 @@ or "`ifdef`"'s may break other tools. .. option:: /*verilator&32;isolate_assignments*/ + Deprecated and has no effect (ignored). + + In versions before 5.050: + Used after a signal declaration to indicate the assignments to this signal in any blocks should be isolated into new blocks. When large combinatorial block results in a :option:`UNOPTFLAT` warning, attaching diff --git a/docs/guide/faq.rst b/docs/guide/faq.rst index ec55a9a9c..96245574c 100644 --- a/docs/guide/faq.rst +++ b/docs/guide/faq.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ****************************** FAQ/Frequently Asked Questions @@ -82,17 +83,17 @@ the licenses for details. Some examples: -* Any SystemVerilog or other input fed into Verilator remains your own. +- Any SystemVerilog or other input fed into Verilator remains your own. -* Any of your VPI/DPI C++ routines that Verilator calls remain your own. +- Any of your VPI/DPI C++ routines that Verilator calls remain your own. -* Any of your main() C++ code that calls into Verilator remains your own. +- Any of your main() C++ code that calls into Verilator remains your own. -* If you change Verilator itself, for example, changing or adding a file +- If you change Verilator itself, for example, changing or adding a file under the src/ directory in the repository, you must make the source code available under the GNU Lesser Public License. -* If you change a header Verilator provides, for example, under include/ in +- If you change a header Verilator provides, for example, under include/ in the repository, you must make the source code available under the GNU Lesser Public License. @@ -384,33 +385,33 @@ example of how to do this. How do I get faster build times? """""""""""""""""""""""""""""""" -* When running make, pass the make variable VM_PARALLEL_BUILDS=1, so that +- When running make, pass the make variable VM_PARALLEL_BUILDS=1, so that builds occur in parallel. Note this is now set by default if an output file is large enough to be split due to the :vlopt:`--output-split` option. -* Verilator emits any infrequently executed "cold" routines into separate +- Verilator emits any infrequently executed "cold" routines into separate __Slow.cpp files. This can accelerate compilation as optimization can be disabled on these routines. See the OPT_FAST and OPT_SLOW make variables and :ref:`Benchmarking & Optimization`. -* Use a recent compiler. Newer compilers tend to be faster. +- Use a recent compiler. Newer compilers tend to be faster. -* Compile in parallel on many machines and use caching; see the web for the +- Compile in parallel on many machines and use caching; see the web for the ccache, sccache, distcc, or icecream packages. ccache will skip GCC runs between identical source builds, even across different users. If ccache was installed when Verilator was built, it is used, or see OBJCACHE environment variable to override this. Also see the :vlopt:`--output-split` option and :ref: `Profiling ccache efficiency`. -* To reduce the compile time of classes that use a Verilated module (e.g., +- To reduce the compile time of classes that use a Verilated module (e.g., a top CPP file) you may wish to add a :option:`/*verilator&32;no_inline_module*/` metacomment to your top-level module. This will decrease the amount of code in the model's Verilated class, improving compile times of any instantiating top-level C++ code, at a relatively small cost of execution performance. -* Use :ref:`hierarchical verilation`. +- Use :ref:`hierarchical verilation`. Why do so many files need to recompile when I add a signal? @@ -498,12 +499,12 @@ equal, the best performance is when Verilator sees all of the design. So, look at the hierarchy of your design, labeling instances as to if they are SystemC or Verilog. Then: -* A module with only SystemC instances below must be SystemC. +- A module with only SystemC instances below must be SystemC. -* A module with a mix of Verilog and SystemC instances below must be +- A module with a mix of Verilog and SystemC instances below must be SystemC. (As Verilator cannot connect to lower-level SystemC instances.) -* A module with only Verilog instances below can be either, but for best +- A module with only Verilog instances below can be either, but for best performance should be Verilog. (The exception is if you have a design that is instantiated many times; in this case, Verilating one of the lower modules and instantiating that Verilated instances multiple times diff --git a/docs/guide/files.rst b/docs/guide/files.rst index 2e5fa2225..c2dfb5004 100644 --- a/docs/guide/files.rst +++ b/docs/guide/files.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ***** Files @@ -42,138 +43,141 @@ For --cc/--sc, it creates: .. list-table:: - * - *{prefix}*.json + - - *{prefix}*.json - JSON build definition compiling (from --make json) - * - *{prefix}*.mk + - - *{prefix}*.mk - Make include file for compiling (from --make gmake) - * - *{prefix}*\ _classes.mk + - - *{prefix}*\ _classes.mk - Make include file with class names (from --make gmake) - * - *{prefix}*.h + - - *{prefix}*.h - Model header - * - *{prefix}*.cpp + - - *{prefix}*.cpp - Model C++ file - * - *{prefix}*\ ___024root.h + - - *{prefix}*\ ___024root.h - Top-level internal header file (from SystemVerilog $root) - * - *{prefix}*\ ___024root.cpp + - - *{prefix}*\ ___024root.cpp - Top-level internal C++ file (from SystemVerilog $root) - * - *{prefix}*\ ___024root\ *{__n}*.cpp + - - *{prefix}*\ ___024root\ *{__n}*.cpp - Additional top-level internal C++ files - * - *{prefix}*\ ___024root__Slow\ *{__n}*.cpp + - - *{prefix}*\ ___024root__Slow\ *{__n}*.cpp - Infrequent cold routines - * - *{prefix}*\ ___024root__Trace\ *{__n}*.cpp + - - *{prefix}*\ ___024root__Trace\ *{__n}*.cpp - Wave file generation code (from --trace-\*) - * - *{prefix}*\ ___024root__Trace__Slow\ *{__n}*.cpp + - - *{prefix}*\ ___024root__Trace__Slow\ *{__n}*.cpp - Wave file generation code (from --trace-\*) - * - *{prefix}*\ __Dpi.h + - - *{prefix}*\ __Dpi.h - DPI import and export declarations (from --dpi) - * - *{prefix}*\ __Dpi.cpp + - - *{prefix}*\ __Dpi.cpp - Global DPI export wrappers (from --dpi) - * - *{prefix}*\ __Dpi_Export\ *{__n}*.cpp + - - *{prefix}*\ __Dpi_Export\ *{__n}*.cpp - DPI export wrappers scoped to this particular model (from --dpi) - * - *{prefix}*\ __Inlines.h + - - *{prefix}*\ __Inlines.h - Inline support functions - * - *{prefix}*\ __Syms.h + - - *{prefix}*\ __Syms.h - Global symbol table header - * - *{prefix}*\ __Syms.cpp + - - *{prefix}*\ __Syms.cpp - Global symbol table C++ - * - *{prefix}{each_verilog_module}*.h + - - *{prefix}{each_verilog_module}*.h - Lower level internal header files - * - *{prefix}{each_verilog_module}*.cpp + - - *{prefix}{each_verilog_module}*.cpp - Lower level internal C++ files - * - *{prefix}{each_verilog_module}{__n}*.cpp + - - *{prefix}{each_verilog_module}{__n}*.cpp - Additional lower C++ files For --hierarchical mode, it creates: .. list-table:: - * - V\ *{hier_block}*\ / + - - V\ *{hier_block}*/ - Directory to Verilate each hierarchical block (from --hierarchical) - * - *{prefix}*\ __hierVer.d + - - *{prefix}*\ __hierVer.d - Make dependencies of the top module (from --hierarchical) - * - *{prefix}*\ _hier.mk + - - *{prefix}*\ _hier.mk - Make file for hierarchical blocks (from --make gmake) - * - *{prefix}*\ __hierMkJsonArgs.f + - - *{prefix}*\ __hierMkJsonArgs.f - Arguments for hierarchical Verilation (from --make json) - * - *{prefix}*\ __hierMkArgs.f + - - *{prefix}*\ __hierMkArgs.f - Arguments for hierarchical Verilation (from --make gmake) - * - *{prefix}*\ __hierParameters.v + - - *{prefix}*\ __hierParameters.v - Module parameters for hierarchical blocks - * - *{prefix}*\ __hier.dir - - Directory to store .dot, .vpp, .tree of top module (from --hierarchical) + - - *{prefix}*\ __hier.dir + - Directory to store .dot, .vpp, .tree of top module (from + --hierarchical) In specific debug and other modes, it also creates: .. list-table:: - * - *{prefix}*.sarif + - - *{prefix}*.sarif - SARIF diagnostics (from --diagnostics-sarif) - * - *{prefix}*.tree.json + - - *{prefix}*.tree.json - JSON tree information (from --json-only) - * - *{prefix}*.tree.meta.json + - - *{prefix}*.tree.meta.json - JSON tree metadata (from --json-only) - * - *{prefix}*\ __cdc.txt + - - *{prefix}*\ __cdc.txt - Clock Domain Crossing checks (from --cdc) - * - *{prefix}*\ __stats.txt + - - *{prefix}*\ __stats.txt - Statistics (from --stats) - * - *{prefix}*\ __idmap.txt + - - *{prefix}*\ __idmap.txt - Symbol demangling (from --protect-ids) - * - *{prefix}*\ __ver.d + - - *{prefix}*\ __ver.d - Make dependencies (from -MMD) - * - *{prefix}*\ __verFiles.dat + - - *{prefix}*\ __verFiles.dat - Timestamps (from --skip-identical) - * - *{prefix}{misc}*.dot + - - *{prefix}{misc}*.dot - Debugging graph files (from --debug) - * - *{prefix}{misc}*.tree + - - *{prefix}{misc}*.tree - Debugging files (from --debug) - * - *{prefix}*\ __inputs.vpp + - - *{prefix}*\ __inputs.vpp - Pre-processed verilog for all files (from --debug) - * - *{prefix}*\ _ *{each_verilog_base_filename}*.vpp + - - *{prefix}*\ _ *{each_verilog_base_filename}*.vpp - Pre-processed verilog for each file (from --debug) After running Make, the C++ compiler may produce the following: .. list-table:: - * - verilated{misc}*.d + - - verilated{misc}*.d - Intermediate dependencies - * - verilated{misc}*.o + - - verilated{misc}*.o - Intermediate objects - * - {mod_prefix}{misc}*.d + - - {mod_prefix}{misc}*.d - Intermediate dependencies - * - {mod_prefix}{misc}*.o + - - {mod_prefix}{misc}*.o - Intermediate objects - * - *{prefix}*\ + - - *{prefix}*\ - Final executable (from --exe) - * - lib\ *{prefix}*.a + - - lib\ *{prefix}*.a - Final archive (default lib mode) - * - libverilated.a + - - libverilated.a - Runtime for verilated model (default lib mode) - * - *{prefix}*\ __ALL.a + - - *{prefix}*\ __ALL.a - Library of all Verilated objects - * - *{prefix}*\ __ALL.cpp + - - *{prefix}*\ __ALL.cpp - Include of all code for single compile - * - *{prefix}{misc}*.d + - - *{prefix}{misc}*.d - Intermediate dependencies - * - *{prefix}{misc}*.o + - - *{prefix}{misc}*.o - Intermediate objects The Verilated executable may produce the following: .. list-table:: - * - coverage.dat - - Code coverage output, and default input filename for :command:`verilator_coverage` - * - gmon.out - - GCC/clang code profiler output, often fed into :command:`verilator_profcfunc` - * - profile.vlt + - - coverage.dat + - Code coverage output, and default input filename for + :command:`verilator_coverage` + - - gmon.out + - GCC/clang code profiler output, often fed into + :command:`verilator_profcfunc` + - - profile.vlt - --prof-pgo data file for :ref:`Thread PGO` - * - profile_exec.dat + - - profile_exec.dat - --prof-exec data file for :command:`verilator_gantt` Verilator_gantt may produce the following: .. list-table:: - * - profile_exec.vcd + - - profile_exec.vcd - Gantt report waveform output diff --git a/docs/guide/index.rst b/docs/guide/index.rst index 5df45c271..8b7271352 100644 --- a/docs/guide/index.rst +++ b/docs/guide/index.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ###################### Verilator User's Guide diff --git a/docs/guide/install-cmake.rst b/docs/guide/install-cmake.rst index 03566ac01..6d840a552 100644 --- a/docs/guide/install-cmake.rst +++ b/docs/guide/install-cmake.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _cmakeinstallation: @@ -16,16 +17,16 @@ Linux). Quick Install ============= -1. Install Python for your platform from https://www.python.org/downloads/. -2. Install CMake for your platform from https://cmake.org/download/ or +#. Install Python for your platform from https://www.python.org/downloads/. +#. Install CMake for your platform from https://cmake.org/download/ or build it from source. -3. If the compiler of your choice is MSVC, then install +#. If the compiler of your choice is MSVC, then install https://visualstudio.microsoft.com/downloads/. If the compiler of your choice is Clang, then install https://releases.llvm.org/download.html or build it from source. -4. For flex and bison use https://github.com/lexxmark/winflexbison to build +#. For flex and bison use https://github.com/lexxmark/winflexbison to build and install. -5. For build on Windows using MSVC set environment variable WIN_FLEX_BISON +#. For build on Windows using MSVC set environment variable WIN_FLEX_BISON to install directory. For build on Windows/Linux/OS-X using ninja set the environment variable FLEX_INCLUDE to the directory containing FlexLexer.h and ensure that flex/bison is available within the PATH. diff --git a/docs/guide/install.rst b/docs/guide/install.rst index 09a58a94f..99d8f66a5 100644 --- a/docs/guide/install.rst +++ b/docs/guide/install.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _installation: @@ -384,12 +385,10 @@ the files: make install - .. Docker Build Environment .. include:: ../../ci/docker/buildenv/README.rst - .. Docker Run Environment .. include:: ../../ci/docker/run/README.rst diff --git a/docs/guide/languages.rst b/docs/guide/languages.rst index 04fb44ba4..b2bd17e69 100644 --- a/docs/guide/languages.rst +++ b/docs/guide/languages.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 *************** Input Languages @@ -102,11 +103,11 @@ Time With :vlopt:`--timing`, all timing controls are supported: -* delay statements, -* event control statements not only at the top of a process, -* intra-assignment timing controls, -* net delays, -* ``wait`` statements, +- delay statements, +- event control statements not only at the top of a process, +- intra-assignment timing controls, +- net delays, +- ``wait`` statements, as well as all flavors of ``fork``. @@ -128,26 +129,26 @@ simulation (perhaps using :vlopt:`--build`) and run it. With :vlopt:`--no-timing`, all timing controls cause the :option:`NOTIMING` error, except: -* delay statements - they are ignored (as they are in synthesis), though they - do issue a :option:`STMTDLY` warning, -* intra-assignment timing controls - they are ignored, though they do issue +- delay statements - they are ignored (as they are in synthesis), though + they do issue a :option:`STMTDLY` warning, +- intra-assignment timing controls - they are ignored, though they do issue an :option:`ASSIGNDLY` warning, -* net delays - they are ignored, -* event controls at the top of the procedure, +- net delays - they are ignored, +- event controls at the top of the procedure, Forks cause this error as well, except: -* forks with no statements, -* ``fork..join`` or ``fork..join_any`` with one statement, -* forks with :vlopt:`--bbox-unsup`. +- forks with no statements, +- ``fork..join`` or ``fork..join_any`` with one statement, +- forks with :vlopt:`--bbox-unsup`. If neither :vlopt:`--timing` nor :vlopt:`--no-timing` is specified, all timing controls cause the :option:`NEEDTIMINGOPT` error, except event controls at the top of the process. Forks cause this error as well, except: -* forks with no statements, -* ``fork..join`` or ``fork..join_any`` with one statement, -* forks with :vlopt:`--bbox-unsup`. +- forks with no statements, +- ``fork..join`` or ``fork..join_any`` with one statement, +- forks with :vlopt:`--bbox-unsup`. Timing controls and forks can also be ignored in specific files or parts of files. The :option:`/*verilator&32;timing_off*/` and diff --git a/docs/guide/overview.rst b/docs/guide/overview.rst index 5ffe52ed2..bf57423a0 100644 --- a/docs/guide/overview.rst +++ b/docs/guide/overview.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ******** Overview @@ -47,9 +48,9 @@ The best place to get started is to try the :ref:`Examples`. uses the shorthand, e.g., "IEEE 1364-2005", to refer to the, e.g., 2005 version of this standard. -.. [#] SystemVerilog is defined by the `Institute of Electrical and - Electronics Engineers (IEEE) Standard for SystemVerilog - Unified - Hardware Design, Specification, and Verification Language`, Standard - 1800, released in 2005, 2009, 2012, 2017, and 2023. The Verilator - documentation uses the shorthand e.g., "IEEE 1800-2023", to refer to - the, e.g., 2023 version of this standard. +.. [#] SystemVerilog is defined by the `Institute of Electrical and Electronics + Engineers (IEEE) Standard for SystemVerilog - Unified Hardware Design, + Specification, and Verification Language`, Standard 1800, released in + 2005, 2009, 2012, 2017, and 2023. The Verilator documentation uses the + shorthand e.g., "IEEE 1800-2023", to refer to the, e.g., 2023 version of + this standard. diff --git a/docs/guide/simulating.rst b/docs/guide/simulating.rst index 89baf9ccc..6ace6014d 100644 --- a/docs/guide/simulating.rst +++ b/docs/guide/simulating.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _simulating: @@ -209,7 +210,8 @@ For simple coverage points, use the ``cover property`` construct: DefaultClock: cover property (@(posedge clk) cyc==3); -This adds a coverage point that tracks whether the condition has been observed. +This adds a coverage point that tracks whether the condition has been +observed. .. _covergroup coverage: @@ -218,8 +220,8 @@ Covergroup Coverage With :vlopt:`--coverage` or :vlopt:`--coverage-user`, Verilator will translate covergroup coverage points the user has inserted manually in -SystemVerilog code into the Verilated model. Verilator supports -coverpoints with value and transition bins, and cross points. +SystemVerilog code into the Verilated model. Verilator supports coverpoints +with value and transition bins, and cross points. .. _fsm coverage: @@ -244,15 +246,15 @@ encodings in these common forms: - Single-process FSMs, whose state dispatch is written as ``case (state)`` or as a top-level ``if`` / ``else if`` chain comparing the same state variable against known state values -- Two-process and three-block FSMs, where a clocked state register is paired - with a combinational next-state block using the same supported +- Two-process and three-block FSMs, where a clocked state register is + paired with a combinational next-state block using the same supported ``case`` or top-level ``if`` / ``else if`` dispatch forms -Scalar state encodings may be wider than 32 bits. This allows sparse -state encodings, such as high-Hamming-distance enum or localparam values, -to be preserved in the detected FSM model. Verilator uses the declared -enum item name, parameter name, or localparam name as the reported state -label where possible. +Scalar state encodings may be wider than 32 bits. This allows sparse state +encodings, such as high-Hamming-distance enum or localparam values, to be +preserved in the detected FSM model. Verilator uses the declared enum item +name, parameter name, or localparam name as the reported state label where +possible. Simple input guards are supported when they appear inside a recognized state branch, or as a top-level conjunction containing exactly one state @@ -267,18 +269,17 @@ deeply nested control recovery, or cross-module state alias tracing. The following metacomments may be attached to the state variable to steer the extracted coverage model: -- ``/*verilator fsm_state*/`` forces the variable to be treated as - FSM state. -- ``/*verilator fsm_reset_arc*/`` marks reset transitions as - user-visible reset arcs instead of defaulting to a hidden reset-only - summary. -- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch - arcs that would otherwise be skipped by the conservative extractor. +- ``/*verilator fsm_state*/`` forces the variable to be treated as FSM + state. +- ``/*verilator fsm_reset_arc*/`` marks reset transitions as user-visible + reset arcs instead of defaulting to a hidden reset-only summary. +- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch arcs that + would otherwise be skipped by the conservative extractor. -State registers may also be wrapped by a transparent instance, for -example a project flop wrapper or primitive. Such wrappers must be -described explicitly with a VLT command file action before Verilator will -use their data, state, clock, or reset connections for FSM extraction: +State registers may also be wrapped by a transparent instance, for example +a project flop wrapper or primitive. Such wrappers must be described +explicitly with a VLT command file action before Verilator will use their +data, state, clock, or reset connections for FSM extraction: .. code-block:: sv @@ -298,11 +299,10 @@ Optional reset metadata may also be supplied: fsm_register_wrapper -module "my_fsm_flop" -d "state_i" -q "state_o" -clock "clk_i" \ -reset "rst_ni" -reset_value "ResetValue" -Reset arcs are emitted only when the configured reset port has an -inferable edge in the wrapper and the configured reset value parameter is -statically resolvable. If reset metadata is incomplete, Verilator warns -and may still emit FSM state and transition coverage, but reset arcs are -omitted. +Reset arcs are emitted only when the configured reset port has an inferable +edge in the wrapper and the configured reset value parameter is statically +resolvable. If reset metadata is incomplete, Verilator warns and may still +emit FSM state and transition coverage, but reset arcs are omitted. Reset transitions are included in the collected data either way. By default, :command:`verilator_coverage` summarizes reset-only arcs rather @@ -433,11 +433,11 @@ coverage point insertions into the model and collect the coverage data. To get the coverage data from the model, write the coverage with either: -1. Using :vlopt:`--binary` or :vlopt:`--main`, and Verilator will dump +#. Using :vlopt:`--binary` or :vlopt:`--main`, and Verilator will dump coverage when the test completes to the filename specified with :vlopt:`+verilator+coverage+file+\`. -2. In the user wrapper code, typically at the end once a test passes, call +#. In the user wrapper code, typically at the end once a test passes, call ``Verilated::threadContextp()->coveragep()->write`` with an argument of the filename for the coverage data file to write coverage data to (typically "logs/coverage.dat"). @@ -502,12 +502,12 @@ how execution time is distributed in a verilated model. With the :vlopt:`--prof-exec` option, Verilator will: -* Add code to the Verilated model to record execution flow. +- Add code to the Verilated model to record execution flow. -* Add code to save profiling data in non-human-friendly form to the file +- Add code to save profiling data in non-human-friendly form to the file specified with :vlopt:`+verilator+prof+exec+file+\`. -* In multithreaded models, add code to record each macro-task's start and +- In multithreaded models, add code to record each macro-task's start and end time across several calls to eval. (What is a macro-task? See the Verilator internals document (:file:`docs/internals.rst` in the distribution.) @@ -607,8 +607,8 @@ There are two forms of profile-guided optimizations. Unfortunately, for best results, they must each be performed from the highest level code to the lowest, which means performing them separately and in this order: -* :ref:`Thread PGO` -* :ref:`Compiler PGO` +- :ref:`Thread PGO` +- :ref:`Compiler PGO` Other forms of PGO may be supported in the future, such as clock and reset toggle rate PGO, branch prediction PGO, statement execution time PGO, or @@ -674,7 +674,7 @@ multithreaded models. Please see the appropriate compiler documentation to use PGO with GCC or Clang. The process in GCC 10 was as follows: -1. Compile the Verilated model with the compiler's "-fprofile-generate" +#. Compile the Verilated model with the compiler's "-fprofile-generate" flag: .. code-block:: bash @@ -685,10 +685,10 @@ Clang. The process in GCC 10 was as follows: Or, if calling make yourself, add -fprofile-generate appropriately to your Makefile. -2. Run your simulation. This will create \*.gcda file(s) in the same +#. Run your simulation. This will create \*.gcda file(s) in the same directory as the source files. -3. Recompile the model with -fprofile-use. The compiler will read the +#. Recompile the model with -fprofile-use. The compiler will read the \*.gcda file(s). For GCC: diff --git a/docs/guide/verilating.rst b/docs/guide/verilating.rst index 977d73c38..6a856248d 100644 --- a/docs/guide/verilating.rst +++ b/docs/guide/verilating.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ********** Verilating @@ -7,21 +8,21 @@ Verilating Verilator may be used in five major ways: -* With the :vlopt:`--binary` option, Verilator will translate the design +- With the :vlopt:`--binary` option, Verilator will translate the design into an executable, via generating C++ and compiling it. See :ref:`Binary, C++ and SystemC Generation`. -* With the :vlopt:`--cc` or :vlopt:`--sc` options, Verilator will translate +- With the :vlopt:`--cc` or :vlopt:`--sc` options, Verilator will translate the design into C++ or SystemC code, respectively. See :ref:`Binary, C++ and SystemC Generation`. -* With the :vlopt:`--lint-only` option, Verilator will lint the design to +- With the :vlopt:`--lint-only` option, Verilator will lint the design to check for warnings but will not typically create any output files. -* With the :vlopt:`--json-only` option, Verilator will create JSON output +- With the :vlopt:`--json-only` option, Verilator will create JSON output that may be used to feed into other user-designed tools. -* With the :vlopt:`-E` option, Verilator will preprocess the code according +- With the :vlopt:`-E` option, Verilator will preprocess the code according to IEEE preprocessing rules and write the output to standard out. This is useful to feed other tools and to debug how "\`define" statements are expanded. @@ -127,9 +128,9 @@ Usage Users need to mark one or more moderate-size modules as hierarchy block(s). There are two ways to mark a module: -* Write :option:`/*verilator&32;hier_block*/` metacomment in HDL code. +- Write :option:`/*verilator&32;hier_block*/` metacomment in HDL code. -* Add a :option:`hier_block` line in the :ref:`Verilator Control Files`. +- Add a :option:`hier_block` line in the :ref:`Verilator Control Files`. Then pass the :vlopt:`--hierarchical` option to Verilator. @@ -145,28 +146,28 @@ Limitations Hierarchy blocks have some limitations, including: -* Internals of the hierarchy block cannot be accessed using dot (.) from +- Internals of the hierarchy block cannot be accessed using dot (.) from the upper module(s) or other hierarchy blocks, except that ports of a hierarchy block instance can be accessed from the directly enclosing nested hierarchy block, or from the top level non-hierarchical portions of the design if not a nested hierarchy block. -* Modport cannot be used at the hierarchical block boundary. +- Modport cannot be used at the hierarchical block boundary. -* The simulation speed is likely not as fast as flat Verilation, in which +- The simulation speed is likely not as fast as flat Verilation, in which all modules are globally scheduled. -* Generated clocks may not work correctly if generated in the hierarchical +- Generated clocks may not work correctly if generated in the hierarchical model and passed into another hierarchical model or the top module. -* Delays are not allowed in hierarchy blocks. +- Delays are not allowed in hierarchy blocks. But, the following usage is supported: -* Nested hierarchy blocks. A hierarchy block may instantiate other +- Nested hierarchy blocks. A hierarchy block may instantiate other hierarchy blocks. -* Parameterized hierarchy block. Parameters of a hierarchy block can be +- Parameterized hierarchy block. Parameters of a hierarchy block can be overridden using ``#(.param_name(value))`` construct. diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index d1818afd5..ce3a0d58c 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 =================== Errors and Warnings @@ -882,6 +883,22 @@ List Of Warnings with a newline." +.. option:: FINALDLY + + Error issued when a non-blocking assignment `<=` is used in a + `final` block. + + This error can be disabled. If disabled, the assignment will be + executed as a `=` blocking assignment. + + Faulty example: + + .. include:: ../../docs/gen/ex_FINALDLY_faulty.rst + + Results in: + + .. include:: ../../docs/gen/ex_FINALDLY_msg.rst + .. option:: FSMMULTI Warns that the same always block contains multiple enum-typed case @@ -1178,7 +1195,7 @@ List Of Warnings .. option:: INITIALDLY - .. TODO better example + Historical, never issued since version 5.050. Warns that the code has a delayed assignment inside of an ``initial`` or ``final`` block. If this message is suppressed, Verilator will convert @@ -2366,11 +2383,6 @@ List Of Warnings the conflict. If you run with :vlopt:`--report-unoptflat`, Verilator will suggest possible candidates for :option:`/*verilator&32;split_var*/`. - The UNOPTFLAT warning may also occur where outputs from a block of logic - are independent, but occur in the same always block. To fix this, use - the :option:`/*verilator&32;isolate_assignments*/` metacomment described - above. - Before version 5.000, the UNOPTFLAT warning may also have been due to clock enables, identified from the reported path going through a clock gating instance. To fix these, the clock_enable meta comment was used. diff --git a/docs/internals.rst b/docs/internals.rst index 7b86beff6..fb3b5281d 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -113,9 +113,9 @@ pointer to the ``AstNode`` currently being processed. There are notable sub-hierarchies of the ``AstNode`` sub-types, namely: -1. All AST nodes representing data types derive from ``AstNodeDType``. +#. All AST nodes representing data types derive from ``AstNodeDType``. -2. All AST nodes representing expressions (i.e.: anything that stands for, +#. All AST nodes representing expressions (i.e.: anything that stands for, or evaluates to a value) derive from ``AstNodeExpr``. @@ -666,23 +666,23 @@ The second visitor in ``V3Timing.cpp``, ``TimingControlVisitor``, uses the information provided by ``TimingSuspendableVisitor`` and transforms each timing control into a ``co_await``. -* event controls are turned into ``co_await`` on a trigger scheduler's +- event controls are turned into ``co_await`` on a trigger scheduler's ``trigger`` method. The awaited trigger scheduler is the one corresponding to the sentree referenced by the event control. This sentree is also referenced by the ``AstCAwait`` node, to be used later by the static scheduling code. -* if an event control waits on a local variable or class member, it uses a +- if an event control waits on a local variable or class member, it uses a local trigger which it evaluates inline. It awaits a dynamic trigger scheduler multiple times: for trigger evaluation, updates, and resumption. The dynamic trigger scheduler is responsible for resuming the coroutine at the correct point of evaluation. -* delays are turned into ``co_await`` on a delay scheduler's ``delay`` +- delays are turned into ``co_await`` on a delay scheduler's ``delay`` method. The created ``AstCAwait`` nodes also reference a special sentree related to delays, to be used later by the static scheduling code. -* ``join`` and ``join_any`` are turned into ``co_await`` on a +- ``join`` and ``join_any`` are turned into ``co_await`` on a ``VlForkSync``'s ``join`` method. Each forked process gets a ``VlForkSync::done`` call at the end. @@ -732,24 +732,24 @@ event `a` was called first - which is necessary to know. There are two functions for managing timing logic called by ``_eval()``: -* ``_timing_ready()``, which commits all coroutines whose triggers were - not set in the current iteration, -* ``_timing_resume()``, which calls `resume()` on all trigger and delay +- ``_timing_ready()``, which commits all coroutines whose triggers were not + set in the current iteration, +- ``_timing_resume()``, which calls `resume()` on all trigger and delay schedulers whose triggers were set in the current iteration. Thanks to this separation a coroutine: -* awaiting a trigger cannot be suspended and resumed in the same iteration +- awaiting a trigger cannot be suspended and resumed in the same iteration (``test_regress/t/t_timing_eval_act.v``) - which is necessary to make Verilator more predictable; this is the reason for introduction of 3rd stage in `VlTriggerScheduler` and thanks to this it is guaranteed that downstream logic will be evaluated before resumption (assuming that the coroutine wasn't already triggered in previous iteration); -* cannot be resumed before it is suspended - +- cannot be resumed before it is suspended - ``test_regress/t/t_event_control_double_excessive.v``; -* firing cannot cannot be lost - (``test_regress/t/t_event_control_double_lost.v``) - which is possible when - triggers are not evaluated right before awaiting. +- firing cannot cannot be lost + (``test_regress/t/t_event_control_double_lost.v``) - which is possible + when triggers are not evaluated right before awaiting. All coroutines are committed and resumed in the 'act' eval loop. With timing features enabled, the ``_eval()`` function takes this form: @@ -937,14 +937,15 @@ macro-task's dataset fits in one core's local caches. To achieve spatial locality, we tag each variable with the set of macro-tasks that access it. Let's call this set the "footprint" of that -variable. The variables in a given module have a set of footprints. We group -variables with identical non-empty footprints, emit those groups in deterministic -footprint-key order, then emit variables with no footprint information last. +variable. The variables in a given module have a set of footprints. We +group variables with identical non-empty footprints, emit those groups in +deterministic footprint-key order, then emit variables with no footprint +information last. -The first emitted variable in each footprint group is aligned to a cache-line -boundary. This avoids false sharing between different macro-task footprints -without building a complete pairwise-distance graph over all footprints, which -would use excessive memory on very large models. +The first emitted variable in each footprint group is aligned to a +cache-line boundary. This avoids false sharing between different macro-task +footprints without building a complete pairwise-distance graph over all +footprints, which would use excessive memory on very large models. This is an old idea. Simulators designed at DEC in the early 1990s used similar techniques to optimize both single-thread and multithread modes. @@ -1299,15 +1300,15 @@ the ```` field is `` : ``, where ```` will be used as the base name of the generated operand accessors, and ```` is one of: -1. An ``AstNode`` sub-class, defining the operand to be of that type, +#. An ``AstNode`` sub-class, defining the operand to be of that type, always no-null, and with an always null ``nextp()``. That is, the child node is always present, and is a single ``AstNode`` (as opposed to a list). -2. ``Optional[]``. This is just like in point 1 above, +#. ``Optional[]``. This is just like in point 1 above, but defines the child node to be optional, meaning it may be null. -3. ``List[AstNode sub-class]`` describes a list operand, which means the +#. ``List[AstNode sub-class]`` describes a list operand, which means the child node may have a non-null ``nextp()`` and in addition the child itself may be null, representing an empty list. @@ -1394,7 +1395,7 @@ calling ``accept`` on ``AstIf`` will look in turn for: There are three ways data is passed between visitor functions. -1. A visitor-class member variable. This is generally for passing "parent" +#. A visitor-class member variable. This is generally for passing "parent" information down to children. ``m_modp`` is a common example. It's set to NULL in the constructor, where that node (``AstModule`` visitor) sets it, then the children are iterated, then it's cleared. Children under an @@ -1404,7 +1405,7 @@ There are three ways data is passed between visitor functions. visitor; otherwise exiting the lower for will lose the upper for's setting. -2. User attributes. Each ``AstNode`` (**Note.** The AST node, not the +#. User attributes. Each ``AstNode`` (**Note.** The AST node, not the visitor) has five user attributes, which may be accessed as an integer using the ``user1()`` through ``user4()`` methods, or as a pointer (of type ``AstNUser``) using the ``user1p()`` through ``user4p()`` methods @@ -1435,7 +1436,7 @@ There are three ways data is passed between visitor functions. so it's ok to call fairly often. For example, it's commonly called on every module. -3. Parameters can be passed between the visitors in close to the "normal" +#. Parameters can be passed between the visitors in close to the "normal" function caller to callee way. This is the second ``vup`` parameter of type ``AstNUser`` that is ignored on most of the visitor functions. V3Width does this, but it proved messier than the above and is @@ -2125,19 +2126,19 @@ To print a node: ``src/.gdbinit`` and ``src/.gdbinit.py`` define handy utilities for working with JSON AST dumps. For example: -* ``jstash nodep`` - Perform a JSON AST dump and save it into GDB value +- ``jstash nodep`` - Perform a JSON AST dump and save it into GDB value history (e.g. ``$1``) -* ``jtree nodep`` - Perform a JSON AST dump and pretty print it using +- ``jtree nodep`` - Perform a JSON AST dump and pretty print it using ``astsee_verilator``. -* ``jtree $1`` - Pretty print a dump that was previously saved by +- ``jtree $1`` - Pretty print a dump that was previously saved by ``jstash``. -* ``jtree nodep -d '.file, .timeunit'`` - Perform a JSON AST dump, filter +- ``jtree nodep -d '.file, .timeunit'`` - Perform a JSON AST dump, filter out some fields and pretty print it. -* ``jtree 0x55555613dca0`` - Pretty print using address literal (rather +- ``jtree 0x55555613dca0`` - Pretty print using address literal (rather than actual pointer). -* ``jtree $1 nodep`` - Diff ``nodep`` against an older dump. +- ``jtree $1 nodep`` - Diff ``nodep`` against an older dump. A detailed description of ``jstash`` and ``jtree`` can be displayed using ``gdb``'s ``help`` command. @@ -2243,25 +2244,25 @@ Adding a New Feature Generally, what would you do to add a new feature? -1. File an issue (if there isn't already) so others know what you're +#. File an issue (if there isn't already) so others know what you're working on. -2. Make a testcase in the test_regress/t/t_EXAMPLE format, see `Testing`. +#. Make a testcase in the test_regress/t/t_EXAMPLE format, see `Testing`. -3. If grammar changes are needed, look at the IEEE 1800-2023 Appendix A, as +#. If grammar changes are needed, look at the IEEE 1800-2023 Appendix A, as src/verilog.y generally follows the same rule layout. -4. If a new Ast type is needed, add it to the appropriate V3AstNode*.h. +#. If a new Ast type is needed, add it to the appropriate V3AstNode*.h. Follow the convention described above about the AstNode type hierarchy. Ordering of definitions is enforced by ``astgen``. -5. Now you can run ``test_regress/t/t_.py --debug`` and it'll +#. Now you can run ``test_regress/t/t_.py --debug`` and it'll probably fail, but you'll see a ``test_regress/obj_dir/t_/*.tree`` file which you can examine to see if the parsing worked. See also the sections above on debugging. -6. Modify the later visitor functions to process the new feature as needed. +#. Modify the later visitor functions to process the new feature as needed. Adding a New Pass diff --git a/docs/security.rst b/docs/security.rst index 4080bad92..3adb6c8d9 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -1,6 +1,7 @@ -.. for github, vim: syntax=reStructuredText -.. SPDX-FileCopyrightText: 2025-2026 Wilson Snyder -.. SPDX-License-Identifier: CC0-1.0 +.. + for github, vim: syntax=reStructuredText + SPDX-FileCopyrightText: 2025-2026 Wilson Snyder + SPDX-License-Identifier: CC0-1.0 Security Policy =============== diff --git a/docs/spelling.txt b/docs/spelling.txt index a3565cb1d..fa7fae5e9 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -861,6 +861,7 @@ hx hyperthreading hyperthreads icecream +ico idmap ifdef ifdefed @@ -916,6 +917,7 @@ linters linux liu livelock +liveness lldb ln loc @@ -924,6 +926,7 @@ localparams localtime logicals longint +lookups lossy lsb lubc diff --git a/include/AGENTS.md b/include/AGENTS.md new file mode 100644 index 000000000..0ac1fcbfe --- /dev/null +++ b/include/AGENTS.md @@ -0,0 +1,60 @@ + + +# include/ -- Verilator runtime library + +Covers the C++ runtime under `include/` (`verilated.h/.cpp`, `verilated_*.h/.cpp`) +that every generated model links against. Read the repository-root +[AGENTS.md](../AGENTS.md) first. The rules here differ from `src/`: this code +ships to users, runs every simulation cycle, and must stay portable and fast. + +______________________________________________________________________ + +# Orientation + +- **This is the runtime, not the compiler.** The passes in `src/` *emit* C++ that + calls into this library; this code then *runs* during simulation. Optimize it + for execution speed and portability, not for compile-time clarity. +- **Key files:** `verilated.h` (core model API), `verilated_types.h` (data + types), `verilated_random.cpp` (constrained-random runtime), `verilated_cov.*` + (coverage), `verilated_threads.*` (MT runtime), `verilated_timing.*` + (`--timing` runtime), `verilated_vcd_c.*`/`verilated_fst_c.*` (tracing). +- A runtime-only fix lives entirely here and does not rebuild `verilator_bin`. + +______________________________________________________________________ + +# Before you open a PR + +- **C++14 baseline.** The runtime must build under `--no-timing` with C++14; C++20 + features are allowed only in `--timing` code paths. +- **Public API naming and docs.** Prefix public classes and types with + `Verilated`/`Vl` to avoid collisions with user code, and document the API with + `///` comments -- they feed doc generation. +- **No exceptions in runtime code** -- use error returns or assertions; exceptions + add overhead on every path. +- **Use fixed-width model types** (`CData`/`SData`/`IData`/`QData`/`VlWide`), never + `size_t`, for model data. Process wide data word-by-word (`VL_ZERO_W`, + `VL_MEMCPY_W`), never bit-by-bit or byte-by-byte. +- **Do all string parsing at verilation time** -- never parse strings during + simulation; emit structured data or compile-time hints instead. +- **Keep per-cycle code lean.** Do not add vtables to high-frequency objects + (8 bytes per instance); guard optional features behind + `hasClasses()`/`hasEvents()`-style checks; functions called per cycle must avoid + mutexes -- use atomics or lockless designs. +- **Emit no runtime loops** the compiler could have expanded at verilation time; + prefer a single runtime call. +- **Thread safety.** Annotate with the hierarchy `VL_PURE` > `VL_MT_SAFE` > + `VL_MT_STABLE`; annotations must match the implementation. Annotate + mutex-protected members with `VL_GUARDED_BY` and document acquisition ordering. + Prefer has-a over is-a: a guarded class wrapping the unguarded internal one, with + the guarded version as the default public API. +- **Keep runtime and compiler headers separate** -- never include `verilated.h` + into the compiler in `src/`. + +## File-specific rules + +| File | Rule | +|---|---| +| `verilated_random.cpp` | Emit only portable SMT-LIB 2.6 -- no solver-specific or MaxSMT extensions; the generated solver text is the model's runtime constraint interface | +| `verilated_cov.cpp` | Coverage runtime is shared by all models -- keep per-point overhead minimal and the on-disk format stable for `verilator_coverage` | diff --git a/include/fstcpp/fstcpp_assertion.h b/include/fstcpp/fstcpp_assertion.h index e9606f665..8a4d549ad 100644 --- a/include/fstcpp/fstcpp_assertion.h +++ b/include/fstcpp/fstcpp_assertion.h @@ -81,6 +81,12 @@ std::abort(); \ } +#define FST_FAIL_STRING(s) \ + do { \ + std::cerr << (s) << std::endl; \ + std::abort(); \ + } while (0) + // We turn on all DCHECKs to CHECKs temporarily for better safety. #if 1 # define FST_DCHECK(a) FST_CHECK(a) diff --git a/include/fstcpp/fstcpp_variable_info.h b/include/fstcpp/fstcpp_variable_info.h index 470bb8ef1..0074705b1 100644 --- a/include/fstcpp/fstcpp_variable_info.h +++ b/include/fstcpp/fstcpp_variable_info.h @@ -335,10 +335,10 @@ public: // Double variables should not use these array-based emitValueChange overloads. // We implement them to satisfy the VairableInfo::dispatchHelper template instantiation. void emitValueChange(uint64_t, const uint32_t *, EncodingType) { - throw std::runtime_error("emitValueChange(uint32_t*) not supported for Double"); + FST_FAIL_STRING("emitValueChange(uint32_t*) not supported for Double"); } void emitValueChange(uint64_t, const uint64_t *, EncodingType) { - throw std::runtime_error("emitValueChange(uint64_t*) not supported for Double"); + FST_FAIL_STRING("emitValueChange(uint64_t*) not supported for Double"); } void dumpInitialBits(std::vector &buf) const { diff --git a/include/fstcpp/fstcpp_writer.cpp b/include/fstcpp/fstcpp_writer.cpp index 2756e23b9..386a76162 100644 --- a/include/fstcpp/fstcpp_writer.cpp +++ b/include/fstcpp/fstcpp_writer.cpp @@ -193,7 +193,7 @@ Handle Writer::createVar( // (void)type; // (void)svt; // (void)sdt; -// throw std::runtime_error("TODO"); +// FST_FAIL_STRING("TODO"); // return 0; // } // LCOV_EXCL_STOP @@ -353,11 +353,7 @@ void compressUsingZlib( uncompressed_size, level ); - if (z_status != Z_OK) { - throw std::runtime_error( - "Failed to compress data with zlib, error code: " + std::to_string(z_status) - ); - } + FST_CHECK_EQ(z_status, Z_OK); compressed_data.resize(compressed_bound); } diff --git a/include/verilated.cpp b/include/verilated.cpp index 6bf868cad..9cbb58ffc 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -318,6 +318,13 @@ void VL_PRINTF_MT(const char* formatp, ...) VL_MT_SAFE { }}); } +void VL_FFLUSH_MT() VL_MT_SAFE { + va_list ap; + VerilatedThreadMsgQueue::post(VerilatedMsg{[=]() { // + Verilated::runFlushCallbacks(); + }}); +} + template static size_t _vl_snprintf_string(std::string& str, const char* format, snprintf_args_ts... args) VL_MT_SAFE { @@ -3028,40 +3035,97 @@ void VerilatedContext::assertOn(bool flag) VL_MT_SAFE { } bool VerilatedContext::assertOnGet(VerilatedAssertType_t type, VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE { - // Check if selected directive type bit in the assertOn is enabled for assertion type. - // Note: it is assumed that this is checked only for one type at the time. - - // Flag unspecified assertion types as disabled. - if (type == 0) return false; - - // Get index of 3-bit group guarding assertion type status. - // Since the assertOnGet is generated __always__ for a single assert type, we assume that only - // a single bit will be set. Thus, ceil log2 will work fine. - VL_DEBUG_IFDEF(assert((type & (type - 1)) == 0);); - const IData typeMaskPosition = VL_CLOG2_I(type); - - // Check if directive type bit is enabled in corresponding assertion type bits. - return m_s.m_assertOn & (directive << (typeMaskPosition * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH)); + return assertCtlGet(VerilatedAssertCtlQuery::ASSERT_CTL_ON, type, directive); +} +uint32_t VerilatedContext::assertOnMask(VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_PURE { + // Place the directive bits at each selected assertion type's 3-bit group. + uint32_t mask = 0; + for (int i = 0; i < std::numeric_limits::digits; ++i) { + if (VL_BITISSET_I(types, i)) mask |= directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH); + } + return mask; } void VerilatedContext::assertOnSet(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { - // For each assertion type, set directive bits. - - // Iterate through all positions of assertion type bits. If bit for this assertion type is set, - // set directive type bits mask at this group index. - for (int i = 0; i < std::numeric_limits::digits; ++i) { - if (VL_BITISSET_I(types, i)) - m_s.m_assertOn |= directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH); - } + m_s.m_assertOn |= assertOnMask(types, directives); } void VerilatedContext::assertOnClear(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { - // Iterate through all positions of assertion type bits. If bit for this assertion type is set, - // clear directive type bits mask at this group index. - for (int i = 0; i < std::numeric_limits::digits; ++i) { - if (VL_BITISSET_I(types, i)) - m_s.m_assertOn &= ~(directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH)); + m_s.m_assertOn &= ~assertOnMask(types, directives); +} +void VerilatedContext::assertCtl(uint32_t controlType, VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { + // IEEE 1800-2023 Table 20-5 control_type. Lock freezes the On/Off state of the + // selected bits until Unlock; On/Off/Kill leave locked bits unchanged. + const uint32_t mask = assertOnMask(types, directives); + const uint32_t lockedMask = mask & ~m_s.m_assertLock; + switch (controlType) { + case 1: // Lock + m_s.m_assertLock |= mask; + break; + case 2: // Unlock + m_s.m_assertLock &= ~mask; + break; + case 3: // On + m_s.m_assertOn |= lockedMask; + break; + case 4: // Off + m_s.m_assertOn &= ~lockedMask; + break; + case 5: { // Kill + m_s.m_assertOn &= ~lockedMask; + for (int slot = 0; slot < static_cast(ASSERT_CONTROL_SLOT_COUNT); ++slot) { + if (VL_BITISSET_I(lockedMask, slot)) { m_s.m_assertKill[slot]++; } + } + break; } + case 6: // PassOn + m_s.m_assertPassOnVacuous |= lockedMask; + m_s.m_assertPassOnNonvacuous |= lockedMask; + break; + case 7: // PassOff + m_s.m_assertPassOnVacuous &= ~lockedMask; + m_s.m_assertPassOnNonvacuous &= ~lockedMask; + break; + case 8: // FailOn + m_s.m_assertFailOn |= lockedMask; + break; + case 9: // FailOff + m_s.m_assertFailOn &= ~lockedMask; + break; + case 10: // NonvacuousOn + m_s.m_assertPassOnNonvacuous |= lockedMask; + break; + case 11: // VacuousOff + m_s.m_assertPassOnVacuous &= ~lockedMask; + break; + default: + VL_WARN_MT("", 0, "", + ("Bad $assertcontrol control_type '" + std::to_string(controlType) + + "' (IEEE 1800-2023 Table 20-5)") + .c_str()); + } +} +uint32_t +VerilatedContext::assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type, + VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE { + const uint32_t mask = assertOnMask(type, directive); + if (!mask) return 0; + switch (query) { // LCOV_EXCL_BR_LINE + case VerilatedAssertCtlQuery::ASSERT_CTL_ON: return (m_s.m_assertOn & mask) != 0; + case VerilatedAssertCtlQuery::ASSERT_CTL_KILL: + assert(mask && (mask & (mask - 1)) == 0); + return m_s.m_assertKill[VL_CLOG2_I(mask)]; + case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_VACUOUS: + return (m_s.m_assertPassOnVacuous & mask) != 0; + case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_NONVACUOUS: + return (m_s.m_assertPassOnNonvacuous & mask) != 0; + case VerilatedAssertCtlQuery::ASSERT_CTL_FAIL_ON: return (m_s.m_assertFailOn & mask) != 0; + default: // LCOV_EXCL_START + VL_FATAL_MT("", 0, "", "Internal: Bad assertCtlGet query"); + VL_UNREACHABLE; + } // LCOV_EXCL_STOP } void VerilatedContext::calcUnusedSigs(bool flag) VL_MT_SAFE { const VerilatedLockGuard lock{m_mutex}; @@ -3893,6 +3957,7 @@ std::unique_ptr VerilatedModel::traceConfig() const { retu // cppcheck-suppress unusedFunction // Used by applications uint32_t VerilatedVarProps::entSize() const VL_MT_SAFE { + if (m_entSize) return m_entSize; uint32_t size = 1; switch (vltype()) { case VLVT_PTR: size = sizeof(void*); break; @@ -4009,6 +4074,27 @@ VerilatedVar* VerilatedScope::varInsert(const char* namep, void* datap, bool isP return &(m_varsp->find(namep)->second); } +VerilatedVar* VerilatedScope::varInsertSized(const char* namep, void* datap, bool isParam, + VerilatedVarType vltype, int vlflags, int udims, + uint32_t entSize...) VL_MT_UNSAFE { + if (!m_varsp) m_varsp = new VerilatedVarNameMap; + VerilatedVar var(namep, datap, vltype, static_cast(vlflags), udims, 0, + isParam, entSize); + + va_list ap; + va_start(ap, entSize); + for (int i = 0; i < udims; ++i) { + const int msb = va_arg(ap, int); + const int lsb = va_arg(ap, int); + var.m_unpacked[i].m_left = msb; + var.m_unpacked[i].m_right = lsb; + } + va_end(ap); + + m_varsp->emplace(namep, std::move(var)); + return &(m_varsp->find(namep)->second); +} + VerilatedVar* VerilatedScope::forceableVarInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, void* forceReadSignalData, diff --git a/include/verilated.h b/include/verilated.h index 707ede5ce..59ae1ad8c 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -137,7 +137,9 @@ enum VerilatedVarType : uint8_t { VLVT_UINT64, // AKA QData VLVT_WDATA, // AKA VlWide VLVT_STRING, // C++ string - VLVT_REAL // AKA double + VLVT_REAL, // AKA double + VLVT_STRUCT, // SystemVerilog unpacked struct + VLVT_UNION // SystemVerilog unpacked union }; enum VerilatedVarFlags : uint32_t { @@ -154,7 +156,8 @@ enum VerilatedVarFlags : uint32_t { VLVF_CONTINUOUSLY = (1 << 11), // Is continously assigned VLVF_FORCEABLE = (1 << 12), // Forceable VLVF_SIGNED = (1 << 13), // Signed integer - VLVF_BITVAR = (1 << 14) // Four state bit (vs two state logic) + VLVF_BITVAR = (1 << 14), // Four state bit (vs two state logic) + VLVF_NET = (1 << 15) // Net object }; // IEEE 1800-2023 Table 20-6 @@ -175,6 +178,15 @@ enum class VerilatedAssertDirectiveType : uint8_t { DIRECTIVE_TYPE_COVER = (1 << 1), DIRECTIVE_TYPE_ASSUME = (1 << 2), }; + +/// Runtime query selector for assertion-control state +enum class VerilatedAssertCtlQuery : uint8_t { + ASSERT_CTL_ON, + ASSERT_CTL_KILL, + ASSERT_CTL_PASS_ON_VACUOUS, + ASSERT_CTL_PASS_ON_NONVACUOUS, + ASSERT_CTL_FAIL_ON, +}; using VerilatedAssertType_t = std::underlying_type::type; using VerilatedAssertDirectiveType_t = std::underlying_type::type; @@ -356,6 +368,10 @@ private: static constexpr size_t ASSERT_ON_WIDTH = ASSERT_DIRECTIVE_TYPE_MASK_WIDTH * std::numeric_limits::digits + 1; + // Build the assertion-control bit mask for the given assertion x directive types. + static uint32_t assertOnMask(VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_PURE; + static constexpr size_t ASSERT_CONTROL_SLOT_COUNT = ASSERT_ON_WIDTH - 1; protected: // TYPES @@ -375,6 +391,16 @@ protected: // for each VerilatedAssertType we store // 3-bits, one for each directive type. Last // bit guards internal directive types. + std::atomic m_assertLock{0}; // Locked assertion bits (IEEE 1800-2023 20.11 + // Lock/Unlock); same layout as m_assertOn. While + // a bit is locked, On/Off/Kill leave it unchanged. + std::atomic m_assertPassOnVacuous{ + std::numeric_limits::max()}; // Enabled vacuous pass actions + std::atomic m_assertPassOnNonvacuous{ + std::numeric_limits::max()}; // Enabled nonvacuous pass actions + std::atomic m_assertFailOn{ + std::numeric_limits::max()}; // Enabled fail actions + std::array, ASSERT_CONTROL_SLOT_COUNT> m_assertKill{}; bool m_calcUnusedSigs = false; // Waves file on, need all signals calculated bool m_fatalOnError = true; // Fatal on $stop/non-fatal error bool m_fatalOnVpiError = true; // Fatal on vpi error/unsupported @@ -484,6 +510,13 @@ public: /// Clear enabled status for given assertion types void assertOnClear(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE; + /// Apply assertion control for given control, assertion, and directive types + void assertCtl(uint32_t controlType, VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_MT_SAFE; + /// Get assertion-control runtime state. Boolean queries return 0/1, Kill returns + /// the generation count. + uint32_t assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type, + VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE; /// Return if calculating of unused signals (for traces) bool calcUnusedSigs() const VL_MT_SAFE { return m_s.m_calcUnusedSigs; } /// Enable calculation of unused signals (for traces) @@ -751,6 +784,9 @@ public: // But internals only - called from verilated modules, VerilatedSyms void exportInsert(int finalize, const char* namep, void* cb) VL_MT_UNSAFE; VerilatedVar* varInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, int udims, int pdims, ...) VL_MT_UNSAFE; + VerilatedVar* varInsertSized(const char* namep, void* datap, bool isParam, + VerilatedVarType vltype, int vlflags, int udims, uint32_t entSize, + ...) VL_MT_UNSAFE; VerilatedVar* forceableVarInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, void* forceReadSignalData, const char* forceReadSignalName, diff --git a/include/verilated_cov_model.h b/include/verilated_cov_model.h new file mode 100644 index 000000000..519fd214c --- /dev/null +++ b/include/verilated_cov_model.h @@ -0,0 +1,63 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage model interfaces +/// +/// Defines interface classes to runtime covergroup coverage-collection classes. +/// These are used to query coverage achievement at runtime, and (future) +/// when writing coverage to the coverage database. +/// +//============================================================================= + +#ifndef VERILATOR_VERILATED_COV_MODEL_H_ +#define VERILATOR_VERILATED_COV_MODEL_H_ + +#include "verilatedos.h" + +#include +#include + +// Per-bin classification. A bin's kind is which set it lives in (structural), +// not a per-bin field. Only Normal feeds coverage(); the rest are recorded. +// Enumerators are 'KIND_'-prefixed because the bare LRM terms collide with +// macros (e.g. IGNORE), which the preprocessor would expand. +enum class VlCovBinKind : uint8_t { + KIND_NORMAL = 0, // Base coverage-collecting bin + KIND_DEFAULT = 1, // Bin declared with 'default' range (which is excluded per LRM) + KIND_IGNORE = 2, // Ignore bin + KIND_ILLEGAL = 3 // Illegal bin +}; + +//============================================================================= +// VlCoverpointIf +/// Read-side view of a coverpoint. The writer queries bins by index; the +/// implementor computes names/kinds on demand. Bounded bin count, so random +/// access by index is the primary usage. + +class VlCoverpointIf VL_NOT_FINAL { +public: + // CONSTRUCTORS + virtual ~VlCoverpointIf() = default; + + // METHODS + // All bins, across every set; index range [0, binCount()) + virtual int binCount() const = 0; + // Bin name in declaration order (e.g. "myBin" or "b[3]") + virtual std::string binName(int i) const = 0; + virtual VlCovBinKind binKind(int i) const = 0; + // Bins covered / effective total (Normal set only) for the coverage calc + virtual void coverageParts(double& covered, double& total) const = 0; +}; + +#endif // Guard diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp new file mode 100644 index 000000000..6dc7006f1 --- /dev/null +++ b/include/verilated_covergroup.cpp @@ -0,0 +1,78 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage collection runtime implementation +/// +/// Compiled and linked when "verilator --coverage" is used with covergroups. +/// +//============================================================================= + +#include "verilatedos.h" + +#include "verilated_covergroup.h" + +#include "verilated_cov.h" + +void VlCoverpoint::init(const char* hier, uint32_t atLeast, int nBins) { + m_hier = hier; + m_atLeast = atLeast; + m_total = nBins; + m_counts.assign(nBins, 0); +} + +void VlCoverpoint::addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name, + const char* file, int line, int col) { + m_namers.emplace_back(set, count, m_nextBase, naming, name, file, line, col); + m_nextBase += count; + if (set == VlCovBinKind::KIND_NORMAL) m_normal += count; +} + +const VlCovNamer& VlCoverpoint::namerFor(int i) const { + // Namers are appended in ascending, contiguous index order covering [0, m_total), + // and i is always a valid bin index, so the matching namer always exists. + for (const VlCovNamer& nm : m_namers) { + if (i < nm.base() + nm.count()) return nm; + } + VL_UNREACHABLE; +} + +std::string VlCoverpoint::binName(int i) const { + const VlCovNamer& nm = namerFor(i); + std::string name = nm.name(); + if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(i - nm.base()) + ']'; + return name; +} + +void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) { + for (int i = 0; i < binCount(); ++i) { + const VlCovNamer& nm = namerFor(i); + const VlCovBinKind kind = binKind(i); + const std::string binp = binName(i); + const std::string full = m_hier + "." + binp; + const std::string lineStr = std::to_string(nm.line()); + const std::string colStr = std::to_string(nm.col()); + if (kind == VlCovBinKind::KIND_NORMAL) { + VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", + nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", + binp.c_str()); + } else { + const char* const binType = kind == VlCovBinKind::KIND_IGNORE ? "ignore" + : kind == VlCovBinKind::KIND_ILLEGAL ? "illegal" + : "default"; + VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", + nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", + binp.c_str(), "bin_type", binType); + } + } +} diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h new file mode 100644 index 000000000..1ffc98ff7 --- /dev/null +++ b/include/verilated_covergroup.h @@ -0,0 +1,144 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage collection runtime +/// +/// VlCoverpoint owns per-instance bin-count storage for one coverpoint, +/// computes coverage, builds bin names on demand, and registers bins with the +/// coverage database. It implements the VlCoverpointIf read interface. +/// +/// Generated covergroup code holds one VlCoverpoint per coverpoint, configures +/// it in the constructor (init + add*Namer), increments bins from sample(), +/// and registers via registerBins(). +/// +//============================================================================= + +#ifndef VERILATOR_VERILATED_COVERGROUP_H_ +#define VERILATOR_VERILATED_COVERGROUP_H_ + +#include "verilatedos.h" + +#include "verilated_cov_model.h" + +#include +#include +#include + +class VerilatedCovContext; + +// How a namer builds the names of the bins it covers. +enum class VlCovBinNaming : uint8_t { + Single, // "" one bin + Array, // "[i]" bins b[N] value array +}; + +// Specifies the naming scheme for a range of bins, allowing the +// specific name to be computed on-demand. +// All name strings are borrowed literals from the generated code. +class VlCovNamer final { + // MEMBERS + VlCovBinKind m_set; // which set the bins belong to + int m_count; // bins this namer covers (1 for Single) + int m_base; // first bin index (declaration order), assigned on append + VlCovBinNaming m_naming; // how bin names are built + const char* m_name; // bin name (Single) or array base name (Array) + const char* m_file; // declaration file + int m_line; // declaration line + int m_col; // declaration column + +public: + // CONSTRUCTORS + VlCovNamer(VlCovBinKind set, int count, int base, VlCovBinNaming naming, const char* name, + const char* file, int line, int col) + : m_set{set} + , m_count{count} + , m_base{base} + , m_naming{naming} + , m_name{name} + , m_file{file} + , m_line{line} + , m_col{col} {} + + // METHODS + VlCovBinKind set() const { return m_set; } + int count() const { return m_count; } + int base() const { return m_base; } + VlCovBinNaming naming() const { return m_naming; } + const char* name() const { return m_name; } + const char* file() const { return m_file; } + int line() const { return m_line; } + int col() const { return m_col; } +}; + +//============================================================================= +// VlCoverpoint +/// Per-instance coverpoint runtime. Bins are stored in declaration order; a +/// bin's set/name come from the owning namer. coverage() is computed on demand +/// by scanning bin counts, keeping the sample() hot path a plain counter bump. + +class VlCoverpoint final : public VlCoverpointIf { + // MEMBERS + std::string m_hier; // "covergroup.coverpoint" + uint32_t m_atLeast = 1; // option.at_least (coverpoint-wide) + int m_total = 0; // bins across all sets + int m_normal = 0; // Normal bins (coverage denominator) + int m_nextBase = 0; // running append cursor + std::vector m_counts; // [m_total], one per bin + std::vector m_namers; // appended in declaration order + + // PRIVATE METHODS + const VlCovNamer& namerFor(int i) const; // obtain the bin-specific name producer + void addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name, + const char* file, int line, int col); + +public: + // CONSTRUCTORS + VlCoverpoint() = default; + + // METHODS + // ---- configuration (from generated constructor) ---- + void init(const char* hier, uint32_t atLeast, int nBins); + void addSingleNamer(VlCovBinKind set, const char* name, const char* file, int line, int col) { + addNamer(set, 1, VlCovBinNaming::Single, name, file, line, col); + } + void addArrayNamer(VlCovBinKind set, int count, const char* name, const char* file, int line, + int col) { + addNamer(set, count, VlCovBinNaming::Array, name, file, line, col); + } + void registerBins(VerilatedCovContext* covcontextp, const char* page); + + // ---- hot path (from generated sample()) ---- + void incrementBin(int i) { ++m_counts[i]; } // Normal bin: count only + void recordHit(int i) { ++m_counts[i]; } // Ignore/Illegal/Default: count only + + // ---- VlCoverpointIf ---- + int binCount() const override { return m_total; } + std::string binName(int i) const override; + VlCovBinKind binKind(int i) const override { return namerFor(i).set(); } + void coverageParts(double& covered, double& total) const override { + // Count Normal bins that reached option.at_least on demand, so the hot + // path (incrementBin) stays a plain counter bump. + int numCovered = 0; + for (const VlCovNamer& nm : m_namers) { + if (nm.set() != VlCovBinKind::KIND_NORMAL) continue; + for (int i = nm.base(); i < nm.base() + nm.count(); ++i) { + if (m_counts[i] >= m_atLeast) ++numCovered; + } + } + covered = numCovered; + total = m_normal; + } +}; + +#endif // Guard diff --git a/include/verilated_force.h b/include/verilated_force.h index d50d27c18..5cbafdc62 100644 --- a/include/verilated_force.h +++ b/include/verilated_force.h @@ -101,12 +101,13 @@ using VlForceStorageType = typename VlForceStorageTypeOf>::ty class VlForceVec final { private: struct Entry final { - int m_lsb; // Inclusive lower bit - int m_msb; // Inclusive upper bit + int m_lsb; // Inclusive lower bit for scalar path or element index for unpacked + int m_msb; // Inclusive upper bit for scalar path or element index for unpacked int m_rhsLsb; // Destination index that maps to RHS index 0 const void* m_rhsDatap; // Pointer to RHS storage - - bool operator<(const Entry& other) const { return m_msb < other.m_msb; } + int m_bitLsb = 0; + int m_bitMsb = 0; + int m_elemWidth = 0; }; std::vector m_entries; // Sorted by msb, non-overlapping @@ -134,6 +135,41 @@ private: return it; } + std::size_t trimElementBitRange(int elem, int bitLsb, int bitMsb) { + auto it = std::lower_bound(m_entries.begin(), m_entries.end(), elem, + [](const Entry& e, int idx) { return e.m_msb < idx; }); + while (it != m_entries.end() && it->m_lsb <= elem) { + if (it->m_elemWidth == 0 || it->m_bitMsb < bitLsb || it->m_bitLsb > bitMsb) { + ++it; + continue; + } + if (it->m_bitLsb < bitLsb && it->m_bitMsb > bitMsb) { + Entry high = *it; + high.m_bitLsb = bitMsb + 1; + it->m_bitMsb = bitLsb - 1; + m_entries.insert(it + 1, high); + break; + } + if (it->m_bitLsb < bitLsb) { + it->m_bitMsb = bitLsb - 1; + ++it; + continue; + } + if (it->m_bitMsb > bitMsb) { + it->m_bitLsb = bitMsb + 1; + break; + } + it = m_entries.erase(it); + } + auto ins = std::lower_bound(m_entries.begin(), m_entries.end(), elem, + [](const Entry& e, int idx) { return e.m_msb < idx; }); + while (ins != m_entries.end() && ins->m_lsb <= elem + && (ins->m_elemWidth == 0 || ins->m_bitLsb <= bitLsb)) { + ++ins; + } + return static_cast(ins - m_entries.begin()); + } + static QData extractRhsChunk(const Entry& entry, int rhsLsb, int width) { assert(width > 0 && width <= VL_QUADSIZE); assert(rhsLsb >= 0); @@ -195,6 +231,22 @@ private: return *static_cast*>(entry.m_rhsDatap); } + template + static typename std::enable_if::value, Elem>::type + blendElem(Elem cur, const Entry& e) { + const Entry bitEntry{e.m_bitLsb, e.m_bitMsb, e.m_rhsLsb, e.m_rhsDatap, 0, 0, 0}; + return applyEntry(cur, bitEntry); + } + + template + static typename std::enable_if::value, Elem>::type blendElem(Elem cur, + const Entry& e) { + Elem res = cur; + const Entry bitEntry{e.m_bitLsb, e.m_bitMsb, e.m_rhsLsb, e.m_rhsDatap, 0, 0, 0}; + applyEntry(res, bitEntry, e.m_bitLsb, e.m_bitMsb, 0); + return res; + } + template typename std::enable_if::value>::type applyEntries(T& val) const { for (const auto& entry : m_entries) { @@ -229,15 +281,19 @@ public: using ElemRef = decltype(VlForceArrayIndexer::elem(result, static_cast(0))); using Elem = VlForceBaseType; - const int total = static_cast(VlForceArrayIndexer::size); for (const auto& entry : m_entries) { - const Elem* const rhsBasep = static_cast(entry.m_rhsDatap); const int startIdx = entry.m_lsb; const int endIdx = entry.m_msb; for (int idx = startIdx; idx <= endIdx; idx++) { - const int rhsIndex = idx - entry.m_rhsLsb; const std::size_t uidx = static_cast(idx); - VlForceArrayIndexer::elem(result, uidx) = rhsBasep[rhsIndex]; + Elem& dst = VlForceArrayIndexer::elem(result, uidx); + if (entry.m_elemWidth == 0) { + const Elem* const rhsBasep = static_cast(entry.m_rhsDatap); + const int rhsIndex = idx - entry.m_rhsLsb; + dst = rhsBasep[rhsIndex]; + } else { + dst = blendElem(dst, entry); + } } } return result; @@ -250,14 +306,18 @@ public: T readIndex(T origVal, int index) const { if (m_entries.empty()) return origVal; - const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index, - [](const Entry& e, int idx) { return e.m_msb < idx; }); - if (it != m_entries.end() && it->m_lsb <= index) { - const int rhsIndex = index - it->m_rhsLsb; - const T* const rhsBasep = static_cast(it->m_rhsDatap); - return rhsBasep[rhsIndex]; + T result = origVal; + for (auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index, + [](const Entry& e, int idx) { return e.m_msb < idx; }); + it != m_entries.end() && it->m_lsb <= index; ++it) { + if (it->m_elemWidth == 0) { + const int rhsIndex = index - it->m_rhsLsb; + result = static_cast(it->m_rhsDatap)[rhsIndex]; + } else { + result = blendElem(result, *it); + } } - return origVal; + return result; } IData readSelI(int lbits, WDataInP valp, int lsb, int width) const { @@ -291,11 +351,28 @@ public: m_entries.insert(it, {lsb, msb, rhsLsb, rhsDatap}); } + void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb, int bitLsb, int bitMsb, + int elemWidth) { + assert(lsb == msb); + assert(rhsDatap); + assert(elemWidth > 0); + assert(0 <= bitLsb && bitLsb <= bitMsb && bitMsb < elemWidth); + const std::size_t at = trimElementBitRange(lsb, bitLsb, bitMsb); + m_entries.insert(m_entries.begin() + at, + Entry{lsb, msb, rhsLsb, rhsDatap, bitLsb, bitMsb, elemWidth}); + } + void release(int lsb, int msb) { assert(lsb <= msb); trimEntries(lsb, msb); } + void release(int lsb, int msb, int bitLsb, int bitMsb) { + assert(lsb == msb); + assert(bitLsb <= bitMsb); + trimElementBitRange(lsb, bitLsb, bitMsb); + } + void touch() {} }; diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index db4c39e5c..d69bca256 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -74,10 +74,8 @@ extern void VL_FATAL_MT(const char* filename, int linenum, const char* hier, extern void VL_WARN_MT(const char* filename, int linenum, const char* hier, const char* msg) VL_MT_SAFE; -// clang-format off /// Print a string, multithread safe. Eventually VL_PRINTF will get called. extern void VL_PRINTF_MT(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; -// clang-format on /// Print a debug message from internals with standard prefix, with printf style format extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; @@ -85,6 +83,9 @@ extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; /// Print a debug message from string via VL_DBG_MSGF inline void VL_DBG_MSGS(const std::string& str) VL_MT_SAFE { VL_DBG_MSGF("%s", str.c_str()); } +/// Flush stdout +extern void VL_FFLUSH_MT() VL_MT_SAFE; + // EMIT_RULE: VL_RANDOM: oclean=dirty inline IData VL_RANDOM_I() VL_MT_SAFE { return vl_rand64(); } inline QData VL_RANDOM_Q() VL_MT_SAFE { return vl_rand64(); } @@ -268,6 +269,41 @@ static inline VlQueue VL_CVT_UNPACK_TO_Q(const VlUnpacked& q) VL_ return ret; } +// Masked match functions +static inline IData VL_MATCHMASKED_I(int, IData lhs, WDataInP matchp) VL_PURE { + size_t i = 0; + while (true) { + const IData mask = matchp[i * 2]; + const IData bits = matchp[i * 2 + 1]; + if ((mask & lhs) == bits) break; + ++i; + } + return i; +} +static inline IData VL_MATCHMASKED_Q(int, QData lhs, WDataInP matchp) VL_PURE { + size_t i = 0; + while (true) { + const QData mask = VL_SET_QW(matchp + i * 4); + const QData bits = VL_SET_QW(matchp + i * 4 + 2); + if ((mask & lhs) == bits) break; + ++i; + } + return i; +} +static inline IData VL_MATCHMASKED_W(int lbits, WDataInP lhsp, WDataInP matchp) VL_MT_SAFE { + const int iwords = VL_WORDS_I(lbits); + size_t i = 0; + while (true) { + const WDataInP maskp = matchp + (i * iwords * 2); + const WDataInP bitsp = matchp + (i * iwords * 2 + iwords); + EData diff = 0; + for (int j = 0; j < iwords; ++j) diff |= (maskp[j] & lhsp[j]) ^ bitsp[j]; + if (!diff) break; + ++i; + } + return i; +} + // Return double from lhs (numeric) unsigned double VL_ITOR_D_W(int lbits, WDataInP const lwp) VL_PURE; static inline double VL_ITOR_D_I(int, IData lhs) VL_PURE { diff --git a/include/verilated_sym_props.h b/include/verilated_sym_props.h index c124fb9e9..11e2fe8b8 100644 --- a/include/verilated_sym_props.h +++ b/include/verilated_sym_props.h @@ -76,6 +76,7 @@ class VerilatedVarProps VL_NOT_FINAL { const uint32_t m_magic; // Magic number const VerilatedVarType m_vltype; // Data type const VerilatedVarFlags m_vlflags; // Direction + const uint32_t m_entSize; // Element size in bytes, or 0 to derive from type std::vector m_unpacked; // Unpacked array ranges std::vector m_packed; // Packed array ranges VerilatedRange m_packedDpi; // Flattened packed array range @@ -104,10 +105,12 @@ class VerilatedVarProps VL_NOT_FINAL { // CONSTRUCTORS protected: friend class VerilatedScope; - VerilatedVarProps(VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims) + VerilatedVarProps(VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, + uint32_t entSize = 0) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags{vlflags} { + , m_vlflags{vlflags} + , m_entSize{entSize} { // Only preallocate the ranges initUnpacked(udims, nullptr); initPacked(pdims, nullptr); @@ -119,12 +122,14 @@ public: VerilatedVarProps(VerilatedVarType vltype, int vlflags) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) {} // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} {} VerilatedVarProps(VerilatedVarType vltype, int vlflags, Unpacked, int udims, const int* ulims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initUnpacked(udims, ulims); } // With packed @@ -132,14 +137,16 @@ public: VerilatedVarProps(VerilatedVarType vltype, int vlflags, Packed, int pdims, const int* plims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initPacked(pdims, plims); } VerilatedVarProps(VerilatedVarType vltype, int vlflags, Unpacked, int udims, const int* ulims, Packed, int pdims, const int* plims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initUnpacked(udims, ulims); initPacked(pdims, plims); } @@ -164,6 +171,7 @@ public: bool isDpiCLayout() const { return ((m_vlflags & VLVF_DPI_CLAY) != 0); } bool isSigned() const { return ((m_vlflags & VLVF_SIGNED) != 0); } bool isBitVar() const { return ((m_vlflags & VLVF_BITVAR) != 0); } + bool isNet() const { return ((m_vlflags & VLVF_NET) != 0); } int udims() const VL_MT_SAFE { return m_unpacked.size(); } int pdims() const VL_MT_SAFE { return m_packed.size(); } int dims() const VL_MT_SAFE { return pdims() + udims(); } @@ -265,6 +273,8 @@ protected: // CONSTRUCTORS VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam); + VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, + VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, uint32_t entSize); VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, std::unique_ptr forceControlSignals); @@ -296,6 +306,13 @@ inline VerilatedVar::VerilatedVar(const char* namep, void* datap, VerilatedVarTy , m_datap{datap} , m_namep{namep} , m_isParam{isParam} {} +inline VerilatedVar::VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, + VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, + uint32_t entSize) + : VerilatedVarProps{vltype, vlflags, udims, pdims, entSize} + , m_datap{datap} + , m_namep{namep} + , m_isParam{isParam} {} inline VerilatedVar::VerilatedVar( const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, diff --git a/include/verilated_vpi.cpp b/include/verilated_vpi.cpp index 8abbc5205..65afe20f3 100644 --- a/include/verilated_vpi.cpp +++ b/include/verilated_vpi.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,25 @@ constexpr unsigned VL_VPI_LINE_SIZE_ = 8192; //====================================================================== // Implementation +static const char* _vl_vpi_find_unescaped_dot(const char* posp) { + for (; *posp; ++posp) { + if (*posp == '\\') { + while (*posp && *posp != ' ') ++posp; + if (!*posp) return nullptr; + } else if (*posp == '.') { + return posp; + } + } + return nullptr; +} + +static std::string _vl_vpi_member_local_name(const char* const namep) { + const char* localp = namep; + const char* posp = namep; + while ((posp = _vl_vpi_find_unescaped_dot(posp))) localp = ++posp; + return localp; +} + // Base VPI handled object class VerilatedVpio VL_NOT_FINAL { // CONSTANTS @@ -201,6 +221,9 @@ public: } const VerilatedVar* varp() const { return m_varp; } const VerilatedScope* scopep() const { return m_scopep; } + bool isStructOrUnion() const { + return varp()->vltype() == VLVT_STRUCT || varp()->vltype() == VLVT_UNION; + } // Returns the number of the currently indexed dimension (starting at -1 for none). int32_t indexedDim() const { return m_indexedDim; } // Returns whether the currently indexed dimension is unpacked. @@ -363,6 +386,8 @@ class VerilatedVpioVar VL_NOT_FINAL : public VerilatedVpioVarBase { uint32_t m_entSize = 0; // memoized variable size uint32_t m_bitOffset = 0; int32_t m_partselBits = -1; // Part-select width, -1 means no part-select active + std::string m_name; + std::string m_fullNameOverride; protected: void* m_varDatap = nullptr; // varp()->datap() adjusted for array entries @@ -373,6 +398,17 @@ public: : VerilatedVpioVarBase{varp, scopep} { m_entSize = varp->entSize(); m_varDatap = varp->datap(); + if (_vl_vpi_find_unescaped_dot(varp->name())) { + m_name = _vl_vpi_member_local_name(varp->name()); + } + } + VerilatedVpioVar(const VerilatedVar* varp, const VerilatedScope* scopep, void* datap, + const std::string& name, const std::string& fullname) + : VerilatedVpioVarBase{varp, scopep} { + m_entSize = varp->entSize(); + m_varDatap = datap; + m_name = name; + m_fullNameOverride = fullname; } explicit VerilatedVpioVar(const VerilatedVpioVar* vop) : VerilatedVpioVarBase{vop} { @@ -380,6 +416,8 @@ public: m_entSize = vop->m_entSize; m_varDatap = vop->m_varDatap; m_index = vop->m_index; + m_name = vop->m_name; + m_fullNameOverride = vop->m_fullNameOverride; m_partselBits = vop->m_partselBits; m_bitOffset = vop->m_bitOffset; // Not copying m_prevDatap, must be nullptr @@ -395,15 +433,22 @@ public: uint32_t bitOffset() const override { return m_bitOffset; } int32_t partselBits() const { return m_partselBits; } uint32_t bitSize() const { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return 0; if (m_partselBits >= 0) return static_cast(m_partselBits); return VerilatedVpioVarBase::bitSize(); } uint32_t size() const override { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return 0; if (m_partselBits >= 0) return static_cast(m_partselBits); return VerilatedVpioVarBase::size(); } + const VerilatedRange* rangep() const override { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return nullptr; + return VerilatedVpioVarBase::rangep(); + } uint32_t entSize() const { return m_entSize; } const std::vector& index() const { return m_index; } + const char* name() const override { return m_name.empty() ? varp()->name() : m_name.c_str(); } // Create a part-selected view of this variable with the given bit range [hi:lo]. VerilatedVpioVar* withPartSelect(int32_t hi, int32_t lo) const { if (isIndexedDimUnpacked()) return nullptr; @@ -456,22 +501,43 @@ public: return ret; } + VerilatedVpioVar* withMember(const VerilatedVar* memberVarp) const { + const char* const parentName = varp()->name(); + const std::string memberName = memberVarp->name(); + const size_t parentLen = std::strlen(parentName); + + void* const parentDatap = varp()->datap(); + void* const memberDatap = memberVarp->datap(); + if (VL_UNLIKELY(!parentDatap) || VL_UNLIKELY(!memberDatap)) return nullptr; + const auto offset + = static_cast(memberDatap) - static_cast(parentDatap); + + const std::string localName = _vl_vpi_member_local_name(memberVarp->name()); + + return new VerilatedVpioVar{memberVarp, scopep(), + static_cast(varDatap()) + offset, localName, + std::string{fullname()} + memberName.substr(parentLen)}; + } uint32_t type() const override { uint32_t type; // TODO have V3EmitCSyms.cpp put vpiType directly into constant table switch (varp()->vltype()) { case VLVT_REAL: type = vpiRealVar; break; case VLVT_STRING: type = vpiStringVar; break; + case VLVT_STRUCT: type = varp()->isNet() ? vpiStructNet : vpiStructVar; break; + case VLVT_UNION: type = varp()->isNet() ? vpiUnionNet : vpiUnionVar; break; default: type = varp()->isBitVar() ? vpiBitVar : vpiReg; break; } - if (isIndexedDimUnpacked()) return vpiRegArray; + if (isIndexedDimUnpacked()) + return isStructOrUnion() && varp()->isNet() ? vpiNetArray : vpiRegArray; return type; } const char* fullname() const override { - static thread_local std::string t_out; - t_out = std::string{scopep()->name()} + "." + name(); - for (auto idx : index()) t_out += "[" + std::to_string(idx) + "]"; - return t_out.c_str(); + m_fullname = m_fullNameOverride.empty() + ? std::string{scopep()->name()} + "." + varp()->name() + : m_fullNameOverride; + for (auto idx : index()) m_fullname += "[" + std::to_string(idx) + "]"; + return m_fullname.c_str(); } void* prevDatap() const { return m_prevDatap; } void* varDatap() const override { return m_varDatap; } @@ -590,25 +656,63 @@ public: } }; +class VerilatedVpioMemberIter final : public VerilatedVpio { + const VerilatedScope* const m_scopep; + const VerilatedVarNameMap* const m_varsp; + VerilatedVarNameMap::const_iterator m_it; + VerilatedVpioVar* m_varp; + const std::string m_namePrefix; + bool m_started = false; + + static std::string namePrefix(const VerilatedVpioVar* vop) { + return std::string{vop->varp()->name()} + "."; + } + + vpiHandle atEnd() { + delete this; // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + return nullptr; + } + +public: + explicit VerilatedVpioMemberIter(const VerilatedVpioVar* vop) + : m_scopep{vop->scopep()} + , m_varsp{m_scopep->varsp()} + , m_varp{new VerilatedVpioVar{vop}} + , m_namePrefix{namePrefix(vop)} {} + ~VerilatedVpioMemberIter() override { VL_DO_CLEAR(delete m_varp, m_varp = nullptr); } + // cppcheck-suppress duplInheritedMember + static VerilatedVpioMemberIter* castp(vpiHandle h) { + return dynamic_cast(reinterpret_cast(h)); + } + uint32_t type() const override { return vpiIterator; } + vpiHandle dovpi_scan() override { + if (VL_UNLIKELY(!m_varsp)) return atEnd(); + if (VL_UNLIKELY(!m_started)) { + m_it = m_varsp->begin(); + m_started = true; + } else if (VL_LIKELY(m_it != m_varsp->end())) { + ++m_it; + } + for (; m_it != m_varsp->end(); ++m_it) { + const char* const name = m_it->second.name(); + if (std::strncmp(name, m_namePrefix.c_str(), m_namePrefix.length()) != 0) continue; + // Only direct members, not grandchildren + if (_vl_vpi_find_unescaped_dot(name + m_namePrefix.length())) continue; + VerilatedVpioVar* const memberp = m_varp->withMember(&(m_it->second)); + if (VL_UNLIKELY(!memberp)) continue; + return memberp->castVpiHandle(); + } + return atEnd(); + } +}; + class VerilatedVpioModule final : public VerilatedVpioScope { public: explicit VerilatedVpioModule(const VerilatedScope* modulep) : VerilatedVpioScope{modulep} { // Look for '.' not inside escaped identifier - const std::string scopename = m_fullname; - std::string::size_type pos = std::string::npos; - size_t i = 0; - while (i < scopename.length()) { - if (scopename[i] == '\\') { - while (i < scopename.length() && scopename[i] != ' ') ++i; - ++i; // Proc ' ', it should always be there. Then grab '.' on next cycle - } else { - while (i < scopename.length() && scopename[i] != '.') ++i; - if (i < scopename.length()) pos = i++; - } - } - if (VL_UNLIKELY(pos == std::string::npos)) m_toplevel = true; + if (VL_UNLIKELY(!_vl_vpi_find_unescaped_dot(m_fullname))) m_toplevel = true; } // cppcheck-suppress duplInheritedMember static VerilatedVpioModule* castp(vpiHandle h) { @@ -1770,6 +1874,8 @@ const char* VerilatedVpiError::strFromVpiObjType(PLI_INT32 vpiVal) VL_PURE { }; // clang-format on if (VL_UNCOVERABLE(vpiVal < 0)) return names[0]; + // vpiUnionNet is outside the otherwise contiguous SystemVerilog object type range. + if (vpiVal == vpiUnionNet) return "vpiUnionNet"; if (vpiVal <= vpiAutomatics) return names[vpiVal]; if (vpiVal >= vpiPackage && vpiVal <= vpiPropFormalDecl) return sv_names1[(vpiVal - vpiPackage)]; @@ -2135,6 +2241,7 @@ void VerilatedVpiError::selfTest() VL_MT_UNSAFE_ONE { SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEnumVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStructVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUnionVar); + SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUnionNet); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBitVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClassObj); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiChandleVar); @@ -2404,6 +2511,162 @@ static bool vl_vpi_parse_indices(std::string& name, std::vector& indi return true; } +static const VerilatedScope* _vl_vpi_top_port_scopep(const VerilatedScope* const scopep) { + if (VL_UNLIKELY(!scopep)) return nullptr; + if (scopep->type() != VerilatedScope::SCOPE_MODULE) return nullptr; + if (std::strcmp(scopep->name(), "TOP") == 0) return nullptr; + if (_vl_vpi_find_unescaped_dot(scopep->name())) return nullptr; + return Verilated::threadContextp()->scopeFind("TOP"); +} + +static bool _vl_vpi_find_dotted_var(const std::string& scopename, const std::string& basename, + const VerilatedScope*& scopep, const VerilatedVar*& varp, + std::string& fullname) { + if (scopename.empty()) return false; + + // Unpacked struct/union members are exposed as synthetic vars whose names contain dots + // (e.g. "mystruct.member"), so the boundary between the scope and the variable name is + // ambiguous. Walk the boundary leftward, moving one scope segment at a time onto the front + // of the dotted variable name, and try each candidate scope. An exhausted scope means the + // variable lives in the toplevel "TOP" scope. + std::string dottedName = basename; + std::string dottedScope = scopename; + while (true) { + const std::string::size_type lastDot = dottedScope.rfind('.'); + dottedName = (lastDot == std::string::npos ? dottedScope : dottedScope.substr(lastDot + 1)) + + "." + dottedName; + dottedScope.resize(lastDot == std::string::npos ? 0 : lastDot); + scopep = Verilated::threadContextp()->scopeFind(dottedScope.empty() ? "TOP" + : dottedScope.c_str()); + if (scopep) { + if (const VerilatedScope* const topScopep = _vl_vpi_top_port_scopep(scopep)) { + if (const VerilatedVar* const topVarp = topScopep->varFind(dottedName.c_str())) { + scopep = topScopep; + varp = topVarp; + fullname = dottedScope.empty() ? dottedName : dottedScope + "." + dottedName; + return true; + } + } + varp = scopep->varFind(dottedName.c_str()); + if (varp) return true; + } + if (lastDot == std::string::npos) return false; + } +} + +static VerilatedVpioVar* _vl_vpi_handle_member_by_name(const std::string& name, + const VerilatedVpioVar* vop) { + const VerilatedScope* const scopep = vop->scopep(); + if (VL_UNLIKELY(!scopep)) return nullptr; + const std::string memberName = std::string{vop->varp()->name()} + "." + name; + const VerilatedVar* const memberVarp = scopep->varFind(memberName.c_str()); + if (!memberVarp) return nullptr; + return vop->withMember(memberVarp); +} + +static VerilatedVpioVar* _vl_vpi_handle_apply_indices(VerilatedVpioVar* vop, + const std::vector& indices) { + for (const PLI_INT32 index : indices) { + VerilatedVpioVar* const nextVop = vop->withIndex(index); + VL_DO_CLEAR(delete vop, vop = nullptr); + if (!nextVop) return nullptr; + vop = nextVop; + } + return vop; +} + +static bool _vl_vpi_parse_optional_indices(std::string& name, std::vector& indices) { + indices.clear(); + if (name.empty()) return false; + if (name.back() != ']') return true; + + VlVpiBitRange bitRange; + return vl_vpi_parse_indices(name, indices, &bitRange) && !bitRange.valid; +} + +static void _vl_vpi_split_dotted_name(const std::string& name, std::vector& parts) { + parts.clear(); + const char* const namep = name.c_str(); + const char* beginp = namep; + const char* posp = beginp; + while ((posp = _vl_vpi_find_unescaped_dot(posp))) { + parts.emplace_back(beginp, posp - beginp); + beginp = ++posp; + } + parts.emplace_back(beginp); +} + +static VerilatedVpioVar* +_vl_vpi_handle_indexed_member_from_scope(const VerilatedScope* const scopep, + const std::vector& parts, + const size_t firstPart) { + if (VL_UNLIKELY(!scopep) || VL_UNLIKELY(firstPart >= parts.size())) return nullptr; + + std::string baseName = parts[firstPart]; + std::vector indices; + if (!_vl_vpi_parse_optional_indices(baseName, indices)) return nullptr; + + const VerilatedScope* varScopep = scopep; + const VerilatedVar* baseVarp = nullptr; + std::string fullnameOverride; + if (const VerilatedScope* const topScopep = _vl_vpi_top_port_scopep(scopep)) { + if (const VerilatedVar* const topVarp = topScopep->varFind(baseName.c_str())) { + varScopep = topScopep; + baseVarp = topVarp; + fullnameOverride = std::string{scopep->name()} + "." + baseName; + } + } + if (!baseVarp) baseVarp = scopep->varFind(baseName.c_str()); + if (!baseVarp) return nullptr; + + VerilatedVpioVar* baseVop + = fullnameOverride.empty() + ? new VerilatedVpioVar{baseVarp, varScopep} + : new VerilatedVpioVar{baseVarp, varScopep, baseVarp->datap(), + _vl_vpi_member_local_name(baseVarp->name()), + fullnameOverride}; + VerilatedVpioVar* vop = _vl_vpi_handle_apply_indices(baseVop, indices); + if (!vop) return nullptr; + + for (size_t i = firstPart + 1; i < parts.size(); ++i) { + std::string memberName = parts[i]; + if (!_vl_vpi_parse_optional_indices(memberName, indices)) { + VL_DO_CLEAR(delete vop, vop = nullptr); + return nullptr; + } + + VerilatedVpioVar* const memberVop = _vl_vpi_handle_member_by_name(memberName, vop); + VL_DO_CLEAR(delete vop, vop = nullptr); + if (!memberVop) return nullptr; + + vop = _vl_vpi_handle_apply_indices(memberVop, indices); + if (!vop) return nullptr; + } + return vop; +} + +static VerilatedVpioVar* +_vl_vpi_handle_dotted_indexed_member_by_name(const std::string& scopeAndName) { + std::vector parts; + _vl_vpi_split_dotted_name(scopeAndName, parts); + if (parts.size() < 2) return nullptr; + + for (size_t firstPart = parts.size() - 1; firstPart > 0; --firstPart) { + std::string scopeName = parts[0]; + for (size_t i = 1; i < firstPart; ++i) scopeName += "." + parts[i]; + const VerilatedScope* const scopep + = Verilated::threadContextp()->scopeFind(scopeName.c_str()); + if (!scopep) continue; + if (VerilatedVpioVar* const vop + = _vl_vpi_handle_indexed_member_from_scope(scopep, parts, firstPart)) { + return vop; + } + } + + const VerilatedScope* const topScopep = Verilated::threadContextp()->scopeFind("TOP"); + return _vl_vpi_handle_indexed_member_from_scope(topScopep, parts, 0); +} + // for obtaining handles vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { @@ -2425,7 +2688,9 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { const VerilatedVar* varp = nullptr; const VerilatedScope* scopep; + std::string fullnameOverride; const VerilatedVpioScope* const voScopep = VerilatedVpioScope::castp(scope); + const VerilatedVpioVar* const voVarp = VerilatedVpioVar::castp(scope); if (0 == std::strncmp(scopeAndName.c_str(), "$root.", std::strlen("$root."))) { scopeAndName.erase(0, std::strlen("$root.")); @@ -2433,6 +2698,12 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { const bool scopeIsPackage = VerilatedVpioPackage::castp(scope) != nullptr; scopeAndName = std::string{voScopep->fullname()} + (scopeIsPackage ? "" : ".") + scopeAndName; + } else if (voVarp && voVarp->isStructOrUnion()) { + if (VerilatedVpioVar* const memberp + = _vl_vpi_handle_member_by_name(scopeAndName, voVarp)) { + return memberp->castVpiHandle(); + } + scopeAndName = std::string{voVarp->fullname()} + "." + scopeAndName; } { // This doesn't yet follow the hierarchy in the proper way @@ -2491,8 +2762,18 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { } if (!varp) { scopep = Verilated::threadContextp()->scopeFind(scopename.c_str()); - if (!scopep) return nullptr; - varp = scopep->varFind(basename.c_str()); + if (scopep) { varp = scopep->varFind(basename.c_str()); } + // Unpacked struct members are exposed as synthetic variables with dotted names. + // Exact unindexed member names can be found directly; indexed member paths need the + // component walker so array indices are applied before member offsets. + if (!varp + && !_vl_vpi_find_dotted_var(scopename, basename, scopep, varp, fullnameOverride)) { + if (VerilatedVpioVar* const memberp + = _vl_vpi_handle_dotted_indexed_member_by_name(scopeAndName)) { + return memberp->castVpiHandle(); + } + return nullptr; + } } } if (!varp) return nullptr; @@ -2501,6 +2782,11 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { vpiHandle resultHandle; if (varp->isParam()) { resultHandle = (new VerilatedVpioParam{varp, scopep})->castVpiHandle(); + } else if (!fullnameOverride.empty()) { + resultHandle + = (new VerilatedVpioVar{varp, scopep, varp->datap(), + _vl_vpi_member_local_name(varp->name()), fullnameOverride}) + ->castVpiHandle(); } else { resultHandle = (new VerilatedVpioVar{varp, scopep})->castVpiHandle(); } @@ -2642,6 +2928,11 @@ vpiHandle vpi_iterate(PLI_INT32 type, vpiHandle object) { if (vop) return ((new VerilatedVpioRegIter{vop})->castVpiHandle()); return nullptr; } + case vpiMember: { + const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object); + if (!vop || !vop->isStructOrUnion()) return nullptr; + return ((new VerilatedVpioMemberIter{vop})->castVpiHandle()); + } case vpiParameter: { const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object); if (VL_UNLIKELY(!vop)) return nullptr; @@ -2734,6 +3025,11 @@ PLI_INT32 vpi_get(PLI_INT32 property, vpiHandle object) { if (VL_UNLIKELY(!vop)) return vpiUndefined; return vop->varp()->isSigned(); } + case vpiPacked: { + const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object); + if (VL_LIKELY(vop && vop->isStructOrUnion())) return 0; + [[fallthrough]]; + } default: VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported property %s, nothing will be returned", __func__, VerilatedVpiError::strFromVpiProp(property)); diff --git a/python-dev-requirements.txt b/python-dev-requirements.txt index 11f9fad84..3e33332cc 100644 --- a/python-dev-requirements.txt +++ b/python-dev-requirements.txt @@ -19,8 +19,10 @@ breathe==4.36.0 compiledb==0.10.7 distro==1.9.0 +docstrfmt==2.2.0 gersemi==0.23.1 mbake==1.4.3 +mdformat==1.0.0 mypy==1.19.0 pylint==3.0.2 ruff==0.14.8 diff --git a/src/AGENTS.md b/src/AGENTS.md new file mode 100644 index 000000000..cb53dc31c --- /dev/null +++ b/src/AGENTS.md @@ -0,0 +1,241 @@ + + +# src/ -- Verilator compiler sources + +Covers all C++ under `src/`, including astgen inputs and the parser/lexer +(`verilog.y`, `verilog.l`). Read the repository-root [AGENTS.md](../AGENTS.md) +first. This file has two parts: **Orientation** explains the AST and pass model; +**Before you open a PR** is the style and correctness checklist. + +______________________________________________________________________ + +# Orientation: the AST and the visitor model + +- **Everything is an `AstNode`.** Each construct is an `Ast*` subclass (`AstAdd`, + `AstVar`, `AstIf`). The design under analysis is one tree, with statement lists + threaded by sibling `nextp()` links. +- **Children sit in numbered slots `op1p()`..`op4p()`, accessed by name.** Use the + named accessors (`lhsp()`, `condp()`, `thensp()`), not the raw slots -- the + numbering is an implementation detail. +- **`astgen` generates node boilerplate.** Declare children and cross-node + pointers with `@astgen op` / `@astgen ptr` annotations in the `V3AstNode*.h` + headers; it emits accessors, clone, and broken-check code. Do not hand-write + what astgen can generate. +- **A pass is a visitor.** Convention: a class with a private constructor and a + static `apply()` entry point, named after its file (`TimingSuspendableVisitor` + in `V3Timing.cpp`). It walks the tree through `visit(AstFoo*)` handlers and + `iterateChildren()`. To understand a pass, read its top-of-file comment first -- + every `.cpp` opens with one describing the algorithm. +- **Scratch state lives on nodes.** Passes stash data in `user1p()`..`user5p()` + (and `user1()`..`user5()`), claimed for the pass lifetime with a `VNUser*InUse` + guard. Save and restore visitor members across recursion with `VL_RESTORER`. +- **Three downcasts, three null behaviors:** `VN_IS` returns bool, `VN_CAST` + returns nullptr on mismatch, `VN_AS` asserts the type. `V3Broken` re-validates + tree invariants between passes, so trust resolved pointers (`dtypep()`, + `varp()`) instead of adding defensive null checks for impossible cases. +- `docs/internals.rst` is the authoritative reference for the AST, the pass list, + and node lifetime. + +______________________________________________________________________ + +# Before you open a PR + +## Code style + +- Mark every variable, parameter, pointer, and member function `const` where possible. +- Pointer variables take a `p` suffix and pointer locals are doubly const where + possible: `AstVar* const varp`; non-pointers never use the `p` suffix. +- Do not use `auto` except for iterators or genuinely unwieldy types. +- Use pre-increment (`++i`) unless you specifically need post-increment's old value. +- Brace-initialize node and struct construction: `new AstIf{fl, condp, thenp, elsep}`. +- Never use C-style casts; instead use `static_cast` for non-AST types and + `VN_AS`/`VN_CAST` for AST downcasts. +- `static constexpr` for compile-time constants, not `#define` or file-scope const. +- Mark every `class`/`struct` `final` or `VL_NOT_FINAL` -- a distribution test + scans all definitions. +- Keep functions under roughly 100-150 lines; thread shared state through a + context struct rather than long parameter lists. +- Keep headers lean: move implementation to `.cpp`; convert large lambdas into + named member functions -- lambdas are opaque in stack traces and block reuse. +- Start every new `.cpp` with a top-of-file comment explaining the algorithm. +- Comments are capitalized sentences written for an unknown future reader, without + "I"/"we"/"our"; remove commented-out code -- version control preserves history. +- No `using namespace`; prefix non-namespaced symbols with `VL`/`Vl`. +- Prefer semantic predicates over enum comparisons: `varp->isClassMember()`, not + `varp->varType() == VVarType::MEMBER`. +- `new*` functions return a `new` object; `make*` functions do something more + complex -- pick the prefix accordingly. +- Name compiler-created temporaries with a `__V` prefix plus a context suffix + (`__VInside`, `__VCase`); runtime utility functions use a `vl_` prefix. +- Use `VL_*` bit/word macros from `verilatedos.h` (`VL_WORDS_I`, `VL_MASK_I`); do + not include `` directly. +- Replace magic numbers with named `static constexpr` constants. + +## AST construction and manipulation + +- Build logic as AST nodes, never as raw C text in `AstCStmt` -- later passes + (V3Name, `--protect`) rename AST identifiers but cannot see into raw strings. + +- Know the cast forms (above) and never pair `VN_IS` with `VN_AS` on the same + node -- use a single `VN_CAST`: + + ```cpp + // BAD: redundant double check + if (VN_IS(nodep, VarRef)) { AstVarRef* const refp = VN_AS(nodep, VarRef); } + // GOOD: single conditional cast + if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) { ... } + ``` + +- Use `UASSERT_OBJ(cond, nodep, "...")` over `UASSERT` when a node is in scope; + use `v3fatalSrc("...")` for unreachable paths, never a silent `if (!ptr) return;`. + +- Use `VL_DO_DANGLING(pushDeletep(nodep), nodep)` instead of `deleteTree()` in + visitors -- deferred deletion is safe against re-entry and unlinking order. + `deleteTree()` is only for fresh nodes that never entered the tree. + +- Every new AST member needs both `dump()` and `dumpJson()` support -- never wrap + these in `LCOV_EXCL`; cover them by adding a construct to `t_debug_emitv.v`. + Override `isSame()` to include any new semantically meaningful field. + +- Pointers to nodes outside op1p-op4p require a `broken()` override and + `cloneRelink()` support -- avoid storing out-of-tree node pointers when possible. + +- Never allocate AstNode objects on the stack or by value -- always pointers. + +- Prefer a new `visit()` in an existing visitor over `nodep->foreach(...)` -- + better for runtime, and handles diverse node types better. Prefer named + accessors over `op1p()`..`op4p()`, and verify traversal order is preserved + when converting manual iteration. + +- Prefer `AstForeach` over generating unrolled loop bodies -- constant-size code + instead of O(N); wrap the body in `AstBegin` for scope isolation. + +- Always `skipRefp()` when comparing or resolving dtypes -- missing it breaks + typedef support; prove the paths with typedef tests. + +- Use `num().isOpaque()` rather than `isDouble() || isString()` when guarding + V3Number comparisons against non-integer types. + +- Use `FileLine::operatorCompare` for source-position ordering -- never hand-roll + filename/lineno comparisons. + +- Identify compiler-generated constructs by an attribute flag on the node (with + dump support), never by name-pattern matching -- magic names break with escaped + identifiers. + +- Use `V3Number` arithmetic for `AstConst` values wider than 32 bits -- `1 << i` + silently overflows at `i >= 32`. + +- Use `VMemberMap`/`findMember()` for name lookups in structs, classes, modules, + and packages -- O(1) versus quadratic linear scans. + +- Never emit raw source filenames or identifiers in generated code -- pass them + through `protect()`/`putsQuoted` so `--protect` does not leak source. + +## Visitors and passes + +- `VL_RESTORER` on every visitor member a `visit()` modifies before iterating + children -- even when nesting "cannot happen" today. +- Every pass using `userNp()` needs a `VNUserNInUse` guard, and the header + documents which user fields it uses. +- Use `iterateAndNextNull()` rather than `iterate()` -- the null-safe form + prevents copy-paste errors during refactors. +- Derive read-only visitors from `VNVisitorConst` with `iterateChildrenConst`. +- Reset per-module visitor state in `visit(AstNodeModule*)`, including numeric ID + counters, to keep generated names stable. +- Capture first-occurrence module state inside the node's own `visit()` handler, + not via a `foreach` pre-scan from `visit(AstNodeModule)` -- source order already + matches IEEE declaration-before-use. +- Avoid `backp()` -- it returns parent or prior sibling depending on position and + causes O(n^2) hunts; build maps or capture context during forward traversal. +- When raw node pointers key a map or set, erase entries when the node is deleted + -- allocators reuse addresses, so stale entries alias new nodes. +- Derive graph-shaped passes from V3Graph (`V3GraphVertex`/`V3GraphEdge`) -- it + gives dump, color, rank, topological sort, and reachability for free. +- When a change outgrows local rewrites, create a dedicated pass instead of + growing an existing one. +- State explicitly how side effects are preserved in optimizations involving + purity, expression lifting, or simplification. + +## Errors and warnings + +(See the root checklist for choosing the diagnostic API and its required test.) + +- Append `nodep->prettyNameQ()` for user-facing names; use `name()` only in + debug/UINFO output. Enclose specific values in single quotes: `'value'`. +- End messages with periods, never exclamation marks; do not write "Error:" in the + text -- the macro prints the prefix. +- State what was attempted and what was found: "Instance attempts to connect to + 'PARAM' as a parameter, but it is a variable". Add a `warnMore()` suggestion + where possible. +- Name warning codes object-first and short (`ASCRANGE`, not `RANGEASC`); rename + via `renamedTo()` so old suppressions keep working. +- Set warning suppression on `AstVar`, not `AstVarRef` -- VarRefs get recreated + and lose `warnIsOff`. +- "Unsupported:" messages describe the specific unsupported context, not just the + construct name -- each message must be distinct. +- When replacing or refactoring a pass, keep existing user-facing error strings -- + `.out` goldens and documentation depend on the wording. + +## Performance and memory + +- O(n^2) is never acceptable -- build maps for batch lookups; any quadratic loop + needs explicit justification in a comment. +- Prefer `std::map` for per-module structures (many small instances); use + `unordered_map` only for one-per-netlist data, and never let `unordered_*` + iteration order reach generated output. +- Prefer `emplace` over `insert` and check the returned `.second` instead of a + separate `find()`. `reserve()` strings and vectors when the size is estimable. +- Add no new static or global mutable data -- statics are being eliminated for + future parallelism. +- Use Verilator's fixed-width data types for model data (`CData`/`SData`/`IData`/ + `QData`/`VlWide`), not `size_t`. Process wide data word-by-word + (`VL_ZERO_W`, `VL_MEMCPY_W`), never bit-by-bit. +- No exceptions in verilated runtime code; do string parsing at verilation time, + never during simulation. +- Wrap unlikely hot-path branches in `VL_UNLIKELY`/`VL_LIKELY`. +- Count what every new pass does via V3Stats -- stats become deterministic + regression anchors. + +## Thread safety + +- Annotate with the hierarchy `VL_PURE` > `VL_MT_SAFE` > `VL_MT_STABLE`: PURE has + no side effects and calls only PURE; MT_SAFE is safe under locks; MT_STABLE is + safe only while tree topology is stable. Annotations must match the + implementation. +- Never include `verilated.h` in the compiler itself -- use `verilatedos.h`. +- Annotate mutex-protected members with `VL_GUARDED_BY` and document acquisition + ordering. `++` on shared state and container `empty()` are not thread-safe. + +## Parser and lexer (verilog.y, verilog.l) + +- Preserve IEEE Appendix A BNF comments (`// IEEE: {rule}`); comment explicitly + when accepting syntax beyond IEEE as an extension. +- The parser only builds AST nodes -- defer semantic validation, `VN_IS` checks, + and context-dependent logic to V3LinkParse/V3Width and later passes. +- Represent hierarchical paths as structured nodes (`AstDot`/parse-ref chains via + `idDotted`), never concatenated strings -- preserves per-segment FileLine. +- Prefer tightening a grammar rule's operand type over a runtime cast-chain guard + in a later visitor -- illegal operands then fail with a clean syntax error. +- Solve ambiguities with token-pipeline look-ahead (`tokenPipeScan*`) rather than + limiting grammar rules; mark unsupported rules with `//UNSUP`. +- Sort token declarations alphabetically by string literal; sort `yD_*` + productions by token name. +- Add a test for every `|` alternative and optional clause of a new or changed + grammar rule -- untested alternatives are where parse regressions hide. + +## File-specific rules + +| File | Rule | +|---|---| +| `src/V3Options.cpp` | Chain `.notForRerun()` onto `DECL_OPTION()` for options that do not affect semantic output | +| `src/V3Ast.cpp` | For composite types (queues, dynamic arrays) use `computeCastableImp()` on subtypes -- shallow `width()`/`similarDType()` checks miss nested incompatibility | +| `src/V3AstNode*.h` | Every node class gets a what-construct comment and every member a semantic-purpose comment; put enum type definitions in `V3AstAttr.h` | +| `src/V3AstNodeExpr.h` | `CCast` is only for basic C types (char/short/int/QData) -- never 4-state logic or structs | +| `src/V3AstNodeOther.h` | `cloneRelink` must propagate all stateful flags (e.g. `maybePointedTo`) and update internal references | +| `src/V3Const.cpp` | Check `keepIfEmpty` before removing empty functions -- flagged functions must survive for codegen/side effects | +| `src/V3Coverage.cpp` | Instrumentation contexts are opt-in (allowlist), never blocklist -- blocklists silently break when new contexts appear | +| `src/V3Inline.cpp` | Preserve `VarXRef::varp()` during passes -- pin-reconnection needs it before V3LinkDot re-resolves | +| `src/V3Sched*.cpp` | Every change needs a test proving necessity; isolate unrelated scheduler changes into separate PRs -- high-risk area | diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad2178203..90a0d3ab4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,7 @@ set(HEADERS V3AstNodeExpr.h V3AstNodeOther.h V3AstNodeStmt.h + V3AstPatterns.h V3AstUserAllocator.h V3Begin.h V3Branch.h @@ -175,7 +176,6 @@ set(HEADERS V3Simulate.h V3Slice.h V3Split.h - V3SplitAs.h V3SplitVar.h V3StackCount.h V3Stats.h @@ -183,7 +183,6 @@ set(HEADERS V3String.h V3Subst.h V3SymTable.h - V3TSP.h V3Table.h V3Task.h V3ThreadPool.h @@ -203,7 +202,6 @@ set(HEADERS V3WidthCommit.h V3WidthRemove.h VlcBucket.h - VlcCovergroup.h VlcOptions.h VlcPoint.h VlcSource.h @@ -253,7 +251,6 @@ set(COMMON_SOURCES V3DfgDataType.cpp V3DfgDecomposition.cpp V3DfgDfgToAst.cpp - V3DfgDumpPatterns.cpp V3DfgOptimizer.cpp V3DfgPasses.cpp V3DfgPeephole.cpp @@ -330,6 +327,7 @@ set(COMMON_SOURCES V3ParseGrammar.cpp V3ParseImp.cpp V3ParseLex.cpp + V3PatternStats.cpp V3PreProc.cpp V3PreShell.cpp V3Premit.cpp @@ -351,14 +349,12 @@ set(COMMON_SOURCES V3Scoreboard.cpp V3Slice.cpp V3Split.cpp - V3SplitAs.cpp V3SplitVar.cpp V3StackCount.cpp V3Stats.cpp V3StatsReport.cpp V3String.cpp V3Subst.cpp - V3TSP.cpp V3Table.cpp V3Task.cpp V3ThreadPool.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 08c8899fc..f5a728063 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -264,7 +264,6 @@ RAW_OBJS_PCH_ASTNOMT = \ V3DfgDataType.o \ V3DfgDecomposition.o \ V3DfgDfgToAst.o \ - V3DfgDumpPatterns.o \ V3DfgOptimizer.o \ V3DfgPasses.o \ V3DfgPeephole.o \ @@ -314,6 +313,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3OrderProcessDomains.o \ V3OrderSerial.o \ V3Param.o \ + V3PatternStats.o \ V3Premit.o \ V3ProtectLib.o \ V3RandSequence.o \ @@ -333,11 +333,9 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Scoreboard.o \ V3Slice.o \ V3Split.o \ - V3SplitAs.o \ V3SplitVar.o \ V3StackCount.o \ V3Subst.o \ - V3TSP.o \ V3Table.o \ V3Task.o \ V3Timing.o \ diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 995f4fd5c..13cd270b4 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -351,7 +351,7 @@ public: class ActiveDlyVisitor final : public VNVisitor { public: - enum CheckType : uint8_t { CT_SEQ, CT_COMB, CT_INITIAL, CT_SUSPENDABLE }; + enum CheckType : uint8_t { CT_COMB, CT_FINAL }; private: // MEMBERS @@ -359,15 +359,9 @@ private: // VISITORS void visit(AstAssignDly* nodep) override { - // Non-blocking assignments are OK in sequential processes - if (m_check == CT_SEQ || m_check == CT_SUSPENDABLE) return; - // Issue appropriate warning - if (m_check == CT_INITIAL) { - nodep->v3warn(INITIALDLY, - "Non-blocking assignment '<=' in initial/final block\n" - << nodep->warnMore() - << "... This will be executed as a blocking assignment '='!"); + if (m_check == CT_FINAL) { + nodep->v3warn(FINALDLY, "Non-blocking assignment '<=' in final block"); } else { nodep->v3warn(COMBDLY, "Non-blocking assignment '<=' in combinational logic process\n" @@ -465,11 +459,7 @@ class ActiveVisitor final : public VNVisitor { wantactivep->addStmtsp(nodep); // Warn and convert any delayed assignments - { - ActiveDlyVisitor{nodep, !m_clockedProcess ? ActiveDlyVisitor::CT_COMB - : oldsentreep ? ActiveDlyVisitor::CT_SEQ - : ActiveDlyVisitor::CT_SUSPENDABLE}; - } + if (!m_clockedProcess) ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_COMB}; // Delete sensitivity list if (oldsentreep) VL_DO_DANGLING(oldsentreep->deleteTree(), oldsentreep); @@ -509,17 +499,11 @@ class ActiveVisitor final : public VNVisitor { void visit(AstInitialStatic* nodep) override { moveUnderSpecial(nodep); } void visit(AstInitial* nodep) override { - const bool timedInitial - = v3Global.opt.timing().isSetTrue() && nodep->exists([](const AstNode* const subp) { - return VN_IS(subp, Delay) || VN_IS(subp, EventControl); - }); - const ActiveDlyVisitor dlyvisitor{nodep, timedInitial ? ActiveDlyVisitor::CT_SUSPENDABLE - : ActiveDlyVisitor::CT_INITIAL}; visitSenItems(nodep); moveUnderSpecial(nodep); } void visit(AstFinal* nodep) override { - const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL}; + const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_FINAL}; moveUnderSpecial(nodep); } void visit(AstCoverToggle* nodep) override { moveUnderSpecial(nodep); } diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 0ccf162fa..8d4bf5275 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -23,6 +23,79 @@ VL_DEFINE_DEBUG_FUNCTIONS; +namespace { + +class DefaultDisableLocalVisitor final : public VNVisitor { + // STATE + AstNode* m_scopep = nullptr; + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + nodep->defaultDisablep(nullptr); + iterateChildren(nodep); + } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + nodep->defaultDisablep(nullptr); + iterateChildren(nodep); + } + void visit(AstDefaultDisable* nodep) override { + UASSERT_OBJ(nodep, m_scopep, + "default disable iff must be inside a module or generate block"); + AstDefaultDisable* defaultp = nullptr; + if (const AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) { + defaultp = modp->defaultDisablep(); + } else { + defaultp = VN_AS(m_scopep, GenBlock)->defaultDisablep(); + } + if (VL_UNLIKELY(defaultp)) { + nodep->v3error("Only one 'default disable iff' allowed per " + << (VN_IS(m_scopep, NodeModule) ? "module" : "generate block") + << " (IEEE 1800-2023 16.15)"); + } else if (AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) { + modp->defaultDisablep(nodep); + } else { + VN_AS(m_scopep, GenBlock)->defaultDisablep(nodep); + } + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit DefaultDisableLocalVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +class DefaultDisablePropagateVisitor final : public VNVisitor { + // STATE + AstDefaultDisable* m_defaultDisablep = nullptr; + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + if (!nodep->defaultDisablep()) nodep->defaultDisablep(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit DefaultDisablePropagateVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +} // namespace + +void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) { + { DefaultDisableLocalVisitor{nodep}; } + { DefaultDisablePropagateVisitor{nodep}; } +} + //###################################################################### // AssertDeFutureVisitor // If any AstFuture, then move all non-future varrefs to be one cycle behind, @@ -153,12 +226,23 @@ class AssertVisitor final : public VNVisitor { bool m_inRestrict = false; // True inside restrict assertion AstNode* m_passsp = nullptr; // Current pass statement AstNode* m_failsp = nullptr; // Current fail statement + AstNodeCoverOrAssert* m_assertp = nullptr; // Current assertion AstFinal* m_finalp = nullptr; // Current final block // Map from (expression, senTree) to AstAlways that computes delayed values of the expression std::unordered_map, std::unordered_map, AstAlways*>> m_modExpr2Sen2DelayedAlwaysp; // METHODS + static string assertCtlGetCall(const char* query, VAssertType type, + VAssertDirectiveType directiveType) { + return "vlSymsp->_vm_contextp__->assertCtlGet(VerilatedAssertCtlQuery::"s + query + ", "s + + std::to_string(type) + ", "s + std::to_string(directiveType) + ")"s; + } + static const char* assertPassOnQuery(bool vacuous) { + static constexpr const char* queries[2] + = {"ASSERT_CTL_PASS_ON_NONVACUOUS", "ASSERT_CTL_PASS_ON_VACUOUS"}; + return queries[vacuous]; + } static AstNodeExpr* assertOnCond(FileLine* fl, VAssertType type, VAssertDirectiveType directiveType) { // cppcheck-suppress missingReturn @@ -178,9 +262,7 @@ class AssertVisitor final : public VNVisitor { case VAssertDirectiveType::ASSUME: { if (v3Global.opt.assertOn()) { return new AstCExpr{fl, AstCExpr::Pure{}, - "vlSymsp->_vm_contextp__->assertOnGet("s + std::to_string(type) - + ", "s + std::to_string(directiveType) + ")"s, - 1}; + assertCtlGetCall("ASSERT_CTL_ON", type, directiveType), 1}; } return new AstConst{fl, AstConst::BitFalse{}}; } @@ -196,6 +278,31 @@ class AssertVisitor final : public VNVisitor { } VL_UNREACHABLE; } + static string assertActionControlPrefix(VAssertDirectiveType directiveType) { + const int controlled = !!(static_cast(directiveType) + & (static_cast(VAssertDirectiveType::ASSERT) + | static_cast(VAssertDirectiveType::COVER) + | static_cast(VAssertDirectiveType::ASSUME))); + const int checkRuntime = controlled & static_cast(v3Global.opt.assertOn()); + return "("s + std::to_string(controlled ^ 1) + " || ("s + std::to_string(checkRuntime) + + " && "s; + } + static AstNodeExpr* assertPassOnCond(FileLine* fl, VAssertType type, + VAssertDirectiveType directiveType, bool vacuous) { + return new AstCExpr{fl, AstCExpr::Pure{}, + assertActionControlPrefix(directiveType) + + assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType) + + "))"s, + 1}; + } + static AstNodeExpr* assertFailOnCond(FileLine* fl, VAssertType type, + VAssertDirectiveType directiveType) { + return new AstCExpr{fl, AstCExpr::Pure{}, + assertActionControlPrefix(directiveType) + + assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType) + + "))"s, + 1}; + } string assertDisplayMessage(const AstNode* nodep, const string& prefix, const string& message, VDisplayType severity) { if (severity == VDisplayType::DT_ERROR || severity == VDisplayType::DT_FATAL) { @@ -207,36 +314,6 @@ class AssertVisitor final : public VNVisitor { + cvtToStr(nodep->fileline()->lineno()) + ": %m" + ((message != "") ? ": " : "") + message + "\n"); } - static bool resolveAssertType(AstAssertCtl* nodep) { - if (!nodep->assertTypesp()) { - nodep->ctlAssertTypes(VAssertType{ALL_ASSERT_TYPES}); - return true; - } - if (const AstConst* const assertTypesp = VN_CAST(nodep->assertTypesp(), Const)) { - nodep->ctlAssertTypes(VAssertType{assertTypesp->toSInt()}); - return true; - } - return false; - } - static bool resolveControlType(AstAssertCtl* nodep) { - if (const AstConst* const constp = VN_CAST(nodep->controlTypep(), Const)) { - nodep->ctlType(constp->toSInt()); - return true; - } - return false; - } - static bool resolveDirectiveType(AstAssertCtl* nodep) { - if (!nodep->directiveTypesp()) { - nodep->ctlDirectiveTypes(VAssertDirectiveType::ASSERT | VAssertDirectiveType::ASSUME - | VAssertDirectiveType::COVER); - return true; - } - if (const AstConst* const directiveTypesp = VN_CAST(nodep->directiveTypesp(), Const)) { - nodep->ctlDirectiveTypes(VAssertDirectiveType{directiveTypesp->toSInt()}); - return true; - } - return false; - } void replaceDisplay(AstDisplay* nodep, const string& prefix) { nodep->fmtp()->text( assertDisplayMessage(nodep, prefix, nodep->fmtp()->text(), nodep->displayType())); @@ -278,8 +355,8 @@ class AssertVisitor final : public VNVisitor { } static AstIf* newIfAssertOn(AstNode* bodyp, VAssertDirectiveType directiveType, VAssertType type = VAssertType::INTERNAL) { - // Add a internal if to check assertions are on. - // Don't make this a AND term, as it's unlikely to need to test this. + // Add an internal if to check assertions are on. + // Don't make this an AND term, as it's unlikely to need to test this. FileLine* const fl = bodyp->fileline(); AstNodeExpr* const condp = assertOnCond(fl, type, directiveType); @@ -288,6 +365,28 @@ class AssertVisitor final : public VNVisitor { newp->user2(true); // Mark as an assertOn() check return newp; } + static AstNodeStmt* newIfAssertPassOn(AstNode* bodyp, VAssertDirectiveType directiveType, + VAssertType type, bool vacuous) { + // Add an internal if to check assertion passOn is enabled. + // Don't make this an AND term, as it's unlikely to need to test this. + FileLine* const fl = bodyp->fileline(); + AstNodeExpr* const condp = assertPassOnCond(fl, type, directiveType, vacuous); + AstNodeIf* const newp = new AstIf{fl, condp, bodyp}; + newp->isBoundsCheck(true); // To avoid LATCH warning + newp->user1(true); // Don't assert/cover this if + return newp; + } + static AstNodeStmt* newIfAssertFailOn(AstNode* bodyp, VAssertDirectiveType directiveType, + VAssertType type) { + // Add an internal if to check assertion failOn is enabled. + // Don't make this an AND term, as it's unlikely to need to test this. + FileLine* const fl = bodyp->fileline(); + AstNodeExpr* const condp = assertFailOnCond(fl, type, directiveType); + AstNodeIf* const newp = new AstIf{fl, condp, bodyp}; + newp->isBoundsCheck(true); // To avoid LATCH warning + newp->user1(true); // Don't assert/cover this if + return newp; + } static AstIf* assertCond(const AstNodeCoverOrAssert* nodep, AstNodeExpr* propp, AstNode* passsp, AstNode* failsp) { @@ -444,6 +543,7 @@ class AssertVisitor final : public VNVisitor { if (failsp) failsp->unlinkFrBackWithNext(); bool selfDestruct = false; + bool passspGated = false; if (const AstCover* const snodep = VN_CAST(nodep, Cover)) { ++m_statCover; if (!v3Global.opt.coverageUser()) { @@ -455,6 +555,9 @@ class AssertVisitor final : public VNVisitor { covincp->unlinkFrBackWithNext(); // next() might have AstAssign for trace if (message != "") covincp->declp()->comment(message); if (passsp) { + passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(), + /*vacuous=*/false); + passspGated = true; passsp = AstNode::addNext(covincp, passsp); } else { passsp = covincp; @@ -475,8 +578,10 @@ class AssertVisitor final : public VNVisitor { VL_RESTORER(m_passsp); VL_RESTORER(m_failsp); + VL_RESTORER(m_assertp); m_passsp = passsp; m_failsp = failsp; + m_assertp = nodep; iterate(nodep->propp()); AstNode* propExprp; @@ -488,6 +593,15 @@ class AssertVisitor final : public VNVisitor { propExprp = nodep->propp()->unlinkFrBack(); } FileLine* const flp = nodep->fileline(); + bool passspAlreadyGated = false; + if (passsp && VN_IS(passsp, If)) passspAlreadyGated = VN_AS(passsp, If)->user1(); + if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr)) { + passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(), + /*vacuous=*/false); + } + if (failsp && !VN_IS(propExprp, PExpr)) { + failsp = newIfAssertFailOn(failsp, nodep->directive(), nodep->userType()); + } AstNode* bodysp = assertBody(nodep, propExprp, passsp, failsp); if (disablep) bodysp = new AstIf{flp, new AstLogNot{flp, disablep}, bodysp}; // Add assertOn check last, for better combining @@ -586,6 +700,34 @@ class AssertVisitor final : public VNVisitor { iterateChildren(nodep); } + if (nodep->user2()) { + // Combine consecutive assertOn checks if possible + if (AstIf* const backp = VN_CAST(nodep->backp(), If)) { + if (backp->nextp() == nodep // + && backp->user2() // + && backp->condp()->sameTree(nodep->condp())) { + ++m_statAssertOnCombined; + backp->addThensp(nodep->thensp()->unlinkFrBackWithNext()); + nodep->unlinkFrBack(); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } + // Combine nested assertOn checks if possible + if (nodep->thensp() && !nodep->thensp()->nextp() && isEmptyStmt(nodep->elsesp())) { + AstIf* const checkp = VN_CAST(nodep->thensp(), If); + if (checkp // + && checkp->user2() // + && checkp->condp()->sameTree(nodep->condp())) { + ++m_statAssertOnCombined; + nodep->addThensp(checkp->thensp()->unlinkFrBackWithNext()); + VL_DO_DANGLING(pushDeletep(checkp->unlinkFrBack()), checkp); + return; + } + } + return; + } + // Swap assertOn check with single statement 'if' statement to bubble up for combining // Note we can't just swap the conditions as they two Ifs have different flags, // so swapping the Ifs themeselves then swapping back the bodies. @@ -611,20 +753,6 @@ class AssertVisitor final : public VNVisitor { } } } - - // Combine consecutive assertOn checks if possible - if (nodep->user2()) { - if (AstIf* const backp = VN_CAST(nodep->backp(), If)) { - if (backp->nextp() == nodep // - && backp->user2() // - && backp->condp()->sameTree(nodep->condp())) { - ++m_statAssertOnCombined; - backp->addThensp(nodep->thensp()->unlinkFrBackWithNext()); - nodep->unlinkFrBack(); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } - } - } } //========== Case assertions @@ -807,8 +935,11 @@ class AssertVisitor final : public VNVisitor { if (nodep->pass() && m_passsp) { // Cover adds COVERINC by AstNode::addNext, thus need to clone next too. stmtsp = m_passsp->cloneTree(true); + stmtsp = newIfAssertPassOn(stmtsp, m_assertp->directive(), m_assertp->userType(), + nodep->vacuous()); } else if (!nodep->pass() && m_failsp) { stmtsp = m_failsp->cloneTree(true); + stmtsp = newIfAssertFailOn(stmtsp, m_assertp->directive(), m_assertp->userType()); } if (stmtsp) { stmtsp->foreachAndNext([](AstNodeVarRef* const refp) { @@ -914,34 +1045,51 @@ class AssertVisitor final : public VNVisitor { visitAssertionIterate(nodep, nodep->failsp()); } void visit(AstAssertCtl* nodep) override { - if (VN_IS(m_modp, Class) || VN_IS(m_modp, Iface)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: assertcontrols in classes or interfaces"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; - } - iterateChildren(nodep); - if (!resolveAssertType(nodep)) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: non-constant assert assertion-type expression"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + bool assertTypeConst = true; + if (!nodep->assertTypesp()) { + nodep->ctlAssertTypes(VAssertType{ALL_ASSERT_TYPES}); + } else if (const AstConst* const assertTypesp = VN_CAST(nodep->assertTypesp(), Const)) { + nodep->ctlAssertTypes(VAssertType{assertTypesp->toSInt()}); + } else { + assertTypeConst = false; + } + + bool controlTypeConst = false; + if (const AstConst* const constp = VN_CAST(nodep->controlTypep(), Const)) { + nodep->ctlType(constp->toSInt()); + controlTypeConst = true; + } + if (controlTypeConst + && (nodep->ctlType() < VAssertCtlType::LOCK + || nodep->ctlType() > VAssertCtlType::VACUOUS_OFF)) { + nodep->unlinkFrBack(); + nodep->v3error("Bad $assertcontrol control_type '" + << cvtToStr(static_cast(nodep->ctlType())) + << "' (IEEE 1800-2023 Table 20-5)"); + VL_DO_DANGLING(pushDeletep(nodep), nodep); return; } - if (nodep->ctlAssertTypes() != ALL_ASSERT_TYPES - && nodep->ctlAssertTypes().containsAny(VAssertType::EXPECT | VAssertType::UNIQUE - | VAssertType::UNIQUE0 + if (assertTypeConst && nodep->ctlAssertTypes() != ALL_ASSERT_TYPES + && nodep->ctlAssertTypes().containsAny(VAssertType::UNIQUE | VAssertType::UNIQUE0 | VAssertType::PRIORITY)) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: assert control assertion_type"); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return; } - if (!resolveControlType(nodep)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: non-const assert control type expression"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; + + bool directiveTypeConst = true; + if (!nodep->directiveTypesp()) { + nodep->ctlDirectiveTypes(VAssertDirectiveType::ASSERT | VAssertDirectiveType::ASSUME + | VAssertDirectiveType::COVER); + } else if (const AstConst* const directiveTypesp + = VN_CAST(nodep->directiveTypesp(), Const)) { + nodep->ctlDirectiveTypes(VAssertDirectiveType{directiveTypesp->toSInt()}); + } else { + directiveTypeConst = false; } - if (!resolveDirectiveType(nodep)) { + if (!directiveTypeConst) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: non-const assert directive type expression"); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); @@ -949,43 +1097,18 @@ class AssertVisitor final : public VNVisitor { } FileLine* const fl = nodep->fileline(); - switch (nodep->ctlType()) { - case VAssertCtlType::ON: - UINFO(9, "Generating assertctl for a module: " << m_modp); - nodep->replaceWith( - new AstCStmt{fl, "vlSymsp->_vm_contextp__->assertOnSet("s - + std::to_string(nodep->ctlAssertTypes()) + ", "s - + std::to_string(nodep->ctlDirectiveTypes()) + ");\n"s}); - break; - case VAssertCtlType::OFF: - case VAssertCtlType::KILL: { - UINFO(9, "Generating assertctl for a module: " << m_modp); - nodep->replaceWith( - new AstCStmt{fl, "vlSymsp->_vm_contextp__->assertOnClear("s - + std::to_string(nodep->ctlAssertTypes()) + " ,"s - + std::to_string(nodep->ctlDirectiveTypes()) + ");\n"s}); - break; - } - case VAssertCtlType::LOCK: - case VAssertCtlType::UNLOCK: - case VAssertCtlType::PASS_ON: - case VAssertCtlType::PASS_OFF: - case VAssertCtlType::FAIL_ON: - case VAssertCtlType::FAIL_OFF: - case VAssertCtlType::NONVACUOUS_ON: - case VAssertCtlType::VACUOUS_OFF: { - nodep->unlinkFrBack(); - nodep->v3warn(E_UNSUPPORTED, "Unsupported: $assertcontrol control_type '" << cvtToStr( - static_cast(nodep->ctlType())) << "'"); - break; - } - default: { - nodep->unlinkFrBack(); - nodep->v3warn(EC_ERROR, "Bad $assertcontrol control_type '" - << cvtToStr(static_cast(nodep->ctlType())) - << "' (IEEE 1800-2023 Table 20-5)"); - } + UINFO(9, "Generating assertctl for a module: " << m_modp); + AstCStmt* const newp = new AstCStmt{fl}; + newp->add("vlSymsp->_vm_contextp__->assertCtl("); + newp->add(nodep->controlTypep()->unlinkFrBack()); + newp->add(", "); + if (nodep->assertTypesp()) { + newp->add(nodep->assertTypesp()->unlinkFrBack()); + } else { + newp->add(std::to_string(ALL_ASSERT_TYPES)); } + newp->add(", " + std::to_string(nodep->ctlDirectiveTypes()) + ");\n"); + nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstAssertIntrinsic* nodep) override { // @@ -1007,10 +1130,12 @@ class AssertVisitor final : public VNVisitor { VL_RESTORER(m_modPastNum); VL_RESTORER(m_modStrobeNum); VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp); + VL_RESTORER(m_finalp); m_modp = nodep; m_modPastNum = 0; m_modStrobeNum = 0; m_modExpr2Sen2DelayedAlwaysp.clear(); + m_finalp = nullptr; iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { diff --git a/src/V3Assert.h b/src/V3Assert.h index dbaac13bf..f144688b6 100644 --- a/src/V3Assert.h +++ b/src/V3Assert.h @@ -24,6 +24,11 @@ //============================================================================ +class V3AssertCommon final { +public: + static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED; +}; + class V3Assert final { public: static void assertAll(AstNetlist* nodep) VL_MT_DISABLED; diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index bf50e97a1..b6ec6964f 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -26,6 +26,7 @@ #include "V3AssertNfa.h" +#include "V3Assert.h" #include "V3Const.h" #include "V3Graph.h" #include "V3Task.h" @@ -77,6 +78,10 @@ public: AstNodeExpr* m_andRhsCondp = nullptr; // OWNED; RHS final condition (may be nullptr) // Reject sink for SAnd rejectOnFail wiring; not a state-signal source bool m_isRejectSink = false; + // In-window vertex of a strong s_always[m:n]: if its state is still set at + // end-of-simulation the universal-quantifier window never completed, which is + // a liveness failure (IEEE 1800-2023 16.12.11 strong semantics). + bool m_strongPending = false; // CONSTRUCTORS explicit SvaStateVertex(V3Graph* graphp) @@ -109,6 +114,9 @@ public: // Reject when source is active and condp is false; set only on // outermost required-step Link bool m_rejectOnFail = false; + // Optional dynamic condition vertex for m_rejectOnFail. Used when the + // success condition is another NFA state rather than a static expression. + SvaStateVertex* m_condVtxp = nullptr; // CONSTRUCTORS SvaTransEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, AstNodeExpr* condp, @@ -176,6 +184,10 @@ struct BuildResult final { // Mid-window sources for range delays (pure boolean RHS): match-only (isUnbounded) std::vector midSources; bool errorEmitted = false; // Builder already emitted specific error; skip generic + // For cover_sequence: when true, midSources already enumerate every + // end-of-match, so wireMatchAndMidSources must NOT add the main + // termVtxp -> matchVertex Link (would double-count via the merge vertex). + bool termIsMidMerge = false; bool valid() const { return termVertexp != nullptr; } static BuildResult fail(bool errored = false) { return {nullptr, nullptr, {}, errored}; } static BuildResult failWithError() { return {nullptr, nullptr, {}, true}; } @@ -186,6 +198,78 @@ static AstNodeExpr* sampled(AstNodeExpr* exprp) { return sp; } +static string assertCtlGetCall(const char* query, VAssertType type, + VAssertDirectiveType directiveType) { + return "vlSymsp->_vm_contextp__->assertCtlGet(VerilatedAssertCtlQuery::"s + query + ", "s + + std::to_string(type) + ", "s + std::to_string(directiveType) + ")"s; +} + +static const char* assertPassOnQuery(bool vacuous) { + static constexpr const char* queries[2] + = {"ASSERT_CTL_PASS_ON_NONVACUOUS", "ASSERT_CTL_PASS_ON_VACUOUS"}; + return queries[vacuous]; +} + +static AstNodeExpr* assertOnCond(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType) { + if (!v3Global.opt.assertOn()) { return new AstConst{flp, AstConst::BitFalse{}}; } + return new AstCExpr{flp, AstCExpr::Pure{}, + assertCtlGetCall("ASSERT_CTL_ON", type, directiveType), 1}; +} + +static AstNodeExpr* assertKillGet(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType) { + return new AstCExpr{flp, AstCExpr::Pure{}, + assertCtlGetCall("ASSERT_CTL_KILL", type, directiveType), 32}; +} + +static string assertActionControlPrefix(VAssertDirectiveType directiveType) { + const int controlled = !!(static_cast(directiveType) + & (static_cast(VAssertDirectiveType::ASSERT) + | static_cast(VAssertDirectiveType::COVER) + | static_cast(VAssertDirectiveType::ASSUME))); + const int checkRuntime = controlled & static_cast(v3Global.opt.assertOn()); + return "("s + std::to_string(controlled ^ 1) + " || ("s + std::to_string(checkRuntime) + + " && "s; +} + +static AstNodeExpr* assertPassOnCond(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType, bool vacuous) { + return new AstCExpr{flp, AstCExpr::Pure{}, + assertActionControlPrefix(directiveType) + + assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType) + + "))"s, + 1}; +} + +static AstNodeExpr* assertFailOnCond(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType) { + return new AstCExpr{flp, AstCExpr::Pure{}, + assertActionControlPrefix(directiveType) + + assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType) + "))"s, + 1}; +} + +static AstIf* newPassOnIf(FileLine* flp, AstNodeExpr* firep, AstNode* bodyp, VAssertType type, + VAssertDirectiveType directiveType, bool vacuous) { + AstNodeExpr* const condp + = new AstLogAnd{flp, firep, assertPassOnCond(flp, type, directiveType, vacuous)}; + AstIf* const ifp = new AstIf{flp, condp, bodyp}; + ifp->isBoundsCheck(true); + ifp->user1(true); + return ifp; +} + +static AstNodeStmt* newIfAssertFailOn(AstNode* bodyp, VAssertDirectiveType directiveType, + VAssertType type) { + FileLine* const flp = bodyp->fileline(); + AstNodeExpr* const condp = assertFailOnCond(flp, type, directiveType); + AstIf* const ifp = new AstIf{flp, condp, bodyp}; + ifp->isBoundsCheck(true); + ifp->user1(true); + return ifp; +} + //###################################################################### // NFA Builder @@ -198,6 +282,17 @@ class SvaNfaBuilder final { // (IEEE 1800-2023 16.12.14 outer-wraps-inner). std::vector m_outerAbortStack; bool m_inUnboundedScope = false; // Sticky: nodes created after inherit liveness + bool m_markStrongPending = false; // Mark new vertices as strong s_always in-window + // IEEE 1800-2023 16.14.3 cover sequence: each end-of-match fires the action, + // not just the first. Builder builds parallel-branch (no first-match-wins) + // topology when true. Default false preserves cover_property semantics. + bool m_isCoverSeq = false; + + struct RangeDelayRejectInfo final { + SvaStateVertex* startp = nullptr; + int range = 0; + int rhsLen = 0; + }; AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) { if (m_throughoutStack.empty()) return baseCondp; @@ -330,6 +425,7 @@ class SvaNfaBuilder final { vtxp->m_throughoutConds.push_back(cp->cloneTreePure(false)); } if (m_inUnboundedScope) vtxp->m_isUnbounded = true; + if (m_markStrongPending) vtxp->m_strongPending = true; return vtxp; } @@ -365,7 +461,7 @@ class SvaNfaBuilder final { // failure, sets outErrorEmitted per semantic-error policy and returns false. bool applyRangeDelay(AstDelay* delayp, AstNodeExpr* rhsExprp, SvaStateVertex*& currentp, std::vector& midSources, FileLine* flp, - bool& outErrorEmitted) { + bool& outErrorEmitted, RangeDelayRejectInfo* rangeRejectInfop = nullptr) { const int minDelay = getConstInt(delayp->lhsp()); if (minDelay < 0) { delayp->v3error("Range delay minimum is not a non-negative elaboration-time constant" @@ -405,6 +501,16 @@ class SvaNfaBuilder final { // count is O(1) in range regardless of user input; no adversarial N // blowup is possible. constexpr int kChainLimit = 256; + // IEEE 1800-2023 16.14.3: only a small bounded range before a plain + // boolean enumerates every end-of-match below. The counter FSM drops + // overlapping ends and the nested-sequence merge collapses them, so + // reject those for a cover sequence rather than under-count. + if (m_isCoverSeq && (range > kChainLimit || VN_IS(rhsExprp, SExpr))) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with this ranged cycle delay"); + outErrorEmitted = true; + return false; + } if (range > kChainLimit) { // Large range: counter FSM. Overlapping triggers during an active // count are dropped (non-overlapping semantics only). @@ -414,8 +520,14 @@ class SvaNfaBuilder final { guardedEdge(currentp, counterVtxp, flp); currentp = counterVtxp; } else if (VN_IS(rhsExprp, SExpr)) { - // Nested-SExpr RHS: merge all [M,N] positions; continuation is per-attempt. + // Nested-SExpr RHS: merge all [M,N] positions. Candidate-local misses + // are not assertion rejects while a later position can still match. + if (rangeRejectInfop) { + const int rhsLen = fixedLength(rhsExprp); + if (rhsLen >= 0) *rangeRejectInfop = {currentp, range, rhsLen}; + } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); + mergeVtxp->m_isUnbounded = true; guardedLink(currentp, mergeVtxp, flp); for (int i = 0; i < range; ++i) { SvaStateVertex* const nextVtxp = scopedCreateVertex(); @@ -424,16 +536,25 @@ class SvaNfaBuilder final { currentp = nextVtxp; } currentp = mergeVtxp; + m_inUnboundedScope = true; } else { // Pure boolean RHS: register chain. Each mid-position links to // match (match-only); last position is the reject source. - AstVar* const hoistVarp = tryHoistSampled(rhsExprp, flp, range); + // For cover_sequence (IEEE 1800-2023 16.14.3) the advance edge is + // unconditional so every (start, end) pair fires independently -- + // dropping NOT(b) turns "first-match-wins" into "every end fires". + AstVar* const hoistVarp + = m_isCoverSeq ? nullptr : tryHoistSampled(rhsExprp, flp, range); midSources.push_back(currentp); for (int i = 0; i < range; ++i) { SvaStateVertex* const nextVtxp = scopedCreateVertex(); - AstNodeExpr* const notExprp - = new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)}; - guardedEdge(currentp, nextVtxp, notExprp, flp); + if (m_isCoverSeq) { + guardedEdge(currentp, nextVtxp, flp); + } else { + AstNodeExpr* const notExprp + = new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)}; + guardedEdge(currentp, nextVtxp, notExprp, flp); + } if (i < range - 1) midSources.push_back(nextVtxp); currentp = nextVtxp; } @@ -441,12 +562,44 @@ class SvaNfaBuilder final { return true; } + void addFiniteRangeReject(const RangeDelayRejectInfo& info, const BuildResult& result, + FileLine* flp) { + if (!info.startp) return; + + SvaStateVertex* const expiryVtxp + = addDelayChain(info.startp, info.range + info.rhsLen, flp); + SvaStateVertex* const expiryMatchp = scopedCreateVertex(); + std::vector sources = result.midSources; + sources.push_back(result.termVertexp); + for (SvaStateVertex* const srcp : sources) { + AstNodeExpr* const condp + = result.finalCondp ? sampled(result.finalCondp->cloneTreePure(false)) : nullptr; + SvaStateVertex* const successNowp = scopedCreateVertex(); + guardedLink(srcp, successNowp, condp, flp); + SvaStateVertex* stagep = successNowp; + guardedLink(stagep, expiryMatchp, flp); + for (int i = 0; i < info.range; ++i) { + SvaStateVertex* const nextp = scopedCreateVertex(); + guardedEdge(stagep, nextp, flp); + stagep = nextp; + guardedLink(stagep, expiryMatchp, flp); + } + } + + SvaStateVertex* const sinkVtxp = m_graph.createStateVertex(); + sinkVtxp->m_isRejectSink = true; + SvaTransEdge* const rejectp = m_graph.addLink(expiryVtxp, sinkVtxp); + rejectp->m_rejectOnFail = true; + rejectp->m_condVtxp = expiryMatchp; + } + BuildResult buildSExpr(AstSExpr* sexprp, SvaStateVertex* entryVtxp, bool isTopLevelStep = false) { AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay); if (!delayp || !delayp->isCycleDelay()) return BuildResult::fail(); FileLine* const flp = sexprp->fileline(); + AstNodeExpr* const exprp = sexprp->exprp(); // Handle LHS (preExpr) SvaStateVertex* currentp = entryVtxp; @@ -469,10 +622,12 @@ class SvaNfaBuilder final { // Handle delay std::vector rangeMidSources; + RangeDelayRejectInfo rangeRejectInfo; + const bool addRangeReject = isTopLevelStep && !m_inUnboundedScope; if (delayp->isRangeDelay()) { bool errorEmitted = false; if (!applyRangeDelay(delayp, sexprp->exprp(), currentp, rangeMidSources, flp, - errorEmitted)) { + errorEmitted, addRangeReject ? &rangeRejectInfo : nullptr)) { return BuildResult::fail(errorEmitted); } } else { @@ -487,8 +642,11 @@ class SvaNfaBuilder final { } // Multi-cycle RHS: recurse (only plain boolean is returned as finalCondp). - AstNodeExpr* const exprp = sexprp->exprp(); - if (exprp->isMultiCycleSva()) return buildExpr(exprp, currentp, isTopLevelStep); + if (exprp->isMultiCycleSva()) { + const BuildResult result = buildExpr(exprp, currentp, isTopLevelStep); + if (result.valid()) addFiniteRangeReject(rangeRejectInfo, result, flp); + return result; + } return {currentp, exprp, std::move(rangeMidSources)}; } @@ -516,6 +674,10 @@ class SvaNfaBuilder final { if (exceedsAssertUnrollLimit(repp, totalSites)) return BuildResult::failWithError(); AstVar* const hoistVarp = tryHoistSampled(exprp, flp, totalSites); + // Cover-sequence (IEEE 1800-2023 16.14.3): collect each end-of-match + // position so they all fire the action, not just the merged terminal. + std::vector consMidSources; + SvaStateVertex* currentp = entryVtxp; for (int i = 0; i < minN; ++i) { if (i > 0) { @@ -531,6 +693,10 @@ class SvaNfaBuilder final { if (isTopLevelStep && (i == 0 || i == minN - 1)) { linkp->m_rejectOnFail = true; } currentp = condVtxp; } + // After minN: currentp is the first valid end-of-match position for [*m:n]. + if (m_isCoverSeq && (repp->unbounded() || repp->maxCountp())) { + consMidSources.push_back(currentp); + } if (repp->unbounded()) { if (minN == 0) { @@ -564,11 +730,19 @@ class SvaNfaBuilder final { guardedLink(nextVtxp, checkVtxp, sampledRefOrClone(hoistVarp, exprp, flp), flp); guardedLink(checkVtxp, mergeVtxp, flp); currentp = checkVtxp; + if (m_isCoverSeq) consMidSources.push_back(checkVtxp); } currentp = mergeVtxp; } // finalCond = nullptr (already checked via Links) - return {currentp, nullptr, {}}; + BuildResult res; + res.termVertexp = currentp; + res.finalCondp = nullptr; + res.midSources = std::move(consMidSources); + // mergeVtxp is the OR of all the end-positions we already pushed to + // midSources, so the main termVtxp -> matchVertex Link would duplicate. + res.termIsMidMerge = m_isCoverSeq && !res.midSources.empty(); + return res; } // always[lo:hi] / s_always[lo:hi] (IEEE 1800-2023 16.12.11). @@ -577,10 +751,37 @@ class SvaNfaBuilder final { FileLine* const flp = nodep->fileline(); AstNodeExpr* const propp = nodep->propp(); const int lo = getConstInt(nodep->loBoundp()); + if (VN_IS(nodep->hiBoundp(), Unbounded)) { + // Weak always [lo:$]: unbounded upper bound (IEEE 1800-2023 16.12.11). + // p must hold at every clock tick at least lo cycles after the attempt + // start; those ticks are not required to exist, so there is no + // end-of-trace obligation (weak). The self-loop keeps the attempt live + // every cycle; each observed cycle is a safety obligation, so a false p + // rejects immediately. + UASSERT_OBJ(!nodep->isStrong() && lo >= 0, nodep, + "Unbounded always must be weak with non-negative lo (V3Width)"); + SvaStateVertex* const livep = addDelayChain(entryVtxp, lo, flp); + livep->m_isUnbounded = true; + guardedEdge(livep, livep, flp); // stay active every subsequent cycle + SvaStateVertex* const sinkp = m_graph.createStateVertex(); + sinkp->m_isRejectSink = true; + SvaTransEdge* const rejEdgep + = guardedLink(livep, sinkp, sampled(propp->cloneTreePure(false)), flp); + if (isTopLevelStep) rejEdgep->m_rejectOnFail = true; + return {livep, nullptr, {}}; + } const int hi = getConstInt(nodep->hiBoundp()); UASSERT_OBJ(lo >= 0 && hi >= lo, nodep, "PropAlways bounds invariant (V3Width)"); if (exceedsAssertUnrollLimit(nodep, hi - lo + 1)) return BuildResult::failWithError(); AstVar* const hoistVarp = tryHoistSampled(propp, flp, hi - lo + 1); + // Strong s_always[m:n]: mark every in-window registered vertex so an + // attempt still mid-window at end-of-simulation is reported as a liveness + // failure (IEEE strong: the n+1 ticks must exist). An attempt that has + // completed earlier in the trace has already cleared its state, so it is + // not flagged; an attempt whose final tick coincides with $finish is still + // flagged, matching the strong reference. Weak always[m:n] is not marked. + VL_RESTORER(m_markStrongPending); + m_markStrongPending = nodep->isStrong(); SvaStateVertex* currentp = addDelayChain(entryVtxp, lo, flp); for (int k = 0; k <= hi - lo; ++k) { if (k > 0) { @@ -607,6 +808,16 @@ class SvaNfaBuilder final { UASSERT_OBJ(maxN >= minN, repp, "GotoRep range max < min (V3Width invariant)"); if (exceedsAssertUnrollLimit(repp, maxN)) return BuildResult::failWithError(); + // IEEE 1800-2023 16.14.3: a ranged goto repetition b[->M:N] ends at every + // M..N-th match, but only the shared merge vertex below reaches the + // terminal, so a cover sequence would under-count. Reject the ranged form + // (the single-count b[->N] has one end and is enumerated correctly). + if (m_isCoverSeq && hasMax && maxN > minN) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with a ranged goto repetition"); + return BuildResult::failWithError(); + } + // Wait + match per iter -> 2 sites per iteration; range form needs // sites for every iteration in [0..maxN). NOT($sampled(x)) matches // $sampled(NOT(x)) at the value level (IEEE 1800-2023 16.9.9); @@ -661,6 +872,16 @@ class SvaNfaBuilder final { if (!lhs.valid() || !rhs.valid()) { // LCOV_EXCL_START -- sub-build fail bail return BuildResult::fail(lhs.errorEmitted || rhs.errorEmitted); } // LCOV_EXCL_STOP + // IEEE 1800-2023 16.14.3: a cover sequence counts every end-of-match. A + // sequence operand of 'or' can end more than once, but only its final + // end reaches the merge vertex below, so reject sequence operands rather + // than under-count. Plain boolean disjunction has one end per cycle and + // is handled by the OR-fold. + if (m_isCoverSeq && (lhs.termVertexp != entryVtxp || rhs.termVertexp != entryVtxp)) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with a sequence operand of 'or'"); + return BuildResult::failWithError(); + } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); if (lhs.finalCondp) { guardedLink(lhs.termVertexp, mergeVtxp, sampled(lhs.finalCondp->cloneTreePure(false)), @@ -970,10 +1191,12 @@ class SvaNfaBuilder final { } public: - SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames) + SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames, + bool isCoverSeq = false) : m_graph{graph} , m_modp{modp} - , m_propTempNames{propTempNames} {} + , m_propTempNames{propTempNames} + , m_isCoverSeq{isCoverSeq} {} // Reset scope between antecedent and consequent: liveness must not leak. void resetScope() { @@ -1129,6 +1352,9 @@ class SvaNfaLowering final { AstNodeExpr* matchCondp; // Final boolean match condition (may be nullptr) AstVar* disableCntVarp; // disable counter var (may be nullptr) AstVar* snapshotVarp; // disable snapshot var (may be nullptr) + VAssertType assertType; // Assertion type for control tasks + VAssertDirectiveType directiveType; // Directive type for control tasks + AstVar* killVarp; // Last observed kill generation SvaGraph& graph; // NFA graph }; @@ -1147,6 +1373,15 @@ class SvaNfaLowering final { if (!ap) return bp; return new AstLogOr{flp, ap, bp}; } + static AstNodeExpr* killActive(LowerCtx& c) { + return new AstNeq{c.flp, new AstVarRef{c.flp, c.killVarp, VAccess::READ}, + assertKillGet(c.flp, c.assertType, c.directiveType)}; + } + static AstNodeExpr* notKillActive(LowerCtx& c) { return new AstLogNot{c.flp, killActive(c)}; } + static AstNodeExpr* gateNotKill(LowerCtx& c, AstNodeExpr* exprp) { + if (!exprp) return nullptr; + return new AstLogAnd{c.flp, exprp, notKillActive(c)}; + } // Phase 3 output signals struct SignalSet final { @@ -1186,6 +1421,7 @@ class SvaNfaLowering final { UASSERT_OBJ(nextStatep, c.vtx[i], "Registered vertex has no clocked incoming contribution"); + nextStatep = gateNotKill(c, nextStatep); AstAssignDly* const assignp = new AstAssignDly{ c.flp, new AstVarRef{c.flp, c.vtx[i]->datap()->stateVarp, VAccess::WRITE}, @@ -1256,7 +1492,8 @@ class SvaNfaLowering final { = new AstEq{c.flp, new AstVarRef{c.flp, cntp, VAccess::READ}, new AstConst{c.flp, AstConst::WidthedValue{}, 32, counterMax}}; - AstNodeExpr* const donep = new AstLogOr{c.flp, matchedNowp, counterAtEndp}; + AstNodeExpr* const donep = new AstLogOr{ + c.flp, killActive(c), new AstLogOr{c.flp, matchedNowp, counterAtEndp}}; AstAssignDly* const clearActivep = new AstAssignDly{c.flp, new AstVarRef{c.flp, activep, VAccess::WRITE}, @@ -1276,7 +1513,8 @@ class SvaNfaLowering final { = new AstAssignDly{c.flp, new AstVarRef{c.flp, cntp, VAccess::WRITE}, new AstConst{c.flp, AstConst::WidthedValue{}, 32, 0u}}; setActivep->addNext(resetCountp); - AstIf* const startIfp = new AstIf{c.flp, incomingp, setActivep, nullptr}; + AstIf* const startIfp + = new AstIf{c.flp, gateNotKill(c, incomingp), setActivep, nullptr}; AstIf* const topIfp = new AstIf{c.flp, new AstVarRef{c.flp, activep, VAccess::READ}, doneIfp, startIfp}; @@ -1335,19 +1573,31 @@ class SvaNfaLowering final { AstIf* const setRIfp = new AstIf{c.flp, gateRp, setRp, nullptr}; setLIfp->addNext(setRIfp); - AstIf* const topp = new AstIf{ - c.flp, c.vtx[ai]->datap()->stateSigp->cloneTreePure(false), clearLp, setLIfp}; + AstNodeExpr* const clearCondp = new AstLogOr{ + c.flp, killActive(c), c.vtx[ai]->datap()->stateSigp->cloneTreePure(false)}; + AstIf* const topp = new AstIf{c.flp, clearCondp, clearLp, setLIfp}; m_modp->addStmtsp( new AstAlways{c.flp, VAlwaysKwd::ALWAYS, c.senTreep->cloneTree(false), topp}); } } + void emitKillAckNba(LowerCtx& c) { + AstAssignDly* const ackp + = new AstAssignDly{c.flp, new AstVarRef{c.flp, c.killVarp, VAccess::WRITE}, + assertKillGet(c.flp, c.assertType, c.directiveType)}; + m_modp->addStmtsp(new AstAlways{c.flp, VAlwaysKwd::ALWAYS, c.senTreep->cloneTree(false), + new AstIf{c.flp, killActive(c), ackp, nullptr}}); + } + // Phase 3/3a/3b: Compute terminal match/reject signals, required-step reject, // throughout-drop reject; clean up intermediate state signals. // Phase 3: terminalActive and rejectBase from Links to matchVertex. // Builder only adds Links (non-clocked) to matchVertex via addLink in - // wireMatchAndMidSources. - void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs) { + // wireMatchAndMidSources. When outPerMidSrcsp is non-null, also collect + // the per-edge match signal (IEEE 1800-2023 16.14.3 cover sequence: each + // end-of-match fires the action independently, no OR-fold). + void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs, + std::vector* outPerMidSrcsp = nullptr) { for (const SvaTransEdge* const tep : c.edges) { if (tep->toVtxp() != c.graph.m_matchVertexp) continue; const int fi = tep->fromVtxp()->color(); @@ -1359,6 +1609,18 @@ class SvaNfaLowering final { if (snapshotOkp) { srcSigp = new AstLogAnd{c.flp, srcSigp, snapshotOkp->cloneTreePure(false)}; } + if (outPerMidSrcsp) { + // Per-mid signal must also AND in matchCondp (the final boolean + // check, e.g. sampled(b) for `a ##[1:3] b`). assembleResult does + // this for the OR-collapsed terminalActivep; we replicate it + // per-edge here so each end-of-match is gated identically. + AstNodeExpr* perMidp = srcSigp->cloneTreePure(false); + if (c.matchCondp) { + perMidp = new AstLogAnd{c.flp, perMidp, + sampled(c.matchCondp->cloneTreePure(false))}; + } + outPerMidSrcsp->push_back(perMidp); + } if (tep->fromVtxp()->m_isCounter) { sigs.terminalActivep @@ -1409,7 +1671,8 @@ class SvaNfaLowering final { } } - SignalSet computeSignals(LowerCtx& c, std::vector* outRequiredStepSrcsp) { + SignalSet computeSignals(LowerCtx& c, std::vector* outRequiredStepSrcsp, + std::vector* outPerMidSrcsp = nullptr) { SignalSet sigs; // Snapshot comparison expression for disable-iff counter. @@ -1421,19 +1684,29 @@ class SvaNfaLowering final { new AstVarRef{c.flp, c.disableCntVarp, VAccess::READ}}; } - computeTerminalMatchAndReject(c, snapshotOkp, sigs); + computeTerminalMatchAndReject(c, snapshotOkp, sigs, outPerMidSrcsp); // Phase 3a: required-step rejection. - // Builder only sets m_rejectOnFail on non-clocked Links with m_condp, - // and the source always has a resolved stateSig. + // Builder only sets m_rejectOnFail on non-clocked Links with m_condp + // or m_condVtxp, and the source always has a resolved stateSig. for (const SvaTransEdge* const tep : c.edges) { if (!tep->m_rejectOnFail) continue; const int fi = tep->fromVtxp()->color(); - UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && tep->m_condp, tep->fromVtxp(), - "rejectOnFail Link must have condp and source stateSig"); + UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && (tep->m_condp || tep->m_condVtxp), + tep->fromVtxp(), + "rejectOnFail Link must have condp/condVtxp and source stateSig"); AstNodeExpr* const srcSigp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false); - AstNodeExpr* const notCondp = new AstLogNot{c.flp, tep->m_condp->cloneTreePure(false)}; - AstNodeExpr* const failp = new AstLogAnd{c.flp, srcSigp, notCondp}; + AstNodeExpr* condp = nullptr; + if (tep->m_condVtxp) { + const int ci = tep->m_condVtxp->color(); + UASSERT_OBJ(c.vtx[ci]->datap()->stateSigp, tep->m_condVtxp, + "rejectOnFail condVtxp missing stateSig"); + condp = c.vtx[ci]->datap()->stateSigp->cloneTreePure(false); + } else { + condp = tep->m_condp->cloneTreePure(false); + } + AstNodeExpr* const notCondp = new AstLogNot{c.flp, condp}; + AstNodeExpr* const failp = gateNotKill(c, new AstLogAnd{c.flp, srcSigp, notCondp}); if (outRequiredStepSrcsp) { outRequiredStepSrcsp->push_back(failp->cloneTreePure(false)); } @@ -1441,6 +1714,9 @@ class SvaNfaLowering final { } computeThroughoutReject(c, sigs); + sigs.terminalActivep = gateNotKill(c, sigs.terminalActivep); + sigs.rejectBasep = gateNotKill(c, sigs.rejectBasep); + sigs.throughoutRejectp = gateNotKill(c, sigs.throughoutRejectp); // Clean up intermediate state signals. These are orphan subtrees // (never linked into the enclosing AST); deleteTree() is immediate @@ -1648,7 +1924,10 @@ public: AstNodeExpr* disableExprp = nullptr, bool negated = false, AstNodeExpr** outMatchpp = nullptr, AstVar* disableCntVarp = nullptr, AstVar* snapshotVarp = nullptr, - std::vector* outRequiredStepSrcsp = nullptr) { + std::vector* outRequiredStepSrcsp = nullptr, + std::vector* outPerMidSrcsp = nullptr, + VAssertType assertType = VAssertType::INTERNAL, + VAssertDirectiveType directiveType = VAssertDirectiveType::INTERNAL) { const std::string baseName = m_names.get(""); // Number vertices with sequential colors for array indexing. @@ -1681,6 +1960,10 @@ public: } AstNodeDType* const u32DTypep = m_modp->findBasicDType(VBasicDTypeKwd::UINT32); + AstVar* const killVarp + = new AstVar{flp, VVarType::MODULETEMP, baseName + "__kill", u32DTypep}; + killVarp->lifetime(VLifetime::STATIC_EXPLICIT); + m_modp->addStmtsp(killVarp); for (int i = 0; i < N; ++i) { if (vtx[i]->m_isAndCombiner) { const std::string base = baseName + "__a" + std::to_string(i); @@ -1721,9 +2004,9 @@ public: } // Build lowering context for phase sub-functions. - LowerCtx c{flp, N, vtx, edges, startIdx, - matchIdx, senTreep, disableExprp, matchCondp, disableCntVarp, - snapshotVarp, graph}; + LowerCtx c{flp, N, vtx, edges, startIdx, + matchIdx, senTreep, disableExprp, matchCondp, disableCntVarp, + snapshotVarp, assertType, directiveType, killVarp, graph}; // Phase 1: Resolve combinational Links via fixed-point propagation. resolveLinks(c, triggerExprp); @@ -1732,14 +2015,42 @@ public: emitStateRegisterNba(c); emitCounterFsmNba(c); emitAndCombinerDoneLatchNba(c); + emitKillAckNba(c); // Phase 3/3a/3b: Compute terminal match/reject signals (cleans up stateSig). - const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp); + const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp, outPerMidSrcsp); AstNodeExpr* const resultp = assembleResult( flp, isCover, negated, matchCondp, sigs.terminalActivep, sigs.rejectBasep, sigs.throughoutRejectp, sigs.requiredStepRejectp, outMatchpp); + // Strong s_always[m:n] end-of-simulation liveness: if any in-window state + // is still set at $finish, the universal-quantifier window never completed + // (IEEE 1800-2023 16.12.11 strong semantics). Fire the assertion failure + // from a final block; V3Assert turns the DT_ERROR display into the standard + // "Assertion failed in %m" message. + AstNodeExpr* pendingp = nullptr; + for (int i = 0; i < N; ++i) { + if (!vtx[i]->m_strongPending || !vtx[i]->datap()->stateVarp) continue; + AstNodeExpr* const svp = new AstVarRef{flp, vtx[i]->datap()->stateVarp, VAccess::READ}; + if (!pendingp) { + pendingp = svp; + } else { + pendingp = new AstLogOr{flp, pendingp, svp}; + } + } + if (pendingp) { + AstCExpr* const assertOnp + = new AstCExpr{flp, AstCExpr::Pure{}, "vlSymsp->_vm_contextp__->assertOn()", 1}; + AstNodeExpr* const condp = new AstLogAnd{flp, assertOnp, pendingp}; + AstDisplay* const dispp + = new AstDisplay{flp, VDisplayType::DT_ERROR, "", nullptr, nullptr}; + dispp->fmtp()->timeunit(m_modp->timeunit()); + AstNodeStmt* const firep = dispp; + if (v3Global.opt.stopFail()) firep->addNext(new AstStop{flp, false}); + m_modp->addStmtsp(new AstFinal{flp, new AstIf{flp, condp, firep}}); + } + // Clear userp on every vertex before vertexData unique_ptrs are destroyed. for (int i = 0; i < N; ++i) vtx[i]->userp(nullptr); return resultp; @@ -1765,7 +2076,10 @@ class AssertNfaVisitor final : public VNVisitor { // Wire match vertex and mid-window sources for a successful NFA build. static void wireMatchAndMidSources(SvaGraph& graph, const BuildResult& result, FileLine* flp) { graph.createMatchVertex(); - graph.addLink(result.termVertexp, graph.m_matchVertexp); + // Skip the main term Link when midSources already cover every + // end-of-match (cover_sequence path); otherwise the per-mid extraction + // double-counts via the merge vertex. + if (!result.termIsMidMerge) { graph.addLink(result.termVertexp, graph.m_matchVertexp); } for (SvaStateVertex* srcVtxp : result.midSources) { AstNodeExpr* condp = nullptr; for (AstNodeExpr* const tc : srcVtxp->m_throughoutConds) { @@ -1968,6 +2282,38 @@ class AssertNfaVisitor final : public VNVisitor { return parts; } + static bool canSplitImplicationPassActions(const PropertyParts& parts) { + UASSERT(parts.hasImplication, + "Implication pass action split requested without implication"); + UASSERT(parts.triggerExprp, "Implication pass action split requested without trigger"); + // Direct vacuous/nonvacuous classification uses the antecedent value in the current + // assertion attempt. Leave delayed antecedents on the existing NFA pass path. + return !hasMultiCycleExpr(parts.triggerExprp); + } + + static void splitImplicationPassActions(AstAssert* assertp, const PropertyParts& parts, + AstNodeExpr* nonvacuousMatchp) { + FileLine* const flp = assertp->fileline(); + + AstNode* const passsp = assertp->passsp()->unlinkFrBackWithNext(); + AstNode* splitsp = nullptr; + + if (!parts.isFollowedBy) { + AstNodeExpr* const vacuousp + = new AstLogNot{flp, sampled(parts.triggerExprp->cloneTreePure(false))}; + AstNode* const vacuousBodyp = passsp->cloneTree(false); + splitsp = newPassOnIf(flp, vacuousp, vacuousBodyp, assertp->userType(), + assertp->directive(), /*vacuous=*/true); + } + + AstIf* const nonvacuousp + = newPassOnIf(flp, nonvacuousMatchp, passsp, assertp->userType(), assertp->directive(), + /*vacuous=*/false); + splitsp = splitsp ? AstNode::addNext(splitsp, nonvacuousp) + : static_cast(nonvacuousp); + assertp->addPasssp(splitsp); + } + // Allocate disable-iff counter + snapshot vars and unlink the original // disable expression from the PropSpec. Returns {cntp, snapp} or // {nullptr, nullptr} if no counter is needed. @@ -2014,6 +2360,43 @@ class AssertNfaVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); } + // Hoist a leading clocking event (IEEE 1800-2023 16.7): + bool hoistClockedSeq(AstPropSpec* specp) { + while (AstSClocked* const clockedp = VN_CAST(specp->propp(), SClocked)) { + if (specp->sensesp()) { + clockedp->v3warn(E_UNSUPPORTED, "Unsupported: multiclocked sequence or property"); + replaceBodyOnBuildError(specp->fileline(), specp, true); + return true; + } + for (const AstSenItem* sp = clockedp->sensesp(); sp; + sp = VN_CAST(sp->nextp(), SenItem)) { + if (!sp->edgeType().anEdge()) { + clockedp->v3warn(E_UNSUPPORTED, + "Unsupported: non-edge clocking event on a sequence; " + "use an edge such as @(posedge clk)"); + replaceBodyOnBuildError(specp->fileline(), specp, true); + return true; + } + } + specp->sensesp(clockedp->sensesp()->unlinkFrBackWithNext()); + AstNodeExpr* const bodyp = clockedp->exprp()->unlinkFrBack(); + clockedp->replaceWith(bodyp); + VL_DO_DANGLING(pushDeletep(clockedp), clockedp); + } + // A clocking event anywhere else in the sequence is not supported. + const AstSClocked* nestedp = nullptr; + specp->propp()->foreach([&](const AstSClocked* p) { + if (!nestedp) nestedp = p; + }); + if (nestedp) { + nestedp->v3warn(E_UNSUPPORTED, + "Unsupported: clocking event inside sequence expression"); + replaceBodyOnBuildError(specp->fileline(), specp, true); + return true; + } + return false; + } + // Build the NFA graph for a property body, handling both the antecedent // |-> consequent and simple sequence cases. Returns the consequent/body // BuildResult (invalid on parse/build failure). @@ -2066,7 +2449,11 @@ class AssertNfaVisitor final : public VNVisitor { cumulativeOrp->cloneTreePure(false)}; m_modp->addStmtsp( new AstAlways{flp, VAlwaysKwd::ALWAYS, perSrcSenTreep->cloneTree(false), - new AstIf{flp, condp, failsp->cloneTree(true), nullptr}}); + new AstIf{flp, condp, + newIfAssertFailOn(failsp->cloneTree(true), + assertWithFailp->directive(), + assertWithFailp->userType()), + nullptr}}); cumulativeOrp = new AstLogOr{flp, cumulativeOrp, srcp->cloneTreePure(false)}; } VL_DO_DANGLING(pushDeletep(cumulativeOrp), cumulativeOrp); @@ -2189,6 +2576,10 @@ class AssertNfaVisitor final : public VNVisitor { inlineAllSequenceRefs(assertp->propp()); + if (AstPropSpec* const specp = VN_CAST(assertp->propp(), PropSpec)) { + if (hoistClockedSeq(specp)) return; + } + AstNode* const propp = assertp->propp(); if (!hasMultiCycleExpr(propp)) return; if (isBareTopLevelUntil(propp)) return; @@ -2234,9 +2625,11 @@ class AssertNfaVisitor final : public VNVisitor { 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}; + SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq}; const BuildResult result = buildAssertionGraph(builder, graph, seqBodyp, parts, flp); if (result.valid()) wireMatchAndMidSources(graph, result, flp); @@ -2259,8 +2652,11 @@ class AssertNfaVisitor final : public VNVisitor { const bool disableExprUnlinked = disableCntVarp && disableExprp; AstAssert* const assertAssertp = VN_CAST(assertp, Assert); - const bool needMatch - = !isCover && !parts.hasImplication && assertAssertp && assertAssertp->passsp(); + const bool splitImplicationPasssp = assertAssertp && assertAssertp->passsp() + && parts.hasImplication + && canSplitImplicationPassActions(parts); + const bool needMatch = assertAssertp && assertAssertp->passsp() + && (!parts.hasImplication || splitImplicationPasssp); AstNodeExpr* matchExprp = nullptr; AstAssert* const assertWithFailp = VN_CAST(assertp, Assert); @@ -2268,12 +2664,20 @@ class AssertNfaVisitor final : public VNVisitor { = !isCover && !parts.hasImplication && assertWithFailp && assertWithFailp->failsp(); std::vector requiredStepSrcs; - AstNodeExpr* const alwaysTriggerp = new AstConst{flp, AstConst::BitTrue{}}; - AstNodeExpr* const outputExprp - = m_loweringp->lower(flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, - disableExprp ? disableExprp->cloneTreePure(false) : nullptr, - negated, needMatch ? &matchExprp : nullptr, disableCntVarp, - snapshotVarp, needPerSrcFail ? &requiredStepSrcs : nullptr); + // For `cover sequence` (IEEE 1800-2023 16.14.3) collect per-edge match + // signals so each end-of-match fires the action independently, rather + // than getting OR-folded into a single per-cycle terminalActive. + // coverp / isCoverSeq are computed earlier (passed to SvaNfaBuilder). + std::vector perMidSrcs; + + AstNodeExpr* const alwaysTriggerp + = 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()); AstSenTree* const perSrcSenTreep = (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr; @@ -2283,12 +2687,48 @@ class AssertNfaVisitor final : public VNVisitor { if (disableExprUnlinked) VL_DO_DANGLING(pushDeletep(disableExprp), disableExprp); if (result.finalCondp && !result.finalCondp->backp()) pushDeletep(result.finalCondp); - attachMatchHandlers(flp, assertAssertp, assertWithFailp, needMatch ? matchExprp : nullptr, - perSrcSenTreep, requiredStepSrcs); + if (splitImplicationPasssp) { + splitImplicationPassActions(assertAssertp, parts, matchExprp); + matchExprp = nullptr; + } else { + attachMatchHandlers(flp, assertAssertp, assertWithFailp, + needMatch ? matchExprp : nullptr, perSrcSenTreep, + requiredStepSrcs); + matchExprp = nullptr; + } - AstNode* const innerPropp = propSpecp->propp(); - innerPropp->replaceWith(outputExprp); - VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); + if (isCoverSeq && perMidSrcs.size() > 1) { + // Clone AstCover (N-1) times, each gated by its own per-mid signal. + // V3Assert sees N independent covers and emits N `if (cond_i) {coverinc; + // userAction}` bodies; the shared AstCoverDecl bucket is incremented + // per fire, matching IEEE "executed each time the sequence matches." + // Clones reuse AstCover->propp's original SVA tree, but we overwrite + // each clone's inner propp with the corresponding per-mid signal + // BEFORE the next iterator step, so hasMultiCycleExpr() returns false + // and processAssertion skips them on revisit. + std::vector coverList; + coverList.push_back(coverp); + for (size_t i = 1; i < perMidSrcs.size(); ++i) { + AstCover* const clonep = coverp->cloneTree(false); + coverp->addNextHere(clonep); + coverList.push_back(clonep); + } + for (size_t i = 0; i < perMidSrcs.size(); ++i) { + AstPropSpec* const clonePropSpecp = VN_CAST(coverList[i]->propp(), PropSpec); + AstNode* const innerp = clonePropSpecp->propp(); + innerp->replaceWith(perMidSrcs[i]); + VL_DO_DANGLING(pushDeletep(innerp), innerp); + } + // Discard the OR-collapsed fallback signal -- cover_sequence path + // does not use it. + VL_DO_DANGLING(outputExprp->deleteTree(), outputExprp); + } else { + AstNode* const innerPropp = propSpecp->propp(); + innerPropp->replaceWith(outputExprp); + VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); + // If we collected per-mid (N==1) but didn't clone, drop the spare. + for (AstNodeExpr* const sp : perMidSrcs) pushDeletep(sp); + } UINFO(4, "NFA converted assertion at " << flp << endl); } @@ -2301,7 +2741,7 @@ class AssertNfaVisitor final : public VNVisitor { VL_RESTORER(m_defaultDisablep); m_modp = nodep; m_defaultClockingp = nullptr; - m_defaultDisablep = nullptr; + m_defaultDisablep = nodep->defaultDisablep(); SvaNfaLowering lowering{nodep}; m_loweringp = &lowering; iterateChildren(nodep); @@ -2310,9 +2750,12 @@ class AssertNfaVisitor final : public VNVisitor { if (nodep->isDefault() && !m_defaultClockingp) m_defaultClockingp = nodep; iterateChildren(nodep); } - void visit(AstDefaultDisable* nodep) override { - if (!m_defaultDisablep) m_defaultDisablep = nodep; + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); } + void visit(AstDefaultDisable* nodep) override {} void visit(AstAssert* nodep) override { processAssertion(nodep); } void visit(AstCover* nodep) override { processAssertion(nodep); } void visit(AstRestrict* nodep) override { diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 54d6d90e0..323731517 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -23,6 +23,7 @@ #include "V3AssertPre.h" +#include "V3Assert.h" #include "V3Const.h" #include "V3Task.h" #include "V3UniqueNames.h" @@ -60,6 +61,7 @@ private: AstNodeExpr* m_disablep = nullptr; // Last disable AstIf* m_disableSeqIfp = nullptr; // Used for handling disable iff in sequences AstPExpr* m_pexprp = nullptr; // Last AstPExpr + bool m_underCover = false; // True if the enclosing assertion is a cover // Other: V3UniqueNames m_cycleDlyNames{"__VcycleDly"}; // Cycle delay counter name generator V3UniqueNames m_consRepNames{"__VconsRep"}; // Consecutive repetition counter name generator @@ -1282,7 +1284,10 @@ private: // Don't iterate pexprp here -- it was already iterated when created // (in visit(AstSExpr*)), so delays and disable iff are already processed. } else if (nodep->isOverlapped()) { - nodep->replaceWith(new AstLogOr{flp, new AstLogNot{flp, lhsp}, rhsp}); + AstNodeExpr* const exprp + = m_underCover ? static_cast(new AstLogAnd{flp, lhsp, rhsp}) + : new AstLogOr{flp, new AstLogNot{flp, lhsp}, rhsp}; + nodep->replaceWith(exprp); } else { if (m_disablep) { lhsp = new AstAnd{flp, new AstNot{flp, m_disablep->cloneTreePure(false)}, lhsp}; @@ -1291,7 +1296,9 @@ private: AstPast* const pastp = new AstPast{flp, lhsp}; pastp->dtypeFrom(lhsp); pastp->sentreep(newSenTree(nodep)); - AstNodeExpr* const exprp = new AstOr{flp, new AstNot{flp, pastp}, rhsp}; + AstNodeExpr* const exprp + = m_underCover ? static_cast(new AstAnd{flp, pastp, rhsp}) + : new AstOr{flp, new AstNot{flp, pastp}, rhsp}; exprp->dtypeSetBit(); nodep->replaceWith(exprp); } @@ -1439,12 +1446,6 @@ private: } void visit(AstDefaultDisable* nodep) override { - if (m_defaultDisablep) { - nodep->v3error("Only one 'default disable iff' allowed per module" - " (IEEE 1800-2023 16.15)"); - } else { - m_defaultDisablep = nodep; - } VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstInferredDisable* nodep) override { @@ -1475,6 +1476,10 @@ private: nodep->propp(new AstSampled{nodep->fileline(), nodep->propp()->unlinkFrBack(), propDtp->dtypep()}); } + // cover counts non-vacuous matches only (IEEE 1800-2023 16.15.2), so an + // implication antecedent must hold; assert passes vacuously instead. + VL_RESTORER(m_underCover); + m_underCover = VN_IS(nodep->backp(), Cover); iterate(nodep->propp()); } void visit(AstPExpr* nodep) override { @@ -1566,10 +1571,15 @@ private: VL_RESTORER(m_modp); m_defaultClockingp = nullptr; m_defaultClkEvtVarp = nullptr; - m_defaultDisablep = nullptr; + m_defaultDisablep = nodep->defaultDisablep(); m_modp = nodep; iterateChildren(nodep); } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } void visit(AstProperty* nodep) override { // The body will be visited when will be substituted in place of property reference // (AstFuncRef) diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 9fe36df38..8c9d6198e 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1745,6 +1745,156 @@ AstNodeDType* AstNode::getCommonClassTypep(AstNode* node1p, AstNode* node2p) { return nullptr; } +//###################################################################### +// Renders the canonical pattern S-expression for a single AstNode + +class VNPatternString final { + std::ostream& m_os; + + std::map m_internedConsts; // Interned constants + std::map m_internedWordWidths; // Interned widths + std::map m_internedWideWidths; // Interned widths + + // Whether to dump the widhtMin as well + const bool m_dumpWidthMin = v3Global.widthMinUsage() != VWidthMinUsage::MATCHES_WIDTH; + + static std::string toLetters(size_t value, bool lowerCase = false) { + const char base = lowerCase ? 'a' : 'A'; + std::string s; + do { s += static_cast(base + value % 26); } while (value /= 26); + return s; + } + + const std::string& internConst(const AstConst& nodep) { + const auto pair = m_internedConsts.emplace(nodep.num().ascii(false), ""); + if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1); + return pair.first->second; + } + + const std::string& internWordWidth(int value) { + const auto pair = m_internedWordWidths.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true); + return pair.first->second; + } + + const std::string& internWideWidth(int value) { + const auto pair = m_internedWideWidths.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1); + return pair.first->second; + } + + // True if the node has no operands (e.g. a VarRef or Const) + static bool isLeaf(const AstNode* nodep) { + const VNTypeInfo& typeInfo = VNType::typeInfo(nodep->type()); + for (const VNTypeInfo::OpEn& opType : typeInfo.m_opType) { + if (opType != VNTypeInfo::OP_UNUSED) return false; + } + return true; + } + + // Render operand, return true if not unused + void renderOperand(VNTypeInfo::OpEn opType, const AstNode* const opp, uint32_t depth) { + switch (opType) { + case VNTypeInfo::OP_UNUSED: // + break; + case VNTypeInfo::OP_USED: // + m_os << ' '; + render(opp, depth); + break; + case VNTypeInfo::OP_LIST: + m_os << " ["; + for (const AstNode* nodep = opp; nodep; nodep = nodep->nextp()) { + if (nodep != opp) m_os << ", "; + render(opp, depth); + } + m_os << ']'; + break; + case VNTypeInfo::OP_OPTIONAL: + if (opp) { + m_os << " "; + render(opp, depth); + } else { + m_os << " nil"; + } + break; + } + } + + // Render the node into the stream. + void render(const AstNode* nodep, uint32_t depth) { + if (const AstConst* const constp = VN_CAST(nodep, Const)) { + // Base case 1: constant + if (constp->isZero()) { + m_os << "(CONST ZERO)"; + } else if (constp->isEqAllOnes()) { + m_os << "(CONST ONES)"; + } else { + m_os << "(CONST #" << internConst(*constp) << ')'; + } + } else if (isLeaf(nodep)) { + // Base case 2: expression with no operands (e.g. a variable reference) + m_os << '(' << nodep->typeName() << ')'; + } else if (depth == 0) { + // Base case 3: deep expression + m_os << '_'; + } else { + // Recursively print an S-expression for the expression + m_os << '('; + // Name + m_os << nodep->typeName(); + // Operands + const VNTypeInfo& typeInfo = VNType::typeInfo(nodep->type()); + renderOperand(typeInfo.m_opType[0], nodep->op1p(), depth - 1); + renderOperand(typeInfo.m_opType[1], nodep->op2p(), depth - 1); + renderOperand(typeInfo.m_opType[2], nodep->op3p(), depth - 1); + renderOperand(typeInfo.m_opType[3], nodep->op4p(), depth - 1); + // S-expression end + m_os << ')'; + } + + // Annotate type + m_os << ':'; + const AstNodeDType* const dtypep = nodep->dtypep() ? nodep->dtypep()->skipRefp() : nullptr; + if (!dtypep) { + m_os << '?'; + } else if (dtypep->isCompound() || VN_IS(dtypep, UnpackArrayDType)) { + dtypep->dumpSmall(m_os); + } else { + const int width = nodep->width(); + if (width == 1) { + m_os << '1'; + } else if (width <= VL_QUADSIZE) { + m_os << internWordWidth(width); + } else { + m_os << internWideWidth(width); + } + if (m_dumpWidthMin) { + m_os << '/'; + const int widthMin = nodep->widthMin(); + if (widthMin == 1) { + m_os << '1'; + } else if (widthMin <= VL_QUADSIZE) { + m_os << internWordWidth(widthMin); + } else { + m_os << internWideWidth(widthMin); + } + } + } + } + +public: + VNPatternString(std::ostream& os, const AstNode* nodep, uint32_t depth) + : m_os{os} { + render(nodep, depth); + } +}; + +std::string AstNodeExpr::patternString(uint32_t depth) const { + std::ostringstream oss; + VNPatternString{oss, this, depth}; + return oss.str(); +} + //###################################################################### // VNDeleter diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index c12627886..afca1bcc2 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -342,7 +342,6 @@ public: VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT_RD, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT_RW, // V3LinkParse moves to AstVar::sigPublic - VAR_ISOLATE_ASSIGNMENTS, // V3LinkParse moves to AstVar::attrIsolateAssign VAR_SC_BIGUINT, // V3LinkParse moves to AstVar::attrScBigUint VAR_SC_BV, // V3LinkParse moves to AstVar::attrScBv VAR_SFORMAT, // V3LinkParse moves to AstVar::attrSFormat @@ -364,7 +363,7 @@ public: "TYPEID", "TYPENAME", "VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC", "VAR_FSM_STATE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT", - "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_ISOLATE_ASSIGNMENTS", + "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR" }; // clang-format on @@ -1191,6 +1190,19 @@ public: = {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"}; return names[m_e]; } + // VlCovBinKind enumerator naming the bin's set + const char* binSetEnum() const { + switch (m_e) { + case BINS_IGNORE: return "VlCovBinKind::KIND_IGNORE"; + case BINS_ILLEGAL: return "VlCovBinKind::KIND_ILLEGAL"; + case BINS_DEFAULT: return "VlCovBinKind::KIND_DEFAULT"; + default: return "VlCovBinKind::KIND_NORMAL"; + } + } + // Normal bins (feed coverage) are anything but ignore/illegal/default + bool binIsNormal() const { + return m_e != BINS_IGNORE && m_e != BINS_ILLEGAL && m_e != BINS_DEFAULT; + } }; constexpr bool operator==(const VCoverBinsType& lhs, VCoverBinsType::en rhs) { return lhs.m_e == rhs; @@ -1416,6 +1428,7 @@ public: ET_EVENT, // VlEventBase::isFired // Involving an expression ET_TRUE, + ET_INITIAL_NBA, // Event that is fired initially and never again // ET_COMBO, // Sensitive to all combo inputs to this block ET_COMBO_STAR, // Sensitive to all combo inputs to this block (from .*) @@ -1434,6 +1447,7 @@ public: true, // ET_NEGEDGE true, // ET_EVENT true, // ET_TRUE + true, // ET_INITIAL_NBA false, // ET_COMBO false, // ET_COMBO_STAR @@ -1457,14 +1471,14 @@ public: } const char* ascii() const { static const char* const names[] - = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "COMBO", - "COMBO_STAR", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; + = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "ET_INITIAL_NBA", + "COMBO", "COMBO_STAR", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; return names[m_e]; } const char* verilogKwd() const { - static const char* const names[] - = {"[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "*", - "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; + static const char* const names[] = { + "[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "[initial_nba]", + "*", "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; return names[m_e]; } // Return true iff this and the other have mutually exclusive transitions diff --git a/src/V3AstInlines.h b/src/V3AstInlines.h index 8a4477060..6ef16eec6 100644 --- a/src/V3AstInlines.h +++ b/src/V3AstInlines.h @@ -163,6 +163,13 @@ bool AstVar::sameNode(const AstNode* samep) const { return m_name == asamep->m_name && varType() == asamep->varType(); } +AstMatchMasked::AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarScope* matchp) + : ASTGEN_SUPER_MatchMasked(fl) { + this->lhsp(lhsp); + this->matchp(new AstVarRef{fl, matchp, VAccess::READ}); + dtypeSetUInt32(); +} + AstVarRef::AstVarRef(FileLine* fl, AstVar* varp, const VAccess& access) : ASTGEN_SUPER_VarRef(fl, varp, access) { if (v3Global.assertDTypesResolved()) { diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 1253c8527..fcb2183ba 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -170,6 +170,11 @@ public: void generic(bool flag) { m_generic = flag; } std::pair dimensions(bool includeBasic) const; uint32_t arrayUnpackedElements() const; // 1, or total multiplication of all dimensions + // Fixed aggregate streaming properties + bool isStreamableFixedAggregate() const; + bool containsUnpackedStruct() const; + int widthStream() const; + string vlEnumType() const; // Return VerilatedVarType: VLVT_UINT32, etc static int uniqueNumInc() { return ++s_uniqueNum; } const char* charIQWN() const { return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I"); diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 41d980697..85c5b0892 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -73,6 +73,9 @@ public: // Returns an error message if widthMin() is not correct otherwise returns nullptr like // broken() virtual const char* widthMismatch() const VL_MT_STABLE { return nullptr; } + + // S-expression inspired dump of node and operands for debugging + std::string patternString(uint32_t depth = 0) const; }; class AstNodeBiop VL_NOT_FINAL : public AstNodeExpr { // Binary expression @@ -1855,6 +1858,22 @@ public: bool index() const { return m_index; } bool isExprCoverageEligible() const override { return false; } }; +class AstMatchMasked final : public AstNodeExpr { + // This is a non-source construct, created internally to represent + // some case statements. It is a '(mask & _) == bits' matching loop + // where {mask, bits} pairs are packed into a single wide 'matchp', + // and the result is the index of the first matching entry. + // See VL_DECODER_* runtime functions. + // @astgen op1 := lhsp : AstNodeExpr + // @astgen op2 := matchp : AstVarRef +public: + inline AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarScope* matchp); + ASTGEN_MEMBERS_AstMatchMasked; + string emitVerilog() override { V3ERROR_NA_RETURN(""); } + string emitC() override { return "VL_MATCHMASKED_%lq(%lw, %li, %ri)"; } + bool cleanOut() const override { return true; } + static uint32_t fold(const V3Number& lhs, AstVar* matchVarp); +}; class AstMatches final : public AstNodeExpr { // "matches" operator: "expr matches pattern" // @astgen op1 := lhsp : AstNodeExpr // Expression to match @@ -2225,6 +2244,23 @@ public: bool sameNode(const AstNode* /*samep*/) const override { return true; } bool isSystemFunc() const override { return true; } }; +class AstSClocked final : public AstNodeExpr { + // Sequence expression with an explicit leading clocking event + // IEEE 1800-2023 16.7: sequence_expr ::= clocking_event sequence_expr + // The clocking event is hoisted to the enclosing assertion clock by V3AssertNfa. + // @astgen op1 := sensesp : AstSenItem + // @astgen op2 := exprp : AstNodeExpr +public: + AstSClocked(FileLine* fl, AstSenItem* sensesp, AstNodeExpr* exprp) + : ASTGEN_SUPER_SClocked(fl) { + this->sensesp(sensesp); + this->exprp(exprp); + } + ASTGEN_MEMBERS_AstSClocked; + string emitVerilog() override { V3ERROR_NA_RETURN(""); } + string emitC() override { V3ERROR_NA_RETURN(""); } + bool cleanOut() const override { V3ERROR_NA_RETURN(false); } +}; class AstSConsRep final : public AstNodeExpr { // Consecutive repetition [*N], [*N:M], [+], [*] (IEEE 1800-2023 16.9.2) // op1 := exprp -- the repeated expression diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index f3692d05e..19f4d07ee 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -97,7 +97,6 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode { string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8) uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees bool m_taskPublic : 1; // Public task - bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_classMethod : 1; // Class method bool m_didProto : 1; // Did prototype processing bool m_prototype : 1; // Just a prototype @@ -130,7 +129,6 @@ protected: : AstNode{t, fl} , m_name{name} , m_taskPublic{false} - , m_attrIsolateAssign{false} , m_classMethod{false} , m_didProto{false} , m_prototype{false} @@ -179,8 +177,6 @@ public: uint64_t dpiOpenParent() const { return m_dpiOpenParent; } bool taskPublic() const { return m_taskPublic; } void taskPublic(bool flag) { m_taskPublic = flag; } - bool attrIsolateAssign() const { return m_attrIsolateAssign; } - void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } bool classMethod() const { return m_classMethod; } void classMethod(bool flag) { m_classMethod = flag; } bool didProto() const { return m_didProto; } @@ -301,6 +297,7 @@ class AstNodeModule VL_NOT_FINAL : public AstNode { VLifetime m_lifetime; // Lifetime VTimescale m_timeunit; // Global time unit VOptionBool m_unconnectedDrive; // State of `unconnected_drive + AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope bool m_modPublic : 1; // Module has public references bool m_modTrace : 1; // Tracing this module @@ -351,6 +348,8 @@ public: string origName() const override { return m_origName; } string someInstanceName() const VL_MT_SAFE { return m_someInstanceName; } void someInstanceName(const string& name) { m_someInstanceName = name; } + AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; } + void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; } bool inLibrary() const { return m_inLibrary; } void inLibrary(bool flag) { m_inLibrary = flag; } void depth(int value) { m_depth = value; } @@ -1001,6 +1000,8 @@ public: // this matters, the caller must handle the dtype difference as appropriate. If 'mergeDType' is // false, the returned VarScope will have _->dtypep()->sameTree(initp->dtypep()) return true. AstVarScope* findConst(AstConst* initp, bool mergeDType); + // Rebuild hashes after potential removals + void reCache(); }; class AstConstraint final : public AstNode { // Constraint @@ -1752,6 +1753,7 @@ public: class Combo {}; // for constructor type-overload selection class Static {}; // for constructor type-overload selection class Initial {}; // for constructor type-overload selection + class InitialNBA {}; // for constructor type-overload selection class Final {}; // for constructor type-overload selection class Never {}; // for constructor type-overload selection AstSenItem(FileLine* fl, VEdgeType edgeType, AstNodeExpr* senp, AstNodeExpr* condp = nullptr) @@ -1769,6 +1771,9 @@ public: AstSenItem(FileLine* fl, Initial) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_INITIAL} {} + AstSenItem(FileLine* fl, InitialNBA) + : ASTGEN_SUPER_SenItem(fl) + , m_edgeType{VEdgeType::ET_INITIAL_NBA} {} AstSenItem(FileLine* fl, Final) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_FINAL} {} @@ -2131,15 +2136,16 @@ class AstVar final : public AstNode { bool m_funcReturn : 1; // Return variable for a function bool m_attrScBv : 1; // User force bit vector attribute bool m_attrScBigUint : 1; // User force sc_biguint attribute - bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_attrSFormat : 1; // User sformat attribute bool m_attrSplitVar : 1; // declared with split_var metacomment bool m_attrFsmState : 1; // declared with fsm_state metacomment bool m_attrFsmRegisterWrapper : 1; // connected to an fsm_register_wrapper instance bool m_attrFsmResetArc : 1; // declared with fsm_reset_arc metacomment bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment + bool m_constPoolEntry : 1; // Constant pool variable bool m_fileDescr : 1; // File descriptor bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration + bool m_icoMaybeWritten : 1; // Design might write this input signal - for ico change detect bool m_isConst : 1; // Table contains constant data bool m_isContinuously : 1; // Ever assigned continuously (for force/release) bool m_hasStrengthAssignment : 1; // Is on LHS of assignment with strength specifier @@ -2194,15 +2200,16 @@ class AstVar final : public AstNode { m_funcReturn = false; m_attrScBv = false; m_attrScBigUint = false; - m_attrIsolateAssign = false; m_attrSFormat = false; m_attrSplitVar = false; m_attrFsmState = false; m_attrFsmRegisterWrapper = false; m_attrFsmResetArc = false; m_attrFsmArcInclCond = false; + m_constPoolEntry = false; m_fileDescr = false; m_gotNansiType = false; + m_icoMaybeWritten = false; m_isConst = false; m_isContinuously = false; m_hasStrengthAssignment = false; @@ -2343,13 +2350,14 @@ public: void attrFileDescr(bool flag) { m_fileDescr = flag; } void attrScBv(bool flag) { m_attrScBv = flag; } void attrScBigUint(bool flag) { m_attrScBigUint = flag; } - void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } void attrSFormat(bool flag) { m_attrSFormat = flag; } void attrSplitVar(bool flag) { m_attrSplitVar = flag; } void attrFsmState(bool flag) { m_attrFsmState = flag; } void attrFsmRegisterWrapper(bool flag) { m_attrFsmRegisterWrapper = flag; } void attrFsmResetArc(bool flag) { m_attrFsmResetArc = flag; } void attrFsmArcInclCond(bool flag) { m_attrFsmArcInclCond = flag; } + bool constPoolEntry() const { return m_constPoolEntry; } + void setConstPoolEntry() { m_constPoolEntry = true; } void rand(const VRandAttr flag) { m_rand = flag; } void usedParam(bool flag) { m_usedParam = flag; } void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; } @@ -2383,6 +2391,8 @@ public: void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; } bool hasUserInit() const { return m_hasUserInit; } void hasUserInit(bool flag) { m_hasUserInit = flag; } + void icoMaybeWritten(bool flag) { m_icoMaybeWritten = flag; } + bool icoMaybeWritten() const { return m_icoMaybeWritten; } bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; } void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; } bool isHideLocal() const { return m_isHideLocal; } @@ -2462,12 +2472,6 @@ public: bool isWor() const { return varType().isWor(); } bool isWiredNet() const { return varType().isWiredNet(); } bool isTemp() const { return varType().isTemp(); } - bool isToggleCoverable() const { - return ((isIO() || isSignal()) - && (isIO() || isBitLogic()) - // Wrapper would otherwise duplicate wrapped module's coverage - && !isSc() && !isPrimaryIO() && !isConst() && !isDouble() && !isString()); - } bool isClassMember() const { return varType() == VVarType::MEMBER; } bool isVirtIface() const { if (AstIfaceRefDType* const dtp = VN_CAST(dtypep(), IfaceRefDType)) { @@ -2519,7 +2523,6 @@ public: bool attrFsmRegisterWrapper() const { return m_attrFsmRegisterWrapper; } bool attrFsmResetArc() const { return m_attrFsmResetArc; } bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; } - bool attrIsolateAssign() const { return m_attrIsolateAssign; } AstIface* sensIfacep() const { return m_sensIfacep; } VRandAttr rand() const { return m_rand; } string verilogKwd() const override; @@ -2531,7 +2534,6 @@ public: // This is getting connected to fromp; keep attributes // Note the method below too if (fromp->attrFileDescr()) attrFileDescr(true); - if (fromp->attrIsolateAssign()) attrIsolateAssign(true); if (fromp->isContinuously()) isContinuously(true); } void propagateWrapAttrFrom(const AstVar* fromp) { @@ -2810,6 +2812,7 @@ class AstGenBlock final : public AstNodeGen { std::string m_name; // Name of block const bool m_unnamed; // Originally unnamed (name change does not affect this) const bool m_implied; // Not inserted by user + AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope public: AstGenBlock(FileLine* fl, const string& name, AstNode* itemsp, bool implied) @@ -2826,6 +2829,8 @@ public: void name(const std::string& name) override { m_name = name; } bool unnamed() const { return m_unnamed; } bool implied() const { return m_implied; } + AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; } + void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; } }; class AstGenCase final : public AstNodeGen { // Generate 'case' diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 336da7bae..25de3ad08 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -59,6 +59,7 @@ protected: public: ASTGEN_MEMBERS_AstNodeAssign; // Clone single node, just get same type back. + void dump(std::ostream& str) const override; virtual AstNodeAssign* cloneType(AstNodeExpr* lhsp, AstNodeExpr* rhsp) = 0; bool hasDType() const override VL_MT_SAFE { return true; } virtual bool cleanRhs() const { return true; } @@ -941,13 +942,16 @@ public: class AstPExprClause final : public AstNodeStmt { const bool m_pass; // True if will be replaced by passing assertion clause, false for // assertion failure clause + const bool m_vacuous; // True if pass is vacuous public: ASTGEN_MEMBERS_AstPExprClause; - explicit AstPExprClause(FileLine* fl, bool pass = true) + explicit AstPExprClause(FileLine* fl, bool pass = true, bool vacuous = false) : ASTGEN_SUPER_PExprClause(fl) - , m_pass{pass} {} + , m_pass{pass} + , m_vacuous{vacuous} {} bool pass() const { return m_pass; } + bool vacuous() const { return m_vacuous; } }; class AstPrintTimeScale final : public AstNodeStmt { // Parents: stmtlist @@ -1606,12 +1610,18 @@ public: }; class AstCover final : public AstNodeCoverOrAssert { // @astgen op3 := coverincsp: List[AstNode] // Coverage node + bool m_isCoverSeq = false; // 'cover sequence' (IEEE 1800-2023 16.14.3): fires per + // end-of-match, not per property success public: ASTGEN_MEMBERS_AstCover; AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type, const string& name = "") : ASTGEN_SUPER_Cover(fl, propp, stmtsp, type, VAssertDirectiveType::COVER, name) {} string verilogKwd() const override { return "cover"; } + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + bool isCoverSeq() const { return m_isCoverSeq; } + void isCoverSeq(bool flag) { m_isCoverSeq = flag; } }; class AstRestrict final : public AstNodeCoverOrAssert { public: diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 3bd3f66f6..ec26f24a9 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -546,6 +546,26 @@ AstConst* AstConst::parseParamLiteral(FileLine* fl, const string& literal) { string AstConstraintRef::name() const { return constrp()->name(); } +uint32_t AstMatchMasked::fold(const V3Number& lhs, AstVar* matchVarp) { + const V3Number& numTable = VN_AS(matchVarp->valuep(), Const)->num(); + V3Number numMask{matchVarp, lhs.width(), 0}; + V3Number numBits{matchVarp, lhs.width(), 0}; + V3Number numAnd{matchVarp, lhs.width(), 0}; + const int width = lhs.width(); + const int entryWidth = VL_WORDS_I(width) * VL_EDATASIZE; + uint32_t i = 0; + while (true) { + const int lsb = 2 * i * entryWidth; + const int msb = lsb + width - 1; + numMask.opSel(numTable, msb, lsb); + numBits.opSel(numTable, msb + entryWidth, lsb + entryWidth); + numAnd.opAnd(numMask, lhs); + if (numAnd.isCaseEq(numBits)) break; + ++i; + } + return i; +} + AstNetlist::AstNetlist() : ASTGEN_SUPER_Netlist(new FileLine{FileLine::builtInFilename()}) , m_typeTablep{new AstTypeTable{fileline()}} @@ -630,6 +650,7 @@ void AstVar::combineType(const AstVar* otherp) { varType(otherp->varType()); direction(otherp->direction()); } + if (otherp->icoMaybeWritten()) icoMaybeWritten(true); } void AstVar::combineType(VVarType type) { // These flags get combined with the existing settings of the flags. @@ -682,9 +703,14 @@ string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string& return ostatic + dtypep()->cType(oname, forFunc, asRef); } -string AstVar::vlEnumType() const { +string AstNodeDType::vlEnumType() const { string arg; - const AstBasicDType* const bdtypep = basicp(); + const AstNodeDType* dtypep = skipRefp(); + while (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + dtypep = adtypep->subDTypep()->skipRefp(); + } + const AstBasicDType* const bdtypep = dtypep->basicp(); + const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType); const bool strtype = bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING; if (bdtypep && bdtypep->keyword() == VBasicDTypeKwd::CHARPTR) { return "VLVT_PTR"; @@ -694,6 +720,8 @@ string AstVar::vlEnumType() const { arg += "VLVT_STRING"; } else if (isDouble()) { arg += "VLVT_REAL"; + } else if (sdtypep && !sdtypep->packed()) { + arg += VN_IS(sdtypep, StructDType) ? "VLVT_STRUCT" : "VLVT_UNION"; } else if (widthMin() <= 8) { arg += "VLVT_UINT8"; } else if (widthMin() <= 16) { @@ -709,6 +737,8 @@ string AstVar::vlEnumType() const { return arg; } +string AstVar::vlEnumType() const { return dtypep()->vlEnumType(); } + string AstVar::vlEnumDir() const { string out; if (isInout()) { @@ -738,6 +768,7 @@ string AstVar::vlEnumDir() const { if (AstBasicDType* const basicp = dtypep()->skipRefp()->basicp()) { if (basicp->keyword() == VBasicDTypeKwd::BIT) out += "|VLVF_BITVAR"; } + if (isNet()) out += "|VLVF_NET"; return out; } @@ -1259,6 +1290,47 @@ uint32_t AstNodeDType::arrayUnpackedElements() const { return entries; } +bool AstNodeDType::isStreamableFixedAggregate() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->isStreamableFixedAggregate(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) return true; + if (!VN_IS(sdtypep, StructDType)) return false; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + if (!itemp->dtypep()->isStreamableFixedAggregate()) return false; + } + return true; + } + return dtypep->isIntegralOrPacked() || dtypep->isDouble(); +} + +bool AstNodeDType::containsUnpackedStruct() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->containsUnpackedStruct(); + } + const AstStructDType* const sdtypep = VN_CAST(dtypep, StructDType); + return sdtypep && !sdtypep->packed(); +} + +int AstNodeDType::widthStream() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->widthStream() * adtypep->elementsConst(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (!VN_IS(sdtypep, StructDType) || sdtypep->packed()) return width(); + int width = 0; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + width += itemp->dtypep()->widthStream(); + } + return width; + } + return dtypep->width(); +} + std::pair AstNodeDType::dimensions(bool includeBasic) const { // How many array dimensions (packed,unpacked) does this Var have? uint32_t packed = 0; @@ -1322,6 +1394,12 @@ AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) { } else if (VN_IS(nodep, Sel)) { nodep = VN_AS(nodep, Sel)->fromp(); continue; + } else if (VN_IS(nodep, AssocSel)) { + nodep = VN_AS(nodep, AssocSel)->fromp(); + continue; + } else if (VN_IS(nodep, WildcardSel)) { + nodep = VN_AS(nodep, WildcardSel)->fromp(); + continue; } else if (overMembers && VN_IS(nodep, MemberSel)) { nodep = VN_AS(nodep, MemberSel)->fromp(); continue; @@ -1581,6 +1659,7 @@ AstConstPool::AstConstPool(FileLine* fl) AstVarScope* AstConstPool::createNewEntry(const string& name, AstNodeExpr* initp) { FileLine* const fl = initp->fileline(); AstVar* const varp = new AstVar{fl, VVarType::MODULETEMP, name, initp->dtypep()}; + varp->setConstPoolEntry(); varp->isConst(true); varp->isStatic(true); varp->valuep(initp->cloneTree(false)); @@ -1700,6 +1779,17 @@ AstVarScope* AstConstPool::findConst(AstConst* initp, bool mergeDType) { return varScopep; } +void AstConstPool::reCache() { + m_tables.clear(); + m_consts.clear(); + for (AstVarScope* vscp = m_scopep->varsp(); vscp; vscp = VN_CAST(vscp->nextp(), VarScope)) { + AstNode* const valuep = vscp->varp()->valuep(); + const V3Hash hash = V3Hasher::uncachedHash(valuep); + if (VN_IS(valuep, InitArray)) m_tables.emplace(hash.value(), vscp); + if (VN_IS(valuep, Const)) m_consts.emplace(hash.value(), vscp); + } +} + //====================================================================== // Per-type Debugging @@ -2133,6 +2223,14 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, immediate); dumpJsonBoolFuncIf(str, senFromAlways); } +void AstCover::dump(std::ostream& str) const { + this->AstNodeCoverOrAssert::dump(str); + if (isCoverSeq()) str << " [COVERSEQ]"; +} +void AstCover::dumpJson(std::ostream& str) const { + dumpJsonBoolFuncIf(str, isCoverSeq); + this->AstNodeCoverOrAssert::dumpJson(str); +} void AstClocking::dump(std::ostream& str) const { this->AstNode::dump(str); if (isDefault()) str << " [DEFAULT]"; @@ -2724,6 +2822,10 @@ void AstNodeArrayDType::dumpJson(std::ostream& str) const { dumpJsonStr(str, "declRange", cvtToStr(declRange())); dumpJsonGen(str); } +void AstNodeAssign::dump(std::ostream& str) const { + this->AstNode::dump(str); + if (timingControlp()) str << " [TIMING=" << nodeAddr(timingControlp()) << "]"; +} string AstPackArrayDType::prettyDTypeName(bool full) const { std::ostringstream os; if (const auto subp = subDTypep()) os << subp->prettyDTypeName(full); @@ -3138,6 +3240,7 @@ int AstVarRef::instrCount() const { } void AstVar::dump(std::ostream& str) const { this->AstNode::dump(str); + if (constPoolEntry()) str << " [CONSTPOOL]"; if (isSc()) str << " [SC]"; if (isPrimaryIO()) str << (isInout() ? " [PIO]" : (isWritable() ? " [PO]" : " [PI]")); if (isPrimaryClock()) str << " [PCLK]"; @@ -3158,7 +3261,6 @@ void AstVar::dump(std::ostream& str) const { if (noReset()) str << " [!RST]"; if (processQueue()) str << " [PROCQ]"; if (sampled()) str << " [SAMPLED]"; - if (attrIsolateAssign()) str << " [aISO]"; if (attrFsmState()) str << " [aFSMSTATE]"; if (attrFsmResetArc()) str << " [aFSMRESETARC]"; if (attrFsmArcInclCond()) str << " [aFSMARCCOND]"; @@ -3169,6 +3271,7 @@ void AstVar::dump(std::ostream& str) const { str << " [FUNC]"; } if (hasUserInit()) str << " [UINIT]"; + if (icoMaybeWritten()) str << " [ICOMAYBEWRITTEN]"; if (isDpiOpenArray()) str << " [DPIOPENA]"; if (ignorePostWrite()) str << " [IGNPWR]"; if (ignoreSchedWrite()) str << " [IGNWR]"; @@ -3179,6 +3282,7 @@ void AstVar::dump(std::ostream& str) const { void AstVar::dumpJson(std::ostream& str) const { dumpJsonStrFunc(str, origName); dumpJsonStrFunc(str, verilogName); + dumpJsonBoolFuncIf(str, constPoolEntry); dumpJsonBoolFuncIf(str, isSc); dumpJsonBoolFuncIf(str, isPrimaryIO); dumpJsonBoolFuncIf(str, isPrimaryClock); @@ -3193,11 +3297,11 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, noReset); dumpJsonBoolFuncIf(str, processQueue); dumpJsonBoolFuncIf(str, sampled); - dumpJsonBoolFuncIf(str, attrIsolateAssign); dumpJsonBoolFuncIf(str, attrFsmState); dumpJsonBoolFuncIf(str, attrFsmResetArc); dumpJsonBoolFuncIf(str, attrFsmArcInclCond); dumpJsonBoolFuncIf(str, attrFileDescr); + dumpJsonBoolFuncIf(str, icoMaybeWritten); dumpJsonBoolFuncIf(str, isDpiOpenArray); dumpJsonBoolFuncIf(str, isFuncReturn); dumpJsonBoolFuncIf(str, isFuncLocal); diff --git a/src/V3SplitAs.h b/src/V3AstPatterns.h similarity index 67% rename from src/V3SplitAs.h rename to src/V3AstPatterns.h index dc5c4cce6..451a518d4 100644 --- a/src/V3SplitAs.h +++ b/src/V3AstPatterns.h @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: Break always into separate statements to reduce temps +// DESCRIPTION: Verilator: Dump AstNodeExpr patterns for analysis // // Code available from: https://verilator.org // @@ -14,8 +14,8 @@ // //************************************************************************* -#ifndef VERILATOR_V3SPLITAS_H_ -#define VERILATOR_V3SPLITAS_H_ +#ifndef VERILATOR_V3ASTPATTERNS_H_ +#define VERILATOR_V3ASTPATTERNS_H_ #include "config_build.h" #include "verilatedos.h" @@ -24,9 +24,11 @@ class AstNetlist; //============================================================================ -class V3SplitAs final { +class V3AstPatterns final { public: - static void splitAsAll(AstNetlist* nodep) VL_MT_DISABLED; + // Accumulate the patterns in the netlist, and dump the collected + // statistics to '__ast_patterns_.txt'. + static void dumpAll(const AstNetlist* nodep, const std::string& suffix) VL_MT_DISABLED; }; #endif // Guard diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 36e151053..2ac71c770 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -61,7 +61,6 @@ class BeginVisitor final : public VNVisitor { // NODE STATE // AstCase::user1 -> bool, if already purified - V3UniqueNames m_caseTempNames; // For generating unique temporary variable names used by cases // STATE - across all visitors BeginState* const m_statep; // Current global state @@ -75,7 +74,6 @@ class BeginVisitor final : public VNVisitor { string m_unnamedScope; // Name of begin blocks, including unnamed blocks int m_ifDepth = 0; // Current if depth bool m_keepBegins = false; // True if begins should not be inlined - VDouble0 m_statPurifiedCaseExpr; // Count of purified case expressions // METHODS @@ -132,7 +130,6 @@ class BeginVisitor final : public VNVisitor { } void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); - VL_RESTORER(m_caseTempNames); m_modp = nodep; // Rename it (e.g. class under a generate) if (m_unnamedScope != "") { @@ -171,7 +168,6 @@ class BeginVisitor final : public VNVisitor { VL_RESTORER(m_liftedp); VL_RESTORER(m_namedScope); VL_RESTORER(m_unnamedScope); - VL_RESTORER(m_caseTempNames); m_displayScope = dot(m_displayScope, nodep->name()); m_namedScope = ""; m_unnamedScope = ""; @@ -415,29 +411,7 @@ class BeginVisitor final : public VNVisitor { // any BEGINs, but V3Coverage adds them all under the module itself. iterateChildren(nodep); } - void visit(AstCase* nodep) override { - // Introduce temporary variable for AstCase if needed - it is done here and not in V3Case - // because this phase is before V3Scope and V3Case is not. Doing it before V3Scope ensures - // that V3Scope will take care of a scope creation - if (!nodep->exprp()->isPure() && !nodep->user1SetOnce()) { - ++m_statPurifiedCaseExpr; - FileLine* const fl = nodep->exprp()->fileline(); - AstVar* const varp = new AstVar{fl, VVarType::XTEMP, m_caseTempNames.get(nodep), - nodep->exprp()->dtypep()}; - nodep->exprp(new AstExprStmt{fl, - new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, - nodep->exprp()->unlinkFrBack()}, - new AstVarRef{fl, varp, VAccess::READ}}); - if (m_ftaskp) { - varp->funcLocal(true); - varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_ftaskp->stmtsp()->addHereThisAsNext(varp); - } else { - m_modp->stmtsp()->addHereThisAsNext(varp); - } - } - iterateChildren(nodep); - } + void visit(AstCase* nodep) override { iterateChildren(nodep); } // VISITORS - LINT CHECK void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered VL_RESTORER(m_keepBegins); @@ -464,10 +438,8 @@ class BeginVisitor final : public VNVisitor { public: // CONSTRUCTORS BeginVisitor(AstNetlist* nodep, BeginState* statep) - : m_caseTempNames{"__VCase"} - , m_statep{statep} { + : m_statep{statep} { iterate(nodep); - V3Stats::addStatSum("Impure case expressions", m_statPurifiedCaseExpr); } ~BeginVisitor() override = default; }; diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 4eca73e01..146f81a89 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -42,10 +42,6 @@ VL_DEFINE_DEBUG_FUNCTIONS; -#define CASE_OVERLAP_WIDTH 16 // Maximum width we can check for overlaps in -#define CASE_BARF 999999 // Magic width when non-constant -#define CASE_ENCODER_GROUP_DEPTH 8 // Levels of priority to be ORed together in top IF tree - //###################################################################### class CaseLintVisitor final : public VNVisitorConst { @@ -119,252 +115,782 @@ class CaseLintVisitor final : public VNVisitorConst { } void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } -public: // CONSTRUCTORS explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); } explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); } ~CaseLintVisitor() override = default; + +public: + static void apply(AstCase* nodep) { CaseLintVisitor{nodep}; } + static void apply(AstGenCase* nodep) { CaseLintVisitor{nodep}; } }; //###################################################################### // Case state, as a visitor of each AstNode class CaseVisitor final : public VNVisitor { - // NODE STATE - // Cleared each Case - // AstIf::user3() -> bool. Set true to indicate clone not needed - const VNUser3InUse m_inuser3; + // Maximum width for detailed analysis + constexpr static int CASE_DETAILS_MAX_WIDTH = 16; + // Levels of priority to be ORed together in top IF tree + constexpr static int CASE_ENCODER_GROUP_DEPTH = 8; + // Maximum size for tiny lookup tables - materialized in code + constexpr static size_t CASE_TABLE_TINY_BITS = 32; // Up to 2 instructions to materialize + // Maximum size for normal lookup tables - stored in constant pool + constexpr static size_t CASE_TABLE_MAX_BITS = 1ULL << 16; // 64Kbits / 8KBytes + // Minimum number of the branches a table must replace to be worth a load + constexpr static size_t CASE_TABLE_MIN_BRANCHES = 3; + + // TYPES + // Record for each case value + struct CaseRecord final { + AstCaseItem* itemp; // Case item that covers value + AstConst* constp; // Expression within 'itemp' that covers value (nullptr for default) + AstNode* stmtsp; // Statements of 'itemp' (might be nullptr if case is empty) + }; + + // Record for each LHS of a decoder pattern + struct LhsRecord final { + AstNodeExpr* lhsp = nullptr; // LHS of the assignment + AstNodeAssign* preDefaultp = nullptr; // Default assignment *before the case statement* + size_t nCaseAssigns = 0; // Number of AstAssigns to this LHS in case clauses + size_t nCaseAssignDlys = 0; // Number of AstAssignDlys to this LHS in case clauses + size_t offset = 0; // Offset in the table for this LHS + + static size_t s_nextId; // Static unique Id counter + size_t id = ++s_nextId; // Unique Id for sorting + }; + + // NODE STATE: + // AstVarScope::user1() -> bool: true if written to, only in parts of analysis phase // STATE - VDouble0 m_statCaseFast; // Statistic tracking - VDouble0 m_statCaseSlow; // Statistic tracking + // Statistics tracking, as a struct so can be passed to 'const' methods + struct Stats final { + VDouble0 caseDecoder; // Cases using decoder method + VDouble0 caseTableNormal; // Cases using table method with normal table + VDouble0 caseTableTiny; // Cases using table method with tiny table + VDouble0 caseFast; // Cases using fast bit tree method + VDouble0 caseGeneric; // Cases using generic if/else tree method + VDouble0 provenAssertions; // Assertions proven to hold + } m_stats; const AstNode* m_alwaysp = nullptr; // Always in which case is located + size_t m_nTmps = 0; // Sequence numbers for temporary variables + AstScope* m_scopep = nullptr; // Current scope - // Per-CASE - int m_caseWidth = 0; // Width of valueItems - int m_caseItems = 0; // Number of caseItem unique values - bool m_caseIncomplete = false; // Proven incomplete - bool m_caseNoOverlapsAllCovered = false; // Proven to be synopsys parallel_case compliant - // For each possible value, the case branch we need - std::array m_valueItem; - bool m_needToClearCache = false; // Whether cache needs to be cleared + // STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise + bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions) + bool m_caseHasDefault = false; // Indicates the case statement has a default case + size_t m_caseNCaseItems = 0; // Number of AstCaseItems in the case statement + size_t m_caseNConditions = 0; // Number of conditions in the case statement + // Map from LHSs of decoder pattern to corresponding LhsRecord. + std::unordered_map, LhsRecord> m_caseLhsRecords; + // Values of 'm_caseLhsRecords' in sorted order, if case statement is a decoder pattern + std::vector m_caseDecoderRecords; + size_t m_caseDecoderEntryWidth = 0; // Width of each entry in the decoder table + size_t m_caseTableWidth = 0; // Total width of the case table - 0 means can't optimize + bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid + struct final { + bool exhaustive = false; // Proven exhaustive + bool exhaustiveOverEnumOnly = false; // Exhaustive over enum values only + bool noOverlaps = false; // Proven no overlaps between cases + // Map from value (index) to the CaseRecord that covers this value + std::array records; + } m_caseDetails; // METHODS - //! Determine whether we should check case items are complete - //! @return Enum's dtype if should check, nullptr if shouldn't - const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { + + // Xs in case or casez are impossible due to two state simulations. + // Returns true if the item is never possible + static bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { + const AstConst* const constp = VN_CAST(itemExprp, Const); + if (!constp) return false; + if (casep->casex() || casep->caseInside()) return false; + if (casep->casez()) return constp->num().isAnyX(); + return constp->num().isFourState(); + } + + // Returns the matching mask and bits for a case item constant condition + // TODO: with 'neverItem' checked, this is alway the same for all types of cases + // 4-state will have to fix this up + static std::pair matchPattern(const AstCase* /*casep*/, + const AstConst* condp) { + // As one to encourage NRVO/move + std::pair pairMaskBits{ + std::piecewise_construct, // + std::forward_as_tuple(condp->fileline(), condp->width(), 0), + std::forward_as_tuple(condp->fileline(), condp->width(), 0)}; + pairMaskBits.first.opBitsNonXZ(condp->num()); + pairMaskBits.second.opBitsOne(condp->num()); + return pairMaskBits; + } + + // If the given statement is an assignment that fits the decoder pattern, + // return it, otherwise return nullptr + static AstNodeAssign* checkDecoderAssign(AstNode* stmtp) { + // Only Assign and AssignDly are supported + if (!VN_IS(stmtp, Assign) && !VN_IS(stmtp, AssignDly)) return nullptr; + AstNodeAssign* const assp = VN_AS(stmtp, NodeAssign); + // Only if no timing control + if (assp->timingControlp()) return nullptr; + // Only if assigning a constant + if (!VN_IS(assp->rhsp(), Const)) return nullptr; + // Only if it's a packed value + AstNodeDType* const dtypep = assp->rhsp()->dtypep(); + if (dtypep->isString() || dtypep->isDouble()) return nullptr; + // Only if the LHS has no reads (can be relaxed, but need to prove there is no r/w hazard) + if (assp->lhsp()->exists([](AstVarRef* refp) { return refp->access().isReadOrRW(); })) { + return nullptr; + } + // This is an assignment that fits the decoder pattern + return assp; + } + + // Analyze if the given case item fits the decoder pattern, return true iff so. + // Updates 'm_caseLhsRecords'. + bool analyzeDecoderCaseItem(AstCaseItem* cip) { + // AstVarScope::user1() -> bool: true if written to + const VNUser1InUse user1InUse; + for (AstNode* stmtp = cip->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + // Must be an assignment that fits the decoder pattern + AstNodeAssign* const assp = checkDecoderAssign(stmtp); + if (!assp) return false; + // Must assign each LHS exactly once - RHS is Const + const bool multipleAssignments = assp->lhsp()->exists([](AstVarRef* refp) { // + return refp->varScopep()->user1SetOnce(); + }); + if (multipleAssignments) return false; + // Update LhsRecord + LhsRecord& lhsRecord = m_caseLhsRecords[*assp->lhsp()]; + if (!lhsRecord.lhsp) lhsRecord.lhsp = assp->lhsp(); + lhsRecord.nCaseAssigns += VN_IS(assp, Assign); + lhsRecord.nCaseAssignDlys += VN_IS(assp, AssignDly); + } + return true; + } + + // Determine whether we should check case items are complete + // Returns enum's dtype if should check, nullptr if shouldn't + static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr; const AstEnumDType* const enumDtp = VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType); if (!enumDtp) return nullptr; // Case isn't enum const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp(); if (!basicp) return nullptr; // Not simple type (perhaps IEEE illegal) - if (basicp->width() > 32) return nullptr; return enumDtp; } - //! @return True if case items are complete, false if there are uncovered enums - bool checkCaseEnumComplete(const AstCase* const nodep, const AstEnumDType* const dtype) { - const uint32_t numCases = 1UL << m_caseWidth; - for (AstEnumItem* itemp = dtype->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), EnumItem)) { - AstConst* const econstp = VN_AS(itemp->valuep(), Const); - V3Number nummask{itemp, econstp->width()}; - nummask.opBitsNonX(econstp->num()); - const uint32_t mask = nummask.toUInt(); - V3Number numval{itemp, econstp->width()}; - numval.opBitsOne(econstp->num()); - const uint32_t val = numval.toUInt(); + // Check and warn if case items are not complete over the given enum type. + // Returns true iff the case items cover all enum values/patterns. + bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) { + const uint32_t numCases = 1UL << nodep->exprp()->width(); + bool fullyCovered = true; + string missingItems; + for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) { + const auto& match = matchPattern(nodep, VN_AS(eip->valuep(), Const)); + const uint32_t mask = match.first.toUInt(); + const uint32_t bits = match.second.toUInt(); + + // Check all cases to see if they cover this enum value/pattern for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) == val) { - if (!m_valueItem[i]) { - if (!nodep->unique0Pragma()) - nodep->v3warn(CASEINCOMPLETE, "Enum item " - << itemp->prettyNameQ() - << " not covered by case\n"); - m_caseIncomplete = true; - return false; // enum has uncovered value by case items - } - } + if ((i & mask) != bits) continue; // This case is not for this enum value + if (m_caseDetails.records[i].itemp) continue; // Covered case + if (!missingItems.empty()) missingItems += ", "; + missingItems += eip->prettyNameQ(); + fullyCovered = false; + break; } } - return true; // enum is fully covered + if (!missingItems.empty() && !nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, "Enum item(s) not covered by case: " << missingItems); + } + return fullyCovered; // enum is fully covered } - bool isCaseTreeFast(AstCase* nodep) { - int width = 0; - bool opaque = false; - m_caseItems = 0; - m_caseNoOverlapsAllCovered = true; - for (AstCaseItem* itemp = nodep->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), CaseItem)) { - for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { - if (icondp->width() > width) width = icondp->width(); - if (icondp->isDouble()) opaque = true; - if (!VN_IS(icondp, Const)) width = CASE_BARF; // Can't parse; not a constant - m_caseItems++; + + // Check and warn if case items are not complete over all possible values. + // Returns true iff the case items cover all values of the case expression. + bool checkExhaustivePacked(AstCase* nodep) { + const uint32_t numCases = 1UL << nodep->exprp()->width(); + for (uint32_t i = 0; i < numCases; ++i) { + if (m_caseDetails.records[i].itemp) continue; // Covered case + if (!nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, + "Case values incompletely covered (example pattern 0x" << std::hex + << i << ")"); } + // TODO: warn for more than one uncovered case, not just the first + return false; } - m_caseWidth = width; - if (width == 0 || width > CASE_OVERLAP_WIDTH || opaque) { - m_caseNoOverlapsAllCovered = false; - return false; // Too wide for analysis + + // It's an exhaustive case statement + return true; + } + + // Analyze each value in the case statement. Updates 'm_caseDetails' and issues warnings. + void analyzeCaseDetails(AstCase* nodep) { + const uint32_t numValues = 1UL << nodep->exprp()->width(); + // Clear case records + for (uint32_t i = 0; i < numValues; ++i) { + m_caseDetails.records[i].itemp = nullptr; + m_caseDetails.records[i].constp = nullptr; + m_caseDetails.records[i].stmtsp = nullptr; } - UINFO(8, "Simple case statement: " << nodep); - const uint32_t numCases = 1UL << m_caseWidth; - // Zero list of items for each value - for (uint32_t i = 0; i < numCases; ++i) m_valueItem[i] = nullptr; // Now pick up the values for each assignment // We can cheat and use uint32_t's because we only support narrow case's bool reportedOverlap = false; - bool reportedSubcase = false; - bool hasDefaultCase = false; - std::map caseItemMap; // case condition -> case item + bool hasDefault = false; + m_caseDetails.noOverlaps = true; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { - // UINFOTREE(9, icondp, "", "caseitem"); - AstConst* const iconstp = VN_AS(icondp, Const); - UASSERT_OBJ(iconstp, nodep, "above 'can't parse' should have caught this"); - if (neverItem(nodep, iconstp)) { - // X in casez can't ever be executed + + // Default case + if (itemp->isDefault()) { + // Default was moved to be the last item by V3LinkDot. Fill remaining cases + for (uint32_t i = 0; i < numValues; ++i) { + CaseRecord& caseRecord = m_caseDetails.records[i]; + if (!caseRecord.itemp) { + caseRecord.itemp = itemp; + caseRecord.stmtsp = itemp->stmtsp(); + } + } + hasDefault = true; + continue; + } + + for (AstConst* iconstp = VN_AS(itemp->condsp(), Const); iconstp; + iconstp = VN_AS(iconstp->nextp(), Const)) { + // Some items can never match due to 2-state simulation + if (neverItem(nodep, iconstp)) continue; + + const auto& match = matchPattern(nodep, iconstp); + const uint32_t mask = match.first.toUInt(); + const uint32_t bits = match.second.toUInt(); + + bool foundNewCase = false; + const AstConst* firstOverlapConstp = nullptr; + uint32_t firstOverlapValue = 0; + for (uint32_t i = 0; i < numValues; ++i) { + if ((i & mask) != bits) continue; + + CaseRecord& caseRecord = m_caseDetails.records[i]; + + // If this is the first case that covers this value, record it + if (!caseRecord.itemp) { + caseRecord.itemp = itemp; + caseRecord.constp = iconstp; + caseRecord.stmtsp = itemp->stmtsp(); + foundNewCase = true; + continue; + } + + // There is an overlap. If it's within the same CaseItem, it is legal + if (caseRecord.itemp == itemp) continue; + + // Otherwise record the first overlapping case + if (!firstOverlapConstp) { + firstOverlapConstp = caseRecord.constp; + firstOverlapValue = i; + m_caseDetails.noOverlaps = false; + } + } + + // Only report first overlap + if (reportedOverlap || !firstOverlapConstp) continue; + + // Report first overlap + if (nodep->priorityPragma()) { + // If this is a priority case, we only want to complain if every possible + // value for this item is already hit by some other item. This is true if + // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only + // covering item is this item itself, which is legal overlap within one + // item. + if (!foundNewCase) { + iconstp->v3warn(CASEOVERLAP, + "Case item ignored: every matching value is covered " + "by an earlier condition\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of previous condition\n" + << firstOverlapConstp->warnContextPrimary()); + reportedOverlap = true; + } } else { - const bool isCondWildcard = iconstp->num().isAnyXZ(); - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - const uint32_t mask = nummask.toUInt(); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - const uint32_t val = numval.toUInt(); - - uint32_t firstOverlap = 0; - const AstNode* overlappedCondp = nullptr; - bool foundHit = false; - for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) == val) { - if (!m_valueItem[i]) { - m_valueItem[i] = icondp; - caseItemMap[icondp] = itemp; - foundHit = true; - } else if (!overlappedCondp) { - // Overlapping case item expressions in the - // same case item are legal - if (caseItemMap[m_valueItem[i]] != itemp) { - firstOverlap = i; - overlappedCondp = m_valueItem[i]; - m_caseNoOverlapsAllCovered = false; - } - } - } - } - if (!nodep->priorityPragma()) { - // If this case statement doesn't have the priority - // keyword, we want to warn on any overlap. - if (!reportedOverlap && overlappedCondp) { - std::ostringstream examplePattern; - if (isCondWildcard) { - examplePattern << " (example pattern 0x" << std::hex - << firstOverlap << ")"; - } - icondp->v3warn(CASEOVERLAP, - "Case conditions overlap" - << examplePattern.str() << "\n" - << icondp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of overlapping condition\n" - << overlappedCondp->warnContextSecondary()); - reportedOverlap = true; - } - } else { - // If this is a priority case, we only want to complain - // if every possible value for this item is already hit - // by some other item. This is true if foundHit is - // false. - if (!reportedSubcase && !foundHit) { - icondp->v3warn(CASEOVERLAP, - "Case item ignored: every matching value is covered " - "by an earlier condition\n" - << icondp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of previous condition\n" - << overlappedCondp->warnContextPrimary()); - reportedSubcase = true; - } + // If this case statement doesn't have the priority keyword, + // we want to warn on any overlap. + std::ostringstream examplePattern; + if (iconstp->num().isAnyXZ()) { + examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue + << ")"; } + iconstp->v3warn(CASEOVERLAP, + "Case conditions overlap" + << examplePattern.str() << "\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of overlapping condition\n" + << firstOverlapConstp->warnContextSecondary()); + reportedOverlap = true; } } - // Defaults were moved to last in the caseitem list by V3LinkDot - if (itemp->isDefault()) { // Case statement's default... Fill the table - for (uint32_t i = 0; i < numCases; ++i) { - if (!m_valueItem[i]) m_valueItem[i] = itemp; - } - caseItemMap[itemp] = itemp; - hasDefaultCase = true; - } } - if (!hasDefaultCase) { - const AstEnumDType* const dtype = getEnumCompletionCheckDType(nodep); - if (dtype) { - if (!checkCaseEnumComplete(nodep, dtype)) { - // checkCaseEnumComplete has already warned of incompletion - m_caseNoOverlapsAllCovered = false; - return false; - } + + // If there was no default, check exhaustiveness + m_caseDetails.exhaustiveOverEnumOnly = false; + m_caseDetails.exhaustive = hasDefault; + if (!hasDefault) { + if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) { + // Only checks enum values are covered, not all bit patterns of the case expression + const bool exhaustiveOverEnum = checkExhaustiveEnum(nodep, enump); + m_caseDetails.exhaustiveOverEnumOnly = exhaustiveOverEnum; + m_caseDetails.exhaustive = exhaustiveOverEnum; } else { - for (uint32_t i = 0; i < numCases; ++i) { - if (!m_valueItem[i]) { // has uncovered case - if (!nodep->unique0Pragma()) - nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered " - "(example pattern 0x" - << std::hex << i << ")"); - m_caseIncomplete = true; - m_caseNoOverlapsAllCovered = false; - return false; - } - } + m_caseDetails.exhaustive = checkExhaustivePacked(nodep); } } - if (m_caseItems <= 3 - // Avoid e.g. priority expanders from going crazy in expansion - || (m_caseWidth >= 8 && (m_caseItems <= (m_caseWidth + 1)))) { - return false; // Not worth simplifying - } - - // Convert valueItem from AstCaseItem* to the expression - // Not done earlier, as we may now have a nullptr because it's just a ";" NOP branch - for (uint32_t i = 0; i < numCases; ++i) { - if (AstNode* const condp = m_valueItem[i]) { - const AstCaseItem* const caseItemp = caseItemMap[condp]; - UASSERT_OBJ(caseItemp, condp, "caseItemp should exist"); - m_valueItem[i] = caseItemp->stmtsp(); - } - } - return true; // All is fine + // Records now valid + m_caseDetailsValid = true; } - AstNode* replaceCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) { - if (msb < 0) { - // There's no space for a IF. We know upperValue is thus down to a specific - // exact value, so just return the tree value - // Note can't clone here, as we're going to check for equivalence above - AstNode* const foundp = m_valueItem[upperValue]; - return foundp; - } else { - // Make left and right subtrees - // cexpr[msb:lsb] == 1 - AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue); - AstNode* tree1p = replaceCaseFastRecurse( - cexprp, msb - 1, upperValue | (1UL << static_cast(msb))); + void analyzeDecoderPattern(AstCase* nodep) { + // Check each LHS record + for (auto it = m_caseLhsRecords.cbegin(); it != m_caseLhsRecords.cend();) { + const LhsRecord& lhsRecord = it->second; - if (tree0p == tree1p) { - // Same logic on both sides, so we can just return one of 'em - return tree0p; + // Delete records that have no assignments in any case item (only pre-defaults) + if (!lhsRecord.nCaseAssigns && !lhsRecord.nCaseAssignDlys) { + it = m_caseLhsRecords.erase(it); + continue; } - // We could have a "checkerboard" with A B A B, we can use the same IF on both edges + ++it; + + // If mixed assignments, it's not a decoder pattern + if (lhsRecord.nCaseAssigns && lhsRecord.nCaseAssignDlys) return; + + // If assigned in all branches, it's good - but only if every table entry will be + // covered, i.e. the case has a default, or is exhaustive over all bit patterns. + // Enum-only exhaustiveness is not enough: out-of-enum values leave entries + // uncovered. + if (m_caseHasDefault + || (m_caseDetailsValid && m_caseDetails.exhaustive + && !m_caseDetails.exhaustiveOverEnumOnly)) { + if (lhsRecord.nCaseAssigns == m_caseNCaseItems) continue; + if (lhsRecord.nCaseAssignDlys == m_caseNCaseItems) continue; + } + + // Otherwise it needs to have a pre-default assignment + AstNode* const preDefaultp = lhsRecord.preDefaultp; + if (!preDefaultp) return; + // And the pre-default needs to be the same type + if (lhsRecord.nCaseAssigns && !VN_IS(preDefaultp, Assign)) return; + if (lhsRecord.nCaseAssignDlys && !VN_IS(preDefaultp, AssignDly)) return; + } + // All cases check out, can optimize if there are some entries left + if (m_caseLhsRecords.empty()) return; + + // Gather all the LhsRecords and sort them - there is a copy here, it's ok, won't be many + m_caseDecoderRecords.reserve(m_caseLhsRecords.size()); + for (const auto& item : m_caseLhsRecords) m_caseDecoderRecords.emplace_back(item.second); + std::sort(m_caseDecoderRecords.begin(), m_caseDecoderRecords.end(), + [](const LhsRecord& a, const LhsRecord& b) { + // Sort by width, then id + const int aWidth = a.lhsp->width(); + const int bWidth = b.lhsp->width(); + if (aWidth != bWidth) return aWidth < bWidth; + return a.id < b.id; + }); + + // We can either create a single lookup table for all LHSs, or one for each LHS. + // With a single table, we need to select out of the lookup via a temporary variable. + // With one table per LHS, we need to do multiple loads. The table is likely to incur a + // D-cache miss on large designs, so we choose single table. + + const int caseWidth = nodep->exprp()->width(); + + // Safely check if table with 'entryWidth' entries would fit within 'maxWidth' bits + const auto fitsLimit = [&](size_t entryWidth, size_t maxWidth) -> bool { + size_t totalWidth = entryWidth; + // Multiply cases - iterative to avoid overflow + for (int i = 0; i < caseWidth; ++i) { + totalWidth <<= 1; + if (totalWidth > maxWidth) return false; + } + return true; + }; + + // Check if the whole table would fit in a tiny table packed tightly + m_caseDecoderEntryWidth = 0; + for (LhsRecord& lhsRecord : m_caseDecoderRecords) { + lhsRecord.offset = m_caseDecoderEntryWidth; + m_caseDecoderEntryWidth += lhsRecord.lhsp->width(); + } + // If it fits, we will pack it tightly + if (fitsLimit(m_caseDecoderEntryWidth, CASE_TABLE_TINY_BITS)) { + m_caseTableWidth = m_caseDecoderEntryWidth << caseWidth; // Can optimize + return; + } + + // Tabel will be bigish. To avoid expensive bit swizzling, align each entry to a + // word boundary if it would cross a word boundary. + m_caseDecoderEntryWidth = 0; + for (LhsRecord& lhsRecord : m_caseDecoderRecords) { + const size_t width = lhsRecord.lhsp->width(); + const size_t lsbWord = VL_BITWORD_E(m_caseDecoderEntryWidth); + const size_t msbWord = VL_BITWORD_E(m_caseDecoderEntryWidth + width - 1); + if (lsbWord != msbWord) { + m_caseDecoderEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; + } + lhsRecord.offset = m_caseDecoderEntryWidth; + m_caseDecoderEntryWidth += width; + } + // Check the table fits max size + const size_t alignedEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; + if (fitsLimit(alignedEntryWidth, CASE_TABLE_MAX_BITS)) { + m_caseTableWidth = alignedEntryWidth << caseWidth; // Can optimize + return; + } + + // Can optimize as AstMatchMasked, no other info needed + } + + // Analyze case statement. Updates 'm_case*' members. Reports warnings. + void analyzeCase(AstCase* nodep) { + // Reset all analysis results + m_caseOpaque = false; + m_caseHasDefault = false; + m_caseNCaseItems = 0; + m_caseNConditions = 0; + m_caseDecoderRecords.clear(); + m_caseDecoderEntryWidth = 0; + m_caseTableWidth = 0; + m_caseLhsRecords.clear(); + m_caseDetailsValid = false; + + AstNode* const caseExprp = nodep->exprp(); + + // Mark opaque if not a packed value - TODO: can this be a class? + if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true; + + // Gather pre-default assignments of decoder pattern + { + // AstVarScope::user1() -> bool: true if written to + const VNUser1InUse user1InUse; + for (AstNode* prevp = nodep->prevp(); prevp; prevp = prevp->prevp()) { + AstNodeAssign* const assp = checkDecoderAssign(prevp); + if (!assp) break; // Stop if not a decoder assignment + // Stop if multiple assignments + const bool multipleAssignments = assp->lhsp()->exists([&](AstVarRef* refp) { // + return refp->varScopep()->user1SetOnce(); + }); + if (multipleAssignments) break; + // Store pre-default assignment + LhsRecord& lhsRecord = m_caseLhsRecords[*assp->lhsp()]; + lhsRecord.lhsp = assp->lhsp(); + lhsRecord.preDefaultp = assp; + } + } + + // Check each case item + bool canBeDecoder = true; + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + // Check conditions + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + // Count conditions that can actually match. + if (!neverItem(nodep, VN_AS(condp, NodeExpr))) ++m_caseNConditions; + // Mark opaque if non-constant condition + if (!VN_IS(condp, Const)) { + m_caseOpaque = true; + canBeDecoder = false; // Can't be a decoder if opaque + } + } + // Check if it has a default case + if (cip->isDefault()) m_caseHasDefault = true; + // Count case items + ++m_caseNCaseItems; + // Check if it fits the decoder pattern, if still possible + if (canBeDecoder) canBeDecoder = analyzeDecoderCaseItem(cip); + } + + // Nothing else to do if not a packed type, or non-const conditions + if (m_caseOpaque) return; + + // If small enough, analyse details + if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep); + + // Check if it actually fits a full decoder pattern + if (canBeDecoder) analyzeDecoderPattern(nodep); + } + + AstNodeStmt* connectDecoderOutputs(AstCase* nodep, AstNodeExpr* exprp, + const char* tmpPrefixp) { + FileLine* const flp = nodep->fileline(); + + // If there is only one LHS, just use the result + if (m_caseDecoderRecords.size() == 1) { + const LhsRecord& lhsRecord = m_caseDecoderRecords[0]; + const int width = lhsRecord.lhsp->width(); + AstNodeExpr* const rhsp + = exprp->width() == width ? exprp : new AstSel{flp, exprp, 0, width}; + AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false); + if (lhsRecord.nCaseAssigns) { + return new AstAssign{flp, lhsp, rhsp}; + } else if (lhsRecord.nCaseAssignDlys) { + return new AstAssignDly{flp, lhsp, rhsp}; + } else { + nodep->v3fatalSrc("Unknown assignment type"); + } + } + + // There are multiple LHSs, store the lookup result in a temporary + const std::string name = tmpPrefixp + std::to_string(m_nTmps++); + AstVarScope* const tempVscp = m_scopep->createTemp(name, m_caseDecoderEntryWidth); + AstNodeExpr* const tempWritep = new AstVarRef{flp, tempVscp, VAccess::WRITE}; + AstNodeStmt* const resultp = new AstAssign{flp, tempWritep, exprp}; + + // For each LHS, select out the result + for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { + const int width = lhsRecord.lhsp->width(); + const int lsb = lhsRecord.offset; + AstNodeExpr* const tempReadp = new AstVarRef{flp, tempVscp, VAccess::READ}; + AstNodeExpr* const rhsp = new AstSel{flp, tempReadp, lsb, width}; + AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false); + if (lhsRecord.nCaseAssigns) { + resultp->addNext(new AstAssign{flp, lhsp, rhsp}); + } else if (lhsRecord.nCaseAssignDlys) { + resultp->addNext(new AstAssignDly{flp, lhsp, rhsp}); + } else { + nodep->v3fatalSrc("Unknown assignment type"); + } + } + return resultp; + } + + AstNodeStmt* convertCaseTable(AstCase* nodep) { + // Create the table constant + FileLine* const flp = nodep->fileline(); + AstConst* const tablep + = new AstConst{flp, AstConst::WidthedValue{}, static_cast(m_caseTableWidth), 0}; + const uint32_t tableEntries = 1U << nodep->exprp()->width(); + + const bool isTinyTable = m_caseTableWidth <= CASE_TABLE_TINY_BITS; + if (isTinyTable) { + ++m_stats.caseTableTiny; + } else { + ++m_stats.caseTableNormal; + } + + // Populate the table. Align entries to a word boundary to avoid bit swizzling at runtime. + const uint32_t entryWidth = isTinyTable + ? m_caseDecoderEntryWidth + : VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; + for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { + const int lhsWidth = lhsRecord.lhsp->width(); + const int lhsOffset = lhsRecord.offset; + + // Broadcast the pre-default assignment + if (lhsRecord.preDefaultp) { + AstConst* const rhsp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const); + for (uint32_t index = 0; index < tableEntries; ++index) { + const uint32_t tableOffset = index * entryWidth + lhsOffset; + tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); + } + } + + // Populate table based on each case item. In reverse order so earlier items win + for (AstCaseItem* cip = VN_AS(nodep->itemsp()->lastp(), CaseItem); cip; + cip = VN_AS(cip->prevp(), CaseItem)) { + // Find the RHS in this case + AstConst* const rhsp = [&]() -> AstConst* { + for (AstNode* stmtp = cip->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + AstNodeAssign* const ap = VN_AS(stmtp, NodeAssign); + if (lhsRecord.lhsp->sameTree(ap->lhsp())) return VN_AS(ap->rhsp(), Const); + } + // Not assigned in this case, use the pre-assigned default + return VN_AS(lhsRecord.preDefaultp->rhsp(), Const); + }(); + + // If default, broadcast it + if (cip->isDefault()) { + for (uint32_t index = 0; index < tableEntries; ++index) { + const uint32_t tableOffset = index * entryWidth + lhsOffset; + tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); + } + continue; + } + + // Iterate case conditions in reverse order + for (AstConst* condp = VN_AS(cip->condsp()->lastp(), Const); condp; + condp = VN_AS(condp->prevp(), Const)) { + if (neverItem(nodep, condp)) continue; // If item never matches, ignore it + const auto& match = matchPattern(nodep, condp); + const uint32_t matchMask = match.first.toUInt(); + const uint32_t matchBits = match.second.toUInt(); + const uint32_t inverseMask = ~matchMask & ((1U << condp->width()) - 1); + // This iterates through all integers that are a subset of the inverse mask, + // i.e.: all don't care values masked out + for (uint32_t i = inverseMask; true; i = (i - 1) & inverseMask) { + const uint32_t index = i | matchBits; + const uint32_t tableOffset = index * entryWidth + lhsOffset; + tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); + if (!i) break; + } + } + } + } + + // Create the table in the constant pool, unless using an inline table + AstVarScope* const tableVscp = [&]() -> AstVarScope* { + if (isTinyTable) return nullptr; + AstVarScope* vscp = v3Global.rootp()->constPoolp()->findConst(tablep, true); + VL_DO_DANGLING(tablep->deleteTree(), tablep); // findConst clones + return vscp; + }(); + + // Create the lookup table reference and index + AstNodeExpr* const tableRefp + = tableVscp ? static_cast(new AstVarRef{flp, tableVscp, VAccess::READ}) + : static_cast(tablep); + AstNodeExpr* const caseExprp + = new AstExtend{flp, nodep->exprp()->cloneTreePure(false), 32}; + AstNodeExpr* const scalep = new AstConst{flp, entryWidth}; + AstNodeExpr* const tableLsbp = new AstMul{flp, scalep, caseExprp}; + AstNodeExpr* const tableSelp + = new AstSel{flp, tableRefp, tableLsbp, static_cast(m_caseDecoderEntryWidth)}; + + // Connect outputs + return connectDecoderOutputs(nodep, tableSelp, "__VcaseTableOut"); + } + + AstNodeStmt* convertCaseDecoder(AstCase* nodep) { + ++m_stats.caseDecoder; + + FileLine* const flp = nodep->fileline(); + + // Gather all the case conditions, paird with their statements. A 'nullptr' condition + // matches anything (the default case, or the catch-all added below). + std::vector> clauses; // (condition, item statements) + clauses.reserve(m_caseNConditions + 1); + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + if (cip->isDefault()) { + clauses.emplace_back(nullptr, cip->stmtsp()); + continue; + } + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + AstConst* const iconstp = VN_AS(condp, Const); + // Skip items that can never match in 2-state simulation (e.g. X in casez) + if (neverItem(nodep, iconstp)) continue; + clauses.emplace_back(iconstp, cip->stmtsp()); + } + } + // If the case has no default item and is not provably exhaustive, unmatched selector + // values fall back to the pre-defaults. Represent that with a catch-all clause (null + // condition and no statements, so every LHS uses its pre-default). 'analyzeDecoderPattern' + // guarantees every LHS has a pre-default in this case. + const bool provenExhaustive = m_caseDetailsValid && m_caseDetails.exhaustive + && !m_caseDetails.exhaustiveOverEnumOnly; + if (clauses.back().first && !provenExhaustive) clauses.emplace_back(nullptr, nullptr); + + // Number of entries in decoder table + const int decoderEnries = clauses.size(); + + // Build the match table: a {matchBits, matchMask} packed pair per clause, shared by all + // LHSs. Each field is rounded up to a whole EDATA word boundary to avoid bit swizzling + // at runtime. We use a packed value so the runtime function can take a VlWide pointer + // without templating on the array size. + const int condWidth = nodep->exprp()->width(); + const int matchWidth = 2 * VL_WORDS_I(condWidth) * VL_EDATASIZE; + AstConst* const matchp + = new AstConst{flp, AstConst::WidthedValue{}, decoderEnries * matchWidth, 0}; + for (int i = 0; i < decoderEnries; ++i) { + const int entryLsb = i * matchWidth; + + // If the entry has a condition, use it's match bits and mask + if (AstConst* const condp = clauses[i].first) { + const auto& match = matchPattern(nodep, condp); + matchp->num().opSelInto(match.first, entryLsb, condWidth); + matchp->num().opSelInto(match.second, entryLsb + matchWidth / 2, condWidth); + continue; + } + + // Otherwise use zero for mask and bits, which matches anything + V3Number numZero{flp, condWidth, 0}; + matchp->num().opSelInto(numZero, entryLsb, condWidth); + matchp->num().opSelInto(numZero, entryLsb + matchWidth / 2, condWidth); + } + + // Create the table initializer + AstRange* const rangep = new AstRange{flp, decoderEnries - 1, 0}; + AstNodeDType* const entryDtypep = nodep->findBitDType( + m_caseDecoderEntryWidth, m_caseDecoderEntryWidth, VSigning::UNSIGNED); + AstNodeDType* const tableDtypep = new AstUnpackArrayDType{flp, entryDtypep, rangep}; + v3Global.rootp()->typeTablep()->addTypesp(tableDtypep); + AstInitArray* const tablep = new AstInitArray{flp, tableDtypep, nullptr}; + + // Build a single value table for all LHSs: one entry per clause, packing each LHS's value + // at its offset. The entry width is the table packing computed by 'analyzeDecoderPattern' + // Rounded up to a whole EDATA word boundary to avoid bit swizzling at runtime. + for (int i = 0; i < decoderEnries; ++i) { + AstNode* const stmtsp = clauses[i].second; + AstConst* const entryp = new AstConst{flp, AstConst::WidthedValue{}, + static_cast(m_caseDecoderEntryWidth), 0}; + for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { + AstNodeExpr* const lhsp = lhsRecord.lhsp; + // Find the value assigned to this LHS in the clause's statements + AstConst* valConstp = nullptr; + for (AstNode* stmtp = stmtsp; stmtp; stmtp = stmtp->nextp()) { + AstNodeAssign* const assignp = VN_AS(stmtp, NodeAssign); + if (!lhsp->sameTree(assignp->lhsp())) continue; + valConstp = VN_AS(assignp->rhsp(), Const); + break; + } + // Not assigned in this clause, so use the pre-assigned default + if (!valConstp) { + UASSERT_OBJ(lhsRecord.preDefaultp, nodep, + "Decoder LHS unassigned in case item without a pre-default"); + valConstp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const); + } + entryp->num().opSelInto(valConstp->num(), lhsRecord.offset, lhsp->width()); + } + tablep->addIndexValuep(i, entryp); + } + + // Create the tables + AstVarScope* const matchVscp = v3Global.rootp()->constPoolp()->findConst(matchp, true); + AstVarScope* const tableVscp = v3Global.rootp()->constPoolp()->findTable(tablep); + VL_DO_DANGLING(matchp->deleteTree(), matchp); + VL_DO_DANGLING(tablep->deleteTree(), tablep); + + // AstMatchMasked produces the index of the matching entry + AstNodeExpr* const tableRefp = new AstVarRef{flp, tableVscp, VAccess::READ}; + AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false); + AstMatchMasked* const indexp = new AstMatchMasked{flp, caseExprp, matchVscp}; + AstNodeExpr* const entryp = new AstArraySel{flp, tableRefp, indexp}; + + return connectDecoderOutputs(nodep, entryp, "__VcaseDecoderOut"); + } + + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const { + // Base case: If reached the last bit, upperValue equals an exact value, just return + // the statements from that CaseItem. Note: Not cloning here as the caller will do + // an identity check. + if (msb < 0) return m_caseDetails.records[upperValue].stmtsp; + + // Recursive case: + // Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively + const uint32_t upperValue0 = upperValue; + const uint32_t upperValue1 = upperValue | (1UL << msb); + AstNode* tree0p = convertCaseFastRecurse(cexprp, msb - 1, upperValue0); + AstNode* tree1p = convertCaseFastRecurse(cexprp, msb - 1, upperValue1); + + // If same logic on both sides, we can just return one of them + if (tree0p == tree1p) return tree0p; + + // We could have a "checkerboard" with A B A B, we can use the same IF on both edges + { bool same = true; - for (uint32_t a = upperValue, b = (upperValue | (1UL << msb)); - a < (upperValue | (1UL << msb)); a++, b++) { - if (m_valueItem[a] != m_valueItem[b]) { + for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) { + if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) { same = false; break; } @@ -373,161 +899,120 @@ class CaseVisitor final : public VNVisitor { VL_DO_DANGLING(tree1p->deleteTree(), tree1p); return tree0p; } - - // Must have differing logic, so make a selection - - // Case expressions can't be linked twice, so clone them - if (tree0p && !tree0p->user3()) tree0p = tree0p->cloneTree(true); - if (tree1p && !tree1p->user3()) tree1p = tree1p->cloneTree(true); - - // Alternate scheme if we ever do multiple bits at a time: - // V3Number nummask (cexprp, cexprp->width(), (1UL<fileline(), cexprp->cloneTreePure(false), - // new AstConst(cexprp->fileline(), nummask)); - AstNodeExpr* const and1p - = new AstSel{cexprp->fileline(), cexprp->cloneTreePure(false), msb, 1}; - AstNodeExpr* const eqp - = new AstNeq{cexprp->fileline(), new AstConst{cexprp->fileline(), 0}, and1p}; - AstIf* const ifp = new AstIf{cexprp->fileline(), eqp, tree1p, tree0p}; - ifp->user3(1); // So we don't bother to clone it - return ifp; } + + // Must have differing logic. Test the bit and convert to an If. + + // Clone if needed + if (tree0p && tree0p->backp()) tree0p = tree0p->cloneTree(true); + if (tree1p && tree1p->backp()) tree1p = tree1p->cloneTree(true); + // Create the If statement + FileLine* const flp = cexprp->fileline(); + AstNodeExpr* const condp = new AstSel{flp, cexprp->cloneTreePure(false), msb, 1}; + AstIf* const ifp = new AstIf{flp, condp, tree1p, tree0p}; + return ifp; } - void replaceCaseFast(AstCase* nodep) { - // CASEx(cexpr,.... - // -> tree of IF(msb, IF(msb-1, 11, 10) - // IF(msb-1, 01, 00)) - AstNodeExpr* cexprp; - AstExprStmt* cexprStmtp = nullptr; - if (nodep->exprp()->isPure()) { - cexprp = nodep->exprp()->unlinkFrBack(); - } else { - cexprStmtp = VN_AS(nodep->exprp()->unlinkFrBack(), ExprStmt); - cexprp = cexprStmtp->resultp()->cloneTreePure(false); - } - - if (debug() >= 9) { // LCOV_EXCL_START - for (uint32_t i = 0; i < (1UL << m_caseWidth); ++i) { - if (const AstNode* const itemp = m_valueItem[i]) { - UINFO(9, "Value " << std::hex << i << " " << itemp); - } - } - } // LCOV_EXCL_STOP - - // Handle any assertions - replaceCaseParallel(nodep, m_caseNoOverlapsAllCovered); - - AstNode::user3ClearTree(); - AstNode* ifrootp = replaceCaseFastRecurse(cexprp, m_caseWidth - 1, 0UL); - if (cexprStmtp) { - cexprStmtp->resultp()->unlinkFrBack()->deleteTree(); - AstIf* const ifp = VN_AS(ifrootp, If); - cexprStmtp->resultp(ifp->condp()->unlinkFrBack()); - ifp->condp(cexprStmtp); - m_needToClearCache = true; - } - - // Case expressions can't be linked twice, so clone them - if (ifrootp && !ifrootp->user3()) ifrootp = ifrootp->cloneTree(true); - - if (ifrootp) { - nodep->replaceWith(ifrootp); - } else { - nodep->unlinkFrBack(); - } - VL_DO_DANGLING(nodep->deleteTree(), nodep); - VL_DO_DANGLING(cexprp->deleteTree(), cexprp); - UINFOTREE(9, ifrootp, "", "_simp"); + // CASEx(cexpr,.... + // -> tree of IF(msb, IF(msb-1, 11, 10) + // IF(msb-1, 01, 00)) + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCaseFast(AstCase* nodep) { + ++m_stats.caseFast; + const int caseWidth = nodep->exprp()->width(); + AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); + return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp; } - void replaceCaseComplicated(AstCase* nodep) { - // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) - // -> IF((cexpr==icond1),istmts1, - // IF((EQ (AND MASK cexpr) (AND MASK icond1) - // ,istmts2, istmts3 - AstNodeExpr* cexprp; - AstExprStmt* cexprStmtp = nullptr; - if (nodep->exprp()->isPure()) { - cexprp = nodep->exprp()->unlinkFrBack(); - } else { - cexprStmtp = VN_AS(nodep->exprp(), ExprStmt)->unlinkFrBack(); - cexprp = cexprStmtp->resultp()->cloneTreePure(false); - } - // We'll do this in two stages. First stage, convert the conditions to - // the appropriate IF AND terms. - UINFOTREE(9, nodep, "", "_comp_IN::"); - bool hadDefault = false; + // Convet case statement using generic if/else tree + // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) + // -> IF((cexpr==icond1),istmts1, + // IF((EQ (AND MASK cexpr) (AND MASK icond1) + // ,istmts2, istmts3 + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCaseGeneric(AstCase* nodep) { + ++m_stats.caseGeneric; + // We'll do this in two stages. + // First stage, convert the conditions to the appropriate IF AND terms. + bool hasDefault = false; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - if (!itemp->condsp()) { - // Default clause. Just make true, we'll optimize it away later - itemp->addCondsp(new AstConst{itemp->fileline(), AstConst::BitTrue{}}); - hadDefault = true; - } else { - // Expressioned clause - AstNodeExpr* icondNextp = nullptr; - AstNodeExpr* ifexprp = nullptr; // If expression to test - for (AstNodeExpr* icondp = itemp->condsp(); icondp; icondp = icondNextp) { - icondNextp = VN_AS(icondp->nextp(), NodeExpr); - icondp->unlinkFrBack(); - AstNodeExpr* condp = nullptr; // Default is to use and1p/and2p - AstConst* const iconstp = VN_CAST(icondp, Const); - if (iconstp && neverItem(nodep, iconstp)) { - // X in casez can't ever be executed - VL_DO_DANGLING(icondp->deleteTree(), icondp); - VL_DANGLING(iconstp); - // For simplicity, make expression that is not equal, and let later - // optimizations remove it - condp = new AstConst{itemp->fileline(), AstConst::BitFalse{}}; - } else if (AstInsideRange* const irangep = VN_CAST(icondp, InsideRange)) { - // Similar logic in V3Width::visit(AstInside) - condp = irangep->newAndFromInside(cexprp->cloneTreePure(true), - irangep->lhsp()->unlinkFrBack(), - irangep->rhsp()->unlinkFrBack()); - VL_DO_DANGLING2(icondp->deleteTree(), icondp, irangep); - } else if (iconstp && iconstp->num().isFourState() - && (nodep->casex() || nodep->casez() || nodep->caseInside())) { - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - AstNodeExpr* const and1p - = new AstAnd{itemp->fileline(), cexprp->cloneTreePure(false), - new AstConst{itemp->fileline(), nummask}}; - AstNodeExpr* const and2p = new AstAnd{ - itemp->fileline(), new AstConst{itemp->fileline(), numval}, - new AstConst{itemp->fileline(), nummask}}; - VL_DO_DANGLING(icondp->deleteTree(), icondp); - VL_DANGLING(iconstp); - condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); - } else { - // Not a caseX mask, we can build CASEEQ(cexpr icond) - AstNodeExpr* const and1p = cexprp->cloneTreePure(false); - AstNodeExpr* const and2p = icondp; - condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); - } - if (!ifexprp) { - ifexprp = condp; - } else { - ifexprp = new AstLogOr{itemp->fileline(), ifexprp, condp}; - } - } - // Replace expression in tree - itemp->addCondsp(ifexprp); + FileLine* const flp = itemp->fileline(); + + // Default clause. Just make true, we'll optimize it away later + if (itemp->isDefault()) { + itemp->addCondsp(new AstConst{flp, AstConst::BitTrue{}}); + hasDefault = true; + continue; } + + // Regular clause. Construct the condition expression. + AstNodeExpr* newCondp = nullptr; + for (AstNodeExpr *itemExprp = itemp->condsp(), *nextp; itemExprp; itemExprp = nextp) { + nextp = VN_AS(itemExprp->nextp(), NodeExpr); + itemExprp->unlinkFrBack(); + + // If case never matches, ignore it + if (neverItem(nodep, itemExprp)) { + VL_DO_DANGLING(itemExprp->deleteTree(), itemExprp); + continue; + } + + // Compute the term to add to the condition expression + AstNodeExpr* const termp = [&]() -> AstNodeExpr* { + // Will need a copy of the caseExpr regardless + AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false); + + // InsideRange: Similar logic in V3Width::visit(AstInside) + if (AstInsideRange* const itemRangep = VN_CAST(itemExprp, InsideRange)) { + AstNodeExpr* const resultp = itemRangep->newAndFromInside( // + caseExprp, // + itemRangep->lhsp()->unlinkFrBack(), + itemRangep->rhsp()->unlinkFrBack()); + VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemRangep); + return resultp; + } + + // Check if we need to perform a wildcard match, this needs masking + if (AstConst* const itemConstp = VN_CAST(itemExprp, Const)) { + // TODO: 4-state will need to fix this + if (itemConstp->num().isFourState() + && (nodep->casex() || nodep->casez() || nodep->caseInside())) { + // Wildcard match, make 'caseExpr' & 'mask' == 'itemExpr' & 'mask' + const auto& match = matchPattern(nodep, itemConstp); + const V3Number& matchMask = match.first; + const V3Number& matchBits = match.second; + VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp); + return AstEq::newTyped( + flp, // + new AstConst{flp, matchBits}, + new AstAnd{flp, caseExprp, new AstConst{flp, matchMask}}); + } + } + + // Regular case, use simple equality comparison + return AstEq::newTyped(flp, caseExprp, itemExprp); + }(); + + // 'Or' new term with previous terms + newCondp = newCondp ? new AstLogOr{flp, newCondp, termp} : termp; + } + // Replace expression in tree. Needs to be non-null, so add a constant false if + // needed + if (!newCondp) newCondp = new AstConst{flp, AstConst::BitFalse{}}; + itemp->addCondsp(newCondp); } - VL_DO_DANGLING(cexprp->deleteTree(), cexprp); - if (!hadDefault) { - // If there was no default, add a empty one, this greatly simplifies below code - // and constant propagation will just eliminate it for us later. + + // If there was no default, add a empty one, this greatly simplifies below code + // and constant propagation will just eliminate it for us later. + if (!hasDefault) { nodep->addItemsp(new AstCaseItem{ nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr}); } - UINFOTREE(9, nodep, "", "_comp_COND"); + // Now build the IF statement tree - // The tree can be quite huge. Pull ever group of 8 out, and make a OR tree. + // The tree can be quite huge. Pull every group of 8 out, and make a OR tree. // This reduces the depth for the bottom elements, at the cost of // some of the top elements. If we ever have profiling data, we // should pull out the most common item from here and instead make @@ -538,8 +1023,11 @@ class CaseVisitor final : public VNVisitor { AstIf* itemnextp = nullptr; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - AstNode* const istmtsp = itemp->stmtsp(); // Maybe null -- no action. + + // Grab the statements from this item. May be empty. + AstNode* const istmtsp = itemp->stmtsp(); if (istmtsp) istmtsp->unlinkFrBackWithNext(); + // Expressioned clause AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack(); { // Prepare for next group @@ -575,67 +1063,114 @@ class CaseVisitor final : public VNVisitor { itemnextp = newp; } } - UINFOTREE(9, nodep, "", "_comp_TREE"); - // Handle any assertions - replaceCaseParallel(nodep, false); - // Replace the CASE... with IF... - if (grouprootp) { - UINFOTREE(9, grouprootp, "", "_new"); - nodep->replaceWith(grouprootp); - if (cexprStmtp) { - pushDeletep(cexprStmtp->resultp()->unlinkFrBack()); - cexprStmtp->resultp(grouprootp->condp()->unlinkFrBack()); - grouprootp->condp(cexprStmtp); - m_needToClearCache = true; + return grouprootp; + } + + // Convert the given case statement to a representation not using AstCase + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCase(AstCase* nodep) { + // Determine if we should use the lookup table method + const bool useTable = [&]() { + // Not if disabled + if (!v3Global.opt.fCaseTable()) return false; + // Not if analysis tells us we can't + if (!m_caseTableWidth) return false; + // Always if tiny - it is materialized inline, so there is no load to amortize + if (m_caseTableWidth <= CASE_TABLE_TINY_BITS) return true; + // For a normal (constant-pool) table, weigh the indexed load against the branch + // lowering it would replace. That lowering's depth is bounded by the selector + // width (a balanced bit tree tests ~one bit per level) and by the number of + // distinct values (a generic if/else does ~one compare per value). A few compares + // are cheaper than a load that is likely to be a cache miss, so only table once that + // depth is exceeded. + const size_t branches = std::min(nodep->exprp()->width(), m_caseNConditions); + if (branches < CASE_TABLE_MIN_BRANCHES) return false; + return true; + }(); + if (useTable) return convertCaseTable(nodep); + + // Determine if we should use the decoder method. + const bool useDecoder = [&]() { + // Not if disabled + if (!v3Global.opt.fCaseDecoder()) return false; + // Not if not a decoder pattern + if (m_caseDecoderRecords.empty()) return false; + // Only worth it once the branch lowering it would replace is deep enough (see + // useTable) + const size_t branches = std::min(nodep->exprp()->width(), m_caseNConditions); + if (branches < CASE_TABLE_MIN_BRANCHES) return false; + return true; + }(); + if (useDecoder) return convertCaseDecoder(nodep); + + // Determine if we should use the fast bitwise branching tree method + const bool useFastBitTree = [&]() { + // Not if disabled + if (!v3Global.opt.fCaseTree()) return false; + // Can't do it without the detailed analysis + if (!m_caseDetailsValid) return false; + // Can't do it if not exhaustive + if (!m_caseDetails.exhaustive) return false; + // Not worth doing if there are few conditions + if (m_caseNConditions <= 3) return false; + // Avoid e.g. priority expanders from going crazy in expansion + const size_t caseWidth = nodep->exprp()->width(); + if (caseWidth >= 8 && m_caseNConditions <= (caseWidth + 1)) return false; + // Otherwise use the bit tree + return true; + }(); + if (useFastBitTree) return convertCaseFast(nodep); + + // Convert using the generic if/else tree method + // If a case statement is exhaustive, presume signals involved aren't forming a latch + // TODO: this is broken, but it is as was before + if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) { + m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); + } + return convertCaseGeneric(nodep); + } + + // VISITORS + void visit(AstCase* nodep) override { + UASSERT_OBJ(nodep->exprp()->isPure(), nodep, + "Impure case expression should have been removed by V3LiftExpr"); + + CaseLintVisitor::apply(nodep); + + // Convert any children first + iterateChildren(nodep); + + // Analyze this case statement + analyzeCase(nodep); + + // Take the 'notParallelp' statements under the case statement created by V3Assert. + // If the statement was proven to have no overlaps and all cases covered, + // it can be removed. Otherwise insert the assertion after the case statement. + if (AstNode* const stmtp = nodep->notParallelp()) { + stmtp->unlinkFrBackWithNext(); + if (m_caseDetailsValid && m_caseDetails.exhaustive && m_caseDetails.noOverlaps) { + ++m_stats.provenAssertions; + VL_DO_DANGLING(stmtp->deleteTree(), stmtp); + } else { + nodep->addNextHere(stmtp); } + } + + // Convert the case statement and replace the original + if (AstNode* const replacementp = convertCase(nodep)) { + nodep->replaceWith(replacementp); } else { nodep->unlinkFrBack(); } VL_DO_DANGLING(nodep->deleteTree(), nodep); } - void replaceCaseParallel(AstCase* nodep, bool noOverlapsAllCovered) { - // Take the notParallelp tree under the case statement created by V3Assert - // If the statement was proven to have no overlaps and all cases - // covered, we're done with it. - // Else, convert to a normal statement parallel with the case statement. - if (nodep->notParallelp() && !noOverlapsAllCovered) { - AstNode* const parp = nodep->notParallelp()->unlinkFrBackWithNext(); - nodep->addNextHere(parp); - } - } - - bool neverItem(const AstCase* casep, const AstConst* itemp) { - // Xs in case or casez are impossible due to two state simulations - if (casep->casex()) { - } else if (casep->casez() || casep->caseInside()) { - if (itemp->num().isAnyX()) return true; - } else { - if (itemp->num().isFourState()) return true; - } - return false; - } - - // VISITORS - void visit(AstCase* nodep) override { - VL_RESTORER(m_caseIncomplete); - { CaseLintVisitor{nodep}; } + void visit(AstScope* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; iterateChildren(nodep); - UINFOTREE(9, nodep, "", "case_old"); - if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) { - // It's a simple priority encoder or complete statement - // we can make a tree of statements to avoid extra comparisons - ++m_statCaseFast; - VL_DO_DANGLING(replaceCaseFast(nodep), nodep); - } else { - // If a case statement is whole, presume signals involved aren't forming a latch - if (m_alwaysp && !m_caseIncomplete) - m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); - ++m_statCaseSlow; - VL_DO_DANGLING(replaceCaseComplicated(nodep), nodep); - } } - //-------------------- + void visit(AstAlways* nodep) override { VL_RESTORER(m_alwaysp); m_alwaysp = nodep; @@ -645,17 +1180,19 @@ class CaseVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit CaseVisitor(AstNetlist* nodep) { - for (auto& itr : m_valueItem) itr = nullptr; - iterate(nodep); - if (m_needToClearCache) VIsCached::clearCacheTree(); - } + explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); } ~CaseVisitor() override { - V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); - V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow); + V3Stats::addStat("Optimizations, Cases decoder", m_stats.caseDecoder); + V3Stats::addStat("Optimizations, Cases table normal", m_stats.caseTableNormal); + V3Stats::addStat("Optimizations, Cases table tiny", m_stats.caseTableTiny); + V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast); + V3Stats::addStat("Optimizations, Cases complex", m_stats.caseGeneric); + V3Stats::addStat("Optimizations, Cases proven assertions", m_stats.provenAssertions); } }; +size_t CaseVisitor::LhsRecord::s_nextId = 0; + //###################################################################### // Case class functions @@ -666,5 +1203,5 @@ void V3Case::caseAll(AstNetlist* nodep) { } void V3Case::caseLint(AstGenCase* nodep) { UINFO(4, __FUNCTION__ << ": "); - { CaseLintVisitor{nodep}; } + CaseLintVisitor::apply(nodep); } diff --git a/src/V3Clean.cpp b/src/V3Clean.cpp index 2c326bbb7..07920f8de 100644 --- a/src/V3Clean.cpp +++ b/src/V3Clean.cpp @@ -232,6 +232,11 @@ class CleanVisitor final : public VNVisitor { ensureClean(nodep->rhsp()); setClean(nodep, true); } + void visit(AstMatchMasked* nodep) override { + iterateChildren(nodep); + ensureClean(nodep->lhsp()); + setClean(nodep, true); + } void visit(AstSel* nodep) override { operandBiop(nodep); setClean(nodep, nodep->cleanOut()); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index de2e1d64f..203fef45a 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -286,7 +286,7 @@ class ConstBitOpTreeVisitor final : public VNVisitorConst { // Get the mask that selects the bits that are relevant in this term V3Number maskNum{srcp, m_width, 0}; - maskNum.opBitsNonX(m_bitPolarity); // 'x' -> 0, 0->1, 1->1 + maskNum.opBitsNonXZ(m_bitPolarity); // 'x' -> 0, 0->1, 1->1 const uint64_t maskVal = maskNum.toUQuad(); UASSERT_OBJ(maskVal != 0, m_refp, "Should have been recognized as having const 0 result"); @@ -973,6 +973,96 @@ class ConstVisitor final : public VNVisitor { return !numv.isNumber() ? numv : V3Number{nodep, nodep->width(), numv}; } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } + + AstStructSel* newStructSel(AstNodeExpr* const fromp, const AstMemberDType* const itemp) { + AstStructSel* const selp = new AstStructSel{fromp->fileline(), fromp, itemp->name()}; + selp->dtypeFrom(itemp->dtypep()); + return selp; + } + + void collectFixedAggregateTerms(AstNodeExpr* const fromp, std::vector& termps, + const bool packReal) { + const AstNodeDType* const dtypep = fromp->dtypep()->skipRefp(); + if (const AstUnpackArrayDType* const unpackDtypep = VN_CAST(dtypep, UnpackArrayDType)) { + const int left = unpackDtypep->left(); + const int right = unpackDtypep->right(); + const int step = left <= right ? 1 : -1; + for (int idx = left;; idx += step) { + AstArraySel* const selp + = new AstArraySel{fromp->fileline(), fromp->cloneTreePure(false), idx}; + collectFixedAggregateTerms(selp, termps, packReal); + if (idx == right) break; + } + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) { + termps.push_back(fromp); + return; + } + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + collectFixedAggregateTerms(newStructSel(fromp->cloneTreePure(false), itemp), + termps, packReal); + } + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else if (packReal && dtypep->isDouble()) { + termps.push_back(new AstRealToBits{fromp->fileline(), fromp}); + } else { + termps.push_back(fromp); + } + } + + AstNodeExpr* packFixedAggregate(AstNodeExpr* const fromp) { + std::vector termps; + collectFixedAggregateTerms(fromp, termps, true); + UASSERT(!termps.empty(), "No stream terms"); + AstNodeExpr* resultp = termps[0]; + for (size_t i = 1; i < termps.size(); ++i) { + resultp = new AstConcat{resultp->fileline(), resultp, termps[i]}; + } + return resultp; + } + + void replaceAssignToFixedAggregate(AstNodeAssign* const nodep, AstNodeExpr* const dstp, + AstNodeExpr* srcp) { + const int dstWidth = dstp->dtypep()->widthStream(); + std::vector termps; + collectFixedAggregateTerms(dstp, termps, false); + int srcWidth = srcp->width(); + if (srcWidth < dstWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dstWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + extendp->fileline(), extendp, + new AstConst{extendp->fileline(), static_cast(dstWidth - srcWidth)}, + dstWidth}; + srcWidth = dstWidth; + } + AstNodeAssign* newp = nullptr; + int offset = 0; + for (size_t i = 0; i < termps.size(); ++i) { + AstNodeExpr* const termp = termps[i]; + const int width = termp->dtypep()->widthStream(); + const int lsb = srcWidth - offset - width; + offset += width; + AstNodeExpr* rhsp + = new AstSel{srcp->fileline(), srcp->cloneTreePure(false), lsb, width}; + if (termp->dtypep()->skipRefp()->isDouble()) { + rhsp = new AstBitsToRealD{rhsp->fileline(), rhsp}; + } + AstNodeAssign* const assignp = nodep->cloneType(termp, rhsp); + assignp->dtypeFrom(termp); + newp = AstNode::addNext(newp, assignp); + } + nodep->addNextHere(newp); + VL_DO_DANGLING(pushDeletep(srcp), srcp); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + } + bool operandConst(const AstNode* nodep) { return VN_IS(nodep, Const); } bool operandAsvConst(const AstNode* nodep) { // BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...) @@ -1327,6 +1417,45 @@ class ConstVisitor final : public VNVisitor { return false; } + bool matchMaskedZero(const AstAnd* nodep) { + // Turn masking of known zero bits into constant zero. Commonly appears after V3Expand. + const AstConst* const maskp = VN_AS(nodep->lhsp(), Const); + const AstNodeExpr* const rhsp = nodep->rhsp(); + const uint32_t msbP1 = maskp->num().mostSetBitP1(); + if (!msbP1) return false; // Don't rewrite, separate rule matches for this + const uint32_t msb = msbP1 - 1; + const uint32_t lsb = maskp->num().leastSetBitP1() - 1; + + if (const AstShiftL* const shiftp = VN_CAST(rhsp, ShiftL)) { + // 'a << S' forces the low S bits to zero + if (AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { + return scp->num().fitsInUInt() // + && (scp->num().toUInt() > msb); + } + } + if (const AstShiftR* const shiftp = VN_CAST(rhsp, ShiftR)) { + // 'a >> S' forces the high S bits to zero. Check against the width of the shifted + // operand, V3Expand can create shifts wider than their inputs + if (AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { + return scp->num().fitsInUInt() + && (lsb + scp->num().toUInt() + >= static_cast(shiftp->lhsp()->widthMin())); + } + } + if (const AstMul* const mulp = VN_CAST(rhsp, Mul)) { + // 'C * a' forces the low N bits to zero where 'C' has low zero bits + if (AstConst* const cp = VN_CAST(mulp->lhsp(), Const)) { + return cp->num().leastSetBitP1() > msb + 1; + } + } + if (const AstExtend* const extendp = VN_CAST(rhsp, Extend)) { + // Zero-extension forces the bits above the source width to zero + return lsb >= static_cast(extendp->lhsp()->width()); + } + + return false; + } + bool matchBitOpTree(AstNodeExpr* nodep) { if (nodep->widthMin() != 1) return false; if (!v3Global.opt.fConstBitOpTree()) return false; @@ -1474,16 +1603,46 @@ class ConstVisitor final : public VNVisitor { && static_cast(nodep->widthConst()) == nodep->fromp()->width()); } bool operandSelExtend(AstSel* nodep) { - // A pattern created by []'s after offsets have been removed - // SEL(EXTEND(any,width,...),(width-1),0) -> ... - // Since select's return unsigned, this is always an extend - // cppcheck-suppress constVariablePointer // children unlinked below + if (!m_doV) return false; AstExtend* const extendp = VN_CAST(nodep->fromp(), Extend); - if (!(m_doV && extendp && VN_IS(nodep->lsbp(), Const) && nodep->lsbConst() == 0 - && static_cast(nodep->widthConst()) == extendp->lhsp()->width())) - return false; - VL_DO_DANGLING(replaceWChild(nodep, extendp->lhsp()), nodep); - return true; + if (!extendp) return false; + AstConst* const lsbp = VN_CAST(nodep->lsbp(), Const); + if (!lsbp) return false; + const int width = nodep->widthConst(); + const int lsb = lsbp->toSInt(); + const int msb = lsb + width - 1; + AstNodeExpr* const lhsp = extendp->lhsp(); + if (!lhsp->isPure()) return false; + + // Selecting the entire extended expression, replace with that + if (lsb == 0 && msb == lhsp->width() - 1) { + lhsp->unlinkFrBack(); + nodep->replaceWithKeepDType(lhsp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + // Select entirely in the extended part - replace with zero + if (lsb >= lhsp->width()) { + replaceZero(nodep); + return true; + } + // Select entirely in the extended expression - replace with select from that + if (msb < lhsp->width()) { + lhsp->unlinkFrBack(); + lsbp->unlinkFrBack(); + nodep->replaceWithKeepDType(new AstSel{nodep->fileline(), lhsp, lsbp, width}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + // Select straddles both sides, but is just a shorter extend + if (lsb == 0) { + lhsp->unlinkFrBack(); + nodep->replaceWithKeepDType(new AstExtend{nodep->fileline(), lhsp}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + + return false; } bool operandSelBiLower(AstSel* nodep) { // SEL(ADD(a,b),(width-1),0) -> ADD(SEL(a),SEL(b)) @@ -2393,6 +2552,25 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(conp), conp); // Further reduce, either node may have more reductions. return true; + } else if (m_doV && VN_IS(nodep->rhsp(), CvtPackedToArray) + && lowerAsFixedAggregate(nodep->lhsp()->dtypep())) { + AstCvtPackedToArray* const cvtp + = VN_AS(nodep->rhsp(), CvtPackedToArray)->unlinkFrBack(); + AstNodeExpr* srcp = cvtp->fromp()->unlinkFrBack(); + if (lowerAsFixedAggregate(srcp->dtypep())) { + srcp = packFixedAggregate(srcp); + } else if (AstNodeStream* const streamp = VN_CAST(srcp, NodeStream)) { + AstNodeExpr* const streamSrcp = streamp->lhsp(); + if (lowerAsFixedAggregate(streamSrcp->dtypep())) { + AstNodeExpr* const packedp = packFixedAggregate(streamSrcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + } + } + VL_DO_DANGLING(pushDeletep(cvtp), cvtp); + replaceAssignToFixedAggregate(nodep, nodep->lhsp()->unlinkFrBack(), srcp); + return true; } else if (m_doV && VN_IS(nodep->rhsp(), StreamR) && !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) { // The right-streaming operator on rhs of assignment does not @@ -2402,7 +2580,9 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack(); AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); const AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); - if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { + if (lowerAsFixedAggregate(srcDTypep)) { + srcp = packFixedAggregate(srcp); + } else if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { int srcElementBits = 0; if (const AstNodeDType* const elemDtp = srcDTypep->subDTypep()) { @@ -2429,6 +2609,19 @@ class ConstVisitor final : public VNVisitor { srcp = new AstShiftL{srcp->fileline(), srcp, new AstConst{srcp->fileline(), offset}, packedBits}; } + if (!VN_IS(dstDTypep, UnpackArrayDType) && !VN_IS(dstDTypep, QueueDType) + && !VN_IS(dstDTypep, DynArrayDType)) { + const int sWidth = srcp->width(); + const int dWidth = nodep->lhsp()->width(); + if (sWidth < dWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + srcp->fileline(), extendp, + new AstConst{srcp->fileline(), static_cast(dWidth - sWidth)}, + dWidth}; + } + } nodep->rhsp(srcp); VL_DO_DANGLING(pushDeletep(streamp), streamp); // Further reduce, any of the nodes may have more reductions. @@ -2438,7 +2631,7 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* streamp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack(); AstNodeDType* const dstDTypep = dstp->dtypep()->skipRefp(); - AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); // Handle unpacked/queue/dynarray source -> queue/dynarray dest via // CvtArrayToArray (StreamL reverses, so reverse=true) @@ -2466,6 +2659,7 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(streamp), streamp); return true; } + if (lowerAsFixedAggregate(srcDTypep)) srcp = packFixedAggregate(srcp); const int sWidth = srcp->width(); const int dWidth = dstp->width(); // Connect the rhs to the stream operator and update its width @@ -2556,7 +2750,7 @@ class ConstVisitor final : public VNVisitor { } else { // Source narrower than destination: left-justify by shifting left. // The right stream operator packs left-to-right, so remaining - // LSBs are zero-filled (IEEE 1800-2023 11.4.14.2). + // LSBs are zero-filled (IEEE 1800-2023 11.4.14.3). if (!VN_IS(srcp->dtypep()->skipRefp(), QueueDType)) { AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); @@ -2580,6 +2774,13 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); + if (lowerAsFixedAggregate(srcDTypep)) { + AstNodeExpr* const packedp = packFixedAggregate(srcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + srcp = packedp; + } if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType) || VN_IS(srcDTypep, UnpackArrayDType))) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { @@ -2887,6 +3088,15 @@ class ConstVisitor final : public VNVisitor { } } void visit(AstClassOrPackageRef* nodep) override { iterateChildren(nodep); } + + void visit(AstMatchMasked* nodep) override { + // Do not iterate the tables, they must be constant pool entries + iterate(nodep->lhsp()); + if (AstConst* const constp = VN_CAST(nodep->lhsp(), Const)) { + replaceNum(nodep, AstMatchMasked::fold(constp->num(), nodep->matchp()->varp())); + } + } + void visit(AstPin* nodep) override { iterateChildren(nodep); } void replaceLogEq(AstLogEq* nodep) { @@ -3149,7 +3359,8 @@ class ConstVisitor final : public VNVisitor { iterateChildren(nodep); UASSERT_OBJ(nodep->varp(), nodep, "Not linked"); bool did = false; - if (m_doV && nodep->varp()->valuep() && !m_attrp) { + if (m_doV && (!nodep->varp()->constPoolEntry() || m_selp) && nodep->varp()->valuep() + && !m_attrp) { // UINFOTREE(1, valuep, "", "visitvaref"); iterateAndNextNull(nodep->varp()->valuep()); // May change nodep->varp()->valuep() AstNode* const valuep = nodep->varp()->valuep(); @@ -4081,6 +4292,8 @@ class ConstVisitor final : public VNVisitor { // Zero on one side or the other TREEOP ("AstAdd {$lhsp.isZero, $rhsp}", "replaceWRhs(nodep)"); TREEOP ("AstAnd {$lhsp.isZero, $rhsp, $rhsp.isPure}", "replaceZero(nodep)"); // Can't use replaceZeroChkPure as we make this pattern in ChkPure + // Masking that always yields zero + TREEOP ("AstAnd {$lhsp.castConst, matchMaskedZero(nodep)}", "replaceZeroChkPure(nodep, $rhsp)"); // This visit function here must allow for short-circuiting. TREEOPS("AstLogAnd {$lhsp.isZero}", "replaceZero(nodep)"); TREEOP ("AstLogAnd{$lhsp.isZero, $rhsp}", "replaceZero(nodep)"); diff --git a/src/V3Control.cpp b/src/V3Control.cpp index e95e3c086..3ac7aa727 100644 --- a/src/V3Control.cpp +++ b/src/V3Control.cpp @@ -182,7 +182,6 @@ class V3ControlFTask final { V3ControlVarResolver m_params; // Parameters in function/task V3ControlVarResolver m_ports; // Ports in function/task V3ControlVarResolver m_vars; // Variables in function/task - bool m_isolate = false; // Isolate function return bool m_noinline = false; // Don't inline function/task bool m_public = false; // Public function/task @@ -190,7 +189,6 @@ public: V3ControlFTask() = default; void update(const V3ControlFTask& f) { // Don't overwrite true with false - if (f.m_isolate) m_isolate = true; if (f.m_noinline) m_noinline = true; if (f.m_public) m_public = true; m_params.update(f.m_params); @@ -203,7 +201,6 @@ public: V3ControlVarResolver& ports() { return m_ports; } V3ControlVarResolver& vars() { return m_vars; } - void setIsolate(bool set) { m_isolate = set; } void setNoInline(bool set) { m_noinline = set; } void setPublic(bool set) { m_public = set; } @@ -212,8 +209,6 @@ public: ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::NO_INLINE_TASK}); if (m_public) ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::PUBLIC_TASK}); - // Only functions can have isolate (return value) - if (VN_IS(ftaskp, Func)) ftaskp->attrIsolateAssign(m_isolate); } }; @@ -981,13 +976,7 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta // Semantics: Most of the attributes operate on signals if (pattern.empty()) { - if (attr == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { - if (ftask.empty()) { - fl->v3error("isolate_assignments only applies to signals or functions/tasks"); - } else { - V3ControlResolver::s().modules().at(module).ftasks().at(ftask).setIsolate(true); - } - } else if (attr == VAttrType::VAR_PUBLIC) { + if (attr == VAttrType::VAR_PUBLIC) { if (ftask.empty()) { // public module, this is the only exception from var here V3ControlResolver::s().modules().at(module).addModulePragma( diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 63cb65b8a..12bb973dc 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -166,10 +166,13 @@ class CoverageVisitor final : public VNVisitor { // METHODS + // Return non-nullptr reason if this variable shouldn't have toggle coverage const char* varIgnoreToggle(const AstVar* nodep) { - // Return true if this shouldn't be traced - // See also similar rule in V3TraceDecl::varIgnoreTrace - if (!nodep->isToggleCoverable()) return "Not relevant signal type"; + const bool cover = nodep->isIO() || (nodep->isSignal() && nodep->isBitLogic()); + if (!cover) return "Not relevant signal"; + if (nodep->isConst()) return "Signal is constant"; + if (nodep->isDouble()) return "Signal is double"; + if (nodep->isString()) return "Signal is string"; if (!v3Global.opt.coverageUnderscore()) { const string prettyName = nodep->prettyName(); if (prettyName[0] == '_') return "Leading underscore"; @@ -530,8 +533,10 @@ class CoverageVisitor final : public VNVisitor { newent.cleanup(); } } - } else if (VN_IS(dtypep, QueueDType)) { + } else if (VN_IS(dtypep, QueueDType) || VN_IS(dtypep, AssocArrayDType) + || VN_IS(dtypep, WildcardArrayDType)) { // Not covered + varp->v3warn(COVERIGN, "Coverage ignored for type " << dtypep->prettyTypeName()); } else { dtypep->v3fatalSrc("Unexpected node data type in toggle coverage generation: " << dtypep->prettyTypeName()); diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 727b5689c..054f27c34 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -68,6 +68,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { , crossBins{cb} {} }; std::vector m_binInfos; // All bins in current covergroup + std::set m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy) + std::vector m_convCpVars; // VlCoverpoint members of converted coverpoints + AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type VMemberMap m_memberMap; // Member names cached for fast lookup @@ -88,6 +91,17 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Clear bin info for this covergroup (deleting any orphaned cross pseudo-bins) clearBinInfos(); + // Coverpoints referenced by a cross keep the legacy per-bin-member path (the cross + // reads those members); collect their names before they are consumed by the cross. + m_crossedCpNames.clear(); + m_convCpVars.clear(); + for (AstCoverCross* crossp : m_coverCrosses) { + for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) { + if (const AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef)) + m_crossedCpNames.insert(refp->name()); + } + } + // For each coverpoint, generate sampling code for (AstCoverpoint* cpp : m_coverpoints) generateCoverpointCode(cpp); @@ -504,6 +518,13 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Create implicit automatic bins if no regular bins exist createImplicitAutoBins(coverpointp, exprp, autoBinMax); + // Eligible coverpoints route through the VlCoverpoint runtime; the rest (cross-fed or + // transition-bearing) keep the legacy per-bin-member path below. + if (coverpointConvertible(coverpointp)) { + generateConvertedCoverpoint(coverpointp, exprp, atLeastValue); + return; + } + // Generate member variables and matching code for each bin // Process in two passes: first non-default bins, then default bins std::vector defaultBins; @@ -595,47 +616,213 @@ class FunctionalCoverageVisitor final : public VNVisitor { UINFO(4, " Successfully added if statement for bin: " << binp->name()); } + // Build the condition under which a default bin matches: NOT(OR of all normal bins). + AstNodeExpr* buildDefaultCondition(AstCoverpoint* coverpointp, AstNodeExpr* exprp, + FileLine* fl) { + AstNodeExpr* anyBinMatchp = nullptr; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT + || cbinp->binsType() == VCoverBinsType::BINS_IGNORE + || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) + continue; + AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); + UASSERT_OBJ(binCondp, cbinp, + "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); + anyBinMatchp = anyBinMatchp ? new AstOr{fl, anyBinMatchp, binCondp} : binCondp; + } + return anyBinMatchp ? static_cast(new AstNot{fl, anyBinMatchp}) + : static_cast(new AstConst{fl, AstConst::BitTrue{}}); + } + + //==================================================================== + // VlCoverpoint conversion (eligible coverpoints) + + // True if a coverpoint routes through the VlCoverpoint runtime. Cross-fed coverpoints + // (the cross reads their per-bin members) and transition-bearing ones stay legacy. + bool coverpointConvertible(AstCoverpoint* coverpointp) { + if (m_crossedCpNames.count(coverpointp->name())) return false; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + if (VN_AS(binp, CoverBin)->transp()) return false; + } + return true; + } + + // A 'this->m_member' reference for embedding in an AstCStmt + AstVarRef* memberRef(FileLine* fl, AstVar* varp) { + AstVarRef* const refp = new AstVarRef{fl, varp, VAccess::READ}; + refp->selfPointer(VSelfPointerText{VSelfPointerText::This{}}); + return refp; + } + + // Individual equality targets of an array bin (bins b[] = {values/ranges}), in order. + // An open-ended bound ('$', AstUnbounded) resolves to the coverpoint domain: '[lo:$]' + // covers [lo:maxVal] and '[$:hi]' covers [0:hi]. One target is produced per value; a + // range whose resolved size would exceed COVER_BINS_LIMIT (e.g. an open '[lo:$]' over a + // wide coverpoint) is unsupported -- emits COVERIGN, sets unsupportedOut, yields nothing. + std::vector extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp, + bool& unsupportedOut) { + unsupportedOut = false; + const int width = exprp->width(); + const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1); + std::vector values; + for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { + if (AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) { + AstNodeExpr* const lhsp = V3Const::constifyEdit(irp->lhsp()); + AstNodeExpr* const rhsp = V3Const::constifyEdit(irp->rhsp()); + const bool loUnb = VN_IS(lhsp, Unbounded); + const bool hiUnb = VN_IS(rhsp, Unbounded); + AstConst* const minp = VN_CAST(lhsp, Const); + AstConst* const maxp = VN_CAST(rhsp, Const); + if ((!minp && !loUnb) || (!maxp && !hiUnb)) { + arrayBinp->v3error("Non-constant expression in array bins range; " + "range bounds must be constants"); + return values; + } + if ((minp && minp->num().isFourState()) || (maxp && maxp->num().isFourState())) { + arrayBinp->v3error("Four-state (x/z) value in array bins range bound; " + "range bounds must be two-state constants"); + return values; + } + const uint64_t lo = loUnb ? 0 : minp->toUQuad(); + const uint64_t hi = hiUnb ? maxVal : maxp->toUQuad(); + if (hi < lo) continue; // empty range contributes no bins + // Guard against a '$'-bounded or otherwise huge range exploding the bin count. + const uint64_t span = hi - lo; // == valueCount - 1 (no overflow: hi >= lo) + if (span >= static_cast(COVER_BINS_LIMIT) + || values.size() + span + 1 > static_cast(COVER_BINS_LIMIT)) { + arrayBinp->v3warn(COVERIGN, "Unsupported: array 'bins' covering more than " + << COVER_BINS_LIMIT + << " values (e.g. an open '[lo:$]' range over " + "a wide coverpoint); bin ignored"); + unsupportedOut = true; + for (AstNodeExpr* const vp : values) VL_DO_DANGLING(pushDeletep(vp), vp); + values.clear(); + return values; + } + for (uint64_t v = lo; v <= hi; ++v) + values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{}, width, + static_cast(v)}); + } else { + values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); + } + } + return values; + } + + // Emit a 'this->m_cp.addSingleNamer/addArrayNamer(...)' statement for one bin + AstCStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count) { + FileLine* const fl = binp->fileline(); + AstCStmt* const cs = new AstCStmt{fl}; + cs->add(memberRef(fl, cpVarp)); + const std::string loc = "\"" + std::string{fl->filename()} + "\", " + + std::to_string(fl->lineno()) + ", " + + std::to_string(fl->firstColumn()) + ");"; + if (count < 0) { // single bin + cs->add(".addSingleNamer(" + std::string{binp->binsType().binSetEnum()} + ", \"" + + binp->name() + "\", " + loc); + } else { // value array bin + cs->add(".addArrayNamer(" + std::string{binp->binsType().binSetEnum()} + ", " + + std::to_string(count) + ", \"" + binp->name() + "\", " + loc); + } + return cs; + } + + // Emit 'if (iff && cond) m_cp.incrementBin(idx);' (or recordHit, + illegal action) in sample() + void emitConvHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, int idx, + AstNodeExpr* condp) { + FileLine* const fl = binp->fileline(); + AstCStmt* const hitp = new AstCStmt{fl}; + hitp->add(memberRef(fl, cpVarp)); + hitp->add((binp->binsType().binIsNormal() ? ".incrementBin(" : ".recordHit(") + + std::to_string(idx) + ");"); + AstNode* actionp = hitp; + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + actionp->addNext(makeIllegalBinAction(fl, "Illegal bin " + binp->prettyNameQ() + + " hit in coverpoint " + + coverpointp->prettyNameQ())); + } + AstNodeExpr* const guardedp = applyCoverpointIffCondition(coverpointp, fl, condp); + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set in converted coverpoint"); + m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr}); + } + + // Route an eligible coverpoint through a VlCoverpoint member: emit the member, its + // sample() increments, the constructor configuration (init + namers), and registration. + void generateConvertedCoverpoint(AstCoverpoint* coverpointp, AstNodeExpr* exprp, + int atLeastValue) { + FileLine* const fl = coverpointp->fileline(); + UINFO(4, " Converting coverpoint to VlCoverpoint: " << coverpointp->name()); + + if (!m_vlCoverpointDTypep) { + m_vlCoverpointDTypep = new AstCDType{fl, "VlCoverpoint"}; + v3Global.rootp()->typeTablep()->addTypesp(m_vlCoverpointDTypep); + } + AstVar* const cpVarp = new AstVar{fl, VVarType::MEMBER, "__Vcp_" + coverpointp->name(), + m_vlCoverpointDTypep}; + cpVarp->isStatic(false); + m_covergroupp->addMembersp(cpVarp); + m_convCpVars.push_back(cpVarp); + + // Walk bins (non-default, then default), assigning sequential indices that match the + // namer append order; emit sample increments and collect namer statements. + std::vector namerStmts; + std::vector defaultBins; + int idx = 0; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT) { + defaultBins.push_back(cbinp); + continue; + } + if (cbinp->isArray()) { // value array: bins b[N] = {...} -> b[0]..b[N-1] + bool unsupported = false; + std::vector values = extractArrayValues(cbinp, exprp, unsupported); + if (unsupported) continue; // bin ignored (COVERIGN emitted); reserve no slot + namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast(values.size()))); + for (AstNodeExpr* valuep : values) { + emitConvHitIf(coverpointp, cbinp, cpVarp, idx++, + new AstEq{cbinp->fileline(), exprp->cloneTree(false), valuep}); + } + } else { + namerStmts.push_back(makeNamer(cpVarp, cbinp, -1)); + // buildBinCondition is null for 'ignore_bins = default' (no ranges); the bin + // still gets a reserved slot (recorded, never incremented). + if (AstNodeExpr* const condp = buildBinCondition(cbinp, exprp)) + emitConvHitIf(coverpointp, cbinp, cpVarp, idx, condp); + ++idx; + } + } + for (AstCoverBin* const defBinp : defaultBins) { + namerStmts.push_back(makeNamer(cpVarp, defBinp, -1)); + emitConvHitIf(coverpointp, defBinp, cpVarp, idx++, + buildDefaultCondition(coverpointp, exprp, defBinp->fileline())); + } + + // Constructor: init (allocates), namers, then registration (under --coverage) + const std::string hier = m_covergroupp->name() + "." + coverpointp->name(); + AstCStmt* const initp = new AstCStmt{fl}; + initp->add(memberRef(fl, cpVarp)); + initp->add(".init(\"" + hier + "\", " + std::to_string(atLeastValue) + ", " + + std::to_string(idx) + ");"); + m_constructorp->addStmtsp(initp); + for (AstCStmt* const ns : namerStmts) m_constructorp->addStmtsp(ns); + if (v3Global.opt.coverage()) { + AstCStmt* const regp = new AstCStmt{fl}; + regp->add(memberRef(fl, cpVarp)); + regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"v_covergroup/" + + m_covergroupp->name() + "\");"); + m_constructorp->addStmtsp(regp); + } + } + // Generate matching code for default bins // Default bins match when value doesn't match any other explicit bin void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp, AstNodeExpr* exprp, AstVar* hitVarp) { UINFO(4, " Generating default bin match for: " << defBinp->name()); - // Build OR of all non-default, non-ignore bins - AstNodeExpr* anyBinMatchp = nullptr; - - for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { - AstCoverBin* const cbinp = VN_AS(binp, CoverBin); - - // Skip default, ignore, and illegal bins - if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT - || cbinp->binsType() == VCoverBinsType::BINS_IGNORE - || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - continue; - } - - // Build condition for this bin - AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); - UASSERT_OBJ(binCondp, cbinp, - "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); - - // OR with previous conditions - if (anyBinMatchp) { - anyBinMatchp = new AstOr{defBinp->fileline(), anyBinMatchp, binCondp}; - } else { - anyBinMatchp = binCondp; - } - } - - // Default matches when NO explicit bin matches - AstNodeExpr* defaultCondp = nullptr; - if (anyBinMatchp) { - // NOT (bin1 OR bin2 OR ... OR binN) - defaultCondp = new AstNot{defBinp->fileline(), anyBinMatchp}; - } else { - // No other bins - default always matches (shouldn't happen in practice) - defaultCondp = new AstConst{defBinp->fileline(), AstConst::BitTrue{}}; - } + AstNodeExpr* defaultCondp = buildDefaultCondition(coverpointp, exprp, defBinp->fileline()); // Apply iff condition if present if (AstNodeExpr* iffp = coverpointp->iffp()) { @@ -903,34 +1090,10 @@ class FunctionalCoverageVisitor final : public VNVisitor { int atLeastValue) { UINFO(4, " Generating array bins for: " << arrayBinp->name()); - // Extract all values from the range list - std::vector values; - for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { - if (AstInsideRange* const insideRangep = VN_CAST(rangep, InsideRange)) { - // For InsideRange [min:max], create bins for each value - AstNodeExpr* const minp = V3Const::constifyEdit(insideRangep->lhsp()); - AstNodeExpr* const maxp = V3Const::constifyEdit(insideRangep->rhsp()); - AstConst* const minConstp = VN_CAST(minp, Const); - AstConst* const maxConstp = VN_CAST(maxp, Const); - if (minConstp && maxConstp) { - const int minVal = minConstp->toSInt(); - const int maxVal = maxConstp->toSInt(); - UINFO(6, " Expanding InsideRange [" << minVal << ":" << maxVal << "]"); - for (int val = minVal; val <= maxVal; ++val) { - values.push_back(new AstConst{insideRangep->fileline(), - AstConst::WidthedValue{}, - (int)exprp->width(), (uint32_t)val}); - } - } else { - arrayBinp->v3error("Non-constant expression in array bins range; " - "range bounds must be constants"); - return; - } - } else { - // Single value - should be an expression - values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); - } - } + // Extract all values from the range list (resolves '$', caps/ignores huge ranges) + bool unsupported = false; + std::vector values = extractArrayValues(arrayBinp, exprp, unsupported); + if (unsupported) return; // bin ignored (COVERIGN emitted) // Create a separate bin for each value int index = 0; @@ -1320,15 +1483,50 @@ class FunctionalCoverageVisitor final : public VNVisitor { void generateCoverageMethodBody(AstFunc* funcp) { FileLine* const fl = funcp->fileline(); + AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Count total bins (excluding ignore_bins and illegal_bins) + // Converted coverpoints hold their bins in VlCoverpoint. Combine their contributions + // (via coverageParts) with any remaining legacy cross/cross-fed bins as the same flat + // covered/total ratio the all-legacy path below computes. Normal bins only: ignore, + // illegal, and default are excluded (LRM 19.5). + if (!m_convCpVars.empty()) { + AstCStmt* const headp = new AstCStmt{fl}; + headp->add("double __Vcov = 0.0; double __Vtot = 0.0;"); + funcp->addStmtsp(headp); + for (AstVar* const cpVarp : m_convCpVars) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("{ double __Vc = 0.0; double __Vt = 0.0; "); + cs->add(memberRef(fl, cpVarp)); + cs->add(".coverageParts(__Vc, __Vt); __Vcov += __Vc; __Vtot += __Vt; }"); + funcp->addStmtsp(cs); + } + int legacyRegular = 0; + for (const BinInfo& bi : m_binInfos) { + if (!bi.binp->binsType().binIsNormal()) continue; + ++legacyRegular; + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("if ("); + cs->add(memberRef(fl, bi.varp)); + cs->add(" >= " + std::to_string(bi.atLeast) + ") __Vcov += 1.0;"); + funcp->addStmtsp(cs); + } + if (legacyRegular) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("__Vtot += " + std::to_string(legacyRegular) + ".0;"); + funcp->addStmtsp(cs); + } + AstCStmt* const retp = new AstCStmt{fl}; + retp->add(new AstVarRef{fl, returnVarp, VAccess::WRITE}); + retp->add(" = (__Vtot != 0.0) ? (100.0 * __Vcov / __Vtot) : 100.0;"); + funcp->addStmtsp(retp); + return; + } + + // Count total bins (Normal only: excludes ignore/illegal/default) int totalBins = 0; for (const BinInfo& bi : m_binInfos) { UINFO(6, " Bin: " << bi.binp->name() << " type=" << bi.binp->binsType().ascii()); - if (bi.binp->binsType() != VCoverBinsType::BINS_IGNORE - && bi.binp->binsType() != VCoverBinsType::BINS_ILLEGAL) { - totalBins++; - } + if (bi.binp->binsType().binIsNormal()) totalBins++; } UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size()); @@ -1337,7 +1535,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { // No coverage to compute - return 100%. // Any parser-generated initialization of returnVar is overridden by our assignment. UINFO(4, " Empty covergroup, returning 100.0"); - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, new AstConst{fl, AstConst::RealDouble{}, 100.0}}); UINFO(4, " Added assignment to return 100.0"); @@ -1356,11 +1553,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // For each regular bin, if count > 0, increment covered_count for (const BinInfo& bi : m_binInfos) { - // Skip ignore_bins and illegal_bins in coverage calculation - if (bi.binp->binsType() == VCoverBinsType::BINS_IGNORE - || bi.binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - continue; - } + // Skip ignore/illegal/default bins in coverage calculation + if (!bi.binp->binsType().binIsNormal()) continue; // if (bin_count >= at_least) covered_count++; AstIf* ifp = new AstIf{ @@ -1375,9 +1569,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { funcp->addStmtsp(ifp); } - // Find the return variable - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Calculate coverage: (covered_count / total_bins) * 100.0 // return_var = (double)covered_count / (double)total_bins * 100.0 diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index 1a08da9e8..677f0b2aa 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -597,6 +597,7 @@ public: // We may have removed some datatypes, cleanup nodep->typeTablep()->repairCache(); VIsCached::clearCacheTree(); // Removing assignments may affect isPure + nodep->constPoolp()->reCache(); } ~DeadVisitor() override { V3Stats::addStatSum("Optimizations, deadified FTasks", m_statFTasksDeadified); diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index 1f6758629..f2ed71ebc 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -275,6 +275,7 @@ class DelayedVisitor final : public VNVisitor { bool m_inSuspendableOrFork = false; // True in suspendable processes and forks bool m_ignoreBlkAndNBlk = false; // Suppress delayed assignment BLKANDNBLK bool m_inNonCombLogic = false; // We are in non-combinational logic + bool m_needsInitialTrigger = false; // Whether a NodeProcedure needs a initial trigger AstVarRef* m_currNbaLhsRefp = nullptr; // Current NBA LHS variable reference // STATE - during NBA conversion (after visit) @@ -291,6 +292,7 @@ class DelayedVisitor final : public VNVisitor { VDouble0 m_nSchemeValueQueuesWhole; // Number of variables using Scheme::ValueQueueWhole VDouble0 m_nSchemeValueQueuesPartial; // Number of variables using Scheme::ValueQueuePartial VDouble0 m_nSharedSetFlags; // "Set" flags actually shared by Scheme::FlagShared variables + VDouble0 m_nInitialNBA; // Number of procedural blocks with initial NBA // METHODS @@ -999,6 +1001,12 @@ class DelayedVisitor final : public VNVisitor { m_writeRefs(nodep->varScopep()).emplace_back(nodep, nonBlocking, m_inNonCombLogic); } + template + static bool isProcedureWithSentreep(const AstNodeProcedure* const nodep) { + const Procedure_T* const procedurep = AstNode::cast(nodep); + return procedurep && procedurep->sentreep(); + } + // VISITORS void visit(AstNetlist* nodep) override { iterateChildren(nodep); @@ -1112,21 +1120,36 @@ class DelayedVisitor final : public VNVisitor { iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { + VL_RESTORER(m_needsInitialTrigger); const size_t firstNBAAddedIndex = m_nbas.size(); { VL_RESTORER(m_inSuspendableOrFork); VL_RESTORER(m_procp); VL_RESTORER(m_ignoreBlkAndNBlk); VL_RESTORER(m_inNonCombLogic); - m_inSuspendableOrFork = nodep->isSuspendable(); + // When we are dealing with initial block we need to + // treat it as suspendable when we meet a NBA + m_inSuspendableOrFork = nodep->isSuspendable() || VN_IS(nodep, Initial); m_procp = nodep; - if (m_inSuspendableOrFork) { + if (nodep->isSuspendable()) { m_ignoreBlkAndNBlk = false; m_inNonCombLogic = true; } iterateChildren(nodep); } - if (m_timingDomains.empty()) return; + auto containsClocled = [](const AstSenItem* itemp) { + while (itemp) { + if (itemp->edgeType().clockedStmt()) return true; + itemp = VN_AS(itemp->nextp(), SenItem); + } + return false; + }; + const bool addInitialTrigger = m_needsInitialTrigger + && !(isProcedureWithSentreep(nodep) + || isProcedureWithSentreep(nodep) + || isProcedureWithSentreep(nodep)) + && !containsClocled(m_activep->sentreep()->sensesp()); + if (m_timingDomains.empty() && !addInitialTrigger) return; // There were some timing domains involved in the process. Add all of them as sensitivities // of all NBA targets in this process. Note this is a bit of a sledgehammer, we should only @@ -1135,6 +1158,11 @@ class DelayedVisitor final : public VNVisitor { // First gather all senItems AstSenItem* senItemp = nullptr; + if (addInitialTrigger) { + senItemp = new AstSenItem{nodep->fileline(), AstSenItem::InitialNBA{}}; + ++m_nInitialNBA; + } + for (const AstSenTree* const domainp : m_timingDomains) { if (domainp->sensesp()) senItemp = AstNode::addNext(senItemp, domainp->sensesp()->cloneTree(true)); @@ -1218,6 +1246,8 @@ class DelayedVisitor final : public VNVisitor { UASSERT_OBJ(m_inSuspendableOrFork || m_activep->hasClocked(), nodep, "<= assignment in non-clocked block, should have been converted in V3Active"); + m_needsInitialTrigger |= m_timingDomains.empty(); + // Record scope of this NBA nodep->user2p(m_scopep); @@ -1327,6 +1357,7 @@ public: V3Stats::addStat("NBA, variables using ValueQueuePartial scheme", m_nSchemeValueQueuesPartial); V3Stats::addStat("Optimizations, NBA flags shared", m_nSharedSetFlags); + V3Stats::addStat("Procedures needing initial NBA trigger", m_nInitialNBA); } }; diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 637cf184f..fc6be54ce 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -96,6 +96,11 @@ std::unique_ptr DfgGraph::clone() const { vtxp2clonep.emplace(&vtx, cp); break; } // LCOV_EXCL_STOP + case VDfgType::MatchMasked: { + DfgMatchMasked* const cp = new DfgMatchMasked{*clonep, vtx.fileline(), vtx.dtype()}; + vtxp2clonep.emplace(&vtx, cp); + break; + } case VDfgType::Sel: { DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtype()}; cp->lsb(vtx.as()->lsb()); @@ -672,6 +677,15 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const { CHECK(v.dtype() == DfgDataType::select(v.srcp()->dtype(), v.lsb(), v.size()), "sel"); return; } + case VDfgType::MatchMasked: { + const DfgMatchMasked& v = *as(); + CHECK(v.isPacked(), "Should be Packed type"); + CHECK(v.size() == 32U, "Should yield a 32-bit result"); + CHECK(v.lhsp()->isPacked(), "Lhs should be packed"); + CHECK(v.matchp()->isPacked(), "Match should be Packed type"); + CHECK(v.matchp()->is(), "Match should be a variable"); + return; + } case VDfgType::Mux: { const DfgMux& v = *as(); CHECK(v.isPacked(), "Should be Packed type"); @@ -945,6 +959,9 @@ void DfgVertex::unlinkDelete(DfgGraph& dfg) { delete this; } +//###################################################################### +// Renders the canonical pattern S-expression for a single DfgVertex + class DfgPatternString final { std::ostream& m_os; diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 95320daad..b93c1c716 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -817,8 +817,11 @@ bool DfgVertex::isCheaperThanLoad() const { if (is()) return true; // Variables if (is()) return true; - // Array sels are just address computation - if (is()) return true; + // Array sels are just address computation, but the address itself can be expensive + if (const DfgArraySel* aselp = cast()) { + if (aselp->bitp()->is()) return false; + return true; + } // Small select from variable if (const DfgSel* const selp = cast()) { if (!selp->fromp()->is()) return false; diff --git a/src/V3DfgBreakCycles.cpp b/src/V3DfgBreakCycles.cpp index 73789cb9b..4bab8858d 100644 --- a/src/V3DfgBreakCycles.cpp +++ b/src/V3DfgBreakCycles.cpp @@ -31,8 +31,109 @@ VL_DEFINE_DEBUG_FUNCTIONS; namespace V3DfgBreakCycles { -// Throughout these algorithm, we use the DfgUserMap as a map to the SCC number -using Vtx2Scc = DfgUserMap; +// Mutable map from vertices to the SCC index they belong to. Initialized +// from a proper analysis, then updated as we break cycles. After updates +// the information is consertvative, meaning a vertex that is marked as +// cyclic might not actually be if an SCC split into multple SCCs after a +// fixup. However it should always be true that if a vertex is thought to +// be non-cyclic based on this datastructure, then it indeed is not cyclic. +class SccInfo final { + DfgGraph& m_dfg; // The annotated graph + // The map from vertices to their SCC index (0 if trivial SCC - non cyclic) + std::unordered_map m_vtx2Scc; + size_t m_nCyclicVertices = 0; // Number of vertices in non-trivial SCCs + +public: + SccInfo(DfgGraph& dfg) + : m_dfg{dfg} { + // Initialize m_vtx2Scc for the graph from a proper analysis + DfgUserMap vtx2Scc = dfg.makeUserMap(); + V3DfgPasses::colorStronglyConnectedComponents(dfg, vtx2Scc); + m_dfg.forEachVertex([&](const DfgVertex& vtx) { + const uint64_t sccIdx = vtx2Scc[vtx]; + m_vtx2Scc[&vtx] = sccIdx; + if (sccIdx) ++m_nCyclicVertices; + }); + } + + // Is the graph cyclic still? + bool isCyclic() const { return m_nCyclicVertices; } + + // Returns the index of the SCC the given vertex is a part of + // This is 0 iff the vertex is in a trivial SCC (no cycles through vertex) + uint64_t get(const DfgVertex& vtx) const { return m_vtx2Scc.at(&vtx); } + + // Add a new vertex to graph that is part of the given SCC + void add(const DfgVertex& vtx, uint64_t sccIdx) { + const bool newEntry = m_vtx2Scc.emplace(&vtx, sccIdx).second; + UASSERT_OBJ(newEntry, &vtx, "Vertex already inserted"); + if (sccIdx) ++m_nCyclicVertices; + } + + bool stillCyclicFwd(const DfgVertex& vtx) const { + // If it only reads non-cyclic vertices, it has also become acyclic + return vtx.foreachSource([&](const DfgVertex& src) { // + return m_vtx2Scc.at(&src); + }); + } + + bool stillCyclicBwd(const DfgVertex& vtx) const { + // If itonly drives non-cyclic vertices, it has also become acyclic + return vtx.foreachSink([&](const DfgVertex& dst) { // + return m_vtx2Scc.at(&dst); + }); + } + + // The given vertex is known to have become acyclic after some edits, + // propagate through graph to mark connected vertices as well. + void updateAcyclic(const DfgVertex& vtx) { + // Mark as acyclic + uint64_t& sccIdxr = m_vtx2Scc.at(&vtx); + UASSERT_OBJ(sccIdxr, &vtx, "Vertex should be cyclic"); + sccIdxr = 0; + --m_nCyclicVertices; + // Propagate the update through the graph forward + vtx.foreachSink([&](const DfgVertex& dst) { + // Sink was known to be acyclic, stop + if (!m_vtx2Scc.at(&dst)) return false; + if (!stillCyclicFwd(dst)) updateAcyclic(dst); + return false; + }); + // Propagate the update through the graph backward + vtx.foreachSource([&](const DfgVertex& src) { + // Source was known to be acyclic, stop + if (!m_vtx2Scc.at(&src)) return false; + if (!stillCyclicBwd(src)) updateAcyclic(src); + return false; + }); + } + + /* + Check stored information is consistent with actual SCCs. Note we + can't detect during updates if an SCC has split into multiple SCCs. + Consider: + A -- E -- G + / \ / \ + B C H I + \ / \ / + D -- F -- J + If we fixed up E, the original SCC would split into two, (A,B,C,D) + and (G,H,I,J), but also F would no longer be cyclic. For this reason, + we can only assert that anything that we think is not cyclic based + on the SccInfo must indeed be not cyclic, but might still think a + vertex is cyclic based on the SccInfo but it actually isn't. + */ + void validate() const { + // Re-compute the actual SCCs + DfgUserMap actual = m_dfg.makeUserMap(); + V3DfgPasses::colorStronglyConnectedComponents(m_dfg, actual); + // Assert that if we think a vertex is not cyclic, it indeed is not + m_dfg.forEachVertex([&](const DfgVertex& vtx) { + // 'think not cyclic' implies 'actually not cyclic' + UASSERT_OBJ(m_vtx2Scc.at(&vtx) || !actual.at(vtx), &vtx, "Inconsisten SccInfo"); + }); + } +}; class TraceDriver final : public DfgVisitor { // TYPES @@ -67,7 +168,7 @@ class TraceDriver final : public DfgVisitor { // STATE DfgGraph& m_dfg; // The graph being processed - Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map + SccInfo& m_sccInfo; // The SccInfo instance // The strongly connected component we are currently trying to escape uint64_t m_component = 0; uint32_t m_lsb = 0; // LSB to extract from the currently visited Vertex @@ -86,11 +187,11 @@ class TraceDriver final : public DfgVisitor { // Create and return a new Vertex. Always use this to create new vertices. // Fileline is taken from 'refp', but 'refp' is otherwise not used. - // Also sets m_vtx2Scc[vtx] to 0, indicating the new vertex is not part of - // a strongly connected component. This should always be true, as all the - // vertices we create here are driven from outside the component we are - // trying to escape, and will sink into that component. Given those are - // separate SCCs, these new vertices must be acyclic. + // Also adds the vertex to m_sccInfo with scc index 0, indicating the new + // vertex is not part of a strongly connected component. This should always + // be true, as all the vertices we create here are driven from outside the + // component we are trying to escape, and will sink into that component. + // Given those are separate SCCs, these new vertices must be acyclic. template Vertex* make(const DfgVertex* refp, uint32_t width) { static_assert(std::is_base_of::value // @@ -99,7 +200,7 @@ class TraceDriver final : public DfgVisitor { if VL_CONSTEXPR_CXX17 (std::is_same::value) { DfgConst* const vtxp = new DfgConst{m_dfg, refp->fileline(), width, 0}; - m_vtx2Scc[vtxp] = 0; + m_sccInfo.add(*vtxp, 0); return reinterpret_cast(vtxp); } else { // TODO: this is a gross hack around lack of C++17 'if constexpr' Vtx is always Vertex @@ -108,7 +209,7 @@ class TraceDriver final : public DfgVisitor { using Vtx = typename std::conditional::value, DfgSel, Vertex>::type; Vtx* const vtxp = new Vtx{m_dfg, refp->fileline(), DfgDataType::packed(width)}; - m_vtx2Scc[vtxp] = 0; + m_sccInfo.add(*vtxp, 0); return reinterpret_cast(vtxp); } } @@ -123,7 +224,7 @@ class TraceDriver final : public DfgVisitor { DfgVertexVar* const varp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep); varp->vscp()->varp()->isInternal(true); varp->tmpForp(varp->vscp()); - m_vtx2Scc[varp] = 0; + m_sccInfo.add(*varp, 0); return varp; } @@ -146,7 +247,7 @@ class TraceDriver final : public DfgVisitor { // If already traced this vtxp/msb/lsb, just use the result. // This is important to avoid combinatorial explosion when the // same sub-expression is needed multiple times. - } else if (m_vtx2Scc.at(vtxp) != m_component) { + } else if (m_sccInfo.get(*vtxp) != m_component) { // If the currently traced vertex is in a different component, // then we found what we were looking for. respr = vtxp; @@ -332,12 +433,25 @@ class TraceDriver final : public DfgVisitor { } void visit(DfgArraySel* vtxp) override { - // Only constant select - const DfgConst* const idxp = vtxp->bitp()->cast(); - if (!idxp) return; // From a variable const DfgVarArray* varp = vtxp->fromp()->cast(); if (!varp) return; + + // 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); + 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()) { @@ -589,13 +703,23 @@ class TraceDriver final : public DfgVisitor { SET_RESULT(resp); } + void visit(DfgMatchMasked* vtxp) override { + DfgMatchMasked* const resp = make(vtxp, vtxp->width()); + resp->lhsp(trace(vtxp->lhsp(), vtxp->lhsp()->width() - 1, 0)); + resp->matchp(trace(vtxp->matchp(), vtxp->matchp()->width() - 1, 0)); + DfgSel* const selp = make(vtxp, m_msb - m_lsb + 1); + selp->fromp(resp); + selp->lsb(m_lsb); + SET_RESULT(resp); + } + #undef SET_RESULT public: // CONSTRUCTOR - TraceDriver(DfgGraph& dfg, Vtx2Scc& vtx2Scc) + TraceDriver(DfgGraph& dfg, SccInfo& sccInfo) : m_dfg{dfg} - , m_vtx2Scc{vtx2Scc} { + , m_sccInfo{sccInfo} { #ifdef VL_DEBUG if (v3Global.opt.debugCheck()) { m_lineCoverageFile.open( // @@ -612,7 +736,7 @@ public: // trace can always succeed. DfgVertex* apply(DfgVertex& vtx, uint32_t lsb, uint32_t width) { VL_RESTORER(m_component); - m_component = m_vtx2Scc.at(&vtx); + m_component = m_sccInfo.get(vtx); return trace(&vtx, lsb + width - 1, lsb); } }; @@ -625,7 +749,7 @@ class IndependentBits final : public DfgVisitor { }; // STATE - const Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map + const SccInfo& m_sccInfo; // The SccInfo instance // Vertex to current bit mask map. The mask is set for the bits that are independent of the SCC std::unordered_map m_vtxp2Mask; // Work list for the traversal (min-queue of vertex RPO numbers) @@ -721,12 +845,23 @@ class IndependentBits final : public DfgVisitor { } void visit(DfgArraySel* vtxp) override { - // Only constant select - const DfgConst* const idxp = vtxp->bitp()->cast(); - if (!idxp) return; // From a variable const DfgVarArray* varp = vtxp->fromp()->cast(); if (!varp) return; + + // 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(); + } + 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 @@ -954,13 +1089,19 @@ class IndependentBits final : public DfgVisitor { } } + void visit(DfgMatchMasked* vtxp) override { + if (MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->matchp()).isEqAllOnes()) { // + MASK(vtxp).setAllBits1(); + } + } + #undef MASK // Enqueue sinks of vertex to work list for traversal - only called from constructor void enqueueSinks(DfgVertex& vtx) { vtx.foreachSink([&](DfgVertex& sink) { // Ignore if sink is not part of an SCC, already has all bits marked independent - if (!m_vtx2Scc.at(sink)) return false; + if (!m_sccInfo.get(sink)) return false; // If a vertex is not handled directly, recursively enqueue its sinks instead if (!handledDirectly(sink)) { enqueueSinks(sink); @@ -991,8 +1132,8 @@ class IndependentBits final : public DfgVisitor { postOrderEnumeration.emplace_back(&vtx); }; - IndependentBits(DfgGraph& dfg, const Vtx2Scc& vtx2Scc) - : m_vtx2Scc{vtx2Scc} { + IndependentBits(DfgGraph& dfg, const SccInfo& sccInfo) + : m_sccInfo{sccInfo} { #ifdef VL_DEBUG if (v3Global.opt.debugCheck()) { @@ -1029,12 +1170,12 @@ class IndependentBits final : public DfgVisitor { // Enqueue sinks of all SCC vertices that have at least one independent bit for (DfgVertex* const vtxp : rpoEnumeration) { if (!handledDirectly(*vtxp)) continue; - if (m_vtx2Scc.at(vtxp)) continue; + if (m_sccInfo.get(*vtxp)) continue; mask(*vtxp).setAllBits1(); } for (DfgVertex* const vtxp : rpoEnumeration) { if (!handledDirectly(*vtxp)) continue; - if (!m_vtx2Scc.at(vtxp)) continue; + if (!m_sccInfo.get(*vtxp)) continue; iterate(vtxp); UINFO(9, "Initial independent bits of " << vtxp << " " << vtxp->typeName() @@ -1049,7 +1190,7 @@ class IndependentBits final : public DfgVisitor { m_workQueue.pop(); m_vtxp2State.at(currp).m_isOnWorkList = false; // Should not enqueue vertices that are not in an SCC - UASSERT_OBJ(m_vtx2Scc.at(currp), currp, "Vertex should be in an SCC"); + 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"); @@ -1088,18 +1229,18 @@ public: // set if the corresponding bit in that vertex is known to be independent // of the values of vertices in the same SCC as the vertex resides in. static std::unordered_map apply(DfgGraph& dfg, - const Vtx2Scc& vtx2Scc) { - return std::move(IndependentBits{dfg, vtx2Scc}.m_vtxp2Mask); + const SccInfo& sccInfo) { + return std::move(IndependentBits{dfg, sccInfo}.m_vtxp2Mask); } }; class FixUp final { DfgGraph& m_dfg; // The graph being processed - Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map - TraceDriver m_traceDriver{m_dfg, m_vtx2Scc}; + SccInfo& m_sccInfo; // The SccInfo instance + TraceDriver m_traceDriver{m_dfg, m_sccInfo}; // The independent bits map const std::unordered_map m_independentBits - = IndependentBits::apply(m_dfg, m_vtx2Scc); + = IndependentBits::apply(m_dfg, m_sccInfo); size_t m_nImprovements = 0; // Number of improvements mde // Returns a bitmask set if that bit of 'vtx' is used (has a sink) @@ -1149,7 +1290,7 @@ class FixUp final { // If dependent, fall back on using the part of the variable DfgSel* const selp = new DfgSel{m_dfg, vtx.fileline(), DfgDataType::packed(width)}; // Same component as 'vtxp', as reads 'vtxp' and will replace 'vtxp' - m_vtx2Scc[selp] = m_vtx2Scc.at(vtx); + m_sccInfo.add(*selp, m_sccInfo.get(vtx)); // Do not connect selp->fromp yet, need to do afer replacing 'vtxp' selp->lsb(lsb); termps.emplace_back(selp); @@ -1198,7 +1339,7 @@ class FixUp final { } else { // The range is not used, just use constant 0 as a placeholder DfgConst* const constp = new DfgConst{m_dfg, flp, msb - lsb + 1, 0}; - m_vtx2Scc[constp] = 0; + m_sccInfo.add(*constp, 0); termps.emplace_back(constp); } // Next iteration @@ -1208,7 +1349,7 @@ class FixUp final { // Concatenate all the terms to create the replacement DfgVertex* replacementp = termps.front(); - const uint64_t vComp = m_vtx2Scc.at(vtx); + const uint64_t vComp = m_sccInfo.get(vtx); for (size_t i = 1; i < termps.size(); ++i) { DfgVertex* const termp = termps[i]; const uint32_t catWidth = replacementp->width() + termp->width(); @@ -1220,9 +1361,9 @@ class FixUp final { // component, it's part of that component, otherwise its not // cyclic (all terms are from outside the original component, // and feed into the original component). - const uint64_t tComp = m_vtx2Scc.at(termp); - const uint64_t rComp = m_vtx2Scc.at(replacementp); - m_vtx2Scc[catp] = tComp == vComp || rComp == vComp ? vComp : 0; + const uint64_t tComp = m_sccInfo.get(*termp); + const uint64_t rComp = m_sccInfo.get(*replacementp); + m_sccInfo.add(*catp, tComp == vComp || rComp == vComp ? vComp : 0); replacementp = catp; } @@ -1234,6 +1375,22 @@ class FixUp final { if (!selp->fromp()) selp->fromp(&vtx); } } + + // If the vertex still has sinks, then the replacement is still cyclic, and vice versa + UASSERT_OBJ(!vtx.hasSinks() == !m_sccInfo.get(*replacementp), &vtx, + "Replacement vertex SCC inconsistent"); + + // If we broke the cycle through this vertex we can update the SccInfo + if (!vtx.hasSinks()) { + m_sccInfo.updateAcyclic(vtx); + if (!m_sccInfo.get(*replacementp)) { + replacementp->foreachSink([&](DfgVertex& dst) { + if (!m_sccInfo.get(dst)) return false; + if (!m_sccInfo.stillCyclicFwd(dst)) m_sccInfo.updateAcyclic(dst); + return false; + }); + } + } } void main(DfgVertexVar& var) { @@ -1244,10 +1401,10 @@ class FixUp final { fixUpPacked(var); } else if (var.is()) { // For array variables, fix up element-wise - const uint64_t component = m_vtx2Scc.at(var); + const uint64_t component = m_sccInfo.get(var); var.foreachSink([&](DfgVertex& sink) { // Ignore if sink is not part of same cycle - if (m_vtx2Scc.at(sink) != component) return false; + if (m_sccInfo.get(sink) != component) return false; // Only handle ArraySels with constant index DfgArraySel* const aselp = sink.cast(); if (!aselp) return false; @@ -1261,23 +1418,27 @@ class FixUp final { UINFO(9, "FixUp made " << m_nImprovements << " improvements"); } - FixUp(DfgGraph& dfg, Vtx2Scc& vtx2Scc) + FixUp(DfgGraph& dfg, SccInfo& sccInfo) : m_dfg{dfg} - , m_vtx2Scc{vtx2Scc} {} + , m_sccInfo{sccInfo} {} public: // Compute which bits of vertices are independent of the SCC they reside // in, and replace rferences to used independent bits with an equivalent // vertex that is not part of the same SCC. - static size_t apply(DfgGraph& dfg, Vtx2Scc& vtx2Scc) { - FixUp fixUp{dfg, vtx2Scc}; + static size_t apply(DfgGraph& dfg, SccInfo& sccInfo) { + if (!sccInfo.isCyclic()) return 0; + + FixUp fixUp{dfg, sccInfo}; // TODO: Compute minimal feedback vertex set and cut only those. // Sadly that is a computationally hard problem. - // Fix up cyclic variables + // Fix up cyclic variables, stop as soon as the graph becomes acyclic for (DfgVertexVar& vtx : dfg.varVertices()) { - if (vtx2Scc.at(vtx)) fixUp.main(vtx); + if (!sccInfo.get(vtx)) continue; + fixUp.main(vtx); + if (!sccInfo.isCyclic()) break; } // Return the number of improvements made return fixUp.m_nImprovements; @@ -1319,35 +1480,35 @@ breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) { // Iterate while an improvement can be made and the graph is still cyclic do { - // Color SCCs (populates DfgVertex::user()) - Vtx2Scc vtx2Scc = res.makeUserMap(); - const uint32_t numNonTrivialSCCs - = V3DfgPasses::colorStronglyConnectedComponents(res, vtx2Scc); - - // Congrats if it has become acyclic - if (!numNonTrivialSCCs) { - UINFO(7, "Graph became acyclic after " << nImprovements << " improvements"); - dump(7, res, "result-acyclic"); - ++ctx.m_breakCyclesContext.m_nFixed; - return {std::move(resultp), true}; - } + // Compute SCCs + SccInfo sccInfo{res}; // Fix up independent ranges in vertices UINFO(9, "New iteration after " << nImprovements << " improvements"); prevNImprovements = nImprovements; - const size_t nFixed = FixUp::apply(res, vtx2Scc); + const size_t nFixed = FixUp::apply(res, sccInfo); if (nFixed) { nImprovements += nFixed; ctx.m_breakCyclesContext.m_nImprovements += nFixed; dump(9, res, "FixUp"); } + + // Validate SccInfo if in debug mode + if (v3Global.opt.debugCheck()) sccInfo.validate(); + + // Congrats if it has become acyclic + if (!sccInfo.isCyclic()) { + UINFO(7, "Graph became acyclic after " << nImprovements << " improvements"); + dump(7, res, "result-acyclic"); + ++ctx.m_breakCyclesContext.m_nFixed; + return {std::move(resultp), true}; + } } while (nImprovements != prevNImprovements); if (dumpDfgLevel() >= 9) { - Vtx2Scc vtx2Scc = res.makeUserMap(); - V3DfgPasses::colorStronglyConnectedComponents(res, vtx2Scc); + const SccInfo sccInfo{res}; res.dumpDotFilePrefixed("breakCycles-remaining", [&](const DfgVertex& vtx) { - return vtx2Scc[vtx]; // + return sccInfo.get(vtx); // }); } diff --git a/src/V3DfgContext.h b/src/V3DfgContext.h index dc1b54666..21e7af31a 100644 --- a/src/V3DfgContext.h +++ b/src/V3DfgContext.h @@ -234,6 +234,26 @@ private: addStat("temporaries introduced", m_temporariesIntroduced); } }; +class V3DfgRemoveSelectsContext final : public V3DfgSubContext { + // Only V3DfgContext can create an instance + friend class V3DfgContext; + +public: + // STATE + VDouble0 m_removedFullWidth; // Number of full width selects removed + VDouble0 m_replacedWithSelFromFull; // Number of selects replaced with sel from full driver + VDouble0 m_replacedWithSelFromPart; // Number of selects replaced with sel from partial driver + VDouble0 m_replacedWithPart; // Number of selects replaced with part of driver +private: + V3DfgRemoveSelectsContext() + : V3DfgSubContext{"RemoveSelects"} {} + ~V3DfgRemoveSelectsContext() { + addStat("full width selects removed", m_removedFullWidth); + addStat("replaced with sel from full driver", m_replacedWithSelFromFull); + addStat("replaced with sel from partial driver", m_replacedWithSelFromPart); + addStat("replaced with partial driver", m_replacedWithPart); + } +}; class V3DfgRemoveUnobservableContext final : public V3DfgSubContext { // Only V3DfgContext can create an instance friend class V3DfgContext; @@ -399,6 +419,7 @@ public: V3DfgPeepholeContext m_peepholeContext; V3DfgPushDownSelsContext m_pushDownSelsContext; V3DfgRegularizeContext m_regularizeContext; + V3DfgRemoveSelectsContext m_removeSelectsContext; V3DfgRemoveUnobservableContext m_removeUnobservableContext; V3DfgSynthesisContext m_synthContext; diff --git a/src/V3DfgCse.cpp b/src/V3DfgCse.cpp index 7a60b4be7..c1b3862e4 100644 --- a/src/V3DfgCse.cpp +++ b/src/V3DfgCse.cpp @@ -72,6 +72,7 @@ class V3DfgCse final { } // Vertices with no internal information + case VDfgType::MatchMasked: case VDfgType::Mux: case VDfgType::UnitArray: return V3Hash{}; @@ -197,6 +198,7 @@ class V3DfgCse final { } // Vertices with no internal information + case VDfgType::MatchMasked: case VDfgType::Mux: case VDfgType::UnitArray: return true; diff --git a/src/V3DfgDataType.h b/src/V3DfgDataType.h index 5af232b84..3011d254f 100644 --- a/src/V3DfgDataType.h +++ b/src/V3DfgDataType.h @@ -139,6 +139,7 @@ public: // Returns a Packed type of the given width static const DfgDataType& packed(uint32_t width) { + UASSERT(width > 0, "Width must be positive"); // Find or create the right sized packed type const DfgDataType*& entryr = s_packedTypes[width]; if (!entryr) entryr = new DfgDataType{width}; diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index ca349ccb3..b2af96461 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -234,6 +234,12 @@ class DfgToAstVisitor final : DfgVisitor { AstVar* const varp = sinkp->as()->vscp()->varp(); m_resultp = new AstCReset{vtxp->fileline(), varp, false}; } + void visit(DfgMatchMasked* vtxp) override { + FileLine* const flp = vtxp->fileline(); + AstNodeExpr* const lhsp = convertDfgVertexToAstNodeExpr(vtxp->lhsp()); + AstVarScope* const matchp = vtxp->matchp()->as()->vscp(); + m_resultp = new AstMatchMasked{flp, lhsp, matchp}; + } void visit(DfgRep* vtxp) override { FileLine* const flp = vtxp->fileline(); diff --git a/src/V3DfgDumpPatterns.cpp b/src/V3DfgDumpPatterns.cpp deleted file mode 100644 index 18611f180..000000000 --- a/src/V3DfgDumpPatterns.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementations of simple passes over DfgGraph -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#ifndef VERILATOR_V3DFGPATTERNSTATS_H_ -#define VERILATOR_V3DFGPATTERNSTATS_H_ - -#include "V3Dfg.h" -#include "V3DfgPasses.h" -#include "V3File.h" - -#include - -class V3DfgPatternStats final { - static constexpr uint32_t MIN_PATTERN_DEPTH = 1; - static constexpr uint32_t MAX_PATTERN_DEPTH = 4; - - // Maps from pattern to the number of times it appears, for each pattern depth - std::vector> m_patterCounts{MAX_PATTERN_DEPTH + 1}; - - void dump(std::ostream& os) { - using Line = std::pair; - for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { - os << "DFG patterns with depth " << i << '\n'; - - // Pick up pattern accumulators with given depth - auto& patternCounts = m_patterCounts[i]; - - // Remove patterns also present at shallower depths - for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) { - for (const auto& pair : m_patterCounts[j]) patternCounts.erase(pair.first); - } - - // Sort patterns, first by descending frequency, then lexically - std::vector lines; - lines.reserve(patternCounts.size()); - for (const auto& pair : patternCounts) lines.emplace_back(pair); - std::sort(lines.begin(), lines.end(), [](const Line& a, const Line& b) { - if (a.second != b.second) return a.second > b.second; - return a.first < b.first; - }); - - // Print each pattern - for (const auto& line : lines) { - os << ' ' << std::setw(12) << std::right << line.second; - os << ' ' << std::left << line.first << '\n'; - } - - // Trailing new-line to separate sections - os << '\n'; - } - } - -public: - V3DfgPatternStats() = default; - ~V3DfgPatternStats() { - // File to dump to - const std::string filename - = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + "__dfg_patterns.txt"; - // Open, write, close - const std::unique_ptr ofp{V3File::new_ofstream(filename)}; - if (ofp->fail()) v3fatal("Can't write file: " << filename); - dump(*ofp); - } - - void accumulate(const DfgGraph& dfg) { - dfg.forEachVertex([&](const DfgVertex& vtx) { - for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { - m_patterCounts[i][vtx.patternString(i)] += 1; - } - }); - } -}; - -void V3DfgPasses::dumpPatterns(const std::vector>& graphs) { - V3DfgPatternStats patternStats; - for (auto& cp : graphs) patternStats.accumulate(*cp); -} - -#endif diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index b2fa0f9e8..07002049d 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -78,9 +78,15 @@ class DataflowOptimize final { if (AstVarScope* const vscp = VN_CAST(nodep, VarScope)) { const AstVar* const varp = vscp->varp(); // Force and trace have already been processed - const bool hasExtRd = varp->isPrimaryIO() || varp->isSigUserRdPublic(); - const bool hasExtWr - = (varp->isPrimaryIO() && varp->isNonOutput()) || varp->isSigUserRWPublic(); + const bool hasExtRd = // + varp->isPrimaryIO() // Top level port - readable + || varp->isSigUserRdPublic() // Readable by user + || varp->constPoolEntry() // Stored in AstConstPool hashmap, but read only + ; + const bool hasExtWr = // + (varp->isPrimaryIO() && varp->isNonOutput()) // Top level port - writable + || varp->isSigUserRWPublic() // Writable by user + ; if (hasExtRd) DfgVertexVar::setHasExtRdRefs(vscp); if (hasExtWr) DfgVertexVar::setHasExtWrRefs(vscp); return; @@ -139,6 +145,13 @@ class DataflowOptimize final { dfg.mergeGraphs(std::move(madeAcyclicComponents)); endOfStage("breakCycles", dfg, cyclicComps); + // Remove redundant selects + V3DfgPasses::removeSelects(dfg, m_ctx.m_removeSelectsContext); + for (std::unique_ptr& compp : cyclicComps) { + V3DfgPasses::removeSelects(*compp, m_ctx.m_removeSelectsContext); + } + endOfStage("removeSelects", dfg, cyclicComps); + // Split the acyclic DFG into [weakly] connected components std::vector> acyclicComps = dfg.splitIntoComponents("acyclic"); UASSERT(dfg.size() == 0, "DfgGraph should have become empty"); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 3e14397c6..afca00589 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -108,6 +108,78 @@ void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) { } } +void V3DfgPasses::removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) { + + std::vector selps; + for (DfgVertex& vtx : dfg.opVertices()) { + DfgSel* const selp = vtx.cast(); + if (!selp) continue; + selps.push_back(selp); + } + + for (DfgSel* const selp : selps) { + FileLine* const flp = selp->fileline(); + const DfgDataType& dtype = selp->dtype(); + + // Remove full width selects + if (selp->fromp()->dtype() == dtype) { + ++ctx.m_removedFullWidth; + selp->replaceWith(selp->fromp()); + continue; + } + + // Push selects through synthesis temporaries only + DfgVarPacked* const varp = selp->fromp()->cast(); + if (!varp || !varp->tmpForp()) continue; + DfgVertex* const srcp = varp->srcp(); + if (!srcp) continue; + // Don't inline CReset + if (srcp->is()) continue; + + const uint32_t lsb = selp->lsb(); + const uint32_t msb = lsb + selp->width() - 1; + + // If driven whole, select from the driver + if (!srcp->is()) { + ++ctx.m_replacedWithSelFromFull; + DfgSel* const newSelp = new DfgSel{dfg, flp, dtype}; + newSelp->lsb(lsb); + newSelp->fromp(srcp); + selp->replaceWith(newSelp); + continue; + } + + // Otherwise attemt to select from the partial driver + DfgSplicePacked* const splicep = srcp->as(); + DfgVertex* driverp = nullptr; + uint32_t driverLsb = 0; + splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLsb) { + const uint32_t dMsb = dLsb + src.width() - 1; + // If it does not cover the whole searched bit range, move on + if (lsb < dLsb || dMsb < msb) return false; + // Save the driver + driverp = &src; + driverLsb = dLsb; + return true; + }); + if (!driverp) continue; + + // If partial driver is the whole thing we are looking for, just replace with the driver + if (driverp->dtype() == dtype) { + ++ctx.m_replacedWithPart; + selp->replaceWith(driverp); + continue; + } + + // Otherwise create a new select from the partial driver + ++ctx.m_replacedWithSelFromPart; + DfgSel* const newSelp = new DfgSel{dfg, flp, dtype}; + newSelp->lsb(lsb - driverLsb); + newSelp->fromp(driverp); + selp->replaceWith(newSelp); + } +} + void V3DfgPasses::inlineVars(DfgGraph& dfg) { for (DfgVertexVar& vtx : dfg.varVertices()) { // Nothing to inline it into diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index a7d2b04fb..d0d8e62d8 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -41,6 +41,8 @@ void removeUnobservable(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; // Synthesize DfgLogic vertices into primitive operations. // Removes all DfgLogic (even those that were not synthesized). void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; +// Remove redundant selects +void removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) VL_MT_DISABLED; // Attempt to make the given cyclic graph into an acyclic, or "less cyclic" // equivalent. If the returned pointer is null, then no improvement was // possible on the input graph. Otherwise the returned graph is an improvement @@ -65,7 +67,8 @@ void regularize(DfgGraph&, V3DfgRegularizeContext&) VL_MT_DISABLED; // Convert DfgGraph back into Ast, and insert converted graph back into the Ast. void dfgToAst(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; // Dump the patterns in the given graphs -void dumpPatterns(const std::vector>&) VL_MT_DISABLED; +void dumpPatterns(const std::vector>&, + const std::string& suffix = "") VL_MT_DISABLED; //=========================================================================== // Intermediate/internal operations diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index f89285eb7..de92790a8 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1188,6 +1188,102 @@ class V3DfgPeephole final : public DfgVisitor { return {fromp, lsb}; } + // Given a pair of vertices, returns a vertex representing the common LSBs of the two, + // and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found. + std::pair commonLSBs(DfgVertex* ap, DfgVertex* bp) { + if (ap == bp) return {ap, ap->width()}; + + // If both constants, check LSBs + if (DfgConst* const aConstp = ap->cast()) { + if (DfgConst* const bConstp = bp->cast()) { + const V3Number& aNum = aConstp->num(); + const V3Number& bNum = bConstp->num(); + // Max match is the shorter constant + const uint32_t maxMatch = std::min(aConstp->width(), bConstp->width()); + // Check all bits + uint32_t matchWidth = 0; + for (; matchWidth < maxMatch; ++matchWidth) { + if (aNum.bitIs0(matchWidth) != bNum.bitIs0(matchWidth)) break; + } + // Will always return the shorter constant in case it can be used directly + DfgConst* const shorterp = aConstp->width() < bConstp->width() ? aConstp : bConstp; + return {matchWidth ? shorterp : nullptr, matchWidth}; + } + } + + // If Concat, check against the RHS + if (DfgConcat* const catp = ap->cast()) return commonLSBs(catp->rhsp(), bp); + if (DfgConcat* const catp = bp->cast()) return commonLSBs(ap, catp->rhsp()); + + // If selecting the LSBs, check against the source of the Sel + if (DfgSel* const selp = ap->cast()) { + if (selp->lsb() == 0) { + DfgVertex* const fromp = selp->fromp(); + const std::pair common = commonLSBs(fromp, bp); + return {common.first, std::min(common.second, selp->width())}; + } + } + if (DfgSel* const selp = bp->cast()) { + if (selp->lsb() == 0) { + DfgVertex* const fromp = selp->fromp(); + const std::pair common = commonLSBs(ap, fromp); + return {common.first, std::min(common.second, selp->width())}; + } + } + + // Otherwise no common LSBs + return {nullptr, 0}; + } + + // Given a pair of vertices, returns a vertex representing the common MSBs of the two, + // and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found. + std::pair commonMSBs(DfgVertex* ap, DfgVertex* bp) { + if (ap == bp) return {ap, ap->width()}; + + // If both constants, check MSBs + if (DfgConst* const aConstp = ap->cast()) { + if (DfgConst* const bConstp = bp->cast()) { + const uint32_t aMsb = aConstp->width() - 1; + const uint32_t bMsb = bConstp->width() - 1; + const V3Number& aNum = aConstp->num(); + const V3Number& bNum = bConstp->num(); + // Max match is the shorter constant + const uint32_t maxMatch = std::min(aConstp->width(), bConstp->width()); + // Check all bits + uint32_t matchWidth = 0; + for (; matchWidth < maxMatch; ++matchWidth) { + if (aNum.bitIs0(aMsb - matchWidth) != bNum.bitIs0(bMsb - matchWidth)) break; + } + // Will always return the shorter constant in case it can be used directly + DfgConst* const shorterp = aMsb < bMsb ? aConstp : bConstp; + return {matchWidth ? shorterp : nullptr, matchWidth}; + } + } + + // If Concat, check against the LHS + if (DfgConcat* const catp = ap->cast()) return commonMSBs(catp->lhsp(), bp); + if (DfgConcat* const catp = bp->cast()) return commonMSBs(ap, catp->lhsp()); + + // If selecting the MSBs, check against the source of the Sel + if (DfgSel* const selp = ap->cast()) { + DfgVertex* const fromp = selp->fromp(); + if (selp->msb() == fromp->width() - 1) { + const std::pair common = commonMSBs(fromp, bp); + return {common.first, std::min(common.second, selp->width())}; + } + } + if (DfgSel* const selp = bp->cast()) { + DfgVertex* const fromp = selp->fromp(); + if (selp->msb() == fromp->width() - 1) { + const std::pair common = commonMSBs(ap, fromp); + return {common.first, std::min(common.second, selp->width())}; + } + } + + // Otherwise no common MSBs + return {nullptr, 0}; + } + // VISIT methods void visit(DfgVertex*) override {} @@ -1789,10 +1885,24 @@ class V3DfgPeephole final : public DfgVisitor { if (!idxp) return; DfgVarArray* const varp = vtxp->fromp()->cast(); if (!varp) return; - if (varp->vscp()->varp()->isForced()) return; - if (varp->vscp()->varp()->isSigUserRWPublic()) return; + AstVar* const astVarp = varp->vscp()->varp(); + if (astVarp->isForced()) return; + if (astVarp->isSigUserRWPublic()) return; DfgVertex* const srcp = varp->srcp(); - if (!srcp) return; + if (!srcp) { + if (vtxp->isPacked()) { + if (AstInitArray* const iap = VN_CAST(astVarp->valuep(), InitArray)) { + if (AstConst* const valp + = VN_CAST(iap->getIndexDefaultedValuep(idxp->toSizeT()), Const)) { + APPLYING(FOLD_ARRAYSEL_TABLE) { + replace(new DfgConst{m_dfg, valp->fileline(), valp->num()}); + return; + } + } + } + } + return; + } if (DfgSpliceArray* const splicep = srcp->cast()) { DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT()); @@ -2641,6 +2751,16 @@ class V3DfgPeephole final : public DfgVisitor { } } + void visit(DfgMatchMasked* const vtxp) override { + if (DfgConst* const constp = vtxp->lhsp()->cast()) { + APPLYING(FOLD_MATCHMASKED) { + AstVar* const matchVarp = vtxp->matchp()->as()->vscp()->varp(); + replace(makeI32(vtxp->fileline(), AstMatchMasked::fold(constp->num(), matchVarp))); + return; + } + } + } + //========================================================================= // DfgVertexTernary //========================================================================= @@ -2834,56 +2954,62 @@ class V3DfgPeephole final : public DfgVisitor { } } - if (DfgConcat* const tConcatp = thenp->cast()) { - if (DfgConcat* const eConcatp = elsep->cast()) { - DfgVertex* const tRhsp = tConcatp->rhsp(); - DfgVertex* const tLhsp = tConcatp->lhsp(); - DfgVertex* const eRhsp = eConcatp->rhsp(); - DfgVertex* const eLhsp = eConcatp->lhsp(); - - if (isSame(tRhsp, eRhsp)) { - APPLYING(REPLACE_COND_SAME_CAT_RHS) { - DfgCond* const newCondp - = make(flp, tLhsp->dtype(), condp, tLhsp, eLhsp); - replace(make(vtxp, newCondp, tRhsp)); - return; - } - } - - if (isSame(tLhsp, eLhsp)) { - APPLYING(REPLACE_COND_SAME_CAT_LHS) { - DfgCond* const newCondp - = make(flp, tRhsp->dtype(), condp, tRhsp, eRhsp); - replace(make(vtxp, tLhsp, newCondp)); + if (!thenp->is() && !elsep->is()) { + const std::pair cLSBs = commonLSBs(thenp, elsep); + if (cLSBs.first) { + APPLYING(REPLACE_COND_COMMON_LSBS) { + // Create new RHS + const uint32_t rWidth = cLSBs.second; + DfgVertex* rhsp = cLSBs.first; + if (rWidth != rhsp->width()) { + const DfgDataType& rDtype = DfgDataType::packed(rWidth); + rhsp = make(flp, rDtype, rhsp, 0U); + } + // If it's all the same, just replace + if (rhsp->dtype() == vtxp->dtype()) { + // Note this branch can only be hit if rules run in the right order, + // it might have missing code coverage after a refactor. + replace(rhsp); return; } + // Create new LHS + const uint32_t lWidth = vtxp->width() - rWidth; + const DfgDataType& lDtype = DfgDataType::packed(lWidth); + DfgVertex* const lThenp = make(flp, lDtype, thenp, rWidth); + DfgVertex* const lElsep = make(flp, lDtype, elsep, rWidth); + DfgVertex* const lhsp = make(flp, lDtype, condp, lThenp, lElsep); + // Replace with concat + replace(make(vtxp, lhsp, rhsp)); + return; } } - if (!tConcatp->hasMultipleSinks()) { - if (DfgConcat* const tRCatp = tConcatp->rhsp()->cast()) { - if (!tRCatp->hasMultipleSinks()) { - if (DfgSel* const tLSelp = tConcatp->lhsp()->cast()) { - if (DfgSel* const tRRSelp = tRCatp->rhsp()->cast()) { - if (tLSelp->lsb() == tRCatp->width() // - && tRRSelp->lsb() == 0 // - && isSame(tLSelp->fromp(), elsep) // - && isSame(tRRSelp->fromp(), elsep)) { - APPLYING(REPLACE_COND_INSERT) { - DfgVertex* const newTp = tRCatp->lhsp(); - DfgVertex* const newEp = make( - flp, newTp->dtype(), elsep, tRRSelp->width()); - DfgCond* const newCp = make(flp, newTp->dtype(), - condp, newTp, newEp); - replace(make( - vtxp, tLSelp, - make(tRCatp, newCp, tRRSelp))); - return; - } - } - } - } + const std::pair cMSBs = commonMSBs(thenp, elsep); + if (cMSBs.first) { + APPLYING(REPLACE_COND_COMMON_MSBS) { + // Create new LHS + const uint32_t lWidth = cMSBs.second; + DfgVertex* lhsp = cMSBs.first; + if (lWidth != lhsp->width()) { + const DfgDataType& lDtype = DfgDataType::packed(lWidth); + lhsp = make(flp, lDtype, lhsp, lhsp->width() - lWidth); } + // If it's all the same, just replace + if (lhsp->dtype() == vtxp->dtype()) { + // Note this branch can only be hit if rules run in the right order, + // it might have missing code coverage after a refactor. + replace(lhsp); + return; + } + // Create new RHS + const uint32_t rWidth = vtxp->width() - lWidth; + const DfgDataType& rDtype = DfgDataType::packed(rWidth); + DfgVertex* const rThenp = make(flp, rDtype, thenp, 0U); + DfgVertex* const rElsep = make(flp, rDtype, elsep, 0U); + DfgVertex* const rhsp = make(flp, rDtype, condp, rThenp, rElsep); + // Replace with concat + replace(make(vtxp, lhsp, rhsp)); + return; } } } diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index d8d295b2d..1c9de14e2 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -27,9 +27,11 @@ // Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests). // clang-format off #define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ARRAYSEL_TABLE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_LHS_OF_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MATCHMASKED) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MUX_FROM_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MUX_FROM_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_REP) \ @@ -112,17 +114,16 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SAME_REP_ON_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_LSBS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_MSBS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONES_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONE_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INSERT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_RHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_LHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_ELSE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_THEN) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_THEN_OR_LHS) \ diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp index 706c9c0c9..c0f5bdc99 100644 --- a/src/V3DfgSynthesize.cpp +++ b/src/V3DfgSynthesize.cpp @@ -402,6 +402,29 @@ class AstToDfgConverter final : public VNVisitor { DfgVertex* const vtxp = make(nodep->fileline(), *dtypep); nodep->user2p(vtxp); } + void visit(AstMatchMasked* nodep) override { + UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); + UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + + const DfgDataType* const dtypep = DfgDataType::fromAst(nodep->dtypep()); + if (!dtypep) { + m_foundUnhandled = true; + ++m_ctx.m_conv.nonRepDType; + return; + } + + iterate(nodep->lhsp()); + if (m_foundUnhandled) return; + iterate(nodep->matchp()); + if (m_foundUnhandled) return; + + FileLine* const flp = nodep->fileline(); + DfgMatchMasked* const vtxp = make(flp, *dtypep); + vtxp->lhsp(nodep->lhsp()->user2u().to()); + vtxp->matchp(nodep->matchp()->user2u().to()); + nodep->user2p(vtxp); + } void visit(AstReplicate* nodep) override { UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index a3d3adf8e..d932de1a3 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -299,6 +299,7 @@ public: void fromp(DfgVertex* vtxp) { srcp(vtxp); } uint32_t lsb() const { return m_lsb; } void lsb(uint32_t value) { m_lsb = value; } + uint32_t msb() const { return m_lsb + width() - 1; } }; class DfgUnitArray final : public DfgVertexUnary { @@ -328,6 +329,21 @@ public: ASTGEN_MEMBERS_DfgVertexBinary; }; +class DfgMatchMasked final : public DfgVertexBinary { + // Dfg equivalent of AstMatchMasked +public: + DfgMatchMasked(DfgGraph& dfg, FileLine* flp, const DfgDataType& dtype) + : DfgVertexBinary{dfg, dfgType(), flp, dtype} {} + ASTGEN_MEMBERS_DfgMatchMasked; + + DfgVertex* lhsp() const { return inputp(0); } + void lhsp(DfgVertex* vtxp) { inputp(0, vtxp); } + DfgVertex* matchp() const { return inputp(1); } + void matchp(DfgVertex* vtxp) { inputp(1, vtxp); } + + std::string srcName(size_t idx) const override { return idx ? "matchp" : "lhsp"; } +}; + class DfgMux final : public DfgVertexBinary { // AstSel is binary, but 'lsbp' is very often constant. As AstSel is fairly // common, we special case as a DfgSel for the constant 'lsbp', and as diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 8eb82b31f..11f2ac14a 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -18,8 +18,6 @@ #include "V3EmitCFunc.h" -#include "V3TSP.h" - #include #include diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 6eedcbc69..5e527870c 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1134,7 +1134,7 @@ public: } void visit(AstFFlush* nodep) override { if (!nodep->filep()) { - putns(nodep, "Verilated::runFlushCallbacks();\n"); + putns(nodep, "VL_FFLUSH_MT();"); } else { putns(nodep, "VL_FFLUSH_I("); iterateAndNextConstNull(nodep->filep()); @@ -1590,6 +1590,9 @@ public: puts(")"); } } + void visit(AstMatchMasked* nodep) override { + emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->matchp(), nullptr); + } void visit(AstMemberSel* nodep) override { iterateAndNextConstNull(nodep->fromp()); putnbs(nodep, "->"); diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 1746139b8..2a3197112 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -419,20 +419,14 @@ class EmitCHeader final : public EmitCConstInit { puts("return !(*this == rhs);\n}\n"); putns(sdtypep, "\nbool operator<(const " + EmitCUtil::prefixNameProtect(sdtypep) + "& rhs) const {\n"); - puts("return "); - puts("std::tie("); for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { - if (itemp != sdtypep->membersp()) puts(", "); - putns(itemp, itemp->nameProtect()); + putns(itemp, "if (" + itemp->nameProtect() + " < rhs." + itemp->nameProtect() + + ") return true;\n"); + putns(itemp, "if (rhs." + itemp->nameProtect() + " < " + itemp->nameProtect() + + ") return false;\n"); } - puts(")\n < std::tie("); - for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; - itemp = VN_AS(itemp->nextp(), MemberDType)) { - if (itemp != sdtypep->membersp()) puts(", "); - putns(itemp, "rhs." + itemp->nameProtect()); - } - puts(");\n"); + puts("return false;\n"); puts("}\n"); puts("};\n"); puts("template <>\n"); @@ -689,6 +683,7 @@ class EmitCHeader final : public EmitCConstInit { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n"); if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n"); if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n"); if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n"); diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 211eea99f..6ddd53e77 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -70,6 +70,7 @@ class EmitCModel final : public EmitCFunc { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n"); if (v3Global.dpi()) puts("#include \"svdpi.h\"\n"); // Declare foreign instances up front to make C++ happy diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index 79add9f3b..bcf79c164 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -176,26 +176,26 @@ class EmitCSyms final : EmitCBaseVisitorConst { return pos != std::string::npos ? scpname.substr(pos + 1) : scpname; } - static std::tuple getDimensions(const AstVar* const varp) { + static std::tuple getDimensions(const AstNodeDType* const rootDtypep) { int pdim = 0; int udim = 0; std::string bounds; - if (const AstBasicDType* const basicp = varp->basicp()) { - // Range is always first, it's not in "C" order - for (AstNodeDType* dtypep = varp->dtypep(); dtypep;) { - // Skip AstRefDType/AstTypedef, or return same node - dtypep = dtypep->skipRefp(); - if (const AstNodeArrayDType* const adtypep = VN_CAST(dtypep, NodeArrayDType)) { - bounds += " ,"; - bounds += std::to_string(adtypep->left()); - bounds += ","; - bounds += std::to_string(adtypep->right()); - if (VN_IS(dtypep, PackArrayDType)) - pdim++; - else - udim++; - dtypep = adtypep->subDTypep(); - } else { + // Range is always first, it's not in "C" order + for (const AstNodeDType* dtypep = rootDtypep; dtypep;) { + // Skip AstRefDType/AstTypedef, or return same node + dtypep = dtypep->skipRefp(); + if (const AstNodeArrayDType* const adtypep = VN_CAST(dtypep, NodeArrayDType)) { + bounds += " ,"; + bounds += std::to_string(adtypep->left()); + bounds += ","; + bounds += std::to_string(adtypep->right()); + if (VN_IS(dtypep, PackArrayDType)) + pdim++; + else + udim++; + dtypep = adtypep->subDTypep(); + } else { + if (const AstBasicDType* const basicp = dtypep->basicp()) { if (basicp->isRanged()) { bounds += " ,"; bounds += std::to_string(basicp->left()); @@ -203,13 +203,33 @@ class EmitCSyms final : EmitCBaseVisitorConst { bounds += std::to_string(basicp->right()); pdim++; } - break; // AstBasicDType - nothing below, 1 } + break; // Non-array leaf } } return {pdim, udim, bounds}; } + static std::tuple getDimensions(const AstVar* const varp) { + return getDimensions(varp->dtypep()); + } + + static int getUnpackedElements(const AstNodeDType* rootDtypep) { + int elements = 1; + for (const AstNodeDType* dtypep = rootDtypep; dtypep;) { + dtypep = dtypep->skipRefp(); + const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType); + if (!adtypep) break; + elements *= adtypep->elementsConst(); + dtypep = adtypep->subDTypep(); + } + return elements; + } + + static bool needsEmittedEntSize(const std::string& vlEnumType) { + return vlEnumType == "VLVT_STRUCT" || vlEnumType == "VLVT_UNION"; + } + static std::pair isForceControlSignal(const AstVar* const signalVarp) { // __VforceRd should not show up here because it is never public, but just in case it does, // it should be skipped because forceableVarInsert creates its VerilatedVar. @@ -225,15 +245,47 @@ class EmitCSyms final : EmitCBaseVisitorConst { return std::pair{false, ""}; } + static void appendVarProperties(std::string& stmt, const std::string& vlEnumType, + const std::string& vlEnumDir, const int udim, const int pdim, + const std::string& bounds, const std::string& entSize = "") { + stmt += vlEnumType; // VLVT_UINT32 etc + stmt += ", "; + stmt += vlEnumDir; // VLVD_IN etc + stmt += ", "; + stmt += std::to_string(udim); + if (!entSize.empty()) { + stmt += ", "; + stmt += entSize; + } else { + stmt += ", "; + stmt += std::to_string(pdim); + } + stmt += bounds; + stmt += ")"; + } + static std::string memberVlEnumDir(const AstVar* const varp, + const AstNodeDType* const dtypep) { + std::string out = "((" + varp->vlEnumDir() + ") & ~(VLVF_SIGNED|VLVF_BITVAR))"; + const AstNodeDType* const skipDTypep = dtypep->skipRefp(); + if (skipDTypep->isSigned()) out += "|VLVF_SIGNED"; + if (const AstBasicDType* const basicp = skipDTypep->basicp()) { + if (basicp->keyword() == VBasicDTypeKwd::BIT) out += "|VLVF_BITVAR"; + } + return out; + } + static std::string insertVarStatement(const ScopeVarData& svd, const AstScope* const scopep, const AstVar* const varp, const int udim, const int pdim, const std::string& bounds) { - std::string stmt; - stmt += protect("__Vscopep_" + svd.m_scopeName) + "->varInsert(\""; - stmt += V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)) + '"'; - const std::string varName = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + protect(varp->name()); + const std::string vlEnumType = varp->vlEnumType(); + const bool needsEntSize = needsEmittedEntSize(vlEnumType); + + std::string stmt; + stmt += protect("__Vscopep_" + svd.m_scopeName); + stmt += needsEntSize ? "->varInsertSized(\"" : "->varInsert(\""; + stmt += V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)) + '"'; if (!varp->isParam()) { stmt += ", &("; @@ -250,18 +302,87 @@ class EmitCSyms final : EmitCBaseVisitorConst { stmt += "))), true, "; } - stmt += varp->vlEnumType(); // VLVT_UINT32 etc - stmt += ", "; - stmt += varp->vlEnumDir(); // VLVD_IN etc - stmt += ", "; - stmt += std::to_string(udim); - stmt += ", "; - stmt += std::to_string(pdim); - stmt += bounds; - stmt += ")"; + const std::string entSize = needsEntSize + ? "sizeof(" + varName + ") / " + + std::to_string(getUnpackedElements(varp->dtypep())) + : ""; + appendVarProperties(stmt, vlEnumType, varp->vlEnumDir(), udim, pdim, bounds, entSize); return stmt; } + static std::string insertDTypeVarStatement(const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyName, + const std::string& cName, + const AstNodeDType* const dtypep, const int udim, + const int pdim, const std::string& bounds) { + const std::string vlEnumType = dtypep->vlEnumType(); + const bool needsEntSize = needsEmittedEntSize(vlEnumType); + std::string stmt; + stmt += protect("__Vscopep_" + svd.m_scopeName); + stmt += needsEntSize ? "->varInsertSized(\"" : "->varInsert(\""; + stmt += V3OutFormatter::quoteNameControls(prettyName) + '"'; + stmt += ", &("; + stmt += VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()); + stmt += "."; + stmt += cName; + stmt += "), false, "; + const std::string varName + = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + cName; + const std::string entSize + = needsEntSize + ? "sizeof(" + varName + ") / " + std::to_string(getUnpackedElements(dtypep)) + : ""; + appendVarProperties(stmt, vlEnumType, memberVlEnumDir(svd.m_varp, dtypep), udim, pdim, + bounds, entSize); + return stmt; + } + + static void addUOrStructMemberVars(std::vector& stmts, const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyPrefix, const std::string& cPrefix, + const AstNodeUOrStructDType* const sdtypep) { + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + const AstNodeDType* const itemDTypep = itemp->dtypep(); + const std::string prettyName + = prettyPrefix + "." + AstNode::vpiName(itemp->shortName()); + const std::string cName = cPrefix + "." + itemp->nameProtect(); + const std::tuple dimensions = getDimensions(itemDTypep); + const int pdim = std::get<0>(dimensions); + const int udim = std::get<1>(dimensions); + const std::string bounds = std::get<2>(dimensions); + stmts.emplace_back(insertDTypeVarStatement(svd, scopep, prettyName, cName, itemDTypep, + udim, pdim, bounds) + + ";"); + if (const AstNodeUOrStructDType* const subp + = VN_CAST(itemDTypep->skipRefp(), NodeUOrStructDType)) { + if (!subp->packed()) + addUOrStructMemberVars(stmts, svd, scopep, prettyName, cName, subp); + } else if (VN_IS(itemDTypep->skipRefp(), UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, prettyName, cName, + itemDTypep); + } + } + } + + static void addUnpackedArrayUOrStructMemberVars(std::vector& stmts, + const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyPrefix, + const std::string& cPrefix, + const AstNodeDType* const dtypep) { + const AstNodeDType* const skipDTypep = dtypep->skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(skipDTypep, UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, prettyPrefix, cPrefix + "[0]", + adtypep->subDTypep()); + } else if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(skipDTypep, NodeUOrStructDType)) { + if (!sdtypep->packed()) + addUOrStructMemberVars(stmts, svd, scopep, prettyPrefix, cPrefix, sdtypep); + } + } + std::string insertForceableVarStatement(const ScopeVarData& svd, const AstScope* const scopep, const AstVar* const varp, const int udim, const int pdim, const std::string& bounds) { @@ -1061,6 +1182,16 @@ std::vector EmitCSyms::getSymCtorStmts() { const std::string stmt = insertVarStatement(svd, scopep, varp, udim, pdim, bounds) + ";"; add(stmt); + if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(varp->dtypeSkipRefp(), NodeUOrStructDType)) { + if (!sdtypep->packed()) { + addUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty, + protect(varp->name()), sdtypep); + } + } else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty, + protect(varp->name()), varp->dtypep()); + } } } } diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 4201a0ad5..db0798e38 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -1142,6 +1142,13 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { puts(") "); iterateConst(nodep->propp()); } + void visit(AstSClocked* nodep) override { + puts("@("); + iterateConst(nodep->sensesp()); + puts(") "); + iterateConst(nodep->exprp()); + puts("\n"); + } void visit(AstPropAlways* nodep) override { puts(nodep->isStrong() ? "s_always" : "always"); if (!VN_IS(nodep->loBoundp(), Unbounded) || !VN_IS(nodep->hiBoundp(), Unbounded)) { diff --git a/src/V3Error.h b/src/V3Error.h index 6d4914329..01e7d6447 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -107,6 +107,7 @@ public: ENUMITEMWIDTH, // Error: enum item width mismatch ENUMVALUE, // Error: enum type needs explicit cast EOFNEWLINE, // End-of-file missing newline + FINALDLY, // Final delayed statement FSMMULTI, // Multiple FSM candidates in one always block FUNCTIMECTL, // Functions cannot have timing/delay/wait FUTURE, // Feature is under development and not yet supported @@ -227,8 +228,8 @@ public: "BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED", - "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FSMMULTI", - "FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", + "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FINALDLY", + "FSMMULTI", "FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IEEEMAYDEPRECATE", "IFDEPTH", "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", @@ -269,11 +270,11 @@ public: bool pretendError() const VL_MT_SAFE { return (m_e == ASSIGNIN || m_e == BADSTDPRAGMA || m_e == BADVLTPRAGMA || m_e == BLKANDNBLK || m_e == BLKLOOPINIT || m_e == CONTASSREG || m_e == ENCAPSULATED - || m_e == ENDLABEL || m_e == ENUMITEMWIDTH || m_e == ENUMVALUE || m_e == HIERPARAM - || m_e == FUNCTIMECTL || m_e == IMPURE || m_e == MODMISSING || m_e == NOTREDOP - || m_e == PARAMNODEFAULT || m_e == PINNOTFOUND || m_e == PKGNODECL - || m_e == PROCASSWIRE || m_e == PROTOTYPEMIS || m_e == SUPERNFIRST - || m_e == ZEROREPL); + || m_e == ENDLABEL || m_e == ENUMITEMWIDTH || m_e == ENUMVALUE || m_e == FINALDLY + || m_e == HIERPARAM || m_e == FUNCTIMECTL || m_e == IMPURE || m_e == MODMISSING + || m_e == NOTREDOP || m_e == PARAMNODEFAULT || m_e == PINNOTFOUND + || m_e == PKGNODECL || m_e == PROCASSWIRE || m_e == PROTOTYPEMIS + || m_e == SUPERNFIRST || m_e == ZEROREPL); } // Warnings to mention manual bool mentionManual() const VL_MT_SAFE { diff --git a/src/V3Expand.cpp b/src/V3Expand.cpp index 267360e15..f58474273 100644 --- a/src/V3Expand.cpp +++ b/src/V3Expand.cpp @@ -206,31 +206,19 @@ class ExpandVisitor final : public VNVisitor { static AstNodeExpr* newWordGrabShift(FileLine* fl, int word, AstNodeExpr* lhsp, int shift) { // Extract the expression to grab the value for the specified word, if it's the shift // of shift bits from lhsp - AstNodeExpr* newp; // Negative word numbers requested for lhs when it's "before" what we want. // We get a 0 then. const int othword = word - shift / VL_EDATASIZE; - AstNodeExpr* const llowp = newAstWordSelClone(lhsp, othword); - if (const int loffset = VL_BITBIT_E(shift)) { - AstNodeExpr* const lhip = newAstWordSelClone(lhsp, othword - 1); - const int nbitsonright = VL_EDATASIZE - loffset; // bits that end up in lword - newp = new AstOr{ - fl, - new AstAnd{fl, new AstConst{fl, AstConst::SizedEData{}, VL_MASK_E(loffset)}, - new AstShiftR{fl, lhip, - new AstConst{fl, static_cast(nbitsonright)}, - VL_EDATASIZE}}, - new AstAnd{fl, - new AstConst{fl, AstConst::SizedEData{}, - static_cast(~VL_MASK_E(loffset))}, - new AstShiftL{fl, llowp, - new AstConst{fl, static_cast(loffset)}, - VL_EDATASIZE}}}; - newp = V3Const::constifyEditCpp(newp); - } else { - newp = llowp; - } - return newp; + AstNodeExpr* const lop = newAstWordSelClone(lhsp, othword); + const uint32_t loShift = VL_BITBIT_E(shift); + if (!loShift) return lop; + AstNodeExpr* const hip = newAstWordSelClone(lhsp, othword - 1); + const uint32_t hiShift = VL_EDATASIZE - loShift; // Complement offset + AstNodeExpr* const newp + = new AstOr{fl, // + new AstShiftR{fl, hip, new AstConst{fl, hiShift}, VL_EDATASIZE}, + new AstShiftL{fl, lop, new AstConst{fl, loShift}, VL_EDATASIZE}}; + return V3Const::constifyEditCpp(newp); } // Return expression indexing the word that contains 'lsbp' + the given word offset diff --git a/src/V3Force.cpp b/src/V3Force.cpp index 75f3cf256..3a7b51ce9 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -870,30 +870,8 @@ class ForceDiscoveryVisitor final : public VNVisitorConst { ForceState::ForceRangeInfo rangeInfo = m_state.getForceRangeInfo(nodep->lhsp(), forcedVarp, true); - // Start from a cloned RHS expression; adjust below for partial bit selects. - const AstSel* const selLhsp = VN_CAST(nodep->lhsp(), Sel); - AstNodeExpr* rhsExprp = nodep->rhsp()->cloneTreePure(false); - - // For bitwise selects inside arrays, merge updated bits with preserved base bits. - if (rangeInfo.m_hasArraySel && rangeInfo.m_arrayInfo.m_hasBitSel && selLhsp - && ForceState::isBitwiseDType(selLhsp->fromp())) { - AstNodeExpr* const baseExprp = selLhsp->fromp()->cloneTreePure(false); - baseExprp->foreach( - [](AstVarRef* const refp) { ForceState::markNonReplaceable(refp); }); - - // Pad the selected value back to full base width before masking/or-ing. - rhsExprp = ForceState::zeroPadToBaseWidth(rhsExprp, selLhsp->fromp()->width(), - rangeInfo.m_padLsb, rangeInfo.m_padMsb); - - // Keep untouched base bits and insert the newly forced bit range. - // rhsExpr = (baseExpr & ~mask(range)) | (zeroPad(force_rhs) & mask(range)); - AstConst* const maskConstp = ForceState::makeRangeMaskConst( - nodep->lhsp(), selLhsp->fromp()->width(), rangeInfo.m_padLsb, rangeInfo.m_padMsb); - AstNodeExpr* const maskedOldp - = new AstAnd{nodep->lhsp()->fileline(), baseExprp, - new AstNot{nodep->lhsp()->fileline(), maskConstp}}; - rhsExprp = new AstOr{nodep->lhsp()->fileline(), maskedOldp, rhsExprp}; - } + // Keep narrow rhs, VlForceVec blends unpacked-array bit-select forces at read time + AstNodeExpr* const rhsExprp = nodep->rhsp()->cloneTreePure(false); m_state.addForceAssignment(forcedVarp, lhsVarRefp->varScopep(), rhsExprp, nodep, rangeInfo.m_rangeLsb, rangeInfo.m_rangeMsb, rangeInfo.m_padLsb, @@ -1114,6 +1092,11 @@ class ForceConvertVisitor final : public VNVisitor { // Verilog pseudocode: // forceVec.addForce(range_lsb, range_msb, &forceRHS[id], rhs_lsb); + const AstSel* const selLhsp = VN_CAST(lhsp, Sel); + const bool arrayBitSel + = info.m_hasArraySel && selLhsp && ForceState::getArraySelInfo(lhsp).m_hasBitSel + && ForceState::isBitwiseDType(selLhsp->fromp()) + && (info.m_padMsb - info.m_padLsb + 1) < selLhsp->fromp()->width(); AstNodeExpr* const rhsDatap = ForceState::buildRhsDataExpr(flp, info); AstCExpr* const rhsAddrp = new AstCExpr{flp}; rhsAddrp->add("&("); @@ -1124,7 +1107,13 @@ class ForceConvertVisitor final : public VNVisitor { ForceState::makeConst32(flp, info.m_rangeLsb)}; addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_rangeMsb)); addForceCallp->addPinsp(rhsAddrp); - addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_rangeLsb)); + addForceCallp->addPinsp( + ForceState::makeConst32(flp, arrayBitSel ? info.m_padLsb : info.m_rangeLsb)); + if (arrayBitSel) { + addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_padLsb)); + addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_padMsb)); + addForceCallp->addPinsp(ForceState::makeConst32(flp, selLhsp->fromp()->width())); + } addForceCallp->dtypeSetVoid(); AstNodeStmt* const stmtp = addForceCallp->makeStmt(); @@ -1164,12 +1153,21 @@ class ForceConvertVisitor final : public VNVisitor { const ForceState::ForceRangeInfo rangeInfo = m_state.getForceRangeInfo(lhsp, releasedVarp, false); + const AstSel* const selLhsp = VN_CAST(lhsp, Sel); + const bool arrayBitSel + = rangeInfo.m_hasArraySel && selLhsp && rangeInfo.m_arrayInfo.m_hasBitSel + && ForceState::isBitwiseDType(selLhsp->fromp()) + && (rangeInfo.m_padMsb - rangeInfo.m_padLsb + 1) < selLhsp->fromp()->width(); AstCMethodHard* const releaseCallp = new AstCMethodHard{ flp, new AstVarRef{flp, varInfo->m_forceVecVscp, VAccess::WRITE}, VCMethod::FORCE_RELEASE, ForceState::makeConst32(flp, rangeInfo.m_rangeLsb)}; releaseCallp->addPinsp(ForceState::makeConst32(flp, rangeInfo.m_rangeMsb)); + if (arrayBitSel) { + releaseCallp->addPinsp(ForceState::makeConst32(flp, rangeInfo.m_padLsb)); + releaseCallp->addPinsp(ForceState::makeConst32(flp, rangeInfo.m_padMsb)); + } releaseCallp->dtypeSetVoid(); - // forceVec.release(range_lsb, range_msb); + // forceVec.release(range_lsb, range_msb [, bit_lsb, bit_msb]); AstNodeStmt* const releasep = releaseCallp->makeStmt(); AstAssign* clearEnp = nullptr; diff --git a/src/V3FsmDetect.cpp b/src/V3FsmDetect.cpp index 4a2ccf8af..5ffee9e4c 100644 --- a/src/V3FsmDetect.cpp +++ b/src/V3FsmDetect.cpp @@ -30,6 +30,7 @@ #include "V3Ast.h" #include "V3Control.h" #include "V3Graph.h" +#include "V3UniqueNames.h" #include #include @@ -987,14 +988,23 @@ class FsmDetectVisitor final : public VNVisitor { return assp; } + static AstVarRef* tryExtractVarRef(AstNodeExpr* const exprp) { + AstVarRef* const varp = VN_CAST(AstArraySel::baseFromp(exprp, true), VarRef); + if (!varp) { + exprp->v3warn(COVERIGN, + "Ignoring unsupported: FSM coverage with " << exprp->prettyTypeName()); + return nullptr; + } + return varp; + } + static AstNodeAssign* nodeStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp, AstVarScope*& fromVscp) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); - UASSERT_OBJ(lhsp, assp, "register commit lhs should be normalized to a VarRef"); + AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp()); AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef); - if (!rhsp) return nullptr; + if (!rhsp || !lhsp) return nullptr; stateVscp = lhsp->varScopep(); fromVscp = rhsp->varScopep(); return assp; @@ -1006,11 +1016,9 @@ class FsmDetectVisitor final : public VNVisitor { FsmStateValue& resetValue) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); - UASSERT_OBJ(lhsp, assp, - "conditional register commit lhs should be normalized to a VarRef"); + AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp()); AstCond* const rhsp = VN_CAST(assp->rhsp(), Cond); - if (!rhsp) return nullptr; + if (!rhsp || !lhsp) return nullptr; if (AstVarRef* const elsep = VN_CAST(rhsp->elsep(), VarRef)) { if (constValueStatus(rhsp->thenp(), resetValue) != ConstValueStatus::OK) return nullptr; @@ -1033,7 +1041,7 @@ class FsmDetectVisitor final : public VNVisitor { FsmStateValue& value) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); + AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(assp->lhsp(), true), VarRef); UASSERT_OBJ(lhsp, assp, "direct constant state assignment lhs should be normalized to a VarRef"); if (constValueStatus(assp->rhsp(), value) != ConstValueStatus::OK) return nullptr; @@ -1220,7 +1228,8 @@ class FsmDetectVisitor final : public VNVisitor { AstVarRef* vrefp = VN_CAST(eqp->lhsp(), VarRef); AstNodeExpr* valuep = eqp->rhsp(); if (!vrefp) { - vrefp = VN_AS(eqp->rhsp(), VarRef); + vrefp = tryExtractVarRef(eqp->rhsp()); + if (!vrefp) { return false; } valuep = eqp->lhsp(); } @@ -2082,6 +2091,7 @@ public: class FsmLowerVisitor final { // STATE - across all visitors const FsmState& m_state; + V3UniqueNames m_fsmBuildNames; // METHODS // Rebuild a state-typed constant using the tracked state variable @@ -2133,8 +2143,8 @@ class FsmLowerVisitor final { AstNodeModule* const modp = scopep->modp(); AstNodeDType* const prevDTypep = scopep->findLogicDType( sampleVscp->width(), sampleVscp->width(), sampleVscp->dtypep()->numeric()); - AstVarScope* const prevVscp - = scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), prevDTypep); + const std::string tmpName = m_fsmBuildNames.get(stateVscp->varp()->shortName()); + AstVarScope* const prevVscp = scopep->createTemp(tmpName, prevDTypep); // The saved previous-state temp crosses the scheduler's pre/post split // in the same way as Verilator's built-in NBA shadow variables, so keep // both vars marked as post-life participants for stable MT ordering. @@ -2283,7 +2293,8 @@ public: // concrete coverage instrumentation while the saved scoped pointers are // still valid in the same pass. explicit FsmLowerVisitor(const FsmState& state) - : m_state{state} { + : m_state{state} + , m_fsmBuildNames{"__Vfsmcov_prev"} { for (const DetectedFsm& fsm : m_state.fsms()) { buildOne(*fsm.graphp); } } }; diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index 4875ac75c..e41b61daf 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -471,6 +471,11 @@ class GateOkVisitor final : public VNVisitorConst { // assign to get randomization etc clearSimple("CReset"); } + void visit(AstMatchMasked* nodep) override { + if (!m_isSimple) return; + // This node can be expensive + clearSimple("MatchMasked"); + } //-------------------- void visit(AstNode* nodep) override { if (!m_isSimple) return; // Fastpath @@ -543,89 +548,62 @@ class GateInline final { // Logic block with pending substitutions are stored in this map, together with their ordinal std::unordered_map m_hasPending; size_t m_statInlined = 0; // Statistic tracking - signals inlined - size_t m_statRefs = 0; // Statistic tracking - size_t m_statExcluded = 0; // Statistic tracking + size_t m_statNotInlined = 0; // Statistic tracking - signals not inlined due to cost + size_t m_statRefs = 0; // Statistic tracking - number of input variable references replaced // METHODS - static bool isCheapWide(const AstNodeExpr* exprp) { + static bool isCheap(const AstNodeExpr* exprp) { + // Constant is cheap + if (VN_IS(exprp, Const)) return true; + // Variable reference is cheap + if (VN_IS(exprp, NodeVarRef)) return true; + // AstSel is cheap if the fromp is cheap, and not a wide needing bit swizzling if (const AstSel* const selp = VN_CAST(exprp, Sel)) { + if (!isCheap(selp->fromp())) return false; + if (!selp->isWide()) return true; + if (!VN_IS(selp->lsbp(), Const)) return false; if (selp->lsbConst() % VL_EDATASIZE != 0) return false; - exprp = selp->fromp(); + return true; } - if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) exprp = aselp->fromp(); - return VN_IS(exprp, Const) || VN_IS(exprp, NodeVarRef); - } - static bool excludedWide(GateVarVertex* const vVtxp, const AstNodeExpr* const rhsp) { - // Handle wides with logic drivers that are too wide for V3Expand. - if (!vVtxp->varScp()->isWide() // - || vVtxp->varScp()->widthWords() <= v3Global.opt.expandLimit() // - || vVtxp->inEmpty() // - || isCheapWide(rhsp)) - return false; - - const GateLogicVertex* const lVtxp - = vVtxp->inEdges().frontp()->fromp()->as(); - - // Exclude from inlining variables READ multiple times. - // To decouple actives thus simplifying scheduling, exclude only those - // VarRefs that are referenced under the same active as they were assigned. - if (const AstActive* const primaryActivep = lVtxp->activep()) { - size_t reads = 0; - for (const V3GraphEdge& edge : vVtxp->outEdges()) { - const GateLogicVertex* const lvp = edge.top()->as(); - if (lvp->activep() != primaryActivep) continue; - - reads += edge.weight(); - if (reads > 1) return true; - } + // AstArraySel is cheap if the fromp is cheap + if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) { + return isCheap(aselp->fromp()); } + // Otherwise it is not cheap return false; } bool shouldInline(GateVarVertex* vVtxp, GateLogicVertex* lVtxp, size_t nReads, AstNodeExpr* substp, bool allowMultiIn) { - AstVarScope* const vscp = vVtxp->varScp(); - // Always inline constants if (VN_IS(substp, Const)) return true; - // Don't inline non-constant static initializers + // Don't inline non-constant static initializers - these are scheduled differently if (lVtxp->staticInit()) return false; // Inline simple variable references if (VN_IS(substp, VarRef)) return true; // Only inline arrays if a simple variable or constant - if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) return false; - // Inline constant array selects - if (VN_IS(substp, ArraySel) && nReads <= 1) return true; - - // Don't inline expensive wide operations - if (excludedWide(vVtxp, substp)) { - ++m_statExcluded; - UINFO(9, "Gate inline exclude '" << vVtxp->name() << "'"); - vVtxp->clearReducible("Excluded wide"); // Check once. - return false; - } - - if (nReads == 0) { - // Reads no variables, likely unfolded constant expression - return true; - } else if (nReads == 1) { - // Reads one variable - return true; - } else { - // Reads more two or more variables - if (!allowMultiIn) return false; - // Do it if not used, or used only once, ignoring slow code - int n = 0; - for (V3GraphEdge& edge : vVtxp->outEdges()) { - const GateLogicVertex* const dstVtxp = edge.top()->as(); - // Ignore slow code, or if the destination is not used - if (dstVtxp->slow()) continue; - if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue; - n += edge.weight(); - if (n > 1) return false; + if (VN_IS(vVtxp->varScp()->dtypep()->skipRefp(), UnpackArrayDType)) return false; + // Inline if reads no variables - unfolded constant expression, nullary builtin e.g.: $time + if (nReads == 0) return true; + // If it reads one variable, inline if not wide, or if cheap + if (nReads == 1 && (!substp->isWide() || isCheap(substp))) return true; + // Don't inline on first round if reads more than one variable + if (nReads > 1 && !allowMultiIn) return false; + // Reads multiple variables, or is expensive to compute. + // Inline if used only once, ignoring slow code, or dead code that can be deleted. + int n = 0; + for (V3GraphEdge& edge : vVtxp->outEdges()) { + const GateLogicVertex* const dstVtxp = edge.top()->as(); + // Ignore slow code, or if the destination is not used + if (dstVtxp->slow()) continue; + if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue; + n += edge.weight(); + if (n > 1) { + ++m_statNotInlined; + return false; } - return true; } + return true; } void recordSubstitution(AstVarScope* vscp, AstNodeExpr* substp, AstNode* logicp) { @@ -724,7 +702,7 @@ class GateInline final { if (!okVisitor.varAssigned(vVtxp->varScp())) continue; // Expression we are considering to substitute with - AstNodeExpr* const substp = okVisitor.substitutionp(); + AstNodeExpr* const substp = V3Const::constifyEdit(okVisitor.substitutionp()); // Number of variables read by the substitution const size_t nReads = okVisitor.readVscps().size(); @@ -832,9 +810,9 @@ class GateInline final { } ~GateInline() { - V3Stats::addStat("Optimizations, Gate sigs deleted", m_statInlined); - V3Stats::addStat("Optimizations, Gate inputs replaced", m_statRefs); - V3Stats::addStat("Optimizations, Gate excluded wide expressions", m_statExcluded); + V3Stats::addStat("Optimizations, Gate signals inlined", m_statInlined); + V3Stats::addStat("Optimizations, Gate signals not inlined due to cost", m_statNotInlined); + V3Stats::addStat("Optimizations, Gate reads replaced", m_statRefs); } public: diff --git a/src/V3Global.cpp b/src/V3Global.cpp index e0b6f42ea..2e7c25fc7 100644 --- a/src/V3Global.cpp +++ b/src/V3Global.cpp @@ -245,6 +245,7 @@ std::vector V3Global::verilatedCppFiles() { if (v3Global.opt.vpi()) result.emplace_back("verilated_vpi.cpp"); if (v3Global.opt.savable()) result.emplace_back("verilated_save.cpp"); if (v3Global.opt.coverage()) result.emplace_back("verilated_cov.cpp"); + if (v3Global.opt.coverage()) result.emplace_back("verilated_covergroup.cpp"); for (const string& base : v3Global.opt.traceSourceBases()) result.emplace_back(base + "_c.cpp"); if (v3Global.usesProbDist()) result.emplace_back("verilated_probdist.cpp"); diff --git a/src/V3InlineCFuncs.cpp b/src/V3InlineCFuncs.cpp index 99efbc201..cdfc24062 100644 --- a/src/V3InlineCFuncs.cpp +++ b/src/V3InlineCFuncs.cpp @@ -15,14 +15,13 @@ //************************************************************************* // V3InlineCFuncs's Transformations: // -// For each CCall to a small CFunc: -// - Check if function is eligible for inlining (small enough, same scope) -// - Clone local variables with unique names to avoid collisions -// - Replace CCall with cloned function body statements +// Build a bipartite call graph containing function and call site vertices, +// then iterate functions leaf to root, inlining if size heuristics are met. +// Finally, remove unused functions. // // Two tunables control inlining: -// --inline-cfuncs : Always inline if size <= n (default 20) -// --inline-cfuncs-product : Also inline if size * call_count <= n (default 200) +// --inline-cfuncs : Inline if size <= n +// --inline-cfuncs-product : Also inline if size * call_count <= n // //************************************************************************* @@ -31,231 +30,465 @@ #include "V3InlineCFuncs.h" #include "V3AstUserAllocator.h" +#include "V3ExecGraph.h" +#include "V3Graph.h" #include "V3Stats.h" -#include #include VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### -// Helper visitor to check if a CFunc contains C statements -// Uses clearOptimizable pattern for debugging +// Bipartite call graph containing function and call site vertices -class CFuncInlineCheckVisitor final : public VNVisitorConst { - // STATE - bool m_optimizable = true; // True if function can be inlined - string m_whyNot; // Reason why not optimizable - AstNode* m_whyNotNodep = nullptr; // Node that caused non-optimizable +class InlineCFuncsFunctionVertex; +class InlineCFuncsCallSiteVertex; - // METHODS - void clearOptimizable(AstNode* nodep, const string& why) { - if (m_optimizable) { - m_optimizable = false; - m_whyNot = why; - m_whyNotNodep = nodep; - UINFO(9, "CFunc not inlineable: " << why); - if (nodep) UINFO(9, ": " << nodep); - UINFO(9, ""); - } - } +class InlineCFuncsCallGraph final : public V3Graph { +public: + InlineCFuncsCallGraph() + : V3Graph{} {} + ~InlineCFuncsCallGraph() override = default; - // VISITORS - void visit(AstCStmt* nodep) override { clearOptimizable(nodep, "contains AstCStmt"); } - void visit(AstCExpr* nodep) override { clearOptimizable(nodep, "contains AstCExpr"); } - void visit(AstCStmtUser* nodep) override { clearOptimizable(nodep, "contains AstCStmtUser"); } - void visit(AstCExprUser* nodep) override { clearOptimizable(nodep, "contains AstCExprUser"); } - void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } + void addEdge(InlineCFuncsFunctionVertex& from, InlineCFuncsCallSiteVertex& top); + void addEdge(InlineCFuncsCallSiteVertex& from, InlineCFuncsFunctionVertex& top); +}; + +class EitherVertex VL_NOT_FINAL : public V3GraphVertex { + VL_RTTI_IMPL(EitherVertex, V3GraphVertex) +protected: + explicit EitherVertex(InlineCFuncsCallGraph& graph) + : V3GraphVertex{&graph} {} +}; + +class InlineCFuncsFunctionVertex final : public EitherVertex { + VL_RTTI_IMPL(InlineCFuncsFunctionVertex, EitherVertex) + AstCFunc* const m_cfuncp; // The function + const char* m_noInlineWyp = nullptr; // First reason the function should not be inlined + const char* m_keepWyp = nullptr; // Why the function should not be removed + size_t m_size = 0; // The size of the function public: - // CONSTRUCTORS - explicit CFuncInlineCheckVisitor(AstCFunc* cfuncp) { iterateConst(cfuncp); } + InlineCFuncsFunctionVertex(InlineCFuncsCallGraph& graph, AstCFunc* cfuncp) + : EitherVertex{graph} + , m_cfuncp{cfuncp} {} + ~InlineCFuncsFunctionVertex() override = default; // ACCESSORS - bool optimizable() const { return m_optimizable; } - string whyNot() const { return m_whyNot; } - AstNode* whyNotNodep() const { return m_whyNotNodep; } + AstCFunc* cfuncp() const { return m_cfuncp; } + size_t size() const { return m_size; } + void sizeInc(size_t value = 1) { m_size += value; } + bool noInline() const { return m_noInlineWyp; } + void setNoInline(const char* whyp) { + if (!m_noInlineWyp) m_noInlineWyp = whyp; + } + bool keep() const { return m_keepWyp; } + void setKeep(const char* whyp) { + if (!m_keepWyp) m_keepWyp = whyp; + } + std::string dotColor() const override { + return m_noInlineWyp ? "red" : m_keepWyp ? "orange" : "black"; + } + + // debug + FileLine* fileline() const override { return m_cfuncp->fileline(); } + std::string dotShape() const override { return "box"; } + std::string name() const override VL_MT_STABLE { + std::string str = cvtToHex(m_cfuncp); + str += "\n" + m_cfuncp->name(); + str += "\nsize: " + std::to_string(m_size); + if (m_noInlineWyp) str += "\nNoInline: "s + m_noInlineWyp; + if (m_keepWyp) str += "\nKeep: "s + m_keepWyp; + return str; + } }; +class InlineCFuncsCallSiteVertex final : public EitherVertex { + VL_RTTI_IMPL(InlineCFuncsCallSiteVertex, EitherVertex) + AstCCall* const m_callp; // The call site + const char* m_noInlineWyp = nullptr; // First reason the function should not be inlined + +public: + InlineCFuncsCallSiteVertex(InlineCFuncsCallGraph& graph, AstCCall* callp) + : EitherVertex{graph} + , m_callp{callp} {} + ~InlineCFuncsCallSiteVertex() override = default; + + // ACCESSORS + AstCCall* callp() const { return m_callp; } + bool noInline() const { return m_noInlineWyp; } + void setNoInline(const char* whyp) { + if (!m_noInlineWyp) m_noInlineWyp = whyp; + } + + // debug + FileLine* fileline() const override { return m_callp->fileline(); } + std::string dotColor() const override { return m_noInlineWyp ? "red" : "black"; } + std::string dotShape() const override { return "ellipse"; } + std::string name() const override VL_MT_STABLE { + std::string str = cvtToHex(m_callp); + if (m_noInlineWyp) str += "\nNoInline: "s + m_noInlineWyp; + return str; + } +}; + +void InlineCFuncsCallGraph::addEdge(InlineCFuncsFunctionVertex& caller, + InlineCFuncsCallSiteVertex& callsite) { + UASSERT_OBJ(callsite.inEmpty(), &callsite, "Call site should have at most one incoming edge"); + new V3GraphEdge{this, &caller, &callsite, 1, true}; // Can cut caller -> callsite +} +void InlineCFuncsCallGraph::addEdge(InlineCFuncsCallSiteVertex& callsite, + InlineCFuncsFunctionVertex& callee) { + UASSERT_OBJ(callsite.outEmpty(), &callsite, "Call site should have at most one outgoing edge"); + new V3GraphEdge{this, &callsite, &callee, 1, false}; +} + //###################################################################### class InlineCFuncsVisitor final : public VNVisitor { // NODE STATE - // AstCFunc::user1() -> vector of AstCCall* pointing to this function - // AstCFunc::user2() -> bool: true if checked for C statements - // AstCFunc::user3() -> bool: true if contains C statements (not inlineable) + // AstCFunc::user1p() -> InlineCFuncsFunctionVertex*, the function vertex + // AstCCall::user1p() -> InlineCFuncsCallSiteVertex*, the call site vertex + // AstVar::user2p() -> AstVar*, the cloned inlined local variable const VNUser1InUse m_user1InUse; - const VNUser2InUse m_user2InUse; - const VNUser3InUse m_user3InUse; - AstUser1Allocator> m_callSites; // STATE - VDouble0 m_statInlined; // Statistic tracking - const int m_threshold1; // Size threshold: always inline if size <= this - const int m_threshold2; // Product threshold: inline if size * calls <= this - AstCFunc* m_callerFuncp = nullptr; // Current caller function - // Tuples of (StmtExpr to replace, CFunc to inline from, caller func for vars) - std::vector> m_toInline; + InlineCFuncsCallGraph m_graph; // The call graph + VDouble0 m_statCallsInlined; // Number of calls inlined + VDouble0 m_statFuncsInlined; // Number of functions inlined at least once + VDouble0 m_statFuncsRemoved; // Number of fully-inlined functions removed + // Size threshold: always inline if size <= this + const size_t m_sizeThreshold = v3Global.opt.inlineCFuncs(); + // Product threshold: inline if size * calls <= this + const size_t m_prodThreshold = v3Global.opt.inlineCFuncsProduct(); + // Maximum size of caller to consider inlining into + const size_t m_maxSizeCFunc = []() -> size_t { + int maxCFunc = v3Global.opt.outputSplitCFuncs(); + int maxFile = v3Global.opt.outputSplit(); + if (maxCFunc <= 0) maxCFunc = std::numeric_limits::max(); + if (maxFile <= 0) maxFile = std::numeric_limits::max(); + return std::min(maxCFunc, maxFile); + }(); + const size_t m_maxSizeTrace = []() -> size_t { + int maxTrace = v3Global.opt.outputSplitCTrace(); + int maxFile = v3Global.opt.outputSplit(); + if (maxTrace <= 0) maxTrace = std::numeric_limits::max(); + if (maxFile <= 0) maxFile = std::numeric_limits::max(); + return std::min(maxTrace, maxFile); + }(); + InlineCFuncsFunctionVertex* m_cfuncVtxp = nullptr; // Vertex of currently iterated function + bool m_inExecGraph = false; // True while inside an AstExecGraph subtree // METHODS - - // Check if a function contains any $c() calls (user or internal) - // Results are cached in user2/user3 for efficiency - bool containsCStatements(AstCFunc* cfuncp) { - if (!cfuncp->user2()) { - // Not yet checked - run the check visitor - cfuncp->user2(true); // Mark as checked - const CFuncInlineCheckVisitor checker{cfuncp}; - cfuncp->user3(!checker.optimizable()); // Store result (true = contains C stmts) - } - return cfuncp->user3(); + InlineCFuncsFunctionVertex* getInlineCFuncsFunctionVertexp(AstCFunc* cfuncp) { + if (!cfuncp->user1p()) cfuncp->user1p(new InlineCFuncsFunctionVertex{m_graph, cfuncp}); + return cfuncp->user1u().to(); } - // Check if a function is eligible for inlining into caller - bool isInlineable(const AstCFunc* callerp, AstCFunc* cfuncp) { - // Must be in the same scope (same class) to access the same members - if (callerp->scopep() != cfuncp->scopep()) return false; + InlineCFuncsCallSiteVertex* getInlineCFuncsCallSiteVertexp(AstCCall* callp) { + if (!callp->user1p()) callp->user1p(new InlineCFuncsCallSiteVertex{m_graph, callp}); + return callp->user1u().to(); + } - // Check for $c() calls that might use 'this' - if (containsCStatements(cfuncp)) return false; + AstCLocalScope* inlineCall(AstCFunc* const callerp, // + AstCCall* const callp, // + AstCFunc* const calleep, // + const size_t seqNum) { + UINFO(6, "Inlining CFunc " << calleep->name() << " into " << callerp->name() + << " at call site " << callp); - // Check it's a void function (not a coroutine) - if (cfuncp->rtnTypeVoid() != "void") return false; + AstNodeStmt* const callSitep = VN_AS(callp->backp(), StmtExpr); + ++m_statCallsInlined; - // Don't inline functions marked dontCombine (e.g. trace, entryPoint) - if (cfuncp->dontCombine()) return false; - - // Don't inline entry point functions - if (cfuncp->entryPoint()) return false; - - // Must have statements to inline - if (!cfuncp->stmtsp()) return false; - - // Check size thresholds - const size_t funcSize = cfuncp->nodeCount(); - - // Always inline if small enough - if (funcSize <= static_cast(m_threshold1)) return true; - - // Also inline if size * call_count is reasonable - const size_t callCount = m_callSites(cfuncp).size(); - if (callCount > 0 && funcSize * callCount <= static_cast(m_threshold2)) { - return true; + // Callee might be empty, just delete the call + if (!calleep->stmtsp()) { + VL_DO_DANGLING(pushDeletep(callSitep->unlinkFrBack()), callSitep); + return nullptr; } - return false; + // Replace call site with a local scope + FileLine* const flp = callSitep->fileline(); + AstCLocalScope* const lscopep = new AstCLocalScope{flp, nullptr}; + callSitep->replaceWith(lscopep); + VL_DO_DANGLING(pushDeletep(callSitep), callSitep); + lscopep->addStmtsp(new AstComment{flp, "Inlined CFunc: " + calleep->name()}); + + // Although it's in a local scope, we still make names of cloned locals unique + const std::string varPrefix + = "__Vinline_" + std::to_string(seqNum) + "_" + calleep->name() + "_"; + + // AstVar::user2p() -> AstVar*, the cloned inlined local variable + const VNUser2InUse user2InUse; + + // Clone local variables, add them to the local scope + for (AstVar* varp = calleep->varsp(); varp; varp = VN_AS(varp->nextp(), Var)) { + AstVar* const newVarp = varp->cloneTree(false); + newVarp->name(varPrefix + varp->name()); + lscopep->addStmtsp(newVarp); + varp->user2p(newVarp); + } + + // Clone the function body + AstNode* const bodyp = calleep->stmtsp()->cloneTree(true); + lscopep->addStmtsp(bodyp); + + // Retarget local variable references to the cloned locals + // Rename locals defined in the body, TODO: there should be none after #6280 + // Reset vertex pointers on calls + bodyp->foreachAndNext([&](AstNode* nodep) { + if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) { + if (AstVar* const varp = VN_AS(refp->varp()->user2p(), Var)) refp->varp(varp); + } else if (AstVar* const varp = VN_CAST(nodep, Var)) { + varp->name(varPrefix + varp->name()); + } else if (AstCCall* const callp = VN_CAST(nodep, CCall)) { + callp->user1p(nullptr); + } + }); + + // Return the local scope + return lscopep; + } + + void doInlining() { + // Need to gather vertices as we are changing the graph + std::vector m_fVtxps; + for (V3GraphVertex& vtx : m_graph.vertices()) { + if (InlineCFuncsFunctionVertex* const fVtxp = vtx.cast()) { + m_fVtxps.emplace_back(fVtxp); + } + } + + // Iterate functions leaf to root + for (InlineCFuncsFunctionVertex* const calleeVtxp : vlstd::reverse_view(m_fVtxps)) { + // Should we inline this function? + if (calleeVtxp->noInline()) continue; // Told not to + + // Check size heuristics + const bool doIt = [&]() { + // Inline if small enough + if (calleeVtxp->size() <= m_sizeThreshold) return true; + // Inline if not too much bloat + const size_t nCalls = calleeVtxp->inEdges().size(); + if (nCalls * calleeVtxp->size() <= m_prodThreshold) return true; + // Otherwise don't inline + return false; + }(); + if (!doIt) continue; + + // Ok, attempt to inline call sites + size_t nInlined = 0; + for (const V3GraphEdge* const edgep : calleeVtxp->inEdges().unlinkable()) { + InlineCFuncsCallSiteVertex* const callVtxp + = edgep->fromp()->as(); + + AstCFunc* const calleep = calleeVtxp->cfuncp(); + AstCCall* const callp = callVtxp->callp(); + UINFO(6, "Consider inlining " << calleep->name() << " at call site " << callp); + // Should we inline this call site? + if (callVtxp->noInline()) continue; // Told not to + if (callVtxp->inEmpty()) continue; // Don't know where it's called from + + // Pick up the caller + UASSERT_OBJ(callVtxp->inSize1(), callVtxp->callp(), + "Expected exactly one input edge for call site"); + InlineCFuncsFunctionVertex* const callerVtxp + = callVtxp->inEdges().frontp()->fromp()->as(); + AstCFunc* const callerp = callerVtxp->cfuncp(); + + // Don't make a function bigger than the limit + const size_t limit = callerp->isTrace() ? m_maxSizeTrace : m_maxSizeCFunc; + if (callerVtxp->size() + calleeVtxp->size() > limit) continue; + + // Can't do it if it's in a different scope, self pointers differ + if (callerp->scopep() != calleep->scopep()) continue; + + // Inline it + if (!nInlined) ++m_statFuncsInlined; + AstNode* const inlinedp = inlineCall(callerp, callp, calleep, nInlined++); + + // Need to adjust the graph: + // 1. Delete inlined call site + VL_DO_DANGLING(callVtxp->unlinkDelete(&m_graph), callVtxp); + // 2. Add new inlined call sites - also increments size of caller + VL_RESTORER(m_cfuncVtxp); + m_cfuncVtxp = callerVtxp; + if (inlinedp) iterateChildrenConst(inlinedp); + } + } + } + + void removeUnusedFuncs() { + // Iterate root to leaves + for (V3GraphVertex* const vtxp : m_graph.vertices().unlinkable()) { + InlineCFuncsFunctionVertex* const fVtxp = vtxp->cast(); + if (!fVtxp) continue; + // Keep if still called + if (!fVtxp->inEmpty()) continue; + // Keep for other reasons + if (fVtxp->keep()) continue; + + AstCFunc* const funcp = fVtxp->cfuncp(); + UINFO(6, "Removing unused CFunc " << funcp); + ++m_statFuncsRemoved; + + // Unlink all call sites + for (const V3GraphEdge* const edgep : vtxp->outEdges().unlinkable()) { + edgep->top()->unlinkEdges(&m_graph); + } + // Delete function vertex + vtxp->unlinkDelete(&m_graph); + // Delete the function + VL_DO_DANGLING(pushDeletep(funcp->unlinkFrBack()), funcp); + } + + // Delete inlined/deleted call site vertices (for debugging only) + for (V3GraphVertex* const vtxp : m_graph.vertices().unlinkable()) { + InlineCFuncsCallSiteVertex* const cVtxp = vtxp->cast(); + if (!cVtxp) continue; + if (!cVtxp->inEmpty()) continue; + if (!cVtxp->outEmpty()) continue; + vtxp->unlinkDelete(&m_graph); + } } // VISITORS - void visit(AstCCall* nodep) override { - iterateChildren(nodep); - - AstCFunc* const cfuncp = nodep->funcp(); - if (!cfuncp) return; - - // Track call site for call counting - m_callSites(cfuncp).emplace_back(nodep); - } - void visit(AstCFunc* nodep) override { - VL_RESTORER(m_callerFuncp); - m_callerFuncp = nodep; - iterateChildren(nodep); + // Create the function vertex + InlineCFuncsFunctionVertex* const vtxp = getInlineCFuncsFunctionVertexp(nodep); + + // Check if the function itself is not inlineable + if (nodep->rtnTypeVoid() != "void") vtxp->setNoInline("Not void"); + if (nodep->dpiImportPrototype()) vtxp->setNoInline("DPI import prototype"); + if (nodep->recursive()) vtxp->setNoInline("Recursive"); + if (nodep->argsp()) vtxp->setNoInline("Has arguments"); + if (nodep->isVirtual()) vtxp->setNoInline("Virtual method"); + + // Check if the function should not be removed + if (nodep->entryPoint()) vtxp->setKeep("Entry point"); + if (nodep->dpiImportPrototype()) vtxp->setKeep("DPI import prototype"); + if (nodep->dpiExportDispatcher()) vtxp->setKeep("DPI export implementation"); + if (nodep->isVirtual()) vtxp->setKeep("Virtual method"); + + // Iterate children + VL_RESTORER(m_cfuncVtxp); + m_cfuncVtxp = vtxp; + iterateChildrenConst(nodep); } - void visit(AstNodeModule* nodep) override { - // Process per module for better cache behavior - m_toInline.clear(); + // Inlineable calls + void visit(AstCCall* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + AstCFunc* const calleep = nodep->funcp(); - // Phase 1: Collect call sites within this module - iterateChildren(nodep); + // Create the call site vertex + InlineCFuncsCallSiteVertex* const vtxp = getInlineCFuncsCallSiteVertexp(nodep); - // Phase 2: Determine which calls to inline - collectInlineCandidates(nodep); + // Check if the call site is not inlineable + if (!VN_IS(nodep->backp(), StmtExpr)) vtxp->setNoInline("Not in statement position"); + if (m_inExecGraph) vtxp->setNoInline("In ExecGraph"); + if (calleep->isVirtual()) vtxp->setNoInline("Virtual method"); - // Phase 3: Perform inlining for this module - doInlining(); + // Add caller/callee edges + if (m_cfuncVtxp) m_graph.addEdge(*m_cfuncVtxp, *vtxp); + m_graph.addEdge(*vtxp, *getInlineCFuncsFunctionVertexp(calleep)); + + // Iterate children + iterateChildrenConst(nodep); } - void visit(AstNode* nodep) override { iterateChildren(nodep); } + // Nodes that reference functions/calls + void visit(AstNetlist* nodep) override { + UASSERT_OBJ(!nodep->evalp(), nodep, "evalp should not be null at this stage"); + UASSERT_OBJ(!nodep->evalNbap(), nodep, "evalNbap should be null at this stage"); + iterateChildrenConst(nodep); + } - // Collect calls that should be inlined within this module - void collectInlineCandidates(AstNodeModule* modp) { - for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { - AstCFunc* const callerp = VN_CAST(stmtp, CFunc); - if (!callerp) continue; + void visit(AstNodeCCall* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + getInlineCFuncsFunctionVertexp(nodep->funcp())->setKeep("Called elsewhere"); + iterateChildrenConst(nodep); + } - callerp->foreach([&](AstCCall* callp) { - AstCFunc* const cfuncp = callp->funcp(); - if (!cfuncp) return; - if (!isInlineable(callerp, cfuncp)) return; + void visit(AstAddrOfCFunc* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + getInlineCFuncsFunctionVertexp(nodep->funcp())->setKeep("Referenced by AddressOfCFunc"); + iterateChildrenConst(nodep); + } - // Walk up to find the containing StmtExpr - AstNode* stmtNodep = callp; - while (stmtNodep && !VN_IS(stmtNodep, StmtExpr) && !VN_IS(stmtNodep, CFunc)) { - stmtNodep = stmtNodep->backp(); - } + // Nodes preventing inlining + void visit(AstTraceDecl* nodep) override { + // Referenced by TraceInc + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains TraceDecl"); - AstStmtExpr* const stmtExprp = VN_CAST(stmtNodep, StmtExpr); - if (!stmtExprp) return; - - m_toInline.emplace_back(stmtExprp, cfuncp, callerp); - }); + if (AstCCall* const callp = nodep->dtypeCallp()) { + getInlineCFuncsCallSiteVertexp(callp)->setNoInline("Referenced by TraceDecl"); } + iterateChildrenConst(nodep); + } + void visit(AstExecGraph* nodep) override { + // AstExecGraph is not cloneable, so can't inline the containing function + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains ExecGraph"); + // Also mark functions referenced in the dependency graph + for (const V3GraphVertex& vtx : nodep->depGraphp()->vertices()) { + getInlineCFuncsFunctionVertexp(vtx.as()->funcp()) + ->setKeep("MTask function"); + } + VL_RESTORER(m_inExecGraph); + m_inExecGraph = true; + iterateChildrenConst(nodep); + } + void visit(AstCStmt* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CStmt"); + iterateChildrenConst(nodep); + } + void visit(AstCExpr* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CExpr"); + iterateChildrenConst(nodep); + } + void visit(AstCStmtUser* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CStmtUser"); + iterateChildrenConst(nodep); + } + void visit(AstCExprUser* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CExprUser"); + iterateChildrenConst(nodep); + } + void visit(AstCReturn* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CReturn"); + iterateChildrenConst(nodep); } - // Perform the actual inlining after iteration is complete - void doInlining() { - for (const auto& tuple : m_toInline) { - AstStmtExpr* const stmtExprp = std::get<0>(tuple); - AstCFunc* const cfuncp = std::get<1>(tuple); - AstCFunc* const callerp = std::get<2>(tuple); - - UINFO(6, "Inlining CFunc " << cfuncp->name() << " into " << callerp->name()); - ++m_statInlined; - - // Clone local variables with unique names to avoid collisions - std::map varMap; - for (AstVar* varp = cfuncp->varsp(); varp; varp = VN_AS(varp->nextp(), Var)) { - const string newName = "__Vinline_" + cfuncp->name() + "_" + varp->name(); - AstVar* const newVarp = varp->cloneTree(false); - newVarp->name(newName); - callerp->addVarsp(newVarp); - varMap[varp] = newVarp; - } - - // Clone the function body - AstNode* const bodyp = cfuncp->stmtsp()->cloneTree(true); - - // Retarget variable references to the cloned variables - // Must iterate all sibling statements, not just the first - if (!varMap.empty()) { - for (AstNode* stmtp = bodyp; stmtp; stmtp = stmtp->nextp()) { - stmtp->foreach([&](AstVarRef* refp) { - auto it = varMap.find(refp->varp()); - if (it != varMap.end()) refp->varp(it->second); - }); - } - } - - // Replace the statement with the inlined body - stmtExprp->addNextHere(bodyp); - VL_DO_DANGLING(stmtExprp->unlinkFrBack()->deleteTree(), stmtExprp); - } + // Base node + void visit(AstNode* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + iterateChildrenConst(nodep); } public: // CONSTRUCTORS - explicit InlineCFuncsVisitor(const AstNetlist* nodep) - : m_threshold1{v3Global.opt.inlineCFuncs()} - , m_threshold2{v3Global.opt.inlineCFuncsProduct()} { - // Don't inline when profiling or tracing - if (v3Global.opt.profCFuncs() || v3Global.opt.trace()) return; - // Process modules one at a time for better cache behavior - iterateAndNextNull(nodep->modulesp()); + explicit InlineCFuncsVisitor(AstNetlist* nodep) { + // Phase 1: Build call graph + iterateConst(nodep); + // Make acyclic in case there is recursion + m_graph.acyclic(V3GraphEdge::followAlwaysTrue); + // Order vertices (any topological order is fine) + m_graph.order(); + if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-graph"); + // Phase 2: Inline calls + doInlining(); + if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-inlined"); + // Phase 3: Remove unused functions + removeUnusedFuncs(); + if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-kept"); } ~InlineCFuncsVisitor() override { - V3Stats::addStat("Optimizations, Inlined CFuncs", m_statInlined); + V3Stats::addStat("Optimizations, Inline CFuncs, calls inlined", m_statCallsInlined); + V3Stats::addStat("Optimizations, Inline CFuncs, functions inlined", m_statFuncsInlined); + V3Stats::addStat("Optimizations, Inline CFuncs, functions removed", m_statFuncsRemoved); } }; @@ -264,6 +497,8 @@ public: void V3InlineCFuncs::inlineAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); + // Don't inline when profiling per-function (it would lose granularity) + if (v3Global.opt.profCFuncs()) return; { InlineCFuncsVisitor{nodep}; } // Destruct before checking V3Global::dumpCheckGlobalTree("inlinecfuncs", 0, dumpTreeEitherLevel() >= 6); } diff --git a/src/V3Life.cpp b/src/V3Life.cpp index 77ac85c87..7b4d5ab1a 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -60,7 +60,7 @@ public: class LifeVarEntry final { // Last assignment to this varscope, nullptr if no longer relevant - AstNodeStmt* m_assignp = nullptr; + AstNodeStmt* m_assignp = nullptr; // Last simple assignment AstConst* m_constp = nullptr; // Known constant value bool m_isNew = true; // Is just created // First access was a set (and thus block above may have a set that can be deleted @@ -77,6 +77,17 @@ public: m_isNew = false; m_setBeforeUse = setBeforeUse; } + string ascii() const { // LCOV_EXCL_START + std::ostringstream os; + os << "[Life "; + if (m_isNew) os << " isNew"; + if (m_setBeforeUse) os << " setBeforeUse"; + if (m_everSet) os << " everSet"; + if (m_assignp) os << " assignp=" << AstNode::nodeAddr(m_assignp); + if (m_constp) os << " constp=" << AstNode::nodeAddr(m_constp); + os << "]"; + return os.str(); + } // LCOV_EXCL_STOP void simpleAssign(AstNodeStmt* nodep) { // New simple A=.... assignment UASSERT_OBJ(!m_isNew, nodep, "Uninitialized new entry"); @@ -84,7 +95,10 @@ public: m_constp = nullptr; m_everSet = true; if (AstNodeAssign* const assp = VN_CAST(nodep, Assign)) { - if (VN_IS(assp->rhsp(), Const)) m_constp = VN_AS(assp->rhsp(), Const); + if (VN_IS(assp->rhsp(), Const)) { + m_constp = VN_AS(assp->rhsp(), Const); + UINFO(9, "assign-const " << assp->rhsp() << " = " << m_constp); + } } } void complexAssign() { // A[x]=... or some complicated assignment @@ -179,6 +193,7 @@ public: // Aha, variable is constant; substitute in. // We'll later constant propagate UINFO(4, " replaceconst: " << varrefp); + UINFO(9, " replaceval: " << constp); varrefp->replaceWith(constp->cloneTree(false)); m_replacedVref = true; VL_DO_DANGLING(varrefp->deleteTree(), varrefp); @@ -264,7 +279,9 @@ class LifeVisitor final : public VNVisitor { LifeBlock* m_lifep = nullptr; // Current active lifetime map for current scope // METHODS - void setNoopt() { + void setNoopt(const char* reasonp) { + (void)reasonp; + // UINFO(9, "setNoopt " << reasonp); m_noopt = true; m_lifep->clear(); } @@ -310,7 +327,7 @@ class LifeVisitor final : public VNVisitor { void visit(AstNodeAssign* nodep) override { if (nodep->isTimingControl() || VN_IS(nodep, AssignForce)) { // V3Life doesn't understand time sense nor force assigns - don't optimize - setNoopt(); + setNoopt("timing|force"); if (nodep->isTimingControl()) m_containsTiming = true; iterateChildren(nodep); return; @@ -325,7 +342,7 @@ class LifeVisitor final : public VNVisitor { // V3Life doesn't understand time sense if (nodep->isTimingControl()) { // Don't optimize - setNoopt(); + setNoopt("assigndly"); m_containsTiming = true; } // Don't treat as normal assign @@ -337,19 +354,19 @@ class LifeVisitor final : public VNVisitor { UINFO(4, " IF " << nodep); // Condition is part of PREVIOUS block iterateAndNextNull(nodep->condp()); - LifeBlock* const prevLifep = m_lifep; - LifeBlock* const ifLifep = new LifeBlock{prevLifep, m_statep}; - LifeBlock* const elseLifep = new LifeBlock{prevLifep, m_statep}; + LifeBlock* const ifLifep = new LifeBlock{m_lifep, m_statep}; + LifeBlock* const elseLifep = new LifeBlock{m_lifep, m_statep}; { + VL_RESTORER(m_lifep); m_lifep = ifLifep; iterateAndNextNull(nodep->thensp()); } { + VL_RESTORER(m_lifep); m_lifep = elseLifep; iterateAndNextNull(nodep->elsesp()); } - m_lifep = prevLifep; - UINFO(4, " join "); + UINFO(4, " join " << nodep); // Find sets on both flows m_lifep->dualBranch(ifLifep, elseLifep); // For the next assignments, clear any variables that were read or written in the block @@ -357,6 +374,7 @@ class LifeVisitor final : public VNVisitor { elseLifep->lifeToAbove(); VL_DO_DANGLING(delete ifLifep, ifLifep); VL_DO_DANGLING(delete elseLifep, elseLifep); + UINFO(4, " if-done " << nodep); } void visit(AstLoop* nodep) override { // Similar problem to AstJumpBlock, don't optimize loop bodies - most are unrolled @@ -366,14 +384,14 @@ class LifeVisitor final : public VNVisitor { VL_RESTORER(m_noopt); VL_RESTORER(m_lifep); m_lifep = new LifeBlock{m_lifep, m_statep}; - setNoopt(); + setNoopt("loop"); iterateAndNextNull(nodep->stmtsp()); UINFO(4, " joinloop"); // For the next assignments, clear any variables that were read or written in the block m_lifep->lifeToAbove(); VL_DO_DANGLING(delete m_lifep, m_lifep); } - if (m_containsTiming) setNoopt(); + if (m_containsTiming) setNoopt("timing"); } void visit(AstJumpBlock* nodep) override { // As with Loop's we can't predict if a JumpGo will kill us or not @@ -384,14 +402,14 @@ class LifeVisitor final : public VNVisitor { VL_RESTORER(m_noopt); VL_RESTORER(m_lifep); m_lifep = new LifeBlock{m_lifep, m_statep}; - setNoopt(); + setNoopt("jumpblock"); iterateAndNextNull(nodep->stmtsp()); UINFO(4, " joinjump"); // For the next assignments, clear any variables that were read or written in the block m_lifep->lifeToAbove(); VL_DO_DANGLING(delete m_lifep, m_lifep); } - if (m_containsTiming) setNoopt(); + if (m_containsTiming) setNoopt("timing"); } void visit(AstNodeCCall* nodep) override { // UINFO(4, " CCALL " << nodep); @@ -399,7 +417,7 @@ class LifeVisitor final : public VNVisitor { // Enter the function and trace it // else is non-inline or public function we optimize separately if (nodep->funcp()->entryPoint()) { - setNoopt(); + setNoopt("ccall"); } else { m_tracingCall = true; iterate(nodep->funcp()); @@ -409,8 +427,8 @@ class LifeVisitor final : public VNVisitor { // UINFO(4, " CFUNC " << nodep); if (!m_tracingCall && !nodep->entryPoint()) return; m_tracingCall = false; - if (nodep->recursive()) setNoopt(); - if (nodep->noLife()) setNoopt(); + if (nodep->recursive()) setNoopt("recursive"); + if (nodep->noLife()) setNoopt("nolife"); if (nodep->dpiImportPrototype() && !nodep->dpiPure()) { m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment } @@ -429,7 +447,7 @@ class LifeVisitor final : public VNVisitor { void visit(AstNode* nodep) override { if (nodep->isTimingControl()) { // V3Life doesn't understand time sense - don't optimize - setNoopt(); + setNoopt("timing"); m_containsTiming = true; } iterateChildren(nodep); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index a04b7bbdf..7dd5fe3ea 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1727,7 +1727,6 @@ class LinkDotFindVisitor final : public VNVisitor { newvarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); newvarp->funcReturn(true); newvarp->trace(false); // Not user visible - newvarp->attrIsolateAssign(nodep->attrIsolateAssign()); nodep->fvarp(newvarp); // Explicit insert required, as the var name shadows the upper level's task name m_statep->insertSym(m_curSymp, newvarp->name(), newvarp, nullptr /*classOrPackagep*/); @@ -1994,6 +1993,17 @@ class LinkDotFindVisitor final : public VNVisitor { // No need to insert, only the real typedef matters, but need to track for errors nodep->user1p(m_curSymp); } + void visit(AstNodeUOrStructDType* nodep) override { // FindVisitor:: + UASSERT_OBJ(m_curSymp, nodep, "Struct/union dtype not under module/package/$unit"); + VL_RESTORER(m_curSymp); + m_curSymp = m_statep->insertBlock(m_curSymp, "__Vdtype" + cvtToStr(nodep->uniqueNum()), + nodep, m_classOrPackagep); + iterateChildren(nodep); + } + void visit(AstMemberDType* nodep) override { // FindVisitor:: + iterateChildren(nodep); + m_statep->insertSym(m_curSymp, nodep->name(), nodep, m_classOrPackagep); + } void visit(AstParamTypeDType* nodep) override { // FindVisitor:: UASSERT_OBJ(m_curSymp, nodep, "Parameter type not under module/package/$unit"); @@ -3512,6 +3522,20 @@ class LinkDotResolveVisitor final : public VNVisitor { << declp->warnContextSecondary()); } } + void checkMemberDeclOrder(AstNode* nodep, AstMemberDType* declp) { + const uint32_t declTokenNum = declp->fileline()->tokenNum(); + if (nodep->fileline()->tokenNum() < declTokenNum) { + UINFO(1, "Related node " << nodep->fileline()->tokenNum() << " " << nodep); + UINFO(1, "Related decl " << declTokenNum << " " << declp); + nodep->v3error("Reference to " + << nodep->prettyNameQ() << " before declaration (IEEE 1800-2023 6.18)\n" + << nodep->warnMore() + << "... Suggest move the declaration before the reference\n" + << nodep->warnContextPrimary() << '\n' + << declp->warnOther() << "... Location of original declaration\n" + << declp->warnContextSecondary()); + } + } void replaceWithCheckBreak(AstNode* oldp, AstNodeDType* newp) { // Flag now to avoid V3Broken throwing an internal error @@ -3537,6 +3561,21 @@ class LinkDotResolveVisitor final : public VNVisitor { if (nodep && nodep->isParam()) nodep->usedParam(true); } + static VSymEnt* findIdFallbackSkipMemberDType(VSymEnt* lookp, const string& name) { + VSymEnt* shadowEntp = nullptr; // Shadowing variable: not a type, kept for error report + while (lookp) { + VSymEnt* const foundp = lookp->findIdFlat(name); + if (foundp && !VN_IS(foundp->nodep(), MemberDType)) { + // A variable is not a type candidate (IEEE 1800-2023 6.18); skip it so an + // enclosing type is found, but keep it to preserve the "found: VAR" error. + if (!VN_IS(foundp->nodep(), Var)) return foundp; + if (!shadowEntp) shadowEntp = foundp; + } + lookp = lookp->fallbackp(); + } + return shadowEntp; + } + void symIterateChildren(AstNode* nodep, VSymEnt* symp) { // Iterate children, changing to given context, with restore to old context VL_RESTORER(m_ds); @@ -4533,6 +4572,16 @@ class LinkDotResolveVisitor final : public VNVisitor { } else { (void)defp; // Prevent unused variable warning } + } else if (AstMemberDType* const defp = VN_CAST(foundp->nodep(), MemberDType)) { + if (allowVar) { + checkMemberDeclOrder(nodep, defp); + AstRefDType* const refp = new AstRefDType{nodep->fileline(), nodep->name()}; + refp->refDTypep(defp); + replaceWithCheckBreak(nodep, refp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + ok = true; + m_ds.m_dotText = ""; + } } else if (AstEnumItem* const valuep = VN_CAST(foundp->nodep(), EnumItem)) { if (allowVar) { AstNode* const newp @@ -4968,6 +5017,10 @@ class LinkDotResolveVisitor final : public VNVisitor { refdtypep->v3error("Self-referential enumerated type definition"); } } + void visit(AstNodeUOrStructDType* nodep) override { + LINKDOT_VISIT_START(); + symIterateChildren(nodep, m_statep->getNodeSym(nodep)); + } void visit(AstEnumItemRef* nodep) override { // Resolve its reference // EnumItemRefs are created by the first pass, but V3Param may regenerate due to @@ -5985,7 +6038,7 @@ class LinkDotResolveVisitor final : public VNVisitor { if (nodep->classOrPackagep()) { foundp = m_statep->getNodeSym(nodep->classOrPackagep())->findIdFlat(nodep->name()); } else if (m_ds.m_dotPos == DP_FIRST || m_ds.m_dotPos == DP_NONE) { - foundp = m_curSymp->findIdFallback(nodep->name()); + foundp = findIdFallbackSkipMemberDType(m_curSymp, nodep->name()); } else { // Defensive: dotPos should be DP_FIRST/DP_NONE or classOrPackagep set. v3fatalSrc("Unexpected dotPos=" diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 21e3cdad9..70115033f 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -262,6 +262,9 @@ class LinkJumpVisitor final : public VNVisitor { if (it != m_beginDisableBegins.end()) return it->second; AstBegin* const beginBodyp = new AstBegin{fl, "", nullptr, false}; + // Disable-by-name rewrites kill this detached block-body process, so mark it as process + // backed to ensure fork/join kill-accounting hooks are always emitted. + beginBodyp->setNeedProcess(); if (beginp->stmtsp()) beginBodyp->addStmtsp(beginp->stmtsp()->unlinkFrBackWithNext()); AstFork* const forkp = new AstFork{fl, VJoinType::JOIN}; diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp index 012c87166..066097239 100644 --- a/src/V3LinkLevel.cpp +++ b/src/V3LinkLevel.cpp @@ -291,6 +291,7 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { varp->sigPublic(true); // User needs to be able to get to it... oldvarp->primaryIO(false); varp->primaryIO(true); + varp->icoMaybeWritten(oldvarp->icoMaybeWritten()); if (varp->isRef() || varp->isConstRef()) { varp->v3warn(E_UNSUPPORTED, "Unsupported: ref/const ref as primary input/output: " diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index e6beb5760..5403d77c2 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -606,10 +606,6 @@ class LinkParseVisitor final : public VNVisitor { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->sigUserRWPublic(true); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } else if (nodep->attrType() == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { - UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); - m_varp->attrIsolateAssign(true); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (nodep->attrType() == VAttrType::VAR_SFORMAT) { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->attrSFormat(true); diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 8adf7ce78..a9c505359 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -1308,9 +1308,17 @@ uint32_t V3Number::mostSetBitP1() const { } return 0; } + +uint32_t V3Number::leastSetBitP1() const { + for (int bit = 0; bit < width(); ++bit) { + if (!bitIs0(bit)) return bit + 1; + } + return 0; +} + //====================================================================== -V3Number& V3Number::opBitsNonX(const V3Number& lhs) { // 0/1->1, X/Z->0 +V3Number& V3Number::opBitsNonXZ(const V3Number& lhs) { // 0/1->1, X/Z->0 // Correct number of zero bits/width matters // op i, L(lhs) bit return NUM_ASSERT_OP_ARGS1(lhs); diff --git a/src/V3Number.h b/src/V3Number.h index 771f5edd4..3791f2f19 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -728,6 +728,7 @@ public: uint32_t countBits(const V3Number& ctrl1, const V3Number& ctrl2, const V3Number& ctrl3) const; uint32_t countOnes() const; uint32_t mostSetBitP1() const; // Highest bit set + 1, e.g. for 16 return 5, for 0 return 0 + uint32_t leastSetBitP1() const; // Lowest bit set + 1, e.g. for 14 return 2, for 0 return 0 // Operators bool operator<(const V3Number& rhs) const { return isLtXZ(rhs); } @@ -741,7 +742,7 @@ public: // MATH // "this" is the output, as we need the output width before some computations - V3Number& opBitsNonX(const V3Number& lhs); // 0/1->1, X/Z->0 + V3Number& opBitsNonXZ(const V3Number& lhs); // 0/1->1, X/Z->0 V3Number& opBitsOne(const V3Number& lhs); // 1->1, 0/X/Z->0 V3Number& opBitsXZ(const V3Number& lhs); // 0/1->0, X/Z->1 V3Number& opBitsZ(const V3Number& lhs); // Z->1, 0/1/X->0 diff --git a/src/V3Options.cpp b/src/V3Options.cpp index b8d7a0a29..a23a408b2 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1077,6 +1077,9 @@ void V3Options::notify() VL_MT_DISABLED { // Preprocessor defines based on options used if (timing().isSetTrue()) V3PreShell::defineCmdLine("VERILATOR_TIMING", "1"); + // If VPI is used, and no explicit ico change detect option was passed, disable it by default + if (m_vpi && m_fIcoChangeDetect.isDefault()) m_fIcoChangeDetect.setTrueOrFalse(false); + // === Leave last // Mark options as available m_available = true; @@ -1445,7 +1448,14 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-facyc-simp", FOnOff, &m_fAcycSimp); DECL_OPTION("-fassemble", FOnOff, &m_fAssemble); - DECL_OPTION("-fcase", FOnOff, &m_fCase); + DECL_OPTION("-fcase", CbFOnOff, [this](bool flag) { + m_fCaseDecoder = flag; + m_fCaseTable = flag; + m_fCaseTree = flag; + }); + DECL_OPTION("-fcase-decoder", FOnOff, &m_fCaseDecoder); + DECL_OPTION("-fcase-table", FOnOff, &m_fCaseTable); + DECL_OPTION("-fcase-tree", FOnOff, &m_fCaseTree); DECL_OPTION("-fcombine", FOnOff, &m_fCombine); DECL_OPTION("-fconst", FOnOff, &m_fConst); DECL_OPTION("-fconst-before-dfg", FOnOff, &m_fConstBeforeDfg); @@ -1483,7 +1493,11 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-ffunc-opt-balance-cat", FOnOff, &m_fFuncBalanceCat); DECL_OPTION("-ffunc-opt-split-cat", FOnOff, &m_fFuncSplitCat); DECL_OPTION("-fgate", FOnOff, &m_fGate); + DECL_OPTION("-fico-change-detect", CbFOnOff, [this](bool flag) { // + m_fIcoChangeDetect.setTrueOrFalse(flag); + }); DECL_OPTION("-finline", FOnOff, &m_fInline); + DECL_OPTION("-finline-cfuncs", FOnOff, &m_fInlineCFuncs); DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs); DECL_OPTION("-finline-funcs-eager", FOnOff, &m_fInlineFuncsEager); DECL_OPTION("-flife", FOnOff, &m_fLife); @@ -2345,7 +2359,9 @@ void V3Options::optimize(int level) { const bool flag = level > 0; m_fAcycSimp = flag; m_fAssemble = flag; - m_fCase = flag; + m_fCaseDecoder = flag; + m_fCaseTable = flag; + m_fCaseTree = flag; m_fCombine = flag; m_fConst = flag; m_fConstBitOpTree = flag; @@ -2356,6 +2372,7 @@ void V3Options::optimize(int level) { m_fExpand = flag; m_fGate = flag; m_fInline = flag; + m_fInlineCFuncs = flag; m_fLife = flag; m_fLifePost = flag; m_fLocalize = flag; diff --git a/src/V3Options.h b/src/V3Options.h index 729c68f47..f3dd59863 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -392,7 +392,9 @@ private: // MEMBERS (optimizations) bool m_fAcycSimp; // main switch: -fno-acyc-simp: acyclic pre-optimizations bool m_fAssemble; // main switch: -fno-assemble: assign assemble - bool m_fCase; // main switch: -fno-case: case tree conversion + bool m_fCaseDecoder; // main switch: -fno-case-decoder: case decoder conversion + bool m_fCaseTable; // main switch: -fno-case-table: case table conversion + bool m_fCaseTree; // main switch: -fno-case-tree: case tree conversion bool m_fCombine; // main switch: -fno-combine: common icode packing bool m_fConst; // main switch: -fno-const: constant folding bool m_fConstBeforeDfg = true; // main switch: -fno-const-before-dfg for testing only! @@ -410,7 +412,10 @@ private: bool m_fFuncBalanceCat = true; // main switch: -fno-func-balance-cat: expansion of C macros bool m_fFuncSplitCat = true; // main switch: -fno-func-split-cat: expansion of C macros bool m_fGate; // main switch: -fno-gate: gate wire elimination + // main switch: -fno-ico-change-detect: input change detection optimization + VOptionBool m_fIcoChangeDetect{VOptionBool::OPT_DEFAULT_TRUE}; bool m_fInline; // main switch: -fno-inline: module inlining + bool m_fInlineCFuncs; // main switch: -fno-inline-cfuncs: inline small C functions bool m_fInlineFuncs = true; // main switch: -fno-inline-funcs: function inlining bool m_fInlineFuncsEager = true; // main switch: -fno-inline-funcs-eager: don't inline eagerly bool m_fLife; // main switch: -fno-life: variable lifetime @@ -547,6 +552,9 @@ public: bool decorationNodes() const VL_MT_SAFE { return m_decorationNodes; } bool diagnosticsSarif() const VL_MT_SAFE { return m_diagnosticsSarif; } bool dpiHdrOnly() const { return m_dpiHdrOnly; } + bool dumpAstPatterns() const { + return m_dumpLevel.count("ast-patterns") && m_dumpLevel.at("ast-patterns"); + } bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); } bool dumpDfgPatterns() const { return m_dumpLevel.count("dfg-patterns") && m_dumpLevel.at("dfg-patterns"); @@ -723,7 +731,9 @@ public: // ACCESSORS (optimization options) bool fAcycSimp() const { return m_fAcycSimp; } bool fAssemble() const { return m_fAssemble; } - bool fCase() const { return m_fCase; } + bool fCaseDecoder() const { return m_fCaseDecoder; } + bool fCaseTable() const { return m_fCaseTable; } + bool fCaseTree() const { return m_fCaseTree; } bool fCombine() const { return m_fCombine; } bool fConst() const { return m_fConst; } bool fConstBeforeDfg() const { return m_fConstBeforeDfg; } @@ -745,7 +755,9 @@ public: bool fFuncSplitCat() const { return m_fFuncSplitCat; } bool fFunc() const { return fFuncSplitCat() || fFuncBalanceCat(); } bool fGate() const { return m_fGate; } + VOptionBool fIcoChangeDetect() const { return m_fIcoChangeDetect; } bool fInline() const { return m_fInline; } + bool fInlineCFuncs() const { return m_fInlineCFuncs; } bool fInlineFuncs() const { return m_fInlineFuncs; } bool fInlineFuncsEager() const { return m_fInlineFuncsEager; } bool fLife() const { return m_fLife; } diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 063c55d3a..0cf109211 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1348,6 +1348,17 @@ class ParamProcessor final { } } + // True if a $bits/$size type query in nodep's parameters reads another type parameter. + static bool defaultParamsHaveTypeQueryOnParamType(const AstClassRefDType* nodep) { + bool found = false; + nodep->foreach([&](const AstAttrOf* attrp) { + if (found || !attrp->attrType().isTypeQuery()) return; + const AstRefDType* const refp = VN_CAST(attrp->fromp(), RefDType); + if (refp && VN_IS(refp->refDTypep(), ParamTypeDType)) found = true; + }); + return found; + } + // Check if exprp's class matches origp's class after deparameterization. // Handles both the simple case (user4p link from defaultsResolved) and the // nested case where the default's inner class has non-default sub-parameters @@ -1362,6 +1373,9 @@ class ParamProcessor final { const AstNodeModule* const defaultClonep = VN_CAST(origClassRefp->classp()->user4p(), Class); if (defaultClonep && defaultClonep == exprClassRefp->classp()) return true; + // Skip the comparison when the default's $bits/$size reads another type parameter, as + // deparameterizing it below would resolve that shared type at the wrong width (#7711). + if (defaultParamsHaveTypeQueryOnParamType(origClassRefp)) return false; // Slow path: deparameterize the default type and compare the result. // Different templates can never match; use origName() because exprp's // class may already be a specialization (clone) of the template. diff --git a/src/V3PatternStats.cpp b/src/V3PatternStats.cpp new file mode 100644 index 000000000..8d4d5a63d --- /dev/null +++ b/src/V3PatternStats.cpp @@ -0,0 +1,129 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Dump data structure pattern frequencies for analysis +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "V3PchAstNoMT.h" + +#include "V3Ast.h" +#include "V3AstPatterns.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3File.h" + +#include +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +//============================================================================ +// Accumulates and dumps the pattern statistics + +class V3PatternStats VL_NOT_FINAL { +public: + static constexpr uint32_t MIN_PATTERN_DEPTH = 1; + static constexpr uint32_t MAX_PATTERN_DEPTH = 4; + +private: + const std::string m_what; // Description of what is being dumped + // Maps from pattern to the number of times it appears, for each pattern depth + std::vector> m_patternCounts{MAX_PATTERN_DEPTH + 1}; + + void dump(std::ostream& os) { + using Line = std::pair; + for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { + os << m_what << " patterns with depth " << i << '\n'; + + // Pick up pattern accumulators with given depth + auto& patternCounts = m_patternCounts[i]; + + // Remove patterns also present at shallower depths + for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) { + for (const auto& pair : m_patternCounts[j]) patternCounts.erase(pair.first); + } + + // Sort patterns, first by descending frequency, then lexically + std::vector lines; + lines.reserve(patternCounts.size()); + for (const auto& pair : patternCounts) lines.emplace_back(pair); + std::sort(lines.begin(), lines.end(), [](const Line& a, const Line& b) { + if (a.second != b.second) return a.second > b.second; + return a.first < b.first; + }); + + // Print each pattern + for (const auto& line : lines) { + os << ' ' << std::setw(12) << std::right << line.second; + os << ' ' << std::left << line.first << '\n'; + } + + // Trailing new-line to separate sections + os << '\n'; + } + } + +public: + V3PatternStats(const std::string& what) + : m_what{what} {} + ~V3PatternStats() = default; + + void accumulate(const std::string& pattern, uint32_t depth) { + m_patternCounts[depth][pattern] += 1; + } + + void dumpToFile(const std::string& filename) { + // Open, write, close + const std::unique_ptr ofp{V3File::new_ofstream(filename)}; + if (ofp->fail()) v3fatal("Can't write file: " << filename); + dump(*ofp); + } +}; + +//============================================================================ +// V3AstPatterns top level + +void V3AstPatterns::dumpAll(const AstNetlist* nodep, const std::string& suffix) { + UINFO(2, __FUNCTION__ << ":"); + V3PatternStats patternStats{"AST"}; + nodep->foreach([&](const AstNodeExpr* exprp) { + for (uint32_t i = V3PatternStats::MIN_PATTERN_DEPTH; + i <= V3PatternStats::MAX_PATTERN_DEPTH; ++i) { + patternStats.accumulate(exprp->patternString(i), i); + } + }); + const std::string fileName = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + + "__ast_patterns_" + suffix + ".txt"; + patternStats.dumpToFile(fileName); + V3Global::dumpCheckGlobalTree("astpatterns", 0, false, false); +} + +//============================================================================ +// V3DfgPasses top level + +void V3DfgPasses::dumpPatterns(const std::vector>& graphs, + const std::string& suffix) { + V3PatternStats patternStats{"DFG"}; + for (auto& cp : graphs) { + cp->forEachVertex([&](const DfgVertex& vtx) { + for (uint32_t i = V3PatternStats::MIN_PATTERN_DEPTH; + i <= V3PatternStats::MAX_PATTERN_DEPTH; ++i) { + patternStats.accumulate(vtx.patternString(i), i); + } + }); + } + const std::string fileName = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + + "__dfg_patterns" + suffix + ".txt"; + patternStats.dumpToFile(fileName); +} diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 50c23da38..c5a9fa9d1 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -139,34 +139,52 @@ class PremitVisitor final : public VNVisitor { } void visitShift(AstNodeBiop* nodep) { - // Shifts of > 32/64 bits in C++ will wrap-around and generate non-0s UINFO(4, " ShiftFix " << nodep); - const AstConst* const shiftp = VN_CAST(nodep->rhsp(), Const); - if (shiftp && shiftp->num().mostSetBitP1() > 32) { - shiftp->v3warn( - E_UNSUPPORTED, - "Unsupported: Shifting of by over 32-bit number isn't supported." - << " (This isn't a shift of 32 bits, but a shift of 2^32, or 4 billion!)\n"); - } - if (nodep->widthMin() <= 64 // Else we'll use large operators which work right - // C operator's width must be < maximum shift which is - // based on Verilog width - && nodep->width() < (1LL << nodep->rhsp()->widthMin())) { - AstNode* newp; - if (VN_IS(nodep, ShiftL)) { - newp = new AstShiftLOvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), - nodep->rhsp()->unlinkFrBack()}; - } else if (VN_IS(nodep, ShiftR)) { - newp = new AstShiftROvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), - nodep->rhsp()->unlinkFrBack()}; - } else { - UASSERT_OBJ(VN_IS(nodep, ShiftRS), nodep, "Bad case"); - newp = new AstShiftRSOvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), - nodep->rhsp()->unlinkFrBack()}; + UASSERT_OBJ(VN_IS(nodep, ShiftL) || VN_IS(nodep, ShiftR) || VN_IS(nodep, ShiftRS), nodep, + "Bad case"); + // Shift larger than the width of the type (overshift) is undefined behavour in C++ + // (in practice will shift by the wrapped shift amount). These are requierd to go to + // zero/msbs, so replacing them here. + FileLine* const flp = nodep->fileline(); + if (const AstConst* const shiftp = VN_CAST(nodep->rhsp(), Const)) { + // Shift amount known to be constant. If oversized shift, replace with zero/msbs. + // Otherwise we can leave the original shifts which have better constant folding + // than the *Ovr versions. + const bool isOversized = shiftp->num().mostSetBitP1() > 32 // + || (shiftp->num().toSQuad() >= nodep->width()); + if (isOversized) { + AstNodeExpr* newp = nullptr; + if (VN_IS(nodep, ShiftRS)) { + AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const msbp = new AstSel{flp, lhsp, nodep->width() - 1, 1}; + newp = new AstExtendS{flp, msbp, nodep->width()}; + } else { + newp = new AstConst{flp, AstConst::DTyped{}, nodep->dtypep()}; + } + nodep->replaceWithKeepDType(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } else { + // Shift amount not known at compile time. Convert to *Ovr version. Don't need to do + // if it would use a wide operation which works correctly at runtime, of if the max + // value of the shift amount is less than the with of the shifted value. + if (nodep->widthMin() <= VL_QUADSIZE + && (nodep->width() < (1LL << nodep->rhsp()->widthMin()))) { + AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); + AstNodeExpr* newp = nullptr; + if (VN_IS(nodep, ShiftL)) { + newp = new AstShiftLOvr{flp, lhsp, rhsp}; + } else if (VN_IS(nodep, ShiftR)) { + newp = new AstShiftROvr{flp, lhsp, rhsp}; + } else { + newp = new AstShiftRSOvr{flp, lhsp, rhsp}; + } + nodep->replaceWithKeepDType(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; } - nodep->replaceWithKeepDType(newp); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - return; } iterateChildren(nodep); checkNode(nodep); @@ -347,6 +365,14 @@ class PremitVisitor final : public VNVisitor { } checkNode(nodep); } + void visit(AstMatchMasked* nodep) override { + iterateChildren(nodep); + if (!nodep->user1SetOnce()) { + // Don't want this replicated by V3Expand + AstVar* const varp = createTemp(nodep); + varp->noSubst(true); // Do not re-inline in V3Subst + } + } void visit(AstCond* nodep) override { // Convert AstCond to AstIf in order to avoid evaluating // sub-expressions in both branches unconditionally. diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 3fcaf5535..36699526d 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -1509,6 +1509,41 @@ class ConstraintExprVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); iterate(sump); } + void visit(AstRedXor* nodep) override { + if (editFormat(nodep)) return; + + // Build popcount expansion: (extract x 1 1) ^ (extract x 2 2) ^ ... + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); + + AstNodeExpr* redxorp = new AstSel{fl, argp, 0, 1}; + redxorp->user1(true); + for (int i = 1; i < argp->width(); i++) { + AstSel* const selp = new AstSel{fl, argp->cloneTreePure(false), i, 1}; + selp->user1(true); + + redxorp = new AstXor{fl, redxorp, selp}; + redxorp->user1(true); + } + + nodep->replaceWith(redxorp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(redxorp); + } + void visit(AstRedAnd* nodep) override { + if (editFormat(nodep)) return; + // Convert to (~x == 0) + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); + const V3Number numZero{fl, argp->width(), 0}; + AstNodeExpr* const negp = new AstNot{fl, argp}; + negp->user1(true); + AstNodeExpr* const eqp = new AstEq{fl, negp, new AstConst{fl, numZero}}; + eqp->user1(true); + nodep->replaceWith(eqp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(eqp); + } void visit(AstRedOr* nodep) override { if (editFormat(nodep)) return; // Convert to (x != 0) @@ -2109,10 +2144,15 @@ class ConstraintExprVisitor final : public VNVisitor { nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp}); } else { iterateAndNextNull(nodep->bodyp()); - nodep->replaceWith(new AstBegin{fl, "", - new AstForeach{fl, nodep->headerp()->unlinkFrBack(), - nodep->bodyp()->unlinkFrBackWithNext()}, - true}); + AstNode* bodyp = nodep->bodyp()->unlinkFrBackWithNext(); + // Prepend bucket preamble stmts stored by lowerDistConstraints (foreach case) + if (AstNode* const preamblep = nodep->user3p()) { + preamblep->addNext(bodyp); + bodyp = preamblep; + nodep->user3p(nullptr); + } + nodep->replaceWith(new AstBegin{ + fl, "", new AstForeach{fl, nodep->headerp()->unlinkFrBack(), bodyp}, true}); } VL_DO_DANGLING(nodep->deleteTree(), nodep); } @@ -3041,6 +3081,7 @@ class RandomizeVisitor final : public VNVisitor { // AstVar::user3() -> bool. Handled in constraints // AstClass::user3p() -> AstVar*. Constrained randomizer variable // AstConstraint::user3p() -> AstTask*. Pointer to resize procedure + // AstConstraintForeach::user3p() -> AstNode*. Dist bucket preamble stmts (foreach case) // AstClass::user4p() -> AstVar*. Constraint mode state variable // AstVar::user4p() -> AstVar*. Size variable for constrained queues // AstMemberSel::user2p() -> AstNodeModule*. Pointer to containing module @@ -3070,6 +3111,8 @@ class RandomizeVisitor final : public VNVisitor { std::map m_staticConstraintModeVars; // Static constraint mode vars per class std::map m_staticRandModeVars; // Static rand mode vars per class + std::map> + m_prePostWrap; // Per-handle-type pre/post virtual wrapper presence // METHODS // Check if two nodes are semantically equivalent (not pointer equality): @@ -3762,12 +3805,75 @@ class RandomizeVisitor final : public VNVisitor { } return nullptr; } - void addPrePostCall(AstClass* const classp, AstFunc* const funcp, const string& name) { + void addPrePostCall(AstClass* const classp, AstNodeFTask* const funcp, const string& name) { if (AstTask* const userFuncp = findPrePostTask(classp, name)) { AstTaskRef* const callp = new AstTaskRef{userFuncp->fileline(), userFuncp}; funcp->addStmtsp(callp->makeStmt()); } } + // Per-class virtual wrapper that invokes the class's effective + // pre_randomize/post_randomize. IEEE 1800-2023 18.6.2: pre_randomize and + // post_randomize "appear to behave as virtual methods" because randomize() + // is virtual. The inline `randomize() with` path builds a non-virtual + // function on the static handle type, so it dispatches pre/post through + // this wrapper to reach the dynamic type's override. + AstTask* getCreatePrePostCallback(AstClass* const classp, const string& which) { + const string name = "__V" + which; + if (AstTask* const existingp = VN_CAST(m_memberMap.findMember(classp, name), Task)) { + return existingp; + } + AstTask* const taskp = new AstTask{classp->fileline(), name, nullptr}; + taskp->classMethod(true); + taskp->isVirtual(classp->isExtended()); + classp->addMembersp(taskp); + m_memberMap.insert(classp, taskp); + addPrePostCall(classp, taskp, which); + return taskp; + } + // Build the virtual pre/post wrappers across classp's whole hierarchy so a + // `randomize() with` through a base handle dispatches to a derived + // override. Returns whether a pre/post wrapper exists anywhere in the + // hierarchy (cached per static handle type). + std::pair buildPrePostVirtualWrappers(AstClass* const classp) { + const auto cachedIt = m_prePostWrap.find(classp); + if (cachedIt != m_prePostWrap.end()) return cachedIt->second; + std::vector hierp{classp}; + v3Global.rootp()->foreach([&](AstClass* subp) { + if (subp != classp && AstClass::isClassExtendedFrom(subp, classp)) + hierp.push_back(subp); + }); + bool hasPre = false; + bool hasPost = false; + for (AstClass* const cp : hierp) { + if (findPrePostTask(cp, "pre_randomize")) { + getCreatePrePostCallback(cp, "pre_randomize"); + hasPre = true; + } + if (findPrePostTask(cp, "post_randomize")) { + getCreatePrePostCallback(cp, "post_randomize"); + hasPost = true; + } + } + // Ensure the static handle type owns the slot whenever a subclass + // overrides, so the virtual call resolves on a base handle. + if (hasPre) getCreatePrePostCallback(classp, "pre_randomize"); + if (hasPost) getCreatePrePostCallback(classp, "post_randomize"); + const std::pair result{hasPre, hasPost}; + m_prePostWrap.emplace(classp, result); + return result; + } + void addVirtualPrePostCall(AstFunc* const randomizeFuncp, AstClass* const classp, + const string& which) { + FileLine* const fl = classp->fileline(); + AstTask* const wrapperp = getCreatePrePostCallback(classp, which); + AstClassRefDType* const refDTypep = new AstClassRefDType{fl, classp, nullptr}; + v3Global.rootp()->typeTablep()->addTypesp(refDTypep); + AstMethodCall* const callp + = new AstMethodCall{fl, new AstThisRef{fl, refDTypep}, wrapperp->name(), nullptr}; + callp->taskp(wrapperp); + callp->dtypeSetVoid(); + randomizeFuncp->addStmtsp(callp->makeStmt()); + } // Check if a class (including inherited members) has any rand class-type members bool classHasRandClassMembers(AstClass* classp) { return classp->existsMember([](const AstClass*, const AstVar* varp) { @@ -4331,20 +4437,41 @@ class RandomizeVisitor final : public VNVisitor { // Replace AstDist with weighted bucket selection via AstConstraintIf chain. // Supports both constant and variable weight expressions. - void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp) { + void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp, + AstConstraintForeach* foreachp = nullptr) { + // When inside a foreach, bucket preamble stmts are stored in foreachp->user3p() + // (as a linked list) so visit(AstConstraintForeach*) can inject them into the + // real AstForeach body. Outside a foreach, they go directly into taskp. + AstNode* foreachTailp = nullptr; + auto addStmt = [&](AstNode* nodep) { + if (foreachp) { + if (!foreachTailp) { + foreachp->user3p(nodep); + foreachTailp = nodep; + } else { + foreachTailp->addNext(nodep); + foreachTailp = nodep; + } + } else { + taskp->addStmtsp(nodep); + } + }; + for (AstNode *nextip, *itemp = constrItemsp; itemp; itemp = nextip) { nextip = itemp->nextp(); // Recursively handle ConstraintIf nodes (dist can be inside if/else) if (AstConstraintIf* const cifp = VN_CAST(itemp, ConstraintIf)) { - if (cifp->thensp()) lowerDistConstraints(taskp, cifp->thensp()); - if (cifp->elsesp()) lowerDistConstraints(taskp, cifp->elsesp()); + if (cifp->thensp()) // LCOV_EXCL_LINE + lowerDistConstraints(taskp, cifp->thensp(), foreachp); // LCOV_EXCL_LINE + if (cifp->elsesp()) lowerDistConstraints(taskp, cifp->elsesp(), foreachp); continue; } // Recursively handle ConstraintForeach nodes (dist can be inside foreach) if (AstConstraintForeach* const cfep = VN_CAST(itemp, ConstraintForeach)) { - if (cfep->bodyp()) lowerDistConstraints(taskp, cfep->bodyp()); + if (cfep->bodyp()) // LCOV_EXCL_LINE + lowerDistConstraints(taskp, cfep->bodyp(), cfep); // LCOV_EXCL_LINE continue; } @@ -4364,7 +4491,7 @@ class RandomizeVisitor final : public VNVisitor { AstConstraintIf* const liftedp = liftLogIfChainToConstraintIf(topLogIfp); constrExprp->replaceWith(liftedp); VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp); - lowerDistConstraints(taskp, liftedp->thensp()); + lowerDistConstraints(taskp, liftedp->thensp(), foreachp); continue; } } @@ -4426,6 +4553,47 @@ class RandomizeVisitor final : public VNVisitor { continue; } + // IEEE 1800-2023 18.5.3: values not in the distribution must never appear. + // Build the union of all non-zero-weight ranges as a single hard ConstraintExpr + AstNodeExpr* unionExprp = nullptr; + for (const auto& bucket : buckets) { + AstNodeExpr* memberp; + if (const AstInsideRange* const irp = VN_CAST(bucket.rangep, InsideRange)) { + // (distExpr >= lo) && (distExpr <= hi); signed comparisons for signed vars + const bool isSigned = distp->exprp()->isSigned(); + AstNodeExpr* const distExprGtep = distp->exprp()->cloneTreePure(false); + AstNodeExpr* const distExprLtep = distp->exprp()->cloneTreePure(false); + distExprGtep->user1(true); + distExprLtep->user1(true); + AstNodeExpr* const gep + = isSigned ? static_cast(new AstGteS{ + fl, distExprGtep, irp->lhsp()->cloneTreePure(false)}) + : static_cast(new AstGte{ + fl, distExprGtep, irp->lhsp()->cloneTreePure(false)}); + AstNodeExpr* const lep + = isSigned ? static_cast(new AstLteS{ + fl, distExprLtep, irp->rhsp()->cloneTreePure(false)}) + : static_cast(new AstLte{ + fl, distExprLtep, irp->rhsp()->cloneTreePure(false)}); + gep->user1(true); + lep->user1(true); + memberp = new AstLogAnd{fl, gep, lep}; + } else { + // distExpr == val + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); + memberp = new AstEq{fl, distExprCopyp, bucket.rangep->cloneTreePure(false)}; + } + memberp->user1(true); + if (!unionExprp) { + unionExprp = memberp; + } else { + unionExprp = new AstLogOr{fl, memberp, unionExprp}; + unionExprp->user1(true); + } + } + AstConstraintExpr* const membershipp = new AstConstraintExpr{fl, unionExprp}; + // Build totalWeight expression: w[0] + w[1] + ... + w[N-1] AstNodeExpr* totalWeightExprp = nullptr; for (auto& bucket : buckets) { @@ -4447,8 +4615,8 @@ class RandomizeVisitor final : public VNVisitor { totalVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); totalVarp->funcLocal(true); totalVarp->isInternal(true); - taskp->addStmtsp(totalVarp); - taskp->addStmtsp( + addStmt(totalVarp); + addStmt( new AstAssign{fl, new AstVarRef{fl, totalVarp, VAccess::WRITE}, totalWeightExprp}); // bucketVar = (rand64() % totalWeight) + 1 @@ -4459,11 +4627,11 @@ class RandomizeVisitor final : public VNVisitor { bucketVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); bucketVarp->funcLocal(true); bucketVarp->isInternal(true); - taskp->addStmtsp(bucketVarp); + addStmt(bucketVarp); - AstNodeExpr* randp = new AstRand{fl, nullptr, false}; + AstNodeExpr* const randp = new AstRand{fl, nullptr, false}; randp->dtypeSetUInt64(); - taskp->addStmtsp(new AstAssign{ + addStmt(new AstAssign{ fl, new AstVarRef{fl, bucketVarp, VAccess::WRITE}, new AstAdd{ fl, new AstConst{fl, AstConst::Unsized64{}, 1}, @@ -4488,27 +4656,59 @@ class RandomizeVisitor final : public VNVisitor { for (int i = static_cast(buckets.size()) - 1; i >= 0; --i) { AstNodeExpr* constraintExprp; if (const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange)) { - AstNodeExpr* const exprCopy1p = distp->exprp()->cloneTreePure(false); - exprCopy1p->user1(true); - AstNodeExpr* const exprCopy2p = distp->exprp()->cloneTreePure(false); - exprCopy2p->user1(true); - AstGte* const gtep - = new AstGte{fl, exprCopy1p, irp->lhsp()->cloneTreePure(false)}; - gtep->user1(true); - AstLte* const ltep - = new AstLte{fl, exprCopy2p, irp->rhsp()->cloneTreePure(false)}; - ltep->user1(true); - constraintExprp = new AstLogAnd{fl, gtep, ltep}; + // Pick distExpr = lo + rand64() % (hi - lo + 1) for a uniform value in range + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); + const int distWidth = distp->exprp()->width(); + // Compute range size in 64-bit to avoid overflow + const AstConst* const lopC = VN_CAST(irp->lhsp(), Const); + const AstConst* const hipC = VN_CAST(irp->rhsp(), Const); + AstNodeExpr* rangeSzp; + if (lopC && hipC) { + const uint64_t rsz = hipC->toUQuad() - lopC->toUQuad() + 1; + rangeSzp = new AstConst{fl, AstConst::Unsized64{}, rsz}; + } else { + const bool isSigned = irp->lhsp()->isSigned(); + AstNodeExpr* const lo64p + = isSigned + ? static_cast( + new AstExtendS{fl, irp->lhsp()->cloneTreePure(false), 64}) + : static_cast( + new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64}); + lo64p->dtypeSetUInt64(); + AstNodeExpr* const hi64p + = isSigned + ? static_cast( + new AstExtendS{fl, irp->rhsp()->cloneTreePure(false), 64}) + : static_cast( + new AstExtend{fl, irp->rhsp()->cloneTreePure(false), 64}); + hi64p->dtypeSetUInt64(); + rangeSzp = new AstAdd{fl, new AstConst{fl, AstConst::Unsized64{}, 1ULL}, + new AstSub{fl, hi64p, lo64p}}; + } + AstNodeExpr* const rand64p = new AstRand{fl, nullptr, false}; + rand64p->dtypeSetUInt64(); + // offset = rand64() % rangeSize (result in [0, rangeSize-1]) + AstNodeExpr* const offset64p = new AstModDiv{fl, rand64p, rangeSzp}; + // Truncate offset to dist expression width, then add lo + AstNodeExpr* const offsetp = new AstCCast{fl, offset64p, distWidth}; + AstNodeExpr* const lop = irp->lhsp()->cloneTreePure(false); + AstNodeExpr* const valuep = new AstAdd{fl, lop, offsetp}; + valuep->dtypeFrom(distp->exprp()); + constraintExprp = new AstEq{fl, distExprCopyp, valuep}; constraintExprp->user1(true); } else { - AstNodeExpr* const exprCopyp = distp->exprp()->cloneTreePure(false); - exprCopyp->user1(true); + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); constraintExprp - = new AstEq{fl, exprCopyp, buckets[i].rangep->cloneTreePure(false)}; + = new AstEq{fl, distExprCopyp, buckets[i].rangep->cloneTreePure(false)}; constraintExprp->user1(true); } AstConstraintExpr* const thenp = new AstConstraintExpr{fl, constraintExprp}; + // Per IEEE 18.5.3: weights are a preference, not a hard constraint. + // The solver may discard this when it conflicts with other constraints. + thenp->isSoft(true); if (!chainp) { chainp = thenp; @@ -4522,6 +4722,8 @@ class RandomizeVisitor final : public VNVisitor { if (chainp) { constrExprp->replaceWith(chainp); VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp); + // Hard membership precedes the soft bucket chain in the constraint list. + chainp->addHereThisAsNext(membershipp); } // Clean up nodes used only as clone templates (never inserted into tree) @@ -4534,6 +4736,16 @@ class RandomizeVisitor final : public VNVisitor { } } + static bool isDynArrOfClassTypeRecurse(const AstNodeDType* const dtypep) { + const AstNodeDType* const refp = dtypep->skipRefp(); + if (VN_IS(refp, DynArrayDType) || VN_IS(refp, QueueDType)) { + return isDynArrOfClassTypeRecurse(refp->subDTypep()); + } else if (VN_IS(refp, ClassRefDType)) { + return true; + } + return false; + } + // VISITORS void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); @@ -4766,6 +4978,18 @@ class RandomizeVisitor final : public VNVisitor { // Refresh array element tables after resize for (AstVar* const arrVarp : sizeArraysIt->second) { + // Array elements of class data type are passed to the solver as separate + // variables, so passing the original array variable is redundant, because it + // won't be referenced + if (isDynArrOfClassTypeRecurse(arrVarp->dtypep())) { + const uint32_t unpackedDims = arrVarp->dtypep()->dimensions(false).second; + if (unpackedDims > 1) { + arrVarp->v3warn( + E_UNSUPPORTED, + "Unsupported: Nested array element access in global constraint"); + } + continue; + } AstCMethodHard* const methodp = new AstCMethodHard{ fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE}, VCMethod::RANDOMIZER_WRITE_VAR}; @@ -5171,7 +5395,17 @@ class RandomizeVisitor final : public VNVisitor { AstFunc* const randomizeFuncp = V3Randomize::newRandomizeFunc( m_memberMap, classp, m_inlineUniqueNames.get(nodep), false); - addPrePostCall(classp, randomizeFuncp, "pre_randomize"); + // A base-handle `randomize() with` must still reach a derived + // pre/post_randomize. Route them through per-class virtual wrappers + // when the static handle type participates in inheritance. + const std::pair prePostWrap = classp->isExtended() + ? buildPrePostVirtualWrappers(classp) + : std::pair{false, false}; + if (prePostWrap.first) { + addVirtualPrePostCall(randomizeFuncp, classp, "pre_randomize"); + } else { + addPrePostCall(classp, randomizeFuncp, "pre_randomize"); + } // Call nested pre_randomize on rand class-type members (IEEE 18.4.1) if (classHasRandClassMembers(classp)) { @@ -5333,7 +5567,11 @@ class RandomizeVisitor final : public VNVisitor { randomizeFuncp->addStmtsp((new AstTaskRef{nodep->fileline(), postTaskp})->makeStmt()); } - addPrePostCall(classp, randomizeFuncp, "post_randomize"); + if (prePostWrap.second) { + addVirtualPrePostCall(randomizeFuncp, classp, "post_randomize"); + } else { + addPrePostCall(classp, randomizeFuncp, "post_randomize"); + } // Replace the node with a call to that function nodep->name(randomizeFuncp->name()); diff --git a/src/V3Sampled.cpp b/src/V3Sampled.cpp index 4fb45e772..841b32d55 100644 --- a/src/V3Sampled.cpp +++ b/src/V3Sampled.cpp @@ -51,8 +51,6 @@ class SampledVisitor final : public VNVisitor { AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; m_scopep->modp()->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; - newvarp->direction(VDirection::INPUT); // Inform V3Sched that it will be driven later - newvarp->primaryIO(true); newvarp->sampled(true); vscp->user1p(newvscp); m_scopep->addVarsp(newvscp); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 8adc0a52e..6078fbbf0 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -529,8 +529,46 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, + entry.m_memberp->name()); } + // Create the input change detect SenTrees. + // If there is a lot of combinationallogic hanging of the top level inputs, we can save + // a lot of work by only evaluating it if an input has actually changed. This in + // paticular helps hierarchical models partitioned across combinaitonal boundaries. + // The change detect itself should be fairly cheap otherwise so alway do it. + // For correctness, don't create a change detect for top level inputs also written + // by the design, as the change detect 'previous value' would get out of sync. + // Also omit a SenTree for types that don't have the required '!=' operator. + // Any signal that does not have an explicit change detect trigger will fall back to + // using the 'first iteration' trigger, same as if this optimization was disabled. + std::unordered_map inp2changedp; + std::vector icoChangeSenTreeps; + if (v3Global.opt.fIcoChangeDetect().isTrue()) { + FileLine* const flp = netlistp->fileline(); + AstScope* const scopep = netlistp->topScopep()->scopep(); + for (AstVarScope* vscp = scopep->varsp(); vscp; vscp = VN_AS(vscp->nextp(), VarScope)) { + // Only for top level ports, assume outputs don't change externally + if (!vscp->varp()->isPrimaryInish()) continue; + // Don't do if written by the design - wouldn't update the change detect 'prev' value + if (vscp->varp()->icoMaybeWritten()) continue; + // Don't do if forceable, as we can't see the actual value - this is belt and braces + if (vscp->varp()->isForced()) continue; + // Can't handle unpacked arrays (they have special types when primary input) + if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) continue; + // Similarly to arrays, can't handle SystemC types + if (vscp->varp()->isSc()) continue; + // Create a sen tree triggered when this input changes + AstSenTree*& senTreepr = inp2changedp[vscp]; + UASSERT_OBJ(!senTreepr, vscp, "Duplicate input change detect trigger"); + AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ}; + AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_CHANGED, refp}; + senTreepr = new AstSenTree{flp, senItemp}; + icoChangeSenTreeps.push_back(senTreepr); + } + } + V3Stats::addStat("Scheduling, 'ico' change detect triggers", icoChangeSenTreeps.size()); + // Gather the relevant sensitivity expressions and create the trigger kit - const auto& senTreeps = getSenTreesUsedBy({&logic}); + std::vector senTreeps = getSenTreesUsedBy({&logic}); + senTreeps.insert(senTreeps.end(), icoChangeSenTreeps.begin(), icoChangeSenTreeps.end()); const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, {}, senTreeps, "ico", extraTriggers, false, false); std::ignore = senExprBuilder.getAndClearResults(); @@ -543,14 +581,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, // Remap sensitivities remapSensitivities(logic, trigKit.mapVec()); + for (auto& pair : inp2changedp) pair.second = trigKit.mapVec().at(pair.second); // Create the inverse map from trigger ref AstSenTree to original AstSenTree V3Order::TrigToSenMap trigToSen; invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec()); - // The trigger top level inputs (first iteration) - AstSenTree* const inputChanged - = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); + // The 'first iteration' trigger for top level inputs - lazy constructed only if needed + AstSenTree* firstIterTriggerp = nullptr; // The DPI Export trigger AstSenTree* const dpiExportTriggered @@ -565,9 +603,19 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, netlistp, {&logic}, trigToSen, "ico", false, false, [&](const AstVarScope* vscp, std::vector& out) { AstVar* const varp = vscp->varp(); - if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) { - out.push_back(inputChanged); + // If it has an explicit change detect trigger, use that, + // otherwise fall back to using the 'first iteration' trigger + auto it = inp2changedp.find(vscp); + if (it != inp2changedp.end()) { + out.push_back(it->second); + } else if (varp->isPrimaryInish() || varp->isSigUserRWPublic() || varp->sampled()) { + if (!firstIterTriggerp) { + firstIterTriggerp + = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); + } + out.push_back(firstIterTriggerp); } + // Add other triggers if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered); if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) { const auto& ifaceTriggered @@ -593,8 +641,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, // Work statements: Invoke the 'ico' function util::callVoidFunc(icoFuncp)); - // Add the first iteration trigger to the trigger computation function - trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false); + // Add the first iteration trigger to the trigger computation function - if used + if (firstIterTriggerp) { + trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false); + } + + // Release temporary input change detect SenTrees + for (AstSenTree* const senTreep : icoChangeSenTreeps) senTreep->deleteTree(); + icoChangeSenTreeps.clear(); return icoLoop.stmtsp; } diff --git a/src/V3SchedAcyclic.cpp b/src/V3SchedAcyclic.cpp index 36a9cd096..ad8ba454e 100644 --- a/src/V3SchedAcyclic.cpp +++ b/src/V3SchedAcyclic.cpp @@ -351,8 +351,7 @@ std::string reportLoopVars(FileLine* /*warnFl*/, Graph* graphp, SchedAcyclicVarV if (splittable) { ss << V3Error::warnMore() - << "... Suggest add /*verilator split_var*/ or /*verilator " - "isolate_assignments*/ to appropriate variables above.\n"; + << "... Suggest add /*verilator split_var*/ to appropriate variables above.\n"; } V3Stats::addStat("Scheduling, split_var, candidates", splittable); return ss.str(); diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 35718b98a..3aa404018 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -150,8 +150,8 @@ public: : SchedReplicateVertex{graphp} , m_vscp{vscp} { // Top level inputs are - if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi() - || varp()->sensIfacep() || varp()->isVirtIface()) { + if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->sampled() + || varp()->isWrittenByDpi() || varp()->sensIfacep() || varp()->isVirtIface()) { addDrivingRegions(INPUT); } // Currently we always execute suspendable processes at the beginning of diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h index 861f8a905..ac9abf196 100644 --- a/src/V3SenExprBuilder.h +++ b/src/V3SenExprBuilder.h @@ -78,7 +78,7 @@ private: // Check if expression contains a class member access that could be null // (e.g., accessing an event through a class reference that may not be initialized) static bool hasClassMemberAccess(const AstNode* const exprp) { - return exprp->exists([](const AstNode* const nodep) { + return exprp && exprp->exists([](const AstNode* const nodep) { if (const AstMemberSel* const mselp = VN_CAST(nodep, MemberSel)) { // Check if the base expression is a class reference return mselp->fromp()->dtypep() @@ -294,6 +294,8 @@ private: } case VEdgeType::ET_TRUE: // return {currp(), false}; + case VEdgeType::ET_INITIAL_NBA: // + return {new AstConst{flp, AstConst::BitFalse{}}, true}; default: // LCOV_EXCL_START senItemp->v3fatalSrc("Unknown edge type"); return {nullptr, false}; diff --git a/src/V3SplitAs.cpp b/src/V3SplitAs.cpp deleted file mode 100644 index 74b72a4dd..000000000 --- a/src/V3SplitAs.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Break always into separate statements to reduce temps -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* -// V3SplitAs's Transformations: -// -// Search each ALWAYS for a VARREF lvalue with a /*isolate_assignments*/ attribute -// If found, color statements with both, assignment to that varref, or other assignments. -// Replicate the Always, and remove mis-colored duplicate code. -// -//************************************************************************* - -#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT - -#include "V3SplitAs.h" - -#include "V3Stats.h" - -VL_DEFINE_DEBUG_FUNCTIONS; - -//###################################################################### -// Find all split variables in a block - -class SplitAsFindVisitor final : public VNVisitorConst { - // STATE - across all visitors - AstVarScope* m_splitVscp = nullptr; // Variable we want to split - - // METHODS - void visit(AstVarRef* nodep) override { - if (nodep->access().isWriteOrRW() && !m_splitVscp && nodep->varp()->attrIsolateAssign()) { - m_splitVscp = nodep->varScopep(); - } - } - void visit(AstExprStmt* nodep) override { - // A function call inside the splitting assignment - // We need to presume the whole call is preserved (if the upper statement is) - // This will break if the m_splitVscp is a "ref" argument to the function, - // but little we can do. - } - void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } - -public: - // CONSTRUCTORS - explicit SplitAsFindVisitor(AstAlways* nodep) { iterateConst(nodep); } - ~SplitAsFindVisitor() override = default; - // METHODS - AstVarScope* splitVscp() const { return m_splitVscp; } -}; - -//###################################################################### -// Remove nodes not containing proper references - -class SplitAsCleanVisitor final : public VNVisitor { - // STATE - across all visitors - const AstVarScope* const m_splitVscp; // Variable we want to split - const bool m_modeMatch; // Remove matching Vscp, else non-matching - // STATE - for current visit position (use VL_RESTORER) - bool m_keepStmt = false; // Current Statement must be preserved - bool m_matches = false; // Statement below has matching lvalue reference - - // METHODS - void visit(AstVarRef* nodep) override { - if (nodep->access().isWriteOrRW()) { - if (nodep->varScopep() == m_splitVscp) { - UINFO(6, " CL VAR " << nodep); - m_matches = true; - } - } - } - void visit(AstNodeStmt* nodep) override { - UINFO(6, " CL STMT " << nodep); - const bool oldKeep = m_keepStmt; - { - VL_RESTORER(m_matches); - m_matches = false; - m_keepStmt = false; - - iterateChildren(nodep); - - if (m_keepStmt || (m_modeMatch ? m_matches : !m_matches)) { - UINFO(6, " Keep STMT " << nodep); - m_keepStmt = true; - } else { - UINFO(6, " Delete STMT " << nodep); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - } - } - // If something below matches, the upper statement remains too. - m_keepStmt = oldKeep || m_keepStmt; - UINFO(9, " upKeep=" << m_keepStmt << " STMT " << nodep); - } - void visit(AstExprStmt* nodep) override { - // A function call inside the splitting assignment - // We need to presume the whole call is preserved (if the upper statement is) - // This will break if the m_splitVscp is a "ref" argument to the function, - // but little we can do. - } - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - SplitAsCleanVisitor(AstAlways* nodep, AstVarScope* vscp, bool modeMatch) - : m_splitVscp{vscp} - , m_modeMatch{modeMatch} { - iterate(nodep); - } - ~SplitAsCleanVisitor() override = default; -}; - -//###################################################################### -// SplitAs class functions - -class SplitAsVisitor final : public VNVisitor { - // NODE STATE - // AstAlways::user() -> bool. True if already processed - const VNUser1InUse m_inuser1; - - // STATE - across all visitors - VDouble0 m_statSplits; // Statistic tracking - - // METHODS - void splitAlways(AstAlways* nodep, AstVarScope* splitVscp) { - UINFOTREE(9, nodep, "", "in"); - // Duplicate it and link in - // Below cloneTree should perhaps be cloneTreePure, but given - // isolate_assignments is required to hit this code, we presume the user - // knows what they are asking for - AstAlways* const newp = nodep->cloneTree(false); - newp->user1(true); // So we don't clone it again - nodep->addNextHere(newp); - { // Delete stuff we don't want in old - const SplitAsCleanVisitor visitor{nodep, splitVscp, false}; - UINFOTREE(9, nodep, "", "out0"); - } - { // Delete stuff we don't want in new - const SplitAsCleanVisitor visitor{newp, splitVscp, true}; - UINFOTREE(9, newp, "", "out1"); - } - } - - void visit(AstAlways* nodep) override { - // Are there any lvalue references below this? - // There could be more than one. So, we process the first one found first. - const AstVarScope* lastSplitVscp = nullptr; - while (!nodep->user1()) { - // Find any splittable variables - const SplitAsFindVisitor visitor{nodep}; - AstVarScope* const splitVscp = visitor.splitVscp(); - // Now isolate the always - if (splitVscp) { - UINFO(3, "Split " << nodep); - UINFO(3, " For " << splitVscp); - // If we did this last time! Something's stuck! - UASSERT_OBJ(splitVscp != lastSplitVscp, nodep, - "Infinite loop in isolate_assignments removal for: " - << splitVscp->prettyNameQ()); - lastSplitVscp = splitVscp; - splitAlways(nodep, splitVscp); - ++m_statSplits; - } else { - nodep->user1(true); - } - } - } - - void visit(AstNodeExpr*) override {} // Accelerate - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - explicit SplitAsVisitor(AstNetlist* nodep) { iterate(nodep); } - ~SplitAsVisitor() override { - V3Stats::addStat("Optimizations, isolate_assignments blocks", m_statSplits); - } -}; - -//###################################################################### -// SplitAs class functions - -void V3SplitAs::splitAsAll(AstNetlist* nodep) { - UINFO(2, __FUNCTION__ << ":"); - { SplitAsVisitor{nodep}; } // Destruct before checking - V3Global::dumpCheckGlobalTree("splitas", 0, dumpTreeEitherLevel() >= 3); -} diff --git a/src/V3SplitVar.cpp b/src/V3SplitVar.cpp index 95bd05954..917123be6 100644 --- a/src/V3SplitVar.cpp +++ b/src/V3SplitVar.cpp @@ -477,7 +477,13 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl { UINFO(4, "Start checking " << nodep->prettyNameQ()); if (!VN_IS(nodep, Module)) { UINFO(4, "Skip " << nodep->prettyNameQ()); - nodep->foreach([this](AstVarXRef* const nodep) { handleVarXRef(nodep); }); + nodep->foreach([this](AstNodeVarRef* const nodep) { + if (AstVarXRef* const varXRefp = VN_CAST(nodep, VarXRef)) { + handleVarXRef(varXRefp); + } else if (m_modp) { + iterate(VN_AS(nodep, VarRef)); + } + }); return; } UASSERT_OBJ(!m_modp, m_modp, "Nested module declaration"); diff --git a/src/V3Stats.cpp b/src/V3Stats.cpp index e1cd949ae..a1ad5edb2 100644 --- a/src/V3Stats.cpp +++ b/src/V3Stats.cpp @@ -35,54 +35,63 @@ class StatsVisitor final : public VNVisitorConst { struct Counters final { // Nodes of given type std::array m_statTypeCount{}; - // Nodes of given type with given type immediate child - std::array, VNType::NUM_TYPES()> m_statAbove{}; // Prediction of given type std::array m_statPred{}; }; // STATE const bool m_fastOnly; // When true, consider only fast functions - const AstNodeExpr* m_parentExprp = nullptr; // Parent expression + bool m_empty = true; // Netlist is empty Counters m_counters; // The actual counts we will display Counters m_dumpster; // Alternate buffer to make discarding parts of the tree easier Counters* m_accump; // The currently active accumulator std::vector m_statVarWidths; // Variables of given width + std::vector m_constPoolConsts; // Constant pool constants of given width + // Constant pool tables of given width and depth + std::map, uint64_t> m_constPoolTables; std::vector> m_statVarWidthNames; // Var names of given width // METHODS void countThenIterateChildren(AstNode* nodep) { ++m_accump->m_statTypeCount[nodep->type()]; + if (nodep->type() != VNType::Netlist) m_empty = false; iterateChildrenConst(nodep); } // VISITORS void visit(AstVar* nodep) override { if (nodep->dtypep()) { - if (m_statVarWidths.size() <= static_cast(nodep->width())) { - m_statVarWidths.resize(nodep->width() + 5); - if (v3Global.opt.statsVars()) { // - m_statVarWidthNames.resize(nodep->width() + 5); + if (nodep->constPoolEntry()) { + // Count constant pool entries + if (AstUnpackArrayDType* const uatp = VN_CAST(nodep->dtypep(), UnpackArrayDType)) { + const int width = uatp->subDTypep()->width(); + const int depth = uatp->elementsConst(); + ++m_constPoolTables[{width, depth}]; + } else { + if (m_constPoolConsts.size() <= static_cast(nodep->width())) { + m_constPoolConsts.resize(nodep->width() + 5); + } + ++m_constPoolConsts.at(nodep->width()); + } + } else { + // Count proper variables + if (m_statVarWidths.size() <= static_cast(nodep->width())) { + m_statVarWidths.resize(nodep->width() + 5); + if (v3Global.opt.statsVars()) { // + m_statVarWidthNames.resize(nodep->width() + 5); + } + } + ++m_statVarWidths.at(nodep->width()); + if (v3Global.opt.statsVars()) { + ++m_statVarWidthNames.at(nodep->width())[nodep->prettyName()]; } - } - ++m_statVarWidths.at(nodep->width()); - if (v3Global.opt.statsVars()) { - ++m_statVarWidthNames.at(nodep->width())[nodep->prettyName()]; } } countThenIterateChildren(nodep); } - void visit(AstNodeExpr* nodep) override { - // Count expression combinations - if (m_parentExprp) ++m_accump->m_statAbove[m_parentExprp->type()][nodep->type()]; - VL_RESTORER(m_parentExprp); - m_parentExprp = nodep; - countThenIterateChildren(nodep); - } - void visit(AstNodeIf* nodep) override { // Track prediction ++m_accump->m_statPred[nodep->branchPred()]; @@ -104,6 +113,7 @@ public: , m_accump{fastOnly ? &m_dumpster : &m_counters} { UINFO(9, "Starting stats, fastOnly=" << fastOnly); iterateConst(nodep); + if (m_empty) return; // Shorthand const auto addStat = [&](const std::string& name, double count, unsigned precision = 0) { @@ -126,6 +136,26 @@ public: } } + // Constant pool constants + for (size_t i = 0; i < m_constPoolConsts.size(); ++i) { + const uint64_t count = m_constPoolConsts.at(i); + if (!count) continue; + std::stringstream ss; + ss << "Vars Const, width " << std::setw(5) << std::dec << i; + addStat(ss.str(), count); + } + + // Constant pool tables + for (const auto& it : m_constPoolTables) { + const int depth = it.first.second; + const int width = it.first.first; + const int count = it.second; + std::ostringstream ss; + ss << "Vars Table, width " << std::setw(5) << std::dec << width // + << " x " << std::setw(5) << std::dec << depth; + addStat(ss.str(), count); + } + // Node types (also total memory usage) const auto typeName = [](size_t t) { return std::string{VNType{static_cast(t)}.ascii()}; }; @@ -138,22 +168,13 @@ public: addStat("Node count, " + typeName(t), count); } } - addStat("Node memory TOTAL (MiB)", totalNodeMemoryUsage >> 20); + addStat("Node mem TOTAL (MiB)", totalNodeMemoryUsage >> 20); // Node Memory usage for (size_t t = 0; t < VNType::NUM_TYPES(); ++t) { if (const uint64_t count = m_counters.m_statTypeCount[t]) { const double share = 100.0 * count * typeSize(t) / totalNodeMemoryUsage; - addStat("Node memory share (%), " + typeName(t), share, 2); - } - } - - // Expression combinations - for (size_t t1 = 0; t1 < VNType::NUM_TYPES(); ++t1) { - for (size_t t2 = 0; t2 < VNType::NUM_TYPES(); ++t2) { - if (const uint64_t c = m_counters.m_statAbove[t1][t2]) { - addStat("Expr combination, " + typeName(t1) + " over " + typeName(t2), c); - } + addStat("Node mem %, " + typeName(t), share, 2); } } diff --git a/src/V3StatsReport.cpp b/src/V3StatsReport.cpp index 32556cb0a..1bf7c512d 100644 --- a/src/V3StatsReport.cpp +++ b/src/V3StatsReport.cpp @@ -119,11 +119,11 @@ class StatsReport final { // Header os << " Stat " << std::left << std::setw(maxWidth - 5 - 2) << ""; - for (const string& i : stages) os << " " << std::left << std::setw(9) << i; + for (const string& i : stages) os << " " << std::left << std::setw(9) << i; os << '\n'; os << " -------- " << std::left << std::setw(maxWidth - 5 - 2) << ""; for (auto it = stages.begin(); it != stages.end(); ++it) { - os << " " << std::left << std::setw(9) << "-------"; + os << " " << std::left << std::setw(9) << "-------"; } // os<name(); } while (col < stages.size() && stages.at(col) != repp->stage()) { - os << std::setw(11) << ""; + os << std::setw(10) << ""; col++; } repp->dump(os); @@ -191,7 +191,7 @@ StatsReport::StatColl StatsReport::s_allStats; // V3Statstic class void V3Statistic::dump(std::ofstream& os) const { - os << " " << std::right << std::fixed << std::setprecision(precision()) << std::setw(9) + os << " " << std::right << std::fixed << std::setprecision(precision()) << std::setw(9) << value(); } diff --git a/src/V3TSP.cpp b/src/V3TSP.cpp deleted file mode 100644 index 4244a5676..000000000 --- a/src/V3TSP.cpp +++ /dev/null @@ -1,701 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementation of Christofides algorithm to -// approximate the solution to the traveling salesman problem. -// -// ISSUES: This isn't exactly Christofides algorithm; see the TODO -// in perfectMatching(). True minimum-weight perfect matching -// would produce a better result. How much better is TBD. -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT - -#include "V3TSP.h" - -#include "V3File.h" -#include "V3Graph.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -VL_DEFINE_DEBUG_FUNCTIONS; - -//###################################################################### -// Support classes - -namespace V3TSP { -static uint32_t s_edgeIdNext = 0; - -static void selfTestStates(); -static void selfTestString(); -} // namespace V3TSP - -// Vertex that tracks a per-vertex key -template -class TspVertexTmpl final : public V3GraphVertex { - VL_RTTI_IMPL(TspVertexTmpl, V3GraphVertex) -private: - const T_Key m_key; - -public: - TspVertexTmpl(V3Graph* graphp, const T_Key& k) - : V3GraphVertex{graphp} - , m_key{k} {} - ~TspVertexTmpl() override = default; - const T_Key& key() const { return m_key; } - -private: - VL_UNCOPYABLE(TspVertexTmpl); -}; - -// TspGraphTmpl represents a complete graph, templatized to work with -// different T_Key types. -template -class TspGraphTmpl final : public V3Graph { -public: - // TYPES - using Vertex = TspVertexTmpl; - - enum VertexState : uint32_t { CLEAR = 0, MST_VISITED = 1, UNMATCHED_ODD = 2 }; - - // MEMBERS - std::unordered_map m_vertices; // T_Key to Vertex lookup map - - // CONSTRUCTORS - TspGraphTmpl() - : V3Graph{} {} - ~TspGraphTmpl() override = default; - - // METHODS - void addVertex(const T_Key& key) { - const bool newEntry = m_vertices.emplace(key, new Vertex{this, key}).second; - UASSERT(newEntry, "Vertex already exists with same key"); - } - - // For purposes of TSP, we are using non-directional graphs. - // Map that onto the normally-directional V3Graph by creating - // a matched pairs of opposite-directional edges to represent - // each non-directional edge: - void addEdge(const T_Key& from, const T_Key& to, int cost) { -#if VL_DEBUG // Hot, so only in debug - UASSERT(from != to, "Adding edge would form a loop"); - UASSERT(cost >= 0, "Negative weight edge"); -#endif - Vertex* const fp = findVertex(from); - Vertex* const tp = findVertex(to); - - // No need to dedup edges. - // The only time we may create duplicate edges is when - // combining the MST with the perfect-matched pairs, - // and in that case, we want to permit duplicate edges. - const uint32_t edgeId = ++V3TSP::s_edgeIdNext; - - // We want to be able to compare edges quickly for a total - // ordering, so pre-compute a sorting key and store it in - // the edge user field. We also want easy access to the 'id' - // which uniquely identifies a single bidir edge. Luckily we - // can do both efficiently. - // cppcheck-suppress badBitmaskCheck - const uint64_t userValue = (static_cast(cost) << 32) | edgeId; - (new V3GraphEdge{this, fp, tp, cost})->user(userValue); - (new V3GraphEdge{this, tp, fp, cost})->user(userValue); - } - - static uint32_t getEdgeId(const V3GraphEdge* edgep) { - return static_cast(edgep->user()); - } - - // cppcheck-suppress duplInheritedMember - bool empty() const { return m_vertices.empty(); } - - const std::list keysToVertexList(const std::vector& odds) { - std::list vertices; - for (unsigned i = 0; i < odds.size(); ++i) vertices.push_back(findVertex(odds.at(i))); - return vertices; - } - -private: - // We will keep sorted lists of edges as vectors - using EdgeList = std::vector; - - static bool edgeCmp(const V3GraphEdge* ap, const V3GraphEdge* bp) { - // We pre-computed these when adding the edge to sort first by cost, then by identity - return ap->user() > bp->user(); - } - - struct EdgeListCmp final { - bool operator()(const EdgeList* ap, const EdgeList* bp) const { - // Compare heads - return edgeCmp(bp->back(), ap->back()); - } - }; - - static Vertex* castVertexp(V3GraphVertex* vxp) { return static_cast(vxp); } - static const Vertex* castVertexp(const V3GraphVertex* vxp) { - return static_cast(vxp); - } - -public: - // From *this, populate *mstp with the minimum spanning tree. - // *mstp must be initially empty. - void makeMinSpanningTree(TspGraphTmpl* mstp) { - UASSERT(mstp->empty(), "Output graph must start empty"); - - // Use Prim's algorithm to efficiently construct the MST. - - uint32_t vertCount = 0; - for (V3GraphVertex& vtx : vertices()) { - mstp->addVertex(castVertexp(&vtx)->key()); - vertCount++; - } - - // Allocate storage for per vertex edge lists up front. - std::vector allocatedEdgeLists{vertCount}; - - // Index of vertex in visitation order (used for indexing allocatedEdgeLists) - uint32_t vertIdx = 0; - - // We keep pending edges as a sorted set of sorted vectors. This allows us to find the - // lowest cost edge quickly, while also reducing the cost of inserting batches of new - // edges, which is what we need in this algorithm. - std::set pendingEdgeListps; - - const auto visit = [&](V3GraphVertex* vtxp) { -#ifdef VL_DEBUG // Very hot, so only in debug - UASSERT(vtxp->user() == VertexState::CLEAR, "Vertex visited twice"); -#endif - // Mark vertex as visited - vtxp->user(VertexState::MST_VISITED); - // Allocate new edge list - EdgeList* const newEdgesp = &allocatedEdgeLists[vertIdx++]; - // Gather out edges of this vertex - for (V3GraphEdge& edge : vtxp->outEdges()) { - // Don't add edges leading to vertices we already visited. This is a highly - // connected graph, so this greatly reduces the cost of maintaining the pending - // set. - if (edge.top()->user() == VertexState::MST_VISITED) continue; - newEdgesp->push_back(&edge); - } - // If no relevant out edges, then we are done - if (newEdgesp->empty()) return; - // Sort new edge list - std::sort(newEdgesp->begin(), newEdgesp->end(), edgeCmp); - // Add edge list to pending set - pendingEdgeListps.insert(newEdgesp); - }; - - // To start, choose an arbitrary vertex and visit it. - visit(vertices().frontp()); - - // Repeatedly find the least costly edge in the pending set. - // If it connects to an unvisited node, visit that node and update - // the pending edge set. If it connects to an already visited node, - // discard it and repeat again. - while (!pendingEdgeListps.empty()) { - // Grab lowest cost edge list - auto it = pendingEdgeListps.begin(); - - // Grab lowest cost edge - EdgeList* const bestEdgeListp = *it; - const V3GraphEdge* const bestEdgep = bestEdgeListp->back(); - - // Remove the lowest cost edge list. We will remove its lowest cost element, and either - // we are done with (if it had a single element) it in which case it will be discarded, - // or the cost of the new head element might be different, so we will need to re-insert - // it in the right place. In either case, it needs to be removed. - pendingEdgeListps.erase(it); - - // If the lowest cost edge list is not a singleton list, then pop the lowest cost - // edge and re-insert the remaining edge list into the pending set. - if (bestEdgeListp->size() > 1) { - bestEdgeListp->pop_back(); - pendingEdgeListps.insert(bestEdgeListp); - } - - // Grab the target vertex - Vertex* const neighborp = castVertexp(bestEdgep->top()); - - // If the neighbour is not yet visited - if (neighborp->user() == VertexState::CLEAR) { - // Visit it - visit(neighborp); - - // Create the edge in our output MST graph - Vertex* const from_vertexp = castVertexp(bestEdgep->fromp()); - mstp->addEdge(from_vertexp->key(), neighborp->key(), bestEdgep->weight()); - -#if VL_DEBUG // Very hot loop, so only in debug - UASSERT(from_vertexp->user() == MST_VISITED, - "bestEdgep->fromp() should be already seen"); -#endif - } - } - - UASSERT(vertIdx == vertCount, "Should have visited all vertices"); - } - - // Populate *outp with a minimal perfect matching of *this. - // *outp must be initially empty. - void perfectMatching(const std::vector& oddKeys, TspGraphTmpl* outp) { - UASSERT(outp->empty(), "Output graph must start empty"); - - const std::list& odds = keysToVertexList(oddKeys); - UASSERT(odds.size() % 2 == 0, "number of odd-order nodes should be even"); - - for (Vertex* const vtxp : odds) { - outp->addVertex(vtxp->key()); - vtxp->user(VertexState::UNMATCHED_ODD); - } - - // TODO: The true Christofides algorithm calls for minimum-weight - // perfect matching. Instead, we have a simple greedy algorithm - // which might get close to the minimum, maybe, with luck? - // - // TODO: Revisit this. It's possible to compute the true minimum in - // N*N*log(N) time using variants of the Blossom algorithm. - // Implementing Blossom looks hard, maybe we can use an existing - // open source implementation -- for example the "LEMON" library - // which has a TSP solver. - - // ----- - - // Gather and sort all edges. We use a vector then sort, because this is faster than a - // sorted set. Reuse the comparator from Prim's routine (note it a 'greater', not a - // 'lesser' comparator). The logic is the same here. - // - // Note that there are two V3GraphEdge's representing a single bidir edge. While we could - // just add both to the pending list and get the same result, we will only add one (based - // on fast pointer comparison - this still yields deterministic results), in order to - // reduce the size of the working set. - std::vector pendingEdges; - - for (Vertex* const fromp : odds) { - for (V3GraphEdge& edge : fromp->outEdges()) { - Vertex* const top = castVertexp(edge.top()); - // There are two edges (in both directions) between these two vertices. Keep one. - if (fromp > top) continue; - // We only care about edges between the odd-order vertices - if (top->user() != VertexState::UNMATCHED_ODD) continue; - // Add to candidate list - pendingEdges.push_back(&edge); - } - } - - // Sort reverse iterators. This yields ascending order with a 'greater' comparator. - std::sort(pendingEdges.rbegin(), pendingEdges.rend(), edgeCmp); - - // Iterate over all edges, in order from low to high cost. - // For any edge whose ends are both odd-order vertices which - // haven't been matched yet, match them. - for (V3GraphEdge* const edgep : pendingEdges) { - Vertex* const fromp = castVertexp(edgep->fromp()); - Vertex* const top = castVertexp(edgep->top()); - if (fromp->user() == VertexState::UNMATCHED_ODD - && top->user() == VertexState::UNMATCHED_ODD) { - outp->addEdge(fromp->key(), top->key(), edgep->weight()); - fromp->user(VertexState::CLEAR); - top->user(VertexState::CLEAR); - } - } - } - - void combineGraph(const TspGraphTmpl& g) { - std::unordered_set edges_done; - for (const V3GraphVertex& vtx : g.vertices()) { - const Vertex* const fromp = castVertexp(&vtx); - for (const V3GraphEdge& edge : fromp->outEdges()) { - const Vertex* const top = castVertexp(edge.top()); - if (edges_done.insert(getEdgeId(&edge)).second) { - addEdge(fromp->key(), top->key(), edge.weight()); - } - } - } - } - - void findEulerTourRecurse(std::unordered_set* markedEdgesp, Vertex* startp, - std::vector* sortedOutp) { - Vertex* cur_vertexp = startp; - - // Go on a random tour. Fun! - std::vector tour; - do { - UINFO(6, "Adding " << cur_vertexp->key() << " to tour."); - tour.push_back(cur_vertexp); - - // Look for an arbitrary edge we've not yet marked - for (V3GraphEdge& edge : cur_vertexp->outEdges()) { - const uint32_t edgeId = getEdgeId(&edge); - if (markedEdgesp->end() == markedEdgesp->find(edgeId)) { - // This edge is not yet marked, so follow it. - markedEdgesp->insert(edgeId); - Vertex* const neighborp = castVertexp(edge.top()); - UINFO(6, "following edge " << edgeId << " from " << cur_vertexp->key() - << " to " << neighborp->key()); - cur_vertexp = neighborp; - goto found; - } - } - v3fatalSrc("No unmarked edges found in tour"); - found:; - } while (cur_vertexp != startp); - UINFO(6, "stopped, got back to start of tour @ " << cur_vertexp->key()); - - // Look for nodes on the tour that still have - // un-marked edges. If we find one, recurse. - for (Vertex* vxp : tour) { - bool recursed; - do { - recursed = false; - // Look for an arbitrary edge at vxp we've not yet marked - for (V3GraphEdge& edge : vxp->outEdges()) { - const uint32_t edgeId = getEdgeId(&edge); - if (markedEdgesp->end() == markedEdgesp->find(edgeId)) { - UINFO(6, "Recursing."); - findEulerTourRecurse(markedEdgesp, vxp, sortedOutp); - recursed = true; - goto recursed; - } - } - recursed:; - } while (recursed); - sortedOutp->push_back(vxp->key()); - } - - if (debug() >= 6) { - UINFO(0, "Tour was:"); - for (const Vertex* vxp : tour) std::cout << "- " << vxp->key() << '\n'; - std::cout << "-\n"; - } - } - - void dumpGraph(std::ostream& os, const string& nameComment) const { - // UINFO(0) as controlled by caller - os << "At " << nameComment << ", dumping graph. Keys:\n"; - for (const V3GraphVertex& vtx : vertices()) { - const Vertex* const tspvp = castVertexp(&vtx); - os << " " << tspvp->key() << '\n'; - for (const V3GraphEdge& edge : tspvp->outEdges()) { - const Vertex* const neighborp = castVertexp(edge.top()); - os << " has edge " << getEdgeId(&edge) << " to " << neighborp->key() << '\n'; - } - } - } - void dumpGraphFilePrefixed(const string& nameComment) const { - if (dumpLevel()) { - const string filename = v3Global.debugFilename(nameComment) + ".txt"; - const std::unique_ptr logp{V3File::new_ofstream(filename)}; - if (logp->fail()) v3fatal("Can't write file: " << filename); - dumpGraph(*logp, nameComment); - } - } - - void findEulerTour(std::vector* sortedOutp) { - UASSERT(sortedOutp->empty(), "Output graph must start empty"); - if (::dumpGraphLevel() >= 6) dumpDotFilePrefixed("findEulerTour"); - std::unordered_set markedEdges; - // Pick a start node - Vertex* const start_vertexp = castVertexp(vertices().frontp()); - findEulerTourRecurse(&markedEdges, start_vertexp, sortedOutp); - } - - std::vector getOddDegreeKeys() const { - std::vector result; - for (const V3GraphVertex& vtx : vertices()) { - const Vertex* const tspvp = castVertexp(&vtx); - const uint32_t degree = vtx.outEdges().size(); - if (degree & 1) result.push_back(tspvp->key()); - } - return result; - } - -private: - Vertex* findVertex(const T_Key& key) const { - const auto it = m_vertices.find(key); - UASSERT(it != m_vertices.end(), "Vertex not found"); - return it->second; - } - VL_UNCOPYABLE(TspGraphTmpl); -}; - -//###################################################################### -// Main algorithm - -void V3TSP::tspSort(const V3TSP::StateVec& states, V3TSP::StateVec* resultp) VL_MT_SAFE { - UASSERT(resultp->empty(), "Output graph must start empty"); - - // Make this TSP implementation work for graphs of size 0 or 1 - // which, unfortunately, is a special case as the following - // code assumes >= 2 nodes. - if (states.empty()) return; - if (states.size() == 1) { - resultp->push_back(*(states.begin())); - return; - } - - // Build the initial graph from the starting state set. - using Graph = TspGraphTmpl; - Graph graph; - for (const auto& state : states) graph.addVertex(state); - for (V3TSP::StateVec::const_iterator it = states.begin(); it != states.end(); ++it) { - for (V3TSP::StateVec::const_iterator jt = it; jt != states.end(); ++jt) { - if (it == jt) continue; - graph.addEdge(*it, *jt, (*it)->cost(*jt)); - } - } - - // Create the minimum spanning tree - Graph minGraph; - graph.makeMinSpanningTree(&minGraph); - if (dumpGraphLevel() >= 6) minGraph.dumpGraphFilePrefixed("minGraph"); - - const std::vector oddDegree = minGraph.getOddDegreeKeys(); - Graph matching; - graph.perfectMatching(oddDegree, &matching); - if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching"); - - // Adds edges to minGraph, the resulting graph will have even number of - // edge counts at every vertex: - minGraph.combineGraph(matching); - - V3TSP::StateVec prelim_result; - minGraph.findEulerTour(&prelim_result); - - UASSERT(prelim_result.size() >= states.size(), "Algorithm size error"); - - // Discard duplicate nodes that the Euler tour might contain. - { - std::unordered_set seen; - for (V3TSP::StateVec::iterator it = prelim_result.begin(); it != prelim_result.end(); - ++it) { - const TspStateBase* const elemp = *it; - const auto itFoundPair = seen.insert(elemp); - if (itFoundPair.second) resultp->push_back(elemp); - } - } - - UASSERT(resultp->size() == states.size(), "Algorithm size error"); - - // Find the most expensive arc and rotate the list so that the most - // expensive arc connects the last and first elements. (Since we're not - // modeling something that actually cycles back, we don't need to pay - // that cost at all.) - { - unsigned max_cost = 0; - unsigned max_cost_idx = 0; - for (unsigned i = 0; i < resultp->size(); ++i) { - const TspStateBase* const ap = (*resultp)[i]; - const TspStateBase* const bp - = (i + 1 == resultp->size()) ? (*resultp)[0] : (*resultp)[i + 1]; - const unsigned cost = ap->cost(bp); - if (cost > max_cost) { - max_cost = cost; - max_cost_idx = i; - } - } - - if (max_cost_idx == resultp->size() - 1) { - // List is already rotated for minimum cost. stop. - return; - } - - V3TSP::StateVec new_result; - unsigned i = max_cost_idx + 1; - UASSERT(i < resultp->size(), "Algorithm size error"); - while (i != max_cost_idx) { - new_result.push_back((*resultp)[i]); - i++; - if (i >= resultp->size()) i = 0; - } - new_result.push_back((*resultp)[i]); - - UASSERT(resultp->size() == new_result.size(), "Algorithm size error"); - *resultp = new_result; - } -} - -//###################################################################### -// Self Tests - -class TspTestState final : public V3TSP::TspStateBase { -public: - TspTestState(unsigned xpos, unsigned ypos) - : m_xpos{xpos} - , m_ypos{ypos} - , m_serial{++s_serialNext} {} - ~TspTestState() override = default; - int cost(const TspStateBase* otherp) const override VL_MT_SAFE { - return cost(dynamic_cast(otherp)); - } - static unsigned diff(unsigned a, unsigned b) VL_PURE { - if (a > b) return a - b; - return b - a; - } - int cost(const TspTestState* otherp) const VL_PURE { - // For test purposes, each TspTestState is merely a point - // on the Cartesian plane; cost is the linear distance - // between two points. - unsigned xabs; - unsigned yabs; - xabs = diff(otherp->m_xpos, m_xpos); - yabs = diff(otherp->m_ypos, m_ypos); - return std::lround(std::sqrt(xabs * xabs + yabs * yabs)); - } - unsigned xpos() const { return m_xpos; } - unsigned ypos() const { return m_ypos; } - - bool operator<(const TspStateBase& other) const override { - return operator<(dynamic_cast(other)); - } - bool operator<(const TspTestState& other) const { return m_serial < other.m_serial; } - -private: - const unsigned m_xpos; - const unsigned m_ypos; - const unsigned m_serial; - static unsigned s_serialNext; -}; - -unsigned TspTestState::s_serialNext = 0; - -void V3TSP::selfTestStates() { - // Linear test -- coords all along the x-axis - { - V3TSP::StateVec states; - const TspTestState s10{10, 0}; - const TspTestState s60{60, 0}; - const TspTestState s20{20, 0}; - const TspTestState s100{100, 0}; - const TspTestState s5{5, 0}; - states.push_back(&s10); - states.push_back(&s60); - states.push_back(&s20); - states.push_back(&s100); - states.push_back(&s5); - - V3TSP::StateVec result; - tspSort(states, &result); - - V3TSP::StateVec expect; - expect.push_back(&s100); - expect.push_back(&s60); - expect.push_back(&s20); - expect.push_back(&s10); - expect.push_back(&s5); - if (VL_UNCOVERABLE(expect != result)) { - for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) { - const TspTestState* const statep = dynamic_cast(*it); - cout << statep->xpos() << " "; - } - cout << endl; - v3fatalSrc("TSP linear self-test fail. Result (above) did not match expectation."); - } - } - - // Second test. Coords are distributed in 2D space. - // Test that tspSort() will rotate the list for minimum cost. - { - V3TSP::StateVec states; - const TspTestState a{0, 0}; - const TspTestState b{100, 0}; - const TspTestState c{200, 0}; - const TspTestState d{200, 100}; - const TspTestState e{150, 150}; - const TspTestState f{0, 150}; - const TspTestState g{0, 100}; - - states.push_back(&a); - states.push_back(&b); - states.push_back(&c); - states.push_back(&d); - states.push_back(&e); - states.push_back(&f); - states.push_back(&g); - - V3TSP::StateVec result; - tspSort(states, &result); - - V3TSP::StateVec expect; - expect.push_back(&f); - expect.push_back(&g); - expect.push_back(&a); - expect.push_back(&b); - expect.push_back(&c); - expect.push_back(&d); - expect.push_back(&e); - - if (VL_UNCOVERABLE(expect != result)) { - for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) { - const TspTestState* const statep = dynamic_cast(*it); - cout << statep->xpos() << "," << statep->ypos() << " "; - } - cout << endl; - v3fatalSrc( - "TSP 2d cycle=false self-test fail. Result (above) did not match expectation."); - } - } -} - -void V3TSP::selfTestString() { - using Graph = TspGraphTmpl; - Graph graph; - graph.addVertex("0"); - graph.addVertex("1"); - graph.addVertex("2"); - graph.addVertex("3"); - - graph.addEdge("0", "1", 3943); - graph.addEdge("0", "2", 3456); - graph.addEdge("0", "3", 4920); - graph.addEdge("1", "2", 2730); - graph.addEdge("1", "3", 8199); - graph.addEdge("2", "3", 4130); - - Graph minGraph; - graph.makeMinSpanningTree(&minGraph); - if (dumpGraphLevel() >= 6) minGraph.dumpGraphFilePrefixed("minGraph"); - - const std::vector oddDegree = minGraph.getOddDegreeKeys(); - Graph matching; - graph.perfectMatching(oddDegree, &matching); - if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching"); - - minGraph.combineGraph(matching); - - std::vector result; - minGraph.findEulerTour(&result); - - std::vector expect; - expect.emplace_back("0"); - expect.emplace_back("2"); - expect.emplace_back("1"); - expect.emplace_back("2"); - expect.emplace_back("3"); - - if (VL_UNCOVERABLE(expect != result)) { - for (const string& i : result) cout << i << " "; - cout << endl; - v3fatalSrc("TSP string self-test fail. Result (above) did not match expectation."); - } -} - -void V3TSP::selfTest() { - selfTestString(); - selfTestStates(); -} diff --git a/src/V3TSP.h b/src/V3TSP.h deleted file mode 100644 index 840f5fa96..000000000 --- a/src/V3TSP.h +++ /dev/null @@ -1,57 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementation of Christofides algorithm to -// approximate the solution to the traveling salesman problem. -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#ifndef VERILATOR_V3TSP_H_ -#define VERILATOR_V3TSP_H_ - -#include "config_build.h" -#include "verilatedos.h" - -#include "V3Error.h" - -#include - -namespace V3TSP { -// Perform a "Traveling Salesman Problem" optimizing sort -// on any type you like -- so long as inherits from TspStateBase. - -class TspStateBase VL_NOT_FINAL { -public: - // This is the cost function that the TSP sort will minimize. - // All costs in V3TSP are int, chosen to match the type of - // V3GraphEdge::weight() which will reflect each edge's cost. - virtual int cost(const TspStateBase* otherp) const VL_MT_SAFE = 0; - - // This operator< must place a meaningless, arbitrary, but - // stable order on all TspStateBase's. It's used only to - // key maps so that iteration is stable, without relying - // on pointer values that could lead to nondeterminism. - virtual bool operator<(const TspStateBase& otherp) const = 0; - - virtual ~TspStateBase() = default; -}; - -using StateVec = std::vector; - -// Given an unsorted set of TspState's, sort them to minimize -// the transition cost for walking the sorted list. -void tspSort(const StateVec& states, StateVec* resultp) VL_MT_SAFE; - -void selfTest() VL_MT_DISABLED; -} // namespace V3TSP - -#endif // Guard diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 5bae5d0aa..0db8d04d3 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -248,9 +248,9 @@ private: UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked task"); TaskFTaskVertex* const taskVtxp = getFTaskVertex(nodep->taskp()); new TaskEdge{&m_callGraph, m_curVxp, taskVtxp}; - if (isVirtualIfaceMethodCall(nodep) && isIfaceFTaskScope(getScope(nodep->taskp()))) { - taskVtxp->needsNonInlineCFunc(true); - } + // Virtual-interface method calls dispatch through a runtime handle and + // must not be inlined. + if (isVirtualIfaceMethodCall(nodep)) taskVtxp->needsNonInlineCFunc(true); // Do we have to disable inlining the function? const V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp()); if (!taskVtxp->noInline()) { // Else short-circuit below @@ -1638,6 +1638,7 @@ class TaskVisitor final : public VNVisitor { // Create cloned statements AstNode* beginp; AstCNew* cnewp = nullptr; + // getScope() is safe here: TaskStateVisitor stamped all FTask scopes before this pass. const bool virtualIfaceCall = TaskStateVisitor::isVirtualIfaceMethodCall(nodep) && TaskStateVisitor::isIfaceFTaskScope(m_statep->getScope(nodep->taskp())); diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index 2a7bb37e3..8cee034fa 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -1094,6 +1094,7 @@ class TraceVisitor final : public VNVisitor { // Create the trace registration function m_regFuncp = new AstCFunc{m_topScopep->fileline(), "trace_register", m_topScopep}; m_regFuncp->argTypes(v3Global.opt.traceClassBase() + "* tracep"); + m_regFuncp->entryPoint(true); m_regFuncp->isTrace(true); m_regFuncp->slow(true); m_regFuncp->isStatic(false); diff --git a/src/V3TraceDecl.cpp b/src/V3TraceDecl.cpp index 820a314c7..1a1ad6b89 100644 --- a/src/V3TraceDecl.cpp +++ b/src/V3TraceDecl.cpp @@ -980,6 +980,7 @@ public: AstCFunc* rootFuncp = nullptr; if (!v3Global.opt.libCreate().empty()) { rootFuncp = newCFunc(flp, "trace_init_root"); + rootFuncp->entryPoint(true); for (size_t i = 0; i < m_topScopeRootFuncCount; ++i) { AstCCall* const callp = new AstCCall{flp, topScopeFuncps.at(i)}; callp->dtypeSetVoid(); @@ -1017,6 +1018,7 @@ public: // Set name of top level function AstCFunc* const topFuncp = m_topFuncps.front(); topFuncp->name("trace_init_top"); + topFuncp->entryPoint(true); if (rootFuncp && v3Global.opt.debugCheck()) checkCallsRecurse(rootFuncp); checkCalls(topFuncp); diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 6fa174ed0..5bc72076b 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -352,7 +352,9 @@ class TristatePinVisitor final : public TristateBaseVisitor { TristateGraph& m_tgraph; const bool m_lvalue; // Flip to be an LVALUE // VISITORS - void visit(AstVarRef* nodep) override { + // AstNodeVarRef, not just AstVarRef: a cross-hierarchy pin expression into an + // interface (.pin(iface.net)) is an AstVarXRef and needs the same access flip. + void visit(AstNodeVarRef* nodep) override { UASSERT_OBJ(!nodep->access().isRW(), nodep, "Tristate unexpected on R/W access flip"); if (m_lvalue && !nodep->access().isWriteOrRW()) { UINFO(9, " Flip-to-LValue " << nodep); @@ -405,6 +407,11 @@ class TristateVisitor final : public TristateBaseVisitor { struct AuxAstVar final { AstPull* pullp = nullptr; // pullup/pulldown direction (whole variable) AstVar* outVarp = nullptr; // output __out var + bool ifaceTristate = false; // Interface var known to be tristate (set when the + // interface module is processed). Lets a module that + // drives this var across hierarchy with a plain (non-Z) + // assign be recognised as a tristate contributor even + // though that module's own graph has no Z on the net. std::unordered_map bitPulls; // Per-bit pull: bit_index -> direction (1=up, 0=down) }; @@ -439,6 +446,7 @@ class TristateVisitor final : public TristateBaseVisitor { int m_unique = 0; bool m_alhs = false; // On LHS of assignment bool m_inAlias = false; // Inside alias statement + bool m_processedIfaces = false; // Interface modules already processed (interfaces-first pass) VStrength m_currentStrength = VStrength::STRONG; // Current strength of assignment, // Used only on LHS of assignment const AstNode* m_logicp = nullptr; // Current logic being built @@ -755,6 +763,9 @@ class TristateVisitor final : public TristateBaseVisitor { } } } else if (isIfaceTri) { + // Mark here too, so a net made tristate only by an external 'z driver + // still captures a plain cross-hierarchy driver processed later. + m_varAux(invarp).ifaceTristate = true; // Interface tristate vars: drivers from different interface instances // (different VarXRef dotted paths) must be processed separately. // E.g. io_ifc.d and io_ifc_local.d both target the same AstVar d in @@ -780,7 +791,11 @@ class TristateVisitor final : public TristateBaseVisitor { } } else if (VN_IS(nodep, Iface) && !invarp->isIO()) { // Local driver in an interface module - use contribution mechanism - // so it can be combined with any external drivers later + // so it can be combined with any external drivers later. Record that + // this interface net is tristate, so a module that drives it across + // hierarchy with a plain (non-Z) assign is also routed through the + // contribution mechanism (its own graph has no Z to mark it tristate). + m_varAux(invarp).ifaceTristate = true; insertTristatesSignal(nodep, invarp, refsp, true, "", "", nullptr); } else { insertTristatesSignal(nodep, invarp, refsp, false, "", "", nullptr); @@ -2080,6 +2095,15 @@ class TristateVisitor final : public TristateBaseVisitor { if (m_graphing) { if (nodep->access().isWriteOrRW()) associateLogic(nodep, nodep->varp()); if (nodep->access().isReadOrRW()) associateLogic(nodep->varp(), nodep); + // Only interface tristate nets need this: a plain (non-Z) cross-hierarchy + // driver has no Z in its own module's graph, so mark the net tristate here to + // collect the driver as a contribution. The ifaceTristate flag is only set for + // interface nets; a non-interface cross-module tri driver does nothing here and + // is instead rejected (E_UNSUPPORTED) later in insertTristates. + if (nodep->access().isWriteOrRW() && VN_IS(nodep, VarXRef) + && m_varAux(nodep->varp()).ifaceTristate) { + m_tgraph.setTristate(nodep->varp()); + } } else { if (nodep->user2() & U2_NONGRAPH) return; // Processed nodep->user2Or(U2_NONGRAPH); @@ -2160,6 +2184,9 @@ class TristateVisitor final : public TristateBaseVisitor { } void visit(AstNodeModule* nodep) override { + // Interfaces are processed first in the constructor; skip the duplicate visit + // during the later iterateChildrenBackwardsConst() pass over all modules. + if (m_processedIfaces && VN_IS(nodep, Iface)) return; UINFO(8, dbgState() << nodep); VL_RESTORER(m_modp); VL_RESTORER(m_graphing); @@ -2296,6 +2323,18 @@ public: // CONSTRUCTORS explicit TristateVisitor(AstNetlist* netlistp) { m_tgraph.clearAndCheck(); + // Process interface modules first, so an interface tristate net is recorded + // (AuxAstVar::ifaceTristate) before any module that drives it across hierarchy is + // graphed. A module driving such a net with a plain (non-Z) assign has no Z in its + // own graph to mark the net tristate, so without this its driver would be dropped. + // Collect only the interfaces (reverse declaration order to match the pass below). + std::vector ifacesp; + for (AstNode* modp = netlistp->modulesp(); modp; modp = modp->nextp()) { + if (VN_IS(modp, Iface)) ifacesp.push_back(VN_AS(modp, NodeModule)); + } + for (auto it = ifacesp.rbegin(); it != ifacesp.rend(); ++it) iterate(*it); + m_processedIfaces = true; + // Then the rest in the historical order; interfaces are skipped (already done). iterateChildrenBackwardsConst(netlistp); // Combine interface tristate contributions after all modules processed diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp index 727e97840..57e12e478 100644 --- a/src/V3Unknown.cpp +++ b/src/V3Unknown.cpp @@ -51,15 +51,14 @@ class UnknownVisitor final : public VNVisitor { static const std::string s_xrandPrefix; // STATE - across all visitors - VDouble0 m_statUnkVars; // Statistic tracking + VDouble0 m_statUnkVars; // Statistic of xrand variable created + VDouble0 m_statElses; // Statistic of else branches created for array selects V3UniqueNames m_lvboundNames; // For generating unique temporary variable names std::unique_ptr m_xrandNames; // For generating unique temporary variable names // STATE - for current visit position (use VL_RESTORER) AstNodeModule* m_modp = nullptr; // Current module AstNodeFTask* m_ftaskp = nullptr; // Current function/task - AstAssignDly* m_assigndlyp = nullptr; // Current assignment - AstNode* m_timingControlp = nullptr; // Current assignment's intra timing control bool m_constXCvt = false; // Convert X's bool m_allowXUnique = true; // Allow unique assignments @@ -69,38 +68,21 @@ class UnknownVisitor final : public VNVisitor { if (m_ftaskp) { varp->funcLocal(true); varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_ftaskp->stmtsp()->addHereThisAsNext(varp); + if (m_ftaskp->stmtsp()) + m_ftaskp->stmtsp()->addHereThisAsNext(varp); + else + m_ftaskp->addStmtsp(varp); } else { - m_modp->stmtsp()->addHereThisAsNext(varp); + if (m_modp->stmtsp()) + m_modp->stmtsp()->addHereThisAsNext(varp); + else + m_modp->addStmtsp(varp); } } void replaceBoundLvalue(AstNodeExpr* nodep, AstNodeExpr* condp) { // Spec says a out-of-range LHS SEL results in a NOP. - // This is a PITA. We could: - // 1. IF(...) around an ASSIGN, - // but that would break a "foo[TOO_BIG]=$fopen(...)". - // 2. Hack to extend the size of the output structure - // by one bit, and write to that temporary, but never read it. - // That makes there be two widths() and is likely a bug farm. - // 3. Make a special SEL to choose between the real lvalue - // and a temporary NOP register. - // 4. Assign to a temp, then IF that assignment. - // This is suspected to be nicest to later optimizations. - // 4 seems best but breaks later optimizations. 3 was tried, - // but makes a mess in the emitter as lvalue switching is needed. So 4. - // SEL(...) -> temp - // if (COND(LTE(bit<=maxlsb))) ASSIGN(SEL(...)),temp) - const bool needDly = (m_assigndlyp != nullptr); - if (m_assigndlyp) { - // Delayed assignments become normal assignments, - // then the temp created becomes the delayed assignment - AstNode* const newp = new AstAssign{m_assigndlyp->fileline(), - m_assigndlyp->lhsp()->unlinkFrBackWithNext(), - m_assigndlyp->rhsp()->unlinkFrBackWithNext()}; - m_assigndlyp->replaceWith(newp); - VL_DO_CLEAR(pushDeletep(m_assigndlyp), m_assigndlyp = nullptr); - } + // We wrap the expression into IF AstNodeExpr* prep = nodep; // Scan back to put the condlvalue above all selects (IE top of the lvalue) @@ -121,32 +103,35 @@ class UnknownVisitor final : public VNVisitor { // Already exists; rather than IF(a,... IF(b... optimize to IF(a&&b, // Saves us teaching V3Const how to optimize, and it won't be needed again. if (const AstIf* const ifp = VN_AS(prep->user2p(), If)) { - UASSERT_OBJ(!needDly, prep, "Should have already converted to non-delay"); VNRelinker replaceHandle; AstNodeExpr* const earliercondp = ifp->condp()->unlinkFrBack(&replaceHandle); AstNodeExpr* const newp = new AstLogAnd{condp->fileline(), condp, earliercondp}; UINFO(4, "Edit BOUNDLVALUE " << newp); replaceHandle.relink(newp); } else { - AstVar* const varp - = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), prep->dtypep()}; - addVar(varp); AstNode* stmtp = prep->backp(); // Grab above point before we replace 'prep' while (!VN_IS(stmtp, NodeStmt)) stmtp = stmtp->backp(); - - prep->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE}); - if (m_timingControlp) m_timingControlp->unlinkFrBack(); - AstIf* const newp = new AstIf{ - fl, condp, - (needDly - ? static_cast(new AstAssignDly{ - fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}) - : static_cast(new AstAssign{ - fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}))}; + VNRelinker replaceHandle; + AstNode* const origStmtp = stmtp->unlinkFrBack(&replaceHandle); + AstNode* elseStmtp = nullptr; + const bool hasSideEffects = origStmtp->exists( + [](AstNode* const np) { return !np->isPure() || np->isTimingControl(); }); + if (hasSideEffects) { + // Copy original statement and replace `prep` with reference to tmp var to make + // sure that side effect will take place + m_statElses++; + elseStmtp = origStmtp->cloneTree(false); + AstVar* const varp = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), + prep->dtypep()}; + addVar(varp); + AstNode* const prepCopyp = prep->clonep(); + prepCopyp->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE}); + pushDeletep(prepCopyp); + } + AstIf* const newp = new AstIf{fl, condp, origStmtp, elseStmtp}; + replaceHandle.relink(newp); newp->branchPred(VBranchPred::BP_LIKELY); newp->isBoundsCheck(true); - UINFOTREE(9, newp, "", "_new"); - stmtp->addNextHere(newp); prep->user2p(newp); // Save so we may LogAnd it next time } } @@ -208,23 +193,6 @@ class UnknownVisitor final : public VNVisitor { m_ftaskp = nodep; iterateChildren(nodep); } - void visit(AstAssignDly* nodep) override { - VL_RESTORER(m_assigndlyp); - VL_RESTORER(m_timingControlp); - m_assigndlyp = nodep; - m_timingControlp = nodep->timingControlp(); - VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - } - void visit(AstAssignW* nodep) override { - VL_RESTORER(m_timingControlp); - m_timingControlp = nodep->timingControlp(); - VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - } - void visit(AstNodeAssign* nodep) override { - VL_RESTORER(m_timingControlp); - m_timingControlp = nodep->timingControlp(); - iterateChildren(nodep); - } void visit(AstCaseItem* nodep) override { VL_RESTORER(m_constXCvt); m_constXCvt = false; // Avoid losing the X's in casex @@ -296,7 +264,7 @@ class UnknownVisitor final : public VNVisitor { } else { // X or Z's become mask, ala case statements. V3Number nummask{rhsp, rhsp->width()}; - nummask.opBitsNonX(VN_AS(rhsp, Const)->num()); + nummask.opBitsNonXZ(VN_AS(rhsp, Const)->num()); V3Number numval{rhsp, rhsp->width()}; numval.opBitsOne(VN_AS(rhsp, Const)->num()); AstNodeExpr* const and1p = new AstAnd{nodep->fileline(), lhsp, @@ -546,7 +514,11 @@ class UnknownVisitor final : public VNVisitor { } else if (nodeDtp->isString()) { xnum = V3Number{nodep, V3Number::String{}, ""}; } else { - xnum.setAllBitsX(); + if (nodeDtp->isFourstate()) { + xnum.setAllBitsX(); + } else { + xnum.setAllBits0(); + } } AstNode* const newp = new AstCond{nodep->fileline(), condp, nodep, new AstConst{nodep->fileline(), xnum}}; @@ -584,6 +556,7 @@ public: } ~UnknownVisitor() override { // V3Stats::addStat("Unknowns, variables created", m_statUnkVars); + V3Stats::addStat("Unknowns, else branches created", m_statElses); } }; diff --git a/src/V3Unroll.cpp b/src/V3Unroll.cpp index ce677324b..b5413e8b6 100644 --- a/src/V3Unroll.cpp +++ b/src/V3Unroll.cpp @@ -375,10 +375,12 @@ class UnrollOneVisitor final : VNVisitor { process(nodep); } void visit(AstJumpGo* nodep) override { + if (!m_ok) return; // Remove trailing dead code if (nodep->nextp()) pushDeletep(nodep->nextp()->unlinkFrBackWithNext()); } void visit(AstLoop* nodep) override { + if (!m_ok) return; m_bindings.checkpoint(); std::pair pair = UnrollOneVisitor::apply(m_stats, m_bindings, nodep); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 861714e34..86d688cc7 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -255,13 +255,6 @@ class WidthVisitor final : public VNVisitor { EXTEND_OFF // No extension }; - int widthUnpacked(const AstNodeDType* const dtypep) { - if (const AstUnpackArrayDType* const arrDtypep = VN_CAST(dtypep, UnpackArrayDType)) { - return arrDtypep->subDTypep()->width() * arrDtypep->arrayUnpackedElements(); - } - return dtypep->width(); - } - static void packIfUnpacked(AstNodeExpr* const nodep) { if (AstUnpackArrayDType* const unpackDTypep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) { const int elementsNum = unpackDTypep->arrayUnpackedElements(); @@ -274,6 +267,9 @@ class WidthVisitor final : public VNVisitor { nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)}); } } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } // When fromp() is a DType (e.g. unlinked RefDType), resolve through // the ref chain; when it's an expression, dtypep() is already resolved. static AstNodeDType* fromDTypep(AstNode* fromp) { @@ -979,7 +975,10 @@ class WidthVisitor final : public VNVisitor { } const AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep()->skipRefToEnump(); if (VN_IS(lhsDtypep, DynArrayDType) || VN_IS(lhsDtypep, QueueDType) - || VN_IS(lhsDtypep, UnpackArrayDType)) { + || (VN_IS(lhsDtypep, UnpackArrayDType) && lhsDtypep->isStreamableFixedAggregate()) + || (VN_IS(lhsDtypep, NodeUOrStructDType) + && !VN_AS(lhsDtypep, NodeUOrStructDType)->packed() + && lhsDtypep->isStreamableFixedAggregate())) { nodep->dtypeSetStream(); } else if (lhsDtypep->isCompound()) { nodep->v3warn(E_UNSUPPORTED, @@ -1564,33 +1563,39 @@ class WidthVisitor final : public VNVisitor { } const bool loUnbounded = VN_IS(nodep->loBoundp(), Unbounded); const bool hiUnbounded = VN_IS(nodep->hiBoundp(), Unbounded); - if (loUnbounded || hiUnbounded) { - if (nodep->isStrong()) { - nodep->v3error("s_always range must be bounded (IEEE 1800-2023 16.12.11)"); - } else { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: unbounded always range (always [m:$])"); - } + // Strong always must be bounded (IEEE 1800-2023 16.12.11: "the range + // for a strong always shall be bounded"). Weak always [m:$] is legal: + // an unbounded upper bound imposes no end-of-trace obligation. + if (nodep->isStrong() && (loUnbounded || hiUnbounded)) { + AstNode* const boundp = loUnbounded ? nodep->loBoundp() : nodep->hiBoundp(); + boundp->v3error("s_always range must be bounded (IEEE 1800-2023 16.12.11)"); + nodep->dtypeSetBit(); + return; + } + if (loUnbounded) { + // Only the high bound may be $ (cycle_delay_const_range_expression). + nodep->loBoundp()->v3error("always range low bound must be a constant expression" + " (IEEE 1800-2023 16.12.11)"); nodep->dtypeSetBit(); return; } const AstConst* const loConstp = VN_CAST(nodep->loBoundp(), Const); const AstConst* const hiConstp = VN_CAST(nodep->hiBoundp(), Const); if (!loConstp) { - nodep->v3error("always range low bound must be a constant expression" - " (IEEE 1800-2023 16.12.11)"); + nodep->loBoundp()->v3error("always range low bound must be a constant expression" + " (IEEE 1800-2023 16.12.11)"); } - if (!hiConstp) { - nodep->v3error("always range high bound must be a constant expression" - " (IEEE 1800-2023 16.12.11)"); + if (!hiUnbounded && !hiConstp) { + nodep->hiBoundp()->v3error("always range high bound must be a constant expression" + " (IEEE 1800-2023 16.12.11)"); } if (loConstp && loConstp->toSInt() < 0) { - nodep->v3error("always range low bound must be non-negative" - " (IEEE 1800-2023 16.12.11)"); + nodep->loBoundp()->v3error("always range low bound must be non-negative" + " (IEEE 1800-2023 16.12.11)"); } - if (loConstp && hiConstp && hiConstp->toSInt() < loConstp->toSInt()) { - nodep->v3error("always range high bound must be >= low bound" - " (IEEE 1800-2023 16.12.11)"); + if (!hiUnbounded && loConstp && hiConstp && hiConstp->toSInt() < loConstp->toSInt()) { + nodep->hiBoundp()->v3error("always range high bound must be >= low bound" + " (IEEE 1800-2023 16.12.11)"); } bool hasPropertyOp = propp->isMultiCycleSva(); if (!hasPropertyOp) { @@ -1838,6 +1843,22 @@ class WidthVisitor final : public VNVisitor { nodep->dtypeSetBit(); } } + void visit(AstSClocked* nodep) override { + VL_RESTORER(m_underSExpr); + m_underSExpr = true; + m_hasSExpr = true; + assertAtExpr(nodep); + if (m_vup->prelim()) { + // Width each clock expression directly; the senitem chain is hoisted to + // the assertion clock by V3AssertNfa, where it is fully processed. + for (AstSenItem* senp = nodep->sensesp(); senp; + senp = VN_CAST(senp->nextp(), SenItem)) { + userIterateAndNext(senp->sensp(), WidthVP{SELF, BOTH}.p()); + } + iterateCheckBool(nodep, "exprp", nodep->exprp(), BOTH); + nodep->dtypeSetBit(); + } + } void visit(AstURandomRange* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { @@ -3074,6 +3095,7 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(nodep->dtypep(), nodep, "LHS var should be dtype completed"); } // UINFOTREE(9, nodep, "", "VRout"); + if (nodep->access().isWriteOrRW()) nodep->varp()->icoMaybeWritten(true); if (nodep->access().isWriteOrRW() && nodep->varp()->direction() == VDirection::CONSTREF) { nodep->v3error("Assigning to const ref variable: " << nodep->prettyNameQ()); } else if (nodep->access().isWriteOrRW() && nodep->varp()->isInput() @@ -3789,7 +3811,7 @@ class WidthVisitor final : public VNVisitor { if (!varp->didWidth()) userIterate(varp, nullptr); nodep->dtypep(foundp->dtypep()); nodep->varp(varp); - AstIface* const ifacep = adtypep->ifacep(); + AstIface* const ifacep = adtypep->ifaceViaCellp(); varp->sensIfacep(ifacep); nodep->didWidth(true); return; @@ -3830,7 +3852,25 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(viftopVarp, nodep, "No __Viftop variable for sub-interface cell"); if (!viftopVarp->didWidth()) userIterate(viftopVarp, nullptr); - nodep->dtypep(viftopVarp->dtypep()); + AstNodeDType* subDtypep = viftopVarp->dtypep(); + if (adtypep->isVirtual()) { + // A sub-interface selected through a virtual interface + // handle is itself a virtual reference (a runtime + // instance pointer), not a static instance. Mark the + // dtype virtual so a method call on it dispatches per + // instance instead of inlining to one fixed scope. + if (AstIfaceRefDType* const subRefp + = VN_CAST(subDtypep->skipRefp(), IfaceRefDType)) { + AstIfaceRefDType* const newDtypep = new AstIfaceRefDType{ + nodep->fileline(), subRefp->cellName(), subRefp->ifaceName()}; + newDtypep->ifacep(subRefp->ifacep()); + newDtypep->cellp(subRefp->cellp()); + newDtypep->isVirtual(true); + v3Global.rootp()->typeTablep()->addTypesp(newDtypep); + subDtypep = newDtypep; + } + } + nodep->dtypep(subDtypep); nodep->varp(viftopVarp); viftopVarp->sensIfacep(VN_AS(cellp->modp(), Iface)); nodep->didWidth(true); @@ -4781,7 +4821,9 @@ class WidthVisitor final : public VNVisitor { } } void methodCallIfaceRef(AstMethodCall* nodep, AstIfaceRefDType* adtypep) { - AstIface* const ifacep = adtypep->ifacep(); + // ifaceViaCellp() resolves the interface via cellp when ifacep is null, + // as for a sub-interface selected through a virtual interface handle. + AstIface* const ifacep = adtypep->ifaceViaCellp(); UINFO(5, __FUNCTION__ << ":" << nodep); if (AstNodeFTask* const ftaskp = VN_CAST(m_memberMap.findMember(ifacep, nodep->name()), NodeFTask)) { @@ -6295,14 +6337,14 @@ class WidthVisitor final : public VNVisitor { userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p()); // // UINFOTREE(1, nodep, "", "assign"); - AstNodeDType* const lhsDTypep + AstNodeDType* lhsDTypep = nodep->lhsp()->dtypep(); // Note we use rhsp for context determined // Check width of stream and wrap if needed if (AstNodeStream* const streamp = VN_CAST(nodep->rhsp(), NodeStream)) { AstNodeDType* const lhsDTypeSkippedRefp = lhsDTypep->skipRefp(); - const int lwidth = widthUnpacked(lhsDTypeSkippedRefp); - const int rwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); + const int lwidth = lhsDTypeSkippedRefp->widthStream(); + const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream(); if (lwidth != 0 && lwidth < rwidth) { nodep->v3widthWarn(lwidth, rwidth, "Target fixed size variable (" @@ -6314,16 +6356,18 @@ class WidthVisitor final : public VNVisitor { const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width(); UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS"); } - if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)) { + if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType) + || lowerAsFixedAggregate(lhsDTypeSkippedRefp)) { streamp->unlinkFrBack(); nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp, lhsDTypeSkippedRefp}); } } - if (const AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { + if (AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { const AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp(); - const int lwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); - const int rwidth = widthUnpacked(rhsDTypep); + AstNodeDType* const lhsStreamDTypep = streamp->lhsp()->dtypep()->skipRefp(); + const int lwidth = lhsStreamDTypep->widthStream(); + const int rwidth = rhsDTypep->widthStream(); if (rwidth != 0 && rwidth < lwidth) { nodep->v3widthWarn(lwidth, rwidth, "Stream target requires " @@ -6331,7 +6375,27 @@ class WidthVisitor final : public VNVisitor { << " bits, but source expression only provides " << rwidth << " bits (IEEE 1800-2023 11.4.14.3)"); } - if (VN_IS(rhsDTypep, UnpackArrayDType)) { + if (lowerAsFixedAggregate(lhsStreamDTypep)) { + AstNodeExpr* const streamExprp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const dstp = streamp->lhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); + if (VN_IS(streamp, StreamL)) { + streamp->lhsp(srcp); + streamp->dtypeSetLogicUnsized(srcp->width(), srcp->widthMin(), + VSigning::UNSIGNED); + srcp = streamExprp; + } else { + if (srcp->width() > lwidth) { + srcp = new AstSel{streamp->fileline(), srcp, srcp->width() - lwidth, + lwidth}; + } + VL_DO_DANGLING(pushDeletep(streamExprp), streamExprp); + } + nodep->lhsp(dstp); + nodep->rhsp(new AstCvtPackedToArray{srcp->fileline(), srcp, lhsStreamDTypep}); + nodep->dtypeFrom(dstp); + lhsDTypep = nodep->lhsp()->dtypep(); + } else if (VN_IS(rhsDTypep, UnpackArrayDType)) { AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); nodep->rhsp( new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()}); @@ -9639,7 +9703,8 @@ class WidthVisitor final : public VNVisitor { continue; } else if (const AstNodeUOrStructDType* const adtypep = VN_CAST(dtypep, NodeUOrStructDType)) { - bits *= adtypep->width(); + bits *= adtypep->isStreamableFixedAggregate() ? adtypep->widthStream() + : adtypep->width(); break; } else if (const AstBasicDType* const adtypep = VN_CAST(dtypep, BasicDType)) { bits *= adtypep->width(); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 13bb95c6f..b1f0a9af0 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -22,6 +22,7 @@ #include "V3AssertNfa.h" #include "V3AssertPre.h" #include "V3Ast.h" +#include "V3AstPatterns.h" #include "V3Begin.h" #include "V3Branch.h" #include "V3Broken.h" @@ -98,12 +99,10 @@ #include "V3Scoreboard.h" #include "V3Slice.h" #include "V3Split.h" -#include "V3SplitAs.h" #include "V3SplitVar.h" #include "V3Stats.h" #include "V3String.h" #include "V3Subst.h" -#include "V3TSP.h" #include "V3Table.h" #include "V3Task.h" #include "V3ThreadPool.h" @@ -261,6 +260,7 @@ static void process() { // Assertion insertion // After we've added block coverage, but before other nasty transforms + V3AssertCommon::collectDefaultDisable(v3Global.rootp()); V3AssertNfa::assertNfaAll(v3Global.rootp()); // V3AssertProp removed: NFA subsumes multi-cycle property lowering. // Unsupported constructs fall through to V3AssertPre. @@ -421,7 +421,6 @@ static void process() { // Split single ALWAYS blocks into multiple blocks for better ordering chances if (v3Global.opt.fSplit()) V3Split::splitAll(v3Global.rootp()); - V3SplitAs::splitAsAll(v3Global.rootp()); // Create tracing sample points, before we start eliminating signals if (v3Global.opt.trace()) V3TraceDecl::traceDeclAll(v3Global.rootp()); @@ -543,6 +542,8 @@ static void process() { V3Const::constifyAll(v3Global.rootp()); V3Dead::deadifyAll(v3Global.rootp()); + if (v3Global.opt.dumpAstPatterns()) V3AstPatterns::dumpAll(v3Global.rootp(), "prec"); + // Here down, widthMin() is the Verilog width, and width() is the C++ width // Bits between widthMin() and width() are irrelevant, but may be non-zero. v3Global.widthMinUsage(VWidthMinUsage::VERILOG_WIDTH); @@ -584,8 +585,14 @@ static void process() { // Must be after all Sel/array index based optimizations V3Reloop::reloopAll(v3Global.rootp()); } + } - if (v3Global.opt.inlineCFuncs()) { + // These are no longer needed, remove references before CFunc inlining + v3Global.rootp()->evalp(nullptr); + v3Global.rootp()->evalNbap(nullptr); + + if (!v3Global.opt.lintOnly() && !v3Global.opt.serializeOnly()) { + if (v3Global.opt.fInlineCFuncs()) { // Inline small CFuncs to reduce function call overhead V3InlineCFuncs::inlineAll(v3Global.rootp()); } @@ -631,6 +638,8 @@ static void process() { } } + if (v3Global.opt.dumpAstPatterns()) V3AstPatterns::dumpAll(v3Global.rootp(), "emit"); + // Output the text if (!v3Global.opt.lintOnly() && !v3Global.opt.serializeOnly() && !v3Global.opt.dpiHdrOnly()) { @@ -734,7 +743,6 @@ static bool verilate(const string& argString) { VHashSha256::selfTest(); VSpellCheck::selfTest(); V3Graph::selfTest(); - V3TSP::selfTest(); V3ScoreboardBase::selfTest(); V3Order::selfTestParallel(); V3ExecGraph::selfTest(); @@ -808,7 +816,7 @@ static bool verilate(const string& argString) { V3Os::filesystemFlushBuildDir(v3Global.opt.makeDir()); if (v3Global.opt.hierTop()) V3Os::filesystemFlushBuildDir(v3Global.opt.hierTopDataDir()); if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "WroteAll"); - if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "WroteFast"); + if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "WroteFast", true); // Final writing shouldn't throw warnings, but... V3Error::abortIfWarnings(); diff --git a/src/bisonpre b/src/bisonpre index 9be647f39..5db061a5a 100755 --- a/src/bisonpre +++ b/src/bisonpre @@ -145,6 +145,26 @@ def clean_output(filename: str, outname: str, is_output: bool, is_c: bool) -> No out.append(line) lines = out out = [] + # Code updates for the .c + if outname.endswith(".c"): + for line in lines: + modify = "YYSTACK_FREE (yyss);" in line and "if (yyss != yyssa)" in out[-1] + ifdefined = "#if defined __GNUC__ && ! defined __ICC\n" + endif = "#endif\n" + if modify: + out.insert(-1, ifdefined) + out.insert(-1, " _Pragma (\"GCC diagnostic push\")\n") + out.insert( + -1, + " _Pragma (\"GCC diagnostic ignored \\\"-Wfree-nonheap-object\\\"\")\n") + out.insert(-1, endif) + out.append(line) + if modify: + out.append(ifdefined) + out.append(" _Pragma (\"GCC diagnostic pop\")\n") + out.append(endif) + lines = out + out = [] with open(outname, "w", encoding="utf-8") as fh: tmpy = re.escape(tmp_prefix() + ".y") diff --git a/src/cppcheck-suppressions.txt b/src/cppcheck-suppressions.txt index 754ef3c4b..6d9ab321d 100644 --- a/src/cppcheck-suppressions.txt +++ b/src/cppcheck-suppressions.txt @@ -137,8 +137,6 @@ constParameterPointer:src/V3Tristate.cpp constParameterReference:src/V3Tristate.cpp constVariablePointer:src/V3Tristate.cpp constVariableReference:src/V3Tristate.cpp -constVariablePointer:src/V3TSP.cpp -constVariableReference:src/V3TSP.cpp constParameterPointer:src/V3Undriven.cpp constVariablePointer:src/V3Undriven.cpp constParameterPointer:src/V3Unroll.cpp diff --git a/src/verilog.y b/src/verilog.y index 663e8a206..6d6efea6b 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3121,7 +3121,7 @@ sigAttr: | yVL_PUBLIC_FLAT { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT}; v3Global.dpi(true); } | yVL_PUBLIC_FLAT_RD { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RD}; v3Global.dpi(true); } | yVL_PUBLIC_FLAT_RW attr_event_controlE { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RW}; v3Global.dpi(true); DEL($2); } - | yVL_ISOLATE_ASSIGNMENTS { $$ = new AstAttrOf{$1, VAttrType::VAR_ISOLATE_ASSIGNMENTS}; } + | yVL_ISOLATE_ASSIGNMENTS { $$ = nullptr; /* Historical, now has no effect */ } | yVL_SC_BIGUINT { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BIGUINT}; } | yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; } | yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; } @@ -4657,12 +4657,12 @@ task_prototype: // ==IEEE: task_prototype function_declaration: // IEEE: function_declaration + function_body_declaration yFUNCTION dynamic_override_specifiersE lifetimeE funcId funcIsolateE tfGuts yENDFUNCTION endLabelE - { $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6); + { $$ = $4; $$->addStmtsp($6); $$->baseOverride($2); $$->lifetime($3); GRAMMARP->endLabel($8, $$, $8); } | yFUNCTION dynamic_override_specifiersE lifetimeE funcIdNew funcIsolateE tfNewGuts yENDFUNCTION endLabelE - { $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6); + { $$ = $4; $$->addStmtsp($6); $$->baseOverride($2); $$->lifetime($3); GRAMMARP->endLabel($8, $$, $8); } @@ -6497,14 +6497,34 @@ concurrent_assertion_statement: // ==IEEE: concurrent_assertion_stat | yCOVER yPROPERTY '(' property_spec ')' stmt { $$ = new AstCover{$1, $4, $6, VAssertType::CONCURRENT}; } // // IEEE: cover_sequence_statement + // // Reuses AstCover + AstPropSpec (same wrapper as + // // cover_property_statement above) and the isCoverSeq + // // flag drives V3AssertNfa to fire stmt per end-of-match + // // (IEEE 1800-2023 16.14.3), not per property success. | yCOVER ySEQUENCE '(' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $6); } - // // IEEE: yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt - // // sexpr already includes "clocking_event sexpr" + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), nullptr, nullptr, $4}, + $6, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } + | yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), $4, nullptr, $5}, + $7, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } | yCOVER ySEQUENCE '(' clocking_event yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $8, $10, $12);} + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), $4, $8, $10}, + $12, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } | yCOVER ySEQUENCE '(' yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($7, $9, $11); } + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$7->fileline(), nullptr, $7, $9}, + $11, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } // // IEEE: restrict_property_statement | yRESTRICT yPROPERTY '(' property_spec ')' ';' { $$ = new AstRestrict{$1, $4}; } @@ -6653,6 +6673,12 @@ sequence_declarationBody: // IEEE: part of sequence_declaration | assertion_variable_declarationList sexpr ';' { $$ = addNextNull($1, $2); } | sexpr { $$ = $1; } | sexpr ';' { $$ = $1; } + // // IEEE: clocking_event sequence_expr (16.7) + // // A leading clocking event on a named sequence body. + | '@' '(' event_expression ')' sexpr { $$ = new AstSClocked{$1, $3, $5}; } + | '@' '(' event_expression ')' sexpr ';' { $$ = new AstSClocked{$1, $3, $5}; } + | '@' senitemVar sexpr { $$ = new AstSClocked{$1, $2, $3}; } + | '@' senitemVar sexpr ';' { $$ = new AstSClocked{$1, $2, $3}; } ; property_spec: // IEEE: property_spec @@ -6664,14 +6690,8 @@ property_spec: // IEEE: property_spec { $$ = new AstPropSpec{$1, $3, nullptr, $5}; } | '@' senitemVar pexpr { $$ = new AstPropSpec{$1, $2, nullptr, $3}; } - // // Disable applied after the event occurs, - // // so no existing AST can represent this - | yDISABLE yIFF '(' expr ')' '@' '(' senitemEdge ')' pexpr - { $$ = new AstPropSpec{$1, $8, nullptr, new AstLogOr{$1, $4, $10}}; - BBUNSUP($1, "Unsupported: property '(disable iff (...) @ (...)'\n" - + $1->warnMore() - + "... Suggest use property '(@(...) disable iff (...))'"); } - //UNSUP remove above + | yDISABLE yIFF '(' expr ')' '@' '(' senitem ')' pexpr + { $$ = new AstPropSpec{$1, $8, $4, $10}; } | yDISABLE yIFF '(' expr ')' pexpr { $$ = new AstPropSpec{$4->fileline(), nullptr, $4, $6}; } | pexpr { $$ = new AstPropSpec{$1->fileline(), nullptr, nullptr, $1}; } ; @@ -8559,8 +8579,7 @@ vltVarAttrSpecE: ; vltVarAttrFront: - yVLT_ISOLATE_ASSIGNMENTS { $$ = VAttrType::VAR_ISOLATE_ASSIGNMENTS; } - | yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; } + yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; } | yVLT_PUBLIC { $$ = VAttrType::VAR_PUBLIC; v3Global.dpi(true); } | yVLT_PUBLIC_FLAT { $$ = VAttrType::VAR_PUBLIC_FLAT; v3Global.dpi(true); } | yVLT_PUBLIC_FLAT_RD { $$ = VAttrType::VAR_PUBLIC_FLAT_RD; v3Global.dpi(true); } @@ -8574,6 +8593,7 @@ vltVarAttrFront: vltVarAttrFrontDeprecated: yVLT_CLOCK_ENABLE { } | yVLT_CLOCKER { } + | yVLT_ISOLATE_ASSIGNMENTS { } | yVLT_NO_CLOCKER { } ; diff --git a/test_regress/AGENTS.md b/test_regress/AGENTS.md new file mode 100644 index 000000000..7d6b1b708 --- /dev/null +++ b/test_regress/AGENTS.md @@ -0,0 +1,134 @@ + + +# test_regress/ -- regression tests + +Covers `.v`/`.sv` sources, `.py` drivers, and `.out` golden files under +`test_regress/`. Read the repository-root [AGENTS.md](../AGENTS.md) first. This +file has two parts: **Orientation** explains how the harness runs a test; +**Before you open a PR** is the test-authoring checklist. + +______________________________________________________________________ + +# Orientation: how the harness works + +- **A test is a `.v`/`.sv` source plus a matching `.py` driver in `t/`.** The + driver calls `test.compile()`, `test.execute()`, and/or `test.lint()`. Without a + `.py` the source never runs and is dead code giving false coverage confidence. +- **Golden output lives in `.out` files**, compared via `expect_filename`. + Generate them with `HARNESS_UPDATE_GOLDEN=1 python3 t/.py` -- never + hand-write them; the harness normalizes paths, version markers, and line numbers + that hand edits get wrong. +- **Run one test from the repository root:** `test_regress/t/t_.py`. When + running from a checkout, set `VERILATOR_ROOT` to the checkout root first so the + harness compiles the generated C++ against the right headers. +- **`test.compile()` defaults are richer than they look:** the vlt driver + auto-injects `--exe` and a generated main, and `assert property` fires its + action blocks without `--assert` -- try the simple form before adding flags. +- **The `t_dist_*` tests enforce repository conventions** (file headers, sorted + lists, warning documentation, ASCII-only) -- run the relevant ones before + submitting. + +______________________________________________________________________ + +# Before you open a PR + +## Naming + +- Name tests `t_{category}_{description}` in snake_case; the first word groups the + category (`t_lint_unused_func_bad`, not `t_unused_func_lint_bad`) so related + tests are findable and runnable together. +- Use `_bad` when the code is illegal SystemVerilog, `_unsup` when it is legal but + unsupported, `_off` for disabled-behavior tests -- never `_fail`. +- Keep filenames under roughly 30-35 characters. + +## Files and drivers + +- Every `.v` needs a matching `.py` driver that actually calls `test.compile()` + and `test.execute()` (or `test.lint()` for static-analysis-only tests) -- a + driver that sets up but never runs hides coverage gaps silently. +- Copy the license header from an existing file: `.v` tests carry the CC0 notice, + `.py` drivers carry the standard driver header -- distribution tests check + headers on every committed file. +- Use `--binary` instead of `--exe --main --timing` -- it implies the others. +- Error tests use `test.compile(fails=True, expect_filename=test.golden_filename)` + (or `test.lint(...)`) and must not call `test.execute()`. +- Use `.out` golden files with `expect_filename` instead of inline `expect` + regex strings -- goldens are diffable and maintainable. +- Use `test.glob_one()`/`test.glob_some()` and `test.timeout()` instead of raw + `glob.glob()` and manual `time.time()` measurements. +- Coverage tests run `verilator_coverage` and verify individual bins and points, + not just aggregate percentages. +- Assert optimization stats with exact expected counts: + `test.file_grep(test.stats, r'Optimizations, ...\s+(\d+)', N)`. +- Add a `_protect_ids` variant when a feature emits user identifiers or filenames; + use conservative thread counts (2 or fewer) in multithreaded tests. +- Extend existing test files with related cases instead of creating many + single-purpose files; keep drivers minimal -- test logic belongs in the `.v`. + +## Golden .out files + +- Never hand-write or hand-edit `.out` goldens -- regenerate with + `HARNESS_UPDATE_GOLDEN=1`. +- When a feature lands, remove its now-supported entries from `t_*_unsup.v` / + `t_*_bad.v` in the same change and regenerate the goldens -- stale entries no + longer error and reviewers will flag them. + +## Verilog style + +- 2-space indentation, no tabs. + +- Declarations are flush-left with a single space between type and name; never + column-align: + + ```systemverilog + // WRONG: column-aligned + bit [63:0] crc = 64'h5aef0c8d_d70a4497; + int cyc = 0; + // RIGHT: flush-left + bit [63:0] crc = 64'h5aef0c8d_d70a4497; + int cyc = 0; + ``` + +- Run `nodist/verilog_format` on new `.v` files; wrap macro definitions in + `// verilog_format: off`/`on` so the formatter does not split them. + +- Use `$display("%0d", ...)` not `%d` -- avoids leading-space padding. + +- Wrap Verilator-specific test code (e.g. `$c`) in `` `ifdef VERILATOR ``. + +- Use inline `// verilator lint_off WARNCODE` only when that warning is itself + under test -- fix root causes otherwise. + +- Use only IEEE 1800-compliant constructs other simulators also accept -- tests + validate standard behavior, not Verilator's parser leniency. + +- Omit optional end labels on `endmodule`/`endclass`/`endtask`/`endfunction`. + +## Self-checking + +- Use the `checkh`/`checkd`/`checks` assertion macros, not manual + `if`/`$display`/`$stop` sequences. Note `checkh` prints with `%p`, which renders + integers as hex -- use `checkd` for integer comparisons. +- Use the `` `stop `` macro rather than direct `$stop`. +- Drive logic with runtime-varying inputs (counters, CRC/LFSR registers) so + constant folding cannot pre-evaluate the logic under test; check behavior across + multiple clock cycles, not just initial values. +- For a test whose pass/fail depends on varying or random values, loop enough + iterations that the values demonstrably differ and size the value space so a + failure is probable per run, then confirm the test fails on an un-fixed tree + before submitting. + +## Test design + +- Use non-power-of-2, non-word-aligned widths (7, 15, 31, 33, 63, 65, 95) -- + exposes masking and word-boundary bugs that 32/64/128 hide. +- Test both `[high:low]` and `[low:high]` orderings plus non-zero bounds like + `[3:1]`; use different ranges for each axis of multidimensional arrays. +- When adding type support, test all basic types (chandle, string, real) and + typedef-wrapped variants. +- Include the issue's own reproducer as a committed test, and verify it fails + without the fix. +- Assert non-blocking-assignment results in the cycle immediately after they take + effect, before later overwrites, using indices that change post-NBA. diff --git a/test_regress/t/t_altera_lpm.v b/test_regress/t/t_altera_lpm.v index 967cd1dea..19d445364 100644 --- a/test_regress/t/t_altera_lpm.v +++ b/test_regress/t/t_altera_lpm.v @@ -49,7 +49,7 @@ //See also: https://github.com/twosigma/verilator_support // verilog_format: off -// verilator lint_off COMBDLY,INITIALDLY,LATCH,MULTIDRIVEN,UNSIGNED,WIDTH +// verilator lint_off COMBDLY,LATCH,MULTIDRIVEN,UNSIGNED,WIDTH // BEGINNING OF MODULE `timescale 1 ps / 1 ps diff --git a/test_regress/t/t_assert_always_unbounded.py b/test_regress/t/t_assert_always_unbounded.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_always_unbounded.v b/test_regress/t/t_assert_always_unbounded.v new file mode 100644 index 000000000..9f41a2627 --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + int cyc = 0; + logic a_high = 1'b1; + logic a_low = 1'b0; + logic a_drop = 1'b1; + + // Weak always [m:$] is a pure safety property: it has no end-of-trace + // obligation, so it can only fail (else fires), once per failing tick. + int high_fail_q[$]; + int low0_fail_q[$]; + int low2_fail_q[$]; + int drop_fail_q[$]; + + // Constant-true input: never fails at any tick. + assert property (@(posedge clk) always [2:$] a_high) else high_fail_q.push_back(cyc); + + // Constant-false input, m=0: fails at every observed tick. + assert property (@(posedge clk) always [0:$] a_low) else low0_fail_q.push_back(cyc); + + // Constant-false input, m=2: fails at every tick once the window is live. + assert property (@(posedge clk) always [2:$] a_low) else low2_fail_q.push_back(cyc); + + // a_drop is high then drops at cyc 5 and stays low: deterministic single + // transition, so Verilator and Questa agree on the failing ticks exactly. + assert property (@(posedge clk) always [2:$] a_drop) else drop_fail_q.push_back(cyc); + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc >= 4) a_drop <= 1'b0; + if (cyc == 19) begin + // Counts pinned to Verilator (NFA per-cycle reject). For all-fail windows + // Questa is one lower (it does not fire the end-of-sim tick); see the sva + // lessons "multi-cycle end-of-simulation offset" note. + `checkd(high_fail_q.size(), 0); // Questa: 0 + `checkd(low0_fail_q.size(), 20); // Questa: 19 + `checkd(low2_fail_q.size(), 18); // Questa: 17 + `checkd(drop_fail_q[0], 5); // first fail tick: a_drop sampled low from cyc 5 + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_assert_always_unbounded_bad.out b/test_regress/t/t_assert_always_unbounded_bad.out new file mode 100644 index 000000000..155120065 --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded_bad.out @@ -0,0 +1,14 @@ +%Error: t/t_assert_always_unbounded_bad.v:13:35: s_always range must be bounded (IEEE 1800-2023 16.12.11) + : ... note: In instance 't' + 13 | assert property (@(posedge clk) s_always a); + | ^~~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_assert_always_unbounded_bad.v:14:47: s_always range must be bounded (IEEE 1800-2023 16.12.11) + : ... note: In instance 't' + 14 | assert property (@(posedge clk) s_always [2:$] a); + | ^ +%Error: t/t_assert_always_unbounded_bad.v:18:43: always range low bound must be a constant expression (IEEE 1800-2023 16.12.11) + : ... note: In instance 't' + 18 | assert property (@(posedge clk) always [$:5] a); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_assert_always_unbounded_bad.py b/test_regress/t/t_assert_always_unbounded_bad.py new file mode 100755 index 000000000..77a0ac64b --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(expect_filename=test.golden_filename, fails=True) + +test.passes() diff --git a/test_regress/t/t_assert_always_unbounded_bad.v b/test_regress/t/t_assert_always_unbounded_bad.v new file mode 100644 index 000000000..9103865ef --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t (input clk); + logic a; + + // A strong always must be bounded (IEEE 1800-2023 16.12.11): there is no bare + // s_always grammar production, and s_always [m:$] is the explicit "// Illegal" + // example (p5). Both forms are rejected. + assert property (@(posedge clk) s_always a); + assert property (@(posedge clk) s_always [2:$] a); + + // A weak always range may only place $ on the high bound; an unbounded low + // bound is not a legal cycle_delay_const_range_expression. + assert property (@(posedge clk) always [$:5] a); + +endmodule diff --git a/test_regress/t/t_assert_basic.v b/test_regress/t/t_assert_basic.v index 7d96ac19c..55360519d 100644 --- a/test_regress/t/t_assert_basic.v +++ b/test_regress/t/t_assert_basic.v @@ -13,6 +13,24 @@ module t ( integer cyc; initial cyc = 1; wire [7:0] cyc_copy = cyc[7:0]; + typedef enum logic { CAST_ONE = 1'b1 } cast_e; + cast_e cast_dst; + integer action_hits = 0; + + assert property (@(posedge clk) 1'b1) + if (cyc >= 0) action_hits++; + + assert property (@(posedge clk) 1'b1) + action_hits++; + else + action_hits--; + + cover property (@(posedge clk) 1'b1) + action_hits++; + + initial begin + $cast(cast_dst, 1); + end always @(negedge clk) begin AssertionFalse1 : assert (cyc < 100); @@ -26,6 +44,12 @@ module t ( if (cyc != 0) begin cyc <= cyc + 1; toggle <= !cyc[0]; + assert (cyc >= 0) + if (cyc >= 0) action_hits++; + assert (cyc >= 0) + action_hits++; + else + action_hits--; if (cyc == 7) assert (cyc[0] == cyc[1]); // bug743 if (cyc == 9) begin `ifdef FAILING_ASSERTIONS diff --git a/test_regress/t/t_assert_ctl_arg.cpp b/test_regress/t/t_assert_ctl_arg.cpp index 7e386b5b0..684db6436 100644 --- a/test_regress/t/t_assert_ctl_arg.cpp +++ b/test_regress/t/t_assert_ctl_arg.cpp @@ -82,6 +82,61 @@ void verilatedTest() { contextp->assertOn(false); // Now everything is disabled TEST_CHECK_Z(contextp->assertOn()); + + // Unified runtime query getter + using Query = VerilatedAssertCtlQuery; + constexpr uint32_t LOCK = 1; + constexpr uint32_t UNLOCK = 2; + constexpr uint32_t ON = 3; + constexpr uint32_t OFF = 4; + constexpr uint32_t KILL = 5; + constexpr uint32_t PASS_ON = 6; + constexpr uint32_t PASS_OFF = 7; + constexpr uint32_t FAIL_ON = 8; + constexpr uint32_t FAIL_OFF = 9; + constexpr uint32_t NONVACUOUS_ON = 10; + constexpr uint32_t VACUOUS_OFF = 11; + constexpr uint32_t TYPE = 1; + constexpr uint32_t DIRECTIVE = 1; + + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, 0, DIRECTIVE)); + + contextp->assertCtl(LOCK, TYPE, DIRECTIVE); + contextp->assertCtl(ON, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(UNLOCK, TYPE, DIRECTIVE); + contextp->assertCtl(ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(OFF, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + + const uint32_t killBefore = contextp->assertCtlGet(Query::ASSERT_CTL_KILL, TYPE, DIRECTIVE); + contextp->assertCtl(KILL, TYPE, DIRECTIVE); + TEST_CHECK_EQ(contextp->assertCtlGet(Query::ASSERT_CTL_KILL, TYPE, DIRECTIVE), killBefore + 1); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + contextp->assertCtl(PASS_OFF, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + + contextp->assertCtl(NONVACUOUS_ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + contextp->assertCtl(PASS_ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + contextp->assertCtl(VACUOUS_OFF, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_FAIL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(FAIL_OFF, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_FAIL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(FAIL_ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_FAIL_ON, TYPE, DIRECTIVE)); } int main(int argc, char** argv) { verilatedTest(); diff --git a/test_regress/t/t_assert_ctl_arg_unsup.out b/test_regress/t/t_assert_ctl_arg_unsup.out index c9bad92c3..8325ccdf1 100644 --- a/test_regress/t/t_assert_ctl_arg_unsup.out +++ b/test_regress/t/t_assert_ctl_arg_unsup.out @@ -1,18 +1,18 @@ %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:15:5: Unsupported: assert control assertion_type : ... note: In instance 't' - 15 | $assertcontrol(OFF, EXPECT); + 15 | $assertcontrol(OFF, UNIQUE); | ^~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:16:5: Unsupported: assert control assertion_type : ... note: In instance 't' - 16 | $assertcontrol(OFF, UNIQUE); + 16 | $assertcontrol(OFF, UNIQUE0); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:17:5: Unsupported: assert control assertion_type : ... note: In instance 't' - 17 | $assertcontrol(OFF, UNIQUE0); + 17 | $assertcontrol(OFF, PRIORITY); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:18:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:18:5: Unsupported: non-const assert directive type expression : ... note: In instance 't' - 18 | $assertcontrol(OFF, PRIORITY); + 18 | $assertcontrol(OFF, 1, directive_type); | ^~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_arg_unsup.v b/test_regress/t/t_assert_ctl_arg_unsup.v index b041652f2..c82410860 100644 --- a/test_regress/t/t_assert_ctl_arg_unsup.v +++ b/test_regress/t/t_assert_ctl_arg_unsup.v @@ -6,15 +6,15 @@ module t; let OFF = 4; - let EXPECT = 16; let UNIQUE = 32; let UNIQUE0 = 64; let PRIORITY = 128; + logic directive_type = 1'b1; initial begin - $assertcontrol(OFF, EXPECT); $assertcontrol(OFF, UNIQUE); $assertcontrol(OFF, UNIQUE0); $assertcontrol(OFF, PRIORITY); + $assertcontrol(OFF, 1, directive_type); end endmodule diff --git a/test_regress/t/t_assert_ctl_concurrent.v b/test_regress/t/t_assert_ctl_concurrent.v index 3cdda2bc1..c7d62648a 100644 --- a/test_regress/t/t_assert_ctl_concurrent.v +++ b/test_regress/t/t_assert_ctl_concurrent.v @@ -7,32 +7,49 @@ module t; bit clock = 1'b0; - bit reset = 1'b0; + bit start = 1'b0; + bit done = 1'b0; + int concurrent_fails = 0; initial begin + $asserton; + + @(negedge clock); + start = 1'b1; + done = 1'b0; + + @(negedge clock); + start = 1'b0; $assertkill; + $asserton; - #10 reset = 1'b1; - $display("%t: deassert reset %d", $time, reset); + @(posedge clock); + #1; + if (concurrent_fails != 0) $stop; - #40 $asserton; + @(negedge clock); + start = 1'b1; + done = 1'b0; - reset = 1'b0; - $display("%t: deassert reset %d", $time, reset); + @(negedge clock); + start = 1'b0; - #200 $display("%t: finish", $time); + @(posedge clock); + #1; + if (concurrent_fails != 1) $stop; + + $assertcontrol(5, 1, 1); + $asserton; + + $display("%t: finish", $time); $write("*-* All Finished *-*\n"); $finish; - end - always #10 clock = ~clock; - reg r = 1'b0; - - always @(posedge clock) if (reset) r <= 1'b1; + always #5 clock = ~clock; assert_test : - assert property (@(posedge clock) (reset | r)) - else $error("%t: assertion triggered", $time); + assert property (@(posedge clock) start |-> ##1 done) + else concurrent_fails++; endmodule diff --git a/test_regress/t/t_assert_ctl_immediate.out b/test_regress/t/t_assert_ctl_immediate.out index a10fd21c4..58b76b5f2 100644 --- a/test_regress/t/t_assert_ctl_immediate.out +++ b/test_regress/t/t_assert_ctl_immediate.out @@ -1,6 +1,15 @@ -[0] %Error: t_assert_ctl_immediate.v:51: Assertion failed in top.t.module_with_assertctl: 'assert' failed. --Info: t/t_assert_ctl_immediate.v:51: Verilog $stop, ignored due to +verilator+error+limit -[0] %Error: t_assert_ctl_immediate.v:57: Assertion failed in top.t.module_with_assertctl: 'assert' failed. -[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. -[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:52: Assertion failed in top.t.module_with_assertctl: 'assert' failed. +-Info: t/t_assert_ctl_immediate.v:52: Verilog $stop, ignored due to +verilator+error+limit +[0] %Error: t_assert_ctl_immediate.v:58: Assertion failed in top.t.module_with_assertctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:159: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:132: Assertion failed in top.t.module_with_method_ctl.Ctl.check_positive: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:169: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:93: Assertion failed in top.t.module_with_method_ctl.iface.check_positive: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:180: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:186: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:195: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:199: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:203: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. *-* All Finished *-* diff --git a/test_regress/t/t_assert_ctl_immediate.v b/test_regress/t/t_assert_ctl_immediate.v index 6c5848aaf..cc9d22b1b 100644 --- a/test_regress/t/t_assert_ctl_immediate.v +++ b/test_regress/t/t_assert_ctl_immediate.v @@ -10,6 +10,7 @@ module t ( module_with_assert module_with_assert (clk); module_with_assertctl module_with_assertctl (clk); + module_with_method_ctl module_with_method_ctl (); always @(posedge clk) begin assert (0); @@ -63,3 +64,146 @@ module module_with_assertctl ( f_assert(); end endmodule + +// Assertion control invoked from class methods and interface functions, in a +// single sub-module with a single initial block so the golden output order is +// stable across --fno-inline (otherwise parallel sub-module initials are +// interleaved differently by the inliner). +// +// Covers: +// - class task $assertoff / $asserton +// - class static method $assertcontrol(Off=4 / On=3), IEEE 1800-2023 Table 20-5 +// - $assertkill from a class method +// - assert that lives inside a class method (obeys context-global gating) +// - interface function $assertoff / $asserton +// - assert inside an interface function +// - virtual interface dispatch to an assertion-control function +// - interface class (AstClass with isInterfaceClass) via a concrete impl +// - multiple instances of each thing: OFF via one instance, ON via another +// (assertion control is global per-context, IEEE 1800-2023 20.11) + +interface AssertCtlIf; + function void suppress(); + $assertoff; + endfunction + function void enable(); + $asserton; + endfunction + function void check_positive(int v); + assert (v > 0); + endfunction +endinterface + +// verilog_format: off (verible-verilog-format mangles `pure virtual function`) +interface class IAssertCtl; + pure virtual function void suppress(); + pure virtual function void enable(); +endclass +// verilog_format: on + +class IAssertCtlImpl implements IAssertCtl; + virtual function void suppress(); + $assertoff; + endfunction + virtual function void enable(); + $asserton; + endfunction +endclass + +module module_with_method_ctl; + class Ctl; + virtual AssertCtlIf vif; + static function void off_all(); + $assertcontrol(4); + endfunction + static function void on_all(); + $asserton; + endfunction + function void kill_all(); + $assertkill; + endfunction + function void inst_off(); + $assertoff; + endfunction + function void inst_on(); + $asserton; + endfunction + function void check_positive(int v); + assert (v > 0); + endfunction + function void vif_suppress(); + vif.suppress(); + endfunction + function void vif_enable(); + vif.enable(); + endfunction + endclass + + Ctl c; + Ctl c2; + AssertCtlIf iface (); + AssertCtlIf iface2 (); + IAssertCtlImpl impl; + IAssertCtlImpl impl2; + + initial begin + c = new; + c2 = new; + impl = new; + impl2 = new; + + // --- class method coverage --- + Ctl::off_all(); + assert (0); // gated via class static -> no fire + Ctl::on_all(); + assert (0); // fires + Ctl::off_all(); + c.check_positive(-1); // assert inside class method, gated -> no fire + Ctl::on_all(); + c.check_positive(-2); // assert inside class method, fires + + // --- interface function coverage --- + iface.suppress(); + assert (0); // gated via iface fn -> no fire + iface.enable(); + assert (0); // fires + iface.suppress(); + iface.check_positive(-1); // assert inside iface fn, gated -> no fire + iface.enable(); + iface.check_positive(-2); // assert inside iface fn, fires + + // --- virtual interface dispatch coverage --- + c.vif = iface; + c.vif_suppress(); + assert (0); // gated via virtual interface dispatch -> no fire + c.vif_enable(); + assert (0); // fires + + // --- interface class via concrete impl --- + impl.suppress(); + assert (0); // gated via interface-class impl -> no fire + impl.enable(); + assert (0); // fires + + // --- multiple instances: OFF via one instance, ON via another --- + // Assertion control is global per-context (IEEE 1800-2023 20.11, no scope + // list), so OFF issued via one instance gates every assertion and ON issued + // via a different instance re-enables them. + c.inst_off(); // class: OFF via c + assert (0); // gated -> no fire + c2.inst_on(); // class: ON via c2 + assert (0); // fires + iface.suppress(); // interface: OFF via iface + assert (0); // gated -> no fire + iface2.enable(); // interface: ON via iface2 + assert (0); // fires + impl.suppress(); // interface class: OFF via impl + assert (0); // gated -> no fire + impl2.enable(); // interface class: ON via impl2 + assert (0); // fires + + // --- $assertkill (last: terminal per IEEE 1800-2023 Table 20-5) --- + c.kill_all(); + assert (0); // killed -> no fire + end +endmodule diff --git a/test_regress/t/t_assert_ctl_lock.py b/test_regress/t/t_assert_ctl_lock.py new file mode 100755 index 000000000..fdd1c0d4d --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary --assert"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_ctl_lock.v b/test_regress/t/t_assert_ctl_lock.v new file mode 100644 index 000000000..59c98a006 --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock.v @@ -0,0 +1,68 @@ +// 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 +`ifdef verilator + `define no_optimize(v) $c(v) +`else + `define no_optimize(v) (v) +`endif +// verilog_format: on + +module t ( /*AUTOARG*/); + logic clk = 0; + int imm_fails = 0, conc_fails = 0; + logic a = 1'b1; // antecedent always true + logic b = 1'b0; // consequent always false -> assertion always violates + + always #5 clk = ~clk; + + // Immediate assertion: assertion_type SIMPLE_IMMEDIATE (mask value 2). + always @(posedge clk) + ia : + assert (b) + else imm_fails = imm_fails + 1; + + // Concurrent assertion: assertion_type CONCURRENT (mask value 1). + ca : + assert property (@(posedge clk) a |-> b) + else conc_fails = conc_fails + 1; + + int ctl; + + // posedge clk at t = 5, 15, 25, 35, 45, 55, 65, 75, 85, 95. + initial begin + // Phase A (t=0..): everything on. edge@5 -> imm+1, conc+1. + #6; // t=6 + // Phase B: lock the on state, then $assertoff must be ignored while locked. + $assertcontrol(1); // Lock, all types + $assertoff; // ignored -> edges @15,@25 still fire both + #24; // t=30 + // Phase C: unlock, then turn off only SIMPLE_IMMEDIATE via assertion_type filter. + $assertcontrol(2); // Unlock + $assertcontrol(4, 2); // Off, assertion_type = SIMPLE_IMMEDIATE only + #20; // t=50: edges @35,@45 -> imm off, conc still on + // Phase D: non-constant control_type and assertion_type re-enable the immediate. + ctl = `no_optimize(3); // On + $assertcontrol(ctl, `no_optimize(2)); // non-const On, SIMPLE_IMMEDIATE + #20; // t=70: edges @55,@65 -> both on + // Phase E: off everything, then a non-const action-control code (no runtime effect). + $assertcontrol(4); // Off all + ctl = `no_optimize(7); // Pass_Off (IEEE 1800-2023 Table 20-5 action control) + $assertcontrol(ctl); // non-const action-control: evaluated at runtime, ignored + #30; // t=100: edges @75,@85,@95 -> none + $finish; + end + + final begin + // Concrete counts cross-checked against Questa 2022.3: imm_fails=5 conc_fails=7. + if (imm_fails != 5 || conc_fails != 7) begin + $display("%%Error: imm_fails=%0d (exp 5) conc_fails=%0d (exp 7)", imm_fails, conc_fails); + $stop; + end + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py b/test_regress/t/t_assert_ctl_lock_noinl.py similarity index 67% rename from test_regress/t/t_gate_inline_wide_noexclude_small_wide.py rename to test_regress/t/t_assert_ctl_lock_noinl.py index c20d324db..83440d018 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py +++ b/test_regress/t/t_assert_ctl_lock_noinl.py @@ -4,15 +4,16 @@ # This program is free software; you can redistribute it and/or modify it # under the terms of either the GNU Lesser General Public License Version 3 # or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap test.scenarios('vlt') +test.top_filename = "t_assert_ctl_lock.v" -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) +test.compile(verilator_flags2=["--binary --assert --fno-inline"]) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) +test.execute() test.passes() diff --git a/test_regress/t/t_assert_ctl_pass_actions.py b/test_regress/t/t_assert_ctl_pass_actions.py new file mode 100755 index 000000000..fdd1c0d4d --- /dev/null +++ b/test_regress/t/t_assert_ctl_pass_actions.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary --assert"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_ctl_pass_actions.v b/test_regress/t/t_assert_ctl_pass_actions.v new file mode 100644 index 000000000..3768f4bd1 --- /dev/null +++ b/test_regress/t/t_assert_ctl_pass_actions.v @@ -0,0 +1,214 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", \ + `__FILE__, `__LINE__, (gotv), (expv), `"gotv`", `"expv`"); \ + `stop; \ +end while (0) +// verilog_format: on + +`ifdef VERILATOR +// The '$c(1)' is there to prevent inlining of the signal by V3Gate. +`define IMPURE_ONE ($c(1)) +`else +// Use standard $random. The chance of getting 2 consecutive zeroes is negligible. +`define IMPURE_ONE (|($random | $random)) +`endif + +interface AssertCtlIface; + bit run_initial = 0; + bit initial_done = 0; + int fails = 0; + + function void clear(); + fails = 0; + endfunction + function void assert_off(); + $assertcontrol(4, 2, 1); + endfunction + function void assert_on(); + $assertcontrol(3, 2, 1); + endfunction + function void fail_check(); + assert (0) `stop; else fails++; + endfunction + function void run_checks(); + assert_off(); + fail_check(); + assert_on(); + fail_check(); + endfunction + + initial begin + wait (run_initial); + run_checks(); + initial_done = 1; + end +endinterface + +module t; + bit clk = 0; + int imm_passes = 0; + int imm_fails = 0; + int vacuous_passes = 0; + int nonvacuous_passes = 0; + int concurrent_fails = 0; + int class_fails = 0; + + class AssertCtlClass; + function void assert_off(); + $assertcontrol(4, 2, 1); + endfunction + function void assert_on(); + $assertcontrol(3, 2, 1); + endfunction + function void fail_check(); + assert (0) `stop; else class_fails++; + endfunction + endclass + + AssertCtlClass assert_ctl_class; + AssertCtlIface assert_ctl_iface(); + virtual AssertCtlIface v_assert_ctl_iface = assert_ctl_iface; + + always #5 clk = !clk; + + default clocking @(posedge clk); + endclocking + + assert property (@(posedge clk) 1'b0 |-> ##1 1'b1) begin + vacuous_passes++; + end else + `stop; + + assert property (@(posedge clk) 1'b1 |-> ##1 1'b1) begin + nonvacuous_passes++; + end else + `stop; + + assert property (@(posedge clk) 1'b1 |-> ##1 1'b0) begin + end else + concurrent_fails++; + + task automatic tick_and_check(input int exp_vacuous, input int exp_nonvacuous, + input int exp_concurrent_fails); + @(posedge clk); + #2; + `checkd(vacuous_passes, exp_vacuous); + `checkd(nonvacuous_passes, exp_nonvacuous); + `checkd(concurrent_fails, exp_concurrent_fails); + endtask + + initial begin + assert_ctl_class = new; + + $assertcontrol(4, 16, 1); + $assertcontrol(5, 16, 1); + $assertcontrol(3*`IMPURE_ONE, 2*`IMPURE_ONE); + + $assertcontrol(3, 255, 7); + $assertcontrol(6, 255, 7); + $assertcontrol(8, 255, 7); + + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 1); + + $assertcontrol(1, 2, 1); + $assertcontrol(4, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 2); + + $assertcontrol(2, 2, 1); + $assertcontrol(4, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 2); + + $assertcontrol(3, 2, 1); + + $assertcontrol(1, 2, 1); + $assertcontrol(7, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 3); + + $assertcontrol(2, 2, 1); + $assertcontrol(7, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 3); + + $assertcontrol(10, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 4); + + $assertcontrol(11, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 5); + + $assertcontrol(6, 2, 1); + + assert (0) `stop; else imm_fails++; + `checkd(imm_fails, 1); + + $assertcontrol(1, 2, 1); + $assertcontrol(9, 2, 1); + assert (0) `stop; else imm_fails++; + `checkd(imm_fails, 2); + + $assertcontrol(2, 2, 1); + $assertcontrol(9, 2, 1); + assert (0) `stop; else imm_fails++; + `checkd(imm_fails, 2); + + $assertcontrol(8, 2, 1); + assert (0) `stop; else imm_fails++; + `checkd(imm_fails, 3); + + assert_ctl_class.assert_off(); + assert_ctl_class.fail_check(); + `checkd(class_fails, 0); + + assert_ctl_class.assert_on(); + assert_ctl_class.fail_check(); + `checkd(class_fails, 1); + + assert_ctl_iface.run_initial = 1; + wait (assert_ctl_iface.initial_done); + `checkd(assert_ctl_iface.fails, 1); + + assert_ctl_iface.clear(); + assert_ctl_iface.run_checks(); + `checkd(assert_ctl_iface.fails, 1); + + assert_ctl_iface.clear(); + v_assert_ctl_iface.run_checks(); + `checkd(assert_ctl_iface.fails, 1); + + $assertcontrol(9, 1, 1); + + tick_and_check(1, 0, 0); + tick_and_check(2, 1, 0); + + $assertcontrol(11, 1, 1); + tick_and_check(2, 2, 0); + + $assertcontrol(7, 1, 1); + tick_and_check(2, 2, 0); + + $assertcontrol(10, 1, 1); + tick_and_check(2, 3, 0); + + $assertcontrol(6, 1, 1); + tick_and_check(3, 4, 0); + + $assertcontrol(8, 1, 1); + tick_and_check(4, 5, 1); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_assert_ctl_type_runtime_bad.out b/test_regress/t/t_assert_ctl_type_runtime_bad.out new file mode 100644 index 000000000..aaa9ff404 --- /dev/null +++ b/test_regress/t/t_assert_ctl_type_runtime_bad.out @@ -0,0 +1 @@ +%Warning: Bad $assertcontrol control_type '100' (IEEE 1800-2023 Table 20-5) diff --git a/test_regress/t/t_assert_ctl_type_runtime_bad.py b/test_regress/t/t_assert_ctl_type_runtime_bad.py new file mode 100755 index 000000000..84d6702d8 --- /dev/null +++ b/test_regress/t/t_assert_ctl_type_runtime_bad.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary', '--assert']) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_assert_ctl_type_runtime_bad.v b/test_regress/t/t_assert_ctl_type_runtime_bad.v new file mode 100644 index 000000000..d35421daf --- /dev/null +++ b/test_regress/t/t_assert_ctl_type_runtime_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +`ifdef VERILATOR +// The '$c1(1)' is there to prevent inlining of the signal by V3Gate +`define IMPURE_ONE ($c(1)) +`else +// Use standard $random (chaces of getting 2 consecutive zeroes is zero). +`define IMPURE_ONE (|($random | $random)) +`endif + +module t; + initial begin + $assertcontrol(100*`IMPURE_ONE); + $finish; + end +endmodule diff --git a/test_regress/t/t_assert_ctl_unsup.out b/test_regress/t/t_assert_ctl_unsup.out index 26e32ff0d..d0aaa3d7e 100644 --- a/test_regress/t/t_assert_ctl_unsup.out +++ b/test_regress/t/t_assert_ctl_unsup.out @@ -1,123 +1,74 @@ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:25:5: Unsupported: non-constant assert assertion-type expression - : ... note: In instance 't.unsupported_ctl_type' - 25 | $assertcontrol(Lock, a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:23:5: Unsupported: $assertcontrol control_type '6' + 23 | $assertcontrol(PassOn); | ^~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:27:5: Unsupported: $assertcontrol control_type '2' - 27 | $assertcontrol(Unlock); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:29:5: Unsupported: $assertcontrol control_type '6' - 29 | $assertcontrol(PassOn); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 30 | $assertpasson; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:24:5: Unsupported: $assertcontrol control_type '6' + 24 | $assertpasson; | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 31 | $assertpasson(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:25:5: Unsupported: $assertcontrol control_type '6' + 25 | $assertpasson(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:32:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 32 | $assertpasson(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:26:5: Unsupported: $assertcontrol control_type '6' + 26 | $assertpasson(a, t); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:34:5: Unsupported: $assertcontrol control_type '7' - 34 | $assertcontrol(PassOff); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:28:5: Unsupported: $assertcontrol control_type '7' + 28 | $assertcontrol(PassOff); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 35 | $assertpassoff; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:29:5: Unsupported: $assertcontrol control_type '7' + 29 | $assertpassoff; | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 36 | $assertpassoff(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: $assertcontrol control_type '7' + 30 | $assertpassoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:37:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 37 | $assertpassoff(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: $assertcontrol control_type '7' + 31 | $assertpassoff(a, t); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:39:5: Unsupported: $assertcontrol control_type '8' - 39 | $assertcontrol(FailOn); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:33:5: Unsupported: $assertcontrol control_type '8' + 33 | $assertcontrol(FailOn); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 40 | $assertfailon; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:34:5: Unsupported: $assertcontrol control_type '8' + 34 | $assertfailon; | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 41 | $assertfailon(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: $assertcontrol control_type '8' + 35 | $assertfailon(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:42:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 42 | $assertfailon(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: $assertcontrol control_type '8' + 36 | $assertfailon(a, t); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:44:5: Unsupported: $assertcontrol control_type '9' - 44 | $assertcontrol(FailOff); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:38:5: Unsupported: $assertcontrol control_type '9' + 38 | $assertcontrol(FailOff); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 45 | $assertfailoff; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:39:5: Unsupported: $assertcontrol control_type '9' + 39 | $assertfailoff; | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 46 | $assertfailoff(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: $assertcontrol control_type '9' + 40 | $assertfailoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:47:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 47 | $assertfailoff(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: $assertcontrol control_type '9' + 41 | $assertfailoff(a, t); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:49:5: Unsupported: $assertcontrol control_type '10' - 49 | $assertcontrol(NonvacuousOn); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:43:5: Unsupported: $assertcontrol control_type '10' + 43 | $assertcontrol(NonvacuousOn); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 50 | $assertnonvacuouson; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:44:5: Unsupported: $assertcontrol control_type '10' + 44 | $assertnonvacuouson; | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 51 | $assertnonvacuouson(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: $assertcontrol control_type '10' + 45 | $assertnonvacuouson(a); | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:52:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 52 | $assertnonvacuouson(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: $assertcontrol control_type '10' + 46 | $assertnonvacuouson(a, t); | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:54:5: Unsupported: $assertcontrol control_type '11' - 54 | $assertcontrol(VacuousOff); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:48:5: Unsupported: $assertcontrol control_type '11' + 48 | $assertcontrol(VacuousOff); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 55 | $assertvacuousoff; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:49:5: Unsupported: $assertcontrol control_type '11' + 49 | $assertvacuousoff; | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:56:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 56 | $assertvacuousoff(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: $assertcontrol control_type '11' + 50 | $assertvacuousoff(a); | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:57:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 57 | $assertvacuousoff(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: $assertcontrol control_type '11' + 51 | $assertvacuousoff(a, t); | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:64:5: Unsupported: non-const assert control type expression - : ... note: In instance 't.unsupported_ctl_type_expr' - 64 | $assertcontrol(ctl_type); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:93:7: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_class' - 93 | $asserton; - | ^~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:99:7: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_class' - 99 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:172:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface' - 172 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:138:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface_class' - 138 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:145:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface_class' - 145 | $asserton; - | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_unsup.v b/test_regress/t/t_assert_ctl_unsup.v index 98afcad10..027b4e824 100644 --- a/test_regress/t/t_assert_ctl_unsup.v +++ b/test_regress/t/t_assert_ctl_unsup.v @@ -4,28 +4,22 @@ // SPDX-FileCopyrightText: 2024 Antmicro // SPDX-License-Identifier: CC0-1.0 -module t(input logic clk); - unsupported_ctl_type unsupported_ctl_type(clk ? 1 : 2); - unsupported_ctl_type_expr unsupported_ctl_type_expr(); - assert_class assert_class(); - assert_iface assert_iface(); - assert_iface_class assert_iface_class(); +module t ( + input logic clk +); + unsupported_ctl_type unsupported_ctl_type (clk ? 1 : 2); endmodule -module unsupported_ctl_type(input int a); +module unsupported_ctl_type ( + input int a +); initial begin - let Lock = 1; - let Unlock = 2; let PassOn = 6; let PassOff = 7; let FailOn = 8; let FailOff = 9; let NonvacuousOn = 10; let VacuousOff = 11; - $assertcontrol(Lock, a); - - $assertcontrol(Unlock); - $assertcontrol(PassOn); $assertpasson; $assertpasson(a); @@ -57,129 +51,3 @@ module unsupported_ctl_type(input int a); $assertvacuousoff(a, t); end endmodule - -module unsupported_ctl_type_expr; - int ctl_type = 1; - initial begin - $assertcontrol(ctl_type); - end -endmodule - -module assert_class; - virtual class AssertCtl; - pure virtual function void virtual_assert_ctl(); - endclass - - class AssertCls; - static function void static_function(); - assert(0); - endfunction - static task static_task(); - assert(0); - endtask - function void assert_function(); - assert(0); - endfunction - task assert_task(); - assert(0); - endtask - virtual function void virtual_assert(); - assert(0); - endfunction - endclass - - class AssertOn extends AssertCtl; - virtual function void virtual_assert_ctl(); - $asserton; - endfunction - endclass - - class AssertOff extends AssertCtl; - virtual function void virtual_assert_ctl(); - $assertoff; - endfunction - endclass - - AssertCls assertCls; - AssertOn assertOn; - AssertOff assertOff; - initial begin - $assertoff; - AssertCls::static_function(); - AssertCls::static_task(); - $asserton; - AssertCls::static_function(); - AssertCls::static_task(); - - assertCls = new; - assertOn = new; - assertOff = new; - - assertOff.virtual_assert_ctl(); - assertCls.assert_function(); - assertCls.assert_task(); - assertCls.virtual_assert(); - - assertOn.virtual_assert_ctl(); - assertCls.assert_function(); - assertCls.assert_task(); - assertCls.virtual_assert(); - assertOff.virtual_assert_ctl(); - assertCls.assert_function(); - end -endmodule - -interface Iface; - function void assert_func(); - assert(0); - endfunction - - function void assertoff_func(); - $assertoff; - endfunction - - initial begin - assertoff_func(); - assert(0); - assert_func(); - $asserton; - assert(0); - assert_func(); - end -endinterface - -module assert_iface; - Iface iface(); - virtual Iface vIface = iface; - initial begin - vIface.assert_func(); - vIface.assertoff_func(); - vIface.assert_func(); - - iface.assert_func(); - iface.assertoff_func(); - iface.assert_func(); - end -endmodule - -interface class IfaceClass; - pure virtual function void assertoff_func(); - pure virtual function void assert_func(); -endclass - -class IfaceClassImpl implements IfaceClass; - virtual function void assertoff_func(); - $assertoff; - endfunction - virtual function void assert_func(); - assert(0); - endfunction -endclass - -module assert_iface_class; - IfaceClassImpl ifaceClassImpl = new; - initial begin - ifaceClassImpl.assertoff_func(); - ifaceClassImpl.assert_func(); - end -endmodule diff --git a/test_regress/t/t_assert_disabled.py b/test_regress/t/t_assert_disabled.py index 3a4d31c50..390d36ef3 100755 --- a/test_regress/t/t_assert_disabled.py +++ b/test_regress/t/t_assert_disabled.py @@ -10,9 +10,9 @@ import vltest_bootstrap test.scenarios('simulator') -test.top_filename = "t/t_assert_on.v" +test.top_filename = "t/t_assert_disabled.v" -test.compile(verilator_flags2=['--no-assert']) +test.compile(verilator_flags2=['--no-assert', '--timing']) test.execute() diff --git a/test_regress/t/t_assert_disabled.v b/test_regress/t/t_assert_disabled.v new file mode 100644 index 000000000..447253c68 --- /dev/null +++ b/test_regress/t/t_assert_disabled.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + integer action_hits = 0; + integer cyc = 0; + + assert property (@(posedge clk) ##1 1'b1) + action_hits++; + else + action_hits--; + + always @(posedge clk) begin + cyc++; + assert (0); + if (cyc == 4) begin + `checkd(action_hits, 0); + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_assert_iff_clk.py b/test_regress/t/t_assert_iff_clk.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_assert_iff_clk.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_iff_clk.v b/test_regress/t/t_assert_iff_clk.v new file mode 100644 index 000000000..73c8b82b5 --- /dev/null +++ b/test_regress/t/t_assert_iff_clk.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro Ltd +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + int cyc; + logic rst = 1'b1; + logic x = 1'b1; + int issue_fail; + int pre_fail; + int post_fail; + int pre_temporal_fail; + int post_temporal_fail; + + a_issue: assert property (disable iff(rst !== 1'b0) @(posedge clk) !x) + else issue_fail++; + + assert property (disable iff (cyc < 5) @(posedge clk) 0) + else pre_fail++; + + assert property (@(posedge clk) disable iff (cyc < 5) 0) + else post_fail++; + + assert property (disable iff (cyc < 5) @(posedge clk) 1 ##1 0) + else pre_temporal_fail++; + + assert property (@(posedge clk) disable iff (cyc < 5) 1 ##1 0) + else post_temporal_fail++; + + always @(negedge clk) begin + cyc <= cyc + 1; + rst <= cyc < 4; + x <= cyc < 4; + if (cyc == 12) begin + if (issue_fail != 0) $stop; + if (pre_fail != post_fail) $stop; + if (pre_temporal_fail != post_temporal_fail) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_assert_iff_clk_unsup.out b/test_regress/t/t_assert_iff_clk_unsup.out deleted file mode 100644 index 3ff98273e..000000000 --- a/test_regress/t/t_assert_iff_clk_unsup.out +++ /dev/null @@ -1,6 +0,0 @@ -%Error-UNSUPPORTED: t/t_assert_iff_clk_unsup.v:20:20: Unsupported: property '(disable iff (...) @ (...)' - : ... Suggest use property '(@(...) disable iff (...))' - 20 | assert property (disable iff (cyc < 5) @(posedge clk) cyc >= 5); - | ^~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_assert_iff_clk_unsup.v b/test_regress/t/t_assert_iff_clk_unsup.v deleted file mode 100644 index efe917a04..000000000 --- a/test_regress/t/t_assert_iff_clk_unsup.v +++ /dev/null @@ -1,22 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2022 Antmicro Ltd -// SPDX-License-Identifier: CC0-1.0 - -module t ( - input clk -); - - input clk; - int cyc = 0; - logic val = 0; - - always @(posedge clk) begin - cyc <= cyc + 1; - val = ~val; - end - - assert property (disable iff (cyc < 5) @(posedge clk) cyc >= 5); - -endmodule diff --git a/test_regress/t/t_assert_opt_check.py b/test_regress/t/t_assert_opt_check.py index b7652f1ab..62b5a95d0 100755 --- a/test_regress/t/t_assert_opt_check.py +++ b/test_regress/t/t_assert_opt_check.py @@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--binary', '--stats']) test.execute(check_finished=True) -test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 2) -test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 11) +test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 3) +test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 16) test.passes() diff --git a/test_regress/t/t_assert_opt_check.v b/test_regress/t/t_assert_opt_check.v index ccfea327e..7d0f54537 100644 --- a/test_regress/t/t_assert_opt_check.v +++ b/test_regress/t/t_assert_opt_check.v @@ -58,4 +58,12 @@ module t; end end + // Should combine the 2 nested assertOn checks after hoisting + always @(posedge clk) begin + if (assertEnable) begin + // This is an 'assert' with another 'assert' in the fail branch + assert(cntB - 100 == cntA); else assert(cntB == cntA + 100); + end + end + endmodule diff --git a/test_regress/t/t_assert_seq_clocking.py b/test_regress/t/t_assert_seq_clocking.py new file mode 100755 index 000000000..23f04b54c --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_seq_clocking.v b/test_regress/t/t_assert_seq_clocking.v new file mode 100644 index 000000000..b835acb85 --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking.v @@ -0,0 +1,53 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + int unsigned crc = 32'h1; + bit a, b; + int cyc = 0; + int fails_single = 0; + int fails_multi = 0; + + // verilog_format: off // verible does not support clocking events inside sequence declarations + sequence s_single; + @(posedge clk) a + endsequence + + sequence s_multi; + @(posedge clk) (a ##1 b); + endsequence + + sequence s_unused; + @(posedge clk) b; + endsequence + // verilog_format: on + + ap_single: assert property (s_single) else fails_single = fails_single + 1; + ap_multi: assert property (s_multi) else fails_multi = fails_multi + 1; + + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[30:0], crc[31] ^ crc[21] ^ crc[1] ^ crc[0]}; + a <= crc[0]; + b <= crc[1]; + if (cyc == 40) $finish; + end + + // Counts read in final (Postponed) to avoid same-timestep races. + // Concrete Verilator counts; Questa: fails_single=17 fails_multi=17 + final begin + `checkd(fails_single, 17); + `checkd(fails_multi, 17); + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_assert_seq_clocking_unsup.out b/test_regress/t/t_assert_seq_clocking_unsup.out new file mode 100644 index 000000000..5ab9bb741 --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking_unsup.out @@ -0,0 +1,30 @@ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:15:5: Unsupported: multiclocked sequence or property + : ... note: In instance 't' + 15 | @(posedge clk) a; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:19:5: Unsupported: clocking event inside sequence expression + : ... note: In instance 't' + 19 | @(posedge clk) b; + | ^ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:23:5: Unsupported: non-edge clocking event on a sequence; use an edge such as @(posedge clk) + : ... note: In instance 't' + 23 | @clk a; + | ^ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:27:5: Unsupported: non-edge clocking event on a sequence; use an edge such as @(posedge clk) + : ... note: In instance 't' + 27 | @clk a + | ^ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:32:3: Unsupported: Unclocked assertion + : ... note: In instance 't' + 32 | assert property (s_nest ##1 a); + | ^~~~~~ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:33:3: Unsupported: Unclocked assertion + : ... note: In instance 't' + 33 | assert property (s_level); + | ^~~~~~ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:34:3: Unsupported: Unclocked assertion + : ... note: In instance 't' + 34 | assert property (s_level2); + | ^~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_initial_dlyass_bad.py b/test_regress/t/t_assert_seq_clocking_unsup.py similarity index 84% rename from test_regress/t/t_initial_dlyass_bad.py rename to test_regress/t/t_assert_seq_clocking_unsup.py index f8feb4a77..38cf36b43 100755 --- a/test_regress/t/t_initial_dlyass_bad.py +++ b/test_regress/t/t_assert_seq_clocking_unsup.py @@ -4,13 +4,12 @@ # This program is free software; you can redistribute it and/or modify it # under the terms of either the GNU Lesser General Public License Version 3 # or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap test.scenarios('linter') -test.top_filename = "t/t_initial_dlyass.v" test.lint(fails=True, expect_filename=test.golden_filename) diff --git a/test_regress/t/t_assert_seq_clocking_unsup.v b/test_regress/t/t_assert_seq_clocking_unsup.v new file mode 100644 index 000000000..785c67000 --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking_unsup.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk, + input clk2 +); + logic a, b; + + // verilog_format: off + sequence s_multi; + @(posedge clk) a; + endsequence + + sequence s_nest; + @(posedge clk) b; + endsequence + + sequence s_level; + @clk a; + endsequence + + sequence s_level2; + @clk a + endsequence + // verilog_format: on + + assert property (@(posedge clk2) s_multi); + assert property (s_nest ##1 a); + assert property (s_level); + assert property (s_level2); + +endmodule diff --git a/test_regress/t/t_assertcontrol.py b/test_regress/t/t_assertcontrol.py new file mode 100755 index 000000000..fdd1c0d4d --- /dev/null +++ b/test_regress/t/t_assertcontrol.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary --assert"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assertcontrol.v b/test_regress/t/t_assertcontrol.v new file mode 100644 index 000000000..26f9d360b --- /dev/null +++ b/test_regress/t/t_assertcontrol.v @@ -0,0 +1,113 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", \ + `__FILE__, `__LINE__, (gotv), (expv), `"gotv`", `"expv`"); \ + `stop; \ +end while (0) +// verilog_format: on + +module t; + let LOCK = 1; + let UNLOCK = 2; + let ON = 3; + let OFF = 4; + let PASS_ON = 6; + let PASS_OFF = 7; + let FAIL_ON = 8; + let FAIL_OFF = 9; + + let SIMPLE_IMMEDIATE = 2; + let ASSERT = 1; + + int pass_count = 0; + int fail_count = 0; + int ctl_type = 0; + + task automatic run_pass(); + assert (1) begin + pass_count++; + end else + `stop; + endtask + + task automatic run_fail(); + assert (0) + `stop; + else begin + fail_count++; + end + endtask + + initial begin + $assertcontrol(ON, 255, 7); + $assertcontrol(PASS_ON, 255, 7); + $assertcontrol(FAIL_ON, 255, 7); + + run_pass(); + `checkd(pass_count, 1); + + // Lock preserves the enabled checking state against OFF. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + ctl_type = OFF; + $assertcontrol(ctl_type, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 2); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(OFF, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 2); + + // Lock preserves the disabled checking state against ON. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(ON, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 2); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(ON, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 3); + + // Lock also preserves pass-action state. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(PASS_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 4); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(PASS_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 4); + + $assertcontrol(PASS_ON, SIMPLE_IMMEDIATE, ASSERT); + + run_fail(); + `checkd(fail_count, 1); + + // Lock also preserves fail-action state. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(FAIL_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_fail(); + `checkd(fail_count, 2); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(FAIL_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_fail(); + `checkd(fail_count, 2); + + $assertcontrol(FAIL_ON, SIMPLE_IMMEDIATE, ASSERT); + run_fail(); + `checkd(fail_count, 3); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_assertcontrol_noinl.py b/test_regress/t/t_assertcontrol_noinl.py new file mode 100755 index 000000000..cbc5575bf --- /dev/null +++ b/test_regress/t/t_assertcontrol_noinl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t_assertcontrol.v" + +test.compile(verilator_flags2=["--binary --assert --fno-inline"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_ast_dump_patterns.out b/test_regress/t/t_ast_dump_patterns.out new file mode 100644 index 000000000..4d2ea4bb2 --- /dev/null +++ b/test_regress/t/t_ast_dump_patterns.out @@ -0,0 +1,251 @@ +AST patterns with depth 1 + 126 (CONST #A):a/a + 54 (VARREF):a/b + 36 (CCAST (VARREF):a/b):a/b + 34 (AND (CONST #A):a/a _:a/1):a/1 + 29 (VARREF):a/a + 23 (CONST ZERO):a/a + 21 (VARREF):(w64)u[1:0] + 20 (CCAST _:a/1):a/1 + 18 (AND (CONST #A):a/a _:a/a):a/a + 18 (SHIFTR _:a/b (CONST #A):a/a):a/1 + 17 (VARREF):a/1 + 15 (SHIFTL _:a/1 (CONST #A):a/a):a/a + 14 (NEGATE _:a/1):a/a + 12 (AND (CONST #A):a/a _:a/b):a/b + 12 (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b + 12 (NOT _:a/b):a/b + 12 (VARREF):(w64)u[0:0] + 11 (OR _:a/a _:a/b):a/c + 9 (OR _:a/a _:a/1):a/b + 9 (VARREF):(G/str) + 8 (CCAST _:a/a):a/a + 8 (CCAST _:a/a):b/b + 8 (CRESET):a/a + 8 (NOT _:a/a):a/a + 8 (REDXOR _:a/b):a/1 + 7 (SHIFTL _:a/b (CONST #A):a/a):a/a + 6 (AND _:a/b _:a/b):a/b + 6 (REDXOR _:a/a):b/1 + 5 (CCAST _:a/a):b/1 + 5 (OR _:a/a _:a/a):a/a + 4 (ADD _:a/a (VARREF):a/a):a/a + 4 (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):a/a):b/b + 4 (CCAST (CONST #A):a/a):a/a + 4 (CCAST (VARREF):a/1):a/1 + 4 (CCAST _:a/1):b/1 + 4 (CONST #A):(G/str) + 4 (CONST #A):a/1 + 4 (NEGATE _:a/1):a/b + 4 (SHIFTL _:a/a (CONST #A):b/b):a/a + 3 (AND (VARREF):a/a (CONST #A):b/b):a/a + 3 (CONST ZERO):a/1 + 3 (CRESET):(w64)u[0:0] + 3 (CRESET):(w64)u[1:0] + 3 (NOT _:a/1):a/1 + 3 (OR (CONST #A):a/a _:a/a):a/a + 2 (CCALL [(VARREF):(w64)u[0:0], (VARREF):(w64)u[0:0]]):a/a + 2 (CCALL [(VARREF):(w64)u[0:0]]):a/1 + 2 (CCALL [(VARREF):(w64)u[1:0], (VARREF):(w64)u[1:0]]):a/a + 2 (CCALL [(VARREF):(w64)u[1:0]]):a/1 + 2 (CCALL []):a/1 + 2 (CRESET):(G/str) + 2 (GT (CONST #A):a/a (VARREF):a/a):a/1 + 2 (LT (CONST #A):a/a (VARREF):a/a):a/1 + 2 (NEQ _:a/b _:a/b):a/1 + 2 (OR _:a/a _:a/a):a/b + 2 (REDXOR (VARREF):a/b):a/1 + 2 (REDXOR _:a/a):a/1 + 2 (REDXOR _:a/b):c/1 + 2 (SHIFTR _:a/a (CONST #A):b/b):a/a + 1 (ARRAYSEL (VARREF):(w64)u[0:0] (VARREF):a/a):b/b + 1 (ARRAYSEL (VARREF):(w64)u[1:0] (CONST #A):a/a):b/b + 1 (ARRAYSEL (VARREF):(w64)u[1:0] (VARREF):a/a):b/b + 1 (CCAST _:a/1):b/b + 1 (CCAST _:a/b):a/b + 1 (CCAST _:a/b):c/c + 1 (CRESET):1/1 + 1 (NEQ _:a/b _:a/b):a/c + 1 (VARREF):1/1 + +AST patterns with depth 2 + 18 (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1 + 18 (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1 + 18 (SHIFTR (CCAST (VARREF):a/b):a/b (CONST #A):a/a):a/1 + 14 (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a + 12 (NOT (CCAST (VARREF):a/b):a/b):a/b + 10 (NEGATE (CCAST _:a/1):a/1):a/a + 8 (CCAST (CCAST _:a/a):a/a):b/b + 8 (CCAST (NOT _:a/a):a/a):a/a + 8 (NOT (NEGATE _:a/1):a/a):a/a + 8 (OR (AND (CONST #A):a/a _:a/a):a/a (AND (CONST #B):a/a _:a/1):a/1):a/b + 8 (REDXOR (AND (CONST #A):a/a _:a/b):a/b):a/1 + 6 (AND (CONST #A):a/a (AND _:a/b _:a/b):a/b):a/b + 6 (AND (NOT _:a/b):a/b (NOT _:a/b):a/b):a/b + 6 (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c + 6 (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a + 5 (AND (CONST #A):a/a (CCAST _:b/b):a/1):a/1 + 4 (ADD (CCAST (CONST #A):a/a):a/a (VARREF):a/a):a/a + 4 (AND (CONST #A):a/a (NEGATE _:a/1):a/b):a/b + 4 (AND (CONST #A):a/a (REDXOR _:a/b):a/1):a/1 + 4 (AND (CONST #A):a/a (REDXOR _:b/b):a/1):a/1 + 4 (CCAST (CCAST _:a/1):a/1):b/1 + 4 (NEGATE (CCAST _:a/1):a/1):a/b + 4 (NEGATE (CCAST _:a/1):b/1):b/b + 4 (REDXOR (NEGATE _:a/1):a/a):b/1 + 4 (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b + 4 (SHIFTL (REDXOR _:a/b):a/1 (CONST #A):a/a):a/a + 3 (AND (CONST #A):a/a (NOT _:a/1):a/1):a/1 + 3 (OR (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):b/b):a/a):a/a + 2 (AND (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):b/b):a/a):a/a + 2 (AND (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):b/b):a/a):a/a + 2 (AND (CONST #A):a/a (OR _:a/a _:a/a):a/b):a/b + 2 (CCAST (SHIFTR _:a/a (CONST #A):b/b):a/a):b/1 + 2 (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1 + 2 (NOT (CCAST _:a/1):a/1):a/1 + 2 (OR (OR _:a/a _:a/a):a/a (OR _:a/a _:a/b):a/c):a/d + 2 (OR (SHIFTL _:a/a (CONST #A):b/b):a/a (CCAST _:b/b):a/a):a/a + 2 (OR (SHIFTL _:a/a (CONST #A):b/b):a/a (CCAST _:b/b):a/a):a/c + 2 (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/c):a/d + 2 (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (SHIFTL _:a/b (CONST #B):a/a):a/a):a/a + 2 (REDXOR (AND (CONST #A):a/a _:a/b):a/b):c/1 + 2 (REDXOR (NEGATE _:a/1):a/a):a/1 + 2 (REDXOR (OR _:a/a _:a/a):a/a):b/1 + 2 (SHIFTL (CCAST (VARREF):a/1):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR (VARREF):a/b):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR _:a/a):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR _:a/a):b/1 (CONST #A):b/b):b/b + 2 (SHIFTL (REDXOR _:a/b):c/1 (CONST #A):c/c):c/c + 2 (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #A):a/a):b/b + 1 (CCAST (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):a/a):b/b):a/1 + 1 (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST #A):a/a):b/b):a/1 + 1 (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b):a/1 + 1 (CCAST (CCALL [(VARREF):(w64)u[0:0]]):a/1):a/1 + 1 (CCAST (CCALL [(VARREF):(w64)u[1:0]]):a/1):a/1 + 1 (CCAST (CCAST (VARREF):a/1):a/1):b/b + 1 (CCAST (CCAST _:a/b):a/b):c/c + 1 (CCAST (OR _:a/a _:a/b):a/c):a/c + 1 (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/c + 1 (NOT (CCAST (VARREF):a/1):a/1):a/1 + 1 (OR (AND (CONST #A):a/a _:a/a):a/a (CCAST _:b/1):a/a):a/a + 1 (OR (SHIFTL _:a/1 (CONST #A):a/a):a/a (NEQ _:a/b _:a/b):a/1):a/c + 1 (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/b):a/c + 1 (SHIFTL (NEQ _:a/b _:a/b):a/1 (CONST #A):a/a):a/a + 1 (SHIFTL (NEQ _:a/b _:a/b):a/c (CONST #A):a/a):a/a + +AST patterns with depth 3 + 18 (AND (CONST #A):a/a (SHIFTR (CCAST (VARREF):a/b):a/b (CONST #A):a/a):a/1):a/1 + 18 (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1 + 10 (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/a + 8 (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b + 8 (CCAST (NOT (NEGATE _:a/1):a/a):a/a):a/a + 8 (NOT (NEGATE (CCAST _:a/1):a/1):a/a):a/a + 6 (AND (CONST #A):a/a (AND (NOT _:a/b):a/b (NOT _:a/b):a/b):a/b):a/b + 6 (AND (NOT (CCAST (VARREF):a/b):a/b):a/b (NOT (CCAST (VARREF):a/b):a/b):a/b):a/b + 6 (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a _:a/a):a/a (AND (CONST #C):a/a _:a/1):a/1):a/b):a/c + 6 (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #B):a/a):a/a + 4 (AND (CONST #A):a/a (NEGATE (CCAST _:a/1):a/1):a/b):a/b + 4 (AND (CONST #A):a/a (REDXOR (AND (CONST #B):a/a _:a/b):a/b):a/1):a/1 + 4 (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/b):a/1 (CONST #B):a/a):a/a):a/a + 4 (CCAST (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):b/1 + 4 (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/b + 4 (NEGATE (CCAST (CCAST _:a/1):a/1):b/1):b/b + 4 (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR _:a/b):a/1):a/1):a/c + 4 (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR _:b/b):a/1):a/1):a/c + 4 (REDXOR (AND (CONST #A):a/a (AND _:a/b _:a/b):a/b):a/b):a/1 + 4 (REDXOR (AND (CONST #A):a/a (NEGATE _:a/1):a/b):a/b):a/1 + 4 (REDXOR (NEGATE (CCAST _:a/1):b/1):b/b):a/1 + 4 (SHIFTL (CCAST (CCAST _:a/a):a/a):b/b (CONST #A):a/a):b/b + 4 (SHIFTL (REDXOR (AND (CONST #A):a/a _:a/b):a/b):a/1 (CONST #B):a/a):a/a + 2 (AND (CONST #A):a/a (NOT (CCAST _:a/1):a/1):a/1):a/1 + 2 (AND (CONST #A):a/a (OR (SHIFTL _:a/a (CONST #B):b/b):a/a (CCAST _:b/b):a/a):a/c):a/c + 2 (AND (CONST #A):a/a (REDXOR (NEGATE _:b/1):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (OR _:b/b _:b/b):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (SHIFTL (CCAST (VARREF):a/1):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (VARREF):a/b):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/a):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/b):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/c):a/1 (CONST #B):a/a):a/a):a/a + 2 (CCAST (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #A):a/a):b/b):a/1 + 2 (OR (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (SHIFTL _:a/b (CONST #B):a/a):a/a):a/a (OR (SHIFTL _:a/b (CONST #C):a/a):a/a (OR _:a/a _:a/1):a/c):a/d):a/e + 2 (OR (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST _:a/a):a/a):b/b):b/b + 2 (OR (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST _:a/a):a/a):b/b):b/c + 2 (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a (OR (AND (CONST #A):a/a _:a/a):a/a (AND (CONST #B):a/a _:a/1):a/1):a/b):a/d + 2 (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a (SHIFTL (OR _:a/a _:a/b):a/c (CONST #B):a/a):a/a):a/a + 2 (REDXOR (AND (CONST #A):a/a (OR _:a/a _:a/a):a/b):a/b):c/1 + 2 (REDXOR (NEGATE (CCAST _:a/1):a/1):a/a):a/1 + 2 (REDXOR (OR (SHIFTL _:a/a (CONST #A):b/b):a/a (CCAST _:b/b):a/a):a/a):b/1 + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a _:a/b):a/b):c/1 (CONST #B):c/c):c/c + 2 (SHIFTL (REDXOR (NEGATE _:a/1):a/a):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR (NEGATE _:a/1):a/a):b/1 (CONST #A):b/b):b/b + 1 (AND (CONST #A):a/a (CCAST (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST #A):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (SHIFTR _:b/b (CONST #A):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (SHIFTR _:b/b (CONST #B):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (NOT (CCAST (VARREF):a/1):a/1):a/1):a/1 + 1 (CCAST (CCAST (OR _:a/a _:a/b):a/c):a/c):d/d + 1 (CCAST (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/b):a/c):a/c + 1 (NOT (CCAST (CCALL [(VARREF):(w64)u[0:0]]):a/1):a/1):a/1 + 1 (NOT (CCAST (CCALL [(VARREF):(w64)u[1:0]]):a/1):a/1):a/1 + 1 (OR (AND (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):b/b):a/a):a/a (CCAST (CCAST (VARREF):b/1):b/1):a/a):a/a + 1 (OR (SHIFTL (NEQ _:a/b _:a/b):a/1 (CONST #A):a/a):a/a (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1):a/c + 1 (OR (SHIFTL (NEQ _:a/b _:a/b):a/c (CONST #A):a/a):a/a (OR (SHIFTL _:a/1 (CONST #B):a/a):a/a (NEQ _:a/b _:a/b):a/1):a/c):a/b + 1 (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1 (CONST #A):a/a):a/a + 1 (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/c (CONST #A):a/a):a/a + +AST patterns with depth 4 + 18 (CCAST (AND (CONST #A):a/a (SHIFTR (CCAST (VARREF):a/b):a/b (CONST #A):a/a):a/1):a/1):a/1 + 10 (NEGATE (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1):a/a + 8 (CCAST (CCAST (NOT (NEGATE _:a/1):a/a):a/a):a/a):b/b + 8 (CCAST (NOT (NEGATE (CCAST _:a/1):a/1):a/a):a/a):a/a + 8 (NOT (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/a):a/a + 6 (AND (CONST #A):a/a (AND (NOT (CCAST (VARREF):a/b):a/b):a/b (NOT (CCAST (VARREF):a/b):a/b):a/b):a/b):a/b + 4 (AND (CONST #A):a/a (NEGATE (CCAST (AND (CONST #B):a/a _:a/1):a/1):a/1):a/b):a/b + 4 (AND (CONST #A):a/a (SHIFTL (REDXOR (AND (CONST #B):a/a _:a/b):a/b):a/1 (CONST #C):a/a):a/a):a/a + 4 (CCAST (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1):c/1 + 4 (NEGATE (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1):a/c + 4 (NEGATE (CCAST (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):b/1):b/b + 4 (REDXOR (AND (CONST #A):a/a (AND (NOT _:a/b):a/b (NOT _:a/b):a/b):a/b):a/b):a/1 + 4 (REDXOR (AND (CONST #A):a/a (NEGATE (CCAST _:a/1):a/1):a/b):a/b):a/1 + 4 (REDXOR (NEGATE (CCAST (CCAST _:a/1):a/1):b/1):b/b):a/1 + 4 (SHIFTL (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b (CONST #A):a/a):b/b + 4 (SHIFTL (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a _:a/a):a/a (AND (CONST #C):a/a _:a/1):a/1):a/b):a/c (CONST #D):a/a):a/a + 2 (AND (CONST #A):a/a (OR (SHIFTL (CCAST _:b/b):a/a (CONST #B):b/b):a/a (CCAST (CCAST _:b/b):b/b):a/a):a/c):a/c + 2 (AND (CONST #A):a/a (REDXOR (AND (CONST #B):a/a (AND _:a/b _:a/b):a/b):a/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (AND (CONST #B):a/a (NEGATE _:a/1):a/b):a/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (NEGATE (CCAST _:a/1):b/1):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (OR (SHIFTL _:b/b (CONST #B):a/a):b/b (CCAST _:a/a):b/b):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (AND (CONST #B):b/b _:b/c):b/c):a/1 (CONST #C):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (NEGATE _:a/1):a/a):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (NEGATE _:b/1):b/b):a/1 (CONST #B):a/a):a/a):a/a + 2 (OR (AND (CONST #A):a/a (SHIFTL (CCAST (VARREF):a/1):a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:a/b):a/1):a/1):a/c):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR (VARREF):a/b):a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:a/b):a/1):a/1):a/c):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/a):a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:b/b):a/1):a/1):a/c):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/b):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (AND (CONST #C):a/a _:a/b):a/b):a/1):a/1):a/c + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/b):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (AND (CONST #C):a/a _:a/c):a/c):a/1):a/1):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/b):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (OR _:b/b _:b/b):b/b):a/1):a/1):a/c + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/c):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (NEGATE _:b/1):b/b):a/1):a/1):a/d + 2 (OR (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a (SHIFTL (OR _:a/a _:a/b):a/c (CONST #B):a/a):a/a):a/a (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #C):a/a):a/a (OR (AND (CONST #C):a/a _:a/a):a/a (AND (CONST #D):a/a _:a/1):a/1):a/b):a/d):a/e + 2 (OR (SHIFTL (CCAST (CCAST _:a/a):a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b):b/b + 2 (OR (SHIFTL (CCAST (CCAST _:a/a):a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b):b/c + 2 (OR (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #B):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:d/d):a/1):a/1):a/b):a/e + 2 (OR (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #B):a/a):a/a (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #C):a/a):a/a):a/a + 2 (REDXOR (AND (CONST #A):a/a (OR (SHIFTL _:a/a (CONST #B):b/b):a/a (CCAST _:b/b):a/a):a/c):a/c):b/1 + 2 (REDXOR (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/a):a/1 + 2 (REDXOR (OR (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST _:a/a):a/a):b/b):b/b):a/1 + 2 (SHIFTL (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a _:a/a):a/a (AND (CONST #C):a/a _:a/1):a/1):a/b):a/c (CONST #B):a/a):a/a + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a (AND _:a/b _:a/b):a/b):a/b):a/1 (CONST #B):a/a):a/a + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a (NEGATE _:a/1):a/b):a/b):a/1 (CONST #B):a/a):a/a + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a (OR _:a/a _:a/a):a/b):a/b):c/1 (CONST #B):c/c):c/c + 2 (SHIFTL (REDXOR (NEGATE (CCAST _:a/1):a/1):a/a):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR (NEGATE (CCAST _:a/1):b/1):b/b):a/1 (CONST #A):a/a):a/a + 1 (AND (CONST #A):a/a (CCAST (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #A):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #B):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (NOT (CCAST (CCALL [(VARREF):(w64)u[0:0]]):a/1):a/1):a/1):a/1 + 1 (AND (CONST #A):a/a (NOT (CCAST (CCALL [(VARREF):(w64)u[1:0]]):a/1):a/1):a/1):a/1 + 1 (CCAST (CCAST (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/b):a/c):a/c):d/d + 1 (CCAST (OR (SHIFTL (NEQ _:a/b _:a/b):a/c (CONST #A):a/a):a/a (OR (SHIFTL _:a/1 (CONST #B):a/a):a/a (NEQ _:a/b _:a/b):a/1):a/c):a/b):a/b + 1 (OR (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1 (CONST #A):a/a):a/a (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1):a/c + 1 (OR (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/c (CONST #A):a/a):a/a (OR (SHIFTL (NEQ _:a/b _:a/b):a/1 (CONST #B):a/a):a/a (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1):a/c):a/b + diff --git a/test_regress/t/t_ast_dump_patterns.py b/test_regress/t/t_ast_dump_patterns.py new file mode 100755 index 000000000..d1bf14b33 --- /dev/null +++ b/test_regress/t/t_ast_dump_patterns.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--dump-ast-patterns", "--no-skip-identical"]) + +test.files_identical(test.obj_dir + "/" + test.vm_prefix + "__ast_patterns_emit.txt", + test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_ast_dump_patterns.v b/test_regress/t/t_ast_dump_patterns.v new file mode 100644 index 000000000..dc96474fb --- /dev/null +++ b/test_regress/t/t_ast_dump_patterns.v @@ -0,0 +1,26 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire [3:0] a, + input wire [3:0] b, + input wire [3:0] c, + output wire [10:0] o +); + wire [3:0] x = ~a & ~b; + wire [3:0] y = ~b & ~c; + wire [3:0] z = ~c & ~a; + wire [0:0] w1 = x[0]; + wire [7:0] w8 = {8{x[1]}}; + wire [15:0] w16 = {2{w8}}; + wire [31:0] w32 = {2{w16}}; + wire [63:0] w64a = {2{w32}}; + wire [63:0] w64b = {2{~w32}}; + wire [62:0] w63 = 63'({2{~w32}}); + wire [95:0] w96 = 96'(w64a); + + assign o = {^x, ^y, ^z, ^w1, ^w8, ^w16, ^w32, ^w64a, ^w64b, ^w63, ^w96}; +endmodule diff --git a/test_regress/t/t_case_call_count.py b/test_regress/t/t_case_call_count.py index 4116217e8..df91d0e3c 100755 --- a/test_regress/t/t_case_call_count.py +++ b/test_regress/t/t_case_call_count.py @@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3) test.passes() diff --git a/test_regress/t/t_case_decoder.py b/test_regress/t/t_case_decoder.py new file mode 100755 index 000000000..4265c138b --- /dev/null +++ b/test_regress/t/t_case_decoder.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases decoder\s+(\d+)', 17) + +test.passes() diff --git a/test_regress/t/t_case_decoder.v b/test_regress/t/t_case_decoder.v new file mode 100644 index 000000000..495c19320 --- /dev/null +++ b/test_regress/t/t_case_decoder.v @@ -0,0 +1,544 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// Case statements that become a "decoder" (the selector is matched against a packed constant +// table at runtime), followed by cases that must not be converted to one. Each output is +// compared against an equivalent reference computed without a case statement, so the reference +// itself is never converted. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Accept A: a 31-bit (I) selector decoded into outputs of three widths (I/Q/W result). + wire [30:0] accept_a_in = 31'b1 << cyc[3:0]; + logic [5:0] accept_a_out_0, accept_a_ref_0; + logic [55:0] accept_a_out_1, accept_a_ref_1; + logic [142:0] accept_a_out_2, accept_a_ref_2; + always_comb begin + casez (accept_a_in) + 31'b???????_????????_????????_???????1: accept_a_out_0 = 6'd00; + 31'b???????_????????_????????_??????1?: accept_a_out_0 = 6'd01; + 31'b???????_????????_????????_?????1??: accept_a_out_0 = 6'd02; + 31'b???????_????????_????????_????1???: accept_a_out_0 = 6'd03; + 31'b???????_????????_????????_???1????: accept_a_out_0 = 6'd04; + 31'b???????_????????_????????_??1?????: accept_a_out_0 = 6'd05; + 31'b???????_????????_????????_?1??????: accept_a_out_0 = 6'd06; + 31'b???????_????????_????????_1???????: accept_a_out_0 = 6'd07; + 31'b???????_????????_???????1_????????: accept_a_out_0 = 6'd08; + 31'b???????_????????_??????1?_????????: accept_a_out_0 = 6'd09; + 31'b???????_????????_?????1??_????????: accept_a_out_0 = 6'd10; + 31'b???????_????????_????1???_????????: accept_a_out_0 = 6'd11; + 31'b???????_????????_???1????_????????: accept_a_out_0 = 6'd12; + 31'b???????_????????_??1?????_????????: accept_a_out_0 = 6'd13; + 31'b???????_????????_?1??????_????????: accept_a_out_0 = 6'd14; + 31'b???????_????????_1???????_????????: accept_a_out_0 = 6'd15; + 31'b???????_???????1_????????_????????: accept_a_out_0 = 6'd16; + 31'b???????_??????1?_????????_????????: accept_a_out_0 = 6'd17; + 31'b???????_?????1??_????????_????????: accept_a_out_0 = 6'd18; + 31'b???????_????1???_????????_????????: accept_a_out_0 = 6'd19; + 31'b???????_???1????_????????_????????: accept_a_out_0 = 6'd20; + 31'b???????_??1?????_????????_????????: accept_a_out_0 = 6'd21; + 31'b???????_?1??????_????????_????????: accept_a_out_0 = 6'd22; + 31'b???????_1???????_????????_????????: accept_a_out_0 = 6'd23; + 31'b??????1_????????_????????_????????: accept_a_out_0 = 6'd24; + 31'b?????1?_????????_????????_????????: accept_a_out_0 = 6'd25; + 31'b????1??_????????_????????_????????: accept_a_out_0 = 6'd26; + 31'b???1???_????????_????????_????????: accept_a_out_0 = 6'd27; + 31'b??1????_????????_????????_????????: accept_a_out_0 = 6'd28; + 31'b?1?????_????????_????????_????????: accept_a_out_0 = 6'd29; + 31'b1??????_????????_????????_????????: accept_a_out_0 = 6'd30; + default: accept_a_out_0 = '1; + endcase + casez (accept_a_in) + 31'b???????_????????_????????_???????1: accept_a_out_1 = 56'd0000; + 31'b???????_????????_????????_??????1?: accept_a_out_1 = 56'd0100; + 31'b???????_????????_????????_?????1??: accept_a_out_1 = 56'd0200; + 31'b???????_????????_????????_????1???: accept_a_out_1 = 56'd0300; + 31'b???????_????????_????????_???1????: accept_a_out_1 = 56'd0400; + 31'b???????_????????_????????_??1?????: accept_a_out_1 = 56'd0500; + 31'b???????_????????_????????_?1??????: accept_a_out_1 = 56'd0600; + 31'b???????_????????_????????_1???????: accept_a_out_1 = 56'd0700; + 31'b???????_????????_???????1_????????: accept_a_out_1 = 56'd0800; + 31'b???????_????????_??????1?_????????: accept_a_out_1 = 56'd0900; + 31'b???????_????????_?????1??_????????: accept_a_out_1 = 56'd1000; + 31'b???????_????????_????1???_????????: accept_a_out_1 = 56'd1100; + 31'b???????_????????_???1????_????????: accept_a_out_1 = 56'd1200; + 31'b???????_????????_??1?????_????????: accept_a_out_1 = 56'd1300; + 31'b???????_????????_?1??????_????????: accept_a_out_1 = 56'd1400; + 31'b???????_????????_1???????_????????: accept_a_out_1 = 56'd1500; + 31'b???????_???????1_????????_????????: accept_a_out_1 = 56'd1600; + 31'b???????_??????1?_????????_????????: accept_a_out_1 = 56'd1700; + 31'b???????_?????1??_????????_????????: accept_a_out_1 = 56'd1800; + 31'b???????_????1???_????????_????????: accept_a_out_1 = 56'd1900; + 31'b???????_???1????_????????_????????: accept_a_out_1 = 56'd2000; + 31'b???????_??1?????_????????_????????: accept_a_out_1 = 56'd2100; + 31'b???????_?1??????_????????_????????: accept_a_out_1 = 56'd2200; + 31'b???????_1???????_????????_????????: accept_a_out_1 = 56'd2300; + 31'b??????1_????????_????????_????????: accept_a_out_1 = 56'd2400; + 31'b?????1?_????????_????????_????????: accept_a_out_1 = 56'd2500; + 31'b????1??_????????_????????_????????: accept_a_out_1 = 56'd2600; + 31'b???1???_????????_????????_????????: accept_a_out_1 = 56'd2700; + 31'b??1????_????????_????????_????????: accept_a_out_1 = 56'd2800; + 31'b?1?????_????????_????????_????????: accept_a_out_1 = 56'd2900; + 31'b1??????_????????_????????_????????: accept_a_out_1 = 56'd3000; + default: accept_a_out_1 = '1; + endcase + casez (accept_a_in) + 31'b???????_????????_????????_???????1: accept_a_out_2 = 143'd0000000000; + 31'b???????_????????_????????_??????1?: accept_a_out_2 = 143'd0100000000; + 31'b???????_????????_????????_?????1??: accept_a_out_2 = 143'd0200000000; + 31'b???????_????????_????????_????1???: accept_a_out_2 = 143'd0300000000; + 31'b???????_????????_????????_???1????: accept_a_out_2 = 143'd0400000000; + 31'b???????_????????_????????_??1?????: accept_a_out_2 = 143'd0500000000; + 31'b???????_????????_????????_?1??????: accept_a_out_2 = 143'd0600000000; + 31'b???????_????????_????????_1???????: accept_a_out_2 = 143'd0700000000; + 31'b???????_????????_???????1_????????: accept_a_out_2 = 143'd0800000000; + 31'b???????_????????_??????1?_????????: accept_a_out_2 = 143'd0900000000; + 31'b???????_????????_?????1??_????????: accept_a_out_2 = 143'd1000000000; + 31'b???????_????????_????1???_????????: accept_a_out_2 = 143'd1100000000; + 31'b???????_????????_???1????_????????: accept_a_out_2 = 143'd1200000000; + 31'b???????_????????_??1?????_????????: accept_a_out_2 = 143'd1300000000; + 31'b???????_????????_?1??????_????????: accept_a_out_2 = 143'd1400000000; + 31'b???????_????????_1???????_????????: accept_a_out_2 = 143'd1500000000; + 31'b???????_???????1_????????_????????: accept_a_out_2 = 143'd1600000000; + 31'b???????_??????1?_????????_????????: accept_a_out_2 = 143'd1700000000; + 31'b???????_?????1??_????????_????????: accept_a_out_2 = 143'd1800000000; + 31'b???????_????1???_????????_????????: accept_a_out_2 = 143'd1900000000; + 31'b???????_???1????_????????_????????: accept_a_out_2 = 143'd2000000000; + 31'b???????_??1?????_????????_????????: accept_a_out_2 = 143'd2100000000; + 31'b???????_?1??????_????????_????????: accept_a_out_2 = 143'd2200000000; + 31'b???????_1???????_????????_????????: accept_a_out_2 = 143'd2300000000; + 31'b??????1_????????_????????_????????: accept_a_out_2 = 143'd2400000000; + 31'b?????1?_????????_????????_????????: accept_a_out_2 = 143'd2500000000; + 31'b????1??_????????_????????_????????: accept_a_out_2 = 143'd2600000000; + 31'b???1???_????????_????????_????????: accept_a_out_2 = 143'd2700000000; + 31'b??1????_????????_????????_????????: accept_a_out_2 = 143'd2800000000; + 31'b?1?????_????????_????????_????????: accept_a_out_2 = 143'd2900000000; + 31'b1??????_????????_????????_????????: accept_a_out_2 = 143'd3000000000; + default: accept_a_out_2 = '1; + endcase + end + assign accept_a_ref_0 = 6'(cyc[3:0]); + assign accept_a_ref_1 = 56'(cyc[3:0]) * 56'd100; + assign accept_a_ref_2 = 143'(cyc[3:0]) * 143'd100000000; + + // Accept B: a 40-bit (Q) selector decoded into outputs of three widths (I/Q/W result). + wire [39:0] accept_b_in = 40'b1 << cyc[5:1]; + logic [5:0] accept_b_out_0, accept_b_ref_0; + logic [55:0] accept_b_out_1, accept_b_ref_1; + logic [142:0] accept_b_out_2, accept_b_ref_2; + always_comb begin + casez (accept_b_in) + 40'b????????_????????_????????_????????_???????1: accept_b_out_0 = 6'd00; + 40'b????????_????????_????????_????????_??????1?: accept_b_out_0 = 6'd01; + 40'b????????_????????_????????_????????_?????1??: accept_b_out_0 = 6'd02; + 40'b????????_????????_????????_????????_????1???: accept_b_out_0 = 6'd03; + 40'b????????_????????_????????_????????_???1????: accept_b_out_0 = 6'd04; + 40'b????????_????????_????????_????????_??1?????: accept_b_out_0 = 6'd05; + 40'b????????_????????_????????_????????_?1??????: accept_b_out_0 = 6'd06; + 40'b????????_????????_????????_????????_1???????: accept_b_out_0 = 6'd07; + 40'b????????_????????_????????_???????1_????????: accept_b_out_0 = 6'd08; + 40'b????????_????????_????????_??????1?_????????: accept_b_out_0 = 6'd09; + 40'b????????_????????_????????_?????1??_????????: accept_b_out_0 = 6'd10; + 40'b????????_????????_????????_????1???_????????: accept_b_out_0 = 6'd11; + 40'b????????_????????_????????_???1????_????????: accept_b_out_0 = 6'd12; + 40'b????????_????????_????????_??1?????_????????: accept_b_out_0 = 6'd13; + 40'b????????_????????_????????_?1??????_????????: accept_b_out_0 = 6'd14; + 40'b????????_????????_????????_1???????_????????: accept_b_out_0 = 6'd15; + 40'b????????_????????_???????1_????????_????????: accept_b_out_0 = 6'd16; + 40'b????????_????????_??????1?_????????_????????: accept_b_out_0 = 6'd17; + 40'b????????_????????_?????1??_????????_????????: accept_b_out_0 = 6'd18; + 40'b????????_????????_????1???_????????_????????: accept_b_out_0 = 6'd19; + 40'b????????_????????_???1????_????????_????????: accept_b_out_0 = 6'd20; + 40'b????????_????????_??1?????_????????_????????: accept_b_out_0 = 6'd21; + 40'b????????_????????_?1??????_????????_????????: accept_b_out_0 = 6'd22; + 40'b????????_????????_1???????_????????_????????: accept_b_out_0 = 6'd23; + 40'b????????_???????1_????????_????????_????????: accept_b_out_0 = 6'd24; + 40'b????????_??????1?_????????_????????_????????: accept_b_out_0 = 6'd25; + 40'b????????_?????1??_????????_????????_????????: accept_b_out_0 = 6'd26; + 40'b????????_????1???_????????_????????_????????: accept_b_out_0 = 6'd27; + 40'b????????_???1????_????????_????????_????????: accept_b_out_0 = 6'd28; + 40'b????????_??1?????_????????_????????_????????: accept_b_out_0 = 6'd29; + 40'b????????_?1??????_????????_????????_????????: accept_b_out_0 = 6'd30; + 40'b????????_1???????_????????_????????_????????: accept_b_out_0 = 6'd31; + 40'b???????1_????????_????????_????????_????????: accept_b_out_0 = 6'd32; + 40'b??????1?_????????_????????_????????_????????: accept_b_out_0 = 6'd33; + 40'b?????1??_????????_????????_????????_????????: accept_b_out_0 = 6'd34; + 40'b????1???_????????_????????_????????_????????: accept_b_out_0 = 6'd35; + 40'b???1????_????????_????????_????????_????????: accept_b_out_0 = 6'd36; + 40'b??1?????_????????_????????_????????_????????: accept_b_out_0 = 6'd37; + 40'b?1??????_????????_????????_????????_????????: accept_b_out_0 = 6'd38; + 40'b1???????_????????_????????_????????_????????: accept_b_out_0 = 6'd39; + default: accept_b_out_0 = '1; + endcase + casez (accept_b_in) + 40'b????????_????????_????????_????????_???????1: accept_b_out_1 = 56'd0000; + 40'b????????_????????_????????_????????_??????1?: accept_b_out_1 = 56'd0100; + 40'b????????_????????_????????_????????_?????1??: accept_b_out_1 = 56'd0200; + 40'b????????_????????_????????_????????_????1???: accept_b_out_1 = 56'd0300; + 40'b????????_????????_????????_????????_???1????: accept_b_out_1 = 56'd0400; + 40'b????????_????????_????????_????????_??1?????: accept_b_out_1 = 56'd0500; + 40'b????????_????????_????????_????????_?1??????: accept_b_out_1 = 56'd0600; + 40'b????????_????????_????????_????????_1???????: accept_b_out_1 = 56'd0700; + 40'b????????_????????_????????_???????1_????????: accept_b_out_1 = 56'd0800; + 40'b????????_????????_????????_??????1?_????????: accept_b_out_1 = 56'd0900; + 40'b????????_????????_????????_?????1??_????????: accept_b_out_1 = 56'd1000; + 40'b????????_????????_????????_????1???_????????: accept_b_out_1 = 56'd1100; + 40'b????????_????????_????????_???1????_????????: accept_b_out_1 = 56'd1200; + 40'b????????_????????_????????_??1?????_????????: accept_b_out_1 = 56'd1300; + 40'b????????_????????_????????_?1??????_????????: accept_b_out_1 = 56'd1400; + 40'b????????_????????_????????_1???????_????????: accept_b_out_1 = 56'd1500; + 40'b????????_????????_???????1_????????_????????: accept_b_out_1 = 56'd1600; + 40'b????????_????????_??????1?_????????_????????: accept_b_out_1 = 56'd1700; + 40'b????????_????????_?????1??_????????_????????: accept_b_out_1 = 56'd1800; + 40'b????????_????????_????1???_????????_????????: accept_b_out_1 = 56'd1900; + 40'b????????_????????_???1????_????????_????????: accept_b_out_1 = 56'd2000; + 40'b????????_????????_??1?????_????????_????????: accept_b_out_1 = 56'd2100; + 40'b????????_????????_?1??????_????????_????????: accept_b_out_1 = 56'd2200; + 40'b????????_????????_1???????_????????_????????: accept_b_out_1 = 56'd2300; + 40'b????????_???????1_????????_????????_????????: accept_b_out_1 = 56'd2400; + 40'b????????_??????1?_????????_????????_????????: accept_b_out_1 = 56'd2500; + 40'b????????_?????1??_????????_????????_????????: accept_b_out_1 = 56'd2600; + 40'b????????_????1???_????????_????????_????????: accept_b_out_1 = 56'd2700; + 40'b????????_???1????_????????_????????_????????: accept_b_out_1 = 56'd2800; + 40'b????????_??1?????_????????_????????_????????: accept_b_out_1 = 56'd2900; + 40'b????????_?1??????_????????_????????_????????: accept_b_out_1 = 56'd3000; + 40'b????????_1???????_????????_????????_????????: accept_b_out_1 = 56'd3100; + 40'b???????1_????????_????????_????????_????????: accept_b_out_1 = 56'd3200; + 40'b??????1?_????????_????????_????????_????????: accept_b_out_1 = 56'd3300; + 40'b?????1??_????????_????????_????????_????????: accept_b_out_1 = 56'd3400; + 40'b????1???_????????_????????_????????_????????: accept_b_out_1 = 56'd3500; + 40'b???1????_????????_????????_????????_????????: accept_b_out_1 = 56'd3600; + 40'b??1?????_????????_????????_????????_????????: accept_b_out_1 = 56'd3700; + 40'b?1??????_????????_????????_????????_????????: accept_b_out_1 = 56'd3800; + 40'b1???????_????????_????????_????????_????????: accept_b_out_1 = 56'd3900; + default: accept_b_out_1 = '1; + endcase + casez (accept_b_in) + 40'b????????_????????_????????_????????_???????1: accept_b_out_2 = 143'd0000000000; + 40'b????????_????????_????????_????????_??????1?: accept_b_out_2 = 143'd0100000000; + 40'b????????_????????_????????_????????_?????1??: accept_b_out_2 = 143'd0200000000; + 40'b????????_????????_????????_????????_????1???: accept_b_out_2 = 143'd0300000000; + 40'b????????_????????_????????_????????_???1????: accept_b_out_2 = 143'd0400000000; + 40'b????????_????????_????????_????????_??1?????: accept_b_out_2 = 143'd0500000000; + 40'b????????_????????_????????_????????_?1??????: accept_b_out_2 = 143'd0600000000; + 40'b????????_????????_????????_????????_1???????: accept_b_out_2 = 143'd0700000000; + 40'b????????_????????_????????_???????1_????????: accept_b_out_2 = 143'd0800000000; + 40'b????????_????????_????????_??????1?_????????: accept_b_out_2 = 143'd0900000000; + 40'b????????_????????_????????_?????1??_????????: accept_b_out_2 = 143'd1000000000; + 40'b????????_????????_????????_????1???_????????: accept_b_out_2 = 143'd1100000000; + 40'b????????_????????_????????_???1????_????????: accept_b_out_2 = 143'd1200000000; + 40'b????????_????????_????????_??1?????_????????: accept_b_out_2 = 143'd1300000000; + 40'b????????_????????_????????_?1??????_????????: accept_b_out_2 = 143'd1400000000; + 40'b????????_????????_????????_1???????_????????: accept_b_out_2 = 143'd1500000000; + 40'b????????_????????_???????1_????????_????????: accept_b_out_2 = 143'd1600000000; + 40'b????????_????????_??????1?_????????_????????: accept_b_out_2 = 143'd1700000000; + 40'b????????_????????_?????1??_????????_????????: accept_b_out_2 = 143'd1800000000; + 40'b????????_????????_????1???_????????_????????: accept_b_out_2 = 143'd1900000000; + 40'b????????_????????_???1????_????????_????????: accept_b_out_2 = 143'd2000000000; + 40'b????????_????????_??1?????_????????_????????: accept_b_out_2 = 143'd2100000000; + 40'b????????_????????_?1??????_????????_????????: accept_b_out_2 = 143'd2200000000; + 40'b????????_????????_1???????_????????_????????: accept_b_out_2 = 143'd2300000000; + 40'b????????_???????1_????????_????????_????????: accept_b_out_2 = 143'd2400000000; + 40'b????????_??????1?_????????_????????_????????: accept_b_out_2 = 143'd2500000000; + 40'b????????_?????1??_????????_????????_????????: accept_b_out_2 = 143'd2600000000; + 40'b????????_????1???_????????_????????_????????: accept_b_out_2 = 143'd2700000000; + 40'b????????_???1????_????????_????????_????????: accept_b_out_2 = 143'd2800000000; + 40'b????????_??1?????_????????_????????_????????: accept_b_out_2 = 143'd2900000000; + 40'b????????_?1??????_????????_????????_????????: accept_b_out_2 = 143'd3000000000; + 40'b????????_1???????_????????_????????_????????: accept_b_out_2 = 143'd3100000000; + 40'b???????1_????????_????????_????????_????????: accept_b_out_2 = 143'd3200000000; + 40'b??????1?_????????_????????_????????_????????: accept_b_out_2 = 143'd3300000000; + 40'b?????1??_????????_????????_????????_????????: accept_b_out_2 = 143'd3400000000; + 40'b????1???_????????_????????_????????_????????: accept_b_out_2 = 143'd3500000000; + 40'b???1????_????????_????????_????????_????????: accept_b_out_2 = 143'd3600000000; + 40'b??1?????_????????_????????_????????_????????: accept_b_out_2 = 143'd3700000000; + 40'b?1??????_????????_????????_????????_????????: accept_b_out_2 = 143'd3800000000; + 40'b1???????_????????_????????_????????_????????: accept_b_out_2 = 143'd3900000000; + default: accept_b_out_2 = '1; + endcase + end + assign accept_b_ref_0 = 6'(cyc[5:1]); + assign accept_b_ref_1 = 56'(cyc[5:1]) * 56'd100; + assign accept_b_ref_2 = 143'(cyc[5:1]) * 143'd100000000; + + // Accept C: a 155-bit (W) selector decoded into outputs of three widths (I/Q/W result). + wire [154:0] accept_c_in = 155'b1 << cyc[5:0]; + logic [5:0] accept_c_out_0, accept_c_ref_0; + logic [55:0] accept_c_out_1, accept_c_ref_1; + logic [142:0] accept_c_out_2, accept_c_ref_2; + always_comb begin + casez (accept_c_in) + 155'b????????_????????_????????_????????_???????1: accept_c_out_0 = 6'd00; + 155'b????????_????????_????????_????????_??????1?: accept_c_out_0 = 6'd01; + 155'b????????_????????_????????_????????_?????1??: accept_c_out_0 = 6'd02; + 155'b????????_????????_????????_????????_????1???: accept_c_out_0 = 6'd03; + 155'b????????_????????_????????_????????_???1????: accept_c_out_0 = 6'd04; + 155'b????????_????????_????????_????????_??1?????: accept_c_out_0 = 6'd05; + 155'b????????_????????_????????_????????_?1??????: accept_c_out_0 = 6'd06; + 155'b????????_????????_????????_????????_1???????: accept_c_out_0 = 6'd07; + 155'b????????_????????_????????_???????1_????????: accept_c_out_0 = 6'd08; + 155'b????????_????????_????????_??????1?_????????: accept_c_out_0 = 6'd09; + 155'b????????_????????_????????_?????1??_????????: accept_c_out_0 = 6'd10; + default: accept_c_out_0 = '1; + endcase + casez (accept_c_in) + 155'b????????_????????_????????_????????_???????1: accept_c_out_1 = 56'd0000; + 155'b????????_????????_????????_????????_??????1?: accept_c_out_1 = 56'd0100; + 155'b????????_????????_????????_????????_?????1??: accept_c_out_1 = 56'd0200; + 155'b????????_????????_????????_????????_????1???: accept_c_out_1 = 56'd0300; + 155'b????????_????????_????????_????????_???1????: accept_c_out_1 = 56'd0400; + 155'b????????_????????_????????_????????_??1?????: accept_c_out_1 = 56'd0500; + 155'b????????_????????_????????_????????_?1??????: accept_c_out_1 = 56'd0600; + 155'b????????_????????_????????_????????_1???????: accept_c_out_1 = 56'd0700; + 155'b????????_????????_????????_???????1_????????: accept_c_out_1 = 56'd0800; + 155'b????????_????????_????????_??????1?_????????: accept_c_out_1 = 56'd0900; + 155'b????????_????????_????????_?????1??_????????: accept_c_out_1 = 56'd1000; + default: accept_c_out_1 = '1; + endcase + casez (accept_c_in) + 155'b????????_????????_????????_????????_???????1: accept_c_out_2 = 143'd0000000000; + 155'b????????_????????_????????_????????_??????1?: accept_c_out_2 = 143'd0100000000; + 155'b????????_????????_????????_????????_?????1??: accept_c_out_2 = 143'd0200000000; + 155'b????????_????????_????????_????????_????1???: accept_c_out_2 = 143'd0300000000; + 155'b????????_????????_????????_????????_???1????: accept_c_out_2 = 143'd0400000000; + 155'b????????_????????_????????_????????_??1?????: accept_c_out_2 = 143'd0500000000; + 155'b????????_????????_????????_????????_?1??????: accept_c_out_2 = 143'd0600000000; + 155'b????????_????????_????????_????????_1???????: accept_c_out_2 = 143'd0700000000; + 155'b????????_????????_????????_???????1_????????: accept_c_out_2 = 143'd0800000000; + 155'b????????_????????_????????_??????1?_????????: accept_c_out_2 = 143'd0900000000; + 155'b????????_????????_????????_?????1??_????????: accept_c_out_2 = 143'd1000000000; + default: accept_c_out_2 = '1; + endcase + end + assign accept_c_ref_0 = cyc[5:0] <= 6'd10 ? 6'(cyc[5:0]) : ~6'd0; + assign accept_c_ref_1 = cyc[5:0] <= 6'd10 ? 56'(cyc[5:0]) * 56'd100 : ~56'd0; + assign accept_c_ref_2 = cyc[5:0] <= 6'd10 ? 143'(cyc[5:0]) * 143'd100000000 : ~143'd0; + + // Accept D: the default value is set before the case, with no default item. + wire [39:0] accept_d_in = 40'b1 << cyc[4:0]; + logic [4:0] accept_d_out, accept_d_ref; + always_comb begin + accept_d_out = '1; + casez (accept_d_in) + 40'b????????_????????_????????_????????_???????1: accept_d_out = 5'd0; + 40'b????????_????????_????????_????????_??????1?: accept_d_out = 5'd1; + 40'b????????_????????_????????_????????_?????1??: accept_d_out = 5'd2; + 40'b????????_????????_????????_????????_????1???: accept_d_out = 5'd3; + endcase + end + assign accept_d_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; + + // Accept E: an empty default item. + wire [39:0] accept_e_in = 40'b1 << cyc[4:0]; + logic [4:0] accept_e_out, accept_e_ref; + always_comb begin + accept_e_out = '1; + casez (accept_e_in) + 40'b????????_????????_????????_????????_???????1: accept_e_out = 5'd0; + 40'b????????_????????_????????_????????_??????1?: accept_e_out = 5'd1; + 40'b????????_????????_????????_????????_?????1??: accept_e_out = 5'd2; + 40'b????????_????????_????????_????????_????1???: accept_e_out = 5'd3; + default: begin + end + endcase + end + assign accept_e_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; + + // Accept F: an item with an empty body (output keeps its pre-case default). + wire [39:0] accept_f_in = 40'b1 << cyc[4:0]; + logic [4:0] accept_f_out, accept_f_ref; + always_comb begin + accept_f_out = '1; + casez (accept_f_in) + 40'b????????_????????_????????_????????_???1????: begin + end + 40'b????????_????????_????????_????????_???????1: accept_f_out = 5'd0; + 40'b????????_????????_????????_????????_??????1?: accept_f_out = 5'd1; + 40'b????????_????????_????????_????????_?????1??: accept_f_out = 5'd2; + 40'b????????_????????_????????_????????_????1???: accept_f_out = 5'd3; + default: begin + end + endcase + end + assign accept_f_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; + + // Accept G: non-blocking assignments. + wire [23:0] accept_g_in = 24'b1 << cyc[4:0]; + logic [5:0] accept_g_out, accept_g_ref; + always_ff @(posedge clk) begin + accept_g_out <= '1; + casez (accept_g_in) + 24'b????????_????????_???????1: accept_g_out <= 6'd0; + 24'b????????_????????_??????1?: accept_g_out <= 6'd1; + 24'b????????_????????_?????1??: accept_g_out <= 6'd2; + 24'b????????_????????_????1???: accept_g_out <= 6'd3; + endcase + end + always_ff @(posedge clk) + accept_g_ref <= cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 + : cyc[4:0] == 5'd2 ? 6'd2 : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + + // Accept H: multiple outputs from one decoder, some items assigning only a subset. + wire [23:0] accept_h_in = 24'b1 << cyc[4:0]; + logic [5:0] accept_h_out_0, accept_h_ref_0; + logic [11:0] accept_h_out_1, accept_h_ref_1; + always_comb begin + accept_h_out_0 = '1; + accept_h_out_1 = '1; + casez (accept_h_in) + 24'b????????_????????_???????1: begin + accept_h_out_0 = 6'd0; + accept_h_out_1 = 12'h001; + end + 24'b????????_????????_??????1?: accept_h_out_0 = 6'd1; + 24'b????????_????????_?????1??: accept_h_out_1 = 12'h004; + 24'b????????_????????_????1???: begin + accept_h_out_0 = 6'd3; + accept_h_out_1 = 12'h008; + end + endcase + end + assign accept_h_ref_0 = cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 + : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + assign accept_h_ref_1 = cyc[4:0] == 5'd0 ? 12'h001 : cyc[4:0] == 5'd2 ? 12'h004 + : cyc[4:0] == 5'd3 ? 12'h008 : ~12'd0; + + // Accept I: multiple outputs from one decoder, non-blocking assignments. + wire [23:0] accept_i_in = 24'b1 << cyc[4:0]; + logic [5:0] accept_i_out_0, accept_i_ref_0; + logic [11:0] accept_i_out_1, accept_i_ref_1; + always_ff @(posedge clk) begin + accept_i_out_0 <= '1; + accept_i_out_1 <= '1; + casez (accept_i_in) + 24'b????????_????????_???????1: begin + accept_i_out_0 <= 6'd0; + accept_i_out_1 <= 12'h001; + end + 24'b????????_????????_??????1?: begin + accept_i_out_0 <= 6'd1; + accept_i_out_1 <= 12'h002; + end + 24'b????????_????????_?????1??: begin + accept_i_out_0 <= 6'd2; + accept_i_out_1 <= 12'h004; + end + 24'b????????_????????_????1???: begin + accept_i_out_0 <= 6'd3; + accept_i_out_1 <= 12'h008; + end + endcase + end + always_ff @(posedge clk) begin + accept_i_ref_0 <= cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 + : cyc[4:0] == 5'd2 ? 6'd2 : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + accept_i_ref_1 <= cyc[4:0] == 5'd0 ? 12'h001 : cyc[4:0] == 5'd1 ? 12'h002 + : cyc[4:0] == 5'd2 ? 12'h004 : cyc[4:0] == 5'd3 ? 12'h008 : ~12'd0; + end + + // Accept J: an item that can never match in 2-state (an X in casez) is skipped. + // verilator lint_off CASEWITHX + wire [23:0] accept_j_in = 24'b1 << cyc[4:0]; + logic [5:0] accept_j_out, accept_j_ref; + always_comb begin + accept_j_out = '1; + casez (accept_j_in) + 24'b????????_????????_???????x: accept_j_out = 6'd9; // X never matches in 2-state, skipped + 24'b????????_????????_??????1?: accept_j_out = 6'd1; + 24'b????????_????????_?????1??: accept_j_out = 6'd2; + 24'b????????_????????_????1???: accept_j_out = 6'd3; + endcase + end + // verilator lint_on CASEWITHX + assign accept_j_ref = cyc[4:0] == 5'd1 ? 6'd1 : cyc[4:0] == 5'd2 ? 6'd2 + : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + + // Accept K: Will constant fold after converting to decoder + wire [23:0] accept_k_in; + logic [5:0] accept_k_out, accept_k_ref; + always_ff @(posedge clk) begin + accept_k_out <= '1; + casez (accept_k_in) + 24'b????????_????????_???????1: accept_k_out <= 6'd0; + 24'b????????_????????_??????1?: accept_k_out <= 6'd1; + 24'b????????_????????_?????1??: accept_k_out <= 6'd2; + 24'b????????_????????_????1???: accept_k_out <= 6'd3; + endcase + end + always_ff @(posedge clk) accept_k_ref <= 6'd2; + assign accept_k_in = 24'b100; + + // The cases below are intentionally NOT converted to a decoder. + + // Reject A: too few conditions to be worth the indexed load. + wire [23:0] reject_a_in = 24'b1 << cyc[4:0]; + logic [5:0] reject_a_out, reject_a_ref; + always_comb begin + reject_a_out = '1; + casez (reject_a_in) + 24'b????????_????????_???????1: reject_a_out = 6'd0; + 24'b????????_????????_??????1?: reject_a_out = 6'd1; + endcase + end + assign reject_a_ref = cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 : ~6'd0; + + // Reject B: every condition contains an X, so none can ever match in 2-state. The case has no + // matchable branches, so it must not be treated as a decoder - it just keeps the + // pre-case default. + // verilator lint_off CASEWITHX + wire [23:0] reject_b_in = 24'b1 << cyc[4:0]; + logic [5:0] reject_b_out, reject_b_ref; + always_comb begin + reject_b_out = 6'h2a; + casez (reject_b_in) + 24'b????????_????????_???????x: reject_b_out = 6'd0; + 24'b????????_????????_??????x?: reject_b_out = 6'd1; + 24'b????????_????????_?????x??: reject_b_out = 6'd2; + endcase + end + // verilator lint_on CASEWITHX + assign reject_b_ref = 6'h2a; // No condition can ever match, so always the pre-default + + // Test driver/checker + always @(posedge clk) begin + `checkh(accept_a_out_0, accept_a_ref_0); + `checkh(accept_a_out_1, accept_a_ref_1); + `checkh(accept_a_out_2, accept_a_ref_2); + `checkh(accept_b_out_0, accept_b_ref_0); + `checkh(accept_b_out_1, accept_b_ref_1); + `checkh(accept_b_out_2, accept_b_ref_2); + `checkh(accept_c_out_0, accept_c_ref_0); + `checkh(accept_c_out_1, accept_c_ref_1); + `checkh(accept_c_out_2, accept_c_ref_2); + `checkh(accept_d_out, accept_d_ref); + `checkh(accept_e_out, accept_e_ref); + `checkh(accept_f_out, accept_f_ref); + `checkh(accept_g_out, accept_g_ref); + `checkh(accept_h_out_0, accept_h_ref_0); + `checkh(accept_h_out_1, accept_h_ref_1); + `checkh(accept_i_out_0, accept_i_ref_0); + `checkh(accept_i_out_1, accept_i_ref_1); + `checkh(accept_j_out, accept_j_ref); + `checkh(accept_k_out, accept_k_ref); + `checkh(reject_a_out, reject_a_ref); + `checkh(reject_b_out, reject_b_ref); + + cyc <= cyc + 32'd1; + if (cyc[16]) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_decoder_off.py b/test_regress/t/t_case_decoder_off.py new file mode 100755 index 000000000..abc61d879 --- /dev/null +++ b/test_regress/t/t_case_decoder_off.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_case_decoder.v" + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-case-decoder']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases decoder\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_enum_incomplete_bad.out b/test_regress/t/t_case_enum_incomplete_bad.out index 6fa08e122..6f3986c1d 100644 --- a/test_regress/t/t_case_enum_incomplete_bad.out +++ b/test_regress/t/t_case_enum_incomplete_bad.out @@ -1,4 +1,4 @@ -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_bad.v:18:12: Enum item 'S1' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_bad.v:18:12: Enum item(s) not covered by case: 'S1', 'S2' 18 | unique case (state) | ^~~~ ... For warning description see https://verilator.org/warn/CASEINCOMPLETE?v=latest diff --git a/test_regress/t/t_case_enum_incomplete_bad.v b/test_regress/t/t_case_enum_incomplete_bad.v index d5d4dabab..6f5dfd6c0 100644 --- a/test_regress/t/t_case_enum_incomplete_bad.v +++ b/test_regress/t/t_case_enum_incomplete_bad.v @@ -17,7 +17,6 @@ module t; unique case (state) S0: $stop; - S2: $stop; endcase end endmodule diff --git a/test_regress/t/t_case_enum_incomplete_wildcard_bad.out b/test_regress/t/t_case_enum_incomplete_wildcard_bad.out index 289933112..59e2f5b81 100644 --- a/test_regress/t/t_case_enum_incomplete_wildcard_bad.out +++ b/test_regress/t/t_case_enum_incomplete_wildcard_bad.out @@ -1,12 +1,12 @@ -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:26:12: Enum item 'S10' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:26:12: Enum item(s) not covered by case: 'S10', 'SX0' 26 | unique case (state) | ^~~~ ... For warning description see https://verilator.org/warn/CASEINCOMPLETE?v=latest ... Use "/* verilator lint_off CASEINCOMPLETE */" and lint_on around source to disable this message. -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:30:12: Enum item 'S00' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:30:12: Enum item(s) not covered by case: 'S00', 'SX0', 'S0X' 30 | unique case (state) | ^~~~ -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:35:12: Enum item 'S10' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:35:12: Enum item(s) not covered by case: 'S10', 'SX0' 35 | unique casez (state) | ^~~~~ %Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:40:5: Case values incompletely covered (example pattern 0x3) diff --git a/test_regress/t/t_case_huge.py b/test_regress/t/t_case_huge.py index 0ac31a2f8..57891d35b 100755 --- a/test_regress/t/t_case_huge.py +++ b/test_regress/t/t_case_huge.py @@ -16,12 +16,10 @@ test.compile(verilator_flags2=["--stats", "-fno-dfg"]) test.execute() -if test.vlt: - test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 11) - test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)', 8) - test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 10) -elif test.vltmt: - test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)', 9) - test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 10) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 8) +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 3) +test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)', 9 if test.vltmt else 8) test.passes() diff --git a/test_regress/t/t_case_huge_nocase.py b/test_regress/t/t_case_huge_nocase.py index 2b3aad742..011bbdc67 100755 --- a/test_regress/t/t_case_huge_nocase.py +++ b/test_regress/t/t_case_huge_nocase.py @@ -16,6 +16,8 @@ test.compile(verilator_flags2=["--stats -fno-case"]) test.execute() +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 0) test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_case_huge_nocase_tree.py b/test_regress/t/t_case_huge_nocase_tree.py new file mode 100755 index 000000000..2083db119 --- /dev/null +++ b/test_regress/t/t_case_huge_nocase_tree.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator_st') +test.top_filename = 't/t_case_huge.v' + +test.compile(verilator_flags2=["--stats -fno-case-tree"]) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 8) +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_inside_call_count.py b/test_regress/t/t_case_inside_call_count.py index 4116217e8..df91d0e3c 100755 --- a/test_regress/t/t_case_inside_call_count.py +++ b/test_regress/t/t_case_inside_call_count.py @@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3) test.passes() diff --git a/test_regress/t/t_case_inside_with_x.py b/test_regress/t/t_case_inside_with_x.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_inside_with_x.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_case_inside_with_x.v b/test_regress/t/t_case_inside_with_x.v new file mode 100644 index 000000000..935d779e8 --- /dev/null +++ b/test_regress/t/t_case_inside_with_x.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module top; + bit clk = 1'b0; + always #1 clk = ~clk; + + logic [2:0] cyc = 3'd0; + int count = 0; + always @(posedge clk) begin + // verilator lint_off CASEWITHX + case (cyc) inside + 3'b000: begin + $display("case inside 000"); + ++count; + end + 3'b001: begin + $display("case inside 001"); + ++count; + end + // Should match z + 3'b01?: begin + $display("case inside 01?"); + ++count; + end + // Should match x + 3'b1xx: begin + $display("case inside 1xx"); + ++count; + end + endcase + // verilator lint_on CASEWITHX + cyc <= cyc + 3'd1; + if (&cyc) begin + `checkh(count, 8); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_priority_overlap.py b/test_regress/t/t_case_priority_overlap.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_case_priority_overlap.v b/test_regress/t/t_case_priority_overlap.v new file mode 100644 index 000000000..9b7e794d6 --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Regression: a `priority` case item whose later condition (INST_B) is fully +// subsumed by an earlier condition (INST_A) of the *same* item. Overlap within +// a single case item is legal, but this previously crashed V3Case with a null +// pointer dereference: the priority "case item ignored" CASEOVERLAP diagnostic +// dereferenced 'overlappedCondp', which is null when the only covering item is +// the item itself. Must compile cleanly and simulate correctly. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [1:0] in; + logic [1:0] out; + + always_comb begin + priority casez (in) + 2'b1?, // fully subsumes 2'b11 below on the same case clause + 2'b11: + out = 2'b10; + 2'b0?: out = 2'b01; + endcase + end + + initial begin + in = 2'b00; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b01; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b10; + @(posedge clk); + `checkh(out, 2'b10); + + in = 2'b11; + @(posedge clk); + `checkh(out, 2'b10); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_case_table_normal.py b/test_regress/t/t_case_table_normal.py new file mode 100755 index 000000000..25746e901 --- /dev/null +++ b/test_regress/t/t_case_table_normal.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 8) +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_table_normal.v b/test_regress/t/t_case_table_normal.v new file mode 100644 index 000000000..a7b6285f0 --- /dev/null +++ b/test_regress/t/t_case_table_normal.v @@ -0,0 +1,315 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// Case statements that become a "normal" (constant-pool) lookup table, followed by +// cases that must not be converted to one. Each output is compared against an +// equivalent reference computed without a case statement, so the reference itself is +// never tabled. Selectors are wide enough, with enough distinct values, that the +// branch lowering they replace is deep enough to make a table worthwhile. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Accept A: single output, blocking assignment, all selector values covered. + logic [15:0] accept_a_out, accept_a_ref; + always_comb + case (cyc[3:0]) + 4'd0: accept_a_out = 16'h1111; + 4'd1: accept_a_out = 16'h2222; + 4'd2: accept_a_out = 16'h4444; + 4'd3: accept_a_out = 16'h8888; + default: accept_a_out = 16'h0f0f; + endcase + assign accept_a_ref = (cyc[3:0] == 4'd0) ? 16'h1111 + : (cyc[3:0] == 4'd1) ? 16'h2222 + : (cyc[3:0] == 4'd2) ? 16'h4444 + : (cyc[3:0] == 4'd3) ? 16'h8888 : 16'h0f0f; + + // Accept B: single output, non-blocking assignment, with a default value set before + // the case and not all selector values covered. + logic [15:0] accept_b_out, accept_b_ref; + // verilator lint_off CASEINCOMPLETE + always_ff @(posedge clk) begin + accept_b_out <= 16'hffff; + case (cyc[3:0]) + 4'd0: accept_b_out <= 16'h0001; + 4'd1: accept_b_out <= 16'h0002; + 4'd2: accept_b_out <= 16'h0004; + 4'd3: accept_b_out <= 16'h0008; + endcase + end + // verilator lint_on CASEINCOMPLETE + always_ff @(posedge clk) + accept_b_ref <= (cyc[3:0] == 4'd0) ? 16'h0001 + : (cyc[3:0] == 4'd1) ? 16'h0002 + : (cyc[3:0] == 4'd2) ? 16'h0004 + : (cyc[3:0] == 4'd3) ? 16'h0008 : 16'hffff; + + // Accept C: three outputs, blocking assignment, with a default branch. + logic [11:0] accept_c_out_0, accept_c_ref_0; + logic [11:0] accept_c_out_1, accept_c_ref_1; + logic [11:0] accept_c_out_2, accept_c_ref_2; + always_comb + case (cyc[3:0]) + 4'd0: begin + accept_c_out_0 = 12'h001; + accept_c_out_1 = 12'h010; + accept_c_out_2 = 12'h100; + end + 4'd1: begin + accept_c_out_0 = 12'h002; + accept_c_out_1 = 12'h020; + accept_c_out_2 = 12'h200; + end + 4'd2: begin + accept_c_out_0 = 12'h004; + accept_c_out_1 = 12'h040; + accept_c_out_2 = 12'h400; + end + 4'd3: begin + accept_c_out_0 = 12'h008; + accept_c_out_1 = 12'h080; + accept_c_out_2 = 12'h800; + end + default: begin + accept_c_out_0 = 12'h000; + accept_c_out_1 = 12'h0ff; + accept_c_out_2 = 12'hfff; + end + endcase + assign accept_c_ref_0 = (cyc[3:0] == 4'd0) ? 12'h001 : (cyc[3:0] == 4'd1) ? 12'h002 + : (cyc[3:0] == 4'd2) ? 12'h004 : (cyc[3:0] == 4'd3) ? 12'h008 : 12'h000; + assign accept_c_ref_1 = (cyc[3:0] == 4'd0) ? 12'h010 : (cyc[3:0] == 4'd1) ? 12'h020 + : (cyc[3:0] == 4'd2) ? 12'h040 : (cyc[3:0] == 4'd3) ? 12'h080 : 12'h0ff; + assign accept_c_ref_2 = (cyc[3:0] == 4'd0) ? 12'h100 : (cyc[3:0] == 4'd1) ? 12'h200 + : (cyc[3:0] == 4'd2) ? 12'h400 : (cyc[3:0] == 4'd3) ? 12'h800 : 12'hfff; + + // Accept D: two outputs, non-blocking assignment, empty default branch, with default + // values set before the case. + logic [15:0] accept_d_out_0, accept_d_ref_0; + logic [15:0] accept_d_out_1, accept_d_ref_1; + always_ff @(posedge clk) begin + accept_d_out_0 <= 16'h0000; + accept_d_out_1 <= 16'hffff; + case (cyc[3:0]) + 4'd0: begin + accept_d_out_0 <= 16'h0001; + accept_d_out_1 <= 16'h0010; + end + 4'd1: begin + accept_d_out_0 <= 16'h0002; + accept_d_out_1 <= 16'h0020; + end + 4'd2: begin + accept_d_out_0 <= 16'h0004; + accept_d_out_1 <= 16'h0040; + end + 4'd3: begin + accept_d_out_0 <= 16'h0008; + accept_d_out_1 <= 16'h0080; + end + default: begin + end + endcase + end + always_ff @(posedge clk) begin + accept_d_ref_0 <= (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002 + : (cyc[3:0] == 4'd2) ? 16'h0004 : (cyc[3:0] == 4'd3) ? 16'h0008 : 16'h0000; + accept_d_ref_1 <= (cyc[3:0] == 4'd0) ? 16'h0010 : (cyc[3:0] == 4'd1) ? 16'h0020 + : (cyc[3:0] == 4'd2) ? 16'h0040 : (cyc[3:0] == 4'd3) ? 16'h0080 : 16'hffff; + end + + // Accept E: casez with don't-care bits. + logic [15:0] accept_e_out, accept_e_ref; + always_comb + casez (cyc[3:0]) + 4'b00??: accept_e_out = 16'haaaa; + 4'b01??: accept_e_out = 16'hbbbb; + 4'b10??: accept_e_out = 16'hcccc; + 4'b11??: accept_e_out = 16'hdddd; + endcase + assign accept_e_ref = (cyc[3:2] == 2'd0) ? 16'haaaa : (cyc[3:2] == 2'd1) ? 16'hbbbb + : (cyc[3:2] == 2'd2) ? 16'hcccc : 16'hdddd; + + // Accept F: an item that can never match, and an item listing multiple values. + logic [15:0] accept_f_out, accept_f_ref; + // verilator lint_off CASEWITHX + always_comb + casez (cyc[3:0]) + 4'bxxx0: accept_f_out = 16'h0000; // X can never match in 2-state + 4'b0001, 4'b0011, 4'b0101: accept_f_out = 16'h5555; // lists three values + default: accept_f_out = 16'h9999; + endcase + // verilator lint_on CASEWITHX + assign accept_f_ref = (cyc[3:0] == 4'd1 || cyc[3:0] == 4'd3 || cyc[3:0] == 4'd5) + ? 16'h5555 : 16'h9999; + + // Accept G: items assign different subsets of two outputs, with default values (and an + // unrelated output) set before the case. + logic [15:0] accept_g_out_0, accept_g_ref_0; + logic [15:0] accept_g_out_1, accept_g_ref_1; + logic [15:0] accept_g_out_2, accept_g_ref_2; + always_comb begin + accept_g_out_0 = 16'h0000; + accept_g_out_1 = 16'hffff; + accept_g_out_2 = 16'h3333; // not assigned in the case + case (cyc[3:0]) + 4'd0: accept_g_out_0 = 16'h0001; + 4'd1: accept_g_out_1 = 16'h0002; + 4'd2: begin + accept_g_out_0 = 16'h0004; + accept_g_out_1 = 16'h0008; + end + 4'd3: accept_g_out_0 = 16'h0010; + default: ; + endcase + end + assign accept_g_ref_0 = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd2) ? 16'h0004 + : (cyc[3:0] == 4'd3) ? 16'h0010 : 16'h0000; + assign accept_g_ref_1 = (cyc[3:0] == 4'd1) ? 16'h0002 : (cyc[3:0] == 4'd2) ? 16'h0008 : 16'hffff; + assign accept_g_ref_2 = 16'h3333; + + // Accept H: unique0 enum case; the selector may hold an out-of-range value. + typedef enum logic [3:0] { + NE0, + NE1, + NE2, + NE3, + NE4 + } ne_t; + ne_t accept_h_in; + assign accept_h_in = ne_t'(cyc[3:0]); + logic [15:0] accept_h_out, accept_h_ref; + always_comb begin + accept_h_out = 16'hffff; + unique0 case (accept_h_in) + NE0: accept_h_out = 16'h0001; + NE1: accept_h_out = 16'h0002; + NE2: accept_h_out = 16'h0003; + NE3: accept_h_out = 16'h0004; + NE4: accept_h_out = 16'h0005; + endcase + end + assign accept_h_ref = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002 + : (cyc[3:0] == 4'd2) ? 16'h0003 : (cyc[3:0] == 4'd3) ? 16'h0004 + : (cyc[3:0] == 4'd4) ? 16'h0005 : 16'hffff; + + // The cases below are intentionally NOT converted to a lookup table. + + // Reject A: too few distinct values, so the branch lowering is cheaper than a load. + logic [15:0] reject_a_out, reject_a_ref; + always_comb + case (cyc[3:0]) + 4'd0: reject_a_out = 16'h0001; + 4'd1: reject_a_out = 16'h0002; + default: reject_a_out = 16'h00ff; + endcase + assign reject_a_ref = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002 : 16'h00ff; + + // Reject B: a one-bit selector, too shallow to be worth a load. + logic [19:0] reject_b_out, reject_b_ref; + always_comb + case (cyc[0]) + 1'b0: reject_b_out = 20'h00001; + 1'b1: reject_b_out = 20'h00002; + default: reject_b_out = 20'h00000; + endcase + assign reject_b_ref = cyc[0] ? 20'h00002 : 20'h00001; + + // Reject C: a 12-bit selector, too wide to table. + logic [15:0] reject_c_out, reject_c_ref; + always_comb + case (cyc[11:0]) + 12'd0: reject_c_out = 16'h0001; + 12'd1: reject_c_out = 16'h0002; + 12'd2: reject_c_out = 16'h0004; + default: reject_c_out = 16'h0000; + endcase + assign reject_c_ref = (cyc[11:0] == 12'd0) ? 16'h0001 + : (cyc[11:0] == 12'd1) ? 16'h0002 + : (cyc[11:0] == 12'd2) ? 16'h0004 : 16'h0000; + + // Reject D: a 17-bit selector, too wide to table. + logic [16:0] reject_d_in; + assign reject_d_in = cyc[16:0]; + logic [15:0] reject_d_out, reject_d_ref; + // verilator lint_off CASEINCOMPLETE + always_comb begin + reject_d_out = 16'hbeef; + case (reject_d_in) + 17'd0: reject_d_out = 16'h0001; + 17'd1: reject_d_out = 16'h0002; + 17'd2: reject_d_out = 16'h0004; + endcase + end + // verilator lint_on CASEINCOMPLETE + assign reject_d_ref = (reject_d_in == 17'd0) ? 16'h0001 + : (reject_d_in == 17'd1) ? 16'h0002 + : (reject_d_in == 17'd2) ? 16'h0004 : 16'hbeef; + + // Reject E: a whole output and a sub-range of it assigned in different items. + logic [7:0] reject_e_out, reject_e_ref; + always_comb begin + reject_e_out = 8'h00; + reject_e_out[3:0] = 4'h0; + case (cyc[1:0]) + 2'b00: reject_e_out = 8'haa; // assigns the whole output + 2'b01: reject_e_out[3:0] = 4'h5; // assigns a sub-range of the same output + default: ; + endcase + end + assign reject_e_ref = (cyc[1:0] == 2'd0) ? 8'haa : (cyc[1:0] == 2'd1) ? 8'h05 : 8'h00; + + // Reject F: a sub-range's default value is overwritten by a later whole-output default + // before the case, so the sub-range's pre-case value is set elsewhere. + logic [31:0] reject_f_out, reject_f_ref; + always_comb begin + reject_f_out[15:0] = 16'h0005; // farther default for the sub-range + reject_f_out = 32'h0; // closer whole-output default overwrites the sub-range to 0 + case (cyc[1:0]) + 2'b00: reject_f_out[15:0] = 16'habcd; // only the sub-range is assigned in the case + default: ; + endcase + end + assign reject_f_ref = (cyc[1:0] == 2'd0) ? 32'h0000abcd : 32'h00000000; + + // Test driver/checker + always @(posedge clk) begin + `checkh(accept_a_out, accept_a_ref); + `checkh(accept_b_out, accept_b_ref); + `checkh(accept_c_out_0, accept_c_ref_0); + `checkh(accept_c_out_1, accept_c_ref_1); + `checkh(accept_c_out_2, accept_c_ref_2); + `checkh(accept_d_out_0, accept_d_ref_0); + `checkh(accept_d_out_1, accept_d_ref_1); + `checkh(accept_e_out, accept_e_ref); + `checkh(accept_f_out, accept_f_ref); + `checkh(accept_g_out_0, accept_g_ref_0); + `checkh(accept_g_out_1, accept_g_ref_1); + `checkh(accept_g_out_2, accept_g_ref_2); + `checkh(accept_h_out, accept_h_ref); + `checkh(reject_a_out, reject_a_ref); + `checkh(reject_b_out, reject_b_ref); + `checkh(reject_c_out, reject_c_ref); + `checkh(reject_d_out, reject_d_ref); + `checkh(reject_e_out, reject_e_ref); + `checkh(reject_f_out, reject_f_ref); + + cyc <= cyc + 32'd1; + if (cyc == 32'd32) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_table_normal_off.py b/test_regress/t/t_case_table_normal_off.py new file mode 100755 index 000000000..7497fbc92 --- /dev/null +++ b/test_regress/t/t_case_table_normal_off.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_case_table_normal.v" + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-case-table']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_table_tiny.py b/test_regress/t/t_case_table_tiny.py new file mode 100755 index 000000000..55d103058 --- /dev/null +++ b/test_regress/t/t_case_table_tiny.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 11) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_case_table_tiny.v b/test_regress/t/t_case_table_tiny.v new file mode 100644 index 000000000..ecf79db3d --- /dev/null +++ b/test_regress/t/t_case_table_tiny.v @@ -0,0 +1,391 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// Case statements that become a "tiny" lookup table, followed by cases that must +// not be converted to one. Each output is compared against an equivalent reference +// computed without a case statement, so the reference itself is never tabled. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Accept A: single output, blocking assignment, all selector values covered. + wire [2:0] accept_a_in = cyc[2:0]; + logic [3:0] accept_a_out, accept_a_ref; + always_comb + case (accept_a_in) + 3'd0: accept_a_out = 4'd3; + 3'd1: accept_a_out = 4'd4; + 3'd2: accept_a_out = 4'd5; + 3'd3: accept_a_out = 4'd6; + 3'd4: accept_a_out = 4'd7; + 3'd5: accept_a_out = 4'd8; + 3'd6: accept_a_out = 4'd9; + 3'd7: accept_a_out = 4'd10; + endcase + assign accept_a_ref = 4'd3 + {1'b0, accept_a_in}; + + // Accept B: single output, non-blocking assignment, with a default value set before + // the case and not all selector values covered. + logic [3:0] accept_b_out, accept_b_ref; + // verilator lint_off CASEINCOMPLETE + always_ff @(posedge clk) begin + accept_b_out <= 4'hf; + case (cyc[1:0]) + 2'b00: accept_b_out <= 4'h1; + 2'b01: accept_b_out <= 4'h2; + endcase + end + // verilator lint_on CASEINCOMPLETE + always_ff @(posedge clk) + accept_b_ref <= (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'hf; + + // Accept C: two outputs of different widths, blocking assignment, with a default branch. + logic [2:0] accept_c_out_0, accept_c_ref_0; + logic [3:0] accept_c_out_1, accept_c_ref_1; + always_comb + case (cyc[1:0]) + 2'b00: begin + accept_c_out_0 = 3'd1; + accept_c_out_1 = 4'd6; + end + 2'b01: begin + accept_c_out_0 = 3'd2; + accept_c_out_1 = 4'd5; + end + default: begin + accept_c_out_0 = 3'd0; + accept_c_out_1 = 4'd7; + end + endcase + assign accept_c_ref_0 = (cyc[1:0] == 2'b00) ? 3'd1 : (cyc[1:0] == 2'b01) ? 3'd2 : 3'd0; + assign accept_c_ref_1 = (cyc[1:0] == 2'b00) ? 4'd6 : (cyc[1:0] == 2'b01) ? 4'd5 : 4'd7; + + // Accept D: two outputs, non-blocking assignment, empty default branch, with default + // values set before the case. + logic [2:0] accept_d_out_0, accept_d_ref_0; + logic [2:0] accept_d_out_1, accept_d_ref_1; + always_ff @(posedge clk) begin + accept_d_out_0 <= 3'd0; + accept_d_out_1 <= 3'd7; + case (cyc[1:0]) + 2'b00: begin + accept_d_out_0 <= 3'd1; + accept_d_out_1 <= 3'd6; + end + 2'b01: begin + accept_d_out_0 <= 3'd2; + accept_d_out_1 <= 3'd5; + end + default: begin + end + endcase + end + always_ff @(posedge clk) begin + accept_d_ref_0 <= (cyc[1:0] == 2'b00) ? 3'd1 : (cyc[1:0] == 2'b01) ? 3'd2 : 3'd0; + accept_d_ref_1 <= (cyc[1:0] == 2'b00) ? 3'd6 : (cyc[1:0] == 2'b01) ? 3'd5 : 3'd7; + end + + // Accept E: casez with a don't-care bit. + logic [3:0] accept_e_out, accept_e_ref; + always_comb + casez (cyc[1:0]) + 2'b1?: accept_e_out = 4'ha; + 2'b0?: accept_e_out = 4'hb; + endcase + assign accept_e_ref = cyc[1] ? 4'ha : 4'hb; + + // Accept F: an item that can never match, and an item listing multiple values. + logic [3:0] accept_f_out, accept_f_ref; + // verilator lint_off CASEWITHX + always_comb + casez (cyc[1:0]) + 2'bx0: accept_f_out = 4'h0; // X can never match in 2-state + 2'b01, 2'b11: accept_f_out = 4'h5; // lists two values + default: accept_f_out = 4'h9; + endcase + // verilator lint_on CASEWITHX + assign accept_f_ref = (cyc[1:0] == 2'b01 || cyc[1:0] == 2'b11) ? 4'h5 : 4'h9; + + // Accept G: items assign different subsets of two outputs, with default values (and an + // unrelated output) set before the case. + logic [3:0] accept_g_out_0, accept_g_ref_0; + logic [3:0] accept_g_out_1, accept_g_ref_1; + logic [3:0] accept_g_out_2, accept_g_ref_2; + // verilator lint_off CASEINCOMPLETE + always_comb begin + accept_g_out_0 = 4'h0; + accept_g_out_1 = 4'hf; + accept_g_out_2 = 4'h3; // not assigned in the case + case (cyc[1:0]) + 2'b00: accept_g_out_0 = 4'h1; + 2'b01: accept_g_out_1 = 4'h2; + endcase + end + // verilator lint_on CASEINCOMPLETE + assign accept_g_ref_0 = (cyc[1:0] == 2'b00) ? 4'h1 : 4'h0; + assign accept_g_ref_1 = (cyc[1:0] == 2'b01) ? 4'h2 : 4'hf; + assign accept_g_ref_2 = 4'h3; + + // Accept H: single output, non-blocking assignment, all selector values covered. + logic [3:0] accept_h_out, accept_h_ref; + always_ff @(posedge clk) + case (cyc[1:0]) + 2'b00: accept_h_out <= 4'h1; + 2'b01: accept_h_out <= 4'h2; + 2'b10: accept_h_out <= 4'h4; + 2'b11: accept_h_out <= 4'h8; + endcase + always_ff @(posedge clk) accept_h_ref <= 4'h1 << cyc[1:0]; + + // Accept I: unique0 enum case; the selector may hold an out-of-range value. + typedef enum logic [1:0] { + E0, + E1, + E2 + } e_t; + e_t accept_i_in; + assign accept_i_in = e_t'(cyc[1:0]); + logic [3:0] accept_i_out, accept_i_ref; + always_comb begin + accept_i_out = 4'hf; + unique0 case (accept_i_in) + E0: accept_i_out = 4'h1; + E1: accept_i_out = 4'h2; + E2: accept_i_out = 4'h3; + endcase + end + assign accept_i_ref = (cyc[1:0] == 2'd0) ? 4'h1 + : (cyc[1:0] == 2'd1) ? 4'h2 + : (cyc[1:0] == 2'd2) ? 4'h3 : 4'hf; + + // Accept J: wide output, materialized as a normal (not tiny) lookup table. + logic [8:0] accept_j_out, accept_j_ref; + always_comb + case (cyc[3:0]) + 4'd0: accept_j_out = 9'h001; + 4'd1: accept_j_out = 9'h002; + 4'd2: accept_j_out = 9'h004; + 4'd3: accept_j_out = 9'h008; + default: accept_j_out = 9'h010; + endcase + assign accept_j_ref = (cyc[3:0] < 4'd4) ? (9'h1 << cyc[3:0]) : 9'h010; + + // Accept K: a non-constant assignment precedes the case. + logic [3:0] accept_k_out_0, accept_k_ref_0; + logic [3:0] accept_k_out_1, accept_k_ref_1; + always_comb begin + accept_k_out_1 = cyc[3:0] ^ 4'ha; // non-constant value + case (cyc[1:0]) + 2'b00: accept_k_out_0 = 4'h1; + 2'b01: accept_k_out_0 = 4'h2; + 2'b10: accept_k_out_0 = 4'h4; + 2'b11: accept_k_out_0 = 4'h8; + endcase + end + assign accept_k_ref_0 = 4'h1 << cyc[1:0]; + assign accept_k_ref_1 = cyc[3:0] ^ 4'ha; + + // Accept L: the same output is given a default value twice before the case. + logic [3:0] accept_l_out, accept_l_ref; + // verilator lint_off CASEINCOMPLETE + always_comb begin + accept_l_out = 4'h1; + accept_l_out = 4'h6; // assigned a second time before the case + case (cyc[1:0]) + 2'b00: accept_l_out = 4'h2; + 2'b01: accept_l_out = 4'h3; + endcase + end + // verilator lint_on CASEINCOMPLETE + assign accept_l_ref = (cyc[1:0] == 2'd0) ? 4'h2 : (cyc[1:0] == 2'd1) ? 4'h3 : 4'h6; + + // The cases below are intentionally NOT converted to a lookup table. + + // Reject A: an item whose body is not a simple assignment. + logic [3:0] reject_a_out, reject_a_ref; + always_comb begin + reject_a_out = 4'h0; + case (cyc[1:0]) + 2'b00: reject_a_out = 4'h1; + 2'b01: if (cyc[0]) reject_a_out = 4'h2; // not a simple assignment + default: reject_a_out = 4'h3; + endcase + end + assign reject_a_ref = (cyc[1:0] == 2'd0) ? 4'h1 : (cyc[1:0] == 2'd1) ? 4'h2 : 4'h3; + + // Reject B: an item assigns through a variable bit-select (the index is read). + logic [3:0] reject_b_out, reject_b_ref; + always_comb begin + reject_b_out = 4'h0; + case (cyc[1:0]) + 2'b00: reject_b_out[cyc[1:0]] = 1'b1; + default: reject_b_out = 4'h5; + endcase + end + assign reject_b_ref = (cyc[1:0] == 2'd0) ? 4'h1 : 4'h5; + + // Reject C: an item assigns the same output twice. + logic [3:0] reject_c_out, reject_c_ref; + always_comb begin + reject_c_out = 4'h0; + case (cyc[1:0]) + 2'b00: begin + reject_c_out = 4'h1; + reject_c_out = 4'h2; + end + default: reject_c_out = 4'h3; + endcase + end + assign reject_c_ref = (cyc[1:0] == 2'd0) ? 4'h2 : 4'h3; + + // Reject D: a non-constant case-item value. + logic [1:0] reject_d_in; + assign reject_d_in = cyc[1:0]; + logic [3:0] reject_d_out, reject_d_ref; + always_comb begin + reject_d_out = 4'h0; + case (cyc[1:0]) + reject_d_in: reject_d_out = 4'h7; // non-constant item value + default: reject_d_out = 4'h9; + endcase + end + assign reject_d_ref = 4'h7; // reject_d_in always equals the case expression + + // Reject E: all items are empty. + logic [3:0] reject_e_out, reject_e_ref; + always_comb begin + reject_e_out = 4'h7; + case (cyc[2:0]) + 3'd0: ; + 3'd1: ; + 3'd2: ; + 3'd3: ; + 3'd4: ; + 3'd5: ; + 3'd6: ; + 3'd7: ; + endcase + end + assign reject_e_ref = 4'h7; + + // Reject F: an item uses a delayed (intra-assignment) assignment. + logic [3:0] reject_f_out, reject_f_ref; + always_ff @(posedge clk) + case (cyc[1:0]) + 2'b00: reject_f_out <= #1 4'h1; // delayed assignment + default: reject_f_out <= 4'h2; + endcase + always_ff @(posedge clk) + if (cyc[1:0] == 2'b00) reject_f_ref <= #1 4'h1; + else reject_f_ref <= 4'h2; + + // Reject G: an output assigned with both blocking and non-blocking assignments. The three + // variants exercise the distinct ways the assignment kinds conflict. The deliberate + // mixing warnings are waived. + // verilator lint_off BLKANDNBLK + // verilator lint_off COMBDLY + // verilator lint_off CASEINCOMPLETE + // Variant 0: an item mixes a blocking and a non-blocking assignment to the same output. + logic [3:0] reject_g_out_0, reject_g_ref_0; + always_comb + case (cyc[1:0]) + 2'b00: reject_g_out_0 = 4'h1; + 2'b01: reject_g_out_0 <= 4'h2; + default: reject_g_out_0 = 4'h3; + endcase + assign reject_g_ref_0 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h3; + // Variant 1: blocking items, but the pre-case default is a non-blocking assignment. + logic [3:0] reject_g_out_1, reject_g_ref_1; + always_comb begin + reject_g_out_1 <= 4'h0; + case (cyc[1:0]) + 2'b00: reject_g_out_1 = 4'h1; + 2'b01: reject_g_out_1 = 4'h2; + endcase + end + assign reject_g_ref_1 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h0; + // Variant 2: non-blocking items, but the pre-case default is a blocking assignment. + logic [3:0] reject_g_out_2, reject_g_ref_2; + always_comb begin + reject_g_out_2 = 4'h0; + case (cyc[1:0]) + 2'b00: reject_g_out_2 <= 4'h1; + 2'b01: reject_g_out_2 <= 4'h2; + endcase + end + assign reject_g_ref_2 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h0; + // verilator lint_on CASEINCOMPLETE + // verilator lint_on COMBDLY + // verilator lint_on BLKANDNBLK + + // Reject H: items assign a real (non-packed) output. + real reject_h_out, reject_h_ref; + always_comb + case (cyc[1:0]) + 2'b00: reject_h_out = 1.5; + 2'b01: reject_h_out = 2.5; + default: reject_h_out = 9.0; + endcase + always_comb reject_h_ref = (cyc[1:0] == 2'b00) ? 1.5 : (cyc[1:0] == 2'b01) ? 2.5 : 9.0; + + // Reject I: items assign a string (non-packed) output. + string reject_i_out, reject_i_ref; + always_comb + case (cyc[1:0]) + 2'b00: reject_i_out = "zero"; + 2'b01: reject_i_out = "one"; + default: reject_i_out = "other"; + endcase + always_comb reject_i_ref = (cyc[1:0] == 2'b00) ? "zero" : (cyc[1:0] == 2'b01) ? "one" : "other"; + + // Test driver/checker + always @(posedge clk) begin + `checkh(accept_a_out, accept_a_ref); + `checkh(accept_b_out, accept_b_ref); + `checkh(accept_c_out_0, accept_c_ref_0); + `checkh(accept_c_out_1, accept_c_ref_1); + `checkh(accept_d_out_0, accept_d_ref_0); + `checkh(accept_d_out_1, accept_d_ref_1); + `checkh(accept_e_out, accept_e_ref); + `checkh(accept_f_out, accept_f_ref); + `checkh(accept_g_out_0, accept_g_ref_0); + `checkh(accept_g_out_1, accept_g_ref_1); + `checkh(accept_g_out_2, accept_g_ref_2); + `checkh(accept_h_out, accept_h_ref); + `checkh(accept_i_out, accept_i_ref); + `checkh(accept_j_out, accept_j_ref); + `checkh(accept_k_out_0, accept_k_ref_0); + `checkh(accept_k_out_1, accept_k_ref_1); + `checkh(accept_l_out, accept_l_ref); + `checkh(reject_a_out, reject_a_ref); + `checkh(reject_b_out, reject_b_ref); + `checkh(reject_c_out, reject_c_ref); + `checkh(reject_d_out, reject_d_ref); + `checkh(reject_e_out, reject_e_ref); + `checkh(reject_f_out, reject_f_ref); + `checkh(reject_g_out_0, reject_g_ref_0); + `checkh(reject_g_out_1, reject_g_ref_1); + `checkh(reject_g_out_2, reject_g_ref_2); + `checkr(reject_h_out, reject_h_ref); + `checks(reject_i_out, reject_i_ref); + + cyc <= cyc + 32'd1; + if (cyc == 32'd32) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_table_tiny_off.py b/test_regress/t/t_case_table_tiny_off.py new file mode 100755 index 000000000..8890dcd07 --- /dev/null +++ b/test_regress/t/t_case_table_tiny_off.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_case_table_tiny.v" + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-case-table']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_unique_overlap.py b/test_regress/t/t_case_unique_overlap.py index 8a938befd..f3945fcdd 100755 --- a/test_regress/t/t_case_unique_overlap.py +++ b/test_regress/t/t_case_unique_overlap.py @@ -11,8 +11,10 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=['--stats']) test.execute() +test.file_grep(test.stats, r'Optimizations, Cases proven assertions\s+(\d+)', 1) + test.passes() diff --git a/test_regress/t/t_class_member_shadow_type.py b/test_regress/t/t_class_member_shadow_type.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_class_member_shadow_type.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_member_shadow_type.v b/test_regress/t/t_class_member_shadow_type.v new file mode 100644 index 000000000..e0d11a0c7 --- /dev/null +++ b/test_regress/t/t_class_member_shadow_type.v @@ -0,0 +1,53 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// A data member/variable named identically to a type from an enclosing scope +// must resolve its type specifier against the enclosing-scope type, not against +// the just-declared variable (IEEE 1800-2023 6.18). This is the UVM RAL / +// peakrdl-regblock pattern: 'rand ;'. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Tom Jackson +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef logic [7:0] my_t; // type at $unit scope + +class C; + my_t my_t; // member named the same as its (outer-scope) type +endclass + +class D; + my_t a; // second use of the type name after the shadowing variable... + my_t my_t; // ... is also legal; both resolve to the $unit typedef +endclass + +// The exact RAL shape: class member named after its class type +class my_blk; + int x; +endclass + +class parent; + rand my_blk my_blk; +endclass + +module t; + my_t my_t; // also legal at module scope + + initial begin + static C c = new; + static parent p = new; + c.my_t = 8'hAB; + p.my_blk = new; + p.my_blk.x = 5; + my_t = 8'h12; + `checkh(c.my_t, 8'hAB); + `checkh(p.my_blk.x, 5); + `checkh(my_t, 8'h12); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_nested_split_var.py b/test_regress/t/t_class_nested_split_var.py new file mode 100755 index 000000000..c87a1e49f --- /dev/null +++ b/test_regress/t/t_class_nested_split_var.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_nested_split_var.v b/test_regress/t/t_class_nested_split_var.v new file mode 100644 index 000000000..c4124f775 --- /dev/null +++ b/test_regress/t/t_class_nested_split_var.v @@ -0,0 +1,40 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +`ifdef VERILATOR +// The '$c(1)' is there to prevent inlining of the signal by V3Gate. +`define IMPURE_ONE ($c(1)) +`else +// Use standard $random. The chance of getting 2 consecutive zeroes is negligible. +`define IMPURE_ONE (|($random | $random)) +`endif + +module t; + bit [2:0] y; + bit [2:0] z; + assign z[0] = 1'b1; + assign z[1] = !(y[0]); + assign z[2] = !(|y[1:0]); + class Foo; + bit foo; + + task run(); + foo = `IMPURE_ONE; + if (z !== 3'b001) begin + $error("Failed: got %0b, expected %0b", z, 3'b001); + end + if (foo != 1'b1) $stop; + endtask + endclass + Foo test; + initial begin + static Foo foo = new; + #10 y = 3'b111; + #1 foo.run(); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_dist.v b/test_regress/t/t_constraint_dist.v index 7d27d8748..3b62845a3 100644 --- a/test_regress/t/t_constraint_dist.v +++ b/test_regress/t/t_constraint_dist.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2024 Antmicro Ltd +// SPDX-FileCopyrightText: 2026 Antmicro // SPDX-License-Identifier: CC0-1.0 `define check_rand(cl, field, cond) \ @@ -11,7 +11,7 @@ begin \ if (!bit'(cl.randomize())) $stop; \ prev_result = longint'(field); \ if (!(cond)) $stop; \ - repeat(9) begin \ + repeat(100) begin \ longint result; \ if (!bit'(cl.randomize())) $stop; \ result = longint'(field); \ @@ -33,8 +33,12 @@ class C; }; constraint distinside { z dist {que}; - w dist {arr}; - }; + w dist {arr}; }; endclass + +class DistNarrow; + rand bit [3:0] x; + constraint c1 { x dist {[4'd1:4'd9] := 1}; } + constraint c2 { x > 4'd5; } endclass module t; @@ -45,6 +49,11 @@ module t; `check_rand(c, c.y, 5 <= c.y && c.y <= 6); `check_rand(c, c.z, 3 <= c.z && c.z <= 5); `check_rand(c, c.w, 5 <= c.w && c.w <= 7); + begin + DistNarrow dn; + dn = new; + `check_rand(dn, dn.x, 6 <= dn.x && dn.x <= 9); + end $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_constraint_dist_foreach_if.py b/test_regress/t/t_constraint_dist_foreach_if.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_foreach_if.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_dist_foreach_if.v b/test_regress/t/t_constraint_dist_foreach_if.v new file mode 100644 index 000000000..3ae315711 --- /dev/null +++ b/test_regress/t/t_constraint_dist_foreach_if.v @@ -0,0 +1,133 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// Test that dist constraints nested inside if / -> inside foreach produce +// values only within the declared distribution and cover all buckets. + +// verilog_format: off +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); +// verilog_format: on + +// foreach (a[i]) if (gate) a[i] dist {...} +class ClsIf; + rand bit [3:0] a [4]; + bit gate; + constraint c { + foreach (a[i]) { + if (gate == 1'b1) { + a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 }; + } + } + } +endclass + +// foreach (a[i]) gate -> a[i] dist {...} +class ClsImpl; + rand bit [3:0] a [4]; + bit gate; + constraint c { + foreach (a[i]) { + gate -> (a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 }); + } + } +endclass + +// foreach (a[i]) gateA -> (gateB -> a[i] dist {...}) -- doubly-nested implication +class ClsImplChained; + rand bit [3:0] a [4]; + bit gateA, gateB; + constraint c { + foreach (a[i]) { + gateA -> (gateB -> (a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 })); + } + } +endclass + +module t; + initial begin + // Test if form + begin + static ClsIf obj = new(); + int seen_zero, seen_nonzero; + obj.gate = 1'b1; + seen_zero = 0; + seen_nonzero = 0; + repeat (100) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4) begin + $write("%%Error: %s:%0d: if: value out of dist range: %0d\n", + `__FILE__, `__LINE__, obj.a[i]); + $stop; + end + if (obj.a[i] == 0) seen_zero++; + else seen_nonzero++; + end + end + if (seen_zero == 0 || seen_nonzero == 0) begin + $write("%%Error: %s:%0d: dist inside foreach+if: not all buckets hit (zero=%0d nonzero=%0d)\n", + `__FILE__, `__LINE__, seen_zero, seen_nonzero); + $stop; + end + end + + // Test -> (implication) form + begin + static ClsImpl obj = new(); + int seen_zero, seen_nonzero; + obj.gate = 1'b1; + seen_zero = 0; + seen_nonzero = 0; + repeat (100) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4) begin + $write("%%Error: %s:%0d: ->: value out of dist range: %0d\n", + `__FILE__, `__LINE__, obj.a[i]); + $stop; + end + if (obj.a[i] == 0) seen_zero++; + else seen_nonzero++; + end + end + if (seen_zero == 0 || seen_nonzero == 0) begin + $write("%%Error: %s:%0d: dist inside foreach+->: not all buckets hit (zero=%0d nonzero=%0d)\n", + `__FILE__, `__LINE__, seen_zero, seen_nonzero); + $stop; + end + end + + // Test doubly-nested -> (chained implication) form + begin + static ClsImplChained obj = new(); + int seen_zero, seen_nonzero; + obj.gateA = 1'b1; + obj.gateB = 1'b1; + seen_zero = 0; + seen_nonzero = 0; + repeat (100) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4) begin + $write("%%Error: %s:%0d: ->->: value out of dist range: %0d\n", + `__FILE__, `__LINE__, obj.a[i]); + $stop; + end + if (obj.a[i] == 0) seen_zero++; + else seen_nonzero++; + end + end + if (seen_zero == 0 || seen_nonzero == 0) begin + $write("%%Error: %s:%0d: dist inside foreach+->->: not all buckets hit (zero=%0d nonzero=%0d)\n", + `__FILE__, `__LINE__, seen_zero, seen_nonzero); + $stop; + end + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_dist_range.py b/test_regress/t/t_constraint_dist_range.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_range.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_dist_range.v b/test_regress/t/t_constraint_dist_range.v new file mode 100644 index 000000000..fbddaf0a1 --- /dev/null +++ b/test_regress/t/t_constraint_dist_range.v @@ -0,0 +1,179 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0); +`define check_tol(gotv,expv) `check_range((gotv), (expv)*(100-TOL_PCT)/100, (expv)*(100+TOL_PCT)/100) +// verilog_format: on + +// Scalar: uniform over [0:9] (10% each) +class DistScalarRange; + rand bit [3:0] x; + constraint c { x dist {[4'd0:4'd9] := 1}; } +endclass + +// Array foreach: uniform over [0:4] (20% each per element) +class DistForeachUniform; + rand bit [2:0] a[5]; + constraint c { foreach (a[i]) a[i] dist {[3'd0:3'd4] := 1}; } +endclass + +// Array foreach: mixed single + range +// a[i] dist {0 := 5, [1:9] := 1} total weight = 5+9 = 14 +// 0: 5/14 ~= 35.7%, 1..9: 1/14 ~= 7.1% each +class DistForeachMixed; + rand bit [3:0] a[5]; + constraint c { foreach (a[i]) a[i] dist {4'd0 := 5, [4'd1:4'd9] := 1}; } +endclass + +// Scalar signed int: uniform over negative range [-9:0] (10% each) +class DistNegRange; + rand int x; + constraint c { x dist {[-9:0] := 1}; } +endclass + +// Non-constant unsigned range bounds: uniform over [lo_val:hi_val] = [2:7] (6 values) +class DistVarRangeUnsigned; + rand bit [3:0] x; + bit [3:0] lo_val = 4'd2, hi_val = 4'd7; + constraint c { x dist {[lo_val:hi_val] := 1}; } +endclass + +// Mixed const/non-const bounds: lo is constant, hi is a variable [1:hi_val] = [1:7] +class DistMixedBounds; + rand bit [3:0] x; + bit [3:0] hi_val = 4'd7; + constraint c { x dist {[4'd1:hi_val] := 1}; } +endclass + +module t; + parameter int N = 2000; // randomize() calls per test + parameter int TOL_PCT = 30; // +-% tolerance on expected counts + + initial begin + + // --- T1: scalar uniform [0:9] --- + // 10 values, expected N/10 each + begin + automatic DistScalarRange obj = new(); + int cnt[10]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x > 4'd9) begin + $write("%%Error: x=%0d outside valid range [0:9]\n", obj.x); + `stop; + end + cnt[obj.x]++; + end + foreach (cnt[v]) + `check_tol(cnt[v], N/10) + end + + // --- T2: array foreach uniform [0:4], 5 elements * N calls --- + // total element samples = N*5; 5 values -> expected N each + begin + automatic DistForeachUniform obj = new(); + int cnt[5]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 3'd4) begin + $write("%%Error: a[%0d]=%0d outside valid range [0:4]\n", i, obj.a[i]); + `stop; + end + cnt[obj.a[i]]++; + end + end + foreach (cnt[v]) + `check_tol(cnt[v], N) + end + + // --- T3: array foreach mixed {0:=5, [1:9]:=1}, 5 elements * N calls --- + // total element samples = N*5; total weight = 5+9 = 14 + // v=0: expected N*5*5/14 + // v=1..9: expected N*5*1/14 + begin + automatic DistForeachMixed obj = new(); + int cnt[10]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4'd9) begin + $write("%%Error: a[%0d]=%0d outside valid range [0:9]\n", i, obj.a[i]); + `stop; + end + cnt[obj.a[i]]++; + end + end + `check_tol(cnt[0], N*5*5/14) + for (int v = 1; v <= 9; v++) + `check_tol(cnt[v], N*5*1/14) + end + + // --- T4: signed int, uniform over negative range [-9:0] --- + // 10 values, expected N/10 each + // cnt[v] = count of (x == v-9), so v=0 -> x=-9, v=9 -> x=0 + begin + automatic DistNegRange obj = new(); + int cnt[10]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x < -9 || obj.x > 0) begin + $write("%%Error: x=%0d outside valid range [-9:0]\n", obj.x); + `stop; + end + cnt[obj.x + 9]++; + end + foreach (cnt[v]) + `check_tol(cnt[v], N/10) + end + + // --- T5: non-constant unsigned range bounds [lo_val:hi_val] = [2:7] --- + // 6 values, expected N/6 each + begin + automatic DistVarRangeUnsigned obj = new(); + int cnt[16]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x < 4'd2 || obj.x > 4'd7) begin + $write("%%Error: x=%0d outside valid range [2:7]\n", obj.x); + `stop; + end + cnt[obj.x]++; + end + for (int v = 2; v <= 7; v++) + `check_tol(cnt[v], N/6) + end + + // --- T6: mixed const/non-const bounds [4'd1:hi_val] = [1:7] --- + // 7 values, expected N/7 each + begin + automatic DistMixedBounds obj = new(); + int cnt[16]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x < 4'd1 || obj.x > 4'd7) begin + $write("%%Error: x=%0d outside valid range [1:7]\n", obj.x); + `stop; + end + cnt[obj.x]++; + end + for (int v = 1; v <= 7; v++) + `check_tol(cnt[v], N/7) + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_global_cls_arr.py b/test_regress/t/t_constraint_global_cls_arr.py new file mode 100755 index 000000000..8862c2c31 --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_global_cls_arr.v b/test_regress/t/t_constraint_global_cls_arr.v new file mode 100644 index 000000000..1c9c3d6d4 --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +class Foo; + rand int abcd; +endclass + +class Bar; + rand Foo foo_arr[]; + + function new(); + foo_arr = new[12]; + foreach (foo_arr[i]) foo_arr[i] = new; + endfunction + + constraint c { + foo_arr.size() == 10; + foreach (foo_arr[i]) foo_arr[i].abcd < 8; + } +endclass + +module t; + Bar bar; + initial begin + bar = new(); + bar.randomize(); + if (bar.foo_arr.size() != 10) $stop; + foreach (bar.foo_arr[i]) begin + if (bar.foo_arr[i] >= 8) $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.out b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.out new file mode 100644 index 000000000..9d049fc2e --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_constraint_global_cls_arr_2d_unsup.v:13:12: Unsupported: Nested array element access in global constraint + : ... note: In instance 't' + 13 | rand Foo foo_arr[$][]; + | ^~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.py b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.py new file mode 100755 index 000000000..4cebd5d8e --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v new file mode 100644 index 000000000..46be2a8f3 --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +class Foo; + rand int abcd; + constraint c {abcd >= 2;} +endclass + +class Bar; + rand Foo foo_arr[$][]; + + function new(); + for (int i = 0; i < 3; i++) foo_arr[i] = new[5]; + foreach (foo_arr[i, j]) foo_arr[i][j] = new; + endfunction + + constraint c { + foo_arr.size() == 10; + foreach (foo_arr[i, j]) foo_arr[i][j].abcd < 8; + } +endclass + +module t; + Bar bar; + initial begin + bar = new(); + void'(bar.randomize()); + if (bar.foo_arr.size() != 10) $stop; + foreach (bar.foo_arr[i, j]) begin + if (bar.foo_arr[i][j].abcd < 2 || bar.foo_arr[i][j].abcd >= 8) $stop; + end + for (int i = 3; i < 10; i++) begin + if (bar.foo_arr[i].size() != 0) $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_json_only.out b/test_regress/t/t_constraint_json_only.out index ce4b7e0d1..c18d8a989 100644 --- a/test_regress/t/t_constraint_json_only.out +++ b/test_regress/t/t_constraint_json_only.out @@ -29,7 +29,7 @@ {"type":"VAR","name":"state","addr":"(Z)","loc":"d,17:10,17:15","dtypep":"(M)","origName":"state","verilogName":"state","direction":"NONE","lifetime":"VAUTOMI","varType":"MEMBER","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"FUNC","name":"strings_equal","addr":"(AB)","loc":"d,61:16,61:29","dtypep":"(U)","method":true,"cname":"strings_equal", "fvarp": [ - {"type":"VAR","name":"strings_equal","addr":"(BB)","loc":"d,61:16,61:29","dtypep":"(U)","origName":"strings_equal","verilogName":"strings_equal","direction":"OUTPUT","noCReset":true,"isFuncReturn":true,"isFuncLocal":true,"lifetime":"VAUTOM","varType":"VAR","dtypeName":"bit","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} + {"type":"VAR","name":"strings_equal","addr":"(BB)","loc":"d,61:16,61:29","dtypep":"(U)","origName":"strings_equal","verilogName":"strings_equal","direction":"OUTPUT","noCReset":true,"icoMaybeWritten":true,"isFuncReturn":true,"isFuncLocal":true,"lifetime":"VAUTOM","varType":"VAR","dtypeName":"bit","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} ],"classOrPackagep": [], "stmtsp": [ {"type":"VAR","name":"a","addr":"(CB)","loc":"d,61:37,61:38","dtypep":"(M)","origName":"a","verilogName":"a","direction":"INPUT","isFuncLocal":true,"lifetime":"VAUTOMI","varType":"PORT","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, diff --git a/test_regress/t/t_constraint_redops.py b/test_regress/t/t_constraint_redops.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_redops.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_redops.v b/test_regress/t/t_constraint_redops.v new file mode 100644 index 000000000..5eff12a63 --- /dev/null +++ b/test_regress/t/t_constraint_redops.v @@ -0,0 +1,123 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 by Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// Test case for reducing and in constraint +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_rand(cl, field, gotv, expv, count) \ +begin \ + automatic longint prev_result; \ + automatic int ok; \ + if (!bit'(cl.randomize())) $stop; \ + prev_result = longint'(field); \ + `checkd(gotv, expv) \ + repeat(count) begin \ + longint result; \ + if (!bit'(cl.randomize())) $stop; \ + result = longint'(field); \ + `checkd(gotv, expv) \ + if (result != prev_result) ok = 1; \ + prev_result = result; \ + end \ + if (ok != 1) $stop; \ +end +// verilog_format: on + +class test_redops_bitfields #(RANDVAL_BITWIDTH=8); + rand bit [RANDVAL_BITWIDTH-1:0] rand_val; + rand bit redand; + rand bit redxor; + rand bit redor; + + constraint c { + redand == &rand_val; + } + + constraint d { + redxor == ^rand_val; + } + + constraint e { + redor == |rand_val; + } + + function bit calc_redand(); + bit result = 1'b1; + + foreach (rand_val[idx]) begin + result &= rand_val[idx]; + end + + return result; + endfunction + + function bit calc_redxor(); + bit result; + + foreach (rand_val[idx]) begin + result ^= rand_val[idx]; + end + + return result; + endfunction + + function bit calc_redor(); + bit result = 1'b0; + + foreach (rand_val[idx]) begin + result |= rand_val[idx]; + end + + return result; + endfunction + + function void verify(); + //`check_rand(this, this.rand_val, this.redand, this.calc_redand(), 20); + `check_rand(this, this.rand_val, this.redxor, this.calc_redxor(), 20); + //`check_rand(this, this.rand_val, this.redor, this.calc_redor(), 20); + endfunction +endclass + +module t; + test_redops_bitfields #(.RANDVAL_BITWIDTH(1)) redops_1bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(8)) redops_8bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(16)) redops_16bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(32)) redops_32bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(47)) redops_47bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(63)) redops_63bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(64)) redops_64bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(128)) redops_128bit; + + initial begin + redops_1bit = new(); + redops_1bit.verify(); + + redops_8bit = new(); + redops_8bit.verify(); + + redops_16bit = new(); + redops_16bit.verify(); + + redops_32bit = new(); + redops_32bit.verify(); + + redops_47bit = new(); + redops_47bit.verify(); + + redops_63bit = new(); + redops_63bit.verify(); + + redops_64bit = new(); + redops_64bit.verify(); + + redops_128bit = new(); + redops_128bit.verify(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_unsup.v b/test_regress/t/t_constraint_unsup.v index 4b1c95031..84f67e5ce 100644 --- a/test_regress/t/t_constraint_unsup.v +++ b/test_regress/t/t_constraint_unsup.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 class Packet; diff --git a/test_regress/t/t_cover_fsm_concat_unsup.out b/test_regress/t/t_cover_fsm_concat_unsup.out new file mode 100644 index 000000000..85da4d440 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.out @@ -0,0 +1,6 @@ +%Warning-COVERIGN: t/t_cover_fsm_concat_unsup.v:12:17: Ignoring unsupported: FSM coverage with CONCAT + 12 | assign c = ({a, b} == 8'h00); + | ^ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_concat_unsup.py b/test_regress/t/t_cover_fsm_concat_unsup.py new file mode 100755 index 000000000..4a63bc600 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage concat as unsupported operation test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--coverage']) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_concat_unsup.v b/test_regress/t/t_cover_fsm_concat_unsup.v new file mode 100644 index 000000000..a7d9a1f84 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.v @@ -0,0 +1,18 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input logic [6:0] a, + input logic b, + output logic c +); + assign c = ({a, b} == 8'h00); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_cover_fsm_sel.out b/test_regress/t/t_cover_fsm_sel.out new file mode 100644 index 000000000..a87e0569d --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.out @@ -0,0 +1,97 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: Verilog Test module + // + // This file ONLY is placed under the Creative Commons Public Domain + // SPDX-FileCopyrightText: 2026 Antmicro + // SPDX-License-Identifier: CC0-1.0 + + package P; + typedef struct packed {logic [7:0] vs;} C; + typedef struct packed { + C a; + int b; + } B; + typedef struct packed {B a;} A; + endpackage + + module t ( +%000009 input clk + ); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + +%000001 logic rst; +%000001 logic start; + integer cyc; +%000001 state_t state /*verilator fsm_reset_arc*/; +%000002 P::A a; +%000001 logic done; + + logic [7:0] va[int]; + logic [7:0] va2d[int][int]; + +%000001 logic b; +%000001 logic c; +%000001 logic d; + + assign b = (a.a.a.vs == 8'h0); + assign c = (va[0] == 8'h0); + assign d = (va2d[0][0] == 8'h0); + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000009 always @(posedge clk) begin +%000009 cyc <= cyc + 1; +%000008 if (cyc == 1) rst <= 1'b0; +%000008 if (cyc == 2) start <= 1'b1; +%000008 if (cyc == 3) start <= 1'b0; +%000008 if (cyc == 8) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000009 always_ff @(posedge clk) begin +%000007 if (rst) begin +%000002 state <= S_IDLE; + end +%000007 else begin +%000007 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S_DONE->S_DONE] +%000003 // [fsm_arc t.state::S_IDLE->S_IDLE] +%000001 // [fsm_arc t.state::S_IDLE->S_RUN] +%000001 // [fsm_state t.state::S_DONE] +%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED *** +%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED *** +%000001 // [fsm_state t.state::S_RUN] +%000002 S_IDLE: +%000001 if (start) state <= S_RUN; +%000001 else state <= S_IDLE; +%000003 S_RUN: begin +%000003 a.a.a.vs <= a.a.a.vs + 1; +%000003 done <= (a.a.a.vs == 8'h1); +%000002 if (done) begin +%000001 state <= S_DONE; + end +%000002 else begin +%000002 state <= S_RUN; + end + end +%000002 S_DONE: state <= S_DONE; +%000000 default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_sel.py b/test_regress/t/t_cover_fsm_sel.py new file mode 100755 index 000000000..9d066e89e --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage array sel test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import os + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage"]) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel.v b/test_regress/t/t_cover_fsm_sel.v new file mode 100644 index 000000000..732a63553 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.v @@ -0,0 +1,86 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +package P; + typedef struct packed {logic [7:0] vs;} C; + typedef struct packed { + C a; + int b; + } B; + typedef struct packed {B a;} A; +endpackage + +module t ( + input clk +); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + + logic rst; + logic start; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + P::A a; + logic done; + + logic [7:0] va[int]; + logic [7:0] va2d[int][int]; + + logic b; + logic c; + logic d; + + assign b = (a.a.a.vs == 8'h0); + assign c = (va[0] == 8'h0); + assign d = (va2d[0][0] == 8'h0); + + initial begin + rst = 1'b1; + start = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) start <= 1'b1; + if (cyc == 3) start <= 1'b0; + if (cyc == 8) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S_IDLE; + end + else begin + case (state) + S_IDLE: + if (start) state <= S_RUN; + else state <= S_IDLE; + S_RUN: begin + a.a.a.vs <= a.a.a.vs + 1; + done <= (a.a.a.vs == 8'h1); + if (done) begin + state <= S_DONE; + end + else begin + state <= S_RUN; + end + end + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_sel_assign.out b/test_regress/t/t_cover_fsm_sel_assign.out new file mode 100644 index 000000000..1f0c3983e --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.out @@ -0,0 +1,90 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: Verilog Test module + // + // This file ONLY is placed under the Creative Commons Public Domain + // SPDX-FileCopyrightText: 2026 Antmicro + // SPDX-License-Identifier: CC0-1.0 + + module t #( + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 + ) ( +%000009 input clk + ); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + +%000001 logic rst; +%000001 logic start; + integer cyc; +%000001 state_t state /*verilator fsm_reset_arc*/; +%000001 logic [1:0] done_arr; + +%000001 logic [W-1:0] a; +%000000 logic [BW-1:0] b; + begin +%000001 logic [D-1:0][W-1:0] s; + begin +%000009 always_ff @(posedge clk) s[b] <= a; + end + end + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000009 always @(posedge clk) begin +%000009 cyc <= cyc + 1; +%000008 if (cyc == 1) rst <= 1'b0; +%000008 if (cyc == 2) start <= 1'b1; +%000008 if (cyc == 3) start <= 1'b0; +%000008 if (cyc == 4) a[0] = 1'b1; +%000008 if (cyc == 8) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000009 always_ff @(posedge clk) begin +%000007 if (rst) begin +%000002 state <= S_IDLE; + end +%000007 else begin +%000007 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S_DONE->S_DONE] +%000003 // [fsm_arc t.state::S_IDLE->S_IDLE] +%000001 // [fsm_arc t.state::S_IDLE->S_RUN] +%000001 // [fsm_state t.state::S_DONE] +%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED *** +%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED *** +%000001 // [fsm_state t.state::S_RUN] +%000002 S_IDLE: +%000001 if (start) state <= S_RUN; +%000001 else state <= S_IDLE; +%000003 S_RUN: begin + ; +%000003 done_arr[0] <= (a[0] == 1'b1); +%000002 if (done_arr[0]) begin +%000001 state <= S_DONE; + end +%000002 else begin +%000002 state <= S_RUN; + end + end +%000002 S_DONE: state <= S_DONE; +%000000 default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_sel_assign.py b/test_regress/t/t_cover_fsm_sel_assign.py new file mode 100755 index 000000000..728658c89 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage assignment test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import os + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage"]) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel_assign.v b/test_regress/t/t_cover_fsm_sel_assign.v new file mode 100644 index 000000000..9f3ecca24 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.v @@ -0,0 +1,79 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t #( + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 +) ( + input clk +); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + + logic rst; + logic start; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + logic [1:0] done_arr; + + logic [W-1:0] a; + logic [BW-1:0] b; + begin + logic [D-1:0][W-1:0] s; + begin + always_ff @(posedge clk) s[b] <= a; + end + end + + initial begin + rst = 1'b1; + start = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) start <= 1'b1; + if (cyc == 3) start <= 1'b0; + if (cyc == 4) a[0] = 1'b1; + if (cyc == 8) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S_IDLE; + end + else begin + case (state) + S_IDLE: + if (start) state <= S_RUN; + else state <= S_IDLE; + S_RUN: begin + ; + done_arr[0] <= (a[0] == 1'b1); + if (done_arr[0]) begin + state <= S_DONE; + end + else begin + state <= S_RUN; + end + end + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out new file mode 100644 index 000000000..4b76f7298 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out @@ -0,0 +1,11 @@ +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:19:16: Coverage ignored for type ASSOCARRAYDTYPE + : ... note: In instance 't' + 19 | input P::A a, + | ^ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:19:16: Coverage ignored for type WILDCARDARRAYDTYPE + : ... note: In instance 't' + 19 | input P::A a, + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py new file mode 100755 index 000000000..59e566c59 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage ignores associative array selection operators +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--coverage']) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v new file mode 100644 index 000000000..9be3e9a7a --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v @@ -0,0 +1,30 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +package P; + typedef struct { + logic [7:0] va[int]; + logic [7:0] vw[*]; + } C; + typedef struct { + C a; + int b; + } B; + typedef struct {B a;} A; +endpackage +module t ( + input P::A a, + output logic b, + output logic c +); + assign b = (a.a.a.va[0] == 8'h0); + assign c = (a.a.a.vw[0] == 8'h0); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_cover_property.py b/test_regress/t/t_cover_property.py new file mode 100755 index 000000000..ef1f5c1ed --- /dev/null +++ b/test_regress/t/t_cover_property.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage', '--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_cover_property.v b/test_regress/t/t_cover_property.v new file mode 100644 index 000000000..40089451c --- /dev/null +++ b/test_regress/t/t_cover_property.v @@ -0,0 +1,77 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + logic [63:0] crc = 64'h5aef0c8dd70a4497; + logic a, b; + int cyc = 0; + + int n_imp_no = 0; // cover property (a |=> b) -- non-overlapped implication + int n_imp_ov = 0; // cover property (a |-> b) -- overlapped implication + int n_seq = 0; // cover property (a ##1 b) -- identity with |=> + int n_seq0 = 0; // cover property (a ##0 b) -- identity with |-> + int n_bool = 0; // cover property (a) -- bare boolean baseline + int n_named = 0; // cover property (named pr) -- identity with |=> + + default clocking cb @(posedge clk); + endclocking + + assign a = crc[0]; + assign b = crc[5]; + + property pr; + a |=> b; + endproperty + + cp_imp_no : + cover property (a |=> b) n_imp_no++; + cp_imp_ov : + cover property (a |-> b) n_imp_ov++; + cp_seq : + cover property (a ##1 b) n_seq++; + cp_seq0 : + cover property (a ##0 b) n_seq0++; + cp_bool : + cover property (a) n_bool++; + cp_named : + cover property (pr) n_named++; + + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[1] ^ crc[0]}; + if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + final begin + // A cover of an implication counts only non-vacuous matches (IEEE + // 1800-2023 16.15.2): the antecedent must match. So it is identical to the + // corresponding sequence cover, not the vacuous implication value. + `checkd(n_imp_no, n_seq) + `checkd(n_imp_ov, n_seq0) + // A named-property cover lowers the same implication, so it also counts + // non-vacuously (regression guard for the property-inlining path). + `checkd(n_named, n_imp_no) + // Pinned Verilator counts; Questa golden cross-checked. + `checkd(n_imp_no, 28) // Questa: 28 + `checkd(n_imp_ov, 27) // Questa: 27 + `checkd(n_seq, 28) // Questa: 28 + `checkd(n_seq0, 27) // Questa: 27 + `checkd(n_bool, 55) // Questa: 54 + `checkd(n_named, 28) // Questa: 28 + end + +endmodule diff --git a/test_regress/t/t_cover_sequence.py b/test_regress/t/t_cover_sequence.py new file mode 100755 index 000000000..23e13d99b --- /dev/null +++ b/test_regress/t/t_cover_sequence.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing', '--coverage-user']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_cover_sequence.v b/test_regress/t/t_cover_sequence.v new file mode 100644 index 000000000..719a3c1ba --- /dev/null +++ b/test_regress/t/t_cover_sequence.v @@ -0,0 +1,90 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + logic [63:0] crc = 64'h5aef0c8dd70a4497; + logic rst_n = 1'b0; + logic a, b, c, d, e; + int cyc = 0; + + int hit_simple = 0; + int hit_clocked = 0; + int hit_clocked_disable = 0; + int hit_default_disable = 0; + int hit_consrep_range = 0; + int hit_consrep_2 = 0; + int hit_consrep_3 = 0; + + default clocking cb @(posedge clk); + endclocking + + // Non-adjacent CRC bits to avoid LFSR shift correlation + assign a = crc[0]; + assign b = crc[5]; + assign c = crc[10]; + assign d = crc[15]; + assign e = crc[20]; + + // Form 1: cover sequence ( sexpr ) stmt + cover sequence (a | b | c | d | e) hit_simple++; + + // Form 2: cover sequence ( clocking_event sexpr ) stmt + cover sequence (@(posedge clk) (a | b | c | d | e) ##[1:3] b) hit_clocked++; + + // Form 3: cover sequence ( clocking_event disable iff (expr) sexpr ) stmt + cover sequence (@(posedge clk) disable iff (!rst_n) a ##1 b) hit_clocked_disable++; + + // Form 4: cover sequence ( disable iff (expr) sexpr ) stmt + cover sequence (disable iff (!rst_n) a ##1 c) hit_default_disable++; + + // Form 5: consecutive repetition, counted per end-of-match + cover sequence (a [* 2: 3]) hit_consrep_range++; + cover sequence (a [* 2]) hit_consrep_2++; + cover sequence (a [* 3]) hit_consrep_3++; + + always @(posedge clk) begin +`ifdef TEST_VERBOSE + $write("[%0t] cyc=%0d crc=%x a=%b b=%b c=%b d=%b e=%b\n", $time, cyc, crc, a, b, c, d, e); +`endif + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[1] ^ crc[0]}; + if (cyc == 2) rst_n <= 1'b1; + if (cyc == 99) begin + `checkh(crc, 64'h261a9f1371d7aadf); + $finish; + end + end + + // Read the counters in 'final', not the clocked block: a same-cycle read of a + // cover counter races the cover's increment under --threads (vltmt). Verilator + // counts one more end-of-match than Questa 2022.3 on some forms at the + // simulation boundary; the Questa value is noted per check. + final begin +`ifdef TEST_VERBOSE + $write("simple=%0d clocked=%0d clk_dis=%0d def_dis=%0d range=%0d 2=%0d 3=%0d\n", hit_simple, + hit_clocked, hit_clocked_disable, hit_default_disable, hit_consrep_range, hit_consrep_2, + hit_consrep_3); +`endif + `checkd(hit_simple, 96); // Questa: 95 + `checkd(hit_clocked, 149); // Questa: 149 + `checkd(hit_clocked_disable, 28); // Questa: 27 + `checkd(hit_default_disable, 30); // Questa: 30 + `checkd(hit_consrep_2, 30); // Questa: 29 + `checkd(hit_consrep_3, 14); // Questa: 13 + // a[*2:3] == a[*2] or a[*3] (IEEE 1800-2023 16.9.2) + `checkd(hit_consrep_range, hit_consrep_2 + hit_consrep_3); // 44; Questa: 42 + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_cover_sequence_unsup.out b/test_regress/t/t_cover_sequence_unsup.out new file mode 100644 index 000000000..47af703cb --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.out @@ -0,0 +1,18 @@ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:21:33: Ignoring unsupported: cover sequence with a sequence operand of 'or' + 21 | cover sequence ((a ##[1:3] b) or 1'b0); + | ^~ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:24:32: Ignoring unsupported: cover sequence with a sequence operand of 'or' + 24 | cover sequence ((a [* 1: 3]) or 1'b0); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:27:21: Ignoring unsupported: cover sequence with this ranged cycle delay + 27 | cover sequence (a ##[1:2] (b ##1 c)); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:30:21: Ignoring unsupported: cover sequence with this ranged cycle delay + 30 | cover sequence (a ##[1:300] b); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:33:21: Ignoring unsupported: cover sequence with a ranged goto repetition + 33 | cover sequence (a [-> 2: 3]); + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_cover_sequence_unsup.py b/test_regress/t/t_cover_sequence_unsup.py new file mode 100755 index 000000000..56c514bc5 --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.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.lint(expect_filename=test.golden_filename, + verilator_flags2=['--assert --error-limit 1000'], + fails=True) + +test.passes() diff --git a/test_regress/t/t_cover_sequence_unsup.v b/test_regress/t/t_cover_sequence_unsup.v new file mode 100644 index 000000000..b2770a2fa --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + logic a, b, c; + + default clocking cb @(posedge clk); + endclocking + + // cover sequence (IEEE 1800-2023 16.14.3) counts every end-of-match. The + // following forms put a sub-sequence where only its final end is forwarded, + // so they are ignored (COVERIGN) rather than under-counted. + + // Sequence operand of 'or' (ranged cycle delay). + cover sequence ((a ##[1:3] b) or 1'b0); + + // Sequence operand of 'or' (consecutive repetition). + cover sequence ((a [* 1: 3]) or 1'b0); + + // Ranged cycle delay before a multi-cycle sequence. + cover sequence (a ##[1:2] (b ##1 c)); + + // Ranged cycle delay wide enough to use the counter FSM. + cover sequence (a ##[1:300] b); + + // Ranged goto repetition (every M..N-th match is a separate end). + cover sequence (a [-> 2: 3]); + +endmodule diff --git a/test_regress/t/t_covergroup_array_bins.out b/test_regress/t/t_covergroup_array_bins.out index 4c0767563..903c2b307 100644 --- a/test_regress/t/t_covergroup_array_bins.out +++ b/test_regress/t/t_covergroup_array_bins.out @@ -1,4 +1,28 @@ cg.data.grouped: 2 -cg.data.values: 3 -cg2.cp.range_arr: 3 -cg3.cp.range_sized: 3 +cg.data.values[0]: 1 +cg.data.values[1]: 1 +cg.data.values[2]: 1 +cg2.cp.range_arr[0]: 1 +cg2.cp.range_arr[1]: 1 +cg2.cp.range_arr[2]: 1 +cg2.cp.range_arr[3]: 0 +cg3.cp.range_sized[0]: 1 +cg3.cp.range_sized[1]: 1 +cg3.cp.range_sized[2]: 1 +cg3.cp.range_sized[3]: 0 +cg4.cp.all_vals[0]: 1 +cg4.cp.all_vals[1]: 1 +cg4.cp.all_vals[2]: 1 +cg4.cp.all_vals[3]: 0 +cg5.cp.hi_vals[0]: 1 +cg5.cp.hi_vals[1]: 0 +cg6.cp.lo_open[0]: 1 +cg6.cp.lo_open[1]: 0 +cg7.cp.rev[0]: 1 +cg7.cp.rev[1]: 0 +cg8.cp.w[0]: 0 +cg8.cp.w[1]: 1 +cg9.__cross7.cumulative_x_lo [cross]: 1 +cg9.__cross7.ok_x_lo [cross]: 1 +cg9.cpA.ok: 1 +cg9.cpB.lo: 1 diff --git a/test_regress/t/t_covergroup_array_bins.v b/test_regress/t/t_covergroup_array_bins.v index 84033319d..209e1b549 100644 --- a/test_regress/t/t_covergroup_array_bins.v +++ b/test_regress/t/t_covergroup_array_bins.v @@ -13,6 +13,8 @@ module t; bit [7:0] data; + bit [1:0] sel; + bit [63:0] wide; covergroup cg; coverpoint data { @@ -38,14 +40,79 @@ module t; } endgroup + // cg4: array bins with '$' (open range) - '$' resolves to the coverpoint domain max. + // For 2-bit sel, {[0:$]} == {[0:3]}: one bin per value -> 4 bins (issue #7750). + covergroup cg4; + cp: coverpoint sel { + bins all_vals[] = {[0 : $]}; + } + endgroup + + // cg5: lower-open range {[lo:$]} == {[lo:maxVal]} -> bins for 2 and 3 + covergroup cg5; + cp: coverpoint sel { + bins hi_vals[] = {[2 : $]}; + } + endgroup + + // cg6: upper-open range {[$:hi]} == {[0:hi]} -> bins for 0 and 1 + covergroup cg6; + cp: coverpoint sel { + bins lo_open[] = {[$ : 1]}; + } + endgroup + + // cg7: a reversed range {[hi:lo]} (hi 2 bins total. + covergroup cg7; + cp: coverpoint data { + bins rev[] = {[3 : 1], 5, 7}; + } + endgroup + + // cg8: wide (>= 64-bit) coverpoint, exercising the 64-bit domain-max path + covergroup cg8; + cp: coverpoint wide { + bins w[] = {[0 : 1]}; + } + endgroup + + // cg9: two ranges that are each under COVER_BINS_LIMIT (1000) but whose + // cumulative size exceeds it. The first range populates the value list, the + // second trips the running-total guard -> COVERIGN, the whole bin is ignored. + // cpA is crossed, so it is non-convertible and routes through the legacy + // per-bin generateArrayBins() path (exercising its unsupported-bin guard). + covergroup cg9; + cpA: coverpoint wide { + bins cumulative[] = {[0 : 500], [0 : 500]}; + bins ok = {5}; + } + cpB: coverpoint sel { + bins lo = {1}; + } + cross cpA, cpB; + endgroup + initial begin cg cg_inst; cg2 cg2_inst; cg3 cg3_inst; + cg4 cg4_inst; + cg5 cg5_inst; + cg6 cg6_inst; + cg7 cg7_inst; + cg8 cg8_inst; + cg9 cg9_inst; cg_inst = new(); cg2_inst = new(); cg3_inst = new(); + cg4_inst = new(); + cg5_inst = new(); + cg6_inst = new(); + cg7_inst = new(); + cg8_inst = new(); + cg9_inst = new(); // Hit first array bin value (1) data = 1; @@ -94,6 +161,42 @@ module t; cg3_inst.sample(); `checkr(cg3_inst.get_inst_coverage(), 75.0); + // Hit cg4 '$' bins ([0:$] == [0:3], 4 bins): cover 3 of 4 + sel = 0; + cg4_inst.sample(); + `checkr(cg4_inst.get_inst_coverage(), 25.0); + sel = 1; + cg4_inst.sample(); + `checkr(cg4_inst.get_inst_coverage(), 50.0); + sel = 2; + cg4_inst.sample(); + `checkr(cg4_inst.get_inst_coverage(), 75.0); + + // Hit cg5 lower-open bins ([2:$] == [2:3], 2 bins): cover 1 of 2 + sel = 2; + cg5_inst.sample(); + `checkr(cg5_inst.get_inst_coverage(), 50.0); + + // Hit cg6 upper-open bins ([$:1] == [0:1], 2 bins): cover 1 of 2 + sel = 0; + cg6_inst.sample(); + `checkr(cg6_inst.get_inst_coverage(), 50.0); + + // Hit cg7 bins (reversed [3:1] -> no bins; 5 and 7 -> 2 bins): cover 1 of 2 + data = 5; + cg7_inst.sample(); + `checkr(cg7_inst.get_inst_coverage(), 50.0); + + // Hit cg8 wide bins ([0:1], 2 bins): cover 1 of 2 + wide = 1; + cg8_inst.sample(); + `checkr(cg8_inst.get_inst_coverage(), 50.0); + + // Exercise cg9 (crossed cpA with an ignored cumulative array bin, legacy path) + wide = 5; + sel = 1; + cg9_inst.sample(); + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_autobins_bad.out b/test_regress/t/t_covergroup_autobins_bad.out index bb8c92918..2e5bb49e5 100644 --- a/test_regress/t/t_covergroup_autobins_bad.out +++ b/test_regress/t/t_covergroup_autobins_bad.out @@ -1,76 +1,88 @@ -%Error: t/t_covergroup_autobins_bad.v:17:12: Automatic bins array size must be a constant +%Error: t/t_covergroup_autobins_bad.v:18:12: Automatic bins array size must be a constant : ... note: In instance 't' - 17 | bins auto[size_var]; + 18 | bins auto[size_var]; | ^~~~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_covergroup_autobins_bad.v:24:12: Automatic bins array size must be >= 1, got 0 +%Error: t/t_covergroup_autobins_bad.v:25:12: Automatic bins array size must be >= 1, got 0 : ... note: In instance 't' - 24 | bins auto[0]; + 25 | bins auto[0]; | ^~~~ -%Error: t/t_covergroup_autobins_bad.v:31:12: Automatic bins array size of 1001 exceeds limit of 1000 +%Error: t/t_covergroup_autobins_bad.v:32:12: Automatic bins array size of 1001 exceeds limit of 1000 : ... note: In instance 't' - 31 | bins auto[1001]; + 32 | bins auto[1001]; | ^~~~ -%Error: t/t_covergroup_autobins_bad.v:43:26: Non-constant expression in bin value list; values must be constants +%Error: t/t_covergroup_autobins_bad.v:44:26: Non-constant expression in bin value list; values must be constants : ... note: In instance 't' - 43 | ignore_bins ign = {size_var}; + 44 | ignore_bins ign = {size_var}; | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:44:32: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:45:32: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 44 | ignore_bins ign_range = {[0:size_var]}; + 45 | ignore_bins ign_range = {[0:size_var]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:38:12: Non-constant expression in array bins range; range bounds must be constants - : ... note: In instance 't' - 38 | bins b[] = {[size_var:size_var]}; - | ^ %Error: t/t_covergroup_autobins_bad.v:39:12: Non-constant expression in array bins range; range bounds must be constants : ... note: In instance 't' - 39 | bins b_mixed[] = {[0:size_var]}; + 39 | bins b[] = {[size_var:size_var]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:40:12: Non-constant expression in array bins range; range bounds must be constants + : ... note: In instance 't' + 40 | bins b_mixed[] = {[0:size_var]}; | ^~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:40:23: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:41:23: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 40 | bins b_range = {[size_var:4]}; + 41 | bins b_range = {[size_var:4]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:41:24: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:42:24: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 41 | bins b_range2 = {[0:size_var]}; + 42 | bins b_range2 = {[0:size_var]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:42:18: Non-constant expression in bin range; values must be constants +%Error: t/t_covergroup_autobins_bad.v:43:18: Non-constant expression in bin range; values must be constants : ... note: In instance 't' - 42 | bins b2 = {size_var}; + 43 | bins b2 = {size_var}; | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:43:26: Non-constant expression in bin range; values must be constants +%Error: t/t_covergroup_autobins_bad.v:44:26: Non-constant expression in bin range; values must be constants : ... note: In instance 't' - 43 | ignore_bins ign = {size_var}; + 44 | ignore_bins ign = {size_var}; | ^~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:51:25: Ignoring unsupported: non-constant 'option.at_least'; using default value +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:52:25: Ignoring unsupported: non-constant 'option.at_least'; using default value : ... note: In instance 't' - 51 | option.at_least = size_var; + 52 | option.at_least = size_var; | ^~~~~~~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Error: t/t_covergroup_autobins_bad.v:61:31: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:62:31: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 61 | ignore_bins ign_nclo = {[size_var:4]}; + 62 | ignore_bins ign_nclo = {[size_var:4]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:58:20: Four-state (x/z) value in bin range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:59:20: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 58 | bins b_xz = {[4'bxxxx:4'hF]}; + 59 | bins b_xz = {[4'bxxxx:4'hF]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:59:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants - : ... note: In instance 't' - 59 | ignore_bins ign_xz_lo = {[4'bxxxx:4'hF]}; - | ^ %Error: t/t_covergroup_autobins_bad.v:60:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 60 | ignore_bins ign_xz_hi = {[4'h0:4'bzzzz]}; + 60 | ignore_bins ign_xz_lo = {[4'bxxxx:4'hF]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:62:23: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:61:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 62 | bins b_nc_ub = {[size_var:$]}; - | ^ -%Error: t/t_covergroup_autobins_bad.v:63:23: Four-state (x/z) value in bin range bound; range bounds must be two-state constants + 61 | ignore_bins ign_xz_hi = {[4'h0:4'bzzzz]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:63:23: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 63 | bins b_xz_ub = {[4'bxxxx:$]}; + 63 | bins b_nc_ub = {[size_var:$]}; | ^ +%Error: t/t_covergroup_autobins_bad.v:64:23: Four-state (x/z) value in bin range bound; range bounds must be two-state constants + : ... note: In instance 't' + 64 | bins b_xz_ub = {[4'bxxxx:$]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:65:12: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants + : ... note: In instance 't' + 65 | bins b_xz_arr[] = {[4'bxxxx:4'hF]}; + | ^~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:66:12: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants + : ... note: In instance 't' + 66 | bins b_xz_arr_hi[] = {[4'h0:4'bzzzz]}; + | ^~~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:73:12: Unsupported: array 'bins' covering more than 1000 values (e.g. an open '[lo:$]' range over a wide coverpoint); bin ignored + : ... note: In instance 't' + 73 | bins b_huge[] = {[0:$]}; + | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_autobins_bad.v b/test_regress/t/t_covergroup_autobins_bad.v index 8a19c0c8e..db8a48fd5 100644 --- a/test_regress/t/t_covergroup_autobins_bad.v +++ b/test_regress/t/t_covergroup_autobins_bad.v @@ -10,6 +10,7 @@ module t; int size_var; logic [3:0] cp_expr; + logic [15:0] cp_wide; // Error: array size must be a constant covergroup cg1; @@ -61,6 +62,15 @@ module t; ignore_bins ign_nclo = {[size_var:4]}; // non-constant lower bound bins b_nc_ub = {[size_var:$]}; // non-constant lower bound, open-ended '$' upper bins b_xz_ub = {[4'bxxxx:$]}; // four-state lower bound, open-ended '$' upper + bins b_xz_arr[] = {[4'bxxxx:4'hF]}; // four-state lower bound (array-bins path) + bins b_xz_arr_hi[] = {[4'h0:4'bzzzz]}; // four-state upper bound (array-bins path) + } + endgroup + + // Warning (COVERIGN): array bins range exceeds COVER_BINS_LIMIT + covergroup cg6; + cp1: coverpoint cp_wide { + bins b_huge[] = {[0:$]}; // open '[lo:$]' over 16-bit coverpoint exceeds bin limit } endgroup @@ -70,6 +80,7 @@ module t; cg3 cg3_inst = new; cg4 cg4_inst = new; cg5 cg5_inst = new; + cg6 cg6_inst = new; initial $finish; endmodule diff --git a/test_regress/t/t_covergroup_clocked_sample.py b/test_regress/t/t_covergroup_clocked_sample.py index 9f6b5465d..be9b453d3 100755 --- a/test_regress/t/t_covergroup_clocked_sample.py +++ b/test_regress/t/t_covergroup_clocked_sample.py @@ -10,6 +10,7 @@ import vltest_bootstrap import coverage_covergroup_common -test.scenarios('vlt_all') +# Issue #7779 unstable with --vltmt +test.scenarios('vlt') coverage_covergroup_common.run(test) diff --git a/test_regress/t/t_covergroup_cross.out b/test_regress/t/t_covergroup_cross.out index 84d9104d6..a7311da80 100644 --- a/test_regress/t/t_covergroup_cross.out +++ b/test_regress/t/t_covergroup_cross.out @@ -65,6 +65,15 @@ cg_at_least.cp_addr.addr0: 1 cg_at_least.cp_addr.addr1: 1 cg_at_least.cp_cmd.read: 1 cg_at_least.cp_cmd.write: 1 +cg_def_cross.axc.a0_x_read [cross]: 1 +cg_def_cross.axc.a0_x_write [cross]: 0 +cg_def_cross.axc.a1_x_read [cross]: 0 +cg_def_cross.axc.a1_x_write [cross]: 0 +cg_def_cross.cp_a.a0: 1 +cg_def_cross.cp_a.a1: 0 +cg_def_cross.cp_a.ad: 1 +cg_def_cross.cp_c.read: 1 +cg_def_cross.cp_c.write: 1 cg_goal.addr_cmd_goal.addr0_x_read [cross]: 1 cg_goal.addr_cmd_goal.addr0_x_write [cross]: 0 cg_goal.addr_cmd_goal.addr1_x_read [cross]: 0 @@ -82,6 +91,16 @@ cg_ignore.cross_ab.a0_x_read [cross]: 1 cg_ignore.cross_ab.a0_x_write [cross]: 1 cg_ignore.cross_ab.a1_x_read [cross]: 1 cg_ignore.cross_ab.a1_x_write [cross]: 1 +cg_mixed.ab.addr0_x_read [cross]: 1 +cg_mixed.ab.addr0_x_write [cross]: 1 +cg_mixed.ab.addr1_x_read [cross]: 1 +cg_mixed.ab.addr1_x_write [cross]: 1 +cg_mixed.cp_addr.addr0: 2 +cg_mixed.cp_addr.addr1: 2 +cg_mixed.cp_cmd.read: 2 +cg_mixed.cp_cmd.write: 2 +cg_mixed.cp_solo.debug: 2 +cg_mixed.cp_solo.normal: 2 cg_range.addr_cmd_range.hi_range_x_read [cross]: 1 cg_range.addr_cmd_range.hi_range_x_write [cross]: 1 cg_range.addr_cmd_range.lo_range_x_read [cross]: 1 diff --git a/test_regress/t/t_covergroup_cross.v b/test_regress/t/t_covergroup_cross.v index 6245baa35..e86f179a4 100644 --- a/test_regress/t/t_covergroup_cross.v +++ b/test_regress/t/t_covergroup_cross.v @@ -99,6 +99,23 @@ module t; cross cp_a, cp_c; // no label: reported under the default cross name endgroup + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted + // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + covergroup cg_mixed; + cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed + ab: cross cp_addr, cp_cmd; + endgroup + + // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the + // legacy default-bin codegen path that converted coverpoints bypass. + covergroup cg_def_cross; + cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} + cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} + axc: cross cp_a, cp_c; + endgroup + cg2 cg2_inst = new; cg_ignore cg_ignore_inst = new; cg_range cg_range_inst = new; @@ -109,6 +126,8 @@ module t; cg_goal cg_goal_inst = new; cg_unsup_cross_opt cg_unsup_cross_opt_inst = new; cg_unnamed_cross cg_unnamed_cross_inst = new; + cg_mixed cg_mixed_inst = new; + cg_def_cross cg_def_cross_inst = new; initial begin // Sample 2-way: hit all 4 combinations @@ -275,6 +294,23 @@ module t; cg_unnamed_cross_inst.sample(); // a1 x write `checkr(cg_unnamed_cross_inst.get_inst_coverage(), 75.0); + // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) + addr = 0; cmd = 0; mode = 0; + cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) + `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 + addr = 0; cmd = 1; mode = 1; + cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) + addr = 1; cmd = 0; mode = 0; + cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) + addr = 1; cmd = 1; mode = 1; + cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) + `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 + + // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) + mode = 1; + addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read + addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_default_bins.out b/test_regress/t/t_covergroup_default_bins.out index 9e4e7a1c6..e034545b8 100644 --- a/test_regress/t/t_covergroup_default_bins.out +++ b/test_regress/t/t_covergroup_default_bins.out @@ -1,11 +1,11 @@ cg.data.high: 1 cg.data.low: 1 -cg.data.other: 2 -cg2.cp_only_default.all: 4 +cg.data.other [default]: 2 +cg2.cp_only_default.all [default]: 4 cg3.data.bad [ignore]: 1 cg3.data.err [illegal]: 0 cg3.data.normal: 2 -cg3.data.other: 2 +cg3.data.other [default]: 2 cg4.cp_idx.auto_0: 1 cg4.cp_idx.auto_1: 1 cg4.cp_idx.auto_2: 1 diff --git a/test_regress/t/t_covergroup_default_bins.v b/test_regress/t/t_covergroup_default_bins.v index f4ba5148c..c97cf8f66 100644 --- a/test_regress/t/t_covergroup_default_bins.v +++ b/test_regress/t/t_covergroup_default_bins.v @@ -157,17 +157,17 @@ module t; // Sample cg3: verify ignore/illegal bins do not contribute to coverage data = 2; cg3_inst.sample(); // hits normal bin - `checkr(cg3_inst.get_inst_coverage(), 50.0); // 1/2 bins hit (normal) + `checkr(cg3_inst.get_inst_coverage(), 100.0); // 1/1: only 'normal' counts (default/ignore/illegal excluded, LRM 19.5) data = 7; cg3_inst.sample(); // hits normal bin again - `checkr(cg3_inst.get_inst_coverage(), 50.0); // no new bins + `checkr(cg3_inst.get_inst_coverage(), 100.0); // no new normal bins data = 255; - cg3_inst.sample(); // ignore_bins value; Verilator counts it toward default bin - `checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2: Verilator hits 'other' (default) even for ignore_bins + cg3_inst.sample(); // ignore_bins value; recorded but excluded from coverage + `checkr(cg3_inst.get_inst_coverage(), 100.0); // note: do not sample 254 (illegal_bins would cause runtime assertion) data = 100; - cg3_inst.sample(); // hits default (other) bin - `checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2 bins hit + cg3_inst.sample(); // hits default (other) bin; recorded but excluded from coverage + `checkr(cg3_inst.get_inst_coverage(), 100.0); // Sample cg4: auto-bins with one excluded value // idx=2 is in ignore_bins, so auto-bins cover 0, 1, 3 only (3 bins total) diff --git a/test_regress/t/t_covergroup_iff.out b/test_regress/t/t_covergroup_iff.out index 0e28a68cf..69a7711dd 100644 --- a/test_regress/t/t_covergroup_iff.out +++ b/test_regress/t/t_covergroup_iff.out @@ -2,9 +2,11 @@ cg_and.cp_concat.b00: 0 cg_and.cp_concat.b01: 1 cg_and.cp_concat.b10: 0 cg_and.cp_concat.b11: 0 -cg_array_iff.cp.arr: 1 +cg_array_iff.cp.arr[0]: 1 +cg_array_iff.cp.arr[1]: 0 +cg_array_iff.cp.arr[2]: 0 cg_bitw.cp.seven: 1 -cg_default_iff.cp.def: 1 +cg_default_iff.cp.def [default]: 1 cg_default_iff.cp.known: 1 cg_iff.cp_value.disabled_hi: 0 cg_iff.cp_value.disabled_lo: 0 diff --git a/test_regress/t/t_covergroup_iff.v b/test_regress/t/t_covergroup_iff.v index 1816bd327..8bf6e0016 100644 --- a/test_regress/t/t_covergroup_iff.v +++ b/test_regress/t/t_covergroup_iff.v @@ -129,7 +129,7 @@ module t; enable = 1; value = 10; cg2.sample(); // hits 'known' - `checkr(cg2.get_inst_coverage(), 50.0); + `checkr(cg2.get_inst_coverage(), 100.0); // 1/1: only 'known' counts (default excluded, LRM 19.5) value = 99; cg2.sample(); // hits 'def' (default) `checkr(cg2.get_inst_coverage(), 100.0); diff --git a/test_regress/t/t_covergroup_ignore_bins.out b/test_regress/t/t_covergroup_ignore_bins.out index bcd0b72e0..6a1cba188 100644 --- a/test_regress/t/t_covergroup_ignore_bins.out +++ b/test_regress/t/t_covergroup_ignore_bins.out @@ -1,5 +1,7 @@ -cg.data.arr [ignore]: 0 -cg.data.bad [illegal]: 0 +cg.data.arr[0] [ignore]: 0 +cg.data.arr[1] [ignore]: 0 +cg.data.bad[0] [illegal]: 0 +cg.data.bad[1] [illegal]: 0 cg.data.catch_all [ignore]: 0 cg.data.high: 1 cg.data.low: 1 diff --git a/test_regress/t/t_covergroup_illegal_bins.out b/test_regress/t/t_covergroup_illegal_bins.out index d2de75a65..798247e10 100644 --- a/test_regress/t/t_covergroup_illegal_bins.out +++ b/test_regress/t/t_covergroup_illegal_bins.out @@ -2,7 +2,9 @@ cg.data.forbidden [illegal]: 0 cg.data.high: 1 cg.data.low: 1 cg.data.mid: 1 -cg2.cp_arr.bad_arr [illegal]: 0 +cg2.cp_arr.bad_arr[0] [illegal]: 0 +cg2.cp_arr.bad_arr[1] [illegal]: 0 +cg2.cp_arr.bad_arr[2] [illegal]: 0 cg2.cp_arr.ok: 1 cg2.cp_arr.wlib [illegal]: 0 cg2.cp_trans.bad_2step [illegal]: 0 diff --git a/test_regress/t/t_covergroup_param_bins.v b/test_regress/t/t_covergroup_param_bins.v index ab57a4a62..ece6521cb 100644 --- a/test_regress/t/t_covergroup_param_bins.v +++ b/test_regress/t/t_covergroup_param_bins.v @@ -26,10 +26,10 @@ module t #( covergroup cg; cp: coverpoint value { bins negative = {[PMIN : -1]}; // parameter as range lower bound - bins zero = {0}; + bins zero = {0}; bins positive = {[1 : LMAX]}; // localparam as range upper bound - bins maxv = {LMAX}; // localparam as single value - bins minv = {PMIN}; // parameter as single value + bins maxv = {LMAX}; // localparam as single value + bins minv = {PMIN}; // parameter as single value } endgroup diff --git a/test_regress/t/t_covergroup_trans.out b/test_regress/t/t_covergroup_trans.out index f7621e74a..17e059ede 100644 --- a/test_regress/t/t_covergroup_trans.out +++ b/test_regress/t/t_covergroup_trans.out @@ -7,3 +7,5 @@ cg.cp_trans2.trans2: 1 cg.cp_trans2.trans3: 1 cg.cp_trans3.seq_a: 1 cg.cp_trans3.seq_b: 1 +cg_legacy.cp_mix.tr: 1 +cg_legacy.cp_mix.vals: 1 diff --git a/test_regress/t/t_covergroup_trans.v b/test_regress/t/t_covergroup_trans.v index dea93e6c5..f95561ed2 100644 --- a/test_regress/t/t_covergroup_trans.v +++ b/test_regress/t/t_covergroup_trans.v @@ -39,7 +39,17 @@ module t; } endgroup + // Non-convertible coverpoint (it has a transition bin) that also carries a value-array bin, + // so the legacy array codegen path (which converted coverpoints bypass) is still exercised. + covergroup cg_legacy; + cp_mix: coverpoint state { + bins tr = (0 => 1); + bins vals[] = {2, [4:5]}; // discrete + range elements (both legacy array paths) + } + endgroup + cg cg_inst = new; + cg_legacy cg_legacy_inst = new; initial begin // Drive sequence 0->1->2->3->4 which hits all bins @@ -56,6 +66,12 @@ module t; cg_inst.sample(); // 3=>4: seq_b done `checkr(cg_inst.get_inst_coverage(), 100.0); + // cg_legacy: exercise legacy array codegen (non-convertible coverpoint) + state = 0; cg_legacy_inst.sample(); + state = 1; cg_legacy_inst.sample(); // 0=>1: tr + state = 2; cg_legacy_inst.sample(); // vals + state = 3; cg_legacy_inst.sample(); // vals + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index b65ab991e..8c0ad2245 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -364,6 +364,16 @@ module Vt_debug_emitv_t; release sum; end end + sequence s_clocked; + @(posedge clk) in + endsequence + begin : assert_seq_clocked + assert property ( s_clocked() + ) begin + end + else begin + end + end property p; @(posedge clk) ##1 sum[0] endproperty @@ -651,6 +661,11 @@ module Vt_debug_emitv_t; $display("pass"); end end + begin : cover_sequence_concurrent + cover property (@(posedge clk) in##1 in + ) begin + end + end begin : assert_prop_always assert property (@(posedge clk) always ['sh0: 'sh3] in diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index edc1ec87b..813d02ea9 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -286,6 +286,14 @@ module t (/*AUTOARG*/ release sum; end + // verilog_format: off // verible does not support clocking events inside sequence declarations + sequence s_clocked; + @(posedge clk) in + endsequence + // verilog_format: on + + assert_seq_clocked: assert property (s_clocked); + property p; @(posedge clk) ##1 sum[0] endproperty @@ -343,6 +351,8 @@ module t (/*AUTOARG*/ cover_concurrent: cover property(prop); cover_concurrent_stmt: cover property(prop) $display("pass"); + cover_sequence_concurrent: cover sequence (@(posedge clk) in ##1 in); + assert_prop_always: assert property (@(posedge clk) always [0:3] in); assert_prop_s_always: assert property (@(posedge clk) s_always [1:2] in); assert_prop_overlap_impl: assert property (@(posedge clk) in |-> in); diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.out b/test_regress/t/t_default_disable_iff_gen_multi_bad.out new file mode 100644 index 000000000..ed0df293e --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_default_disable_iff_gen_multi_bad.v:15:7: Only one 'default disable iff' allowed per generate block (IEEE 1800-2023 16.15) + : ... note: In instance 't' + 15 | default disable iff (cyc < 7); + | ^~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_unsup.py b/test_regress/t/t_default_disable_iff_gen_multi_bad.py similarity index 74% rename from test_regress/t/t_assert_ctl_unsup.py rename to test_regress/t/t_default_disable_iff_gen_multi_bad.py index da00b062f..38cf36b43 100755 --- a/test_regress/t/t_assert_ctl_unsup.py +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.py @@ -4,13 +4,13 @@ # This program is free software; you can redistribute it and/or modify it # under the terms of either the GNU Lesser General Public License Version 3 # or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap test.scenarios('linter') -test.lint(verilator_flags2=['--assert'], fails=True, expect_filename=test.golden_filename) +test.lint(fails=True, expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.v b/test_regress/t/t_default_disable_iff_gen_multi_bad.v new file mode 100644 index 000000000..61e69830c --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t(input clk); + int cyc; + + default disable iff (cyc < 3); + + generate + begin : g + default disable iff (cyc < 5); + default disable iff (cyc < 7); + + assert property (@(posedge clk) 0); + end + endgenerate +endmodule diff --git a/test_regress/t/t_default_disable_iff_scope.py b/test_regress/t/t_default_disable_iff_scope.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_default_disable_iff_scope.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_default_disable_iff_scope.v b/test_regress/t/t_default_disable_iff_scope.v new file mode 100644 index 000000000..6cdabba82 --- /dev/null +++ b/test_regress/t/t_default_disable_iff_scope.v @@ -0,0 +1,230 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + bit clk; + int cyc; + + always #1 clk = !clk; + + bit rst_mod; + int before_mod_false; + int before_mod_gen_false; + int before_gen_default_false; + int before_gen_child_default_false; + int prog_false_count; + + always_comb rst_mod = (cyc < 3); + + // The default declaration appears later in the module, but still applies here. + assert property (@(posedge clk) 0) + else before_mod_false++; + + generate + begin : g_before_mod_default + // Inherits the module default declaration that appears later in this module. + assert property (@(posedge clk) 0) + else before_mod_gen_false++; + end + + begin : g_before_local_default + // The generate-local default declaration appears later in the block. + assert property (@(posedge clk) 0) + else before_gen_default_false++; + + begin : g_child_inherit + // Inherits the generate-local default declaration that appears later. + assert property (@(posedge clk) 0) + else before_gen_child_default_false++; + end + default disable iff (cyc < 7); + end + endgenerate + + default disable iff (rst_mod); + + generate + begin : g_override + bit rst_gen; + int override_false; + + always_comb rst_gen = (cyc < 5); + + default disable iff (rst_gen); + + assert property (@(posedge clk) 0) + else override_false++; + end + + begin : g_inherit + bit rst_mod; + int inherit_false; + + always_comb rst_mod = (cyc < 8); + + // Inherits the module default, whose rst_mod was resolved in module scope. + assert property (@(posedge clk) 0) + else inherit_false++; + end + endgenerate + + if_scope u_if (.clk(clk), .cyc(cyc)); + p_scope u_prog (.clk(clk), .cyc(cyc), .false_count(prog_false_count)); + examples_with_default_count u_with (.clk(clk), .cyc(cyc)); + examples_without_default_count u_without (.clk(clk), .cyc(cyc)); + examples_with_default u_ieee_with (.a(1'b0), .b(1'b0), .clk(clk), .rst(1'b0), .rst1(1'b0)); + examples_without_default u_ieee_without (.a(1'b0), .b(1'b0), .clk(clk), .rst(1'b0)); + m_override u_m_override (.clk(clk), .cyc(cyc)); + + // The disable iff expression is unsampled, so same-edge updates race in MT simulation. + // Change clk on negedge while the properties are sampled on posedge to avoid races. + always @(negedge clk) begin + cyc++; + if (cyc == 12) begin + `checkd(before_mod_false, 9); + `checkd(before_mod_gen_false, 9); + `checkd(before_gen_default_false, 5); + `checkd(before_gen_child_default_false, 5); + `checkd(g_inherit.inherit_false, 9); + `checkd(g_override.override_false, 7); + `checkd(u_if.false_count, 6); + `checkd(u_if.g_inherit.false_count, 6); + `checkd(prog_false_count, 4); + `checkd(u_with.explicit_assert_false, 5); + `checkd(u_with.explicit_property_false, 5); + `checkd(u_with.inferred_default_false, 9); + `checkd(u_without.explicit_assert_false, 9); + `checkd(u_without.explicit_property_false, 9); + `checkd(u_m_override.false_count, 7); + `checkd(u_m_override.g_inherit_from_module.false_count, 7); + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module examples_with_default (input logic a, b, clk, rst, rst1); +default disable iff rst; +property p1; +disable iff (rst1) a |=> b; +endproperty +// Disable condition is rst1 - explicitly specified within a1 +a1 : assert property (@(posedge clk) disable iff (rst1) a |=> b); +// Disable condition is rst1 - explicitly specified within p1 +a2 : assert property (@(posedge clk) p1); +// Disable condition is rst - no explicit specification, inferred from +// default disable iff declaration +a3 : assert property (@(posedge clk) a |=> b); +// Disable condition is 1'b0. This is the only way to +// cancel the effect of default disable. +a4 : assert property (@(posedge clk) disable iff (1'b0) a |=> b); +endmodule + +module examples_without_default (input logic a, b, clk, rst); +property p2; +disable iff (rst) a |=> b; +endproperty +// Disable condition is rst - explicitly specified within a5 +a5 : assert property (@(posedge clk) disable iff (rst) a |=> b); +// Disable condition is rst - explicitly specified within p2 +a6 : assert property (@ (posedge clk) p2); +// No disable condition +a7 : assert property (@ (posedge clk) a |=> b); +endmodule + +module examples_with_default_count(input bit clk, input int cyc); + int explicit_assert_false; + int explicit_property_false; + int inferred_default_false; + + default disable iff (cyc < 3); + + property p1; + disable iff (cyc < 7) 0; + endproperty + + // Disable condition is explicit in the assertion. + assert property (@(posedge clk) disable iff (cyc < 7) 0) + else explicit_assert_false++; + + // Disable condition is explicit in the property. + assert property (@(posedge clk) p1) + else explicit_property_false++; + + // Disable condition is inferred from the default. + assert property (@(posedge clk) 0) + else inferred_default_false++; + +endmodule + +module examples_without_default_count(input bit clk, input int cyc); + int explicit_assert_false; + int explicit_property_false; + + property p2; + disable iff (cyc < 3) 0; + endproperty + + // Disable condition is explicit in the assertion. + assert property (@(posedge clk) disable iff (cyc < 3) 0) + else explicit_assert_false++; + + // Disable condition is explicit in the property. + assert property (@(posedge clk) p2) + else explicit_property_false++; + +endmodule + +module m_override(input bit clk, input int cyc); + bit rst2; + int false_count; + + always_comb rst2 = (cyc < 5); + default disable iff (rst2); + + assert property (@(posedge clk) 0) + else false_count++; + + generate + begin : g_inherit_from_module + int false_count; + assert property (@(posedge clk) 0) + else false_count++; + end + endgenerate +endmodule + +interface if_scope(input bit clk, input int cyc); + bit rst_if; + int false_count; + + always_comb rst_if = (cyc < 6); + default disable iff (rst_if); + + assert property (@(posedge clk) 0) + else false_count++; + + generate + begin : g_inherit + int false_count; + assert property (@(posedge clk) 0) + else false_count++; + end + endgenerate +endinterface + +program p_scope(input bit clk, input int cyc, + output int false_count); + default disable iff (cyc < 8); + + assert property (@(posedge clk) 0) + else false_count++; +endprogram diff --git a/test_regress/t/t_dfg_break_cycles.py b/test_regress/t/t_dfg_break_cycles.py index 2c35f08f5..ec6c6b339 100755 --- a/test_regress/t/t_dfg_break_cycles.py +++ b/test_regress/t/t_dfg_break_cycles.py @@ -17,6 +17,8 @@ test.sim_time = 2000000 if not os.path.exists(test.root + "/.git"): test.skip("Not in a git repository") +test.top_filename = "t/t_dfg_break_cycles.v" + # Read expected source lines hit expectedLines = set() @@ -65,7 +67,8 @@ with open(rdFile, 'r', encoding="utf8") as rdFh, \ test.compile(verilator_flags2=[ "--stats", "--build", - "-fno-dfg-break-cycles", + "-fno-dfg" if test.name == "t_dfg_break_cycles" else "-fno-dfg-break-cycles", + "-fno-gate", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_ref", "--prefix", "Vref", @@ -73,8 +76,9 @@ test.compile(verilator_flags2=[ ]) # yapf:disable # Check we got the expected number of circular logic warnings -test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt", - r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles) +if test.name == "t_dfg_break_cycles": + test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt", + r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles) # Compile optimized - also builds executable test.compile(verilator_flags2=[ @@ -82,6 +86,7 @@ test.compile(verilator_flags2=[ "--build", "--exe", "-fno-const-before-dfg", + "-fno-gate", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_opt", "--prefix", "Vopt", @@ -90,7 +95,7 @@ test.compile(verilator_flags2=[ "--debug", "--debugi", "0", "--dumpi-tree", "0", "-CFLAGS \"-I .. -I ../obj_ref\"", "../obj_ref/Vref__ALL.a", - "../../t/" + test.name + ".cpp" + "../../t/t_dfg_break_cycles.cpp" ]) # yapf:disable # Execute test to check equivalence diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index 5de4c628b..b2124b1d6 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -124,6 +124,9 @@ module t ( assign SHIFTRS_VARIABLE_4_B = signed'({4'b1111, SHIFTRS_VARIABLE_4_A[1]}) >>> rand_b[0]; assign SHIFTRS_VARIABLE_4_A = rand_a[3:0] ^ SHIFTRS_VARIABLE_4_B[4:1]; + `signal(SHIFTRS_VARIABLE_5, 2); // UNOPTFLAT + assign SHIFTRS_VARIABLE_5 = signed'({rand_a[0], SHIFTRS_VARIABLE_5[1]}) >>> rand_b[0]; + `signal(SHIFTR, 14); // UNOPTFLAT assign SHIFTR = { SHIFTR[6:5], // 13:12 @@ -201,6 +204,16 @@ module t ( assign REPLICATE_4_B = {2{REPLICATE_4_A[1:0]}}; assign REPLICATE_4_A = {REPLICATE_4_B[2:1], rand_a[1:0]}; + function automatic logic [1:0] span_repl(input logic [1:0] x); + logic [3:0] r; + r = {2{x}}; + return r[2:1]; + endfunction + wire logic [3:0] replicate_5_int; // UNOPTFLAT + assign replicate_5_int = {span_repl(replicate_5_int[1:0]), rand_a[1:0]}; + `signal(REPLICATE_5, 4); + assign REPLICATE_5 = replicate_5_int; + `signal(PARTIAL, 4); // UNOPTFLAT assign PARTIAL[0] = rand_a[0]; // PARTIAL[1] intentionally unconnected @@ -219,14 +232,14 @@ module t ( `signal(ARRAY_1, 3); // UNOPTFLAT assign ARRAY_1 = array_1[0]; - wire [2:0] array_2a [2]; - wire [2:0] array_2b [2]; + wire [2:0] array_2a [2]; // UNOPTFLAT + wire [2:0] array_2b [2]; // UNOPTFLAT assign array_2a[0][0] = rand_a[0]; assign array_2a[0][1] = array_2b[1][0]; assign array_2a[0][2] = array_2b[1][1]; assign array_2a[1] = array_2a[0]; assign array_2b = array_2a; - `signal(ARRAY_2, 3); // UNOPTFLAT + `signal(ARRAY_2, 3); assign ARRAY_2 = array_2a[0]; wire [2:0] array_3 [2]; @@ -436,4 +449,86 @@ module t ( assign VOLATILE_ARRAY_IN_CYCLE_1 = volatile_array_in_cycle_1a[1]; // verilator lint_on + ////////////////////////////////////////////////////////////////////////// + // Match masked + ////////////////////////////////////////////////////////////////////////// + + logic [63:0] match_masked; // UNOPTFLAT + always_comb begin + casez (rand_a[31:0]) + 32'b????????_????????_????????_???????1 : match_masked[31:0] = 32'd00; + 32'b????????_????????_????????_??????1? : match_masked[31:0] = 32'd01; + 32'b????????_????????_????????_?????1?? : match_masked[31:0] = 32'd02; + 32'b????????_????????_????????_????1??? : match_masked[31:0] = 32'd03; + 32'b????????_????????_????????_???1???? : match_masked[31:0] = 32'd04; + 32'b????????_????????_????????_??1????? : match_masked[31:0] = 32'd05; + 32'b????????_????????_????????_?1?????? : match_masked[31:0] = 32'd06; + 32'b????????_????????_????????_1??????? : match_masked[31:0] = 32'd07; + 32'b????????_????????_???????1_???????? : match_masked[31:0] = 32'd08; + 32'b????????_????????_??????1?_???????? : match_masked[31:0] = 32'd09; + 32'b????????_????????_?????1??_???????? : match_masked[31:0] = 32'd10; + 32'b????????_????????_????1???_???????? : match_masked[31:0] = 32'd11; + 32'b????????_????????_???1????_???????? : match_masked[31:0] = 32'd12; + 32'b????????_????????_??1?????_???????? : match_masked[31:0] = 32'd13; + 32'b????????_????????_?1??????_???????? : match_masked[31:0] = 32'd14; + 32'b????????_????????_1???????_???????? : match_masked[31:0] = 32'd15; + 32'b????????_???????1_????????_???????? : match_masked[31:0] = 32'd16; + 32'b????????_??????1?_????????_???????? : match_masked[31:0] = 32'd17; + 32'b????????_?????1??_????????_???????? : match_masked[31:0] = 32'd18; + 32'b????????_????1???_????????_???????? : match_masked[31:0] = 32'd19; + 32'b????????_???1????_????????_???????? : match_masked[31:0] = 32'd20; + 32'b????????_??1?????_????????_???????? : match_masked[31:0] = 32'd21; + 32'b????????_?1??????_????????_???????? : match_masked[31:0] = 32'd22; + 32'b????????_1???????_????????_???????? : match_masked[31:0] = 32'd23; + 32'b???????1_????????_????????_???????? : match_masked[31:0] = 32'd24; + 32'b??????1?_????????_????????_???????? : match_masked[31:0] = 32'd25; + 32'b?????1??_????????_????????_???????? : match_masked[31:0] = 32'd26; + 32'b????1???_????????_????????_???????? : match_masked[31:0] = 32'd27; + 32'b???1????_????????_????????_???????? : match_masked[31:0] = 32'd28; + 32'b??1?????_????????_????????_???????? : match_masked[31:0] = 32'd29; + 32'b?1??????_????????_????????_???????? : match_masked[31:0] = 32'd30; + 32'b1???????_????????_????????_???????? : match_masked[31:0] = 32'd31; + default : match_masked[31:0] = '1; + endcase + end + always_comb begin + casez (match_masked[31:0]) + 32'd00 : match_masked[63:32] = 32'b00000000_00000000_00000000_00000001; + 32'd01 : match_masked[63:32] = 32'b00000000_00000000_00000000_00000010; + 32'd02 : match_masked[63:32] = 32'b00000000_00000000_00000000_00000100; + 32'd03 : match_masked[63:32] = 32'b00000000_00000000_00000000_00001000; + 32'd04 : match_masked[63:32] = 32'b00000000_00000000_00000000_00010000; + 32'd05 : match_masked[63:32] = 32'b00000000_00000000_00000000_00100000; + 32'd06 : match_masked[63:32] = 32'b00000000_00000000_00000000_01000000; + 32'd07 : match_masked[63:32] = 32'b00000000_00000000_00000000_10000000; + 32'd08 : match_masked[63:32] = 32'b00000000_00000000_00000001_00000000; + 32'd09 : match_masked[63:32] = 32'b00000000_00000000_00000010_00000000; + 32'd10 : match_masked[63:32] = 32'b00000000_00000000_00000100_00000000; + 32'd11 : match_masked[63:32] = 32'b00000000_00000000_00001000_00000000; + 32'd12 : match_masked[63:32] = 32'b00000000_00000000_00010000_00000000; + 32'd13 : match_masked[63:32] = 32'b00000000_00000000_00100000_00000000; + 32'd14 : match_masked[63:32] = 32'b00000000_00000000_01000000_00000000; + 32'd15 : match_masked[63:32] = 32'b00000000_00000000_10000000_00000000; + 32'd16 : match_masked[63:32] = 32'b00000000_00000001_00000000_00000000; + 32'd17 : match_masked[63:32] = 32'b00000000_00000010_00000000_00000000; + 32'd18 : match_masked[63:32] = 32'b00000000_00000100_00000000_00000000; + 32'd19 : match_masked[63:32] = 32'b00000000_00001000_00000000_00000000; + 32'd20 : match_masked[63:32] = 32'b00000000_00010000_00000000_00000000; + 32'd21 : match_masked[63:32] = 32'b00000000_00100000_00000000_00000000; + 32'd22 : match_masked[63:32] = 32'b00000000_01000000_00000000_00000000; + 32'd23 : match_masked[63:32] = 32'b00000000_10000000_00000000_00000000; + 32'd24 : match_masked[63:32] = 32'b00000001_00000000_00000000_00000000; + 32'd25 : match_masked[63:32] = 32'b00000010_00000000_00000000_00000000; + 32'd26 : match_masked[63:32] = 32'b00000100_00000000_00000000_00000000; + 32'd27 : match_masked[63:32] = 32'b00001000_00000000_00000000_00000000; + 32'd28 : match_masked[63:32] = 32'b00010000_00000000_00000000_00000000; + 32'd29 : match_masked[63:32] = 32'b00100000_00000000_00000000_00000000; + 32'd30 : match_masked[63:32] = 32'b01000000_00000000_00000000_00000000; + 32'd31 : match_masked[63:32] = 32'b10000000_00000000_00000000_00000000; + default: match_masked[63:32] = 32'b00000000_00000000_00000000_00000000; + endcase + end + `signal(MATCH_MASKED, 64); + assign MATCH_MASKED = match_masked; + endmodule diff --git a/test_regress/t/t_dfg_break_cycles_off.py b/test_regress/t/t_dfg_break_cycles_off.py new file mode 100755 index 000000000..bf6af753c --- /dev/null +++ b/test_regress/t/t_dfg_break_cycles_off.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +import runpy + +test.scenarios('vlt_all') + +runpy.run_path("t/t_dfg_break_cycles.py", globals()) diff --git a/test_regress/t/t_dfg_constpool_unused.py b/test_regress/t/t_dfg_constpool_unused.py new file mode 100755 index 000000000..d8194a13e --- /dev/null +++ b/test_regress/t/t_dfg_constpool_unused.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, DFG, Peephole, remove var\s+(\d+)', 2) +test.file_grep_not(test.stats, r'ConstPool, Constants emitted') # Removed by V3Dead later + +test.passes() diff --git a/test_regress/t/t_dfg_constpool_unused.v b/test_regress/t/t_dfg_constpool_unused.v new file mode 100644 index 000000000..339346ffa --- /dev/null +++ b/test_regress/t/t_dfg_constpool_unused.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Converted to case table in const pool, but proven unused by Dfg + logic [15:0] out; + always_comb begin + case (cyc[3:0]) + 4'd0: out = 16'h1111; + 4'd1: out = 16'h2222; + 4'd2: out = 16'h4444; + 4'd3: out = 16'h8888; + default: out = 16'h0f0f; + endcase + end + + // Complicated way to write constant 0 that only Dfg can decipher + wire [63:0] convoluted_zero = (({64{cyc[0]}} & ~{64{cyc[0]}})); + + wire logic [15:0] zero = &convoluted_zero ? out : 16'd0; + + // Test driver/checker + always @(posedge clk) begin + `checkh(zero, 16'd0); + cyc <= cyc + 32'd1; + if (cyc == 32'd32) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 3c9ce7ae8..3e393a297 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -50,6 +50,9 @@ module t ( assign unitArrayParts[0][1] = rand_a[1]; assign unitArrayParts[0][9] = rand_a[9]; + // Complicated way to write constant 0 that only Dfg can decipher + wire [63:0] convoluted_zero = (({64{rand_a[0]}} & ~{64{rand_a[0]}})); + `signal(FOLD_UNARY_LogNot, !const_a[0]); `signal(FOLD_UNARY_Negate, -const_a); `signal(FOLD_UNARY_Not, ~const_a); @@ -133,6 +136,13 @@ module t ( `signal(FOLD_SEL, const_a[3:1]); + int fold_arraysel_table; + ffs ffs_a(convoluted_zero[0] ? 20'hff: 20'd2, fold_arraysel_table); + int fold_matchmasked; + ffs ffs_b(convoluted_zero[1] ? 20'hff: 20'd7, fold_matchmasked); + `signal(FOLD_ARRAYSEL_TABLE, fold_arraysel_table); + `signal(FOLD_MATCHMASKED, fold_matchmasked); + `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, rand_a + ~rand_a); `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rand_b + rand_a); @@ -283,10 +293,20 @@ module t ( `signal(REPLACE_COND_CONST_ZERO_ONAE, rand_a[0] ? 80'b0 : -80'b1); `signal(REPLACE_COND_CAT_LHS_CONST_ONE_ZERO, rand_a[0] ? {8'b1, rand_b[0]} : {8'b0, rand_b[1]}); `signal(REPLACE_COND_CAT_LHS_CONST_ZERO_ONE, rand_a[0] ? {8'b0, rand_b[0]} : {8'b1, rand_b[1]}); - `signal(REPLACE_COND_SAME_CAT_LHS, rand_a[0] ? {8'd0, rand_b[0]} : {8'd0, rand_b[1]}); - `signal(REPLACE_COND_SAME_CAT_RHS, rand_a[0] ? {rand_b[0], 8'd0} : {rand_b[1], 8'd0}); `signal(REPLACE_COND_SAM_COND_THEN, rand_a[0] ? (rand_a[0] ? rand_b[1:0] : rand_b[3:2]) : rand_b[5:4]); `signal(REPLACE_COND_SAM_COND_ELSE, rand_a[0] ? rand_b[1:0] : (rand_a[0] ? rand_b[3:2] : rand_b[5:4])); + + `signal(REPLACE_COND_COMMON_MSBS_A, rand_a[0] ? {8'd0, rand_b[0]} : {8'd0, rand_b[1]}); + `signal(REPLACE_COND_COMMON_MSBS_B, rand_a[0] ? {8'hf0, rand_b[1:0]} : {9'h1e2, rand_b[1]}); + `signal(REPLACE_COND_COMMON_MSBS_C, rand_a[0] ? {rand_a[63 -: 3] , rand_b[0]} : {rand_a[63 -: 2], rand_b[2:1]}); + `signal(REPLACE_COND_COMMON_LSBS_A, rand_a[0] ? {rand_b[0], 8'd0} : {rand_b[1], 8'd0}); + `signal(REPLACE_COND_COMMON_LSBS_B, rand_a[0] ? {rand_b[2:1], 8'h0f} : {rand_b[1], 9'h08f}); + `signal(REPLACE_COND_COMMON_LSBS_C, rand_a[0] ? {rand_b[0], rand_a[3:0]} : {rand_b[1:0], rand_a[2:0]}); + wire [5:0] tmp_REPLACE_COND_COMMON_LSBS_D = rand_b[5:0]; + wire [5:0] tmp_REPLACE_COND_COMMON_MSBS_D = rand_b[63:58]; + `signal(REPLACE_COND_COMMON_LSBS_D, rand_a[0] ? rand_b[4:0] : tmp_REPLACE_COND_COMMON_LSBS_D[4:0]); + `signal(REPLACE_COND_COMMON_MSBS_D, rand_a[0] ? rand_b[63:59] : tmp_REPLACE_COND_COMMON_MSBS_D[5:1]); + `signal(REMOVE_SHIFTL_ZERO, rand_a << 0); `signal(REPLACE_SHIFTL_OVER, rand_a << 64); `signal(REPLACE_SHIFTL_SEL, rand_a[27:0] << 4); @@ -352,7 +372,6 @@ module t ( `signal(REMOVE_EQ_BIT_1, 1'b1 == rand_a[0]); `signal(REMOVE_NEQ_BIT_0, 1'b0 != rand_a[0]); `signal(REPLACE_NEQ_BIT_1, 1'b1 != rand_a[0]); - `signal(REPLACE_COND_INSERT, rand_a[0] ? {rand_b[63:40], {1'd0, rand_b[38:0]}} : rand_b); `signal(REPLACE_REP_REP, {2{({3{rand_a[0]}})}}); // Operators that should work wiht mismatched widths @@ -418,3 +437,35 @@ module t ( assign zero = '0; assign ones = '1; endmodule + +module ffs( + input logic [19:0] i, + output int o +); + // V3Table will convert this + always_comb begin + casez (i) + 20'b1???????????????????: o = 19; + 20'b?1??????????????????: o = 18; + 20'b??1?????????????????: o = 17; + 20'b???1????????????????: o = 16; + 20'b????1???????????????: o = 15; + 20'b?????1??????????????: o = 14; + 20'b??????1?????????????: o = 13; + 20'b???????1????????????: o = 12; + 20'b????????1???????????: o = 11; + 20'b?????????1??????????: o = 10; + 20'b??????????1?????????: o = 9; + 20'b???????????1????????: o = 8; + 20'b????????????1???????: o = 7; + 20'b?????????????1??????: o = 6; + 20'b??????????????1?????: o = 5; + 20'b???????????????1????: o = 4; + 20'b????????????????1???: o = 3; + 20'b?????????????????1??: o = 2; + 20'b??????????????????1?: o = 1; + 20'b???????????????????1: o = 0; + default: o = 32'hffffffff; + endcase + end +endmodule diff --git a/test_regress/t/t_disable_fork_nested.out b/test_regress/t/t_disable_fork_nested.out new file mode 100644 index 000000000..dce504813 --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.out @@ -0,0 +1,3 @@ +Fast clock (200ns half-period): o_counter=7 +Slow clock (5400ns half-period): o_counter=3 +*-* All Finished *-* diff --git a/test_regress/t/t_disable_fork_nested.py b/test_regress/t/t_disable_fork_nested.py new file mode 100755 index 000000000..b716266af --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary", "-Wno-ZERODLY"]) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_disable_fork_nested.v b/test_regress/t/t_disable_fork_nested.v new file mode 100644 index 000000000..de1b2526a --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.v @@ -0,0 +1,89 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Clock-period detector exercising a named-block `disable` of a block whose +// body contains a nested fork..join. Disabling such a block from a sibling +// process must release the enclosing fork..join so the surrounding `always` +// block keeps iterating. + +module disable_fork ( + input logic i_clk, + output logic [2:0] o_counter +); + time delay1 = 500ns; // min period + time delay2 = 3333ns; // max period + + logic clk_re = 1'b0; // rising edge of the clock + logic [2:0] counter = 3'b000; + + always begin + fork + begin : check1 + #delay1; + #1 disable check2; + fork + begin : check3 + #(delay2 - delay1); + clk_re <= 1'b0; + #1 disable check4; + if (counter < 3'b111) counter <= counter + 3'b001; + end + begin : check4 + @(posedge i_clk); + clk_re <= 1'b1; + counter <= 3'b000; + #1 disable check3; + end + join + end + begin : check2 + @(posedge i_clk); + clk_re <= 1'b0; + #1 disable check1; + if (counter < 3'b111) counter <= counter + 3'b001; + end + join + end + + assign o_counter = counter; +endmodule + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk; + logic [2:0] counter; + + task clk_cycle(input time half_period); + clk = 1'b1; + #half_period; + clk = 1'b0; + #half_period; + endtask : clk_cycle + + initial begin + // Fast clock (period below delay1): every edge arrives before the + // min-period timeout, so the counter saturates at its max. + repeat (100) clk_cycle(200ns); + $display("Fast clock (200ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h7); + // Slow clock (period above delay2): the nested fork path runs, which + // only works if disabling check1 releases the inner fork..join. + repeat (100) clk_cycle(5400ns); + $display("Slow clock (5400ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h3); + $write("*-* All Finished *-*\n"); + $finish; + end + + disable_fork a_inst ( + .i_clk(clk), + .o_counter(counter) + ); +endmodule diff --git a/test_regress/t/t_display_merge.py b/test_regress/t/t_display_merge.py index 999f03507..205550676 100755 --- a/test_regress/t/t_display_merge.py +++ b/test_regress/t/t_display_merge.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator_st') -test.compile(verilator_flags2=["--stats", "--inline-cfuncs", "0"]) +test.compile(verilator_flags2=["--stats", "-fno-inline-cfuncs"]) test.execute(expect_filename=test.golden_filename) diff --git a/test_regress/t/t_finaldly_bad.out b/test_regress/t/t_finaldly_bad.out new file mode 100644 index 000000000..01c50dacb --- /dev/null +++ b/test_regress/t/t_finaldly_bad.out @@ -0,0 +1,5 @@ +%Error-FINALDLY: t/t_finaldly_bad.v:9:13: Non-blocking assignment '<=' in final block + 9 | final foo <= 1; + | ^~ + ... For error description see https://verilator.org/warn/FINALDLY?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_finaldly_bad.py b/test_regress/t/t_finaldly_bad.py new file mode 100755 index 000000000..09d3c8e81 --- /dev/null +++ b/test_regress/t/t_finaldly_bad.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.compile(fails=True, expect_filename=test.golden_filename) + +test.extract(in_filename=test.top_filename, + out_filename=test.root + "/docs/gen/ex_FINALDLY_faulty.rst", + lines="8-9") + +test.extract(in_filename=test.golden_filename, + out_filename=test.root + "/docs/gen/ex_FINALDLY_msg.rst", + lines="1-3") + +test.passes() diff --git a/test_regress/t/t_finaldly_bad.v b/test_regress/t/t_finaldly_bad.v new file mode 100644 index 000000000..81b5223ff --- /dev/null +++ b/test_regress/t/t_finaldly_bad.v @@ -0,0 +1,10 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + bit foo; + final foo <= 1; // <--- Error +endmodule diff --git a/test_regress/t/t_flag_csplit_eval.py b/test_regress/t/t_flag_csplit_eval.py index 1b88de3bb..8311f5d9b 100755 --- a/test_regress/t/t_flag_csplit_eval.py +++ b/test_regress/t/t_flag_csplit_eval.py @@ -23,7 +23,7 @@ def check_evals(): test.error("Too few _eval functions found: " + str(got)) -test.compile(v_flags2=["--output-split 1 --output-split-cfuncs 20"], +test.compile(v_flags2=["--output-split 1 --output-split-cfuncs 20 -fno-inline-cfuncs"], verilator_make_gmake=False) # Slow to compile, so skip it) check_evals() diff --git a/test_regress/t/t_flag_expand_limit.py b/test_regress/t/t_flag_expand_limit.py index ec0cc54a6..d9b19b528 100755 --- a/test_regress/t/t_flag_expand_limit.py +++ b/test_regress/t/t_flag_expand_limit.py @@ -13,6 +13,6 @@ test.scenarios('vlt') test.compile(verilator_flags2=['--expand-limit 1 --stats -fno-dfg']) -test.file_grep(test.stats, r'Optimizations, expand limited\s+(\d+)', 7) +test.file_grep(test.stats, r'Optimizations, expand limited\s+(\d+)', 29) test.passes() diff --git a/test_regress/t/t_force_unpacked_bitsel.py b/test_regress/t/t_force_unpacked_bitsel.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_force_unpacked_bitsel.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_force_unpacked_bitsel.v b/test_regress/t/t_force_unpacked_bitsel.v new file mode 100644 index 000000000..402d15f83 --- /dev/null +++ b/test_regress/t/t_force_unpacked_bitsel.v @@ -0,0 +1,133 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Nikolai Kumar +// SPDX-License-Identifier: CC0-1.0 + +`define checkh(g, + e) do if ((g) !==(e)) begin $write("%%Error: %s:%0d: got=%b exp=%b\n", `__FILE__,`__LINE__, (g),(e)); $stop; end while(0) + + +module t ( + input clk +); + int cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + logic [4:1] var_arr[2]; + /* verilator lint_off ASCRANGE */ + logic [1:4] var_arr_a[2]; + /* verilator lint_on ASCRANGE */ + logic [72:1] wide_arr[2]; + + always @(posedge clk) begin + var_arr[0] <= 4'b0101; + var_arr[1] <= (cyc <= 3) ? 4'b1111 : (cyc <= 7) ? 4'b0000 : 4'b0001; + var_arr_a[0] <= 4'b1010; + var_arr_a[1] <= 4'b0000; + wide_arr[0] <= '0; + wide_arr[1] <= 72'hAB_CDEF0123_456789AB; + end + + always @(posedge clk) begin + if (cyc == 2) force var_arr[1][1] = 1'b0; + if (cyc == 6) force var_arr[1][4] = 1'b1; + if (cyc == 8) release var_arr[1][1]; + if (cyc == 10) force var_arr[1] = 4'b1010; + if (cyc == 12) release var_arr[1]; + + if (cyc == 2) force wide_arr[1][36:5] = 32'hffff_ffff; + if (cyc == 4) release wide_arr[1]; + + if (cyc == 2) force var_arr_a[1][2:4] = 3'b111; + if (cyc == 4) force var_arr_a[1][3] = 1'b0; + if (cyc == 6) release var_arr_a[1]; + if (cyc == 7) force var_arr_a[1][3:4] = 2'b11; + if (cyc == 9) force var_arr_a[1][2:3] = 2'b00; + if (cyc == 11) release var_arr_a[1]; + if (cyc == 12) force var_arr_a[1][1:2] = 2'b11; + if (cyc == 14) force var_arr_a[1][2:3] = 2'b00; + if (cyc == 16) release var_arr_a[1]; + if (cyc == 17) force var_arr_a[1][2:3] = 2'b11; + if (cyc == 19) force var_arr_a[1][1:3] = 3'b000; + if (cyc == 21) release var_arr_a[1]; + + if (cyc == 14) force var_arr[1][1] = 1'b0; + if (cyc == 14) force var_arr[0][1] = 1'b0; + if (cyc == 16) release var_arr[1][1]; + if (cyc == 16) release var_arr[0][1]; + if (cyc == 18) force var_arr[0] = 4'b1010; + if (cyc == 20) force var_arr[0][1] = 1'b1; + if (cyc == 22) release var_arr[0]; + end + + always @(posedge clk) + case (cyc) + 1: begin + `checkh(var_arr[0], 4'b0101); + `checkh(var_arr[1], 4'b1111); + end + 3: begin + `checkh(var_arr[1], 4'b1110); + `checkh(wide_arr[1][36:5], 32'hffff_ffff); + `checkh(wide_arr[1][4:1], 4'hB); + `checkh(wide_arr[1][72:37], 36'hABCDEF012); + `checkh(var_arr_a[1], 4'b0111); + `checkh(var_arr_a[0], 4'b1010); + end + 5: begin + `checkh(var_arr[1], 4'b0000); + `checkh(wide_arr[1], 72'hAB_CDEF0123_456789AB); + `checkh(var_arr_a[1], 4'b0101); + end + 7: begin + `checkh(var_arr[1], 4'b1000); + end + 8: begin + `checkh(var_arr_a[1], 4'b0011); + end + 9: begin + `checkh(var_arr[1], 4'b1001); + end + 10: begin + `checkh(var_arr_a[1], 4'b0001); + end + 11: begin + `checkh(var_arr[1], 4'b1010); + `checkh(var_arr[0], 4'b0101); + end + 13: begin + `checkh(var_arr[1], 4'b0001); + `checkh(var_arr_a[1], 4'b1100); + end + 15: begin + `checkh(var_arr_a[1], 4'b1000); + `checkh(var_arr[1], 4'b0000); + `checkh(var_arr[0], 4'b0100); + end + 17: begin + `checkh(var_arr[1], 4'b0001); + `checkh(var_arr[0], 4'b0101); + end + 18: begin + `checkh(var_arr_a[1], 4'b0110); + end + 19: begin + `checkh(var_arr[0], 4'b1010); + end + 20: begin + `checkh(var_arr_a[1], 4'b0000); + end + 21: begin + `checkh(var_arr[0], 4'b1011); + end + 22: begin + `checkh(var_arr_a[1], 4'b0000); + end + 23: begin + `checkh(var_arr[0], 4'b0101); + $write("*-* All Finished *-*\n"); + $finish; + end + endcase +endmodule diff --git a/test_regress/t/t_fork_dynscope_out.py b/test_regress/t/t_fork_dynscope_out.py index f478cb764..7ded63f3a 100755 --- a/test_regress/t/t_fork_dynscope_out.py +++ b/test_regress/t/t_fork_dynscope_out.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--binary -Wno-INITIALDLY"]) +test.compile(verilator_flags2=["--binary"]) test.execute() diff --git a/test_regress/t/t_fsm_duplicate.py b/test_regress/t/t_fsm_duplicate.py new file mode 100755 index 000000000..7f4205522 --- /dev/null +++ b/test_regress/t/t_fsm_duplicate.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM no duplicate variables test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage -Wno-PINMISSING"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fsm_duplicate.v b/test_regress/t/t_fsm_duplicate.v new file mode 100644 index 000000000..c0edc5755 --- /dev/null +++ b/test_regress/t/t_fsm_duplicate.v @@ -0,0 +1,157 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module rr #( +) ( + input logic clk, + input logic rst, + input logic [7:0] data, + input logic data_q +); + logic a; + logic [15:0] dcnt; + typedef enum logic [7:0] { + S0, + S1, + S2, + S3 + } state_t; + state_t state_d, state_q; + always_ff @(posedge clk or negedge rst) if (!rst) state_q <= S0; + always_ff @(posedge clk) + unique case (state_q) + S1: if (a) dcnt[7:0] <= data; + S2: if (a) dcnt[15:8] <= data; + S3: if (data_q) dcnt <= dcnt - 1; + default: dcnt <= dcnt; + endcase +endmodule +module re #( +) ( + input logic clk, + input logic rst, + output logic o, + input unused0, /* block optimizations */ + input unused1, + input unused2, + input unused3, + input unused4, + input unused5, + input unused6, + input unused7, + input unused8, + input unused9, + input unused10, + input unused11, + input unused12, + input unused13, + input unused14, + input unused15, + input unused16, + input unused17, + input unused18, + input unused19, + input unused20, + input unused21, + input unused22, + input unused23, + input unused24, + input unused25, + input unused26, + input unused27, + input unused28, + input unused29, + input unused30, + input unused31, + input unused32, + input unused33, + input unused34, + input unused35, + input unused36, + input unused37, + input unused38, + input unused39, + input unused40 +); + logic [15:0] dcnt; + typedef enum logic [7:0] { + S0, + S1 + } state_t; + state_t state_d, state_q; + always_ff @(posedge clk or negedge rst) if (!rst) state_q <= S0; + always_ff @(posedge clk) + unique case (state_q) + S1: o <= dcnt[0]; + default: o <= '0; + endcase + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule +module rh #( +) ( + input logic clk +); + logic [7:0] a; + logic b; + logic c; + logic d; + logic rst; + rr xrr ( + .clk, + .rst(rst), + .data(a), + .data_q(b & c) + ); + re xre ( + .clk, + .rst(rst), + .o(d) + ); +endmodule +module U #( +) ( + input clk, + input rst +); + rh xrh (.clk(clk)); +endmodule +module C #( +) ( + input clk, + input rst +); + U U ( + .clk, + .rst + ); +endmodule +module A #() (); + logic clk; + logic rst; + C c0 ( + .clk, + .rst + ); + C c1 ( + .clk, + .rst + ); +endmodule +module B #() (); + logic clk; + logic rst; + C xC ( + .clk, + .rst + ); +endmodule +module t #() (); + B b (); + A a (); +endmodule diff --git a/test_regress/t/t_gate_chained.py b/test_regress/t/t_gate_chained.py index 7fa18e3a3..ddf8bef62 100755 --- a/test_regress/t/t_gate_chained.py +++ b/test_regress/t/t_gate_chained.py @@ -48,6 +48,6 @@ test.compile( test.execute() # Must be <<9000 above to prove this worked -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 8550) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 8550) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_exclude_multiple.py b/test_regress/t/t_gate_inline_wide_exclude_multiple.py index 76a8991cf..2bf5b1241 100755 --- a/test_regress/t/t_gate_inline_wide_exclude_multiple.py +++ b/test_regress/t/t_gate_inline_wide_exclude_multiple.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 2) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 4) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py index 8c99e4b33..15370ae7c 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-dfg']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_const.py b/test_regress/t/t_gate_inline_wide_noexclude_const.py index 5102631f7..cb6511e21 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_const.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_const.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 2) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v b/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v deleted file mode 100644 index c70022d24..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v +++ /dev/null @@ -1,34 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -localparam N = 256; // Wider than expand limit. - -module t ( - input wire [N-1:0] i, - output wire [N-1:0] o -); - - // Do not exclude from inlining wides referenced in different scope. - wire [N-1:0] wide = N ~^ i; - - sub sub ( - i, - wide, - o - ); -endmodule - -module sub ( - input wire [N-1:0] i, - input wire [N-1:0] wide, - output logic [N-1:0] o -); - initial begin - for (integer n = 0; n < N; ++n) begin - o[n] = i[N-1-n] | wide[N-1-n]; - end - end -endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_sel.py b/test_regress/t/t_gate_inline_wide_noexclude_sel.py index aa65429ab..41d13c55d 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_sel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_sel.py @@ -13,8 +13,8 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-var-split']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 1) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 1) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v b/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v deleted file mode 100644 index 1b1231c4e..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v +++ /dev/null @@ -1,21 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -localparam N = 65; // Wide but narrower than expand limit - -module t ( - input wire [N-1:0] i, - output wire [N-1:0] o -); - - // Do not exclude from inlining wides small enough to be handled by - // V3Expand. - wire [65:0] wide_small = N << i * i / N; - - for (genvar n = 0; n < N; ++n) begin - assign o[n] = i[n] ^ wide_small[n]; - end -endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_varref.py b/test_regress/t/t_gate_inline_wide_noexclude_varref.py index 1d91e6493..282e88b84 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_varref.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_varref.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_hier_block_chained.py b/test_regress/t/t_hier_block_chained.py index fb17db8e6..41e89faab 100755 --- a/test_regress/t/t_hier_block_chained.py +++ b/test_regress/t/t_hier_block_chained.py @@ -31,9 +31,9 @@ test.compile(v_flags2=[ if test.vltmt: test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule count\s+(\d+)', 3) + r'Optimizations, Thread schedule count\s+(\d+)', 4) test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule total tasks\s+(\d+)', 5) + r'Optimizations, Thread schedule total tasks\s+(\d+)', 6) test.execute() diff --git a/test_regress/t/t_hier_block_perf.py b/test_regress/t/t_hier_block_perf.py index d91e3475e..ea6d40c8b 100755 --- a/test_regress/t/t_hier_block_perf.py +++ b/test_regress/t/t_hier_block_perf.py @@ -35,9 +35,9 @@ test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "_ if test.vltmt: test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule count\s+(\d+)', 1) + r'Optimizations, Thread schedule count\s+(\d+)', 2) test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule total tasks\s+(\d+)', 2) + r'Optimizations, Thread schedule total tasks\s+(\d+)', 3) test.execute(all_run_flags=[ "+verilator+prof+exec+start+2", diff --git a/test_regress/t/t_initial_dlyass.py b/test_regress/t/t_initial_dlyass.py index 99d3b795b..3cc73805c 100755 --- a/test_regress/t/t_initial_dlyass.py +++ b/test_regress/t/t_initial_dlyass.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=['-Wno-INITIALDLY']) +test.compile() test.execute() diff --git a/test_regress/t/t_initial_dlyass_bad.out b/test_regress/t/t_initial_dlyass_bad.out deleted file mode 100644 index 54d3da81c..000000000 --- a/test_regress/t/t_initial_dlyass_bad.out +++ /dev/null @@ -1,11 +0,0 @@ -%Warning-INITIALDLY: t/t_initial_dlyass.v:17:7: Non-blocking assignment '<=' in initial/final block - : ... This will be executed as a blocking assignment '='! - 17 | a <= 22; - | ^~ - ... For warning description see https://verilator.org/warn/INITIALDLY?v=latest - ... Use "/* verilator lint_off INITIALDLY */" and lint_on around source to disable this message. -%Warning-INITIALDLY: t/t_initial_dlyass.v:18:7: Non-blocking assignment '<=' in initial/final block - : ... This will be executed as a blocking assignment '='! - 18 | b <= 33; - | ^~ -%Error: Exiting due to diff --git a/test_regress/t/t_inst_tree_inl0_pub1.py b/test_regress/t/t_inst_tree_inl0_pub1.py index 31d27a095..be831d2c5 100755 --- a/test_regress/t/t_inst_tree_inl0_pub1.py +++ b/test_regress/t/t_inst_tree_inl0_pub1.py @@ -14,8 +14,8 @@ test.top_filename = "t/t_inst_tree.v" default_vltmt_threads = test.get_default_vltmt_threads test.compile( - # Disable --inline-cfuncs so functions exist to be combined - verilator_flags2=['--stats', '--inline-cfuncs', '0', test.t_dir + "/" + test.name + ".vlt"], + # Disable CFunc inlining so functions exist to be combined + verilator_flags2=['--stats', '-fno-inline-cfuncs', test.t_dir + "/" + test.name + ".vlt"], # Force 3 threads even if we have fewer cores threads=(default_vltmt_threads if test.vltmt else 1)) diff --git a/test_regress/t/t_interface_inout_tristate.py b/test_regress/t/t_interface_inout_tristate.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_interface_inout_tristate.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_inout_tristate.v b/test_regress/t/t_interface_inout_tristate.v new file mode 100644 index 000000000..36b20fea2 --- /dev/null +++ b/test_regress/t/t_interface_inout_tristate.v @@ -0,0 +1,70 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`ifdef verilator + `define no_optimize(v) $c(v) +`else + `define no_optimize(v) (v) +`endif +// verilog_format: on + +// verilator lint_off MULTIDRIVEN + +interface ifc; + // Tristate net resolved across an inout port connected hierarchically to + // this interface signal (iface.data <-> dut inout port). + wire [7:0] data; +endinterface + +module dut ( + inout wire [7:0] data, + input bit oe, + input logic [7:0] val +); + // The inout port drives the interface net when enabled, releases it (Z) otherwise. + assign data = oe ? val : 8'hzz; +endmodule + +module t; + ifc ifc0 (); + bit oe, top_oe; + logic [7:0] val, topval; + + // A second driver from the top, so resolution is observable from both sides. + assign ifc0.data = top_oe ? topval : 8'hzz; + + dut u ( + .data(ifc0.data), + .oe(oe), + .val(val) + ); + + initial begin + // dut drives the interface net through the inout port. + oe = `no_optimize(1'b1); + val = `no_optimize(8'hA5); + top_oe = `no_optimize(1'b0); + topval = `no_optimize(8'h00); + #1; + `checkh(ifc0.data, 8'hA5); + // top drives, dut releases. + oe = `no_optimize(1'b0); + top_oe = `no_optimize(1'b1); + topval = `no_optimize(8'h3C); + #1; + `checkh(ifc0.data, 8'h3C); + // Neither drives: net floats to Z. + oe = `no_optimize(1'b0); + top_oe = `no_optimize(1'b0); + #1; + `checkh(ifc0.data, 8'hzz); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_param_class_bits.py b/test_regress/t/t_interface_param_class_bits.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_interface_param_class_bits.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_param_class_bits.v b/test_regress/t/t_interface_param_class_bits.v new file mode 100644 index 000000000..35f80c334 --- /dev/null +++ b/test_regress/t/t_interface_param_class_bits.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// A class type parameter whose default reads $bits of a sibling type +// parameter must not freeze that sibling at its default width when other +// parameters of the interface are overridden (#7711). +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +class cls #( + int width +); +endclass + +interface ifc #( + parameter int width = 8, + parameter type dtype = logic [width-1:0], + parameter type cparam = cls#($bits(dtype)) +); + dtype data; +endinterface + +module t; + // width is overridden, dtype keeps its default logic[width-1:0], and the + // class type parameter is overridden. dtype must follow width (1 bit). + ifc #( + .width(1), + .cparam(cls #(1)) + ) inst1 (); + // Same interface left at its default width (8 bits) must still work. + ifc inst8 (); + + always_comb inst1.data = 1'b0; + + initial begin + if ($bits(inst1.data) != 1) begin + $write("%%Error: $bits(inst1.data)=%0d exp=1\n", $bits(inst1.data)); + $stop; + end + if ($bits(inst8.data) != 8) begin + $write("%%Error: $bits(inst8.data)=%0d exp=8\n", $bits(inst8.data)); + $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_tristate_plain_xhier.py b/test_regress/t/t_interface_tristate_plain_xhier.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_interface_tristate_plain_xhier.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_tristate_plain_xhier.v b/test_regress/t/t_interface_tristate_plain_xhier.v new file mode 100644 index 000000000..d7c7d6ca6 --- /dev/null +++ b/test_regress/t/t_interface_tristate_plain_xhier.v @@ -0,0 +1,104 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`ifdef verilator + `define no_optimize(v) $c(v) +`else + `define no_optimize(v) (v) +`endif +// verilog_format: on + +// verilator lint_off MULTIDRIVEN + +interface ifc; + wire [1:0] w; + // Self-contained interface-internal tristate driver: 'z while en==0, so any + // external driver must win the net resolution. no_optimize keeps the driver + // values from being constant-folded, so the resolution runs at run time. + logic en; + logic [1:0] wint; + assign en = `no_optimize(1'b0); + assign wint = `no_optimize(2'b11); + assign w = en ? wint : 2'bzz; + // Read the resolved net from inside the interface (a consumer's view), so the + // check sees the true resolution, not the driving module's own local copy. + function automatic logic [1:0] get_w(); + return w; + endfunction +endinterface + +// Interface net made tristate only by an external 'z driver (no internal driver). +interface ifc_tri; + tri [1:0] w; +endinterface + +module driver ( + ifc io, + input logic [1:0] din +); + // Plain (non-Z) cross-hierarchy driver of an interface tristate net. + // This module's own tristate graph has no 'z on io.w, so before the fix this + // contribution was silently dropped and io.w resolved to the interface-internal + // 'z (== 0) only. + assign io.w = din; +endmodule + +module driver_tri ( + ifc_tri io, + input logic [1:0] din +); + assign io.w = din; +endmodule + +module zdriver ( + ifc_tri io +); + assign io.w = 2'bzz; +endmodule + +module t; + // u_a, u_b: interface nets driven plainly from the top module (depth-0 xref). + // u_c: interface net driven plainly from a child module (depth-1 xref). + ifc u_a (); + ifc u_b (); + ifc u_c (); + // u_e: net is tristate only via zdriver's external 'z; the plain driver must + // still win (the interface itself has no internal driver). + ifc_tri u_e (); + + logic [1:0] va, vb, vc, ve; + assign va = `no_optimize(2'b01); + assign vb = `no_optimize(2'b10); + assign vc = `no_optimize(2'b11); + assign ve = `no_optimize(2'b01); + + assign u_a.w = va; // plain top-level driver + assign u_b.w = vb; // plain top-level driver + driver u_drv ( + .io(u_c), + .din(vc) + ); // plain driver in a child module + driver_tri u_pdrv ( + .io(u_e), + .din(ve) + ); // plain driver of an externally-tristated net + zdriver u_zdrv (.io(u_e)); // external 'z driver + + initial begin + #1; + // The plain external driver must win over the interface-internal 'z. + `checkh(u_a.get_w(), 2'b01); + `checkh(u_b.get_w(), 2'b10); + `checkh(u_c.get_w(), 2'b11); + // The plain driver must win over an external-only 'z (no internal driver). + `checkh(u_e.w, 2'b01); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_tristate_plain_xhier_noinl.py b/test_regress/t/t_interface_tristate_plain_xhier_noinl.py new file mode 100755 index 000000000..12aff0c40 --- /dev/null +++ b/test_regress/t/t_interface_tristate_plain_xhier_noinl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t_interface_tristate_plain_xhier.v" + +test.compile(timing_loop=True, verilator_flags2=['--timing', '-fno-inline']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_func_module_call.py b/test_regress/t/t_interface_virtual_func_module_call.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_module_call.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_func_module_call.v b/test_regress/t/t_interface_virtual_func_module_call.v new file mode 100644 index 000000000..7489bead4 --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_module_call.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +interface iface; + int cnt = 0; + function void bump(); + cnt++; + endfunction +endinterface + +module t; + iface theIf (); + virtual iface vif; + initial begin + vif = theIf; + vif.bump(); + if (theIf.cnt !== 1) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_virtual_sub_iface_method.py b/test_regress/t/t_interface_virtual_sub_iface_method.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_interface_virtual_sub_iface_method.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_sub_iface_method.v b/test_regress/t/t_interface_virtual_sub_iface_method.v new file mode 100644 index 000000000..6cf486f5b --- /dev/null +++ b/test_regress/t/t_interface_virtual_sub_iface_method.v @@ -0,0 +1,71 @@ +// 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 checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); +// verilog_format: on + +interface inner_if; + logic [7:0] cnt = 0; + function automatic void bump(); + cnt = cnt + 1; + endfunction + function automatic logic [7:0] get(); + return cnt; + endfunction + task automatic addn(input logic [7:0] n); + cnt = cnt + n; + endtask +endinterface + +interface mid_if; + inner_if sub (); +endinterface + +interface data_if; + logic [7:0] val = 0; +endinterface + +interface outer_if; + inner_if a (); + inner_if b (); + mid_if m (); + data_if d (); +endinterface + +class driver_c; + virtual outer_if vif; + task run(); + vif.a.bump(); // sibling a: void function, +1 + vif.b.bump(); // sibling b: void function, +1 + vif.b.addn(8'd5); // sibling b: task with arg, +5 + vif.m.sub.bump(); // two-level nesting, +1 + vif.d.val = 8'd99; // sub-interface variable write via vif (no method) + endtask + function logic [7:0] read_a(); + return vif.a.get(); // function returning value via vif sub-interface + endfunction +endclass + +module t; + outer_if oif (); + driver_c drv; + initial begin + drv = new(); + drv.vif = oif; + drv.run(); + // Per-instance dispatch: a, b and m.sub are distinct instances. + `checkd(oif.a.cnt, 8'd1) + `checkd(oif.b.cnt, 8'd6) + `checkd(oif.m.sub.cnt, 8'd1) + `checkd(drv.read_a(), 8'd1) + `checkd(oif.d.val, 8'd99) + // Direct (non-virtual) sub-interface method call: covers the non-virtual dtype path. + oif.a.bump(); + `checkd(oif.a.cnt, 8'd2) + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_json_only_begin_hier.out b/test_regress/t/t_json_only_begin_hier.out index 8e779675c..68cc2bd11 100644 --- a/test_regress/t/t_json_only_begin_hier.out +++ b/test_regress/t/t_json_only_begin_hier.out @@ -2,7 +2,7 @@ "modulesp": [ {"type":"MODULE","name":"test","addr":"(E)","loc":"d,21:8,21:12","origName":"test","verilogName":"test","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"N","addr":"(F)","loc":"d,22:10,22:11","dtypep":"(G)","origName":"N","verilogName":"N","direction":"NONE","isUsedLoopIdx":true,"lifetime":"VSTATICI","varType":"GENVAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"N","addr":"(F)","loc":"d,22:10,22:11","dtypep":"(G)","origName":"N","verilogName":"N","direction":"NONE","isUsedLoopIdx":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"GENVAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"GENBLOCK","name":"FOR_GENERATE","addr":"(H)","loc":"d,24:5,24:8","implied":true,"genforp": [],"itemsp": []}, {"type":"GENBLOCK","name":"FOR_GENERATE[0]","addr":"(I)","loc":"d,25:14,25:24","genforp": [], "itemsp": [ diff --git a/test_regress/t/t_json_only_debugcheck.py b/test_regress/t/t_json_only_debugcheck.py index 3137b8632..aeef0a17b 100755 --- a/test_regress/t/t_json_only_debugcheck.py +++ b/test_regress/t/t_json_only_debugcheck.py @@ -16,7 +16,7 @@ test.top_filename = "t/t_enum_type_methods.v" out_filename = test.obj_dir + "/V" + test.name + ".tree.json" test.compile(verilator_flags2=[ - '--no-std', '--debug-check', '--no-json-edit-nums', '--flatten', '--inline-cfuncs', '0' + '--no-std', '--debug-check', '--no-json-edit-nums', '--flatten', '-fno-inline-cfuncs' ], verilator_make_gmake=False, make_top_shell=False, diff --git a/test_regress/t/t_json_only_first.out b/test_regress/t/t_json_only_first.out index f32e2e41d..0f60e9c25 100644 --- a/test_regress/t/t_json_only_first.out +++ b/test_regress/t/t_json_only_first.out @@ -2,13 +2,13 @@ "modulesp": [ {"type":"MODULE","name":"t","addr":"(E)","loc":"d,7:8,7:9","origName":"t","verilogName":"t","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"q","addr":"(F)","loc":"d,16:21,16:22","dtypep":"(G)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(F)","loc":"d,16:21,16:22","dtypep":"(G)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(H)","loc":"d,14:9,14:12","dtypep":"(I)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(J)","loc":"d,15:15,15:16","dtypep":"(G)","origName":"d","verilogName":"d","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"between","addr":"(K)","loc":"d,18:15,18:22","dtypep":"(G)","origName":"between","verilogName":"between","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"direct_named","addr":"(L)","loc":"d,19:9,19:21","dtypep":"(I)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"computed_named","addr":"(M)","loc":"d,20:9,20:23","dtypep":"(I)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"anonymous_expr","addr":"(N)","loc":"d,21:9,21:23","dtypep":"(I)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"between","addr":"(K)","loc":"d,18:15,18:22","dtypep":"(G)","origName":"between","verilogName":"between","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"direct_named","addr":"(L)","loc":"d,19:9,19:21","dtypep":"(I)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"computed_named","addr":"(M)","loc":"d,20:9,20:23","dtypep":"(I)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"anonymous_expr","addr":"(N)","loc":"d,21:9,21:23","dtypep":"(I)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"S_IDLE","addr":"(O)","loc":"d,23:26,23:32","dtypep":"(P)","origName":"S_IDLE","verilogName":"S_IDLE","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"2'h0","addr":"(Q)","loc":"d,23:35,23:40","dtypep":"(R)"} @@ -122,7 +122,7 @@ "stmtsp": [ {"type":"VAR","name":"clk","addr":"(QC)","loc":"d,62:11,62:14","dtypep":"(I)","origName":"clk","verilogName":"clk","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(KC)","loc":"d,63:17,63:18","dtypep":"(G)","origName":"d","verilogName":"d","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"q","addr":"(NC)","loc":"d,64:23,64:24","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(NC)","loc":"d,64:23,64:24","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(SC)","loc":"d,67:12,67:13","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(TC)","loc":"d,67:12,67:13","dtypep":"(G)", @@ -142,7 +142,7 @@ ],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(CC)","loc":"d,50:11,50:14","dtypep":"(I)","origName":"clk","verilogName":"clk","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(FC)","loc":"d,51:23,51:24","dtypep":"(G)","origName":"d","verilogName":"d","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"q","addr":"(ZB)","loc":"d,52:30,52:31","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(ZB)","loc":"d,52:30,52:31","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"IGNORED","addr":"(AD)","loc":"d,55:14,55:21","dtypep":"(XC)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"32'sh1","addr":"(BD)","loc":"d,55:24,55:25","dtypep":"(ZC)"} diff --git a/test_regress/t/t_json_only_flat.out b/test_regress/t/t_json_only_flat.out index 727724001..e90bba08b 100644 --- a/test_regress/t/t_json_only_flat.out +++ b/test_regress/t/t_json_only_flat.out @@ -2,16 +2,16 @@ "modulesp": [ {"type":"MODULE","name":"$root","addr":"(F)","loc":"d,7:8,7:9","origName":"$root","verilogName":"$root","level":1,"modPublic":true,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"q","addr":"(G)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(G)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(I)","loc":"d,14:9,14:12","dtypep":"(J)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(K)","loc":"d,15:15,15:16","dtypep":"(H)","origName":"d","verilogName":"d","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.q","addr":"(L)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.q","addr":"(L)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.clk","addr":"(M)","loc":"d,14:9,14:12","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.d","addr":"(N)","loc":"d,15:15,15:16","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.between","addr":"(O)","loc":"d,18:15,18:22","dtypep":"(H)","origName":"between","verilogName":"between","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.direct_named","addr":"(P)","loc":"d,19:9,19:21","dtypep":"(J)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.computed_named","addr":"(Q)","loc":"d,20:9,20:23","dtypep":"(J)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.anonymous_expr","addr":"(R)","loc":"d,21:9,21:23","dtypep":"(J)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.between","addr":"(O)","loc":"d,18:15,18:22","dtypep":"(H)","origName":"between","verilogName":"between","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.direct_named","addr":"(P)","loc":"d,19:9,19:21","dtypep":"(J)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.computed_named","addr":"(Q)","loc":"d,20:9,20:23","dtypep":"(J)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.anonymous_expr","addr":"(R)","loc":"d,21:9,21:23","dtypep":"(J)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.S_IDLE","addr":"(S)","loc":"d,23:26,23:32","dtypep":"(T)","origName":"S_IDLE","verilogName":"S_IDLE","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"2'h0","addr":"(U)","loc":"d,23:35,23:40","dtypep":"(V)"} @@ -30,14 +30,14 @@ ],"attrsp": []}, {"type":"VAR","name":"t.cell1.clk","addr":"(EB)","loc":"d,50:11,50:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell1.d","addr":"(FB)","loc":"d,51:23,51:24","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell1.q","addr":"(GB)","loc":"d,52:30,52:31","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell1.q","addr":"(GB)","loc":"d,52:30,52:31","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell1.IGNORED","addr":"(HB)","loc":"d,55:14,55:21","dtypep":"(BB)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"32'sh1","addr":"(IB)","loc":"d,55:24,55:25","dtypep":"(DB)"} ],"attrsp": []}, {"type":"VAR","name":"t.cell2.clk","addr":"(JB)","loc":"d,62:11,62:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell2.d","addr":"(KB)","loc":"d,63:17,63:18","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell2.q","addr":"(LB)","loc":"d,64:23,64:24","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell2.q","addr":"(LB)","loc":"d,64:23,64:24","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"TOPSCOPE","name":"","addr":"(E)","loc":"d,7:8,7:9","senTreesp": [], "scopep": [ {"type":"SCOPE","name":"TOP","addr":"(MB)","loc":"d,7:8,7:9","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(F)", diff --git a/test_regress/t/t_json_only_flat_vlvbound.out b/test_regress/t/t_json_only_flat_vlvbound.out index ff23ac385..1725da9ef 100644 --- a/test_regress/t/t_json_only_flat_vlvbound.out +++ b/test_regress/t/t_json_only_flat_vlvbound.out @@ -4,12 +4,12 @@ "stmtsp": [ {"type":"VAR","name":"i_a","addr":"(G)","loc":"d,8:24,8:27","dtypep":"(H)","origName":"i_a","verilogName":"i_a","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"i_b","addr":"(I)","loc":"d,9:24,9:27","dtypep":"(H)","origName":"i_b","verilogName":"i_b","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"o_a","addr":"(J)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"o_b","addr":"(L)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"o_a","addr":"(J)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"o_b","addr":"(L)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"vlvbound_test.i_a","addr":"(M)","loc":"d,8:24,8:27","dtypep":"(H)","origName":"i_a","verilogName":"i_a","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"vlvbound_test.i_b","addr":"(N)","loc":"d,9:24,9:27","dtypep":"(H)","origName":"i_b","verilogName":"i_b","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"vlvbound_test.o_a","addr":"(O)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"vlvbound_test.o_b","addr":"(P)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"vlvbound_test.o_a","addr":"(O)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"vlvbound_test.o_b","addr":"(P)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"TOPSCOPE","name":"","addr":"(E)","loc":"d,7:8,7:21","senTreesp": [], "scopep": [ {"type":"SCOPE","name":"TOP","addr":"(Q)","loc":"d,7:8,7:21","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(F)", diff --git a/test_regress/t/t_json_only_primary_io.out b/test_regress/t/t_json_only_primary_io.out index 896aa716b..a611f6917 100644 --- a/test_regress/t/t_json_only_primary_io.out +++ b/test_regress/t/t_json_only_primary_io.out @@ -5,8 +5,8 @@ {"type":"VAR","name":"clk","addr":"(F)","loc":"d,13:9,13:12","dtypep":"(G)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a1","addr":"(H)","loc":"d,14:9,14:11","dtypep":"(G)","origName":"a1","verilogName":"a1","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a2","addr":"(I)","loc":"d,15:9,15:11","dtypep":"(G)","origName":"a2","verilogName":"a2","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"ready","addr":"(J)","loc":"d,16:10,16:15","dtypep":"(G)","origName":"ready","verilogName":"ready","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"ready_reg","addr":"(K)","loc":"d,18:8,18:17","dtypep":"(G)","origName":"ready_reg","verilogName":"ready_reg","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"ready","addr":"(J)","loc":"d,16:10,16:15","dtypep":"(G)","origName":"ready","verilogName":"ready","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"ready_reg","addr":"(K)","loc":"d,18:8,18:17","dtypep":"(G)","origName":"ready_reg","verilogName":"ready_reg","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"CELL","name":"and_cell","addr":"(L)","loc":"d,20:11,20:19","origName":"and_cell","verilogName":"and_cell","modp":"(M)", "pinsp": [ {"type":"PIN","name":"a1","addr":"(N)","loc":"d,21:8,21:10","svDotName":true,"modVarp":"(O)","modPTypep":"UNLINKED", @@ -37,7 +37,7 @@ "stmtsp": [ {"type":"VAR","name":"a1","addr":"(O)","loc":"d,30:16,30:18","dtypep":"(G)","origName":"a1","verilogName":"a1","direction":"INPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a2","addr":"(R)","loc":"d,31:16,31:18","dtypep":"(G)","origName":"a2","verilogName":"a2","direction":"INPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"zn","addr":"(U)","loc":"d,32:17,32:19","dtypep":"(G)","origName":"zn","verilogName":"zn","direction":"OUTPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"zn","addr":"(U)","loc":"d,32:17,32:19","dtypep":"(G)","origName":"zn","verilogName":"zn","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(AB)","loc":"d,34:13,34:14","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(BB)","loc":"d,34:13,34:14","dtypep":"(G)", diff --git a/test_regress/t/t_json_only_tag.out b/test_regress/t/t_json_only_tag.out index c0d396689..c7e9999c6 100644 --- a/test_regress/t/t_json_only_tag.out +++ b/test_regress/t/t_json_only_tag.out @@ -9,7 +9,7 @@ {"type":"CELL","name":"itop","addr":"(L)","loc":"d,29:7,29:11","origName":"itop","verilogName":"itop","modp":"(M)","pinsp": [],"paramsp": [],"rangep": [],"intfRefsp": []}, {"type":"VAR","name":"itop","addr":"(N)","loc":"d,29:7,29:11","dtypep":"(O)","origName":"itop__Viftop","verilogName":"itop__Viftop","direction":"NONE","lifetime":"VSTATICI","varType":"IFACEREF","dtypeName":"","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"this_struct","addr":"(P)","loc":"d,31:13,31:24","dtypep":"(Q)","origName":"this_struct","verilogName":"this_struct","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"dotted","addr":"(R)","loc":"d,33:15,33:21","dtypep":"(S)","origName":"dotted","verilogName":"dotted","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"dotted","addr":"(R)","loc":"d,33:15,33:21","dtypep":"(S)","origName":"dotted","verilogName":"dotted","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(T)","loc":"d,33:22,33:23","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(U)","loc":"d,33:22,33:23","dtypep":"(S)", diff --git a/test_regress/t/t_lint_historical.v b/test_regress/t/t_lint_historical.v index 014c059a9..d92949dc9 100644 --- a/test_regress/t/t_lint_historical.v +++ b/test_regress/t/t_lint_historical.v @@ -40,6 +40,7 @@ module t; // verilator lint_off ENUMITEMWIDTH // verilator lint_off ENUMVALUE // verilator lint_off EOFNEWLINE + // verilator lint_off FINALDLY // verilator lint_off FUNCTIMECTL // verilator lint_off GENCLK // verilator lint_off GENUNNAMED diff --git a/test_regress/t/t_oob_2state_array.py b/test_regress/t/t_oob_2state_array.py new file mode 100755 index 000000000..7b63fce7f --- /dev/null +++ b/test_regress/t/t_oob_2state_array.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +# x-assign shouldn't affect 2-state oob read +test.compile(verilator_flags2=["--binary --x-assign 1"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_oob_2state_array.v b/test_regress/t/t_oob_2state_array.v new file mode 100644 index 000000000..ac59838cb --- /dev/null +++ b/test_regress/t/t_oob_2state_array.v @@ -0,0 +1,29 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #1 clk = ~clk; + + int idx = 0; + bit a[0:2] = {1, 1, 1}; + + always @(posedge clk) begin + if (idx == 4) begin + $write("*-* All Finished *-*\n"); + $finish; + end + else begin + idx <= idx + 1; + `checkh(a[idx], idx <= 2 ? 1 : 0); + end + end +endmodule diff --git a/test_regress/t/t_opt_balance_cats.py b/test_regress/t/t_opt_balance_cats.py index 577c19985..d5e863848 100755 --- a/test_regress/t/t_opt_balance_cats.py +++ b/test_regress/t/t_opt_balance_cats.py @@ -14,7 +14,7 @@ test.scenarios('vlt') test.compile( verilator_flags2=["--stats", "--build", "--gate-stmts", "10000", "--expand-limit", "128"]) -test.file_grep(test.stats, r'Optimizations, FuncOpt concat trees balanced\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, FuncOpt concat trees balanced\s+(\d+)', 3) test.file_grep(test.stats, r'Optimizations, FuncOpt concat splits\s+(\d+)', 67) test.passes() diff --git a/test_regress/t/t_opt_const.py b/test_regress/t/t_opt_const.py index 9a49ab61d..fa2e1cfee 100755 --- a/test_regress/t/t_opt_const.py +++ b/test_regress/t/t_opt_const.py @@ -16,7 +16,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "-fno-dfg", "--stats", test. test.execute() if test.vlt: - test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 48) + test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 46) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_opt_const_dfg.py b/test_regress/t/t_opt_const_dfg.py index 6ad2d3549..41f6a2976 100755 --- a/test_regress/t/t_opt_const_dfg.py +++ b/test_regress/t/t_opt_const_dfg.py @@ -18,7 +18,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "--stats", test.pli_filename test.execute() if test.vlt: - test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 38) + test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 40) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs.py b/test_regress/t/t_opt_inline_cfuncs.py index 7981f48c3..1821f5ec5 100755 --- a/test_regress/t/t_opt_inline_cfuncs.py +++ b/test_regress/t/t_opt_inline_cfuncs.py @@ -9,17 +9,17 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('vlt_all') -# Use --output-split-cfuncs to create small functions that can be inlined -# Also test --inline-cfuncs-product option test.compile(verilator_flags2=[ - "--stats", "--binary", "--output-split-cfuncs", "1", "--inline-cfuncs-product", "200" + "--stats", "--binary", "--inline-cfuncs-product", "200", "--dumpi-V3InlineCFuncs", "9" ]) -# Verify inlining happened with exact count -test.file_grep(test.stats, r'Optimizations, Inlined CFuncs\s+(\d+)', 39) - test.execute() +if test.vlt: + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 7) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 7) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 7) + test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_args.py b/test_regress/t/t_opt_inline_cfuncs_args.py new file mode 100755 index 000000000..05fb055dd --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_args.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=["--stats", "--binary"]) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]') + +test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_args.v b/test_regress/t/t_opt_inline_cfuncs_args.v new file mode 100644 index 000000000..d94ac40c4 --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_args.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire clk +); + + integer cyc = 0; + reg [31:0] acc; + + task automatic add_pair(input [31:0] a, input [31:0] b, inout [31:0] sum); + // verilator no_inline_task + sum = sum + a + b; + endtask + + always @(posedge clk) begin + cyc <= cyc + 1; + acc = 0; + add_pair(cyc[31:0], 32'd1, acc); // + cyc + 1 + add_pair(32'd1000, cyc[31:0], acc); // + 1000 + cyc + // acc = (cyc + 1) + (1000 + cyc) = 2*cyc + 1001 + if (cyc > 1) begin + if (acc !== (2 * cyc[31:0] + 32'd1001)) begin + $write("%%Error: cyc=%0d acc=%0d expected %0d\n", cyc, acc, 2 * cyc + 1001); + $stop; + end + end + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_inline_cfuncs_dup.py b/test_regress/t/t_opt_inline_cfuncs_dup.py new file mode 100755 index 000000000..05fb055dd --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_dup.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=["--stats", "--binary"]) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]') + +test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_dup.v b/test_regress/t/t_opt_inline_cfuncs_dup.v new file mode 100644 index 000000000..2a39fa47e --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_dup.v @@ -0,0 +1,30 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire clk +); + + integer cyc = 0; + + task automatic tick(); + // verilator no_inline_task + automatic time t = $time; + $display("TICK: %0t", t); + endtask + + always @(posedge clk) begin + cyc <= cyc + 1; + tick(); + tick(); + tick(); + tick(); + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_inline_cfuncs_off.py b/test_regress/t/t_opt_inline_cfuncs_off.py index 8b045e8ca..7e7361d8c 100755 --- a/test_regress/t/t_opt_inline_cfuncs_off.py +++ b/test_regress/t/t_opt_inline_cfuncs_off.py @@ -9,15 +9,13 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('vlt_all') test.top_filename = "t/t_opt_inline_cfuncs.v" -# Disable inlining with --inline-cfuncs 0 -test.compile(verilator_flags2=["--stats", "--binary", "--inline-cfuncs", "0"]) - -# Verify inlining did NOT happen (stat doesn't exist when pass is skipped) -test.file_grep_not(test.stats, r'Optimizations, Inlined CFuncs\s+[1-9]') +test.compile(verilator_flags2=["--stats", "--binary", "-fno-inline-cfuncs"]) test.execute() +test.file_grep_not(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]') + test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_threshold.py b/test_regress/t/t_opt_inline_cfuncs_threshold.py index 677dee894..c7c6713b4 100755 --- a/test_regress/t/t_opt_inline_cfuncs_threshold.py +++ b/test_regress/t/t_opt_inline_cfuncs_threshold.py @@ -9,17 +9,16 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('vlt_all') -# Use thresholds that guarantee rejection to test the "return false" path in isInlineable() -# --inline-cfuncs 1: pass still runs (not skipped) -# --inline-cfuncs-product 0: guarantees all functions rejected (node_count * call_count > 0 always) test.compile(verilator_flags2=[ - "--stats", "--binary", "--inline-cfuncs", "1", "--inline-cfuncs-product", "0" + "--stats", "--binary", "--inline-cfuncs", "0", "--inline-cfuncs-product", "0" ]) -test.file_grep(test.stats, r'Optimizations, Inlined CFuncs\s+(\d+)', 0) - test.execute() +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 0) + test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_trace.py b/test_regress/t/t_opt_inline_cfuncs_trace.py new file mode 100755 index 000000000..515aa4510 --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_trace.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') +test.top_filename = "t/t_opt_inline_cfuncs.v" + +test.compile(verilator_flags2=["--stats", "--binary", "--trace", "--inline-cfuncs-product", "200"]) + +if test.vlt: + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 8) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 7) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 9) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_opt_table_enum.py b/test_regress/t/t_opt_table_enum.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_enum.py +++ b/test_regress/t/t_opt_table_enum.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_packed_array.py b/test_regress/t/t_opt_table_packed_array.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_packed_array.py +++ b/test_regress/t/t_opt_table_packed_array.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_real.py b/test_regress/t/t_opt_table_real.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_real.py +++ b/test_regress/t/t_opt_table_real.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_same.py b/test_regress/t/t_opt_table_same.py index 2cee4586a..8f09062b6 100755 --- a/test_regress/t/t_opt_table_same.py +++ b/test_regress/t/t_opt_table_same.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 2) diff --git a/test_regress/t/t_opt_table_signed.py b/test_regress/t/t_opt_table_signed.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_signed.py +++ b/test_regress/t/t_opt_table_signed.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_string.py b/test_regress/t/t_opt_table_string.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_string.py +++ b/test_regress/t/t_opt_table_string.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_struct.py b/test_regress/t/t_opt_table_struct.py index 5908d7cde..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_struct.py +++ b/test_regress/t/t_opt_table_struct.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_procedure_always_nba.py b/test_regress/t/t_procedure_always_nba.py new file mode 100755 index 000000000..a68ed3ea9 --- /dev/null +++ b/test_regress/t/t_procedure_always_nba.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.top_filename = "t/t_procedure_nba.v" + +test.compile(verilator_flags2=["--binary", "--stats", "-DPROCEDURE=always"]) + +test.file_grep(test.stats, r'Scheduling, \'act\' extra triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' pre triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' sense triggers\s+(\d+)', 3) +test.file_grep(test.stats, r'Procedures needing initial NBA trigger\s+(\d+)', 100) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_procedure_initial_nba.py b/test_regress/t/t_procedure_initial_nba.py new file mode 100755 index 000000000..528290717 --- /dev/null +++ b/test_regress/t/t_procedure_initial_nba.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.top_filename = "t/t_procedure_nba.v" + +test.compile(verilator_flags2=["--binary", "--stats", "-DPROCEDURE=initial"]) + +test.file_grep(test.stats, r'Scheduling, \'act\' extra triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' pre triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' sense triggers\s+(\d+)', 3) +test.file_grep(test.stats, r'Procedures needing initial NBA trigger\s+(\d+)', 100) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_procedure_nba.v b/test_regress/t/t_procedure_nba.v new file mode 100644 index 000000000..a5cbf19a5 --- /dev/null +++ b/test_regress/t/t_procedure_nba.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk; + initial clk = 0; + always #5 clk = ~clk; + bit [99:0][2:0] foo; + bit bar; + + always @(posedge clk) begin + bar <= ~bar; + #1; + end + + genvar i; + for (i = 0; i < 100; i=i+1) + `PROCEDURE begin + foo[i] <= 3; + if (foo[i] !== 0) $stop; + @(posedge clk); + if (foo[i] !== 3) $stop; + foo[i] = 2; + if (foo[i] !== 2) $stop; + #1; + if (foo[i] !== 2) $stop; + foo[i] = 0; + end + + initial #100 begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_prop_always.v b/test_regress/t/t_prop_always.v index 3f54b1023..52cfea02c 100644 --- a/test_regress/t/t_prop_always.v +++ b/test_regress/t/t_prop_always.v @@ -24,7 +24,6 @@ module t ( // For "always [m:n] P" the action runs at cyc=K+n on success and at the // detected-violation cyc on failure -- both deterministic given the inputs. int high_bounded_pass_q[$]; - int high_sbounded_pass_q[$]; int high_degenerate_pass_q[$]; int low_bounded_fail_q[$]; int low_degenerate_fail_q[$]; @@ -39,9 +38,6 @@ module t ( // Bounded weak always over constant-true input. assert property (@(posedge clk) always [0:3] a_high) high_bounded_pass_q.push_back(cyc); - // Bounded strong s_always over constant-true input. - assert property (@(posedge clk) s_always [1:2] a_high) high_sbounded_pass_q.push_back(cyc); - // Degenerate [0:0]: equivalent to immediate sample. assert property (@(posedge clk) always [0:0] a_high) high_degenerate_pass_q.push_back(cyc); @@ -83,10 +79,6 @@ module t ( `checkd(high_bounded_pass_q.size(), 17); `checkd(high_bounded_pass_q[0], 3); `checkd(high_bounded_pass_q[$], 19); - // Strong [1:2]: K=0..17 succeed at cyc K+2 = 2..19. - `checkd(high_sbounded_pass_q.size(), 18); - `checkd(high_sbounded_pass_q[0], 2); - `checkd(high_sbounded_pass_q[$], 19); // Degenerate [0:0]: K=0..19 succeed at cyc K = 0..19. `checkd(high_degenerate_pass_q.size(), 20); `checkd(high_degenerate_pass_q[0], 0); diff --git a/test_regress/t/t_prop_always_bad.out b/test_regress/t/t_prop_always_bad.out index 4e06a8feb..62d3d66a8 100644 --- a/test_regress/t/t_prop_always_bad.out +++ b/test_regress/t/t_prop_always_bad.out @@ -1,34 +1,34 @@ -%Error: t/t_prop_always_bad.v:13:20: always range low bound must be non-negative (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:13:28: always range low bound must be non-negative (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 13 | assert property (always [-1:3] a); - | ^~~~~~ + | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_prop_always_bad.v:14:20: always range high bound must be >= low bound (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:14:30: always range high bound must be >= low bound (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 14 | assert property (always [5:2] a); - | ^~~~~~ + | ^ %Error: t/t_prop_always_bad.v:15:28: Expecting expression to be constant, but variable isn't const: 'x' : ... note: In instance 't' 15 | assert property (always [x:3] a); | ^ -%Error: t/t_prop_always_bad.v:15:20: always range low bound must be a constant expression (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:15:28: always range low bound must be a constant expression (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 15 | assert property (always [x:3] a); - | ^~~~~~ + | ^ %Error: t/t_prop_always_bad.v:16:30: Expecting expression to be constant, but variable isn't const: 'x' : ... note: In instance 't' 16 | assert property (always [1:x] a); | ^ -%Error: t/t_prop_always_bad.v:16:20: always range high bound must be a constant expression (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:16:30: always range high bound must be a constant expression (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 16 | assert property (always [1:x] a); - | ^~~~~~ + | ^ %Error: t/t_prop_always_bad.v:17:20: s_always range must be bounded (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 17 | assert property (s_always a); | ^~~~~~~~ -%Error: t/t_prop_always_bad.v:18:20: s_always range must be bounded (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:18:32: s_always range must be bounded (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 18 | assert property (s_always [1:$] a); - | ^~~~~~~~ + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_prop_always_unsup.out b/test_regress/t/t_prop_always_unsup.out index 52bd14918..3bc897181 100644 --- a/test_regress/t/t_prop_always_unsup.out +++ b/test_regress/t/t_prop_always_unsup.out @@ -1,18 +1,14 @@ -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:12:35: Unsupported: unbounded always range (always [m:$]) +%Error-UNSUPPORTED: t/t_prop_always_unsup.v:12:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) : ... note: In instance 't' - 12 | assert property (@(posedge clk) always [2:$] a); + 12 | assert property (@(posedge clk) always [0:3] (a |-> b)); | ^~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:15:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) +%Error-UNSUPPORTED: t/t_prop_always_unsup.v:13:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) : ... note: In instance 't' - 15 | assert property (@(posedge clk) always [0:3] (a |-> b)); + 13 | assert property (@(posedge clk) always [0:3] (a |=> b)); | ^~~~~~ -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:16:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) +%Error-UNSUPPORTED: t/t_prop_always_unsup.v:14:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) : ... note: In instance 't' - 16 | assert property (@(posedge clk) always [0:3] (a |=> b)); - | ^~~~~~ -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:17:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) - : ... note: In instance 't' - 17 | assert property (@(posedge clk) always [0:3] (a ##1 b)); + 14 | assert property (@(posedge clk) always [0:3] (a ##1 b)); | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_prop_always_unsup.v b/test_regress/t/t_prop_always_unsup.v index 7013c9fff..998516d7d 100644 --- a/test_regress/t/t_prop_always_unsup.v +++ b/test_regress/t/t_prop_always_unsup.v @@ -8,9 +8,6 @@ module t (input clk); logic a; logic b; - // IEEE-legal but engine has no sim-end liveness. - assert property (@(posedge clk) always [2:$] a); - // Nested sequence/property operators inside bounded always. assert property (@(posedge clk) always [0:3] (a |-> b)); assert property (@(posedge clk) always [0:3] (a |=> b)); diff --git a/test_regress/t/t_prop_always_wide.v b/test_regress/t/t_prop_always_wide.v index a0e297293..742e37d9a 100644 --- a/test_regress/t/t_prop_always_wide.v +++ b/test_regress/t/t_prop_always_wide.v @@ -15,17 +15,13 @@ module t ( int cyc = 0; logic a_high = 1'b1, b_high = 1'b1, c_high = 1'b1; - int wide_pass_q[$], wide_strong_pass_q[$]; + int wide_pass_q[$]; // Wide range with multi-operand pure propp -- exercises the shared // $sampled(propp) hoist path; pre-fix would clone propp 33 times. assert property (@(posedge clk) always[1: 33] (a_high && b_high && c_high)) wide_pass_q.push_back(cyc); - // Wide strong s_always over the same expression (in-window matches weak). - assert property (@(posedge clk) s_always[1: 33] (a_high && b_high && c_high)) - wide_strong_pass_q.push_back(cyc); - always @(posedge clk) begin cyc <= cyc + 1; if (cyc == 49) begin @@ -33,9 +29,6 @@ module t ( `checkd(wide_pass_q.size(), 17); `checkd(wide_pass_q[0], 33); `checkd(wide_pass_q[$], 49); - `checkd(wide_strong_pass_q.size(), 17); - `checkd(wide_strong_pass_q[0], 33); - `checkd(wide_strong_pass_q[$], 49); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_prop_followed_by.v b/test_regress/t/t_prop_followed_by.v index 0fea33f5b..c1e5778a6 100644 --- a/test_regress/t/t_prop_followed_by.v +++ b/test_regress/t/t_prop_followed_by.v @@ -25,9 +25,12 @@ module t ( integer impl_f = 0; integer nimp_f = 0; integer wide_f = 0; + integer action_hits = 0; // Smoke: trivially-true forms must compile and never fail. assert property (@(posedge clk) 1'b1 #-# 1'b1); + assert property (@(posedge clk) 1'b1 #-# 1'b1) + action_hits++; assert property (@(posedge clk) 0 |-> (0 #-# 0)); assert property (@(posedge clk) 0 |-> (0 #=# 0)); diff --git a/test_regress/t/t_prop_s_always_liveness.out b/test_regress/t/t_prop_s_always_liveness.out new file mode 100644 index 000000000..8a1f69c16 --- /dev/null +++ b/test_regress/t/t_prop_s_always_liveness.out @@ -0,0 +1,4 @@ +*-* All Finished *-* +[115] %Error: t_prop_s_always_liveness.v:25: Assertion failed in top.t +%Error: t/t_prop_s_always_liveness.v:25: Verilog $stop +Aborting... diff --git a/test_regress/t/t_prop_s_always_liveness.py b/test_regress/t/t_prop_s_always_liveness.py new file mode 100755 index 000000000..e6fa1c51f --- /dev/null +++ b/test_regress/t/t_prop_s_always_liveness.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--assert']) +test.execute(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_prop_s_always_liveness.v b/test_regress/t/t_prop_s_always_liveness.v new file mode 100644 index 000000000..3251b7754 --- /dev/null +++ b/test_regress/t/t_prop_s_always_liveness.v @@ -0,0 +1,43 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + int cyc = 0; + logic a_high = 1'b1; + logic a_low = 1'b0; + + int low_s_fail_q[$]; + int low_w_fail_q[$]; + + // The youngest [2:5] windows are still open at $finish, so strong s_always + // reports a liveness failure even with a_high always 1; weak always does not. + assert property (@(posedge clk) s_always [2:5] a_high); + assert property (@(posedge clk) always [2:5] a_high); + + assert property (@(posedge clk) s_always [2:5] a_low) + else low_s_fail_q.push_back(cyc); + assert property (@(posedge clk) always [2:5] a_low) + else low_w_fail_q.push_back(cyc); + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 10) begin + `checkd(low_s_fail_q.size(), low_w_fail_q.size()); + `checkd(low_w_fail_q.size(), 9); + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_property_s_eventually_iface_param.py b/test_regress/t/t_property_s_eventually_iface_param.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface_param.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_property_s_eventually_iface_param.v b/test_regress/t/t_property_s_eventually_iface_param.v new file mode 100644 index 000000000..6a76feabf --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface_param.v @@ -0,0 +1,40 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +interface iface_if #( + parameter int W = 8 +) ( + input bit clk +); + logic [W-1:0] sig = 0; + int passed = 0; + assert property (@(posedge clk) s_eventually (sig == 1)) passed++; +endinterface + +module t; + bit clk = 0; + initial forever #1 clk = ~clk; + int cyc = 0; + + // Two distinct specializations: V3Param clones the interface into two + // modules, each with its own s_eventually tracking. The generated final + // block must stay per-module. + iface_if #(.W(4)) a (.clk(clk)); + iface_if #(.W(8)) b (.clk(clk)); + + always @(posedge clk) begin + ++cyc; + if (cyc == 2) begin + a.sig <= 1; + b.sig <= 1; + end + if (cyc == 5) begin + if (a.passed == 0 || b.passed == 0) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_property_sexpr_cov.v b/test_regress/t/t_property_sexpr_cov.v index 59fa3ddc9..ef58a1fd8 100644 --- a/test_regress/t/t_property_sexpr_cov.v +++ b/test_regress/t/t_property_sexpr_cov.v @@ -52,4 +52,12 @@ module t ( /*AUTOARG*/ cover property (@(posedge clk) ##3 val[0] && val[1]) $display("[%0t] concurrent cover, fileline:%0d", $time, `__LINE__); + + integer action_hits = 0; + + assert property (@(posedge clk) ##1 1'b1) + action_hits++; + + assert property (@(posedge clk) (val[0] ##1 val[1]) |-> 1'b1) + action_hits++; endmodule diff --git a/test_regress/t/t_property_sexpr_multi.v b/test_regress/t/t_property_sexpr_multi.v index 9a99739e5..ff126810f 100644 --- a/test_regress/t/t_property_sexpr_multi.v +++ b/test_regress/t/t_property_sexpr_multi.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 // verilog_format: off diff --git a/test_regress/t/t_property_sexpr_range_delay.v b/test_regress/t/t_property_sexpr_range_delay.v index 5594b6a54..cd3037d03 100644 --- a/test_regress/t/t_property_sexpr_range_delay.v +++ b/test_regress/t/t_property_sexpr_range_delay.v @@ -30,6 +30,7 @@ module t ( int count_fail2 = 0; int count_fail3 = 0; int count_fail4 = 0; + int count_fail5 = 0; always_ff @(posedge clk) begin `ifdef TEST_VERBOSE @@ -49,13 +50,11 @@ module t ( else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); `checkh(sum, 64'h38c614665c6b71ad); - // count_fail1 overcounts Questa by 1: NFA per-cycle reject merging - // OR's requiredStep-fail and terminal-fail into one signal; Questa - // resolves the same overlap as a single per-attempt miss. - `checkd(count_fail1, 25); // Questa: 24 - `checkd(count_fail2, 50); // Questa: 50 - `checkd(count_fail3, 24); // Questa: 24 - `checkd(count_fail4, 1); // Questa: 1 + `checkd(count_fail1, 24); + `checkd(count_fail2, 50); + `checkd(count_fail3, 24); + `checkd(count_fail4, 1); + `checkd(count_fail5, 1); $write("*-* All Finished *-*\n"); $finish; end @@ -131,4 +130,25 @@ module t ( assert property (@(posedge clk) disable iff (cyc < 2) a |-> ##[+] b ##1 (a | b | c | d | e)); + // Finite range delay with a multi-cycle RHS must not reject on an earlier + // candidate when a later candidate in the same window matches. + assert property (@(posedge clk) + cyc == 10 |-> ##[2:4] ((cyc == 12 || cyc == 13) ##1 cyc == 14)); + + // Same shape, but every RHS candidate in the range window rejects, so the + // assertion attempt itself must reject once. + assert property (@(posedge clk) + cyc == 20 |-> ##[2:4] ((cyc == 22 || cyc == 23) ##1 cyc == 25)) + else count_fail5 <= count_fail5 + 1; + + // Variable-length nested RHS, then another finite range below + // the liveness scope. + assert property (@(posedge clk) + cyc == 30 |-> ##[1:2] ((cyc == 31) ##[1:2] ((cyc == 32) ##1 1'b1))); + + // Finite range whose RHS ends in an NFA state, not a final + // boolean condition. + assert property (@(posedge clk) + cyc == 40 |-> ##[1:2] (##1 (((cyc == 43) ##1 1'b1) or ((cyc == 43) ##1 1'b1)))); + endmodule diff --git a/test_regress/t/t_protect_ids_key.out b/test_regress/t/t_protect_ids_key.out index 110f826c1..a9a6d264e 100644 --- a/test_regress/t/t_protect_ids_key.out +++ b/test_regress/t/t_protect_ids_key.out @@ -16,13 +16,11 @@ - - - + @@ -36,13 +34,10 @@ - - - diff --git a/test_regress/t/t_randomize_prepost_with_baseref.py b/test_regress/t/t_randomize_prepost_with_baseref.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_prepost_with_baseref.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_randomize_prepost_with_baseref.v b/test_regress/t/t_randomize_prepost_with_baseref.v new file mode 100644 index 000000000..1bfe4ca64 --- /dev/null +++ b/test_regress/t/t_randomize_prepost_with_baseref.v @@ -0,0 +1,94 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +class Base; + rand bit [7:0] a; + bit [7:0] m_pre; + bit [7:0] m_post; +endclass + +class Derived extends Base; + function void pre_randomize; + `checkd(m_pre, 8'd0); + m_pre = 8'd10; + endfunction + function void post_randomize; + `checkd(m_pre, 8'd10); + m_post = a + 8'd1; + endfunction +endclass + +class Base2; + rand bit [7:0] b; + bit [7:0] bp; + bit [7:0] bq; + function void pre_randomize; + bp = 8'd1; + endfunction + function void post_randomize; + bq = b; + endfunction +endclass + +class Derived2 extends Base2; + bit [7:0] dp; + bit [7:0] dq; + function void pre_randomize; + dp = 8'd2; + super.pre_randomize(); + endfunction + function void post_randomize; + dq = b + 8'd1; + super.post_randomize(); + endfunction +endclass + +module t; + initial begin + Base b; + Derived d; + Base2 b2; + Derived2 d2; + int ok; + + // Plain randomize through a base handle already dispatches pre/post + d = new; + b = d; + ok = b.randomize(); + `checkd(ok, 1); + `checkd(d.m_pre, 8'd10); + `checkd(d.m_post, d.a + 8'd1); + + // randomize() with through a base handle whose static type lacks pre/post + d = new; + b = d; + ok = b.randomize() with {a == 8'h3c;}; + `checkd(ok, 1); + `checkd(b.a, 8'h3c); + `checkd(d.m_pre, 8'd10); + `checkd(d.m_post, 8'h3d); + + // randomize() with through a base handle that DOES define pre/post, + // overridden by the derived class with super chaining + d2 = new; + b2 = d2; + ok = b2.randomize() with {b == 8'h11;}; + `checkd(ok, 1); + `checkd(d2.b, 8'h11); + `checkd(d2.dp, 8'd2); + `checkd(d2.bp, 8'd1); + `checkd(d2.dq, 8'h12); + `checkd(d2.bq, 8'h11); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp b/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp new file mode 100644 index 000000000..27e7a3d39 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp @@ -0,0 +1,45 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +#include + +#include +#include + +#include VM_PREFIX_INCLUDE + +int main(int argc, char** argv) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->threads(1); + contextp->commandArgs(argc, argv); + + const std::unique_ptr topp{new VM_PREFIX{contextp.get(), "top"}}; + topp->clk = 0; + topp->i = 0; + topp->eval(); + + while ((contextp->time() < 10000) && !contextp->gotFinish()) { + contextp->timeInc(1); + topp->clk = !topp->clk; + // Always set to the same constant value, so change detection will think it's not changing + topp->i = 500000; + topp->eval(); + if (topp->o != topp->i + 10) { + const std::string msg = "%Error: incorrect output, got: " + std::to_string(topp->o) + + " expected: " + std::to_string(topp->i + 10); + vl_fatal(__FILE__, __LINE__, "main", msg.c_str()); + break; + } + } + + if (!contextp->gotFinish()) { + vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish"); + } + topp->final(); + return 0; +} diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.py b/test_regress/t/t_sched_ico_change_detect_input_assigned.py new file mode 100755 index 000000000..c837fa2b9 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp" + ]) + +test.execute() + +# There should be a change detect for 'clk', but not for 'i' +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.v b/test_regress/t/t_sched_ico_change_detect_input_assigned.v new file mode 100644 index 000000000..9a4f79b0b --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + clk, + i, + o, + cyc +); + + input clk, i; + output o, cyc; + + logic clk; + int i; // Primary input that the design also drives + int o; + int cyc = 0; + + // Logic dependent on primary input 'i' + always_comb o = i + 10; + + always @(posedge clk) begin + cyc <= cyc + 1; + // On even cycles, assign 'i' + if (cyc % 2 == 0) i = cyc + 1000; + if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py new file mode 100755 index 000000000..b98a8e1b6 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "-fno-ico-change-detect" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 0) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py new file mode 100755 index 000000000..cd22fef43 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "--vpi" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 0) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py new file mode 100755 index 000000000..afd44e7ce --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "-fico-change-detect", "--vpi" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_select_bound4.py b/test_regress/t/t_select_bound4.py new file mode 100755 index 000000000..00e3aae74 --- /dev/null +++ b/test_regress/t/t_select_bound4.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_select_bound4.v b/test_regress/t/t_select_bound4.v new file mode 100644 index 000000000..d09a59a52 --- /dev/null +++ b/test_regress/t/t_select_bound4.v @@ -0,0 +1,29 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module t; + int q[2][$]; + + task automatic pop_q(input int qid, input int expected); + int actual; + actual = q[qid].pop_front(); + if (qid < 2 && actual !== expected) $stop; + endtask + + initial begin + for (int i = 0; i < 4; i++) begin + q[i].push_back(i); + end + + for (int i = 0; i < 4; i++) begin + pop_q(i, i); + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_select_bound_side_effect.py b/test_regress/t/t_select_bound_side_effect.py new file mode 100755 index 000000000..d696b53d9 --- /dev/null +++ b/test_regress/t/t_select_bound_side_effect.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_select_bound_side_effect.v b/test_regress/t/t_select_bound_side_effect.v new file mode 100644 index 000000000..b6a182d04 --- /dev/null +++ b/test_regress/t/t_select_bound_side_effect.v @@ -0,0 +1,74 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + int arr[5]; + int x = 0; + int y = 0; + int z = 0; + + task automatic incr(input int i, input int expected); + arr[i] = x++; + if (i < 5) `checkh(arr[i], expected); + endtask + + function int get_y; + y++; + return y; + endfunction + + task automatic assign_side_effect(input int i, input int expected); + arr[i] = get_y(); + if (i < 5) `checkh(arr[i], expected); + endtask + + task automatic add_z(inout int a); + a += z; + z++; + endtask + + task automatic assign_side_effect_inout(input int i, input int expected); + if (i < 5) arr[i] = 1; + add_z(arr[i]); + if (i < 5) `checkh(arr[i], expected); + endtask + + initial begin + incr(0, 0); + incr(7, 0); + incr(4, 2); + + assign_side_effect(3, 1); + assign_side_effect(8, 0); + assign_side_effect(9, 0); + assign_side_effect(3, 4); + + assign_side_effect_inout(3, 1); + assign_side_effect_inout(4, 2); + assign_side_effect_inout(5, 0); + assign_side_effect_inout(1, 4); + + y = 0; + for (int i = 0; i < 10; i++) begin + arr[get_y()] = i; + if (y < 5) `checkh(arr[y], i); + `checkh(y, 2 * i + 1); + arr[get_y()%(i+1)] = i; + if (y % (i + 1) < 5) `checkh(arr[y%(i+1)], i); + `checkh(y, 2 * (i + 1)); + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py b/test_regress/t/t_select_bound_timing_intra.py similarity index 71% rename from test_regress/t/t_gate_inline_wide_noexclude_other_scope.py rename to test_regress/t/t_select_bound_timing_intra.py index c20d324db..4fae43d09 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py +++ b/test_regress/t/t_select_bound_timing_intra.py @@ -9,10 +9,12 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('simulator') -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) +test.compile(verilator_flags2=["--binary", "--stats"]) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_select_bound_timing_intra.v b/test_regress/t/t_select_bound_timing_intra.v new file mode 100644 index 000000000..31d30c48c --- /dev/null +++ b/test_regress/t/t_select_bound_timing_intra.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + int arr[5]; + + task automatic intra(input int i); + time t = $time; + arr[i] = #1 i; + #1; + if (i < 5) `checkh(arr[i], i); + `checkh($time, t + 2); + endtask + + initial begin + intra(0); + intra(7); + intra(4); + intra(1); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_sequence_sexpr_unsup.out b/test_regress/t/t_sequence_sexpr_unsup.out index 975a06491..9bf5bf9ea 100644 --- a/test_regress/t/t_sequence_sexpr_unsup.out +++ b/test_regress/t/t_sequence_sexpr_unsup.out @@ -8,15 +8,4 @@ %Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:99:5: Unsupported: first_match with sequence_match_items 99 | first_match (a, res0 = 1, res1 = 2); | ^~~~~~~~~~~ -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:102:9: Ignoring unsupported: cover sequence - 102 | cover sequence (s_a) $display(""); - | ^~~~~~~~ - ... For warning description see https://verilator.org/warn/COVERIGN?v=latest - ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:103:9: Ignoring unsupported: cover sequence - 103 | cover sequence (@(posedge a) disable iff (b) s_a) $display(""); - | ^~~~~~~~ -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:104:9: Ignoring unsupported: cover sequence - 104 | cover sequence (disable iff (b) s_a) $display(""); - | ^~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_sequence_within.v b/test_regress/t/t_sequence_within.v index a8ec28b25..093b7afcb 100644 --- a/test_regress/t/t_sequence_within.v +++ b/test_regress/t/t_sequence_within.v @@ -91,6 +91,8 @@ module t ( (a ##3 b) intersect ((c ##1 d) within (a ##3 b))) count_p10 <= count_p10 + 1; + initial $assertvacuousoff; + always_ff @(posedge clk) begin cyc <= cyc + 1; crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; @@ -103,11 +105,11 @@ module t ( // p1/p2/p5 use |->; the NFA currently fires the pass action on // vacuous passes too, so counts are inflated vs. Questa. Pre-existing // engine-wide behavior, not within-specific. - `checkd(count_p1, 89); // Questa: 23 - `checkd(count_p2, 89); // Questa: 44 + `checkd(count_p1, 23); // Questa: 23 + `checkd(count_p2, 44); // Questa: 44 `checkd(count_p3, 26); // Questa: 20 `checkd(count_p4, 24); // Questa: 22 - `checkd(count_p5, 89); // Questa: 26 + `checkd(count_p5, 26); // Questa: 26 `checkd(count_p6, 21); // Questa: 16 `checkd(count_p7, 15); // Questa: 9 `checkd(count_p8, 15); // Questa: 4 diff --git a/test_regress/t/t_stream_queue.py b/test_regress/t/t_stream_queue.py index 3a46a7545..f7785cc52 100755 --- a/test_regress/t/t_stream_queue.py +++ b/test_regress/t/t_stream_queue.py @@ -9,7 +9,8 @@ import vltest_bootstrap -test.scenarios('simulator') +# Issue #7780 unstable with --vltmt +test.scenarios('simulator_st') test.compile(verilator_flags2=["--timing"]) diff --git a/test_regress/t/t_stream_unpacked_struct.py b/test_regress/t/t_stream_unpacked_struct.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct.v b/test_regress/t/t_stream_unpacked_struct.v new file mode 100644 index 000000000..6b0c6f2d6 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.v @@ -0,0 +1,378 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// Ref. to IEEE 1800-2023 11.4.14, A.8.1 + +module t; + + `define checkh(gotv, expv) \ + do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__, `__LINE__, (gotv), (expv)); \ + $stop; \ + end while (0); + + typedef struct packed { + logic [3:0] hi; + logic [3:0] lo; + } packed_pair_t; + + typedef union packed { + logic [7:0] byte_data; + packed_pair_t pair; + } packed_union_t; + + typedef struct { + byte a; + logic [3:0] b; + packed_pair_t p; + } simple_t; + + typedef struct { + byte prefix; + byte data[2]; + packed_pair_t p; + } array_t; + + typedef struct { + simple_t inner; + byte tail[2]; + } nested_t; + + typedef struct { + byte prefix; + simple_t items[2]; + byte suffix; + } struct_array_t; + + typedef struct { + byte data[1:0]; + } descending_array_t; + + typedef struct { + byte matrix[2][3]; + } matrix_array_t; + + typedef struct { + logic [1:0][3:0] data[2]; + } mixed_array_t; + + typedef enum logic [2:0] { + E0 = 3'd0, + E5 = 3'd5 + } enum_t; + + typedef struct { + enum_t e; + logic [4:0] x; + } enum_struct_t; + + typedef struct { + byte tag; + real r; + realtime rt; + real ra[2]; + } real_struct_t; + + typedef struct { + packed_union_t u; + byte tail; + } packed_union_struct_t; + + localparam int WORD_WIDTH = 16; + + typedef union packed { + struct packed { + logic [15:0] temp_1; + logic [15:0] temp_2; + logic [15:0] temp_3; + } regs; + logic [($bits(regs) / WORD_WIDTH)-1:0][WORD_WIDTH-1:0] words; + } issue_4521_union_t; + + simple_t simple; + simple_t simple_out; + simple_t simple_cont_out; + array_t arrayed; + array_t arrayed_out; + nested_t nested; + nested_t nested_out; + struct_array_t struct_array; + struct_array_t struct_array_out; + descending_array_t descending; + descending_array_t descending_out; + matrix_array_t matrix_array; + matrix_array_t matrix_array_out; + mixed_array_t mixed_array; + mixed_array_t mixed_array_out; + simple_t simple_array[2]; + simple_t simple_array_out[2]; + enum_struct_t enum_struct; + enum_struct_t enum_struct_out; + real_struct_t real_struct; + real_struct_t real_struct_out; + packed_union_struct_t packed_union_struct; + packed_union_struct_t packed_union_struct_out; + issue_4521_union_t issue_4521_union; + + logic [19:0] simple_bits; + logic [31:0] wide_simple_bits; + logic [31:0] array_bits; + logic [35:0] nested_bits; + logic [55:0] struct_array_bits; + logic [15:0] descending_bits; + logic [47:0] matrix_array_bits; + logic [15:0] mixed_array_bits; + logic [39:0] simple_array_bits; + logic [31:0] byteswapped_bits; + logic [7:0] enum_bits; + logic [263:0] real_bits; + logic [15:0] packed_union_bits; + logic [11:0] narrow_bits; + logic [19:0] simple_streaml_src; + logic [$bits(simple_t)-1:0] simple_bits_from_bits; + byte byte_array_out[2]; + + assign {>>{simple_cont_out}} = 20'habcde; + + initial begin + byteswapped_bits = {<<8{32'h11223344}}; + `checkh(byteswapped_bits, 32'h44332211); + byteswapped_bits = {<>{simple_bits}} = 20'h13579; + `checkh(simple_bits, 20'h13579); + {<<4{simple_bits}} = 20'h12345; + `checkh(simple_bits, 20'h54321); + + simple = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + `checkh($bits(simple_t), 20); + `checkh($bits(array_t), 32); + `checkh($bits(nested_t), 36); + `checkh($bits(struct_array_t), 56); + `checkh($bits(simple_array), 40); + simple_bits_from_bits = {>>{simple}}; + `checkh(simple_bits_from_bits, 20'h12abc); + simple_bits = {>>{simple}}; + `checkh(simple_bits, 20'h12abc); + /* verilator lint_off WIDTHEXPAND */ + wide_simple_bits = {>>{simple}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(wide_simple_bits, 32'h12abc000); + simple_bits = {<<4{simple}}; + `checkh(simple_bits, 20'hcba21); + + {>>{simple_out}} = 20'h345de; + `checkh(simple_out.a, 8'h34); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'hde); + `checkh(simple_cont_out.a, 8'hab); + `checkh(simple_cont_out.b, 4'hc); + `checkh(simple_cont_out.p, 8'hde); + + {<<4{simple_out}} = 20'h789ab; + `checkh(simple_out.a, 8'hba); + `checkh(simple_out.b, 4'h9); + `checkh(simple_out.p, 8'h87); + + simple_out = {>>{20'hfedcb}}; + `checkh(simple_out.a, 8'hfe); + `checkh(simple_out.b, 4'hd); + `checkh(simple_out.p, 8'hcb); + + simple_out = {>>{simple}}; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + {>>{simple_out}} = simple; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + if ($test$plusargs("t_stream_unpacked_struct_alt")) begin + narrow_bits = 12'h123; + end + else begin + narrow_bits = 12'habd; + end + /* verilator lint_off WIDTHEXPAND */ + simple_bits = {>>{narrow_bits}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(simple_bits, {narrow_bits, 8'h00}); + + simple_out = {>>{narrow_bits}}; + `checkh(simple_out.a, narrow_bits[11:4]); + `checkh(simple_out.b, narrow_bits[3:0]); + `checkh(simple_out.p, 8'h00); + + {>>{simple_out}} = 24'habcdef; + `checkh(simple_out.a, 8'hab); + `checkh(simple_out.b, 4'hc); + `checkh(simple_out.p, 8'hde); + + simple_out = {<<4{20'h13579}}; + `checkh(simple_out.a, 8'h97); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'h31); + + simple_streaml_src = 20'h2468a; + simple_out = {<<4{simple_streaml_src}}; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<4{simple_out}} = simple_streaml_src; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<8{byte_array_out}} = 16'h1234; + `checkh(byte_array_out[0], 8'h34); + `checkh(byte_array_out[1], 8'h12); + + arrayed = '{8'h11, '{8'h22, 8'h33}, '{4'h4, 4'h5}}; + array_bits = {>>{arrayed}}; + `checkh(array_bits, 32'h11223345); + array_bits = {<<8{arrayed}}; + `checkh(array_bits, 32'h45332211); + + {>>{arrayed_out}} = 32'haabbccdd; + `checkh(arrayed_out.prefix, 8'haa); + `checkh(arrayed_out.data[0], 8'hbb); + `checkh(arrayed_out.data[1], 8'hcc); + `checkh(arrayed_out.p, 8'hdd); + + nested = '{'{8'h12, 4'ha, '{4'hb, 4'hc}}, '{8'h34, 8'h56}}; + nested_bits = {>>{nested}}; + `checkh(nested_bits, 36'h12abc3456); + + {>>{nested_out}} = 36'h6543210fe; + `checkh(nested_out.inner.a, 8'h65); + `checkh(nested_out.inner.b, 4'h4); + `checkh(nested_out.inner.p, 8'h32); + `checkh(nested_out.tail[0], 8'h10); + `checkh(nested_out.tail[1], 8'hfe); + + struct_array.prefix = 8'haa; + struct_array.items[0] = '{8'h12, 4'h3, '{4'h4, 4'h5}}; + struct_array.items[1] = '{8'h67, 4'h8, '{4'h9, 4'ha}}; + struct_array.suffix = 8'hbb; + struct_array_bits = {>>{struct_array}}; + `checkh(struct_array_bits, 56'haa123456789abb); + + {>>{struct_array_out}} = 56'hccdef0123456dd; + `checkh(struct_array_out.prefix, 8'hcc); + `checkh(struct_array_out.items[0].a, 8'hde); + `checkh(struct_array_out.items[0].b, 4'hf); + `checkh(struct_array_out.items[0].p, 8'h01); + `checkh(struct_array_out.items[1].a, 8'h23); + `checkh(struct_array_out.items[1].b, 4'h4); + `checkh(struct_array_out.items[1].p, 8'h56); + `checkh(struct_array_out.suffix, 8'hdd); + + descending = '{data: '{8'hcc, 8'hdd}}; + descending_bits = {>>{descending}}; + `checkh(descending_bits, 16'hccdd); + + {>>{descending_out}} = 16'h1234; + `checkh(descending_out.data[1], 8'h12); + `checkh(descending_out.data[0], 8'h34); + + matrix_array.matrix[0][0] = 8'h11; + matrix_array.matrix[0][1] = 8'h22; + matrix_array.matrix[0][2] = 8'h33; + matrix_array.matrix[1][0] = 8'h44; + matrix_array.matrix[1][1] = 8'h55; + matrix_array.matrix[1][2] = 8'h66; + matrix_array_bits = {>>{matrix_array}}; + `checkh(matrix_array_bits, 48'h112233445566); + + {>>{matrix_array_out}} = 48'haabbccddeeff; + `checkh(matrix_array_out.matrix[0][0], 8'haa); + `checkh(matrix_array_out.matrix[0][1], 8'hbb); + `checkh(matrix_array_out.matrix[0][2], 8'hcc); + `checkh(matrix_array_out.matrix[1][0], 8'hdd); + `checkh(matrix_array_out.matrix[1][1], 8'hee); + `checkh(matrix_array_out.matrix[1][2], 8'hff); + + mixed_array.data[0] = 8'hab; + mixed_array.data[1] = 8'hcd; + mixed_array_bits = {>>{mixed_array}}; + `checkh(mixed_array_bits, 16'habcd); + + {>>{mixed_array_out}} = 16'h1234; + `checkh(mixed_array_out.data[0], 8'h12); + `checkh(mixed_array_out.data[0][1], 4'h1); + `checkh(mixed_array_out.data[0][0], 4'h2); + `checkh(mixed_array_out.data[1], 8'h34); + `checkh(mixed_array_out.data[1][1], 4'h3); + `checkh(mixed_array_out.data[1][0], 4'h4); + + simple_array[0] = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + simple_array[1] = '{8'h34, 4'hd, '{4'he, 4'hf}}; + simple_array_bits = {>>{simple_array}}; + `checkh(simple_array_bits, 40'h12abc34def); + + {>>{simple_array_out}} = 40'h567899abcd; + `checkh(simple_array_out[0].a, 8'h56); + `checkh(simple_array_out[0].b, 4'h7); + `checkh(simple_array_out[0].p, 8'h89); + `checkh(simple_array_out[1].a, 8'h9a); + `checkh(simple_array_out[1].b, 4'hb); + `checkh(simple_array_out[1].p, 8'hcd); + + enum_struct = '{E5, 5'ha}; + enum_bits = {>>{enum_struct}}; + `checkh(enum_bits, 8'haa); + + {>>{enum_struct_out}} = 8'h71; + `checkh(enum_struct_out.e, 3'h3); + `checkh(enum_struct_out.x, 5'h11); + + real_struct.tag = 8'h42; + real_struct.r = 1.0; + real_struct.rt = 3.0; + real_struct.ra[0] = 4.0; + real_struct.ra[1] = 5.0; + real_bits = {>>{real_struct}}; + `checkh(real_bits, + {8'h42, $realtobits(1.0), $realtobits(3.0), $realtobits(4.0), $realtobits(5.0)}); + + {>>{real_struct_out}} + = {8'h99, $realtobits(2.0), $realtobits(6.0), $realtobits(7.0), $realtobits(8.0)}; + `checkh(real_struct_out.tag, 8'h99); + `checkh($realtobits(real_struct_out.r), $realtobits(2.0)); + `checkh($realtobits(real_struct_out.rt), $realtobits(6.0)); + `checkh($realtobits(real_struct_out.ra[0]), $realtobits(7.0)); + `checkh($realtobits(real_struct_out.ra[1]), $realtobits(8.0)); + + packed_union_struct.u.byte_data = 8'hbe; + packed_union_struct.tail = 8'hef; + packed_union_bits = {>>{packed_union_struct}}; + `checkh(packed_union_bits, 16'hbeef); + + {>>{packed_union_struct_out}} = 16'hcafe; + `checkh(packed_union_struct_out.u.byte_data, 8'hca); + `checkh(packed_union_struct_out.tail, 8'hfe); + + `checkh($bits(issue_4521_union_t), 48); + `checkh($bits(issue_4521_union.regs), 48); + `checkh($bits(issue_4521_union.words), 48); + issue_4521_union.regs.temp_1 = 16'h1234; + issue_4521_union.regs.temp_2 = 16'h5678; + issue_4521_union.regs.temp_3 = 16'h9abc; + `checkh(issue_4521_union.words[2], 16'h1234); + `checkh(issue_4521_union.words[1], 16'h5678); + `checkh(issue_4521_union.words[0], 16'h9abc); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_stream_unpacked_struct_bad.out b/test_regress/t/t_stream_unpacked_struct_bad.out new file mode 100644 index 000000000..9955e9208 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.out @@ -0,0 +1,30 @@ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:54:12: Unsupported: Stream operation on a variable of a type 'struct{}t.queue_struct_t' + : ... note: In instance 't' + 54 | out = {>>{queue_struct}}; + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:55:12: Unsupported: Stream operation on a variable of a type 'struct{}t.dynamic_struct_t' + : ... note: In instance 't' + 55 | out = {>>{dynamic_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:56:12: Unsupported: Stream operation on a variable of a type 'struct{}t.associative_struct_t' + : ... note: In instance 't' + 56 | out = {>>{associative_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:57:12: Unsupported: Stream operation on a variable of a type 'struct{}t.union_struct_t' + : ... note: In instance 't' + 57 | out = {>>{union_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:58:12: Unsupported: Stream operation on a variable of a type 'struct{}t.string_struct_t' + : ... note: In instance 't' + 58 | out = {>>{string_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:59:12: Unsupported: Stream operation on a variable of a type 'struct{}t.chandle_struct_t' + : ... note: In instance 't' + 59 | out = {>>{chandle_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:60:12: Unsupported: Stream operation on a variable of a type 'struct{}t.event_struct_t' + : ... note: In instance 't' + 60 | out = {>>{event_struct}}; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_stream_unpacked_struct_bad.py b/test_regress/t/t_stream_unpacked_struct_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct_bad.v b/test_regress/t/t_stream_unpacked_struct_bad.v new file mode 100644 index 000000000..2c1e00b97 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.v @@ -0,0 +1,63 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module t; + + typedef struct { + byte bytes[$]; + } queue_struct_t; + + typedef struct { + byte bytes[]; + } dynamic_struct_t; + + typedef struct { + byte bytes[int]; + } associative_struct_t; + + typedef union { + byte a; + logic [15:0] b; + } unpacked_union_t; + + typedef struct { + unpacked_union_t u; + } union_struct_t; + + typedef struct { + string s; + } string_struct_t; + + typedef struct { + chandle c; + } chandle_struct_t; + + typedef struct { + event e; + } event_struct_t; + + logic [255:0] out; + queue_struct_t queue_struct; + dynamic_struct_t dynamic_struct; + associative_struct_t associative_struct; + union_struct_t union_struct; + string_struct_t string_struct; + chandle_struct_t chandle_struct; + event_struct_t event_struct; + + initial begin + out = {>>{queue_struct}}; + out = {>>{dynamic_struct}}; + out = {>>{associative_struct}}; + out = {>>{union_struct}}; + out = {>>{string_struct}}; + out = {>>{chandle_struct}}; + out = {>>{event_struct}}; + end + +endmodule diff --git a/test_regress/t/t_struct_type_bad.out b/test_regress/t/t_struct_type_bad.out index f57c1dd0c..2085d6ecd 100644 --- a/test_regress/t/t_struct_type_bad.out +++ b/test_regress/t/t_struct_type_bad.out @@ -2,4 +2,11 @@ 13 | i badi; | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_struct_type_bad.v:18:19: Reference to 'later' before declaration (IEEE 1800-2023 6.18) + : ... Suggest move the declaration before the reference + 18 | logic [($bits(later) / 8)-1:0][7:0] bad_forward; + | ^~~~~ + t/t_struct_type_bad.v:19:18: ... Location of original declaration + 19 | logic [15:0] later; + | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_struct_type_bad.v b/test_regress/t/t_struct_type_bad.v index 622f2e8e8..1d977d7b7 100644 --- a/test_regress/t/t_struct_type_bad.v +++ b/test_regress/t/t_struct_type_bad.v @@ -13,4 +13,10 @@ module t; i badi; // Bad } struct_t; + typedef union packed { + // Bad + logic [($bits(later) / 8)-1:0][7:0] bad_forward; + logic [15:0] later; + } forward_member_t; + endmodule diff --git a/test_regress/t/t_sys_file_scan_delay.dat b/test_regress/t/t_sys_file_scan_delay.dat new file mode 100644 index 000000000..b85d75e37 --- /dev/null +++ b/test_regress/t/t_sys_file_scan_delay.dat @@ -0,0 +1,4 @@ +00000010 +00000011 + +00000012 diff --git a/test_regress/t/t_assert_iff_clk_unsup.py b/test_regress/t/t_sys_file_scan_delay.py similarity index 79% rename from test_regress/t/t_assert_iff_clk_unsup.py rename to test_regress/t/t_sys_file_scan_delay.py index b5718946c..3cc73805c 100755 --- a/test_regress/t/t_assert_iff_clk_unsup.py +++ b/test_regress/t/t_sys_file_scan_delay.py @@ -9,8 +9,10 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('simulator') -test.lint(expect_filename=test.golden_filename, verilator_flags2=['--assert'], fails=True) +test.compile() + +test.execute() test.passes() diff --git a/test_regress/t/t_sys_file_scan_delay.v b/test_regress/t/t_sys_file_scan_delay.v new file mode 100644 index 000000000..f1db0a34e --- /dev/null +++ b/test_regress/t/t_sys_file_scan_delay.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2024 by David Harris. +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); +// verilog_format: on + +module t; + + int fd, code; + string line; + int siglines; + + localparam SIGNATURESIZE = 5000000; + logic [31:0] sig32[0:SIGNATURESIZE]; + logic [31:0] parsed; + string signame; + + initial begin + signame = "t/t_sys_file_scan_delay.dat"; + + fd = $fopen(signame, "r"); + siglines = 0; + if (fd == 0) $display("Unable to read %s", signame); + else begin + $display("Read %s", signame); + while (!$feof( + fd + )) begin + code = $fgets(line, fd); + if (code != 0) begin + if (line.len() > 1) begin + if ($sscanf(line, "%x", sig32[siglines]) != 0) begin + $display("sig32[%1d] = %x line: ", siglines, sig32[siglines], line); + siglines = siglines + 1; // increment if line is not blank + end + end + end + end + $fclose(fd); + end + + `checkh(sig32[0], 32'h10); + `checkh(sig32[1], 32'h11); + `checkh(sig32[2], 32'h12); + $display("*-* All Finished *-*"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_timing_debug1.py b/test_regress/t/t_timing_debug1.py index 94534429f..a8648a97a 100755 --- a/test_regress/t/t_timing_debug1.py +++ b/test_regress/t/t_timing_debug1.py @@ -13,7 +13,7 @@ test.scenarios('vlt_all') test.top_filename = "t/t_timing_sched.v" test.compile( - verilator_flags2=["--binary", "--timing", "--inline-cfuncs", "0", "-CFLAGS", "-DVL_DEBUG"]) + verilator_flags2=["--binary", "--timing", "-fno-inline-cfuncs", "-CFLAGS", "-DVL_DEBUG"]) test.execute(all_run_flags=["+verilator+debug"]) diff --git a/test_regress/t/t_timing_debug2.py b/test_regress/t/t_timing_debug2.py index 18a53b76f..e5bb01e3c 100755 --- a/test_regress/t/t_timing_debug2.py +++ b/test_regress/t/t_timing_debug2.py @@ -12,8 +12,8 @@ import vltest_bootstrap test.scenarios('vlt_all') test.top_filename = "t/t_timing_class.v" -# Disable --inline-cfuncs so debug traces show all function entries -test.compile(verilator_flags2=["--exe --main --timing --inline-cfuncs 0"]) +# Disable CFunc inlining so debug traces show all function entries +test.compile(verilator_flags2=["--exe --main --timing -fno-inline-cfuncs"]) test.execute(all_run_flags=["+verilator+debug"]) diff --git a/test_regress/t/t_timing_eval_act.out b/test_regress/t/t_timing_eval_act.out index 1d71dde4f..46417dac7 100644 --- a/test_regress/t/t_timing_eval_act.out +++ b/test_regress/t/t_timing_eval_act.out @@ -4,6 +4,7 @@ -V{t#,#}+ Vt_timing_eval_act___024root___eval_debug_assertions -V{t#,#}+ Initial -V{t#,#}+ Vt_timing_eval_act___024root___eval_static +-V{t#,#}+ Vt_timing_eval_act___024root___eval_static__TOP -V{t#,#}+ Vt_timing_eval_act___024root___timing_ready -V{t#,#}+ Vt_timing_eval_act___024root___eval_initial -V{t#,#}+ Vt_timing_eval_act___024root___eval_initial__TOP__Vtiming__0 diff --git a/test_regress/t/t_timing_eval_act.py b/test_regress/t/t_timing_eval_act.py index 787745ab3..46dae2791 100755 --- a/test_regress/t/t_timing_eval_act.py +++ b/test_regress/t/t_timing_eval_act.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('vlt') -test.compile(verilator_flags2=["--binary", "--runtime-debug"]) +test.compile(verilator_flags2=["--binary", "--runtime-debug", "-fno-inline-cfuncs"]) test.file_grep( test.obj_dir + "/" + test.vm_prefix + "___024root__0.cpp", r'void\s+' + test.vm_prefix + diff --git a/test_regress/t/t_unopt_combo_isolate.py b/test_regress/t/t_unopt_combo_isolate.py index f7b4618d3..3ffd80545 100755 --- a/test_regress/t/t_unopt_combo_isolate.py +++ b/test_regress/t/t_unopt_combo_isolate.py @@ -9,37 +9,14 @@ import vltest_bootstrap -test.scenarios('simulator') +# Note: This test is historical for the isolate_assignments attribute, which +# was deprecated and has no effect today. This test ensures it still parses +# in SystemVerilog and Verilator control files for backward compatibility. + +test.scenarios('vlt') test.top_filename = "t/t_unopt_combo.v" -out_filename = test.obj_dir + "/V" + test.name + ".tree.json" - -test.compile(verilator_flags2=[ - "--no-json-edit-nums", "+define+ISOLATE", "--stats", "-fno-dfg", "-fno-lift-expr" -]) - -if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+4') - test.file_grep( - out_filename, - r'{"type":"VAR","name":"t.b",.*"loc":"\w,23:[^"]*",.*"origName":"b",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__Vfuncout",.*"loc":"\w,99:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__t_crc",.*"loc":"\w,100:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_crc",.*"loc":"\w,112:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_c",.*"loc":"\w,113:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_c",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) +test.compile(verilator_flags2=["+define+ISOLATE"]) test.execute() diff --git a/test_regress/t/t_unopt_combo_isolate_vlt.py b/test_regress/t/t_unopt_combo_isolate_vlt.py index 35e0cc8d2..1a041dac0 100755 --- a/test_regress/t/t_unopt_combo_isolate_vlt.py +++ b/test_regress/t/t_unopt_combo_isolate_vlt.py @@ -9,38 +9,14 @@ import vltest_bootstrap -test.scenarios('simulator') +# Note: This test is historical for the isolate_assignments attribute, which +# was deprecated and has no effect today. This test ensures it still parses +# in SystemVerilog and Verilator control files for backward compatibility. + +test.scenarios('vlt') test.top_filename = "t/t_unopt_combo.v" -out_filename = test.obj_dir + "/V" + test.name + ".tree.json" - -test.compile(verilator_flags2=[ - "--no-json-edit-nums", "--stats", test.t_dir + - "/t_unopt_combo_isolate.vlt", "-fno-dfg", "-fno-lift-expr" -]) - -if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+4') - test.file_grep( - out_filename, - r'{"type":"VAR","name":"t.b",.*"loc":"\w,23:[^"]*",.*"origName":"b",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__Vfuncout",.*"loc":"\w,104:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__t_crc",.*"loc":"\w,105:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_crc",.*"loc":"\w,115:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_c",.*"loc":"\w,116:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_c",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) +test.compile(verilator_flags2=[test.t_dir + "/t_unopt_combo_isolate.vlt"]) test.execute() diff --git a/test_regress/t/t_unoptflat_simple_2_bad.out b/test_regress/t/t_unoptflat_simple_2_bad.out index 8588ac55c..4c8793b04 100644 --- a/test_regress/t/t_unoptflat_simple_2_bad.out +++ b/test_regress/t/t_unoptflat_simple_2_bad.out @@ -10,5 +10,5 @@ t/t_unoptflat_simple_2.v:16:14: t.x, width 3, circular fanout 2, can split_var ... Candidates with the highest fanout: t/t_unoptflat_simple_2.v:16:14: t.x, width 3, circular fanout 2, can split_var - ... Suggest add /*verilator split_var*/ or /*verilator isolate_assignments*/ to appropriate variables above. + ... Suggest add /*verilator split_var*/ to appropriate variables above. %Error: Exiting due to diff --git a/test_regress/t/t_verilated_debug.out b/test_regress/t/t_verilated_debug.out index 1d7867bab..7d98054ea 100644 --- a/test_regress/t/t_verilated_debug.out +++ b/test_regress/t/t_verilated_debug.out @@ -37,6 +37,7 @@ internalsDump: -V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba -V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act -V{t#,#}+ Vt_verilated_debug___024root___eval_nba +-V{t#,#}+ Vt_verilated_debug___024root___nba_sequent__TOP__0 *-* All Finished *-* -V{t#,#}+ Vt_verilated_debug___024root___trigger_clear__act -V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act diff --git a/test_regress/t/t_verilated_debug.py b/test_regress/t/t_verilated_debug.py index dbc82d1c1..e880f1866 100755 --- a/test_regress/t/t_verilated_debug.py +++ b/test_regress/t/t_verilated_debug.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('vlt_all') test.verilated_debug = True -test.compile(verilator_flags2=[]) +test.compile(verilator_flags2=['-fno-inline-cfuncs']) test.execute() diff --git a/test_regress/t/t_vlcov_covergroup.annotate.out b/test_regress/t/t_vlcov_covergroup.annotate.out index 436973cb7..b8616ad2d 100644 --- a/test_regress/t/t_vlcov_covergroup.annotate.out +++ b/test_regress/t/t_vlcov_covergroup.annotate.out @@ -14,9 +14,9 @@ module t; %000001 logic [1:0] addr; --000001 point: type=toggle comment=addr[0]:0->1 hier=top.t +-000000 point: type=toggle comment=addr[0]:0->1 hier=top.t -000000 point: type=toggle comment=addr[0]:1->0 hier=top.t --000000 point: type=toggle comment=addr[1]:0->1 hier=top.t +-000001 point: type=toggle comment=addr[1]:0->1 hier=top.t -000000 point: type=toggle comment=addr[1]:1->0 hier=top.t %000001 logic cmd; -000001 point: type=toggle comment=cmd:0->1 hier=top.t @@ -278,6 +278,50 @@ // cross: [a1, write] endgroup + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted + // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + covergroup cg_mixed; +%000002 cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} +-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr0 +-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr1 +%000002 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.write +%000002 cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed +-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.normal +-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.debug +%000001 ab: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_read + // cross: [addr0, read] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_write + // cross: [addr0, write] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_read + // cross: [addr1, read] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_write + // cross: [addr1, write] + endgroup + + // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the + // legacy default-bin codegen path that converted coverpoints bypass. + covergroup cg_def_cross; +%000001 cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.a0 +-000000 point: type=covergroup comment= hier=cg_def_cross.cp_a.a1 +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.ad +%000001 cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.read +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.write +%000001 axc: cross cp_a, cp_c; +-000001 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_read + // cross: [a0, read] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_write + // cross: [a0, write] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_read + // cross: [a1, read] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_write + // cross: [a1, write] + endgroup + %000001 cg2 cg2_inst = new; -000001 point: type=line comment=block hier=top.t %000001 cg_ignore cg_ignore_inst = new; @@ -298,6 +342,10 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_unnamed_cross cg_unnamed_cross_inst = new; -000001 point: type=line comment=block hier=top.t +%000001 cg_mixed cg_mixed_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_def_cross cg_def_cross_inst = new; +-000001 point: type=line comment=block hier=top.t %000001 initial begin -000001 point: type=line comment=block hier=top.t @@ -641,6 +689,40 @@ -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t + // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) +%000001 addr = 0; cmd = 0; mode = 0; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t +%000001 addr = 0; cmd = 1; mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 0; mode = 0; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 1; mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) +%000001 mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write +-000001 point: type=line comment=block hier=top.t + %000001 $write("*-* All Finished *-*\n"); -000001 point: type=line comment=block hier=top.t %000001 $finish; diff --git a/test_regress/t/t_vlt_syntax_bad.out b/test_regress/t/t_vlt_syntax_bad.out index f1ad706be..65a3941ea 100644 --- a/test_regress/t/t_vlt_syntax_bad.out +++ b/test_regress/t/t_vlt_syntax_bad.out @@ -2,28 +2,25 @@ 9 | public -module "t" @(posedge clk) | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_vlt_syntax_bad.vlt:11:1: isolate_assignments only applies to signals or functions/tasks - 11 | isolate_assignments -module "t" - | ^~~~~~~~~~~~~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:13:1: Argument -match only supported for lint_off - 13 | tracing_off --file "*" -match "nothing" +%Error: t/t_vlt_syntax_bad.vlt:11:1: Argument -match only supported for lint_off + 11 | tracing_off --file "*" -match "nothing" | ^~~~~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:13:1: Argument -scope only supported for tracing_on/off + 13 | lint_off --rule UNOPTFLAT -scope "top*" + | ^~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:14:1: Argument -scope only supported for tracing_on/off_off + 14 | lint_off --rule UNOPTFLAT -scope "top*" -levels 0 + | ^~~~~~~~ %Error: t/t_vlt_syntax_bad.vlt:15:1: Argument -scope only supported for tracing_on/off - 15 | lint_off --rule UNOPTFLAT -scope "top*" - | ^~~~~~~~ + 15 | lint_on --rule UNOPTFLAT -scope "top*" + | ^~~~~~~ %Error: t/t_vlt_syntax_bad.vlt:16:1: Argument -scope only supported for tracing_on/off_off - 16 | lint_off --rule UNOPTFLAT -scope "top*" -levels 0 - | ^~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:17:1: Argument -scope only supported for tracing_on/off - 17 | lint_on --rule UNOPTFLAT -scope "top*" + 16 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 | ^~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:18:1: Argument -scope only supported for tracing_on/off_off - 18 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 - | ^~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:20:1: forceable missing -module - 20 | forceable -module "" -var "net_*" +%Error: t/t_vlt_syntax_bad.vlt:18:1: forceable missing -module + 18 | forceable -module "" -var "net_*" | ^~~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:22:1: missing -var - 22 | forceable -module "top" -var "" +%Error: t/t_vlt_syntax_bad.vlt:20:1: missing -var + 20 | forceable -module "top" -var "" | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_vlt_syntax_bad.vlt b/test_regress/t/t_vlt_syntax_bad.vlt index 2ebcefa7c..0a5cdbce0 100644 --- a/test_regress/t/t_vlt_syntax_bad.vlt +++ b/test_regress/t/t_vlt_syntax_bad.vlt @@ -7,8 +7,6 @@ `verilator_config public -module "t" @(posedge clk) -// only signals/functions/tasks -isolate_assignments -module "t" // -match not supported tracing_off --file "*" -match "nothing" // -scope not supported diff --git a/test_regress/t/t_vpi_var.cpp b/test_regress/t/t_vpi_var.cpp index 3afaf92ca..7c0eac4f4 100644 --- a/test_regress/t/t_vpi_var.cpp +++ b/test_regress/t/t_vpi_var.cpp @@ -38,6 +38,8 @@ #endif +#include + #ifdef VERILATOR #include "verilated.h" #endif @@ -258,6 +260,626 @@ int _mon_check_big() { return 0; } +int _mon_check_unpacked_struct_members() { + const char* p; + PLI_INT32 d; + t_vpi_value tmpValue; + + // unpacked struct port members + tmpValue.format = vpiIntVal; + { + TestVpiHandle vh101 = VPI_HANDLE("unpacked_struct_port"); + CHECK_RESULT_NZ(vh101); + d = vpi_get(vpiType, vh101); + CHECK_RESULT(d, vpiStructVar); + CHECK_RESULT(vpi_get(vpiSize, vh101), 0); + CHECK_RESULT(vpi_get(vpiPacked, vh101), 0); + CHECK_RESULT_Z(vpi_handle(vpiLeftRange, vh101)); + p = vpi_get_str(vpiType, vh101); + CHECK_RESULT_CSTR(p, "vpiStructVar"); + + TestVpiHandle vh102 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh102); + CHECK_RESULT(vpi_get(vpiType, vh102), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh102), 7); + p = vpi_get_str(vpiName, vh102); + CHECK_RESULT_CSTR(p, "member_a"); + p = vpi_get_str(vpiFullName, vh102); + CHECK_RESULT_CSTR(p, "t.unpacked_struct_port.member_a"); + CHECK_RESULT_NZ(vpi_handle(vpiLeftRange, vh102)); + CHECK_RESULT_Z(vpi_iterate(vpiMember, vh102)); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"member_a", vh102)); + + TestVpiHandle vh103 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_b", NULL); + CHECK_RESULT_NZ(vh103); + CHECK_RESULT(vpi_get(vpiType, vh103), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh103), 1); + + TestVpiHandle vh104 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh101); + CHECK_RESULT_NZ(vh104); + CHECK_RESULT(vpi_get(vpiType, vh104), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh104), 16); + + CHECK_RESULT_Z(vpi_iterate(vpiMember, nullptr)); + TestVpiHandle vh105 = vpi_iterate(vpiMember, vh101); + CHECK_RESULT_NZ(vh105); + CHECK_RESULT(vpi_get(vpiType, vh105), vpiIterator); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh105)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh105.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x35; + vpi_put_value(vh102, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh102, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x35); + + putValue.value.integer = 0x4321; + vpi_put_value(vh104, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh104, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x4321); + + // Members resolve even without the toplevel scope prefix + TestVpiHandle vh106 + = vpi_handle_by_name((PLI_BYTE8*)"unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh106); + CHECK_RESULT(vpi_get(vpiType, vh106), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh106), 7); + vpi_get_value(vh106, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x35); + + TestVpiHandle vh107 + = vpi_handle_by_name((PLI_BYTE8*)"$root.t.unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh107); + CHECK_RESULT(vpi_get(vpiType, vh107), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh107), 7); + + TestVpiHandle vh108 = VPI_HANDLE(""); + CHECK_RESULT_NZ(vh108); + TestVpiHandle vh109 + = vpi_handle_by_name((PLI_BYTE8*)"unpacked_struct_port.member_b", vh108); + CHECK_RESULT_NZ(vh109); + CHECK_RESULT(vpi_get(vpiType, vh109), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh109), 1); + + TestVpiHandle vh100 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_c[7:0]", NULL); + CHECK_RESULT_NZ(vh100); + CHECK_RESULT(vpi_get(vpiType, vh100), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh100), 8); + + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port[7:0]", NULL)); + + CHECK_RESULT_Z( + vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.no_such_member", NULL)); + } + + // Synthetic member vars preserve type-derived flags from the member dtype + { + TestVpiHandle vh114 = VPI_HANDLE("member_flags_struct_signal"); + CHECK_RESULT_NZ(vh114); + CHECK_RESULT(vpi_get(vpiType, vh114), vpiStructVar); + + TestVpiHandle vh115 = vpi_handle_by_name((PLI_BYTE8*)"unsigned_member", vh114); + CHECK_RESULT_NZ(vh115); + CHECK_RESULT(vpi_get(vpiType, vh115), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh115), 7); + CHECK_RESULT(vpi_get(vpiSigned, vh115), 0); + + TestVpiHandle vh116 = vpi_handle_by_name((PLI_BYTE8*)"signed_member", vh114); + CHECK_RESULT_NZ(vh116); + CHECK_RESULT(vpi_get(vpiType, vh116), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh116), 7); + CHECK_RESULT(vpi_get(vpiSigned, vh116), 1); + + TestVpiHandle vh117 = vpi_handle_by_name((PLI_BYTE8*)"bit_member", vh114); + CHECK_RESULT_NZ(vh117); + CHECK_RESULT(vpi_get(vpiType, vh117), vpiBitVar); + CHECK_RESULT(vpi_get(vpiSize, vh117), 1); + CHECK_RESULT(vpi_get(vpiSigned, vh117), 0); + } + + // unpacked union port members + { + TestVpiHandle vh110 = VPI_HANDLE("unpacked_union_port"); + CHECK_RESULT_NZ(vh110); + CHECK_RESULT(vpi_get(vpiType, vh110), vpiUnionVar); + CHECK_RESULT(vpi_get(vpiSize, vh110), 0); + CHECK_RESULT(vpi_get(vpiPacked, vh110), 0); + p = vpi_get_str(vpiType, vh110); + CHECK_RESULT_CSTR(p, "vpiUnionVar"); + + TestVpiHandle vh111 = vpi_handle_by_name((PLI_BYTE8*)"union_byte0", vh110); + CHECK_RESULT_NZ(vh111); + CHECK_RESULT(vpi_get(vpiType, vh111), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh111), 8); + + TestVpiHandle vh112 = vpi_iterate(vpiMember, vh110); + CHECK_RESULT_NZ(vh112); + std::set unionMembers; + while (TestVpiHandle member = vpi_scan(vh112)) { + unionMembers.insert(vpi_get_str(vpiName, member)); + } + vh112.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(unionMembers.count("union_byte0"), 1); + CHECK_RESULT(unionMembers.count("union_byte1"), 1); + } + + // nested unpacked struct port members + { + TestVpiHandle vh120 = VPI_HANDLE("nested_struct_port"); + CHECK_RESULT_NZ(vh120); + CHECK_RESULT(vpi_get(vpiType, vh120), vpiStructVar); + + // The nested struct member is itself a struct + TestVpiHandle vh121 = vpi_handle_by_name((PLI_BYTE8*)"outer_inner", vh120); + CHECK_RESULT_NZ(vh121); + CHECK_RESULT(vpi_get(vpiType, vh121), vpiStructVar); + CHECK_RESULT(vpi_get(vpiSize, vh121), 0); + + // Leaf reachable via the full hierarchical name + TestVpiHandle vh122 + = vpi_handle_by_name((PLI_BYTE8*)"t.nested_struct_port.outer_inner.inner_x", NULL); + CHECK_RESULT_NZ(vh122); + CHECK_RESULT(vpi_get(vpiType, vh122), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh122), 4); + + // Leaf reachable relative to the intermediate struct handle + TestVpiHandle vh123 = vpi_handle_by_name((PLI_BYTE8*)"inner_y", vh121); + CHECK_RESULT_NZ(vh123); + CHECK_RESULT(vpi_get(vpiType, vh123), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh123), 4); + + // Iterating the outer struct yields direct members only, not grandchildren + TestVpiHandle vh124 = vpi_iterate(vpiMember, vh120); + CHECK_RESULT_NZ(vh124); + std::set nestedMembers; + while (TestVpiHandle member = vpi_scan(vh124)) { + nestedMembers.insert(vpi_get_str(vpiName, member)); + } + vh124.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(nestedMembers.count("outer_a"), 1); + CHECK_RESULT(nestedMembers.count("outer_inner"), 1); + CHECK_RESULT(nestedMembers.count("inner_x"), 0); + + TestVpiHandle vh125 = vpi_handle_by_name((PLI_BYTE8*)"t.sub.subsig1", NULL); + CHECK_RESULT_NZ(vh125); + CHECK_RESULT(vpi_get(vpiType, vh125), vpiReg); + + // Write through a leaf of the nested struct + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xa; + vpi_put_value(vh122, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh122, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xa); + } + + // unpacked struct port with multidimensional packed-array members + { + TestVpiHandle vh130 = VPI_HANDLE("struct_with_packed_arrays_port"); + CHECK_RESULT_NZ(vh130); + CHECK_RESULT(vpi_get(vpiType, vh130), vpiStructVar); + + TestVpiHandle vh131 = vpi_handle_by_name((PLI_BYTE8*)"packed_matrix", vh130); + CHECK_RESULT_NZ(vh131); + CHECK_RESULT(vpi_get(vpiType, vh131), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh131), 512); + + TestVpiHandle vh132 = vpi_handle_by_index(vh131, 2); + CHECK_RESULT_NZ(vh132); + CHECK_RESULT(vpi_get(vpiSize, vh132), 32); + + TestVpiHandle vh133 = vpi_handle_by_index(vh132, 2); + CHECK_RESULT_NZ(vh133); + CHECK_RESULT(vpi_get(vpiSize, vh133), 8); + + TestVpiHandle vh134 = vpi_handle_by_name((PLI_BYTE8*)"reverse_matrix", vh130); + CHECK_RESULT_NZ(vh134); + CHECK_RESULT(vpi_get(vpiType, vh134), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh134), 128); + + TestVpiHandle vh135 = vpi_handle_by_index(vh134, -2); + CHECK_RESULT_NZ(vh135); + CHECK_RESULT(vpi_get(vpiSize, vh135), 8); + } + + // port array of unpacked structs + { + TestVpiHandle vh140 = VPI_HANDLE("struct_array_port"); + CHECK_RESULT_NZ(vh140); + CHECK_RESULT(vpi_get(vpiType, vh140), vpiRegArray); + + TestVpiHandle vh141 = vpi_handle_by_index(vh140, 0); + CHECK_RESULT_NZ(vh141); + CHECK_RESULT(vpi_get(vpiType, vh141), vpiStructVar); + + TestVpiHandle vh142 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh141); + CHECK_RESULT_NZ(vh142); + CHECK_RESULT(vpi_get(vpiType, vh142), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh142), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x6789; + vpi_put_value(vh142, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh142, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6789); + + TestVpiHandle vh144 = vpi_handle_by_index(vh140, 1); + CHECK_RESULT_NZ(vh144); + TestVpiHandle vh145 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh144); + CHECK_RESULT_NZ(vh145); + putValue.value.integer = 0x2468; + vpi_put_value(vh145, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh145, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x2468); + vpi_get_value(vh142, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6789); + + TestVpiHandle vh146 + = vpi_handle_by_name((PLI_BYTE8*)"struct_array_port[1].member_c", nullptr); + CHECK_RESULT_NZ(vh146); + vpi_get_value(vh146, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x2468); + + TestVpiHandle vh143 = vpi_iterate(vpiMember, vh141); + CHECK_RESULT_NZ(vh143); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh143)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh143.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + } + + // multidimensional port array of unpacked structs + { + TestVpiHandle vh150 = VPI_HANDLE("struct_matrix_port"); + CHECK_RESULT_NZ(vh150); + CHECK_RESULT(vpi_get(vpiType, vh150), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh150), 6); + + TestVpiHandle vh151 = vpi_handle_by_index(vh150, 1); + CHECK_RESULT_NZ(vh151); + CHECK_RESULT(vpi_get(vpiType, vh151), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh151), 3); + + TestVpiHandle vh152 = vpi_handle_by_index(vh151, 2); + CHECK_RESULT_NZ(vh152); + CHECK_RESULT(vpi_get(vpiType, vh152), vpiStructVar); + + TestVpiHandle vh153 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh152); + CHECK_RESULT_NZ(vh153); + CHECK_RESULT(vpi_get(vpiType, vh153), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh153), 16); + p = vpi_get_str(vpiName, vh153); + CHECK_RESULT_CSTR(p, "member_c"); + p = vpi_get_str(vpiFullName, vh153); + { + const std::string expectedFullName + = std::string{vpi_get_str(vpiFullName, vh152)} + ".member_c"; + CHECK_RESULT_CSTR(p, expectedFullName.c_str()); + } + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x1357; + vpi_put_value(vh153, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh153, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x1357); + + TestVpiHandle vh154 + = vpi_handle_by_name((PLI_BYTE8*)"struct_matrix_port[1][2].member_c", nullptr); + CHECK_RESULT_NZ(vh154); + vpi_get_value(vh154, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x1357); + } + + // unpacked struct net port members + { + TestVpiHandle vh190 = VPI_HANDLE("wire_unpacked_struct_port"); + CHECK_RESULT_NZ(vh190); + CHECK_RESULT(vpi_get(vpiType, vh190), vpiStructNet); + CHECK_RESULT(vpi_get(vpiPacked, vh190), 0); + p = vpi_get_str(vpiType, vh190); + CHECK_RESULT_CSTR(p, "vpiStructNet"); + + TestVpiHandle vh191 = vpi_handle_by_name((PLI_BYTE8*)"member_a", vh190); + CHECK_RESULT_NZ(vh191); + CHECK_RESULT(vpi_get(vpiType, vh191), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh191), 7); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x25; + vpi_put_value(vh191, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh191, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x25); + + TestVpiHandle vh192 = vpi_iterate(vpiMember, vh190); + CHECK_RESULT_NZ(vh192); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh192)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh192.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + } + + // net array of unpacked structs + { + TestVpiHandle vh193 = VPI_HANDLE("wire_struct_array_port"); + CHECK_RESULT_NZ(vh193); + CHECK_RESULT(vpi_get(vpiType, vh193), vpiNetArray); + + TestVpiHandle vh194 = vpi_handle_by_index(vh193, 0); + CHECK_RESULT_NZ(vh194); + CHECK_RESULT(vpi_get(vpiType, vh194), vpiStructNet); + CHECK_RESULT(vpi_get(vpiPacked, vh194), 0); + + TestVpiHandle vh195 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh194); + CHECK_RESULT_NZ(vh195); + CHECK_RESULT(vpi_get(vpiType, vh195), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh195), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x579b; + vpi_put_value(vh195, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh195, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x579b); + } + + // non-port array of unpacked structs + { + TestVpiHandle vh160 = VPI_HANDLE("struct_array_signal"); + CHECK_RESULT_NZ(vh160); + CHECK_RESULT(vpi_get(vpiType, vh160), vpiRegArray); + + TestVpiHandle vh161 = vpi_handle_by_index(vh160, 1); + CHECK_RESULT_NZ(vh161); + CHECK_RESULT(vpi_get(vpiType, vh161), vpiStructVar); + + TestVpiHandle vh162 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh161); + CHECK_RESULT_NZ(vh162); + CHECK_RESULT(vpi_get(vpiType, vh162), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh162), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xace0; + vpi_put_value(vh162, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh163 = vpi_handle_by_name( + const_cast(TestSimulator::rooted("struct_array_signal[1].member_c")), + nullptr); + CHECK_RESULT_NZ(vh163); + vpi_get_value(vh163, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xace0); + } + + // array of unpacked structs with unpacked-array members + { + TestVpiHandle vh170 = VPI_HANDLE("parent_struct_array"); + CHECK_RESULT_NZ(vh170); + CHECK_RESULT(vpi_get(vpiType, vh170), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh170), 2); + + TestVpiHandle vh171 = vpi_handle_by_index(vh170, 0); + CHECK_RESULT_NZ(vh171); + CHECK_RESULT(vpi_get(vpiType, vh171), vpiStructVar); + + TestVpiHandle vh172 = vpi_handle_by_index(vh170, 1); + CHECK_RESULT_NZ(vh172); + CHECK_RESULT(vpi_get(vpiType, vh172), vpiStructVar); + + TestVpiHandle vh173 = vpi_handle_by_name((PLI_BYTE8*)"tail_array", vh171); + CHECK_RESULT_NZ(vh173); + CHECK_RESULT(vpi_get(vpiType, vh173), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh173), 4); + + TestVpiHandle vh174 = vpi_handle_by_index(vh173, 3); + CHECK_RESULT_NZ(vh174); + CHECK_RESULT(vpi_get(vpiType, vh174), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh174), 8); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xaa; + vpi_put_value(vh174, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh17a = vpi_handle_by_name((PLI_BYTE8*)"trailing_children", vh171); + CHECK_RESULT_NZ(vh17a); + CHECK_RESULT(vpi_get(vpiType, vh17a), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh17a), 2); + + TestVpiHandle vh17b = vpi_handle_by_index(vh17a, 2); + CHECK_RESULT_NZ(vh17b); + CHECK_RESULT(vpi_get(vpiType, vh17b), vpiStructVar); + + TestVpiHandle vh17c = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh17b); + CHECK_RESULT_NZ(vh17c); + CHECK_RESULT(vpi_get(vpiType, vh17c), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh17c), 8); + putValue.value.integer = 0x11; + vpi_put_value(vh17c, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh175 = vpi_handle_by_name((PLI_BYTE8*)"scalar", vh172); + CHECK_RESULT_NZ(vh175); + CHECK_RESULT(vpi_get(vpiType, vh175), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh175), 16); + putValue.value.integer = 0x55cc; + vpi_put_value(vh175, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh175, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x55cc); + + vpi_get_value(vh174, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xaa); + vpi_get_value(vh17c, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x11); + + TestVpiHandle vh176 = vpi_handle_by_name((PLI_BYTE8*)"children", vh172); + CHECK_RESULT_NZ(vh176); + CHECK_RESULT(vpi_get(vpiType, vh176), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh176), 2); + + TestVpiHandle vh177 = vpi_handle_by_index(vh176, 3); + CHECK_RESULT_NZ(vh177); + CHECK_RESULT(vpi_get(vpiType, vh177), vpiStructVar); + + TestVpiHandle vh178 = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh177); + CHECK_RESULT_NZ(vh178); + CHECK_RESULT(vpi_get(vpiType, vh178), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh178), 8); + putValue.value.integer = 0x5a; + vpi_put_value(vh178, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh178, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x5a); + + TestVpiHandle vh179 + = vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].children[3].child_leaf")), + nullptr); + CHECK_RESULT_NZ(vh179); + vpi_get_value(vh179, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x5a); + + TestVpiHandle vh17d = vpi_handle_by_name((PLI_BYTE8*)"trailing_children", vh172); + CHECK_RESULT_NZ(vh17d); + TestVpiHandle vh17e = vpi_handle_by_index(vh17d, 2); + CHECK_RESULT_NZ(vh17e); + TestVpiHandle vh17f = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh17e); + CHECK_RESULT_NZ(vh17f); + putValue.value.integer = 0x6b; + vpi_put_value(vh17f, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh17f, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6b); + + TestVpiHandle vh17g + = vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].trailing_children[2].child_leaf")), + nullptr); + CHECK_RESULT_NZ(vh17g); + vpi_get_value(vh17g, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6b); + + CHECK_RESULT_Z(vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].children[3:2].child_leaf")), + nullptr)); + } + + // array of unpacked structs with members requiring different C++ alignments + { + TestVpiHandle vh180 = VPI_HANDLE("aligned_struct_array"); + CHECK_RESULT_NZ(vh180); + CHECK_RESULT(vpi_get(vpiType, vh180), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh180), 2); + + TestVpiHandle vh181 = vpi_handle_by_index(vh180, 1); + CHECK_RESULT_NZ(vh181); + CHECK_RESULT(vpi_get(vpiType, vh181), vpiStructVar); + + TestVpiHandle vh182 = vpi_handle_by_name((PLI_BYTE8*)"word_member", vh181); + CHECK_RESULT_NZ(vh182); + CHECK_RESULT(vpi_get(vpiType, vh182), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh182), 32); + + TestVpiHandle vh183 = vpi_handle_by_name((PLI_BYTE8*)"quad_member", vh181); + CHECK_RESULT_NZ(vh183); + CHECK_RESULT(vpi_get(vpiType, vh183), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh183), 64); + + TestVpiHandle vh184 = vpi_handle_by_name((PLI_BYTE8*)"real_member", vh181); + CHECK_RESULT_NZ(vh184); + CHECK_RESULT(vpi_get(vpiType, vh184), vpiRealVar); + + TestVpiHandle vh185 = vpi_handle_by_name((PLI_BYTE8*)"string_member", vh181); + CHECK_RESULT_NZ(vh185); + CHECK_RESULT(vpi_get(vpiType, vh185), vpiStringVar); + + TestVpiHandle vh186 = vpi_handle_by_name((PLI_BYTE8*)"nested_member", vh181); + CHECK_RESULT_NZ(vh186); + CHECK_RESULT(vpi_get(vpiType, vh186), vpiStructVar); + + TestVpiHandle vh187 = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh186); + CHECK_RESULT_NZ(vh187); + CHECK_RESULT(vpi_get(vpiType, vh187), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh187), 8); + } + + // Array stride for a struct whose C++ size needs tail padding after a nested struct + { + TestVpiHandle vh188a = VPI_HANDLE("alignment_stride_array"); + CHECK_RESULT_NZ(vh188a); + CHECK_RESULT(vpi_get(vpiType, vh188a), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh188a), 2); + + TestVpiHandle vh188b = vpi_handle_by_index(vh188a, 1); + CHECK_RESULT_NZ(vh188b); + CHECK_RESULT(vpi_get(vpiType, vh188b), vpiStructVar); + + TestVpiHandle vh188c = vpi_handle_by_name((PLI_BYTE8*)"tail", vh188b); + CHECK_RESULT_NZ(vh188c); + CHECK_RESULT(vpi_get(vpiType, vh188c), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh188c), 8); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xc3; + vpi_put_value(vh188c, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh188c, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xc3); + } + + TestVpiHandle vh188 = VPI_HANDLE("nested_struct_port"); + CHECK_RESULT_NZ(vh188); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"outer_inner.no_such", vh188)); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.\\no.such escaped.member", nullptr)); + + // Unpacked struct member with an escaped identifier containing a dot + { + TestVpiHandle vh200 = VPI_HANDLE("escaped_member_struct_signal"); + CHECK_RESULT_NZ(vh200); + CHECK_RESULT(vpi_get(vpiType, vh200), vpiStructVar); + + TestVpiHandle vh201 = vpi_handle_by_name((PLI_BYTE8*)"\\a.b ", vh200); + CHECK_RESULT_NZ(vh201); + CHECK_RESULT(vpi_get(vpiType, vh201), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh201), 8); + + TestVpiHandle vh202 + = vpi_handle_by_name((PLI_BYTE8*)"t.escaped_member_struct_signal.\\a.b ", nullptr); + CHECK_RESULT_NZ(vh202); + CHECK_RESULT(vpi_get(vpiType, vh202), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh202), 8); + + TestVpiHandle vh203 = vpi_iterate(vpiMember, vh200); + CHECK_RESULT_NZ(vh203); + std::set escapedMembers; + while (TestVpiHandle member = vpi_scan(vh203)) { + escapedMembers.insert(vpi_get_str(vpiName, member)); + } + vh203.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(escapedMembers.count("\\a.b "), 1); + CHECK_RESULT(escapedMembers.count("plain_after"), 1); + } + + return 0; +} + int _mon_check_var() { TestVpiHandle vh1 = VPI_HANDLE("onebit"); CHECK_RESULT_NZ(vh1); @@ -1543,6 +2165,9 @@ extern "C" int mon_check() { printf("-mon_check()\n"); #endif +#if !defined(T_VPI_VAR2) && !defined(T_VPI_VAR3) + if (int status = _mon_check_unpacked_struct_members()) return status; +#endif if (int status = _mon_check_mcd()) return status; if (int status = _mon_check_callbacks()) return status; if (int status = _mon_check_value_callbacks()) return status; @@ -1607,6 +2232,7 @@ int main(int argc, char** argv) { uint64_t sim_time = 1100; contextp->debug(0); contextp->commandArgs(argc, argv); + VerilatedVpi::selfTest(); const std::unique_ptr topp{new VM_PREFIX{contextp.get(), // Note null name - we're flattening it out diff --git a/test_regress/t/t_vpi_var.v b/test_regress/t/t_vpi_var.v index e115a1c4d..ea66b0712 100644 --- a/test_regress/t/t_vpi_var.v +++ b/test_regress/t/t_vpi_var.v @@ -16,7 +16,9 @@ module t (/*AUTOARG*/ // Outputs x, // Inputs - clk, a + clk, a, unpacked_struct_port, unpacked_union_port, nested_struct_port, + struct_array_port, struct_matrix_port, struct_with_packed_arrays_port, + wire_unpacked_struct_port, wire_struct_array_port ); `ifdef VERILATOR @@ -54,6 +56,99 @@ extern "C" int mon_check(); // verilator lint_on ASCRANGE reg unpacked_only[7:0]; + typedef struct { + logic [6:0] member_a; + logic member_b; + logic [15:0] member_c; + } unpacked_struct_t; + + input unpacked_struct_t unpacked_struct_port /*verilator public_flat_rw*/; + input wire unpacked_struct_t wire_unpacked_struct_port /*verilator public_flat_rw*/; + + typedef union { + logic [7:0] union_byte0; + logic [7:0] union_byte1; + } unpacked_union_t; + + input unpacked_union_t unpacked_union_port /*verilator public_flat_rw*/; + + typedef struct { + logic [3:0] inner_x; + logic [3:0] inner_y; + } inner_struct_t; + + typedef struct { + logic [7:0] outer_a; + inner_struct_t outer_inner; + } nested_struct_t; + + input nested_struct_t nested_struct_port /*verilator public_flat_rw*/; + + input unpacked_struct_t struct_array_port [1:0] /*verilator public_flat_rw*/; + input unpacked_struct_t struct_matrix_port [1:0][2:0] /*verilator public_flat_rw*/; + input wire unpacked_struct_t wire_struct_array_port [1:0] /*verilator public_flat_rw*/; + unpacked_struct_t struct_array_signal [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [6:0] unsigned_member; + logic signed [6:0] signed_member; + bit bit_member; + } member_flags_struct_t; + + member_flags_struct_t member_flags_struct_signal /*verilator public_flat_rw*/; + + typedef struct { + logic [7:0] child_leaf; + } child_struct_t; + + typedef struct { + logic [15:0] scalar; + child_struct_t children [3:2]; + logic [7:0] tail_array [2:5]; + child_struct_t trailing_children [3:2]; + } parent_struct_t; + + parent_struct_t parent_struct_array [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [7:0] \a.b ; + logic [7:0] plain_after; + } escaped_member_struct_t; + + escaped_member_struct_t escaped_member_struct_signal /*verilator public_flat_rw*/; + + typedef struct { + logic [31:0] word_member; + logic [63:0] quad_member; + real real_member; + string string_member; + child_struct_t nested_member; + } aligned_struct_t; + + aligned_struct_t aligned_struct_array [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [63:0] quad_leaf; + logic [7:0] byte_tail; + } stride_inner_struct_t; + + typedef struct { + logic [7:0] lead; + stride_inner_struct_t nested; + logic [7:0] tail; + } stride_outer_struct_t; + + stride_outer_struct_t alignment_stride_array [1:0] /*verilator public_flat_rw*/; + + // verilator lint_off ASCRANGE + typedef struct { + logic [0:15][0:3][7:0] packed_matrix; + logic [8:-7][3:-4] reverse_matrix; + } struct_with_packed_arrays_t; + // verilator lint_on ASCRANGE + + input struct_with_packed_arrays_t struct_with_packed_arrays_port /*verilator public_flat_rw*/; + reg [7:0] text_byte /*verilator public_flat_rw @(posedge clk) */; reg [15:0] text_half /*verilator public_flat_rw @(posedge clk) */; reg [31:0] text_word /*verilator public_flat_rw @(posedge clk) */; @@ -166,6 +261,7 @@ extern "C" int mon_check(); if (text != "lorem ipsum") $stop; if (str1 != "something a lot longer than hello") $stop; if (real1 > 123456.7895 || real1 < 123456.7885 ) $stop; + if (alignment_stride_array[1].tail != 8'hc3) $stop; end always @(posedge clk) begin diff --git a/test_regress/t/t_wrapper_context__top0.dat.out b/test_regress/t/t_wrapper_context__top0.dat.out index 07d4800da..ae165bca9 100644 --- a/test_regress/t/t_wrapper_context__top0.dat.out +++ b/test_regress/t/t_wrapper_context__top0.dat.out @@ -139,12 +139,12 @@ C 'ft/t_wrapper_context.vl21n3tlinepagev_line/topoblockS21-24,28,3 C 'ft/t_wrapper_context.vl35n3tlinepagev_line/topoblockS35htop0.top' 11 C 'ft/t_wrapper_context.vl36n5tbranchpagev_branch/topoifS36htop0.top' 1 C 'ft/t_wrapper_context.vl36n6tbranchpagev_branch/topoelseS37htop0.top' 10 -C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop0.top' 34 +C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop0.top' 13 C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop0.top' 0 -C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop0.top' 34 +C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop0.top' 13 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop0.top' 0 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==1 && stop==1) => 1htop0.top' 0 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo(stop==0) => 0htop0.top' 0 C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop0.top' 0 C 'ft/t_wrapper_context.vl48n7tbranchpagev_branch/topoifS48-50htop0.top' 1 -C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop0.top' 33 +C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop0.top' 12 diff --git a/test_regress/t/t_wrapper_context__top1.dat.out b/test_regress/t/t_wrapper_context__top1.dat.out index 8ef66f3d0..073c8c4b5 100644 --- a/test_regress/t/t_wrapper_context__top1.dat.out +++ b/test_regress/t/t_wrapper_context__top1.dat.out @@ -139,12 +139,12 @@ C 'ft/t_wrapper_context.vl21n3tlinepagev_line/topoblockS21-24,28,3 C 'ft/t_wrapper_context.vl35n3tlinepagev_line/topoblockS35htop1.top' 6 C 'ft/t_wrapper_context.vl36n5tbranchpagev_branch/topoifS36htop1.top' 1 C 'ft/t_wrapper_context.vl36n6tbranchpagev_branch/topoelseS37htop1.top' 5 -C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop1.top' 19 -C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop1.top' 19 +C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop1.top' 8 +C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop1.top' 8 C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop1.top' 0 -C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop1.top' 18 +C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop1.top' 7 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==1 && stop==1) => 1htop1.top' 1 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo(stop==0) => 0htop1.top' 0 -C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop1.top' 18 +C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop1.top' 7 C 'ft/t_wrapper_context.vl48n7tbranchpagev_branch/topoifS48-50htop1.top' 0 C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop1.top' 0