diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..bd9dfe4ef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,2 @@ +--- +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md index 121f9d197..53cfabfe1 100644 --- a/.github/ISSUE_TEMPLATE/issue.md +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -7,18 +7,34 @@ assignees: '' --- + + +Can you please attach an example that shows the issue or missing feature? + + What output from that test indicates it is wrong, and what is the correct or expected output? (Or, please make test self-checking if possible.) + + What 'verilator' command line do we use to run your example? + + What 'verilator --version' are you using? Did you try it with the git master version? Did you try it with other simulators? + + What OS and distribution are you using? + + May we assist you in trying to fix this in Verilator yourself? -(Please avoid attaching screenshots that show text - you can convert images to text using e.g. https://ocr.space) + diff --git a/.github/ISSUE_TEMPLATE/questions.md b/.github/ISSUE_TEMPLATE/questions.md index c162a5ff8..a9ed1beee 100644 --- a/.github/ISSUE_TEMPLATE/questions.md +++ b/.github/ISSUE_TEMPLATE/questions.md @@ -7,6 +7,14 @@ assignees: '' --- + + How may we help - what is your question? -(If reporting a bug or requesting a feature please hit BACK on your browser and use a different issue templates.) + diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1724ebf18..439143c85 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -182,7 +182,7 @@ jobs: uses: actions/upload-artifact@v7 with: path: repo/notification - name: coverage-pr-notification + name: pr-notification # Create GitHub issue for failed scheduled jobs # This should always be the last job (we want an issue if anything breaks) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index e027c7a04..eefc544a1 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -38,11 +38,11 @@ jobs: ./configure make venv source .venv/bin/activate - make -j 2 format CLANGFORMAT=clang-format-18 + make -j 4 format CLANGFORMAT=clang-format-18 git status - name: Push run: |- if [ -n "$(git status --porcelain)" ]; then - git commit . -m "Apply 'make format'" && + git commit . -m "Apply 'make format' [ci skip]" && git push origin fi diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 8b53d95b6..4d1523ac2 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -10,7 +10,7 @@ on: paths: ["ci/**", ".github/workflows"] workflow_dispatch: workflow_run: - workflows: ["Code coverage"] + workflows: ["Code coverage", "RTLMeter"] types: [completed] # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages @@ -35,7 +35,7 @@ jobs: name: Build content runs-on: ubuntu-24.04 outputs: - coverage-pr-run-ids: ${{ steps.build.outputs.coverage-pr-run-ids }} + pr-run-ids: ${{ steps.build.outputs.pr-run-ids }} steps: - name: Checkout uses: actions/checkout@v6 @@ -44,7 +44,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - bash -x ./ci/ci-pages.bash + ./ci/ci-pages.bash ls -lsha tree -L 3 pages - name: Upload pages artifact @@ -83,5 +83,5 @@ jobs: - name: Comment on PR env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} - COVERAGE_PR_RUN_IDS: ${{ needs.build.outputs.coverage-pr-run-ids }} - run: bash -x ./ci/ci-pages-notify.bash + PR_RUN_IDS: ${{ needs.build.outputs.pr-run-ids }} + run: ./ci/ci-pages-notify.bash diff --git a/.github/workflows/reusable-rtlmeter-build.yml b/.github/workflows/reusable-rtlmeter-build.yml index 85deb9706..321033a72 100644 --- a/.github/workflows/reusable-rtlmeter-build.yml +++ b/.github/workflows/reusable-rtlmeter-build.yml @@ -15,6 +15,14 @@ on: description: "Compiler to use: 'gcc' or 'clang'" type: string required: true + sha: + description: "Git SHA to build" + type: string + required: true + outputs: + archive: + description: "Name of the built installation archive artifact" + value: ${{ jobs.build.outputs.archive }} defaults: run: @@ -26,8 +34,10 @@ env: jobs: build: - runs-on: ${{ inputs.runs-on }} name: Build + runs-on: ${{ inputs.runs-on }} + outputs: + archive: ${{ steps.create-archive.outputs.archive }} steps: - name: Install dependencies run: | @@ -43,13 +53,17 @@ jobs: uses: actions/cache@v5 with: path: ccache - key: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }} + key: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: | + rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }} + rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }} + rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }} - name: Checkout uses: actions/checkout@v6 with: path: repo + ref: ${{ inputs.sha }} fetch-depth: 0 # Required for 'git describe' used for 'verilator --version' - name: Configure @@ -67,11 +81,16 @@ jobs: run: make install - name: Tar up installation - run: tar --posix -c -z -f verilator-rtlmeter.tar.gz install + id: create-archive + run: | + SHA=$(git -C repo rev-parse HEAD) + ARCHIVE=verilator-$SHA-rtlmeter-${{ inputs.runs-on }}-${{ inputs.cc }}.tar.gz + tar --posix -c -z -f $ARCHIVE install + echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT - name: Upload Verilator installation archive uses: actions/upload-artifact@v7 with: - path: verilator-rtlmeter.tar.gz - name: verilator-rtlmeter-${{ inputs.runs-on }}-${{ inputs.cc }} + path: ${{ steps.create-archive.outputs.archive }} + name: ${{ steps.create-archive.outputs.archive }} overwrite: true diff --git a/.github/workflows/reusable-rtlmeter-run.yml b/.github/workflows/reusable-rtlmeter-run.yml index 5e163f555..ebbaffef8 100644 --- a/.github/workflows/reusable-rtlmeter-run.yml +++ b/.github/workflows/reusable-rtlmeter-run.yml @@ -19,6 +19,15 @@ on: description: "Compiler to use: 'gcc' or 'clang'" type: string required: true + verilator-archive-new: + description: "Name of the installation archive artifact from reusable-rtlmeter-build, new version" + type: string + required: true + verilator-archive-old: + description: "Name of the installation archive artifact from reusable-rtlmeter-build, old version" + type: string + required: false + default: "" # Note: The combination of 'cases' and 'run-name' must be unique for all # invocations of this workflow within a run of the parent workflow. # These two are used together to generate a unique results file name. @@ -63,24 +72,6 @@ jobs: sudo apt install ccache mold libfl-dev libjemalloc-dev libsystemc-dev || \ sudo apt install ccache mold libfl-dev libjemalloc-dev libsystemc-dev - - name: Download Verilator installation archive - uses: actions/download-artifact@v8 - with: - name: verilator-rtlmeter-${{ inputs.runs-on }}-${{ inputs.cc }} - - - name: Unpack Verilator installation archive - run: | - tar -x -z -f verilator-rtlmeter.tar.gz - echo "${{ github.workspace }}/install/bin" >> $GITHUB_PATH - - - name: Use saved ccache - if: ${{ env.CCACHE_DISABLE == 0 }} - uses: actions/cache@v5 - with: - path: ${{ env.CCACHE_DIR }} - key: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }} - - name: Checkout RTLMeter uses: actions/checkout@v6 with: @@ -91,32 +82,59 @@ jobs: working-directory: rtlmeter run: make venv - - name: Compile cases + - name: Use saved ccache + if: ${{ env.CCACHE_DISABLE == 0 }} + uses: actions/cache@v5 + with: + path: ${{ env.CCACHE_DIR }} + key: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }}-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }} + + ######################################################################## + # Run with new Verilator + ######################################################################## + + - name: Download Verilator installation archive - new + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.verilator-archive-new }} + + - name: Unpack Verilator installation archive - new + run: | + tar -x -z -f ${{ inputs.verilator-archive-new }} + mv install verilator-new + + - name: Compile cases - new working-directory: rtlmeter run: | + export PATH="${{ github.workspace }}/verilator-new/bin:$PATH" ./rtlmeter run --timeout 60 --verbose --cases='${{inputs.cases}}' --compileArgs='${{inputs.compileArgs}}' --executeArgs='${{inputs.executeArgs}}' --nExecute=0 + ccache -svv - - name: Execute cases + - name: Execute cases - new working-directory: rtlmeter continue-on-error: true # Do not fail on error, so we can at least save the successful results run: | + export PATH="${{ github.workspace }}/verilator-new/bin:$PATH" ./rtlmeter run --timeout 60 --verbose --cases='${{inputs.cases}}' --compileArgs='${{inputs.compileArgs}}' --executeArgs='${{inputs.executeArgs}}' - - name: Collate results + - name: Collate results - new id: results working-directory: rtlmeter run: | + export PATH="${{ github.workspace }}/verilator-new/bin:$PATH" # Use 'inputs.cases' and 'inputs.run-name' to generate a unique file name hash=$(md5sum <<< '${{ inputs.cases }} ${{ inputs.run-name }}' | awk '{print $1}') echo "hash=${hash}" >> $GITHUB_OUTPUT ./rtlmeter collate --runName "${{ inputs.run-name }}" > ../results-${hash}.json - - name: Report results + - name: Report results - new working-directory: rtlmeter run: | + export PATH="${{ github.workspace }}/verilator-new/bin:$PATH" ./rtlmeter report --steps '*' --metrics '*' ../results-${{ steps.results.outputs.hash }}.json - - name: Upload results + - name: Upload results - new uses: actions/upload-artifact@v7 with: path: results-${{ steps.results.outputs.hash }}.json @@ -124,7 +142,65 @@ jobs: overwrite: true retention-days: 2 - - name: Report status + - name: Report status - new working-directory: rtlmeter run: |- # This will fail the job if any of the runs failed + export PATH="${{ github.workspace }}/verilator-new/bin:$PATH" ./rtlmeter run --verbose --cases='${{inputs.cases}}' --compileArgs='${{inputs.compileArgs}}' --executeArgs='${{inputs.executeArgs}}' + # Clean up for run with old version + rm -rf work + rm -rf ${{ github.workspace }}/verilator-new + + ######################################################################## + # Run with old Verilator **on same machine for consistency** + ######################################################################## + + - name: Download Verilator installation archive - old + if: ${{ inputs.verilator-archive-old != '' }} + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.verilator-archive-old }} + + - name: Unpack Verilator installation archive - old + if: ${{ inputs.verilator-archive-old != '' }} + run: | + tar -x -z -f ${{ inputs.verilator-archive-old }} + mv install verilator-old + + - name: Compile cases - old + if: ${{ inputs.verilator-archive-old != '' }} + working-directory: rtlmeter + run: | + export PATH="${{ github.workspace }}/verilator-old/bin:$PATH" + ./rtlmeter run --timeout 60 --verbose --cases='${{inputs.cases}}' --compileArgs='${{inputs.compileArgs}}' --executeArgs='${{inputs.executeArgs}}' --nExecute=0 + ccache -svv + + - name: Execute cases - old + if: ${{ inputs.verilator-archive-old != '' }} + working-directory: rtlmeter + run: | + export PATH="${{ github.workspace }}/verilator-old/bin:$PATH" + ./rtlmeter run --timeout 60 --verbose --cases='${{inputs.cases}}' --compileArgs='${{inputs.compileArgs}}' --executeArgs='${{inputs.executeArgs}}' + + - name: Collate results - old + if: ${{ inputs.verilator-archive-old != '' }} + working-directory: rtlmeter + run: | + export PATH="${{ github.workspace }}/verilator-old/bin:$PATH" + ./rtlmeter collate --runName "${{ inputs.run-name }}" > ../reference-${{ steps.results.outputs.hash }}.json + + - name: Report results - old + if: ${{ inputs.verilator-archive-old != '' }} + working-directory: rtlmeter + run: | + export PATH="${{ github.workspace }}/verilator-old/bin:$PATH" + ./rtlmeter report --steps '*' --metrics '*' ../reference-${{ steps.results.outputs.hash }}.json + + - name: Upload results - old + if: ${{ inputs.verilator-archive-old != '' }} + uses: actions/upload-artifact@v7 + with: + path: reference-${{ steps.results.outputs.hash }}.json + name: rtlmeter-${{ inputs.tag }}-reference-${{ steps.results.outputs.hash }} + overwrite: true + retention-days: 2 diff --git a/.github/workflows/rtlmeter-pr-results.yml b/.github/workflows/rtlmeter-pr-results.yml deleted file mode 100644 index 2a8bbb2a4..000000000 --- a/.github/workflows/rtlmeter-pr-results.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- -# DESCRIPTION: Github actions config -# This name is key to badges in README.rst, so we use the name build -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -name: RTLMeter PR results - -on: - workflow_run: - workflows: [RTLMeter] - types: [completed] - -jobs: - publish: - name: Publish - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} - permissions: - actions: read - pull-requests: write - steps: - - name: Download report - uses: actions/download-artifact@v8 - with: - name: rtlmeter-pr-results - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Download PR number - uses: actions/download-artifact@v8 - with: - name: pr-number - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - # 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 - with: - app-id: ${{ vars.VERILATOR_CI_ID }} - private-key: ${{ secrets.VERILATOR_CI_KEY }} - permission-pull-requests: write - - name: Comment on PR - env: - GH_TOKEN: ${{ steps.generate-token.outputs.token }} - run: |- - ls -la - cat report.txt - gh pr --repo ${{ github.repository }} comment $(cat pr-number.txt) --body-file report.txt diff --git a/.github/workflows/rtlmeter.yml b/.github/workflows/rtlmeter.yml index 372df70ae..d2880b476 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -40,179 +40,229 @@ jobs: (github.event_name == 'workflow_dispatch') || (github.event_name == 'push') runs-on: ubuntu-24.04 + outputs: + old-sha: ${{ steps.start.outputs.old-sha }} + cases: ${{ steps.cases.outputs.cases }} steps: - - name: Startup - run: echo + - name: Checkout + uses: actions/checkout@v6 - build-gcc: - name: Build GCC + - name: Startup + id: start + env: + GH_TOKEN: ${{ github.token }} + run: | + [[ "${{ github.event_name }}" == 'pull_request' ]] || exit 0 + # For a pull request, 'github.sha' is the test merge commit. Its + # first parent is the target branch commit it was merged with for this run. + OLD_SHA="$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}" --jq '.parents[0].sha')" + echo "old-sha=$OLD_SHA" >> "$GITHUB_OUTPUT" + + - name: Load RTLMeter cases + id: cases + run: echo "cases=$(ci/ci-rtlmeter-cases.py --event-name ${{ github.event_name }})" >> "$GITHUB_OUTPUT" + + build-gcc-new: + name: Build New Verilator - GCC needs: start uses: ./.github/workflows/reusable-rtlmeter-build.yml with: runs-on: ubuntu-24.04 cc: gcc + sha: ${{ github.sha }} - build-clang: - name: Build Clang + build-clang-new: + name: Build New Verilator - Clang needs: start uses: ./.github/workflows/reusable-rtlmeter-build.yml with: runs-on: ubuntu-24.04 cc: clang + sha: ${{ github.sha }} + + build-gcc-old: + name: Build Old Verilator - GCC + needs: start + if: ${{ needs.start.outputs.old-sha != '' }} + uses: ./.github/workflows/reusable-rtlmeter-build.yml + with: + runs-on: ubuntu-24.04 + cc: gcc + sha: ${{ needs.start.outputs.old-sha }} + + build-clang-old: + name: Build Old Verilator - Clang + needs: start + if: ${{ needs.start.outputs.old-sha != '' }} + uses: ./.github/workflows/reusable-rtlmeter-build.yml + with: + runs-on: ubuntu-24.04 + cc: clang + sha: ${{ needs.start.outputs.old-sha }} run-gcc: name: Run GCC | ${{ matrix.cases }} - needs: build-gcc + needs: + - start + - build-gcc-new + - build-gcc-old + # Run even if 'build-*-old' was skipped (no old SHA), but not if any + # dependency failed, the workflow was cancelled, or 'start' was skipped + # (e.g. the workflow was not enabled, so there is nothing to run against). + if: ${{ !failure() && !cancelled() && needs.start.result == 'success' }} uses: ./.github/workflows/reusable-rtlmeter-run.yml with: tag: gcc runs-on: ubuntu-24.04 cc: gcc + verilator-archive-new: ${{ needs.build-gcc-new.outputs.archive }} + verilator-archive-old: ${{ needs.build-gcc-old.outputs.archive }} cases: ${{ matrix.cases }} run-name: "gcc" compileArgs: "" executeArgs: "" strategy: - fail-fast: false - max-parallel: ${{ github.event == 'schedule' && 2 || 7 }} + fail-fast: ${{ github.event_name == 'pull_request' }} + max-parallel: 7 matrix: - cases: - - "BlackParrot:1x1:*" - - "BlackParrot:4x4:*" - - "Caliptra:default:*" - - "NVDLA:*" - - "OpenPiton:1x1:*" - - "OpenPiton:4x4:*" - - "OpenTitan:*" - - "Servant:serv:*" - - "Servant:qerv:*" - - "VeeR-EH1:asic*" - - "VeeR-EH1:default*" - - "VeeR-EH1:hiperf*" - - "VeeR-EH2:asic*" - - "VeeR-EH2:default*" - - "VeeR-EH2:hiperf*" - - "VeeR-EL2:asic*" - - "VeeR-EL2:default*" - - "VeeR-EL2:hiperf*" - - "Vortex:mini:*" - - "Vortex:sane:*" - - "XiangShan:default-chisel3:* !*:linux" - - "XiangShan:default-chisel6:* !*:linux" - - "XiangShan:mini-chisel3:* !*:linux" - - "XiangShan:mini-chisel6:* !*:linux" - - "XuanTie-E902:*" - - "XuanTie-E906:*" - - "XuanTie-C906:*" - - "XuanTie-C910:*" + cases: ${{ fromJSON(needs.start.outputs.cases)['gcc'] }} run-clang: name: Run Clang | ${{ matrix.cases }} - needs: build-clang + needs: + - start + - build-clang-new + - build-clang-old + # Run even if 'build-*-old' was skipped (no old SHA), but not if any + # dependency failed, the workflow was cancelled, or 'start' was skipped + # (e.g. the workflow was not enabled, so there is nothing to run against). + if: ${{ !failure() && !cancelled() && needs.start.result == 'success' }} uses: ./.github/workflows/reusable-rtlmeter-run.yml with: tag: clang runs-on: ubuntu-24.04 cc: clang + verilator-archive-new: ${{ needs.build-clang-new.outputs.archive }} + verilator-archive-old: ${{ needs.build-clang-old.outputs.archive }} cases: ${{ matrix.cases }} run-name: "clang --threads 4" compileArgs: "--threads 4" executeArgs: "" strategy: - fail-fast: false - max-parallel: ${{ github.event == 'schedule' && 2 || 7 }} + fail-fast: ${{ github.event_name == 'pull_request' }} + max-parallel: 7 matrix: - cases: - - "BlackParrot:1x1:*" - - "BlackParrot:4x4:*" - - "Caliptra:default:*" - - "NVDLA:*" - - "OpenPiton:1x1:*" - - "OpenPiton:4x4:*" - - "OpenTitan:*" - - "Servant:serv:*" - - "Servant:qerv:*" - - "VeeR-EH1:asic*" - - "VeeR-EH1:default*" - - "VeeR-EH1:hiperf*" - - "VeeR-EH2:asic*" - - "VeeR-EH2:default*" - - "VeeR-EH2:hiperf*" - - "VeeR-EL2:asic*" - - "VeeR-EL2:default*" - - "VeeR-EL2:hiperf*" - - "Vortex:mini:*" - - "Vortex:sane:*" - - "XiangShan:default-chisel3:* !*:linux" - - "XiangShan:default-chisel6:* !*:linux" - - "XiangShan:mini-chisel3:* !*:linux" - - "XiangShan:mini-chisel6:* !*:linux" - - "XuanTie-E902:*" - - "XuanTie-E906:*" - - "XuanTie-C906:*" - - "XuanTie-C910:*" + cases: ${{ fromJSON(needs.start.outputs.cases)['clang'] }} run-gcc-hier: name: Run GCC hier | ${{ matrix.cases }} - needs: build-gcc + needs: + - start + - build-gcc-new + - build-gcc-old + # Run even if 'build-*-old' was skipped (no old SHA), but not if any + # dependency failed, the workflow was cancelled, or 'start' was skipped + # (e.g. the workflow was not enabled, so there is nothing to run against). + if: ${{ !failure() && !cancelled() && needs.start.result == 'success' }} uses: ./.github/workflows/reusable-rtlmeter-run.yml with: tag: gcc-hier runs-on: ubuntu-24.04 cc: gcc + verilator-archive-new: ${{ needs.build-gcc-new.outputs.archive }} + verilator-archive-old: ${{ needs.build-gcc-old.outputs.archive }} cases: ${{ matrix.cases }} run-name: "gcc --hierarchical" compileArgs: "--hierarchical" executeArgs: "" strategy: - fail-fast: false - max-parallel: ${{ github.event == 'schedule' && 2 || 7 }} + fail-fast: ${{ github.event_name == 'pull_request' }} + max-parallel: 6 matrix: - cases: - - "BlackParrot:1x1:* !-hier" - - "BlackParrot:4x4:* !-hier" - - "NVDLA:* !-hier" - - "OpenPiton:1x1:* !-hier" - - "OpenPiton:4x4:* !-hier" - - "OpenPiton:8x8:* !-hier" - - "OpenPiton:16x16:dhry !-hier" - - "XuanTie-C910:* !-hier" + cases: ${{ fromJSON(needs.start.outputs.cases)['gcc-hier'] }} combine-results: name: Combine results needs: [run-gcc, run-clang, run-gcc-hier] - # Run if any of the dependencies have run, even if failed. - # That is: do not run if all skipped, or the workflow was cancelled. - if: ${{ (contains(needs.*.result, 'success') || contains(needs.*.result, 'failure')) && !cancelled() }} + # Skip if cancelled. + # On PRs, run if something succeeded and nothing failed. + # On non-PRs, run if anything succeeded or failed (so partial results are still published). + if: >- + ${{ !cancelled() && ( + (github.event_name == 'pull_request' && (contains(needs.*.result, 'success') && !contains(needs.*.result, 'failure'))) + || (github.event_name != 'pull_request' && (contains(needs.*.result, 'success') || contains(needs.*.result, 'failure'))) + )}} runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - tag: [gcc, clang, gcc-hier] + outputs: + # The run tags, derived from the 'run-*' dependency job names + run-tags: ${{ steps.tags.outputs.tags }} + env: + GH_TOKEN: ${{ github.token }} + # 'gh' resolves the repo from git otherwise, but this job has no checkout + # of this repo at the workspace root + GH_REPO: ${{ github.repository }} steps: + # Derive the run tags from the dependency job names ('run-') + - name: Determine run tags + id: tags + run: echo "tags=$(jq -r 'keys | map(sub("^run-"; "")) | join(" ")' <<< '${{ toJSON(needs) }}')" >> "$GITHUB_OUTPUT" + - name: Checkout RTLMeter uses: actions/checkout@v6 with: repository: "verilator/rtlmeter" path: rtlmeter + - name: Setup RTLMeter venv working-directory: rtlmeter run: make venv + - name: Download all results - uses: actions/download-artifact@v8 - with: - pattern: rtlmeter-${{ matrix.tag }}-results-* - path: all-results-${{ matrix.tag }} - merge-multiple: true + run: | + for tag in ${{ steps.tags.outputs.tags }}; do + mkdir artifacts all-results-$tag + gh run download ${{ github.run_id }} --pattern "rtlmeter-$tag-results-*" --dir artifacts + mv $(find artifacts -name "*.json") all-results-$tag/ + rm -rf artifacts + done - name: Combine results working-directory: rtlmeter run: | - ./rtlmeter collate ../all-results-${{ matrix.tag }}/*.json > ../all-results-${{ matrix.tag }}.json + for tag in ${{ steps.tags.outputs.tags }}; do + ./rtlmeter collate ../all-results-$tag/*.json > ../all-results-$tag.json + done - name: Upload combined results uses: actions/upload-artifact@v7 with: - path: all-results-${{ matrix.tag }}.json - name: all-results-${{ matrix.tag }} + path: all-results-*.json + name: all-results + overwrite: true + retention-days: 30 + + # The reference (old) results only exist for pull requests, where the + # 'build-*-old' jobs run and the run jobs produce '*-reference-*' artifacts. + - name: Download reference results + if: ${{ github.event_name == 'pull_request' }} + run: | + for tag in ${{ steps.tags.outputs.tags }}; do + mkdir artifacts all-reference-$tag + gh run download ${{ github.run_id }} --pattern "rtlmeter-$tag-reference-*" --dir artifacts + mv $(find artifacts -name "*.json") all-reference-$tag/ + rm -rf artifacts + done + - name: Combine reference results + if: ${{ github.event_name == 'pull_request' }} + working-directory: rtlmeter + run: | + for tag in ${{ steps.tags.outputs.tags }}; do + ./rtlmeter collate ../all-reference-$tag/*.json > ../all-reference-$tag.json + done + - name: Upload reference results + if: ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v7 + with: + path: all-reference-*.json + name: all-reference overwrite: true retention-days: 30 @@ -230,9 +280,8 @@ jobs: - name: Download combined results uses: actions/download-artifact@v8 with: - pattern: all-results-* + name: all-results path: results - merge-multiple: true - name: Upload published results uses: actions/upload-artifact@v7 with: @@ -276,7 +325,7 @@ jobs: prepare-pr-results: name: Prepare Pull Request results needs: combine-results - if: ${{ github.event_name == 'pull_request' && github.repository == 'verilator/verilator' && contains(needs.*.result, 'success') && !cancelled() }} + if: ${{ github.event_name == 'pull_request' && github.repository == 'verilator/verilator' && needs.combine-results.result == 'success' }} runs-on: ubuntu-24.04 permissions: actions: read @@ -286,106 +335,51 @@ jobs: with: repository: "verilator/rtlmeter" path: rtlmeter + - name: Setup RTLMeter venv working-directory: rtlmeter run: make venv - - name: Download combined results - uses: actions/download-artifact@v8 + + - name: Checkout Verilator + uses: actions/checkout@v6 with: - pattern: all-results-* - path: all-results - merge-multiple: true - - name: Get scheduled run info - id: scheduled-info - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - ID=$(gh run --repo ${{ github.repository }} list --workflow RTLMeter --event schedule --status success --limit 1 --json databaseId --jq ".[0].databaseId") - echo "id=$ID" >> $GITHUB_OUTPUT - URL=$(gh run --repo ${{ github.repository }} view $ID --json url --jq ".url") - echo "url=$URL" >> $GITHUB_OUTPUT - NUM=$(gh run --repo ${{ github.repository }} view $ID --json number --jq ".number") - echo "num=$NUM" >> $GITHUB_OUTPUT - DATE=$(gh run --repo ${{ github.repository }} view $ID --json createdAt --jq ".createdAt") - echo "date=$DATE" >> $GITHUB_OUTPUT - - name: Download scheduled run results - uses: actions/download-artifact@v8 - with: - name: published-results - path: nightly-results - run-id: ${{ steps.scheduled-info.outputs.id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Compare results - working-directory: rtlmeter - run: | - for tag in gcc clang gcc-hier; do - ADATA=../nightly-results/all-results-${tag}.json - BDATA=../all-results/all-results-${tag}.json - touch ../verilate-${tag}.txt - touch ../execute-${tag}.txt - touch ../cppbuild-${tag}.txt - if [[ ! -e $ADATA ]]; then - continue - fi - ./rtlmeter compare --cases '* !Example:* !*:hello' --steps "verilate" --metrics "elapsed memory" $ADATA $BDATA > ../verilate-${tag}.txt - cat ../verilate-${tag}.txt - ./rtlmeter compare --cases '* !Example:* !*:hello' --steps "execute" --metrics "speed memory elapsed" $ADATA $BDATA > ../execute-${tag}.txt - cat ../execute-${tag}.txt - ./rtlmeter compare --cases '* !Example:* !*:hello' --steps "cppbuild" --metrics "elapsed memory cpu codeSize" $ADATA $BDATA > ../cppbuild-${tag}.txt - cat ../cppbuild-${tag}.txt - done + path: verilator + - name: Create report + id: report + working-directory: verilator env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - set -x - NUM=$(gh run --repo ${{ github.repository }} view ${{ github.run_id }} --json number --jq ".number") - URL=$(gh run --repo ${{ github.repository }} view ${{ github.run_id }} --json url --jq ".url") - echo -n "Performance metrics for PR workflow [#$NUM]($URL) (B) compared to scheduled run" > report.txt - echo -n " [#${{ steps.scheduled-info.outputs.num }}](${{ steps.scheduled-info.outputs.url }}) (A)" >> report.txt - echo " from ${{ steps.scheduled-info.outputs.date }}" >> report.txt - for tag in gcc clang gcc-hier; do - echo "" >> report.txt - if [[ $tag == "gcc" ]]; then - echo "
" >> report.txt - else - echo "
" >> report.txt - fi - echo -n "" >> report.txt - jq -rj ".[0].runName" all-results/all-results-${tag}.json >> report.txt - echo "" >> report.txt - awk -v RS= -v tag=${tag} '{print > sprintf("frag-%02d-verilate-%s.txt",NR,tag)}' verilate-${tag}.txt - awk -v RS= -v tag=${tag} '{print > sprintf("frag-%02d-execute-%s.txt" ,NR,tag)}' execute-${tag}.txt - awk -v RS= -v tag=${tag} '{print > sprintf("frag-$02d-cppbuild-%s.txt",NR,tag)}' cppbuild-${tag}.txt - for f in $(ls -1 frag-*-verilate-${tag}.txt | sort) $(ls -1 frag-*-execute-${tag}.txt | sort) $(ls -1 frag-*-cppbuild-${tag}.txt | sort); do - if [[ $f == frag-01-verilate-${tag}.txt || $f == frag-01-execute-${tag}.txt ]]; then - echo "
" >> report.txt - else - echo "
" >> report.txt - fi - echo -n "" >> report.txt - head -n 1 $f | tr -d '\n' >> report.txt - echo "" >> report.txt - echo '
' >> report.txt
-              tail -n +2 $f >> report.txt
-              echo '
' >> report.txt - echo "
" >> report.txt - done - echo "
" >> report.txt - done - cat report.txt + ln -s ../rtlmeter rtlmeter + gh repo set-default ${{ github.repository }} + # Create run report - save status to fail job if the script did + STATUS=0 + ci/ci-rtlmeter-report.bash ${{ github.run_id }} ${{ github.sha }} ${{ needs.combine-results.outputs.run-tags }} || STATUS=$? + echo "status=$STATUS" >> "$GITHUB_OUTPUT" + # Create the report artifact + mkdir ../report-artifact + mv rtlmeter-report/report ../report-artifact/ + echo ${{ github.event.number }} > ../report-artifact/pr-number.txt + # Create the notification artifact + mkdir ../notification-artifact + mv rtlmeter-report/notification.txt ../notification-artifact/body.txt + echo ${{ github.event.number }} > ../notification-artifact/pr-number.txt + - name: Upload report uses: actions/upload-artifact@v7 with: - path: report.txt - name: rtlmeter-pr-results - - name: Save PR number - run: echo ${{ github.event.number }} > pr-number.txt - - name: Upload PR number + path: report-artifact + name: rtlmeter-report + + - name: Upload notification uses: actions/upload-artifact@v7 with: - path: pr-number.txt - name: pr-number + path: notification-artifact + name: pr-notification + + - name: Report status + run: exit ${{ steps.report.outputs.status }} # Create GitHub issue for failed scheduled jobs # This should always be the last job (we want an issue if anything breaks) diff --git a/Changes b/Changes index 4ce7cff30..fcb769eae 100644 --- a/Changes +++ b/Changes @@ -21,6 +21,15 @@ Verilator 5.049 devel **Other:** +* Add `+verilator+log+file` (#4505) (#7645). [Tracy Narine] +* Add peak memory usage to `--stats`. [Geza Lore, Testorrent USA, Inc.] +* Add error on mixed-initialization (#7352) (#7357). +* 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] +* 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 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.] @@ -44,20 +53,12 @@ Verilator 5.049 devel * 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] +* Support forceable on unpacked array variables (#7677) (#7678). [Nikolai Kumar] * Support pre/post increment/decrement inside && and || (#7683). [Nick Brereton] * Support streaming into 2-D unpacked arrays (#7686) (#7687). [Paul Swirhun] * 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] -* Add `+verilator+log+file` (#4505) (#7645). [Tracy Narine] -* Add peak memory usage to `--stats`. [Geza Lore, Testorrent USA, Inc.] -* Add error on mixed-initialization (#7352) (#7357). -* 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] -* 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] * 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.] @@ -68,6 +69,8 @@ Verilator 5.049 devel * Optimize formatting functions (#7701) (#7702) (#7703). [Geza Lore, Testorrent USA, Inc.] * Optimize DPI import argument passing (#7704). [Geza Lore, Testorrent USA, Inc.] * 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.] * 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 segmentation fault when using --trace with --lib-create (#7299) (#7518). [anonkey] @@ -96,6 +99,7 @@ Verilator 5.049 devel * Fix internal error on consecutive repetition with N > 256 (#7552) (#7603). [Yilou Wang] * Fix inherited rand array with .size + foreach constraint (#7558) (#7650). [Yilou Wang] * Fix biased bit distribution under value < (1 << N) constraints (#7563) (#7684). [Yilou Wang] +* Fix HIERPARAM lint checking (#7570) (#7690). [em2machine] * Fix display of %m in non-first argument (#7574). * Fix floating point compile warning on min/max delays. * Fix force of unpacked arrays (#7579) (#7580). [Zubin Jain] diff --git a/Makefile.in b/Makefile.in index 9d92056ae..c2b60d004 100644 --- a/Makefile.in +++ b/Makefile.in @@ -535,6 +535,7 @@ PY_PROGRAMS = \ # Python files, subject to format but not lint PY_FILES = \ $(PY_PROGRAMS) \ + ci/*.py \ test_regress/t/*.py \ # Python files, test_regress tests diff --git a/ci/ci-pages-notify.bash b/ci/ci-pages-notify.bash index 05bb92fe6..e5dd599b6 100755 --- a/ci/ci-pages-notify.bash +++ b/ci/ci-pages-notify.bash @@ -4,7 +4,7 @@ # SPDX-FileCopyrightText: 2025 Geza Lore # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -# Notify PRs via comment that their coverage reports are available +# Notify PRs via comment that their workflow reports are available # Get the current repo URL - might differ on a fork readonly REPO_URL=$(gh repo view --json url --jq .url) @@ -13,7 +13,7 @@ readonly REPO_URL=$(gh repo view --json url --jq .url) ARTIFACTS_ROOT=artifacts mkdir -p ${ARTIFACTS_ROOT} -for RUN_ID in ${COVERAGE_PR_RUN_IDS//,/ }; do +for RUN_ID in ${PR_RUN_IDS//,/ }; do echo "@@@ Processing run ${RUN_ID}" # Create workflow artifacts directory @@ -21,7 +21,7 @@ for RUN_ID in ${COVERAGE_PR_RUN_IDS//,/ }; do mkdir -p ${ARTIFACTS_DIR} # Download artifact of this run, if exists - gh run download ${RUN_ID} --name coverage-pr-notification --dir ${ARTIFACTS_DIR} || true + gh run download ${RUN_ID} --name pr-notification --dir ${ARTIFACTS_DIR} || true ls -lsha ${ARTIFACTS_DIR} # Move on if no notification is required @@ -35,7 +35,7 @@ for RUN_ID in ${COVERAGE_PR_RUN_IDS//,/ }; do gh pr comment $(cat ${ARTIFACTS_DIR}/pr-number.txt) --body-file ${ARTIFACTS_DIR}/body.txt # Get the artifact ID - ARTIFACT_ID=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "coverage-pr-notification") | .id') + ARTIFACT_ID=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id') # Delete it, so we only notify once gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}" diff --git a/ci/ci-pages.bash b/ci/ci-pages.bash index ba9013621..bd9fcfaa2 100755 --- a/ci/ci-pages.bash +++ b/ci/ci-pages.bash @@ -5,8 +5,7 @@ # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 # This scipt build the content of the GitHub Pages for the repository. -# Currently this only hosts code coverage reports, but it would be possible to -# add any other contents to the page in parallel here. +# Currently this hosts code coverage reports, and PR RTLMeter detailed reports. # Developer note: You should be able to run this script in your local checkout # if you have GitHub CLI (command 'gh') setup, authenticated ('gh auth login'), @@ -25,6 +24,9 @@ if [[ -z "$GITHUB_OUTPUT" ]]; then GITHUB_OUTPUT=github-output.txt fi +# Run IDs of PR jobs processed +PR_RUN_IDS="" + # Populates ${PAGES_ROOT}/coverage-reports compile_coverage_reports() { # We will process all runs up to and including this date. This is chosen to be @@ -42,7 +44,7 @@ compile_coverage_reports() { jq "." completedRuns.json # Create artifacts root directory - local ARTIFACTS_ROOT=artifacts + local ARTIFACTS_ROOT=artifacts-coverage mkdir -p ${ARTIFACTS_ROOT} # Create coverage reports root directory @@ -53,9 +55,6 @@ compile_coverage_reports() { local CONTENTS=contents.tmp echo > ${CONTENTS} - # Run IDs of PR jobs processed - local PR_RUN_IDS="" - # Iterate over all unique event types that triggered the workflows for EVENT in $(jq -r 'map(.event) | sort | unique | .[]' completedRuns.json); do echo "@@@ Processing '${EVENT}' runs" @@ -72,13 +71,7 @@ compile_coverage_reports() { jq "." workflow.json # Record run ID of PR job - if [[ $EVENT == "pull_request" ]]; then - if [[ -z "$PR_RUN_IDS" ]]; then - PR_RUN_IDS="$RUN_ID" - else - PR_RUN_IDS="$PR_RUN_IDS,$RUN_ID" - fi - fi + [[ $EVENT != "pull_request" ]] || PR_RUN_IDS="$PR_RUN_IDS $RUN_ID" # Create workflow artifacts directory local ARTIFACTS_DIR=${ARTIFACTS_ROOT}/${RUN_ID} @@ -148,7 +141,7 @@ CONTENTS_TEMPLATE $(cat ${CONTENTS}) -

Assembled $(date --iso-8601=minutes --utc)

+

Assembled $(date --iso-8601=minutes --utc)

@@ -156,12 +149,136 @@ INDEX_TEMPLATE # Report size du -shc ${COVERAGE_ROOT}/* +} - # Set output - echo "coverage-pr-run-ids=${PR_RUN_IDS}" >> $GITHUB_OUTPUT +# Populates ${PAGES_ROOT}/rtlmeter-reports +compile_rtlmeter_reports() { + # We will process all runs up to and including this date. This is chosen to be + # slightly less than the artifact retention period for simplicity. + local OLDEST=$(date --date="28 days ago" --iso-8601=date) + + # Gather all RTLMeter workflow runs within the time window + gh run list -w rtlmeter.yml --limit 1000 --created ">=${OLDEST}" --json "databaseId,event,status,conclusion,createdAt,number" > recentRuns.json + echo @@@ Recent runs: + jq "." recentRuns.json + + # Select completd runs that were not cancelled or skipped, sort by descending run number + jq 'sort_by(-.number) | map(select(.status == "completed" and (.conclusion == "success" or .conclusion == "failure")))' recentRuns.json > completedRuns.json + echo @@@ Completed with success or failure: + jq "." completedRuns.json + + # Create artifacts root directory + local ARTIFACTS_ROOT=artifacts-rtlmeter + mkdir -p ${ARTIFACTS_ROOT} + + # Create rtlmeter reports root directory + local RTLMETER_ROOT=${PAGES_ROOT}/rtlmeter-reports + mkdir -p ${RTLMETER_ROOT} + + # Create index page contents fragment + local CONTENTS=contents.tmp + echo > ${CONTENTS} + + # Iterate over all unique event types that triggered the workflows + for EVENT in $(jq -r 'map(.event) | sort | unique | .[]' completedRuns.json); do + echo "@@@ Processing '${EVENT}' runs" + + # Emit section header if a report exists with this event type + EMIT_SECTION_HEADER=1 + + # For each worfklow run that was triggered by this event type + for RUN_ID in $(jq ".[] | select(.event == \"${EVENT}\") |.databaseId" completedRuns.json); do + echo "@@@ Processing run ${RUN_ID}" + + # Extract the info of this run + jq ".[] | select(.databaseId == $RUN_ID)" completedRuns.json > workflow.json + jq "." workflow.json + + # Record run ID of PR job + [[ $EVENT != "pull_request" ]] || PR_RUN_IDS="$PR_RUN_IDS $RUN_ID" + + # Create workflow artifacts directory + local ARTIFACTS_DIR=${ARTIFACTS_ROOT}/${RUN_ID} + mkdir -p ${ARTIFACTS_DIR} + + # Download artifacts of this run, if exists + gh run download ${RUN_ID} --name rtlmeter-report --dir ${ARTIFACTS_DIR} || true + ls -lsha ${ARTIFACTS_DIR} + + # Move on if no RTLMeter report is available + if [ ! -d ${ARTIFACTS_DIR}/report ]; then + echo "No RTLMeter report found" + continue + fi + echo "RTLMeter report found" + + # Emit section header + if [[ -n $EMIT_SECTION_HEADER ]]; then + unset EMIT_SECTION_HEADER + echo "

RTLMeter reports for '${EVENT}' runs:

" >> ${CONTENTS} + fi + + # Create pages subdirectory + mv ${ARTIFACTS_DIR}/report ${RTLMETER_ROOT}/${RUN_ID} + + # Add index page content + local WORKFLOW_CREATED=$(jq -r '.createdAt' workflow.json) + local WOFKRLOW_NUMBER=$(jq -r '.number' workflow.json) + cat >> ${CONTENTS} <#${WOFKRLOW_NUMBER} + | GitHub: ${RUN_ID} + | started at: ${WORKFLOW_CREATED} +CONTENTS_TEMPLATE + if [ -e ${ARTIFACTS_DIR}/pr-number.txt ]; then + local PRNUMBER=$(cat ${ARTIFACTS_DIR}/pr-number.txt) + echo " | Pull request: #${PRNUMBER}" >> ${CONTENTS} + fi + echo "
" >> ${CONTENTS} + done + + # Section break + if [[ -z "$EMIT_SECTION_HEADER" ]]; then + echo "
" >> ${CONTENTS} + fi + done + + # Write rtlmeter report index.html + cat > ${RTLMETER_ROOT}/index.html < + + + Verilator CI RTLMeter reports + + + + + $(cat ${CONTENTS}) +

Assembled $(date --iso-8601=minutes --utc)

+ + + +INDEX_TEMPLATE + + # Report size + du -shc ${RTLMETER_ROOT}/* } # Compilie coverage reports compile_coverage_reports; +# Compile RTLMeter reports +compile_rtlmeter_reports; + # You can build any other content here to be put under ${PAGES_ROOT} + +# Set output to all affected unique PR run IDs (comma separated, no trailing comma) +IDS=$(echo ${PR_RUN_IDS} | xargs -n1 | sort -u | paste -sd,) +echo "pr-run-ids=${IDS}" >> $GITHUB_OUTPUT diff --git a/ci/ci-rtlmeter-cases.py b/ci/ci-rtlmeter-cases.py new file mode 100755 index 000000000..8c25cb106 --- /dev/null +++ b/ci/ci-rtlmeter-cases.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: RTLMeter cases to run in the rtlmeter.yml CI workflow +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +############################################################################### +# Defines the RTLMeter cases to run in CI. This is consumed by the 'start' job +# in rtlmeter.yml, which captures the printed JSON and feeds it to the run job +# matrices. +############################################################################### + +import argparse +import json + +# Cases to run for scheduled runs, keyyed by the 'run tag' used in rtlmeter.yml +# The associated priority is roughly the number of minutes it takes to run the +# job, the emitted job matrix will try to start longest jobs first. + +# Cases to run for non-PR jobs (e.g.: nightly schedule) +# fmt: off +CASES = { + "gcc": [ + ("BlackParrot:1x1:*", 5), + ("BlackParrot:2x2:*", 14), + ("BlackParrot:4x4:*", 25), + ("Caliptra:default:*", 19), + ("NVDLA:*", 29), + ("OpenPiton:1x1:*", 4), + ("OpenPiton:2x2:*", 7), + ("OpenPiton:4x4:*", 10), + ("OpenTitan:*", 16), + ("Servant:qerv:*", 2), + ("Servant:serv:*", 2), + ("VeeR-EH1:asic:*", 4), + ("VeeR-EH1:default:*", 6), + ("VeeR-EH1:hiperf:*", 5), + ("VeeR-EH2:asic:*", 13), + ("VeeR-EH2:default:*", 6), + ("VeeR-EH2:hiperf:*", 12), + ("VeeR-EL2:asic:*", 4), + ("VeeR-EL2:default:*", 4), + ("VeeR-EL2:hiperf:*", 4), + ("Vortex:mini:*", 3), + ("Vortex:sane:*", 6), + ("XiangShan:default-chisel3:* !*:linux", 31), + ("XiangShan:default-chisel6:* !*:linux", 28), + ("XiangShan:mini-chisel3:* !*:linux", 13), + ("XiangShan:mini-chisel6:* !*:linux", 15), + ("XuanTie-C906:*", 5), + ("XuanTie-C910:*", 9), + ("XuanTie-E902:*", 5), + ("XuanTie-E906:*", 6), + ], + "clang": [ + ("BlackParrot:1x1:*", 5), + ("BlackParrot:2x2:*", 9), + ("BlackParrot:4x4:*", 12), + ("Caliptra:default:*", 16), + ("NVDLA:*", 28), + ("OpenPiton:1x1:*", 5), + ("OpenPiton:2x2:*", 6), + ("OpenPiton:4x4:*", 8), + ("OpenTitan:*", 10), + ("Servant:qerv:*", 17), + ("Servant:serv:*", 15), + ("VeeR-EH1:asic:*", 6), + ("VeeR-EH1:default:*", 8), + ("VeeR-EH1:hiperf:*", 5), + ("VeeR-EH2:asic:*", 9), + ("VeeR-EH2:default:*", 8), + ("VeeR-EH2:hiperf:*", 7), + ("VeeR-EL2:asic:*", 4), + ("VeeR-EL2:default:*", 5), + ("VeeR-EL2:hiperf:*", 5), + ("Vortex:mini:*", 3), + ("Vortex:sane:*", 5), + ("XiangShan:default-chisel3:* !*:linux", 21), + ("XiangShan:default-chisel6:* !*:linux", 19), + ("XiangShan:mini-chisel3:* !*:linux", 8), + ("XiangShan:mini-chisel6:* !*:linux", 8), + ("XuanTie-C906:*", 3), + ("XuanTie-C910:*", 7), + ("XuanTie-E902:*", 7), + ("XuanTie-E906:*", 7), + ], + "gcc-hier": [ + ("BlackParrot:1x1:*", 4), + ("BlackParrot:2x2:*", 16), + ("BlackParrot:4x4:*", 25), + ("NVDLA:*", 25), + ("OpenPiton:16x16:dhry", 16), + ("OpenPiton:1x1:*", 4), + ("OpenPiton:2x2:*", 4), + ("OpenPiton:4x4:*", 4), + ("OpenPiton:8x8:*", 6), + ("XuanTie-C910:*", 7), + ], +} +# fmt: on + +# Cases to run for PR jobs +# fmt: off +CASES_PR = { + "gcc": [ + ("BlackParrot:1x1:*", 5), + ("BlackParrot:4x4:*", 25), + ("Caliptra:default:*", 19), + ("NVDLA:*", 29), + ("OpenPiton:1x1:*", 4), + ("OpenPiton:4x4:*", 10), + ("OpenTitan:*", 16), + ("Servant:qerv:*", 2), + ("Servant:serv:*", 2), + ("VeeR-EH1:asic:*", 4), + ("VeeR-EH1:default:*", 6), + ("VeeR-EH1:hiperf:*", 5), + ("VeeR-EH2:asic:*", 13), + ("VeeR-EH2:default:*", 6), + ("VeeR-EH2:hiperf:*", 12), + ("VeeR-EL2:asic:*", 4), + ("VeeR-EL2:default:*", 4), + ("VeeR-EL2:hiperf:*", 4), + ("Vortex:mini:*", 3), + ("Vortex:sane:*", 6), + ("XiangShan:default-chisel3:* !*:linux", 31), + ("XiangShan:default-chisel6:* !*:linux", 28), + ("XiangShan:mini-chisel3:* !*:linux", 13), + ("XiangShan:mini-chisel6:* !*:linux", 15), + ("XuanTie-C906:*", 5), + ("XuanTie-C910:*", 9), + ("XuanTie-E902:*", 5), + ("XuanTie-E906:*", 6), + ], + "clang": [ + ("BlackParrot:1x1:*", 5), + ("BlackParrot:4x4:*", 12), + ("Caliptra:default:*", 16), + ("NVDLA:*", 28), + ("OpenPiton:1x1:*", 5), + ("OpenPiton:4x4:*", 8), + ("OpenTitan:*", 10), + ("Servant:qerv:*", 17), + ("Servant:serv:*", 15), + ("VeeR-EH1:asic:*", 6), + ("VeeR-EH1:default:*", 8), + ("VeeR-EH1:hiperf:*", 5), + ("VeeR-EH2:asic:*", 9), + ("VeeR-EH2:default:*", 8), + ("VeeR-EH2:hiperf:*", 7), + ("VeeR-EL2:asic:*", 4), + ("VeeR-EL2:default:*", 5), + ("VeeR-EL2:hiperf:*", 5), + ("Vortex:mini:*", 3), + ("Vortex:sane:*", 5), + ("XiangShan:default-chisel3:* !*:linux", 21), + ("XiangShan:default-chisel6:* !*:linux", 19), + ("XiangShan:mini-chisel3:* !*:linux", 8), + ("XiangShan:mini-chisel6:* !*:linux", 8), + ("XuanTie-C906:*", 3), + ("XuanTie-C910:*", 7), + ("XuanTie-E902:*", 7), + ("XuanTie-E906:*", 7), + ], + "gcc-hier": [ + ("BlackParrot:1x1:*", 4), + ("BlackParrot:4x4:*", 25), + ("NVDLA:*", 25), + ("OpenPiton:16x16:dhry", 16), + ("OpenPiton:1x1:*", 4), + ("OpenPiton:4x4:*", 4), + ("OpenPiton:8x8:*", 6), + ("XuanTie-C910:*", 7), + ], +} +# fmt: on + +parser = argparse.ArgumentParser( + description="Print the RTLMeter CI cases as JSON, keyed by run tag") +parser.add_argument("--event-name", + required=True, + help="GitHub event name that triggered the workflow") +parser.add_argument( + "--ci-testing", + action="store_true", + help="Print only the shortest (lowest priority) case per tag, to reduce CI testing time") +args = parser.parse_args() + +# Pull requests use the reduced 'CASES_PR' set, other events (e.g.: the nightly +# schedule) use the full 'CASES' set. +is_pr = args.event_name == "pull_request" +selected = CASES_PR if is_pr else CASES + +cases = {} +for tag, entries in selected.items(): + # Highest priority (longest) first + ordered = sorted(entries, key=lambda cp: cp[1], reverse=True) + # While tweaking the CI, keep only the shortest (lowest priority) case to minimise churn time + if args.ci_testing: + ordered = ordered[-1:] + # Case filters appended to every case for this tag/event + suffix = "" + # Filters out the non-hierarchical case variants for the hierarchical job + if tag == "gcc-hier": + suffix += " !-hier" + # Filter out hello tests on pull requests, these just add measurement noise + if is_pr: + suffix += " !*:hello" + cases[tag] = [case + suffix for case, _ in ordered] +print(json.dumps(cases, separators=(",", ":"))) diff --git a/ci/ci-rtlmeter-report.bash b/ci/ci-rtlmeter-report.bash new file mode 100755 index 000000000..03c6acc5e --- /dev/null +++ b/ci/ci-rtlmeter-report.bash @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +# DESCRIPTION: Verilator: CI script for 'rtlmeter.yml' PR results +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +# This script builds the content of the response comment posted on PRs +# at the end of RTLMeter runs. + +# Developer note: You should be able to run this script in your local checkout +# if you have GitHub CLI (command 'gh') setup, authenticated ('gh auth login'), +# and have set a default repository ('gh repo set-default'). + +# Trace when running in the CI +[ "$GITHUB_ACTIONS" != "true" ] || set -x + +# Arguments: +# 1. run ID +# 2. SHA of the event that triggered the run (for a PR, the test merge commit) +# rest: run tags +RUN_ID=$1; shift +RUN_SHA=$1; shift +RUNS="$@" + +# $VERILATOR_CHECKOUT/ci directory +SCRIPT_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]})) + +# Move into a temporary directory +rm -rf rtlmeter-report +mkdir rtlmeter-report +pushd rtlmeter-report &> /dev/null +TMP_DIR=$(readlink -f .) + +# Download the combined results. 'combine-results' uploads a single artifact +# for each version, holding the per-tag JSON files (e.g. all-results-gcc.json): +# - 'all-results' for the new version +# - 'all-reference' for the old reference version +mkdir new ref +NEW_DIR=$(readlink -f new) +REF_DIR=$(readlink -f ref) +gh run download ${RUN_ID} --name all-results --dir $NEW_DIR +gh run download ${RUN_ID} --name all-reference --dir $REF_DIR + +# Get Some metadata about the runs +RUN_URL=$(gh run view $RUN_ID --json url --jq ".url") +RUN_NUM=$(gh run view $RUN_ID --json number --jq ".number") + +# Repository owner and name of the default repository, used to build the +# GitHub Pages URL of the detailed report. The owner is lowercased, as required +# for the '.github.io' Pages domain. Resolved here, while still in the +# default repository's git context (before the 'cd rtlmeter' below). +PAGES_OWNER=$(gh repo view --json owner --jq '.owner.login' | tr '[:upper:]' '[:lower:]') +PAGES_NAME=$(gh repo view --json name --jq '.name') + +# The 'old' reference was built from the target branch base commit, which is +# the first parent of the triggering merge commit (same as the 'start' job in +# rtlmeter.yml). Resolved here, while still in the default repository's git +# context (before the 'cd rtlmeter' below). +REF_SHA=$(gh api "repos/{owner}/{repo}/commits/$RUN_SHA" --jq '.parents[0].sha') + +# Go back to root directory +popd &> /dev/null +# Go to RTLMeter directory +cd rtlmeter + +# Compare results +SUMMARY_ARGS=() +for r in $RUNS; do + CMP_JSON=$TMP_DIR/cmp-$r.json + # Gather args for summary script + SUMMARY_ARGS+=($NEW_DIR/all-results-$r.json $CMP_JSON) + # Create JSON + ./rtlmeter compare --format json --steps "*" --metrics "*" \ + $REF_DIR/all-reference-$r.json $NEW_DIR/all-results-$r.json > $CMP_JSON + # Also create detailed tables + ./rtlmeter compare --format ascii --steps 'verilate' --metrics '*' $REF_DIR/all-reference-$r.json $NEW_DIR/all-results-$r.json > $TMP_DIR/verilate-$r.txt + ./rtlmeter compare --format ascii --steps 'cppbuild' --metrics '*' $REF_DIR/all-reference-$r.json $NEW_DIR/all-results-$r.json > $TMP_DIR/cppbuild-$r.txt + ./rtlmeter compare --format ascii --steps 'execute' --metrics '*' $REF_DIR/all-reference-$r.json $NEW_DIR/all-results-$r.json > $TMP_DIR/execute-$r.txt + # Chop them at new lines, into one table per file + awk -v RS= -v prefix=$TMP_DIR/$r-frag '{print > sprintf("%s-verilate-%02d.txt",prefix,NR)}' $TMP_DIR/verilate-$r.txt + awk -v RS= -v prefix=$TMP_DIR/$r-frag '{print > sprintf("%s-cppbuild-%02d.txt",prefix,NR)}' $TMP_DIR/cppbuild-$r.txt + awk -v RS= -v prefix=$TMP_DIR/$r-frag '{print > sprintf("%s-execute-%02d.txt" ,prefix,NR)}' $TMP_DIR/execute-$r.txt +done + +# Create summary, suppress failure, but save the status reported to pass back. +STATUS=0 +venv/bin/python3 $SCRIPT_DIR/ci-rtlmeter-report.py ${SUMMARY_ARGS[@]} > $TMP_DIR/summary.txt || STATUS=$? +# Print it +cat $TMP_DIR/summary.txt + +# Create notification comment content +NOTIFICATION=$TMP_DIR/notification.txt +cat > $NOTIFICATION < + Summary of all runs +
+$(cat $TMP_DIR/summary.txt)
+  
+
+ +The reported numbers are the geometric means of the ratios of the metrics over all cases, +less than 1 is a regression, greater than 1 is an improvement. +The jobs run on variable and noisy runners, so some variance is expected between runs. + +Detailed report: [${RUN_ID}](https://${PAGES_OWNER}.github.io/${PAGES_NAME}/rtlmeter-reports/${RUN_ID}/index.html) +NOTIFICATION_TEMPLATE + +# Create detailed report +REPORT=$TMP_DIR/body.html +cat > $REPORT <Summary of all runs +
+$(cat $TMP_DIR/summary.txt)
+
+SUMMARY_TEMPLATE +echo "

Detailed results

" >> $REPORT +for r in $RUNS; do + RUN_NAME=$(jq -rj ".[0].runName" $NEW_DIR/all-results-$r.json) + echo "

$RUN_NAME

" >> $REPORT + for f in $(ls -1 $TMP_DIR/$r-frag-verilate-*.txt | sort) \ + $(ls -1 $TMP_DIR/$r-frag-cppbuild-*.txt | sort) \ + $(ls -1 $TMP_DIR/$r-frag-execute-*.txt | sort); do + cat >> $REPORT < + $(head -n 1 $f | tr -d '\n') +
+$(tail -n +2 $f)
+      
+
+DETAIL_TAMPLATE + done +done + +# Turn the report into a proper HTML page +mkdir -p ${TMP_DIR}/report +cat > ${TMP_DIR}/report/index.html < + + + Verilator RTLMeter report #${RUN_NUM} + + + + +$(cat ${TMP_DIR}/body.html) + + + +INDEX_TEMPLATE + +exit $STATUS diff --git a/ci/ci-rtlmeter-report.py b/ci/ci-rtlmeter-report.py new file mode 100644 index 000000000..27883526d --- /dev/null +++ b/ci/ci-rtlmeter-report.py @@ -0,0 +1,137 @@ +# DESCRIPTION: Verilator: CI script for 'rtlmeter.yml' PR results +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +############################################################################### +# This script runs with the venv of **RTLMeter** +############################################################################### + +import sys +import json +import tabulate +from typing import Final, List + +tabulate.PRESERVE_WHITESPACE = True +tabulate.MIN_PADDING = 0 + +_ASCII_TABLE_FORMAT: Final = tabulate.TableFormat( + lineabove=tabulate.Line("╒═", "═", "═╤═", "═╕"), + linebelowheader=tabulate.Line("╞═", "═", "═╪═", "═╡"), + linebelow=tabulate.Line("╘═", "═", "═╧═", "═╛"), + headerrow=tabulate.DataRow("│ ", " │ ", " │"), + datarow=tabulate.DataRow("│ ", " │ ", " │"), + linebetweenrows=None, + padding=0, + with_header_hide=None, +) + + +def printTable(table: List[List[str]], **kwargs) -> None: + print(tabulate.tabulate(table, tablefmt=_ASCII_TABLE_FORMAT, **kwargs)) + + +# fmt: off +stepMetric = ( + # Step, Metric, Status Brackets + ("execute", "speed", ("❌", 0.96, "⚠️", 0.98, "✅", 1.02, "💡", 1.04, "⭐")), + ("execute", "memory", ("❌", 0.90, "⚠️", 0.95, "✅", 1.05, "💡", 1.10, "⭐")), + ("verilate", "cpu", ("❌", 0.96, "⚠️", 0.98, "✅", 1.02, "💡", 1.04, "⭐")), + ("verilate", "memory", ("❌", 0.90, "⚠️", 0.95, "✅", 1.05, "💡", 1.10, "⭐")), + ("cppbuild", "cpu", ("❌", 0.96, "⚠️", 0.98, "✅", 1.02, "💡", 1.04, "⭐")), + ("cppbuild", "memory", ("❌", 0.90, "⚠️", 0.95, "✅", 1.05, "💡", 1.10, "⭐")), + ("cppbuild", "codeSize",("❌", 0.96, "⚠️", 0.98, "✅", 1.02, "💡", 1.04, "⭐")), + ("cppbuild", "elapsed", ("❌", 0.70, "⚠️", 0.85, "✅", 1.15, "💡", 1.30, "⭐")), +) + +badgeLegend = [ + ("❌", "Likely regression"), + ("⚠️", "Possible regression"), + ("✅", "Within acceptable range"), + ("💡", "Possible improvement"), + ("⭐", "Likely improvement"), +] +# fmt: on + +changedCycles = [] +table = [] +badgesToExplain = set() + +for ref, cmp in zip(sys.argv[1::2], sys.argv[2::2]): + with open(ref, "r", encoding="utf-8") as f: + ref_json = json.load(f)[0] + with open(cmp, "r", encoding="utf-8") as f: + cmp_json = json.load(f) + if table: + table.append(tabulate.SEPARATING_LINE) + runName = ref_json["runName"] + + # Check simulated cycles match - it's ok to crash if this entry does not exist + for entry in cmp_json["execute"]["clocks"]["table"]: + case, _, _, (refCycles, _), (newCycles, _), _, _ = entry + refCycles = int(refCycles) + newCycles = int(newCycles) + if refCycles != newCycles: + changedCycles.append([runName, case, refCycles, newCycles]) + + # Check metrics + for step, metric, brackets in stepMetric: + if step not in cmp_json: + continue + data = cmp_json[step] + if metric not in data: + continue + data = data[metric] + minGain = float("inf") + maxGain = float("-inf") + meanGain = 1 + count = 0 + for _, _, _, (a, _), (b, _), g, _ in data["table"]: + # for wall clock and CPU time, ignore small values that just add noise + if metric == "elapsed" or metric == "cpu": + if a < 30 or b < 30: + continue + minGain = min(minGain, g) + maxGain = max(maxGain, g) + meanGain *= g + count += 1 + if count == 0: + continue + meanGain = meanGain**(1 / count) + + status = brackets[0] + for limit, badge in zip(brackets[1::2], brackets[2::2]): + if meanGain >= limit: + status = badge + badgesToExplain.add(status) + + table.append([ + runName, step, ref_json["metrics"][metric]["header"], f"{meanGain:.2f}x {status} ", + f"{minGain:.2f}x", f"{maxGain:.2f}x", f"{count}" + ]) + +# Print changed cycles if any +if changedCycles: + print("❌ simulated cycles changed (must be fixed):") + printTable(changedCycles, + headers=("Run", "Case", "Old Cycles", "New Cycles"), + colalign=("left", "left", "right", "right"), + disable_numparse=True) + print() + +# Print results +printTable( + table, + headers=("Run", "Step", "Metric", "Improvement", "Min", "Max", "Samples"), + colalign=("left", "left", "left", "right", "right", "right", "right"), + disable_numparse=True, +) + +# Explain badges +print() +for badge, legend in badgeLegend: + if badge in badgesToExplain: + print(f" {badge} : {legend}") + +# Fail job if status changed +sys.exit(0 if not changedCycles else 1) diff --git a/configure.ac b/configure.ac index 2cc95c267..6917f0c4b 100644 --- a/configure.ac +++ b/configure.ac @@ -421,6 +421,15 @@ AC_DEFUN([_MY_LDLIBS_CHECK_OPT], _MY_LDLIBS_CHECK_IFELSE($2, $1="$$1 $2") ]) +AC_DEFUN([_MY_LDLIBS_CHECK_SET], + [# _MY_LDLIBS_CHECK_SET(variable, flag) -- Check if linker supports specific + # options. If it does, set variable to flag, only if not previously set + # (so the first supported flag wins and the rest are not tested). + if test "$$1" = ""; then + _MY_LDLIBS_CHECK_IFELSE($2, $1="$2") + fi + ]) + # Add the coverage flags early as they influence later checks. if test "$CFG_WITH_DEV_GCOV" = "yes"; then _MY_CXX_CHECK_OPT(CXX,--coverage) @@ -589,11 +598,6 @@ if test "$CFG_ENABLE_PARTIAL_STATIC" = "yes"; then _MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_SRC, -static-libgcc) _MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_SRC, -static-libstdc++) _MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_SRC, -Xlinker -gc-sections) - LTCMALLOC="-Wl,--whole-archive -l:libtcmalloc_minimal.a -Wl,--no-whole-archive" - LJEMALLOC="-Wl,--whole-archive -l:libjemalloc.a -Wl,--no-whole-archive" -else - LTCMALLOC=-ltcmalloc_minimal - LJEMALLOC=-ljemalloc fi AC_SUBST(CFG_LDFLAGS_SRC) AC_SUBST(CFG_LDFLAGS_VERILATED) @@ -609,51 +613,56 @@ _MY_LDLIBS_CHECK_OPT(CFG_LIBS, -latomic) _MY_LDLIBS_CHECK_OPT(CFG_LIBS, -lbcrypt) _MY_LDLIBS_CHECK_OPT(CFG_LIBS, -lpsapi) -# Check if jemalloc is available based on --enable-jemalloc -# jemalloc is preferred over tcmalloc when both are available +# Figure out which malloc library to use: +# - Prefer jemalloc over tcmalloc (it's faster) +# - If partial static link enabled, prefer static libraries, +# but fall back on dynamic if static is not available + +# Test for jemalloc CFG_HAVE_JEMALLOC=no -_MY_LDLIBS_CHECK_IFELSE( - $LJEMALLOC, - [if test "$CFG_WITH_JEMALLOC" != "no"; then - CFG_LIBS="$LJEMALLOC $CFG_LIBS"; - CFG_HAVE_JEMALLOC=yes; - # If using jemalloc, add some extra options to make the compiler not assume - # it is using its own versions of the standard library functions - _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-malloc) - _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-calloc) - _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-realloc) - _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-free) +if test "$CFG_WITH_JEMALLOC" != "no"; then + if test "$CFG_ENABLE_PARTIAL_STATIC" = "yes"; then + _MY_LDLIBS_CHECK_SET(CFG_LIBS_MALLOC, -Xlinker --whole-archive -l:libjemalloc.a -Xlinker --no-whole-archive) + fi + _MY_LDLIBS_CHECK_SET(CFG_LIBS_MALLOC, -ljemalloc) + if test "$CFG_LIBS_MALLOC" != ""; then + CFG_HAVE_JEMALLOC=yes AC_DEFINE([HAVE_JEMALLOC],[1],[Defined if have jemalloc]) - fi], - [if test "$CFG_WITH_JEMALLOC" = "yes"; then - AC_MSG_ERROR([--enable-jemalloc was given but test for ${LJEMALLOC} failed]) - fi]) + elif test "$CFG_WITH_JEMALLOC" = "yes"; then + AC_MSG_ERROR([--enable-jemalloc was given but jemalloc is not available]) + fi +fi AC_SUBST(HAVE_JEMALLOC) -# Check if tcmalloc is available based on --enable-tcmalloc -# Only use tcmalloc if jemalloc was not found/enabled -if test "$CFG_HAVE_JEMALLOC" = "yes"; then - AC_MSG_NOTICE([jemalloc found, skipping tcmalloc check]) -else +# Test for tcmalloc CFG_HAVE_TCMALLOC=no -_MY_LDLIBS_CHECK_IFELSE( - $LTCMALLOC, - [if test "$CFG_WITH_TCMALLOC" != "no"; then - CFG_LIBS="$LTCMALLOC $CFG_LIBS"; - CFG_HAVE_TCMALLOC=yes; - # If using tcmalloc, add some extra options to make the compiler not assume - # it is using its own versions of the standard library functions - _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-malloc) - _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-calloc) - _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-realloc) - _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-free) +if test "$CFG_HAVE_JEMALLOC" = "yes"; then + AC_MSG_NOTICE([jemalloc found, skipping check for tcmalloc]) +elif test "$CFG_WITH_TCMALLOC" != "no"; then + if test "$CFG_ENABLE_PARTIAL_STATIC" = "yes"; then + _MY_LDLIBS_CHECK_SET(CFG_LIBS_MALLOC, -Xlinker --whole-archive -l:libtcmalloc_minimal.a -Xlinker --no-whole-archive) + fi + _MY_LDLIBS_CHECK_SET(CFG_LIBS_MALLOC, -ltcmalloc_minimal) + if test "$CFG_LIBS_MALLOC" != ""; then + CFG_HAVE_TCMALLOC=yes AC_DEFINE([HAVE_TCMALLOC],[1],[Defined if have tcmalloc]) - fi], - [if test "$CFG_WITH_TCMALLOC" = "yes"; then - AC_MSG_ERROR([--enable-tcmalloc was given but test for ${LTCMALLOC} failed]) - fi]) + elif test "$CFG_WITH_TCMALLOC" = "yes"; then + AC_MSG_ERROR([--enable-tcmalloc was given but tcmalloc is not available]) + fi fi AC_SUBST(HAVE_TCMALLOC) + +if test "$CFG_HAVE_JEMALLOC" = "yes" || test "$CFG_HAVE_TCMALLOC" = "yes"; then + # If using custom malloc, add some extra options to make the compiler not assume + # it is using its own versions of the standard library functions + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-malloc) + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-calloc) + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-realloc) + _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-fno-builtin-free) +fi + +# Finished with libraries +CFG_LIBS="$CFG_LIBS_MALLOC $CFG_LIBS" AC_SUBST(CFG_LIBS) # Need C++14 at least diff --git a/nodist/log_changes b/nodist/log_changes index e56bef01c..c4632e355 100755 --- a/nodist/log_changes +++ b/nodist/log_changes @@ -97,10 +97,10 @@ def process() -> None: line, flags=re.IGNORECASE) if m: - mid = m.group(2) - if ("#" + mid) not in msgs[key]: + bug_id = m.group(2) + if ("#" + bug_id) not in msgs[key]: # print("K %s" % line) - msgs[key] += ' (#' + mid + ')' + msgs[key] += ' (#' + bug_id + ')' if not msgs: print("No Changes need to be inserted.") diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 3b482c427..0ccf162fa 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -253,8 +253,7 @@ class AssertVisitor final : public VNVisitor { } } AstSampled* newSampledExpr(AstNodeExpr* nodep) { - AstSampled* const sampledp = new AstSampled{nodep->fileline(), nodep}; - sampledp->dtypeFrom(nodep); + AstSampled* const sampledp = new AstSampled{nodep->fileline(), nodep, nodep->dtypep()}; return sampledp; } AstVarRef* newMonitorNumVarRefp(const AstNode* nodep, VAccess access) { diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index 3bfe7dfda..bf50e97a1 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -182,8 +182,7 @@ struct BuildResult final { }; static AstNodeExpr* sampled(AstNodeExpr* exprp) { - AstSampled* const sp = new AstSampled{exprp->fileline(), exprp}; - sp->dtypeFrom(exprp); + AstSampled* const sp = new AstSampled{exprp->fileline(), exprp, exprp->dtypep()}; return sp; } @@ -838,13 +837,14 @@ class SvaNfaBuilder final { BuildResult buildUntil(AstUntil* nodep, SvaStateVertex* entryVtxp, bool isTopLevelStep) { FileLine* const flp = nodep->fileline(); if (!isTopLevelStep) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: 'until' in complex property expression"); + nodep->v3warn(E_UNSUPPORTED, "Unsupported: '" << nodep->verilogKwd() + << "' in complex property expression"); return BuildResult::failWithError(); } if (nodep->isStrong()) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: s_until" << (nodep->isOverlapping() ? "_with" : "") - << " (in property expresion)"); + << " (in property expression)"); return BuildResult::failWithError(); } AstNodeExpr* const lhsp = nodep->lhsp(); @@ -853,7 +853,8 @@ class SvaNfaBuilder final { return ep->exists([](const AstNodeExpr* np) { return np->isMultiCycleSva(); }); }; if (hasSeq(lhsp) || hasSeq(rhsp)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: 'until' in complex property expression"); + nodep->v3warn(E_UNSUPPORTED, "Unsupported: '" << nodep->verilogKwd() + << "' in complex property expression"); return BuildResult::failWithError(); } @@ -1926,16 +1927,16 @@ class AssertNfaVisitor final : public VNVisitor { // Bare `assert property (p until q)` with boolean operands stays on // V3AssertPre's AstLoop lowering, which preserves per-attempt action-block - // firings that this NFA's single-bit aggregated state cannot. NFA still - // owns strong forms, sequence operands, and any embedding inside a - // multi-cycle context (implication consequent, or/and operands, etc.). + // firings that this NFA's single-bit aggregated state cannot. Strong bare + // forms are also lowered there. NFA still owns sequence operands and any + // embedding inside a multi-cycle context (implication consequent, or/and + // operands, etc.). static bool isBareTopLevelUntil(AstNode* propp) { AstNode* p = propp; if (AstPropSpec* const specp = VN_CAST(p, PropSpec)) p = specp->propp(); while (AstLogNot* const notp = VN_CAST(p, LogNot)) p = notp->lhsp(); AstUntil* const untilp = VN_CAST(p, Until); if (!untilp) return false; - if (untilp->isStrong()) return false; const auto hasSeq = [](const AstNodeExpr* ep) { return ep->exists([](const AstNodeExpr* np) { return np->isMultiCycleSva(); }); }; diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 28bb57635..c61299a66 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -366,8 +366,8 @@ private: if (skewp->num().is1Step()) { // #1step means the value that is sampled is always the signal's last value // before the clock edge (IEEE 1800-2023 14.4) - AstSampled* const sampledp = new AstSampled{flp, exprp->cloneTreePure(false)}; - sampledp->dtypeFrom(exprp); + AstSampled* const sampledp + = new AstSampled{flp, exprp->cloneTreePure(false), exprp->dtypep()}; AstAssign* const assignp = new AstAssign{flp, refp, sampledp}; m_clockingp->addNextHere(new AstAlways{ flp, VAlwaysKwd::ALWAYS, @@ -413,7 +413,7 @@ private: } } void visit(AstDelay* nodep) override { - m_hasCycleDelay = true; + m_hasCycleDelay |= nodep->isCycleDelay(); // Only cycle delays are relevant in this stage; also only process once if (!nodep->isCycleDelay()) { if (m_inSynchDrive) { @@ -811,7 +811,6 @@ private: static AstStmtExpr* getProcessAssocArrayDelete(AstVarRef* const refp) { // Constructs refp.delete(std::process::self()) statement FileLine* const flp = refp->fileline(); - refp->classOrPackagep(v3Global.rootp()->stdPackageProcessp()); AstCMethodHard* const deletep = new AstCMethodHard{ flp, refp, VCMethod::ASSOC_ERASE, v3Global.rootp()->stdPackageProcessSelfp(flp)}; deletep->dtypep(refp->findVoidDType()); @@ -819,7 +818,6 @@ private: } static AstNodeExpr* getProcessAssocArraySize(AstVarRef* const refp) { // Constructs refp.size() statement - refp->classOrPackagep(v3Global.rootp()->stdPackageProcessp()); AstCMethodHard* const sizep = new AstCMethodHard{refp->fileline(), refp, VCMethod::ASSOC_SIZE}; sizep->dtypep(refp->findBasicDType(VBasicDTypeKwd::UINT32)); @@ -846,7 +844,7 @@ private: // Assertion condition check AstLoop* const loopp = new AstLoop{flp}; - AstNodeExpr* const condp = new AstSampled{flp, nodep->exprp()->unlinkFrBack()}; + AstNodeExpr* const condp = new AstSampled{flp, nodep->exprp()->unlinkFrBack(), nullptr}; loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLogNot{flp, condp}}); loopp->addStmtsp(new AstEventControl{flp, sentreep, nullptr}); @@ -1300,17 +1298,121 @@ private: } void visit(AstUntil* nodep) override { FileLine* const flp = nodep->fileline(); - if (m_pexprp) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: 'until' in complex property expression"); - nodep->replaceWith(new AstConst{flp, AstConst::BitFalse{}}); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - return; - } + UASSERT_OBJ( + !m_pexprp, nodep, + "'" << nodep->verilogKwd() + << "' in complex property expression should have been rejected by V3AssertNfa"); if (nodep->isStrong()) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: s_until" - << (nodep->isOverlapping() ? "_with" : "") - << " (in property expresion)"); - nodep->replaceWith(new AstConst{flp, AstConst::BitFalse{}}); + // p s_until q / p s_until_with q: q must eventually be true. Until then, p must + // be true on every sampled tick. For s_until, check q first: when q is true on + // this tick, p is not required. For s_until_with, p must be true on the q tick too. + AstNodeExpr* const rawLhsp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const rawRhsp = nodep->rhsp()->unlinkFrBack(); + AstSampled* const lhsp = new AstSampled{flp, rawLhsp, rawLhsp->dtypep()}; + AstSampled* const rhsp = new AstSampled{flp, rawRhsp, rawRhsp->dtypep()}; + AstNodeExpr* finalCondp = rhsp->cloneTreePure(false); + if (nodep->isOverlapping()) { + finalCondp = new AstLogAnd{flp, lhsp->cloneTreePure(false), finalCondp}; + } + + // Track active assertion attempts. Only the count is needed to emit final failures. + AstVar* const activep = new AstVar{flp, VVarType::MODULETEMP, m_activeNames.get(""), + nodep->findBasicDType(VBasicDTypeKwd::UINT32)}; + activep->lifetime(VLifetime::STATIC_EXPLICIT); + m_modp->addStmtsp(activep); + + AstVar* const donep + = new AstVar{flp, VVarType::BLOCKTEMP, "__VassertDone", nodep->findBitDType()}; + donep->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + auto setDone = [&]() { + return new AstAssign{flp, new AstVarRef{flp, donep, VAccess::WRITE}, + new AstConst{flp, AstConst::BitTrue{}}}; + }; + + AstLoop* const loopp = new AstLoop{flp}; + AstAssign* const decrementVar = new AstAssign{ + flp, new AstVarRef{flp, activep, VAccess::WRITE}, + new AstSub{flp, new AstVarRef{flp, activep, VAccess::READ}, new AstConst{flp, 1}}}; + loopp->addStmtsp(new AstLoopTest{ + flp, loopp, new AstLogNot{flp, new AstVarRef{flp, donep, VAccess::READ}}}); + { + AstBegin* const passp = new AstBegin{flp, "", nullptr, true}; + passp->addStmtsp(new AstPExprClause{flp}); + passp->addStmtsp(decrementVar); + passp->addStmtsp(setDone()); + AstNodeExpr* passCondp = rhsp; + if (nodep->isOverlapping()) { + passCondp = new AstLogAnd{flp, lhsp->cloneTreePure(false), passCondp}; + } + loopp->addStmtsp(new AstIf{flp, passCondp, passp}); + } + { + AstBegin* const failp = new AstBegin{flp, "", nullptr, true}; + failp->addStmtsp(new AstPExprClause{flp, false}); + failp->addStmtsp(decrementVar->cloneTree(false)); + failp->addStmtsp(setDone()); + loopp->addStmtsp(new AstIf{ + flp, + new AstLogAnd{flp, + new AstLogNot{flp, new AstVarRef{flp, donep, VAccess::READ}}, + new AstLogNot{flp, lhsp}}, + failp}); + } + AstDelay* const delayp = new AstDelay{flp, new AstConst{flp, 1}, false}; + delayp->timeunit(m_modp->timeunit()); + loopp->addStmtsp(new AstIf{ + flp, new AstLogNot{flp, new AstVarRef{flp, donep, VAccess::READ}}, delayp}); + + // Main assertion block + AstBegin* const bodyp = new AstBegin{flp, "", nullptr, true}; + bodyp->addStmtsp(donep); + bodyp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, donep, VAccess::WRITE}, + new AstConst{flp, AstConst::BitFalse{}}}); + FileLine* const flp = activep->fileline(); + bodyp->addStmtsp( + new AstAssign{flp, new AstVarRef{flp, activep, VAccess::WRITE}, + new AstAdd{flp, new AstVarRef{flp, activep, VAccess::READ}, + new AstConst{flp, 1}}}); + bodyp->addStmtsp(loopp); + + // Validate assertion condition for each active assert + AstVar* const activeCountp = new AstVar{flp, VVarType::BLOCKTEMP, "__VassertCount", + nodep->findBasicDType(VBasicDTypeKwd::UINT32)}; + activeCountp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + + AstAssign* const initActiveCountp + = new AstAssign{flp, new AstVarRef{flp, activeCountp, VAccess::WRITE}, + new AstVarRef{flp, activep, VAccess::READ}}; + AstLoop* const finalLoopp = new AstLoop{flp}; + finalLoopp->addStmtsp( + new AstLoopTest{flp, finalLoopp, + new AstNeq{flp, new AstVarRef{flp, activeCountp, VAccess::READ}, + new AstConst{flp, 0}}}); + finalLoopp->addStmtsp(new AstIf{flp, finalCondp, new AstPExprClause{flp}, + new AstPExprClause{flp, false}}); + finalLoopp->addStmtsp( + new AstAssign{flp, new AstVarRef{flp, activeCountp, VAccess::WRITE}, + new AstSub{flp, new AstVarRef{flp, activeCountp, VAccess::READ}, + new AstConst{flp, 1}}}); + + // Final assertion block + AstBegin* const finalp = new AstBegin{flp, "", nullptr, true}; + finalp->addStmtsp(activeCountp); + finalp->addStmtsp(initActiveCountp); + finalp->addStmtsp(finalLoopp); + + AstPExpr* const pexprp = new AstPExpr{flp, bodyp, finalp, nodep->dtypep()}; + VL_RESTORER(m_pexprp); + VL_RESTORER(m_hasCycleDelay); + m_pexprp = pexprp; + m_hasCycleDelay = false; + iterate(bodyp); + iterate(finalp); + + UASSERT_OBJ(!m_hasCycleDelay, nodep, + "s_until cycle delay should have been handled by V3AssertNfa"); + + nodep->replaceWith(pexprp); VL_DO_DANGLING(pushDeletep(nodep), nodep); return; } @@ -1369,8 +1471,8 @@ private: iterateNull(nodep->disablep()); if (!VN_AS(nodep->backp(), NodeCoverOrAssert)->immediate()) { const AstNodeDType* const propDtp = nodep->propp()->dtypep(); - nodep->propp(new AstSampled{nodep->fileline(), nodep->propp()->unlinkFrBack()}); - nodep->propp()->dtypeFrom(propDtp); + nodep->propp(new AstSampled{nodep->fileline(), nodep->propp()->unlinkFrBack(), + propDtp->dtypep()}); } iterate(nodep->propp()); } diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index e912bae20..c12627886 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -370,6 +370,24 @@ public: // clang-format on return names[m_e]; } + // True for attributes that read an operand's type rather than its value, such as $bits. + bool isTypeQuery() const { + switch (m_e) { + case DIM_BITS: + case DIM_BITS_OR_NUMBER: + case DIM_DIMENSIONS: + case DIM_HIGH: + case DIM_INCREMENT: + case DIM_LEFT: + case DIM_LOW: + case DIM_RIGHT: + case DIM_SIZE: + case DIM_UNPK_DIMENSIONS: + case TYPEID: + case TYPENAME: return true; + default: return false; + } + } VAttrType() : m_e{ILLEGAL} {} // cppcheck-suppress noExplicitConstructor diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 7d4a3842d..41d980697 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -2519,9 +2519,10 @@ class AstSampled final : public AstNodeExpr { // Verilog $sampled // @astgen op1 := exprp : AstNode public: - AstSampled(FileLine* fl, AstNode* exprp) + AstSampled(FileLine* fl, AstNode* exprp, AstNodeDType* dtypep) : ASTGEN_SUPER_Sampled(fl) { this->exprp(exprp); + this->dtypep(dtypep); } ASTGEN_MEMBERS_AstSampled; string emitVerilog() override { return "$sampled(%l)"; } @@ -2906,6 +2907,9 @@ public: string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } + string verilogKwd() const override { + return (isStrong() ? "s_" : string()) + "until" + (isOverlapping() ? "_with" : ""); + } bool cleanOut() const override { V3ERROR_NA_RETURN(""); } int instrCount() const override { return widthInstrs(); } bool sameNode(const AstNode* /*samep*/) const override { return true; } @@ -5784,6 +5788,7 @@ public: }; class AstOneHot final : public AstNodeUniop { // True if only single bit set in vector + // @astgen makeDfgVertex public: AstOneHot(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_OneHot(fl, lhsp) { @@ -5801,6 +5806,7 @@ public: }; class AstOneHot0 final : public AstNodeUniop { // True if only single bit, or no bits set in vector + // @astgen makeDfgVertex public: AstOneHot0(FileLine* fl, AstNodeExpr* lhsp) : ASTGEN_SUPER_OneHot0(fl, lhsp) { diff --git a/src/V3Cfg.h b/src/V3Cfg.h index d4cf8a9af..f367a8366 100644 --- a/src/V3Cfg.h +++ b/src/V3Cfg.h @@ -559,13 +559,11 @@ public: namespace V3Cfg { -// Compute AstVars live on entry to given CFG. That is, variables that might +// Compute AstVarScopes live on entry to given CFG. That is, variables that might // be read before wholly assigned in the CFG. Returns nullptr if the analysis -// failed due to unhandled statements or data types involved in the CFG. -// On success, returns a vector of AstVar or AstVarScope nodes live on entry. -std::unique_ptr> liveVars(const CfgGraph&); - -// Same as liveVars, but return AstVarScopes insted +// failed due to unhandled statements involved in the CFG. The analysis is +// conservative and may return variables that are not actually live on entry. +// On success, returns a vector of AstVarScope nodes that might be live on entry. std::unique_ptr> liveVarScopes(const CfgGraph&); } //namespace V3Cfg diff --git a/src/V3CfgLiveVariables.cpp b/src/V3CfgLiveVariables.cpp index 3b6eeab70..79033af9d 100644 --- a/src/V3CfgLiveVariables.cpp +++ b/src/V3CfgLiveVariables.cpp @@ -25,25 +25,19 @@ #include "V3Ast.h" #include "V3Cfg.h" -#include #include -#include VL_DEFINE_DEBUG_FUNCTIONS; -template class CfgLiveVariables final : VNVisitorConst { - // TYPES - using Variable = std::conditional_t; - // State associted with each basic block struct BlockState final { // Variables used in block, before a complete assignment in the same block - std::unordered_set m_gen; + std::unordered_set m_gen; // Variables that are assigned a complete value in the basic block - std::unordered_set m_kill; - std::unordered_set m_liveIn; // Variables live on entry to the block - std::unordered_set m_liveOut; // Variables live on exit from the block + std::unordered_set m_kill; + std::unordered_set m_liveIn; // Variables live on entry to the block + std::unordered_set m_liveOut; // Variables live on exit from the block bool m_isOnWorkList = false; // Block is on work list bool m_wasProcessed = false; // Already processed at least once }; @@ -56,14 +50,6 @@ class CfgLiveVariables final : VNVisitorConst { bool m_abort = false; // Abort analysis - unhandled construct // METHODS - static Variable* getTarget(const AstNodeVarRef* refp) { - // TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works - if VL_CONSTEXPR_CXX17 (T_Scoped) { - return reinterpret_cast(refp->varScopep()); - } else { - return reinterpret_cast(refp->varp()); - } - } // This is to match DFG, but can be extended independently - eventually should handle all static bool isSupportedPackedDType(const AstNodeDType* dtypep) { @@ -81,52 +67,41 @@ class CfgLiveVariables final : VNVisitorConst { } // Check and return if variable is incompatible - bool incompatible(Variable* varp) { - if (!isSupportedPackedDType(varp->dtypep())) return true; - const AstVar* astVarp = nullptr; - // TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works - if VL_CONSTEXPR_CXX17 (T_Scoped) { - astVarp = reinterpret_cast(varp)->varp(); - } else { - astVarp = reinterpret_cast(varp); - } - if (astVarp->ignoreSchedWrite()) return true; + bool incompatible(AstVarScope* vscp) { + if (!isSupportedPackedDType(vscp->dtypep())) return true; + if (vscp->varp()->ignoreSchedWrite()) return true; return false; } void updateGen(AstNode* nodep) { UASSERT_OBJ(!m_abort, nodep, "Doing useless work"); - m_abort = nodep->exists([&](const AstNodeVarRef* refp) { - // Cross reference is ambiguous - if (VN_IS(refp, VarXRef)) return true; + m_abort = nodep->exists([&](const AstVarRef* refp) { // Only care about reads if (refp->access().isWriteOnly()) return false; // Grab referenced variable - Variable* const tgtp = getTarget(refp); + AstVarScope* const vscp = refp->varScopep(); // Bail if not a compatible type - if (incompatible(tgtp)) return true; + if (incompatible(vscp)) return true; // Add to gen set, if not killed - assume whole variable read - if (m_currp->m_kill.count(tgtp)) return false; - m_currp->m_gen.emplace(tgtp); + if (m_currp->m_kill.count(vscp)) return false; + m_currp->m_gen.emplace(vscp); return false; }); } void updateKill(AstNode* nodep) { UASSERT_OBJ(!m_abort, nodep, "Doing useless work"); - m_abort = nodep->exists([&](const AstNodeVarRef* refp) { - // Cross reference is ambiguous - if (VN_IS(refp, VarXRef)) return true; + m_abort = nodep->exists([&](const AstVarRef* refp) { // Only care about writes if (refp->access().isReadOnly()) return false; // Grab referenced variable - Variable* const tgtp = getTarget(refp); + AstVarScope* const vscp = refp->varScopep(); // Bail if not a compatible type - if (incompatible(tgtp)) return true; + if (incompatible(vscp)) return true; // If whole written, add to kill set if (refp->nextp()) return false; if (VN_IS(refp->abovep(), Sel)) return false; - m_currp->m_kill.emplace(tgtp); + m_currp->m_kill.emplace(vscp); return false; }); } @@ -144,8 +119,8 @@ class CfgLiveVariables final : VNVisitorConst { BlockState& state = m_blockState[bb]; // liveIn = gen union (liveOut - kill) - std::unordered_set liveIn = state.m_gen; - for (Variable* const varp : state.m_liveOut) { + std::unordered_set liveIn = state.m_gen; + for (AstVarScope* const varp : state.m_liveOut) { if (state.m_kill.count(varp)) continue; liveIn.insert(varp); } @@ -219,25 +194,23 @@ class CfgLiveVariables final : VNVisitorConst { } public: - static std::unique_ptr> apply(const CfgGraph& cfg) { + static std::unique_ptr> apply(const CfgGraph& cfg) { CfgLiveVariables analysis{cfg}; // If failed, return nullptr if (analysis.m_abort) return nullptr; - // Gather variables live in to the entry blockstd::unique_ptr> - const std::unordered_set& lin = analysis.m_blockState[cfg.enter()].m_liveIn; - std::vector* const resultp = new std::vector{lin.begin(), lin.end()}; + // Gather variables live in to the entry blockstd::unique_ptr> + const std::unordered_set& lin = analysis.m_blockState[cfg.enter()].m_liveIn; + std::vector* const resultp + = new std::vector{lin.begin(), lin.end()}; // Sort for stability - std::stable_sort(resultp->begin(), resultp->end(), [](Variable* ap, Variable* bp) { // - return ap->name() < bp->name(); - }); - return std::unique_ptr>{resultp}; + std::stable_sort(resultp->begin(), resultp->end(), + [](AstVarScope* ap, AstVarScope* bp) { // + return ap->name() < bp->name(); + }); + return std::unique_ptr>{resultp}; } }; -std::unique_ptr> V3Cfg::liveVars(const CfgGraph& cfg) { - return CfgLiveVariables::apply(cfg); -} - std::unique_ptr> V3Cfg::liveVarScopes(const CfgGraph& cfg) { - return CfgLiveVariables::apply(cfg); + return CfgLiveVariables::apply(cfg); } diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index df68246aa..273d1dcff 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -91,6 +91,11 @@ std::unique_ptr DfgGraph::clone() const { for (const DfgVertex& vtx : m_opVertices) { switch (vtx.type()) { #include "V3Dfg__gen_clone_cases.h" // From ./astgen + case VDfgType::CReset: { // LCOV_EXCL_START - No algorithm actually hits this today + DfgCReset* const cp = new DfgCReset{*clonep, vtx.fileline(), vtx.dtype()}; + vtxp2clonep.emplace(&vtx, cp); + break; + } // LCOV_EXCL_STOP case VDfgType::Sel: { DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtype()}; cp->lsb(vtx.as()->lsb()); @@ -132,11 +137,14 @@ std::unique_ptr DfgGraph::clone() const { VL_UNREACHABLE; break; } - default: { - vtx.v3fatalSrc("Unhandled operation vertex type: " + vtx.typeName()); + case VDfgType::AstRd: // LCOV_EXCL_START + case VDfgType::Const: + case VDfgType::VarArray: + case VDfgType::VarPacked: { + vtx.v3fatalSrc("Vertex should have been handled above: " + vtx.typeName()); VL_UNREACHABLE; break; - } + } // LCOV_EXCL_STOP } } UASSERT(size() == clonep->size(), "Size of clone should be the same"); @@ -618,6 +626,10 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const { CHECK(isPacked(), "Should be Packed type"); return; } + case VDfgType::CReset: { + CHECK(isPacked() || isArray(), "Should be Packed or Array type"); + return; + } case VDfgType::AstRd: { const DfgAstRd& v = *as(); CHECK(v.isPacked() || v.isArray(), "Should be Packed or Array type"); @@ -774,7 +786,9 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const { case VDfgType::LogNot: case VDfgType::RedAnd: case VDfgType::RedOr: - case VDfgType::RedXor: { + case VDfgType::RedXor: + case VDfgType::OneHot: + case VDfgType::OneHot0: { CHECK(dtype() == DfgDataType::packed(1), "Should be 1-bit"); CHECK(inputp(0)->isPacked(), "Operand should be Packed type"); return; diff --git a/src/V3DfgColorSCCs.cpp b/src/V3DfgColorSCCs.cpp index 6bdce8ffb..1986a8775 100644 --- a/src/V3DfgColorSCCs.cpp +++ b/src/V3DfgColorSCCs.cpp @@ -125,6 +125,12 @@ class ColorStronglyConnectedComponents final { // Initialize state of operation vertices for (const DfgVertex& vtx : m_dfg.opVertices()) { + // If it has no inputs or no outputs, it cannot be part of a non-trivial SCC. + if (!vtx.nInputs() || !vtx.hasSinks()) { + index(vtx) = 0; + component(vtx) = 0; + continue; + } index(vtx) = UNASSIGNED; component(vtx) = UNASSIGNED; } diff --git a/src/V3DfgCse.cpp b/src/V3DfgCse.cpp index abce554ba..7a60b4be7 100644 --- a/src/V3DfgCse.cpp +++ b/src/V3DfgCse.cpp @@ -52,6 +52,7 @@ class V3DfgCse final { // Special vertices case VDfgType::Const: // LCOV_EXCL_START + case VDfgType::CReset: case VDfgType::VarArray: case VDfgType::VarPacked: case VDfgType::AstRd: // LCOV_EXCL_STOP @@ -110,6 +111,8 @@ class V3DfgCse final { case VDfgType::NeqCase: case VDfgType::NeqWild: case VDfgType::Not: + case VDfgType::OneHot: + case VDfgType::OneHot0: case VDfgType::Or: case VDfgType::Pow: case VDfgType::PowSS: @@ -168,6 +171,7 @@ class V3DfgCse final { // Special vertices case VDfgType::Const: return a.as()->num().isCaseEq(b.as()->num()); + case VDfgType::CReset: return false; case VDfgType::VarArray: case VDfgType::VarPacked: // CSE does not combine variables @@ -232,6 +236,8 @@ class V3DfgCse final { case VDfgType::NeqCase: case VDfgType::NeqWild: case VDfgType::Not: + case VDfgType::OneHot: + case VDfgType::OneHot0: case VDfgType::Or: case VDfgType::Pow: case VDfgType::PowSS: @@ -299,6 +305,10 @@ class V3DfgCse final { for (const DfgVertexVar& vtx : dfg.varVertices()) m_hashCache[vtx] = V3Hash{++varHash}; // Pre-hash Ast references, these are all unique like variables for (const DfgVertexAst& vtx : dfg.astVertices()) m_hashCache[vtx] = V3Hash{++varHash}; + // Pre-hash CReset vertices, these are all unique + for (const DfgVertex& vtx : dfg.opVertices()) { + if (vtx.is()) m_hashCache[vtx] = V3Hash{++varHash}; + } // Similarly pre-hash constants for speed. While we don't combine constants, we do want // expressions using the same constants to be combined, so we do need to hash equal diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index 6da7bfc8d..ca349ccb3 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -227,6 +227,13 @@ class DfgToAstVisitor final : DfgVisitor { void visit(DfgConst* vtxp) override { // m_resultp = new AstConst{vtxp->fileline(), vtxp->num()}; } + void visit(DfgCReset* vtxp) override { + DfgVertex* const sinkp = vtxp->singleSink(); + UASSERT_OBJ(sinkp, vtxp, "CReset should only have one sink"); + UASSERT_OBJ(sinkp->is(), sinkp, "CReset should drive a variable"); + AstVar* const varp = sinkp->as()->vscp()->varp(); + m_resultp = new AstCReset{vtxp->fileline(), varp, false}; + } void visit(DfgRep* vtxp) override { FileLine* const flp = vtxp->fileline(); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index eaad8872e..3e14397c6 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -120,6 +120,8 @@ void V3DfgPasses::inlineVars(DfgGraph& dfg) { // Partial driver cannot be inlined if (srcp->is()) continue; if (srcp->is()) continue; + // Don't inline CReset + if (srcp->is()) continue; // Okie dokie, here we go ... vtx.replaceWith(srcp); } diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index f8a9a2b46..f89285eb7 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -138,11 +138,14 @@ template <> const DfgDataType& resultDType (const DfgVertex* lhsp // Unary constant folding template void foldOp(V3Number& out, const V3Number& src); +template <> void foldOp (V3Number& out, const V3Number& src) { out.opCountOnes(src); } template <> void foldOp (V3Number& out, const V3Number& src) { out.opAssign(src); } template <> void foldOp (V3Number& out, const V3Number& src) { out.opExtendS(src, src.width()); } template <> void foldOp (V3Number& out, const V3Number& src) { out.opLogNot(src); } template <> void foldOp (V3Number& out, const V3Number& src) { out.opNegate(src); } template <> void foldOp (V3Number& out, const V3Number& src) { out.opNot(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opOneHot(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opOneHot0(src); } template <> void foldOp (V3Number& out, const V3Number& src) { out.opRedAnd(src); } template <> void foldOp (V3Number& out, const V3Number& src) { out.opRedOr(src); } template <> void foldOp (V3Number& out, const V3Number& src) { out.opRedXor(src); } @@ -1155,7 +1158,7 @@ class V3DfgPeephole final : public DfgVisitor { // Sel from a partial variable (including narrowed vertex) if (DfgVarPacked* const varp = fromp->cast()) { - if (varp->srcp() && !varp->isVolatile()) { + if (varp->srcp() && !varp->isVolatile() && !varp->srcp()->is()) { // Must be a splice, otherwise it would have been inlined DfgSplicePacked* splicep = varp->srcp()->as(); DfgVertex* driverp = nullptr; @@ -1193,6 +1196,10 @@ class V3DfgPeephole final : public DfgVisitor { // DfgVertexUnary //========================================================================= + void visit(DfgCountOnes* const vtxp) override { + if (foldUnary(vtxp)) return; + } + void visit(DfgExtend* const vtxp) override { if (foldUnary(vtxp)) return; @@ -1325,6 +1332,14 @@ class V3DfgPeephole final : public DfgVisitor { } } + void visit(DfgOneHot* const vtxp) override { + if (foldUnary(vtxp)) return; + } + + void visit(DfgOneHot0* const vtxp) override { + if (foldUnary(vtxp)) return; + } + void visit(DfgRedOr* const vtxp) override { if (optimizeReduction(vtxp)) return; } diff --git a/src/V3DfgPushDownSels.cpp b/src/V3DfgPushDownSels.cpp index 658be1b34..71b2bfaca 100644 --- a/src/V3DfgPushDownSels.cpp +++ b/src/V3DfgPushDownSels.cpp @@ -89,6 +89,9 @@ class V3DfgPushDownSels final { m_stack.push_back(&vtx); } for (DfgConst& vtx : m_dfg.constVertices()) m_stack.push_back(&vtx); + for (DfgVertex& vtx : m_dfg.opVertices()) { + if (!vtx.nInputs()) m_stack.push_back(&vtx); + } // Reverse post order number to assign to next vertex uint32_t rpoNext = m_dfg.size(); diff --git a/src/V3DfgRegularize.cpp b/src/V3DfgRegularize.cpp index 588dbd52c..f8e7b7935 100644 --- a/src/V3DfgRegularize.cpp +++ b/src/V3DfgRegularize.cpp @@ -100,6 +100,9 @@ class DfgRegularize final { // No need to add a temporary if the single sink is a variable already if (sink.is()) return false; + // CReset always needs to be driving a variable + if (aVtx.is()) return true; + // Do not inline expressions into a loop body if (const DfgAstRd* const astRdp = sink.cast()) { return astRdp->inLoop(); } diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp index 827bede0e..706c9c0c9 100644 --- a/src/V3DfgSynthesize.cpp +++ b/src/V3DfgSynthesize.cpp @@ -384,6 +384,24 @@ class AstToDfgConverter final : public VNVisitor { nodep->user2p(vtxp); } } + void visit(AstCReset* 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; + } + + UASSERT_OBJ(!nodep->constructing(), nodep, + "CReset should be non-constructing at this stage"); + + DfgVertex* const vtxp = make(nodep->fileline(), *dtypep); + 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 8ab12c214..a3d3adf8e 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -248,6 +248,14 @@ public: } }; +class DfgCReset final : public DfgVertexNullary { +public: + DfgCReset(DfgGraph& dfg, FileLine* flp, const DfgDataType& dtype) + : DfgVertexNullary{dfg, dfgType(), flp, dtype} {} + + ASTGEN_MEMBERS_DfgCReset; +}; + //------------------------------------------------------------------------------ // Unary vertices - 1 inputs diff --git a/src/V3Param.cpp b/src/V3Param.cpp index fe2273d98..063c55d3a 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -2722,10 +2722,14 @@ class ParamVisitor final : public VNVisitor { // LCOV_EXCL_STOP } - void checkParamNotHier(AstNode* valuep) { - if (!valuep) return; - valuep->foreachAndNext([&](const AstNodeExpr* exprp) { - if (const AstVarXRef* const refp = VN_CAST(exprp, VarXRef)) { + // Flag hierarchical refs in a parameter value. Single top-down pass. + void checkParamNotHierRecurse(AstNode* nodep, bool underTypeQuery = false) { + for (; nodep; nodep = nodep->nextp()) { + // Refs read only for their type ($bits etc.) are allowed + const AstAttrOf* const attrp = VN_CAST(nodep, AttrOf); + const bool childUnderQuery + = underTypeQuery || (attrp && attrp->attrType().isTypeQuery()); + if (const AstVarXRef* const refp = VN_CAST(nodep, VarXRef)) { // Allow hierarchical ref to interface params through interface/modport ports // or local interface instances bool isIfaceRef = false; @@ -2735,18 +2739,21 @@ class ParamVisitor final : public VNVisitor { = !refname.empty() && (m_ifacePortNames.count(refname) || m_ifaceInstCells.count(refname)); } - - if (!isIfaceRef) { + if (!isIfaceRef && !underTypeQuery) { refp->v3warn(HIERPARAM, "Parameter values cannot use hierarchical values" " (IEEE 1800-2023 6.20.2)"); } - } else if (const AstNodeFTaskRef* refp = VN_CAST(exprp, NodeFTaskRef)) { + } else if (const AstNodeFTaskRef* const refp = VN_CAST(nodep, NodeFTaskRef)) { if (refp->dotted() != "") { refp->v3error("Parameter values cannot call hierarchical functions" " (IEEE 1800-2023 6.20.2)"); } } - }); + checkParamNotHierRecurse(nodep->op1p(), childUnderQuery); + checkParamNotHierRecurse(nodep->op2p(), childUnderQuery); + checkParamNotHierRecurse(nodep->op3p(), childUnderQuery); + checkParamNotHierRecurse(nodep->op4p(), childUnderQuery); + } } // Deparameterize and constify nested interface cells within ifaceModp. @@ -2888,7 +2895,7 @@ class ParamVisitor final : public VNVisitor { iterateChildren(nodep); } void visit(AstCell* nodep) override { - checkParamNotHier(nodep->paramsp()); + checkParamNotHierRecurse(nodep->paramsp()); if (VN_IS(nodep->modp(), Iface)) m_ifaceInstCells.emplace(nodep->name(), nodep); visitCellOrClassRef(nodep, VN_IS(nodep->modp(), Iface)); } @@ -2896,7 +2903,7 @@ class ParamVisitor final : public VNVisitor { if (nodep->ifacep()) visitCellOrClassRef(nodep, true); } void visit(AstClassRefDType* nodep) override { - checkParamNotHier(nodep->paramsp()); + checkParamNotHierRecurse(nodep->paramsp()); visitCellOrClassRef(nodep, false); } void visit(AstClassOrPackageRef* nodep) override { @@ -2913,7 +2920,7 @@ class ParamVisitor final : public VNVisitor { if (nodep->isIfaceRef()) { m_ifacePortNames.insert(nodep->name()); } iterateChildren(nodep); if (nodep->isParam()) { - checkParamNotHier(nodep->valuep()); + checkParamNotHierRecurse(nodep->valuep()); if (!nodep->valuep() && !VN_IS(m_modp, Class)) { nodep->v3error("Parameter without default value is never given value" << " (IEEE 1800-2023 6.20.1): " << nodep->prettyNameQ()); diff --git a/src/V3ParseImp.cpp b/src/V3ParseImp.cpp index 7cf4e76e6..549166f6d 100644 --- a/src/V3ParseImp.cpp +++ b/src/V3ParseImp.cpp @@ -94,6 +94,69 @@ void V3ParseImp::importIfInStd(FileLine* fileline, const string& id, bool doImpo } } +AstNodeExpr* V3ParseImp::makePropertyCase(FileLine* flp, AstNodeExpr* exprp, AstCaseItem* itemsp) { + AstNodeExpr* resultp = nullptr; + AstNodeExpr* matchedp = nullptr; + AstNodeExpr* defaultPropp = nullptr; + FileLine* defaultFlp = flp; + + if (!itemsp) { + flp->v3error("Property case statement with no items"); + exprp->deleteTree(); + return new AstConst{flp, AstConst::BitTrue{}}; + } + + for (AstCaseItem *nextp, *itemp = itemsp; itemp; itemp = nextp) { + nextp = VN_AS(itemp->nextp(), CaseItem); + AstNodeExpr* const propp = VN_AS(itemp->stmtsp()->unlinkFrBack(), NodeExpr); + + if (itemp->isDefault()) { + if (defaultPropp) { + itemp->v3error("Multiple default statements in property case statement"); + defaultPropp->deleteTree(); + exprp->deleteTree(); + return new AstConst{flp, AstConst::BitTrue{}}; + } + defaultFlp = itemp->fileline(); + defaultPropp = propp; + continue; + } + + AstNodeExpr* itemMatchp = nullptr; + for (AstNodeExpr *condNextp, *condp = itemp->condsp(); condp; condp = condNextp) { + condNextp = VN_AS(condp->nextp(), NodeExpr); + condp->unlinkFrBack(); + AstNodeExpr* const eqp + = new AstEqCase{condp->fileline(), exprp->cloneTreePure(false), condp}; + itemMatchp = itemMatchp ? new AstLogOr{itemp->fileline(), itemMatchp, eqp} : eqp; + } + UASSERT_OBJ(itemMatchp, itemp, "Property case item without condition"); + AstNodeExpr* const guardp + = matchedp + ? new AstLogAnd{itemp->fileline(), itemMatchp->cloneTreePure(false), + new AstLogNot{itemp->fileline(), matchedp->cloneTreePure(false)}} + : itemMatchp->cloneTreePure(false); + AstNodeExpr* const branchp = new AstImplication{itemp->fileline(), guardp, propp, true}; + resultp = resultp ? new AstSAnd{flp, resultp, branchp} : branchp; + matchedp = matchedp ? new AstLogOr{itemp->fileline(), matchedp, itemMatchp} : itemMatchp; + } + itemsp->deleteTree(); + + if (defaultPropp) { + if (!matchedp) { + exprp->deleteTree(); + return defaultPropp; + } + AstNodeExpr* const noMatchp + = static_cast(new AstLogNot{defaultFlp, matchedp->cloneTreePure(false)}); + AstNodeExpr* const branchp = new AstImplication{defaultFlp, noMatchp, defaultPropp, true}; + resultp = new AstSAnd{flp, resultp, branchp}; + } + matchedp->deleteTree(); + exprp->deleteTree(); + return resultp; +} + void V3ParseImp::lexPpline(const char* textp) { // Handle lexer `line directive // FileLine* const prevFl = lexFileline(); diff --git a/src/V3ParseImp.h b/src/V3ParseImp.h index 33dd99957..882b0e817 100644 --- a/src/V3ParseImp.h +++ b/src/V3ParseImp.h @@ -309,6 +309,7 @@ public: void dumpTokensAhead(int line) VL_MT_DISABLED; static void candidatePli(VSpellCheck* spellerp) VL_MT_DISABLED; void importIfInStd(FileLine* fileline, const string& id, bool doImport); + AstNodeExpr* makePropertyCase(FileLine* flp, AstNodeExpr* exprp, AstCaseItem* itemsp); private: void preprocDumps(std::ostream& os); diff --git a/src/V3Slice.cpp b/src/V3Slice.cpp index 875ce7624..4331e4015 100644 --- a/src/V3Slice.cpp +++ b/src/V3Slice.cpp @@ -210,7 +210,7 @@ class SliceVisitor final : public VNVisitor { UINFO(9, " cloneSliceSel(" << elements << "," << elemIdx << ") " << nodep); AstNodeExpr* const exprp = VN_AS(snodep->exprp(), NodeExpr); AstNodeExpr* const selp = cloneAndSel(exprp, elements, elemIdx, needPure); - return new AstSampled{nodep->fileline(), selp}; + return new AstSampled{nodep->fileline(), selp, nullptr}; } else if (AstExprStmt* const snodep = VN_CAST(nodep, ExprStmt)) { UINFO(9, " cloneExprStmt(" << elements << "," << elemIdx << ") " << nodep); AstNodeExpr* const resultSelp diff --git a/src/V3Unroll.cpp b/src/V3Unroll.cpp index d8dd4221e..ce677324b 100644 --- a/src/V3Unroll.cpp +++ b/src/V3Unroll.cpp @@ -64,16 +64,69 @@ struct UnrollStats final { Stat m_nUnrolledIters{"Unrolled iterations"}; }; +//###################################################################### +// Variable bindings +class UnrolllBindings final { + // NODE STATE + // AstVarScope::user1() int: index of the binding in m_bindings + const VNUser1InUse m_inuser1; + + // STATE + // Map from AstVarScope::user1() to current variable value, nullptr if not bound - idx 0 unused + std::vector m_curr{nullptr}; + // Stack of binding checkpoints + std::vector> m_checkpoints; + +public: + // CONSTRUCTOR + UnrolllBindings() = default; + ~UnrolllBindings() = default; + VL_UNCOPYABLE(UnrolllBindings); + VL_UNMOVABLE(UnrolllBindings); + + // METHODS + void clear() { + m_curr.resize(1); + m_checkpoints.clear(); + AstNode::user1ClearTree(); + } + + void checkpoint() { m_checkpoints.push_back(m_curr); } + + void revert() { + m_curr = std::move(m_checkpoints.back()); + m_checkpoints.pop_back(); + } + + void commit() { m_checkpoints.pop_back(); } + + void set(AstVarScope* vscp, AstConst* valp) { + if (!vscp->user1()) { + vscp->user1(m_curr.size()); + m_curr.push_back(nullptr); + } + UINFO(6, "Binding SET " << vscp->name() << " / " << vscp->user1() << " := " << valp); + m_curr[vscp->user1()] = valp; + } + + AstConst* get(AstVarScope* vscp) { + // It's possible after a revert that user1 is set, but the vector is shorter, pad up + if (static_cast(vscp->user1()) >= m_curr.size()) { + m_curr.resize(vscp->user1() + 1, nullptr); + } + AstConst* const valp = vscp->user1() ? m_curr[vscp->user1()] : nullptr; + UINFO(6, "Binding GET " << vscp->name() << " / " << vscp->user1() << " == " << valp); + return valp; + } +}; + //###################################################################### // Unroll one AstLoop class UnrollOneVisitor final : VNVisitor { - // NODE STATE - // AstVarScope::user1p() AstConst: Value of this AstVarScope - const VNUser1InUse m_user1InUse; - // STATE UnrollStats& m_stats; // Statistics tracking + UnrolllBindings& m_bindings; // Variable bindings AstLoop* const m_loopp; // The loop we are trying to unroll AstNode* m_stmtsp = nullptr; // Resulting unrolled statement size_t m_unrolledSize = 0; // Number of nodes in unrolled loop @@ -204,11 +257,11 @@ class UnrollOneVisitor final : VNVisitor { AstVarRef* const refp = VN_CAST(np, VarRef); if (!refp) return; // Ignore if the referenced variable has no binding - AstConst* const valp = VN_AS(refp->varScopep()->user1p(), Const); + AstConst* const valp = m_bindings.get(refp->varScopep()); if (!valp) return; // If writen, remove the binding if (refp->access().isWriteOrRW()) { - refp->varScopep()->user1p(nullptr); + m_bindings.set(refp->varScopep(), nullptr); return; } // Otherwise add it to the list of variables to substitute @@ -219,7 +272,7 @@ class UnrollOneVisitor final : VNVisitor { // Actually substitute the variables that still have bindings for (AstVarRef* const refp : toSubstitute) { // Pick up bound value - AstConst* const valp = VN_AS(refp->varScopep()->user1p(), Const); + AstConst* const valp = m_bindings.get(refp->varScopep()); // Binding might have been removed after adding to 'toSubstitute' if (!valp) continue; // Substitute it @@ -230,8 +283,9 @@ class UnrollOneVisitor final : VNVisitor { } // CONSTRUCTOR - UnrollOneVisitor(UnrollStats& stats, AstLoop* loopp) + UnrollOneVisitor(UnrollStats& stats, UnrolllBindings& bindings, AstLoop* loopp) : m_stats{stats} + , m_bindings{bindings} , m_loopp{loopp} { UASSERT_OBJ(!loopp->contsp(), loopp, "'contsp' only used before LinkJump"); // Do not unroll if we are told not to @@ -239,25 +293,9 @@ class UnrollOneVisitor final : VNVisitor { cantUnroll(loopp, m_stats.m_nPragmaDisabled); return; } - // Gather variable bindings from the preceding statements - for (AstNode *succp = loopp, *currp = loopp->backp(); currp->nextp() == succp; - succp = currp, currp = currp->backp()) { - AstAssign* const assignp = VN_CAST(currp, Assign); - if (!assignp) break; - AstConst* const valp = VN_CAST(V3Const::constifyEdit(assignp->rhsp()), Const); - if (!valp) break; - AstVarRef* const lhsp = VN_CAST(assignp->lhsp(), VarRef); - if (!lhsp) break; - // Don't bind if volatile - if (lhsp->varp()->isForced() || lhsp->varp()->isSigUserRWPublic()) continue; - // Don't overwrite a later binding - if (lhsp->varScopep()->user1p()) continue; - // Set up the binding - lhsp->varScopep()->user1p(valp); - } // Attempt to unroll the loop - const size_t iterLimit - = m_unrollFull ? v3Global.opt.unrollLimit() : v3Global.opt.unrollCount(); + const size_t iterLimit = m_unrollFull ? v3Global.opt.unrollLimit() // + : v3Global.opt.unrollCount(); size_t iterCount = 0; do { if (iterCount > iterLimit) { @@ -316,7 +354,7 @@ class UnrollOneVisitor final : VNVisitor { AstVarRef* const lhsp = VN_CAST(nodep->lhsp(), VarRef); AstConst* const valp = VN_CAST(V3Const::constifyEdit(nodep->rhsp()), Const); if (lhsp && valp && !lhsp->varp()->isForced() && !lhsp->varp()->isSigUserRWPublic()) { - lhsp->varScopep()->user1p(valp); + m_bindings.set(lhsp->varScopep(), valp); return; } // Otherwise just like a generic statement @@ -340,12 +378,32 @@ class UnrollOneVisitor final : VNVisitor { // Remove trailing dead code if (nodep->nextp()) pushDeletep(nodep->nextp()->unlinkFrBackWithNext()); } + void visit(AstLoop* nodep) override { + m_bindings.checkpoint(); + std::pair pair = UnrollOneVisitor::apply(m_stats, m_bindings, nodep); + + // If failed, revert the bindings and process the loop as a generic statement + if (!pair.second) { + m_bindings.revert(); + process(nodep); + return; + } + + // Commit the bindings and replace the loop with the unrolled code + m_bindings.commit(); + if (pair.first) { + nodep->replaceWith(pair.first); + } else { + nodep->unlinkFrBack(); + } + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } public: - // Unroll the given loop. Returns the resulting statements and the number of - // iterations unrolled (0 if unrolling failed); - static std::pair apply(UnrollStats& stats, AstLoop* loopp) { - UnrollOneVisitor visitor{stats, loopp}; + // Unroll the given loop. Returns the resulting statements and a flag indicating success + static std::pair apply(UnrollStats& stats, UnrolllBindings& bindings, + AstLoop* loopp) { + UnrollOneVisitor visitor{stats, bindings, loopp}; // If successfully unrolled, return the resulting list of statements - might be empty if (visitor.m_ok) return {visitor.m_stmtsp, true}; // Otherwise delete intermediate results @@ -358,13 +416,40 @@ public: // Unroll all AstLoop statements class UnrollAllVisitor final : VNVisitor { - // STATE - Statistic tracking - UnrollStats m_stats; + // STATE + UnrollStats m_stats; // Statistic tracking + UnrolllBindings m_bindings; // Variable bindings // VISIT void visit(AstLoop* nodep) override { + // Gather variable bindings from the preceding statements + m_bindings.clear(); + for (AstNode *succp = nodep, *currp = nodep->backp(); true; + succp = currp, currp = currp->backp()) { + // If the current statement is in higher list, proceed carefully + if (currp->nextp() != succp) { + // Jump block is OK, there is only one way to get here + if (VN_IS(currp, JumpBlock)) continue; + // TODO: if? + // Otherwise we dont' know the control flow, give up + break; + } + AstAssign* const assignp = VN_CAST(currp, Assign); + if (!assignp) break; + AstConst* const valp = VN_CAST(V3Const::constifyEdit(assignp->rhsp()), Const); + if (!valp) break; + AstVarRef* const lhsp = VN_CAST(assignp->lhsp(), VarRef); + if (!lhsp) break; + // Don't bind if volatile + if (lhsp->varp()->isForced() || lhsp->varp()->isSigUserRWPublic()) continue; + // Don't overwrite a later binding + if (m_bindings.get(lhsp->varScopep())) continue; + // Set up the binding + m_bindings.set(lhsp->varScopep(), valp); + } + // Attempt to unroll this loop - const std::pair pair = UnrollOneVisitor::apply(m_stats, nodep); + const std::pair pair = UnrollOneVisitor::apply(m_stats, m_bindings, nodep); // If failed, carry on with nested loop if (!pair.second) { diff --git a/src/V3Width.cpp b/src/V3Width.cpp index e8df0f4df..5a24539b2 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -1714,6 +1714,13 @@ class WidthVisitor final : public VNVisitor { } void visit(AstUntil* nodep) override { + if (nodep->isStrong() + && (v3Global.opt.timing().isSetFalse() || !v3Global.opt.timing().isSetTrue())) { + nodep->v3warn(E_NOTIMING, nodep->verilogKwd() << " requires --timing"); + nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}}); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + return; + } assertAtExpr(nodep); if (m_vup->prelim()) { iterateCheckBool(nodep, "LHS", nodep->lhsp(), BOTH); @@ -1926,6 +1933,25 @@ class WidthVisitor final : public VNVisitor { if (nodep->iffp()) iterateCheckBool(nodep, "iff condition", nodep->iffp(), BOTH); userIterateAndNext(nodep->optionsp(), nullptr); } + void visit(AstCoverBin* nodep) override { + // Bin range/value entries are self-determined constant expressions (IEEE 1800-2023 + // 19.5). Width each plain single-value entry self-determined so a referenced + // parameter acquires a dtype, then constify so the reference folds to the AstConst + // value that V3Covergroup requires. AstInsideRange entries fold their own bounds in + // visit(AstInsideRange). + for (AstNode *nextp, *itemp = nodep->rangesp(); itemp; itemp = nextp) { + nextp = itemp->nextp(); + if (VN_IS(itemp, InsideRange)) { + userIterate(itemp, nullptr); + } else { + userIterate(itemp, WidthVP{SELF, BOTH}.p()); + V3Const::constifyEdit(itemp); // itemp may change + } + } + userIterateAndNext(nodep->iffp(), nullptr); + userIterateAndNext(nodep->arraySizep(), nullptr); + userIterateAndNext(nodep->transp(), nullptr); + } void visit(AstPow* nodep) override { // Pow is special, output sign only depends on LHS sign, but // function result depends on both signs @@ -3566,17 +3592,20 @@ class WidthVisitor final : public VNVisitor { } void visit(AstInsideRange* nodep) override { // Just do each side; AstInside will rip these nodes out later. - // When m_vup is null, this range appears outside a normal expression context (e.g. - // in a covergroup bin declaration). Pre-fold constant arithmetic in that case - // (e.g., AstNegate(Const) -> Const) so children have their types set before widthing. - // We cannot do this unconditionally: in a normal 'inside' expression (m_vup set), - // range bounds may be enum refs not yet widthed, and constifyEdit would crash. if (!m_vup) { + // This range appears outside a normal expression context (e.g. in a covergroup + // bin declaration). Width the bounds self-determined first so each bound (and any + // referenced parameter) has a dtype, then constify so constant arithmetic is folded + // (e.g. AstNegate(Const) -> Const, or a parameter reference -> Const). Folding + // preserves the now-present dtype, so no bound reaches V3WidthCommit without one. + userIterateAndNext(nodep->lhsp(), WidthVP{SELF, BOTH}.p()); + userIterateAndNext(nodep->rhsp(), WidthVP{SELF, BOTH}.p()); V3Const::constifyEdit(nodep->lhsp()); // lhsp may change V3Const::constifyEdit(nodep->rhsp()); // rhsp may change + } else { + userIterateAndNext(nodep->lhsp(), m_vup); + userIterateAndNext(nodep->rhsp(), m_vup); } - userIterateAndNext(nodep->lhsp(), m_vup); - userIterateAndNext(nodep->rhsp(), m_vup); nodep->dtypeFrom(nodep->lhsp()); } diff --git a/src/verilog.y b/src/verilog.y index 829f8c4a4..663e8a206 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -4531,7 +4531,7 @@ system_f_or_t_expr_call: // IEEE: part of system_tf_call (can be tas | yD_ROSE '(' expr ',' expr ')' { $$ = new AstRose{$1, $3, GRAMMARP->createSenTreeChanged($1, $5)}; } | yD_ROSE_GCLK '(' expr ')' { $$ = new AstRose{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}; } | yD_RTOI '(' expr ')' { $$ = new AstRToIS{$1, $3}; } - | yD_SAMPLED '(' expr ')' { $$ = new AstSampled{$1, $3}; } + | yD_SAMPLED '(' expr ')' { $$ = new AstSampled{$1, $3, $3->dtypep()}; } | yD_SFORMATF '(' exprDispList ')' { $$ = new AstSFormatF{$1, AstSFormatF::ExprFormat{}, $3, 'd', false}; } | yD_SHORTREALTOBITS '(' expr ')' { $$ = new AstRealToBits{$1, $3}; UNSUPREAL($1); } | yD_SIGNED '(' expr ')' { $$ = new AstSigned{$1, $3}; } @@ -6678,13 +6678,9 @@ property_spec: // IEEE: property_spec property_exprCaseIf: // IEEE: part of property_expr for if/case yCASE '(' expr/*expression_or_dist*/ ')' property_case_itemList yENDCASE - { $$ = new AstConst{$1, AstConst::BitFalse{}}; - BBUNSUP($1, "Unsupported: property case expression"); - DEL($3, $5); } + { $$ = PARSEP->makePropertyCase($1, $3, $5); } | yCASE '(' expr/*expression_or_dist*/ ')' yENDCASE - { $$ = new AstConst{$1, AstConst::BitFalse{}}; - BBUNSUP($1, "Unsupported: property case expression"); - DEL($3); } + { $$ = PARSEP->makePropertyCase($1, $3, nullptr); } | yIF '(' expr/*expression_or_dist*/ ')' pexpr %prec prIF { $$ = new AstImplication{$1, $3, $5, true}; } | yIF '(' expr/*expression_or_dist*/ ')' pexpr yELSE pexpr @@ -6695,16 +6691,15 @@ property_exprCaseIf: // IEEE: part of property_expr for if/case property_case_itemList: // IEEE: {property_case_item} property_case_item { $$ = $1; } - | property_case_itemList ',' property_case_item { $$ = addNextNull($1, $3); } + | property_case_itemList property_case_item { $$ = addNextNull($1, $2); } ; property_case_item: // ==IEEE: property_case_item - // // IEEE: expression_or_dist { ',' expression_or_dist } ':' property_statement + // // IEEE: expression_or_dist { ',' expression_or_dist } ':' property_expr // // IEEE 1800-2012 changed from property_statement to property_expr // // IEEE 1800-2017 changed to require the semicolon - caseCondList ':' pexpr { $$ = new AstCaseItem{$2, $1, $3}; } - | caseCondList ':' pexpr ';' { $$ = new AstCaseItem{$2, $1, $3}; } - | yDEFAULT pexpr { $$ = new AstCaseItem{$1, nullptr, $2}; } + caseCondList ':' pexpr ';' { $$ = new AstCaseItem{$2, $1, $3}; } + | yDEFAULT pexpr ';' { $$ = new AstCaseItem{$1, nullptr, $2}; } | yDEFAULT ':' pexpr ';' { $$ = new AstCaseItem{$1, nullptr, $3}; } ; diff --git a/test_regress/t/t_altera_lpm.v b/test_regress/t/t_altera_lpm.v index 3b48d349c..967cd1dea 100644 --- a/test_regress/t/t_altera_lpm.v +++ b/test_regress/t/t_altera_lpm.v @@ -48,6 +48,7 @@ //END_MODULE_NAME-------------------------------------------------------------- //See also: https://github.com/twosigma/verilator_support +// verilog_format: off // verilator lint_off COMBDLY,INITIALDLY,LATCH,MULTIDRIVEN,UNSIGNED,WIDTH // BEGINNING OF MODULE diff --git a/test_regress/t/t_clk_first_bad.out b/test_regress/t/t_clk_first_bad.out index 9ce9543db..425d4ec7f 100644 --- a/test_regress/t/t_clk_first_bad.out +++ b/test_regress/t/t_clk_first_bad.out @@ -1,5 +1,5 @@ -%Warning-DEPRECATED: t/t_clk_first_deprecated.v:12:14: sc_clock is ignored - 12 | input clk /*verilator sc_clock*/; +%Warning-DEPRECATED: t/t_clk_first_deprecated.v:11:14: sc_clock is ignored + 11 | input clk /*verilator sc_clock*/; | ^~~~~~~~~~~~~~~~~~~~~~ ... For warning description see https://verilator.org/warn/DEPRECATED?v=latest ... Use "/* verilator lint_off DEPRECATED */" and lint_on around source to disable this message. diff --git a/test_regress/t/t_clk_first_deprecated.v b/test_regress/t/t_clk_first_deprecated.v index 9142eb9ab..915c60a1e 100644 --- a/test_regress/t/t_clk_first_deprecated.v +++ b/test_regress/t/t_clk_first_deprecated.v @@ -4,8 +4,7 @@ // SPDX-FileCopyrightText: 2003 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 -module t ( /*AUTOARG*/ - // Inputs +module t ( clk ); diff --git a/test_regress/t/t_constraint_pure_nonabs_bad.out b/test_regress/t/t_constraint_pure_nonabs_bad.out index 3de1a490d..4f97b4013 100644 --- a/test_regress/t/t_constraint_pure_nonabs_bad.out +++ b/test_regress/t/t_constraint_pure_nonabs_bad.out @@ -1,6 +1,6 @@ -%Error: t/t_constraint_pure_nonabs_bad.v:8:20: Illegal to have 'pure constraint' in non-abstract class (IEEE 1800-2023 18.5.2) +%Error: t/t_constraint_pure_nonabs_bad.v:8:19: Illegal to have 'pure constraint' in non-abstract class (IEEE 1800-2023 18.5.2) : ... note: In instance 't' - 8 | pure constraint raintBad; - | ^~~~~~~~ + 8 | pure constraint raintBad; + | ^~~~~~~~ ... 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_constraint_pure_nonabs_bad.v b/test_regress/t/t_constraint_pure_nonabs_bad.v index 0ecef66a6..f907c9737 100644 --- a/test_regress/t/t_constraint_pure_nonabs_bad.v +++ b/test_regress/t/t_constraint_pure_nonabs_bad.v @@ -5,7 +5,7 @@ // SPDX-License-Identifier: CC0-1.0 class NonAsbstract; - pure constraint raintBad; // Bad: Not in abstract class + pure constraint raintBad; // Bad: Not in abstract class endclass module t; diff --git a/test_regress/t/t_covergroup_param_bins.out b/test_regress/t/t_covergroup_param_bins.out new file mode 100644 index 000000000..f0cb20f17 --- /dev/null +++ b/test_regress/t/t_covergroup_param_bins.out @@ -0,0 +1,5 @@ +cg.cp.maxv: 2 +cg.cp.minv: 1 +cg.cp.negative: 1 +cg.cp.positive: 3 +cg.cp.zero: 1 diff --git a/test_regress/t/t_covergroup_param_bins.py b/test_regress/t/t_covergroup_param_bins.py new file mode 100755 index 000000000..ab1d96563 --- /dev/null +++ b/test_regress/t/t_covergroup_param_bins.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap +import coverage_covergroup_common + +test.scenarios('vlt') + +coverage_covergroup_common.run(test) diff --git a/test_regress/t/t_covergroup_param_bins.v b/test_regress/t/t_covergroup_param_bins.v new file mode 100644 index 000000000..ab57a4a62 --- /dev/null +++ b/test_regress/t/t_covergroup_param_bins.v @@ -0,0 +1,76 @@ +// DESCRIPTION: Verilator: Verilog Test module - parameter/localparam refs in bins +// This file ONLY is placed into the Public Domain, for any use, without warranty. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Test: Referencing a parameter/localparam identifier inside a covergroup +// `bins` value or range. This previously triggered an internal error +// (V3WidthCommit.cpp: No dtype) because the folded constant had no dtype. +// Expected: compiles and the bounds behave exactly like numeric literals. + +// verilog_format: off +`define stop $stop +`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); +// verilog_format: on + +module t #( + parameter int PMIN = -100 +) ( + input clk +); + + localparam int LMAX = 100; + + int signed value; + + covergroup cg; + cp: coverpoint value { + bins negative = {[PMIN : -1]}; // parameter as range lower bound + 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 + } + endgroup + + cg cg_inst = new; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + + case (cyc) + 0: value <= -100; // Hit negative + minv + 1: value <= 0; // Hit zero + 2: value <= 50; // Hit positive + 3: value <= 100; // Hit positive (dup) + maxv + 4: begin + $write("*-* All Finished *-*\n"); + $finish; + end + endcase + + cg_inst.sample(); + + // Coverage progression (NBA committed before sample() within always block). + // A final sample() also runs on the $finish cycle (value still 100), so the + // positive/maxv hit counts are one higher than the number of distinct stimuli. + // cyc=0: value=-100 -> negative + minv -> 2/5=40% + // cyc=1: value=0 -> zero -> 3/5=60% + // cyc=2: value=50 -> positive -> 4/5=80% + // cyc=3: value=100 -> positive(dup) + maxv -> 5/5=100% + if (cyc == 0) begin + `checkr(cg_inst.get_inst_coverage(), 40.0); + end + if (cyc == 1) begin + `checkr(cg_inst.get_inst_coverage(), 60.0); + end + if (cyc == 2) begin + `checkr(cg_inst.get_inst_coverage(), 80.0); + end + if (cyc == 3) begin + `checkr(cg_inst.get_inst_coverage(), 100.0); + end + end +endmodule diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index aa15acf60..3c9ce7ae8 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -62,6 +62,13 @@ module t ( //verilator lint_on WIDTH `signal(FOLD_UNARY_Extend, tmp_FOLD_UNARY_Extend); `signal(FOLD_UNARY_ExtendS, tmp_FOLD_UNARY_ExtendS); + `signal(FOLD_UNARY_CountOnes, $countones(const_a)); + `signal(FOLD_UNARY_OneHot, $onehot(const_a)); + `signal(FOLD_UNARY_OneHot0, $onehot0(const_a)); + `signal(FOLD_UNARY_OneHot_A, $onehot(const_a[0])); + `signal(FOLD_UNARY_OneHot_B, $onehot(~const_a[0])); + `signal(FOLD_UNARY_OneHot0_A, $onehot0(const_a[0])); + `signal(FOLD_UNARY_OneHot0_B, $onehot0(~const_a[0])); `signal(FOLD_BINARY_Add, const_a + const_b); `signal(FOLD_BINARY_And, const_a & const_b); diff --git a/test_regress/t/t_dfg_synthesis.v b/test_regress/t/t_dfg_synthesis.v index 8bcb19f55..9076b4101 100644 --- a/test_regress/t/t_dfg_synthesis.v +++ b/test_regress/t/t_dfg_synthesis.v @@ -570,4 +570,11 @@ module t ( wire logic [7:0] func_3 = pkg::branchy(rand_a[7:0], rand_b[7:0]); `signal(FUNC_3, func_3); + logic [1:0] via_creset; + always_comb begin + automatic logic [1:0] a /* = AstCReset */; + via_creset = a + 2'd1; + end + `signal(VIA_CRESET, via_creset); + endmodule diff --git a/test_regress/t/t_dist_whitespace.py b/test_regress/t/t_dist_whitespace.py index c6678f5b6..3d92a8750 100755 --- a/test_regress/t/t_dist_whitespace.py +++ b/test_regress/t/t_dist_whitespace.py @@ -8,12 +8,13 @@ # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap +import collections test.scenarios('dist') Tabs_Exempt_Re = r'(\.out$)|(/fstcpp)|(Makefile)|(\.mk$)|(\.mk\.in$)|test_regress/t/t_preproc\.v|install-sh' -Unicode_Exempt_Re = r'(Changes$|CONTRIBUTORS$|LICENSES?|contributors.rst$|spelling.txt$)' +Unicode_Exempt_Re = r'(Changes$|CONTRIBUTORS$|LICENSES?|contributors.rst$|spelling.txt$|ci-rtlmeter-report.py)' def get_source_files(): @@ -28,6 +29,39 @@ def get_source_files(): return files +def check_verilog_indent(filename, contents): + # Check for proper spacing + # This isn't automated as part of CI action auto-format because .out files + # might break as a result + indents = collections.defaultdict(int) + is_on = True + for line in contents.splitlines(): + if re.search(r'// verilog_format: off', line): + is_on = False + if re.search(r'// verilog_format: on', line): + is_on = True + if not is_on: + continue + # verilog-format puts input/output as double levels, ignore + if re.match(r' *(input|output|inout) ', line): + continue + + m = re.match(r'^ *', line) + num_spaces = m.end() if m else 0 + if num_spaces > 0: + indents[num_spaces] += 1 + + # pprint(indents) + spacing = 2 + if (indents[3] + indents[6] + indents[9]) > (indents[2] + indents[4] + indents[6]) * 2: + spacing = 3 + elif indents[2] == 0 and indents[4] > 1: # Hard because two levels of 2-space indents gives 4 + spacing = 4 + if spacing != 2: + warns[filename] = "Indents should be 2 spaces per level, not what seems to be " + str( + spacing) + "/level in: " + filename + + if not os.path.exists(test.root + "/.git"): test.skip("Not in a git repository") @@ -46,7 +80,7 @@ for filename in sorted(files.keys()): continue # Ignore binary files if contents != "" and contents[-1] != "\n": contents += "\n" - warns[filename] = "Missing trailing newline (add one) in " + filename + warns[filename] = "Missing trailing newline (add one) in: " + filename if "\r" in contents: contents = re.sub(r'\r', '', contents) warns[filename] = "Carriage returns (remove them) in: " + filename @@ -84,7 +118,7 @@ for filename in sorted(files.keys()): warns[filename] += " (last character is ASCII " + str(ord(line[-1])) + ")" if not eol_ws_exempt and re.search(r'\n\n+$', contents): # Regexp repeated above - warns[filename] = "Trailing newlines at EOF in " + filename + warns[filename] = "Trailing newlines at EOF in: " + filename # Unicode checker; should this be done in another file? # No way to auto-fix. @@ -92,7 +126,10 @@ for filename in sorted(files.keys()): m = re.search(r'(([^ \t\r\n\x20-\x7e]).*)', contents) if not unicode_exempt and m: warns[filename] = "Warning: non-ASCII contents '" + m.group(2) + "' at '" + m.group( - 1) + "' in " + filename + 1) + "' in: " + filename + + if re.search(r'\.s?vh?$', filename): + check_verilog_indent(filename, contents) fcount += 1 @@ -106,7 +143,8 @@ if len(warns): msg += "Updated files with whitespace errors: " + ' '.join(sorted(warns.keys())) + "\n" else: msg += "Files have whitespace errors: " + ' '.join(sorted(warns.keys())) + "\n" - msg += "To auto-fix: HARNESS_UPDATE_GOLDEN=1 {command} or --golden\n" + msg += "To auto-fix (some): HARNESS_UPDATE_GOLDEN=1 {command} or --golden\n" + msg += "If change any Verilog then remember to update .out files too (with --golden)\n" for filename in sorted(warns.keys()): msg += warns[filename] + "\n" test.error(msg) diff --git a/test_regress/t/t_dpi_accessors_inc.vh b/test_regress/t/t_dpi_accessors_inc.vh index 7a2339678..911d4e874 100644 --- a/test_regress/t/t_dpi_accessors_inc.vh +++ b/test_regress/t/t_dpi_accessors_inc.vh @@ -9,29 +9,26 @@ // See t_dpi_accessors.v for details of the test. This file should be included // by the top level module to define all the accessors needed. - // Use the macros to provide the desire access to our data. First simple - // access to the registers, array elements and wires. For consistency with - // simulators, we do not attempt to write wires. - `RW_ACCESS([0:0], a, {t.i_test_sub.a}); - `RW_ACCESS([7:0], b, {t.i_test_sub.b}); - `RW_ACCESS([7:0], mem32, {t.i_test_sub.mem[32]}); - `R_ACCESS ([0:0], c, {t.i_test_sub.c}); - `R_ACCESS ([7:0], d, {t.i_test_sub.d}); - `RW_ACCESS([7:0], e, {t.i_test_sub.e}); - `RW_ACCESS([7:0], f, {t.i_test_sub.f}); +// Use the macros to provide the desire access to our data. First simple +// access to the registers, array elements and wires. For consistency with +// simulators, we do not attempt to write wires. +`RW_ACCESS([0:0], a, {t.i_test_sub.a}); +`RW_ACCESS([7:0], b, {t.i_test_sub.b}); +`RW_ACCESS([7:0], mem32, {t.i_test_sub.mem[32]}); +`R_ACCESS([0:0], c, {t.i_test_sub.c}); +`R_ACCESS([7:0], d, {t.i_test_sub.d}); +`RW_ACCESS([7:0], e, {t.i_test_sub.e}); +`RW_ACCESS([7:0], f, {t.i_test_sub.f}); - // Slices of vectors and array elements. For consistency with simulators, - // we do not attempt to write wire slices. - `RW_ACCESS([3:0], b_slice, {t.i_test_sub.b[3:0]}); - `RW_ACCESS([4:0], mem32_slice, - {t.i_test_sub.mem[32][7:6], t.i_test_sub.mem[32][2:0]}); - `R_ACCESS([5:0], d_slice, {t.i_test_sub.d[6:1]}); +// Slices of vectors and array elements. For consistency with simulators, +// we do not attempt to write wire slices. +`RW_ACCESS([3:0], b_slice, {t.i_test_sub.b[3:0]}); +`RW_ACCESS([4:0], mem32_slice, {t.i_test_sub.mem[32][7:6], t.i_test_sub.mem[32][2:0]}); +`R_ACCESS([5:0], d_slice, {t.i_test_sub.d[6:1]}); - // Complex registers, one with distinct read and write. We avoid use of - // wires for consistency with simulators. - `RW_ACCESS([14:0], l1, {t.i_test_sub.b[3:0], - t.i_test_sub.mem[32][7:6], - t.i_test_sub.e[6:1], - t.i_test_sub.mem[32][2:0]}); - `R_ACCESS([7:0], l2, {t.i_test_sub.e[7:4], t.i_test_sub.f[3:0]}); - `W_ACCESS([7:0], l2, {t.i_test_sub.e[5:2], t.i_test_sub.f[5:2]}); +// Complex registers, one with distinct read and write. We avoid use of +// wires for consistency with simulators. +`RW_ACCESS([14:0], l1, {t.i_test_sub.b[3:0], t.i_test_sub.mem[32][7:6], t.i_test_sub.e[6:1], + t.i_test_sub.mem[32][2:0]}); +`R_ACCESS([7:0], l2, {t.i_test_sub.e[7:4], t.i_test_sub.f[3:0]}); +`W_ACCESS([7:0], l2, {t.i_test_sub.e[5:2], t.i_test_sub.f[5:2]}); diff --git a/test_regress/t/t_dpi_accessors_macros_inc.vh b/test_regress/t/t_dpi_accessors_macros_inc.vh index 90c41f5ce..1a017ec7e 100644 --- a/test_regress/t/t_dpi_accessors_macros_inc.vh +++ b/test_regress/t/t_dpi_accessors_macros_inc.vh @@ -9,20 +9,22 @@ // See t_dpi_accessors.v for details of the test. This file should be included // by the top level module to define the generic accessor macros. +// verilog_format: off + // Accessor macros, to keep stuff concise `define R_ACCESS(type_spec, name, expr) \ - export "DPI-C" function name``_read; \ - function bit type_spec name``_read; \ - name``_read = (expr); \ - endfunction + export "DPI-C" function name``_read; \ + function bit type_spec name``_read; \ + name``_read = (expr); \ + endfunction -`define W_ACCESS(type_spec, name, expr) \ - export "DPI-C" task name``_write; \ - task name``_write; \ - input bit type_spec in; \ - expr = in; \ - endtask +`define W_ACCESS(type_spec, name, expr) \ + export "DPI-C" task name``_write; \ + task name``_write; \ + input bit type_spec in; \ + expr = in; \ + endtask `define RW_ACCESS(type_spec, name, expr) \ - `R_ACCESS (type_spec, name, expr); \ - `W_ACCESS (type_spec, name, expr) + `R_ACCESS (type_spec, name, expr); \ + `W_ACCESS (type_spec, name, expr) diff --git a/test_regress/t/t_forceable_unpacked.v b/test_regress/t/t_forceable_unpacked.v index 5016bdbef..5e0048563 100644 --- a/test_regress/t/t_forceable_unpacked.v +++ b/test_regress/t/t_forceable_unpacked.v @@ -4,13 +4,16 @@ // SPDX-FileCopyrightText: 2026 Nikolai Kumar // SPDX-License-Identifier: CC0-1.0 +// verilog_format: off `define checkh(g,e) do if ((g) !==(e)) begin $write("%%Error: %s:%0d: got=%x exp=%x\n", `__FILE__,`__LINE__, (g),(e)); $stop; end while(0) `ifdef CMT - `define FORCEABLE /*verilator forceable*/ + `define FORCEABLE /*verilator forceable*/ `else - `define FORCEABLE + `define FORCEABLE `endif +// verilog_format: on + module t (input wire clk, output reg [31:0] cyc); initial cyc = 0; always @(posedge clk) cyc <= cyc + 1; diff --git a/test_regress/t/t_hier_block_chained.v b/test_regress/t/t_hier_block_chained.v index 5d3739823..db3fe8a9b 100644 --- a/test_regress/t/t_hier_block_chained.v +++ b/test_regress/t/t_hier_block_chained.v @@ -11,204 +11,204 @@ module t (clk); logic reset; - reg [255:0] v2_0; - reg [255:0] v1_0; - reg [255:0] v1_1; - reg [255:0] v1_2; - reg [255:0] v1_3; - reg [255:0] v1_4; - reg [255:0] v1_5; - reg [255:0] v1_6; - reg [255:0] v1_7; - // verilator lint_off MULTIDRIVEN - reg [255:0] dummy; - // verilator lint_on MULTIDRIVEN + reg [255:0] v2_0; + reg [255:0] v1_0; + reg [255:0] v1_1; + reg [255:0] v1_2; + reg [255:0] v1_3; + reg [255:0] v1_4; + reg [255:0] v1_5; + reg [255:0] v1_6; + reg [255:0] v1_7; + // verilator lint_off MULTIDRIVEN + reg [255:0] dummy; + // verilator lint_on MULTIDRIVEN - Calculate calc0(.clk(clk), .reset(reset), .v1_0(v1_0), .v1_1(dummy), .v1_2(dummy), .v1_3(dummy), .v1_4(dummy), .v1_5(dummy), .v1_6(dummy), .v1_7(dummy)); - Calculate calc1(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(v1_1), .v1_2(dummy), .v1_3(dummy), .v1_4(dummy), .v1_5(dummy), .v1_6(dummy), .v1_7(dummy)); - Calculate calc2(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(v1_2), .v1_3(dummy), .v1_4(dummy), .v1_5(dummy), .v1_6(dummy), .v1_7(dummy)); - Calculate calc3(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(dummy), .v1_3(v1_3), .v1_4(dummy), .v1_5(dummy), .v1_6(dummy), .v1_7(dummy)); - Calculate calc4(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(dummy), .v1_3(dummy), .v1_4(v1_4), .v1_5(dummy), .v1_6(dummy), .v1_7(dummy)); - Calculate calc5(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(dummy), .v1_3(dummy), .v1_4(dummy), .v1_5(v1_5), .v1_6(dummy), .v1_7(dummy)); - Calculate calc6(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(dummy), .v1_3(dummy), .v1_4(dummy), .v1_5(dummy), .v1_6(v1_6), .v1_7(dummy)); - Calculate calc7(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(dummy), .v1_3(dummy), .v1_4(dummy), .v1_5(dummy), .v1_6(dummy), .v1_7(v1_7)); - always @ (posedge clk) v2_0 <= v1_0 + v1_1 + v1_2 + v1_3 + v1_4 + v1_5 + v1_6 + v1_7; - Check chk(.clk(clk), .reset(reset), .v2_0(v2_0)); + Calculate calc0(.clk(clk), .reset(reset), .v1_0(v1_0), .v1_1(dummy), .v1_2(dummy), .v1_3(dummy), .v1_4(dummy), .v1_5(dummy), .v1_6(dummy), .v1_7(dummy)); + Calculate calc1(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(v1_1), .v1_2(dummy), .v1_3(dummy), .v1_4(dummy), .v1_5(dummy), .v1_6(dummy), .v1_7(dummy)); + Calculate calc2(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(v1_2), .v1_3(dummy), .v1_4(dummy), .v1_5(dummy), .v1_6(dummy), .v1_7(dummy)); + Calculate calc3(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(dummy), .v1_3(v1_3), .v1_4(dummy), .v1_5(dummy), .v1_6(dummy), .v1_7(dummy)); + Calculate calc4(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(dummy), .v1_3(dummy), .v1_4(v1_4), .v1_5(dummy), .v1_6(dummy), .v1_7(dummy)); + Calculate calc5(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(dummy), .v1_3(dummy), .v1_4(dummy), .v1_5(v1_5), .v1_6(dummy), .v1_7(dummy)); + Calculate calc6(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(dummy), .v1_3(dummy), .v1_4(dummy), .v1_5(dummy), .v1_6(v1_6), .v1_7(dummy)); + Calculate calc7(.clk(clk), .reset(reset), .v1_0(dummy), .v1_1(dummy), .v1_2(dummy), .v1_3(dummy), .v1_4(dummy), .v1_5(dummy), .v1_6(dummy), .v1_7(v1_7)); + always @ (posedge clk) v2_0 <= v1_0 + v1_1 + v1_2 + v1_3 + v1_4 + v1_5 + v1_6 + v1_7; + Check chk(.clk(clk), .reset(reset), .v2_0(v2_0)); endmodule module Check(input clk, output logic reset, input reg [255:0] v2_0); integer cyc=0; - always @ (posedge clk) begin - cyc <= cyc + 1; + always @ (posedge clk) begin + cyc <= cyc + 1; `ifdef TEST_VERBOSE - $write("[%0t] rst=%0x v0_0=%0x v1_0=%0x result=%0x\n", $time, reset, v0_0, v1_0, v2_0); + $write("[%0t] rst=%0x v0_0=%0x v1_0=%0x result=%0x\n", $time, reset, v0_0, v1_0, v2_0); `endif - if (cyc==0) begin - reset <= 1; - end - else if (cyc==10) begin - reset <= 0; - end + if (cyc==0) begin + reset <= 1; + end + else if (cyc==10) begin + reset <= 0; + end `ifndef SIM_CYCLES `define SIM_CYCLES 99 `endif - else if (cyc==`SIM_CYCLES) begin - if (v2_0 != 256'd2017) $stop; - $write("VARS=64 WIDTH=256 WORKINGSET=2KB\n"); - $write("*-* All Finished *-*\n"); - $finish; - end - end + else if (cyc==`SIM_CYCLES) begin + if (v2_0 != 256'd2017) $stop; + $write("VARS=64 WIDTH=256 WORKINGSET=2KB\n"); + $write("*-* All Finished *-*\n"); + $finish; + end + end endmodule module Calculate(input clk, - input reset, - output reg [255:0] v1_0, - output reg [255:0] v1_1, - output reg [255:0] v1_2, - output reg [255:0] v1_3, - output reg [255:0] v1_4, - output reg [255:0] v1_5, - output reg [255:0] v1_6, - output reg [255:0] v1_7 - ); - reg [255:0] v0_0; - reg [255:0] v0_1; - reg [255:0] v0_2; - reg [255:0] v0_3; - reg [255:0] v0_4; - reg [255:0] v0_5; - reg [255:0] v0_6; - reg [255:0] v0_7; - reg [255:0] v0_8; - reg [255:0] v0_9; - reg [255:0] v0_10; - reg [255:0] v0_11; - reg [255:0] v0_12; - reg [255:0] v0_13; - reg [255:0] v0_14; - reg [255:0] v0_15; - reg [255:0] v0_16; - reg [255:0] v0_17; - reg [255:0] v0_18; - reg [255:0] v0_19; - reg [255:0] v0_20; - reg [255:0] v0_21; - reg [255:0] v0_22; - reg [255:0] v0_23; - reg [255:0] v0_24; - reg [255:0] v0_25; - reg [255:0] v0_26; - reg [255:0] v0_27; - reg [255:0] v0_28; - reg [255:0] v0_29; - reg [255:0] v0_30; - reg [255:0] v0_31; - reg [255:0] v0_32; - reg [255:0] v0_33; - reg [255:0] v0_34; - reg [255:0] v0_35; - reg [255:0] v0_36; - reg [255:0] v0_37; - reg [255:0] v0_38; - reg [255:0] v0_39; - reg [255:0] v0_40; - reg [255:0] v0_41; - reg [255:0] v0_42; - reg [255:0] v0_43; - reg [255:0] v0_44; - reg [255:0] v0_45; - reg [255:0] v0_46; - reg [255:0] v0_47; - reg [255:0] v0_48; - reg [255:0] v0_49; - reg [255:0] v0_50; - reg [255:0] v0_51; - reg [255:0] v0_52; - reg [255:0] v0_53; - reg [255:0] v0_54; - reg [255:0] v0_55; - reg [255:0] v0_56; - reg [255:0] v0_57; - reg [255:0] v0_58; - reg [255:0] v0_59; - reg [255:0] v0_60; - reg [255:0] v0_61; - reg [255:0] v0_62; - reg [255:0] v0_63; + input reset, + output reg [255:0] v1_0, + output reg [255:0] v1_1, + output reg [255:0] v1_2, + output reg [255:0] v1_3, + output reg [255:0] v1_4, + output reg [255:0] v1_5, + output reg [255:0] v1_6, + output reg [255:0] v1_7 + ); + reg [255:0] v0_0; + reg [255:0] v0_1; + reg [255:0] v0_2; + reg [255:0] v0_3; + reg [255:0] v0_4; + reg [255:0] v0_5; + reg [255:0] v0_6; + reg [255:0] v0_7; + reg [255:0] v0_8; + reg [255:0] v0_9; + reg [255:0] v0_10; + reg [255:0] v0_11; + reg [255:0] v0_12; + reg [255:0] v0_13; + reg [255:0] v0_14; + reg [255:0] v0_15; + reg [255:0] v0_16; + reg [255:0] v0_17; + reg [255:0] v0_18; + reg [255:0] v0_19; + reg [255:0] v0_20; + reg [255:0] v0_21; + reg [255:0] v0_22; + reg [255:0] v0_23; + reg [255:0] v0_24; + reg [255:0] v0_25; + reg [255:0] v0_26; + reg [255:0] v0_27; + reg [255:0] v0_28; + reg [255:0] v0_29; + reg [255:0] v0_30; + reg [255:0] v0_31; + reg [255:0] v0_32; + reg [255:0] v0_33; + reg [255:0] v0_34; + reg [255:0] v0_35; + reg [255:0] v0_36; + reg [255:0] v0_37; + reg [255:0] v0_38; + reg [255:0] v0_39; + reg [255:0] v0_40; + reg [255:0] v0_41; + reg [255:0] v0_42; + reg [255:0] v0_43; + reg [255:0] v0_44; + reg [255:0] v0_45; + reg [255:0] v0_46; + reg [255:0] v0_47; + reg [255:0] v0_48; + reg [255:0] v0_49; + reg [255:0] v0_50; + reg [255:0] v0_51; + reg [255:0] v0_52; + reg [255:0] v0_53; + reg [255:0] v0_54; + reg [255:0] v0_55; + reg [255:0] v0_56; + reg [255:0] v0_57; + reg [255:0] v0_58; + reg [255:0] v0_59; + reg [255:0] v0_60; + reg [255:0] v0_61; + reg [255:0] v0_62; + reg [255:0] v0_63; - always @ (posedge clk) v0_0 <= reset ? 256'd1 : v0_1; - always @ (posedge clk) v0_1 <= reset ? 256'd1 : v0_2; - always @ (posedge clk) v0_2 <= reset ? 256'd2 : v0_3; - always @ (posedge clk) v0_3 <= reset ? 256'd3 : v0_4; - always @ (posedge clk) v0_4 <= reset ? 256'd4 : v0_5; - always @ (posedge clk) v0_5 <= reset ? 256'd5 : v0_6; - always @ (posedge clk) v0_6 <= reset ? 256'd6 : v0_7; - always @ (posedge clk) v0_7 <= reset ? 256'd7 : v0_0; - always @ (posedge clk) v0_8 <= reset ? 256'd8 : v0_9; - always @ (posedge clk) v0_9 <= reset ? 256'd9 : v0_10; - always @ (posedge clk) v0_10 <= reset ? 256'd10 : v0_11; - always @ (posedge clk) v0_11 <= reset ? 256'd11 : v0_12; - always @ (posedge clk) v0_12 <= reset ? 256'd12 : v0_13; - always @ (posedge clk) v0_13 <= reset ? 256'd13 : v0_14; - always @ (posedge clk) v0_14 <= reset ? 256'd14 : v0_15; - always @ (posedge clk) v0_15 <= reset ? 256'd15 : v0_8; - always @ (posedge clk) v0_16 <= reset ? 256'd16 : v0_17; - always @ (posedge clk) v0_17 <= reset ? 256'd17 : v0_18; - always @ (posedge clk) v0_18 <= reset ? 256'd18 : v0_19; - always @ (posedge clk) v0_19 <= reset ? 256'd19 : v0_20; - always @ (posedge clk) v0_20 <= reset ? 256'd20 : v0_21; - always @ (posedge clk) v0_21 <= reset ? 256'd21 : v0_22; - always @ (posedge clk) v0_22 <= reset ? 256'd22 : v0_23; - always @ (posedge clk) v0_23 <= reset ? 256'd23 : v0_16; - always @ (posedge clk) v0_24 <= reset ? 256'd24 : v0_25; - always @ (posedge clk) v0_25 <= reset ? 256'd25 : v0_26; - always @ (posedge clk) v0_26 <= reset ? 256'd26 : v0_27; - always @ (posedge clk) v0_27 <= reset ? 256'd27 : v0_28; - always @ (posedge clk) v0_28 <= reset ? 256'd28 : v0_29; - always @ (posedge clk) v0_29 <= reset ? 256'd29 : v0_30; - always @ (posedge clk) v0_30 <= reset ? 256'd30 : v0_31; - always @ (posedge clk) v0_31 <= reset ? 256'd31 : v0_24; - always @ (posedge clk) v0_32 <= reset ? 256'd32 : v0_33; - always @ (posedge clk) v0_33 <= reset ? 256'd33 : v0_34; - always @ (posedge clk) v0_34 <= reset ? 256'd34 : v0_35; - always @ (posedge clk) v0_35 <= reset ? 256'd35 : v0_36; - always @ (posedge clk) v0_36 <= reset ? 256'd36 : v0_37; - always @ (posedge clk) v0_37 <= reset ? 256'd37 : v0_38; - always @ (posedge clk) v0_38 <= reset ? 256'd38 : v0_39; - always @ (posedge clk) v0_39 <= reset ? 256'd39 : v0_32; - always @ (posedge clk) v0_40 <= reset ? 256'd40 : v0_41; - always @ (posedge clk) v0_41 <= reset ? 256'd41 : v0_42; - always @ (posedge clk) v0_42 <= reset ? 256'd42 : v0_43; - always @ (posedge clk) v0_43 <= reset ? 256'd43 : v0_44; - always @ (posedge clk) v0_44 <= reset ? 256'd44 : v0_45; - always @ (posedge clk) v0_45 <= reset ? 256'd45 : v0_46; - always @ (posedge clk) v0_46 <= reset ? 256'd46 : v0_47; - always @ (posedge clk) v0_47 <= reset ? 256'd47 : v0_40; - always @ (posedge clk) v0_48 <= reset ? 256'd48 : v0_49; - always @ (posedge clk) v0_49 <= reset ? 256'd49 : v0_50; - always @ (posedge clk) v0_50 <= reset ? 256'd50 : v0_51; - always @ (posedge clk) v0_51 <= reset ? 256'd51 : v0_52; - always @ (posedge clk) v0_52 <= reset ? 256'd52 : v0_53; - always @ (posedge clk) v0_53 <= reset ? 256'd53 : v0_54; - always @ (posedge clk) v0_54 <= reset ? 256'd54 : v0_55; - always @ (posedge clk) v0_55 <= reset ? 256'd55 : v0_48; - always @ (posedge clk) v0_56 <= reset ? 256'd56 : v0_57; - always @ (posedge clk) v0_57 <= reset ? 256'd57 : v0_58; - always @ (posedge clk) v0_58 <= reset ? 256'd58 : v0_59; - always @ (posedge clk) v0_59 <= reset ? 256'd59 : v0_60; - always @ (posedge clk) v0_60 <= reset ? 256'd60 : v0_61; - always @ (posedge clk) v0_61 <= reset ? 256'd61 : v0_62; - always @ (posedge clk) v0_62 <= reset ? 256'd62 : v0_63; - always @ (posedge clk) v0_63 <= reset ? 256'd63 : v0_56; + always @ (posedge clk) v0_0 <= reset ? 256'd1 : v0_1; + always @ (posedge clk) v0_1 <= reset ? 256'd1 : v0_2; + always @ (posedge clk) v0_2 <= reset ? 256'd2 : v0_3; + always @ (posedge clk) v0_3 <= reset ? 256'd3 : v0_4; + always @ (posedge clk) v0_4 <= reset ? 256'd4 : v0_5; + always @ (posedge clk) v0_5 <= reset ? 256'd5 : v0_6; + always @ (posedge clk) v0_6 <= reset ? 256'd6 : v0_7; + always @ (posedge clk) v0_7 <= reset ? 256'd7 : v0_0; + always @ (posedge clk) v0_8 <= reset ? 256'd8 : v0_9; + always @ (posedge clk) v0_9 <= reset ? 256'd9 : v0_10; + always @ (posedge clk) v0_10 <= reset ? 256'd10 : v0_11; + always @ (posedge clk) v0_11 <= reset ? 256'd11 : v0_12; + always @ (posedge clk) v0_12 <= reset ? 256'd12 : v0_13; + always @ (posedge clk) v0_13 <= reset ? 256'd13 : v0_14; + always @ (posedge clk) v0_14 <= reset ? 256'd14 : v0_15; + always @ (posedge clk) v0_15 <= reset ? 256'd15 : v0_8; + always @ (posedge clk) v0_16 <= reset ? 256'd16 : v0_17; + always @ (posedge clk) v0_17 <= reset ? 256'd17 : v0_18; + always @ (posedge clk) v0_18 <= reset ? 256'd18 : v0_19; + always @ (posedge clk) v0_19 <= reset ? 256'd19 : v0_20; + always @ (posedge clk) v0_20 <= reset ? 256'd20 : v0_21; + always @ (posedge clk) v0_21 <= reset ? 256'd21 : v0_22; + always @ (posedge clk) v0_22 <= reset ? 256'd22 : v0_23; + always @ (posedge clk) v0_23 <= reset ? 256'd23 : v0_16; + always @ (posedge clk) v0_24 <= reset ? 256'd24 : v0_25; + always @ (posedge clk) v0_25 <= reset ? 256'd25 : v0_26; + always @ (posedge clk) v0_26 <= reset ? 256'd26 : v0_27; + always @ (posedge clk) v0_27 <= reset ? 256'd27 : v0_28; + always @ (posedge clk) v0_28 <= reset ? 256'd28 : v0_29; + always @ (posedge clk) v0_29 <= reset ? 256'd29 : v0_30; + always @ (posedge clk) v0_30 <= reset ? 256'd30 : v0_31; + always @ (posedge clk) v0_31 <= reset ? 256'd31 : v0_24; + always @ (posedge clk) v0_32 <= reset ? 256'd32 : v0_33; + always @ (posedge clk) v0_33 <= reset ? 256'd33 : v0_34; + always @ (posedge clk) v0_34 <= reset ? 256'd34 : v0_35; + always @ (posedge clk) v0_35 <= reset ? 256'd35 : v0_36; + always @ (posedge clk) v0_36 <= reset ? 256'd36 : v0_37; + always @ (posedge clk) v0_37 <= reset ? 256'd37 : v0_38; + always @ (posedge clk) v0_38 <= reset ? 256'd38 : v0_39; + always @ (posedge clk) v0_39 <= reset ? 256'd39 : v0_32; + always @ (posedge clk) v0_40 <= reset ? 256'd40 : v0_41; + always @ (posedge clk) v0_41 <= reset ? 256'd41 : v0_42; + always @ (posedge clk) v0_42 <= reset ? 256'd42 : v0_43; + always @ (posedge clk) v0_43 <= reset ? 256'd43 : v0_44; + always @ (posedge clk) v0_44 <= reset ? 256'd44 : v0_45; + always @ (posedge clk) v0_45 <= reset ? 256'd45 : v0_46; + always @ (posedge clk) v0_46 <= reset ? 256'd46 : v0_47; + always @ (posedge clk) v0_47 <= reset ? 256'd47 : v0_40; + always @ (posedge clk) v0_48 <= reset ? 256'd48 : v0_49; + always @ (posedge clk) v0_49 <= reset ? 256'd49 : v0_50; + always @ (posedge clk) v0_50 <= reset ? 256'd50 : v0_51; + always @ (posedge clk) v0_51 <= reset ? 256'd51 : v0_52; + always @ (posedge clk) v0_52 <= reset ? 256'd52 : v0_53; + always @ (posedge clk) v0_53 <= reset ? 256'd53 : v0_54; + always @ (posedge clk) v0_54 <= reset ? 256'd54 : v0_55; + always @ (posedge clk) v0_55 <= reset ? 256'd55 : v0_48; + always @ (posedge clk) v0_56 <= reset ? 256'd56 : v0_57; + always @ (posedge clk) v0_57 <= reset ? 256'd57 : v0_58; + always @ (posedge clk) v0_58 <= reset ? 256'd58 : v0_59; + always @ (posedge clk) v0_59 <= reset ? 256'd59 : v0_60; + always @ (posedge clk) v0_60 <= reset ? 256'd60 : v0_61; + always @ (posedge clk) v0_61 <= reset ? 256'd61 : v0_62; + always @ (posedge clk) v0_62 <= reset ? 256'd62 : v0_63; + always @ (posedge clk) v0_63 <= reset ? 256'd63 : v0_56; - always @ (posedge clk) v1_0 <= v0_0 + v0_1 + v0_2 + v0_3 + v0_4 + v0_5 + v0_6 + v0_7; - always @ (posedge clk) v1_1 <= v0_8 + v0_9 + v0_10 + v0_11 + v0_12 + v0_13 + v0_14 + v0_15; - always @ (posedge clk) v1_2 <= v0_16 + v0_17 + v0_18 + v0_19 + v0_20 + v0_21 + v0_22 + v0_23; - always @ (posedge clk) v1_3 <= v0_24 + v0_25 + v0_26 + v0_27 + v0_28 + v0_29 + v0_30 + v0_31; - always @ (posedge clk) v1_4 <= v0_32 + v0_33 + v0_34 + v0_35 + v0_36 + v0_37 + v0_38 + v0_39; - always @ (posedge clk) v1_5 <= v0_40 + v0_41 + v0_42 + v0_43 + v0_44 + v0_45 + v0_46 + v0_47; - always @ (posedge clk) v1_6 <= v0_48 + v0_49 + v0_50 + v0_51 + v0_52 + v0_53 + v0_54 + v0_55; - always @ (posedge clk) v1_7 <= v0_56 + v0_57 + v0_58 + v0_59 + v0_60 + v0_61 + v0_62 + v0_63; + always @ (posedge clk) v1_0 <= v0_0 + v0_1 + v0_2 + v0_3 + v0_4 + v0_5 + v0_6 + v0_7; + always @ (posedge clk) v1_1 <= v0_8 + v0_9 + v0_10 + v0_11 + v0_12 + v0_13 + v0_14 + v0_15; + always @ (posedge clk) v1_2 <= v0_16 + v0_17 + v0_18 + v0_19 + v0_20 + v0_21 + v0_22 + v0_23; + always @ (posedge clk) v1_3 <= v0_24 + v0_25 + v0_26 + v0_27 + v0_28 + v0_29 + v0_30 + v0_31; + always @ (posedge clk) v1_4 <= v0_32 + v0_33 + v0_34 + v0_35 + v0_36 + v0_37 + v0_38 + v0_39; + always @ (posedge clk) v1_5 <= v0_40 + v0_41 + v0_42 + v0_43 + v0_44 + v0_45 + v0_46 + v0_47; + always @ (posedge clk) v1_6 <= v0_48 + v0_49 + v0_50 + v0_51 + v0_52 + v0_53 + v0_54 + v0_55; + always @ (posedge clk) v1_7 <= v0_56 + v0_57 + v0_58 + v0_59 + v0_60 + v0_61 + v0_62 + v0_63; endmodule diff --git a/test_regress/t/t_initial_inc.vh b/test_regress/t/t_initial_inc.vh index 3e52492c1..d6b3bee9f 100644 --- a/test_regress/t/t_initial_inc.vh +++ b/test_regress/t/t_initial_inc.vh @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: 2003 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 +// verilog_format: off `define foo bar `ifdef foo `ifdef baz `else diff --git a/test_regress/t/t_interface_hierparam_bits.py b/test_regress/t/t_interface_hierparam_bits.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_interface_hierparam_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_hierparam_bits.v b/test_regress/t/t_interface_hierparam_bits.v new file mode 100644 index 000000000..6064bade7 --- /dev/null +++ b/test_regress/t/t_interface_hierparam_bits.v @@ -0,0 +1,67 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// $bits() of interface member signals used as a child module parameter value. +// $bits reads the type, not the hierarchical value, so it is legal in a +// parameter (IEEE 1800-2023 6.20.2 forbids hierarchical values only). +// +// 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='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +// verilog_format: on + +interface axi_if #( + parameter int ID_W = 8, + parameter int ADDR_W = 32 +); + logic [ID_W-1:0] AWID; + logic [ADDR_W-1:0] AWADDR; + logic [7:0] AWLEN; +endinterface + +module chkmod #( + parameter int PAYLOAD_WIDTH = 1, + parameter int EXPECT = 1 +); + initial `checkh(PAYLOAD_WIDTH, EXPECT); +endmodule + +module bridge #( + parameter int EXPECT = 1 +) ( + axi_if axi +); + // $bits of a concat of interface members + chkmod #( + .PAYLOAD_WIDTH($bits({axi.AWID, axi.AWADDR, axi.AWLEN})), + .EXPECT(EXPECT) + ) u_concat (); + // $bits of a single interface member + chkmod #( + .PAYLOAD_WIDTH($bits(axi.AWADDR)), + .EXPECT(EXPECT - 12 - 8) + ) u_single (); +endmodule + +module t; + axi_if #( + .ID_W(12), + .ADDR_W(64) + ) if0 (); // 12 + 64 + 8 = 84 + axi_if #( + .ID_W(12), + .ADDR_W(16) + ) if1 (); // 12 + 16 + 8 = 36 + + bridge #(.EXPECT(84)) dut0 (.axi(if0)); + bridge #(.EXPECT(36)) dut1 (.axi(if1)); + + initial begin + #1; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_lint_in_inc_bad.out b/test_regress/t/t_lint_in_inc_bad.out index 99675736e..8044313b5 100644 --- a/test_regress/t/t_lint_in_inc_bad.out +++ b/test_regress/t/t_lint_in_inc_bad.out @@ -1,6 +1,6 @@ -%Error: t/t_lint_in_inc_bad_2.vh:9:7: syntax error, unexpected if, expecting '(' - 9 | if if if; - | ^~ +%Error: t/t_lint_in_inc_bad_2.vh:9:6: syntax error, unexpected if, expecting '(' + 9 | if if if; + | ^~ t/t_lint_in_inc_bad_1.vh:8:1: ... note: In file included from 't_lint_in_inc_bad_1.vh' t/t_lint_in_inc_bad.v:8:1: ... note: In file included from 't_lint_in_inc_bad.v' ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. diff --git a/test_regress/t/t_lint_in_inc_bad_2.vh b/test_regress/t/t_lint_in_inc_bad_2.vh index cfcc5be74..f509db1c1 100644 --- a/test_regress/t/t_lint_in_inc_bad_2.vh +++ b/test_regress/t/t_lint_in_inc_bad_2.vh @@ -5,6 +5,6 @@ // SPDX-License-Identifier: CC0-1.0 module x; - // Syntax error - if if if; + // Syntax error + if if if; endmodule diff --git a/test_regress/t/t_lint_latch_bad_3.out b/test_regress/t/t_lint_latch_bad_3.out index 93a9577b8..137c4bc26 100644 --- a/test_regress/t/t_lint_latch_bad_3.out +++ b/test_regress/t/t_lint_latch_bad_3.out @@ -1,7 +1,7 @@ -%Warning-LATCH: t/t_lint_latch_bad_3.v:19:1: Latch inferred for signal 'o5' (not all control paths of combinational always assign a value) +%Warning-LATCH: t/t_lint_latch_bad_3.v:30:3: Latch inferred for signal 'o5' (not all control paths of combinational always assign a value) : ... Suggest use of always_latch for intentional latches - 19 | always_comb - | ^~~~~~~~~~~ + 30 | always_comb + | ^~~~~~~~~~~ ... For warning description see https://verilator.org/warn/LATCH?v=latest ... Use "/* verilator lint_off LATCH */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_lint_latch_bad_3.v b/test_regress/t/t_lint_latch_bad_3.v index ee459ade6..c37a5749d 100644 --- a/test_regress/t/t_lint_latch_bad_3.v +++ b/test_regress/t/t_lint_latch_bad_3.v @@ -4,72 +4,73 @@ // SPDX-FileCopyrightText: 2020 Julien Margetts // SPDX-License-Identifier: Unlicense -module t (/*AUTOARG*/ reset, a, b, c, en, o1, o2, o3, o4, o5); - input reset; - input a; - input b; - input c; - input en; - output reg o1; // Always assigned - output reg o2; // " - output reg o3; // " - output reg o4; // " - output reg o5; // Latch +module t ( /*AUTOARG*/ + reset, + a, + b, + c, + en, + o1, + o2, + o3, + o4, + o5 +); + input reset; + input a; + input b; + input c; + input en; + output reg o1; // Always assigned + output reg o2; // " + output reg o3; // " + output reg o4; // " + output reg o5; // Latch -always_comb -if (reset) -begin - o1 = 1'b0; - o2 = 1'b0; - o3 = 1'b0; - o4 = 1'b0; - o5 = 1'b0; -end -else -begin - o1 = 1'b1; - if (en) - begin + always_comb + if (reset) begin + o1 = 1'b0; o2 = 1'b0; - - if (a) - begin - o3 = a; - o5 = 1'b1; - end - else - begin - o3 = ~a; - o5 = a; - end - - // o3 is not assigned in either path of this if/else - // but no latch because always assigned above - if (c) - begin - o2 = a ^ b; - o4 = 1'b1; - end - else - o4 = ~a ^ b; - - o2 = 1'b1; - end - else - begin - o2 = 1'b1; - if (b) - begin - o3 = ~a | b; - o5 = ~b; - end - else - begin - o3 = a & ~b; - // No assignment to o5, expect Warning-LATCH - end + o3 = 1'b0; o4 = 1'b0; - end -end + o5 = 1'b0; + end + else begin + o1 = 1'b1; + if (en) begin + o2 = 1'b0; + + if (a) begin + o3 = a; + o5 = 1'b1; + end + else begin + o3 = ~a; + o5 = a; + end + + // o3 is not assigned in either path of this if/else + // but no latch because always assigned above + if (c) begin + o2 = a ^ b; + o4 = 1'b1; + end + else o4 = ~a ^ b; + + o2 = 1'b1; + end + else begin + o2 = 1'b1; + if (b) begin + o3 = ~a | b; + o5 = ~b; + end + else begin + o3 = a & ~b; + // No assignment to o5, expect Warning-LATCH + end + o4 = 1'b0; + end + end endmodule diff --git a/test_regress/t/t_lint_warn_incfile2_bad_b.vh b/test_regress/t/t_lint_warn_incfile2_bad_b.vh index bc6ffde2f..9a9e217dd 100644 --- a/test_regress/t/t_lint_warn_incfile2_bad_b.vh +++ b/test_regress/t/t_lint_warn_incfile2_bad_b.vh @@ -5,6 +5,6 @@ // SPDX-License-Identifier: CC0-1.0 module sub; - // verilator lint_off WIDTHTRUNC - int warn_sub = 64'h1; // Suppressed + // verilator lint_off WIDTHTRUNC + int warn_sub = 64'h1; // Suppressed endmodule diff --git a/test_regress/t/t_math_shift.v b/test_regress/t/t_math_shift.v index c78333832..bd8df1c16 100644 --- a/test_regress/t/t_math_shift.v +++ b/test_regress/t/t_math_shift.v @@ -5,248 +5,248 @@ // SPDX-License-Identifier: CC0-1.0 module t (/*AUTOARG*/ - // Outputs - ign, ign2, ign3, c_wright_32, c_wleft_32, ign4, ign4s, - // Inputs - clk - ); + // Outputs + ign, ign2, ign3, c_wright_32, c_wleft_32, ign4, ign4s, + // Inputs + clk + ); - input clk; - output [31:0] ign; - output [3:0] ign2; - output [11:0] ign3; + input clk; + output [31:0] ign; + output [3:0] ign2; + output [11:0] ign3; - parameter [95:0] P6 = 6; - localparam P64 = (1 << P6); + parameter [95:0] P6 = 6; + localparam P64 = (1 << P6); - // verilator lint_off WIDTH - localparam [4:0] PBIG23 = 1'b1 << ~73'b0; - localparam [3:0] PBIG29 = 4'b1 << 33'h100000000; - // verilator lint_on WIDTH + // verilator lint_off WIDTH + localparam [4:0] PBIG23 = 1'b1 << ~73'b0; + localparam [3:0] PBIG29 = 4'b1 << 33'h100000000; + // verilator lint_on WIDTH - reg [31:0] iright; - reg signed [31:0] irights; - reg [31:0] ileft; - reg [P64-1:0] qright; - reg signed [P64-1:0] qrights; - reg [P64-1:0] qleft; - reg [95:0] wright; - reg signed [95:0] wrights; - reg [95:0] wleft; + reg [31:0] iright; + reg signed [31:0] irights; + reg [31:0] ileft; + reg [P64-1:0] qright; + reg signed [P64-1:0] qrights; + reg [P64-1:0] qleft; + reg [95:0] wright; + reg signed [95:0] wrights; + reg [95:0] wleft; - reg [31:0] q_iright; - reg signed [31:0] q_irights; - reg [31:0] q_ileft; - reg [P64-1:0] q_qright; - reg signed [P64-1:0] q_qrights; - reg [P64-1:0] q_qleft; - reg [95:0] q_wright; - reg signed [95:0] q_wrights; - reg [95:0] q_wleft; + reg [31:0] q_iright; + reg signed [31:0] q_irights; + reg [31:0] q_ileft; + reg [P64-1:0] q_qright; + reg signed [P64-1:0] q_qrights; + reg [P64-1:0] q_qleft; + reg [95:0] q_wright; + reg signed [95:0] q_wrights; + reg [95:0] q_wleft; - reg [31:0] w_iright; - reg signed [31:0] w_irights; - reg [31:0] w_ileft; - reg [P64-1:0] w_qright; - reg signed [P64-1:0] w_qrights; - reg [P64-1:0] w_qleft; - reg [95:0] w_wright; - reg signed [95:0] w_wrights; - reg [95:0] w_wleft; + reg [31:0] w_iright; + reg signed [31:0] w_irights; + reg [31:0] w_ileft; + reg [P64-1:0] w_qright; + reg signed [P64-1:0] w_qrights; + reg [P64-1:0] w_qleft; + reg [95:0] w_wright; + reg signed [95:0] w_wrights; + reg [95:0] w_wleft; - reg [31:0] iamt; - reg [63:0] qamt; - reg [95:0] wamt; + reg [31:0] iamt; + reg [63:0] qamt; + reg [95:0] wamt; - output reg [95:0] c_wright_32; - output reg [95:0] c_wleft_32; + output reg [95:0] c_wright_32; + output reg [95:0] c_wleft_32; - reg [63:0] crc = 64'h5aef0c8d_d70a4497; - wire [95:0] rand_96 = {crc[63:32] | crc[31:0], crc}; + reg [63:0] crc = 64'h5aef0c8d_d70a4497; + wire [95:0] rand_96 = {crc[63:32] | crc[31:0], crc}; - assign ign = {31'h0, clk} >>> 4'bx; // bug760 - assign ign2 = {iamt[1:0] >> {22{iamt[5:2]}}, iamt[1:0] << (0 <<< iamt[5:2])}; // bug1174 - assign ign3 = {iamt[1:0] >> {22{iamt[5:2]}}, - iamt[1:0] >> {11{iamt[5:2]}}, - $signed(iamt[1:0]) >>> {22{iamt[5:2]}}, - $signed(iamt[1:0]) >>> {11{iamt[5:2]}}, - iamt[1:0] << {22{iamt[5:2]}}, - iamt[1:0] << {11{iamt[5:2]}}}; + assign ign = {31'h0, clk} >>> 4'bx; // bug760 + assign ign2 = {iamt[1:0] >> {22{iamt[5:2]}}, iamt[1:0] << (0 <<< iamt[5:2])}; // bug1174 + assign ign3 = {iamt[1:0] >> {22{iamt[5:2]}}, + iamt[1:0] >> {11{iamt[5:2]}}, + $signed(iamt[1:0]) >>> {22{iamt[5:2]}}, + $signed(iamt[1:0]) >>> {11{iamt[5:2]}}, + iamt[1:0] << {22{iamt[5:2]}}, + iamt[1:0] << {11{iamt[5:2]}}}; - wire [95:0] wamtt = {iamt,iamt,iamt}; - output wire [95:0] ign4; - assign ign4 = wamtt >> {11{iamt[5:2]}}; - output wire signed [95:0] ign4s; - assign ign4s = $signed(wamtt) >>> {11{iamt[5:2]}}; + wire [95:0] wamtt = {iamt,iamt,iamt}; + output wire [95:0] ign4; + assign ign4 = wamtt >> {11{iamt[5:2]}}; + output wire signed [95:0] ign4s; + assign ign4s = $signed(wamtt) >>> {11{iamt[5:2]}}; - always @* begin - iright = 32'h819b018a >> iamt; - irights = 32'sh819b018a >>> signed'(iamt); - ileft = 32'h819b018a << iamt; - qright = 64'hf784bf8f_12734089 >> iamt; - qrights = 64'shf784bf8f_12734089 >>> signed'(iamt); - qleft = 64'hf784bf8f_12734089 << iamt; - wright = 96'hf784bf8f_12734089_190abe48 >> iamt; - wrights = 96'shf784bf8f_12734089_190abe48 >>> signed'(iamt); - wleft = 96'hf784bf8f_12734089_190abe48 << iamt; + always @* begin + iright = 32'h819b018a >> iamt; + irights = 32'sh819b018a >>> signed'(iamt); + ileft = 32'h819b018a << iamt; + qright = 64'hf784bf8f_12734089 >> iamt; + qrights = 64'shf784bf8f_12734089 >>> signed'(iamt); + qleft = 64'hf784bf8f_12734089 << iamt; + wright = 96'hf784bf8f_12734089_190abe48 >> iamt; + wrights = 96'shf784bf8f_12734089_190abe48 >>> signed'(iamt); + wleft = 96'hf784bf8f_12734089_190abe48 << iamt; - q_iright = 32'h819b018a >> qamt; - q_irights = 32'sh819b018a >>> signed'(qamt); - q_ileft = 32'h819b018a << qamt; - q_qright = 64'hf784bf8f_12734089 >> qamt; - q_qrights = 64'shf784bf8f_12734089 >>> signed'(qamt); - q_qleft = 64'hf784bf8f_12734089 << qamt; - q_wright = 96'hf784bf8f_12734089_190abe48 >> qamt; - q_wrights = 96'shf784bf8f_12734089_190abe48 >>> signed'(qamt); - q_wleft = 96'hf784bf8f_12734089_190abe48 << qamt; + q_iright = 32'h819b018a >> qamt; + q_irights = 32'sh819b018a >>> signed'(qamt); + q_ileft = 32'h819b018a << qamt; + q_qright = 64'hf784bf8f_12734089 >> qamt; + q_qrights = 64'shf784bf8f_12734089 >>> signed'(qamt); + q_qleft = 64'hf784bf8f_12734089 << qamt; + q_wright = 96'hf784bf8f_12734089_190abe48 >> qamt; + q_wrights = 96'shf784bf8f_12734089_190abe48 >>> signed'(qamt); + q_wleft = 96'hf784bf8f_12734089_190abe48 << qamt; - w_iright = 32'h819b018a >> wamt; - w_irights = 32'sh819b018a >>> signed'(wamt); - w_ileft = 32'h819b018a << wamt; - w_qright = 64'hf784bf8f_12734089 >> wamt; - w_qrights = 64'shf784bf8f_12734089 >>> signed'(wamt); - w_qleft = 64'hf784bf8f_12734089 << wamt; - w_wright = 96'hf784bf8f_12734089_190abe48 >> wamt; - w_wrights = 96'shf784bf8f_12734089_190abe48 >>> signed'(wamt); - w_wleft = 96'hf784bf8f_12734089_190abe48 << wamt; + w_iright = 32'h819b018a >> wamt; + w_irights = 32'sh819b018a >>> signed'(wamt); + w_ileft = 32'h819b018a << wamt; + w_qright = 64'hf784bf8f_12734089 >> wamt; + w_qrights = 64'shf784bf8f_12734089 >>> signed'(wamt); + w_qleft = 64'hf784bf8f_12734089 << wamt; + w_wright = 96'hf784bf8f_12734089_190abe48 >> wamt; + w_wrights = 96'shf784bf8f_12734089_190abe48 >>> signed'(wamt); + w_wleft = 96'hf784bf8f_12734089_190abe48 << wamt; - c_wright_32 = rand_96 >> 32; - c_wleft_32 = rand_96 << 32; - end + c_wright_32 = rand_96 >> 32; + c_wleft_32 = rand_96 << 32; + end - integer cyc; initial cyc=1; - always @ (posedge clk) begin - if (cyc!=0) begin - cyc <= cyc + 1; - crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; + integer cyc; initial cyc=1; + always @ (posedge clk) begin + if (cyc!=0) begin + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; `ifdef TEST_VERBOSE - $write("%d %x %x %x %x %x %x\n", cyc, ileft, iright, qleft, qright, wleft, wright); + $write("%d %x %x %x %x %x %x\n", cyc, ileft, iright, qleft, qright, wleft, wright); `endif - if (cyc==1) begin - iamt <= 0; - qamt <= 0; - wamt <= 0; - if (P64 != 64) $stop; - if (5'b10110>>2 != 5'b00101) $stop; - if (5'b10110>>>2 != 5'b00101) $stop; // Note it cares about sign-ness - if (5'b10110<<2 != 5'b11000) $stop; - if (5'b10110<<<2 != 5'b11000) $stop; - if (5'sb10110>>2 != 5'sb00101) $stop; - if (5'sb10110>>>2 != 5'sb11101) $stop; - if (5'sb10110<<2 != 5'sb11000) $stop; - if (5'sb10110<<<2 != 5'sb11000) $stop; - // Allow >64 bit shifts if the shift amount is a constant - if ((64'sh458c2de282e30f8b >> 68'sh4) !== 64'sh0458c2de282e30f8) $stop; - end - if (cyc==2) begin - iamt <= 28; - qamt <= 28; - wamt <= 28; - if (ileft != 32'h819b018a) $stop; - if (iright != 32'h819b018a) $stop; - if (irights != 32'h819b018a) $stop; - if (qleft != 64'hf784bf8f_12734089) $stop; - if (qright != 64'hf784bf8f_12734089) $stop; - if (qrights != 64'hf784bf8f_12734089) $stop; - if (wleft != 96'hf784bf8f12734089190abe48) $stop; - if (wright != 96'hf784bf8f12734089190abe48) $stop; - if (wrights != 96'hf784bf8f12734089190abe48) $stop; - end - if (cyc==3) begin - iamt <= 31; - qamt <= 31; - wamt <= 31; - if (ileft != 32'ha0000000) $stop; - if (iright != 32'h8) $stop; - if (irights != 32'hfffffff8) $stop; - if (qleft != 64'hf127340890000000) $stop; - if (qright != 64'h0000000f784bf8f1) $stop; - if (qrights != 64'hffffffff784bf8f1) $stop; - if (wleft != 96'hf12734089190abe480000000) $stop; - if (wright != 96'h0000000f784bf8f127340891) $stop; - if (wrights != 96'hffffffff784bf8f127340891) $stop; - end - if (cyc==4) begin - iamt <= 32; - qamt <= 32; - wamt <= 32; - if (ileft != 32'h0) $stop; - if (iright != 32'h1) $stop; - if (qleft != 64'h8939a04480000000) $stop; - if (qright != 64'h00000001ef097f1e) $stop; - end - if (cyc==5) begin - iamt <= 33; - qamt <= 33; - wamt <= 33; - if (ileft != 32'h0) $stop; - if (iright != 32'h0) $stop; - if (qleft != 64'h1273408900000000) $stop; - if (qright != 64'h00000000f784bf8f) $stop; - end - if (cyc==6) begin - iamt <= 64; - qamt <= 64; - wamt <= 64; - if (ileft != 32'h0) $stop; - if (iright != 32'h0) $stop; - if (qleft != 64'h24e6811200000000) $stop; - if (qright != 64'h000000007bc25fc7) $stop; - end - if (cyc==7) begin - iamt <= 128; - qamt <= 128; - wamt <= 128; - if (ileft != 32'h0) $stop; - if (iright != 32'h0) $stop; - if (qleft != 64'h0) $stop; - if (qright != 64'h0) $stop; - end - if (cyc==8) begin - iamt <= 100; - qamt <= {32'h10, 32'h0}; - wamt <= {32'h10, 64'h0}; - if (ileft != '0) $stop; - if (iright != '0) $stop; - if (irights != '1) $stop; - if (qleft != '0) $stop; - if (qright != '0) $stop; - if (qrights != '1) $stop; - if (wleft != '0) $stop; - if (wright != '0) $stop; - if (wrights != '1) $stop; - end - if (cyc==19) begin - $write("*-* All Finished *-*\n"); - $finish; - end - - // General rule to test all q's - if (cyc != 0) begin - if (ileft != q_ileft) $stop; - if (iright != q_iright) $stop; - if (irights != q_irights) $stop; - if (qleft != q_qleft) $stop; - if (qright != q_qright) $stop; - if (qrights != q_qrights) $stop; - if (wleft != q_wleft) $stop; - if (wright != q_wright) $stop; - if (wrights != q_wrights) $stop; - - if (ileft != w_ileft) $stop; - if (iright != w_iright) $stop; - if (irights != w_irights) $stop; - if (qleft != w_qleft) $stop; - if (qright != w_qright) $stop; - if (qrights != w_qrights) $stop; - if (wleft != w_wleft) $stop; - if (wright != w_wright) $stop; - if (wrights != w_wrights) $stop; - - if (c_wright_32 << 32 != {rand_96[95:32], 32'd0}) $stop; - if (c_wleft_32 >> 32 != {32'd0, rand_96[63:0]}) $stop; - end + if (cyc==1) begin + iamt <= 0; + qamt <= 0; + wamt <= 0; + if (P64 != 64) $stop; + if (5'b10110>>2 != 5'b00101) $stop; + if (5'b10110>>>2 != 5'b00101) $stop; // Note it cares about sign-ness + if (5'b10110<<2 != 5'b11000) $stop; + if (5'b10110<<<2 != 5'b11000) $stop; + if (5'sb10110>>2 != 5'sb00101) $stop; + if (5'sb10110>>>2 != 5'sb11101) $stop; + if (5'sb10110<<2 != 5'sb11000) $stop; + if (5'sb10110<<<2 != 5'sb11000) $stop; + // Allow >64 bit shifts if the shift amount is a constant + if ((64'sh458c2de282e30f8b >> 68'sh4) !== 64'sh0458c2de282e30f8) $stop; end - end + if (cyc==2) begin + iamt <= 28; + qamt <= 28; + wamt <= 28; + if (ileft != 32'h819b018a) $stop; + if (iright != 32'h819b018a) $stop; + if (irights != 32'h819b018a) $stop; + if (qleft != 64'hf784bf8f_12734089) $stop; + if (qright != 64'hf784bf8f_12734089) $stop; + if (qrights != 64'hf784bf8f_12734089) $stop; + if (wleft != 96'hf784bf8f12734089190abe48) $stop; + if (wright != 96'hf784bf8f12734089190abe48) $stop; + if (wrights != 96'hf784bf8f12734089190abe48) $stop; + end + if (cyc==3) begin + iamt <= 31; + qamt <= 31; + wamt <= 31; + if (ileft != 32'ha0000000) $stop; + if (iright != 32'h8) $stop; + if (irights != 32'hfffffff8) $stop; + if (qleft != 64'hf127340890000000) $stop; + if (qright != 64'h0000000f784bf8f1) $stop; + if (qrights != 64'hffffffff784bf8f1) $stop; + if (wleft != 96'hf12734089190abe480000000) $stop; + if (wright != 96'h0000000f784bf8f127340891) $stop; + if (wrights != 96'hffffffff784bf8f127340891) $stop; + end + if (cyc==4) begin + iamt <= 32; + qamt <= 32; + wamt <= 32; + if (ileft != 32'h0) $stop; + if (iright != 32'h1) $stop; + if (qleft != 64'h8939a04480000000) $stop; + if (qright != 64'h00000001ef097f1e) $stop; + end + if (cyc==5) begin + iamt <= 33; + qamt <= 33; + wamt <= 33; + if (ileft != 32'h0) $stop; + if (iright != 32'h0) $stop; + if (qleft != 64'h1273408900000000) $stop; + if (qright != 64'h00000000f784bf8f) $stop; + end + if (cyc==6) begin + iamt <= 64; + qamt <= 64; + wamt <= 64; + if (ileft != 32'h0) $stop; + if (iright != 32'h0) $stop; + if (qleft != 64'h24e6811200000000) $stop; + if (qright != 64'h000000007bc25fc7) $stop; + end + if (cyc==7) begin + iamt <= 128; + qamt <= 128; + wamt <= 128; + if (ileft != 32'h0) $stop; + if (iright != 32'h0) $stop; + if (qleft != 64'h0) $stop; + if (qright != 64'h0) $stop; + end + if (cyc==8) begin + iamt <= 100; + qamt <= {32'h10, 32'h0}; + wamt <= {32'h10, 64'h0}; + if (ileft != '0) $stop; + if (iright != '0) $stop; + if (irights != '1) $stop; + if (qleft != '0) $stop; + if (qright != '0) $stop; + if (qrights != '1) $stop; + if (wleft != '0) $stop; + if (wright != '0) $stop; + if (wrights != '1) $stop; + end + if (cyc==19) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + // General rule to test all q's + if (cyc != 0) begin + if (ileft != q_ileft) $stop; + if (iright != q_iright) $stop; + if (irights != q_irights) $stop; + if (qleft != q_qleft) $stop; + if (qright != q_qright) $stop; + if (qrights != q_qrights) $stop; + if (wleft != q_wleft) $stop; + if (wright != q_wright) $stop; + if (wrights != q_wrights) $stop; + + if (ileft != w_ileft) $stop; + if (iright != w_iright) $stop; + if (irights != w_irights) $stop; + if (qleft != w_qleft) $stop; + if (qright != w_qright) $stop; + if (qrights != w_qrights) $stop; + if (wleft != w_wleft) $stop; + if (wright != w_wright) $stop; + if (wrights != w_wrights) $stop; + + if (c_wright_32 << 32 != {rand_96[95:32], 32'd0}) $stop; + if (c_wleft_32 >> 32 != {32'd0, rand_96[63:0]}) $stop; + end + end + end endmodule diff --git a/test_regress/t/t_mod_topmodule.v b/test_regress/t/t_mod_topmodule.v index e14726373..235011495 100644 --- a/test_regress/t/t_mod_topmodule.v +++ b/test_regress/t/t_mod_topmodule.v @@ -8,27 +8,24 @@ // SPDX-FileCopyrightText: 2021 Dan Petrisko // SPDX-License-Identifier: CC0-1.0 -module top(/*AUTOARG*/ - // Inputs - clk - ); - input clk; +module top ( + input clk +); - always_ff @(posedge clk) begin - $write("*-* All Finished *-*\n"); - $finish(); - end + always_ff @(posedge clk) begin + $write("*-* All Finished *-*\n"); + $finish(); + end endmodule -module faketop(/*AUTOARG*/ - ); +module faketop; - top top(); + top top (); - // Stop immediately if this module is instantiated + // Stop immediately if this module is instantiated initial begin $stop(); - end + end endmodule diff --git a/test_regress/t/t_notiming.out b/test_regress/t/t_notiming.out index 7d6b42ef8..9b27de641 100644 --- a/test_regress/t/t_notiming.out +++ b/test_regress/t/t_notiming.out @@ -51,4 +51,12 @@ : ... note: In instance 't' 38 | assert property (@(e) s_eventually 1'h1); | ^~~~~~~~~~~~ +%Error-NOTIMING: t/t_notiming.v:39:31: s_until requires --timing + : ... note: In instance 't' + 39 | assert property (@(e) 1'h1 s_until 1'h1); + | ^~~~~~~ +%Error-NOTIMING: t/t_notiming.v:40:31: s_until_with requires --timing + : ... note: In instance 't' + 40 | assert property (@(e) 1'h1 s_until_with 1'h1); + | ^~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_notiming.v b/test_regress/t/t_notiming.v index f91dd2c27..667d9b1d3 100644 --- a/test_regress/t/t_notiming.v +++ b/test_regress/t/t_notiming.v @@ -36,6 +36,8 @@ module t; s.get(); end assert property (@(e) s_eventually 1'h1); + assert property (@(e) 1'h1 s_until 1'h1); + assert property (@(e) 1'h1 s_until_with 1'h1); endmodule `ifdef VERILATOR_TIMING diff --git a/test_regress/t/t_preproc_inc_inc_bad.vh b/test_regress/t/t_preproc_inc_inc_bad.vh index e854c817a..0cd401d5e 100644 --- a/test_regress/t/t_preproc_inc_inc_bad.vh +++ b/test_regress/t/t_preproc_inc_inc_bad.vh @@ -6,6 +6,6 @@ module xx; - xx // intentional error + xx // intentional error endmodule diff --git a/test_regress/t/t_property_case.py b/test_regress/t/t_property_case.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_property_case.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_property_case.v b/test_regress/t/t_property_case.v new file mode 100644 index 000000000..62f875570 --- /dev/null +++ b/test_regress/t/t_property_case.v @@ -0,0 +1,90 @@ +// 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 +); + + int cyc = 0; + logic [1:0] sel; + logic a; + logic b; + + int linear_f = 0; + int default_f = 0; + int vacuous_f = 0; + int delay_f = 0; + + always_comb begin + sel = cyc[1:0]; + a = 1'b1; + b = 1'b1; + end + + property p_delay(logic [1:0] delay); + case (delay) + 2'd0 : a && b; + 2'd1 : a ##2 b; + 2'd2 : a ##4 b; + 2'd3 : a ##8 b; + default: 0; + endcase + endproperty + + property p_linear_search; + case (sel) + 2'd0 : 1'b0; + 2'd0, 2'd1 : 1'b1; + default 1'b1; + endcase + endproperty + + property p_default_ignored_during_search; + case (sel) + default: 1'b0; + 2'd2 : 1'b1; + endcase + endproperty + + property p_no_default_vacuous_success; + case (sel) + 2'd0 : 1'b0; + endcase + endproperty + + property p_only_default; + case (sel) + default: 1'b1; + endcase + endproperty + + assert property (@(posedge clk) p_delay(sel)) else delay_f++; + assert property (@(posedge clk) p_linear_search) else linear_f++; + assert property (@(posedge clk) p_default_ignored_during_search) else default_f++; + assert property (@(posedge clk) p_no_default_vacuous_success) else vacuous_f++; + assert property (@(posedge clk) p_only_default) else `stop; + + always @(posedge clk) begin + cyc <= cyc + 1; + end + + always @(negedge clk) begin + if (cyc == 16) begin + `checkd(delay_f, 0); + `checkd(linear_f, 4); + `checkd(default_f, 12); + `checkd(vacuous_f, 4); + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_property_case_bad.out b/test_regress/t/t_property_case_bad.out new file mode 100644 index 000000000..c2c064b26 --- /dev/null +++ b/test_regress/t/t_property_case_bad.out @@ -0,0 +1,8 @@ +%Error: t/t_property_case_bad.v:13:7: Multiple default statements in property case statement + 13 | default: 1'b0; + | ^~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_property_case_bad.v:18:5: Property case statement with no items + 18 | case (sel) + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_property_case_bad.py b/test_regress/t/t_property_case_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_property_case_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_property_case_bad.v b/test_regress/t/t_property_case_bad.v new file mode 100644 index 000000000..a66bf805b --- /dev/null +++ b/test_regress/t/t_property_case_bad.v @@ -0,0 +1,21 @@ +// 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 [1:0] sel; + + property p1; + case (sel) + default: 1'b1; + default: 1'b0; + endcase + endproperty + + property p2; + case (sel) + endcase + endproperty +endmodule diff --git a/test_regress/t/t_property_pexpr_parse_unsup.out b/test_regress/t/t_property_pexpr_parse_unsup.out index 8d3609b07..6a23bc18e 100644 --- a/test_regress/t/t_property_pexpr_parse_unsup.out +++ b/test_regress/t/t_property_pexpr_parse_unsup.out @@ -1,50 +1,44 @@ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:26:13: Unsupported: strong (in property expression) - 26 | strong(a); - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:30:11: Unsupported: weak (in property expression) - 30 | weak(a); +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:24:11: Unsupported: strong (in property expression) + 24 | strong(a); | ^ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:58:7: Unsupported: nexttime (in property expression) - 58 | nexttime a; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:62:7: Unsupported: nexttime[] (in property expression) - 62 | nexttime [2] a; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:66:7: Unsupported: s_nexttime (in property expression) - 66 | s_nexttime a; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:70:7: Unsupported: s_nexttime[] (in property expression) - 70 | s_nexttime [2] a; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:74:7: Unsupported: nexttime (in property expression) - 74 | nexttime always a; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:78:7: Unsupported: nexttime[] (in property expression) - 78 | nexttime [2] always a; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:82:7: Unsupported: nexttime[] (in property expression) - 82 | nexttime [2] always a; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:86:7: Unsupported: nexttime (in property expression) - 86 | nexttime s_eventually a; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:90:16: Unsupported: s_eventually[] (in property expression) - 90 | nexttime s_eventually [2:$] always a; - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:90:7: Unsupported: nexttime (in property expression) - 90 | nexttime s_eventually [2:$] always a; - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:109:27: Unsupported: property argument data type - 109 | property p_arg_propery(property inprop); - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:112:27: Unsupported: sequence argument data type - 112 | property p_arg_seqence(sequence inseq); - | ^~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:117:7: Unsupported: property case expression - 117 | case (a) endcase - | ^~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:120:7: Unsupported: property case expression - 120 | case (a) default: b; endcase - | ^~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:28:9: Unsupported: weak (in property expression) + 28 | weak(a); + | ^ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:56:5: Unsupported: nexttime (in property expression) + 56 | nexttime a; + | ^~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:60:5: Unsupported: nexttime[] (in property expression) + 60 | nexttime [2] a; + | ^~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:64:5: Unsupported: s_nexttime (in property expression) + 64 | s_nexttime a; + | ^~~~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:68:5: Unsupported: s_nexttime[] (in property expression) + 68 | s_nexttime [2] a; + | ^~~~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:72:5: Unsupported: nexttime (in property expression) + 72 | nexttime always a; + | ^~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:76:5: Unsupported: nexttime[] (in property expression) + 76 | nexttime [2] always a; + | ^~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:80:5: Unsupported: nexttime[] (in property expression) + 80 | nexttime [2] always a; + | ^~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:84:5: Unsupported: nexttime (in property expression) + 84 | nexttime s_eventually a; + | ^~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:88:14: Unsupported: s_eventually[] (in property expression) + 88 | nexttime s_eventually [2:$] always a; + | ^~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:88:5: Unsupported: nexttime (in property expression) + 88 | nexttime s_eventually [2:$] always a; + | ^~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:107:26: Unsupported: property argument data type + 107 | property p_arg_propery(property inprop); + | ^~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:110:26: Unsupported: sequence argument data type + 110 | property p_arg_seqence(sequence inseq); + | ^~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_property_pexpr_unsup.out b/test_regress/t/t_property_pexpr_unsup.out index dc3914b8f..bae43d802 100644 --- a/test_regress/t/t_property_pexpr_unsup.out +++ b/test_regress/t/t_property_pexpr_unsup.out @@ -1,26 +1,26 @@ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:130:22: Unsupported: Unclocked assertion +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:115:21: Unsupported: Unclocked assertion : ... note: In instance 't' - 130 | assert property ((s_eventually a) implies (s_eventually a)); - | ^~~~~~~~~~~~ + 115 | assert property ((s_eventually a) implies (s_eventually a)); + | ^~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:130:47: Unsupported: Unclocked assertion +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:115:46: Unsupported: Unclocked assertion : ... note: In instance 't' - 130 | assert property ((s_eventually a) implies (s_eventually a)); - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:130:4: Unsupported: Unclocked assertion + 115 | assert property ((s_eventually a) implies (s_eventually a)); + | ^~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:115:3: Unsupported: Unclocked assertion : ... note: In instance 't' - 130 | assert property ((s_eventually a) implies (s_eventually a)); - | ^~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:132:22: Unsupported: Unclocked assertion + 115 | assert property ((s_eventually a) implies (s_eventually a)); + | ^~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:117:21: Unsupported: Unclocked assertion : ... note: In instance 't' - 132 | assert property ((s_eventually a) iff (s_eventually a)); - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:132:43: Unsupported: Unclocked assertion + 117 | assert property ((s_eventually a) iff (s_eventually a)); + | ^~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:117:42: Unsupported: Unclocked assertion : ... note: In instance 't' - 132 | assert property ((s_eventually a) iff (s_eventually a)); - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:132:4: Unsupported: Unclocked assertion + 117 | assert property ((s_eventually a) iff (s_eventually a)); + | ^~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_property_pexpr_unsup.v:117:3: Unsupported: Unclocked assertion : ... note: In instance 't' - 132 | assert property ((s_eventually a) iff (s_eventually a)); - | ^~~~~~ + 117 | assert property ((s_eventually a) iff (s_eventually a)); + | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_property_pexpr_unsup.v b/test_regress/t/t_property_pexpr_unsup.v index 8b9bb9ade..10798685e 100644 --- a/test_regress/t/t_property_pexpr_unsup.v +++ b/test_regress/t/t_property_pexpr_unsup.v @@ -4,137 +4,122 @@ // SPDX-FileCopyrightText: 2026 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 -module t (/*AUTOARG*/ - // Inputs - clk - ); +module t ( + input clk +); - input clk; - bit a; - bit b; - bit c; - int cyc = 0; + bit a; + bit b; + bit c; + int cyc = 0; - always @(posedge clk) begin - cyc <= cyc + 1; - end + always @(posedge clk) begin + cyc <= cyc + 1; + end `ifdef PARSING_TIME - // NOTE this grammar hasn't been checked with other simulators, - // is here just to avoid uncovered code lines in the grammar. - property p_strong; - strong(a); - endproperty + // NOTE this grammar hasn't been checked with other simulators, + // is here just to avoid uncovered code lines in the grammar. + property p_strong; + strong(a); + endproperty - property p_weak; - weak(a); - endproperty + property p_weak; + weak(a); + endproperty - property p_until; - a until b; - endproperty + property p_until; + a until b; + endproperty - property p_suntil; - a s_until b; - endproperty + property p_suntil; + a s_until b; + endproperty - property p_untilwith; - a until_with b; - endproperty + property p_untilwith; + a until_with b; + endproperty - property p_suntilwith; - a s_until_with b; - endproperty + property p_suntilwith; + a s_until_with b; + endproperty - property p_poundminuspound1; - a #-# b; - endproperty + property p_poundminuspound1; + a #-# b; + endproperty - property p_poundeqpound; - a #=# b; - endproperty + property p_poundeqpound; + a #=# b; + endproperty - property p_nexttime; - nexttime a; - endproperty + property p_nexttime; + nexttime a; + endproperty - property p_nexttime2; - nexttime [2] a; - endproperty + property p_nexttime2; + nexttime [2] a; + endproperty - property p_snexttime; - s_nexttime a; - endproperty + property p_snexttime; + s_nexttime a; + endproperty - property p_snexttime2; - s_nexttime [2] a; - endproperty + property p_snexttime2; + s_nexttime [2] a; + endproperty - property p_nexttime_always; - nexttime always a; - endproperty + property p_nexttime_always; + nexttime always a; + endproperty - property p_nexttime_always2; - nexttime [2] always a; - endproperty + property p_nexttime_always2; + nexttime [2] always a; + endproperty - property p_nexttime_eventually2; - nexttime [2] always a; - endproperty + property p_nexttime_eventually2; + nexttime [2] always a; + endproperty - property p_nexttime_seventually; - nexttime s_eventually a; - endproperty + property p_nexttime_seventually; + nexttime s_eventually a; + endproperty - property p_nexttime_seventually2; - nexttime s_eventually [2:$] always a; - endproperty + property p_nexttime_seventually2; + nexttime s_eventually [2:$] always a; + endproperty - property p_accepton; - accept_on (a) b; - endproperty + property p_accepton; + accept_on (a) b; + endproperty - property p_syncaccepton; - sync_accept_on (a) b; - endproperty + property p_syncaccepton; + sync_accept_on (a) b; + endproperty - property p_rejecton; - reject_on (a) b; - endproperty + property p_rejecton; + reject_on (a) b; + endproperty - property p_syncrejecton; - sync_reject_on (a) b; - endproperty + property p_syncrejecton; + sync_reject_on (a) b; + endproperty - property p_arg_propery(property inprop); - inprop; - endproperty - property p_arg_seqence(sequence inseq); - inseq; - endproperty - - property p_case_1; - case (a) endcase - endproperty - property p_case_2; - case (a) default: b; endcase - endproperty - property p_if; - if (a) b - endproperty - property p_ifelse; - if (a) b else c - endproperty + property p_arg_propery(property inprop); + inprop; + endproperty + property p_arg_seqence(sequence inseq); + inseq; + endproperty `endif - assert property ((s_eventually a) implies (s_eventually a)); + assert property ((s_eventually a) implies (s_eventually a)); - assert property ((s_eventually a) iff (s_eventually a)); + assert property ((s_eventually a) iff (s_eventually a)); - always @(posedge clk) begin - if (cyc == 10) begin - $write("*-* All Finished *-*\n"); - $finish; - end - end + always @(posedge clk) begin + if (cyc == 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end endmodule diff --git a/test_regress/t/t_property_s_eventually_iface.py b/test_regress/t/t_property_s_eventually_iface.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface.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.v b/test_regress/t/t_property_s_eventually_iface.v new file mode 100644 index 000000000..fa79e43cd --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface.v @@ -0,0 +1,35 @@ +// 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 + +// s_eventually (strong eventually) inside an interface used to trigger an +// internal error in V3Scope ("Can't locate varref scope"). + +interface my_if ( + input logic clk +); + logic a; + assert property (@(posedge clk) s_eventually a); +endinterface + +module t ( /*AUTOARG*/); + bit clk = 0; + initial forever #1 clk = ~clk; + + integer cyc = 0; + + my_if u_if (.clk(clk)); + + initial u_if.a = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + u_if.a <= (cyc >= 3); + if (cyc == 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_property_sexpr_parse_unsup.out b/test_regress/t/t_property_sexpr_parse_unsup.out index f84b96594..4c8e5bba1 100644 --- a/test_regress/t/t_property_sexpr_parse_unsup.out +++ b/test_regress/t/t_property_sexpr_parse_unsup.out @@ -1,16 +1,16 @@ -%Warning-WIDTHTRUNC: t/t_property_sexpr_unsup.v:72:14: Logical operator IMPLICATION expects 1 bit on the RHS, but RHS's CMETHODHARD 'at' generates 8 bits. +%Warning-WIDTHTRUNC: t/t_property_sexpr_unsup.v:80:14: Logical operator IMPLICATION expects 1 bit on the RHS, but RHS's CMETHODHARD 'at' generates 8 bits. : ... note: In instance 't.ieee' - 72 | $rose(a) |-> q[0]; + 80 | $rose(a) |-> q[0]; | ^~~ ... For warning description see https://verilator.org/warn/WIDTHTRUNC?v=latest ... Use "/* verilator lint_off WIDTHTRUNC */" and lint_on around source to disable this message. -%Warning-WIDTHTRUNC: t/t_property_sexpr_unsup.v:77:29: Logical operator SEXPR expects 1 bit on the exprp, but exprp's CMETHODHARD 'at' generates 8 bits. +%Warning-WIDTHTRUNC: t/t_property_sexpr_unsup.v:85:29: Logical operator SEXPR expects 1 bit on the exprp, but exprp's CMETHODHARD 'at' generates 8 bits. : ... note: In instance 't.ieee' - 77 | ($rose(a), l_b = b) |-> ##[3:10] q[l_b]; + 85 | ($rose(a), l_b = b) |-> ##[3:10] q[l_b]; | ^~ -%Warning-WIDTHTRUNC: t/t_property_sexpr_unsup.v:98:31: Logical operator SEXPR expects 1 bit on the exprp, but exprp's VARREF 'b' generates 32 bits. - : ... note: In instance 't.ieee' - 98 | assert property (@clk not a ##1 b); +%Warning-WIDTHTRUNC: t/t_property_sexpr_unsup.v:106:31: Logical operator SEXPR expects 1 bit on the exprp, but exprp's VARREF 'b' generates 32 bits. + : ... note: In instance 't.ieee' + 106 | assert property (@clk not a ##1 b); | ^~ %Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:47:39: Unsupported: 'until' in complex property expression : ... note: In instance 't' @@ -21,16 +21,32 @@ : ... note: In instance 't' 49 | assert property (@(posedge clk) val until (val ##1 val)) $display("[%0t] sequence in until, fileline:%d", $time, 49); | ^~~~~ -%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:51:39: Unsupported: s_until (in property expresion) +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:51:44: Unsupported: s_until (in property expression) : ... note: In instance 't' - 51 | assert property (@(posedge clk) val s_until !val) $display("[%0t] s_until, fileline:%d", $time, 51); + 51 | assert property (@(posedge clk) ##1 (val s_until val)) $display("[%0t] s_until in sequence, fileline:%d", $time, 51); + | ^~~~~~~ +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:53:39: Unsupported: s_until (in property expression) + : ... note: In instance 't' + 53 | assert property (@(posedge clk) val s_until val s_until val) $display("[%0t] nested s_until, fileline:%d", $time, 53); | ^~~~~~~ -%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:53:39: Unsupported: s_until_with (in property expresion) +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:55:39: Unsupported: s_until (in property expression) : ... note: In instance 't' - 53 | assert property (@(posedge clk) val s_until_with val) $display("[%0t] s_until_with, fileline:%d", $time, 53); + 55 | assert property (@(posedge clk) val s_until (val ##1 val)) $display("[%0t] sequence in until_with, fileline:%d", $time, 55); + | ^~~~~~~ +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:57:44: Unsupported: s_until_with (in property expression) + : ... note: In instance 't' + 57 | assert property (@(posedge clk) ##1 (val s_until_with val)) $display("[%0t] s_until_with in sequence, fileline:%d", $time, 57); + | ^~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:59:39: Unsupported: s_until_with (in property expression) + : ... note: In instance 't' + 59 | assert property (@(posedge clk) val s_until_with val s_until_with val) $display("[%0t] nested s_until_with, fileline:%d", $time, 59); | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:55:40: Unsupported: 'until' in complex property expression +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:61:39: Unsupported: s_until_with (in property expression) : ... note: In instance 't' - 55 | assert property (@(posedge clk) (val until val) or val) $display("[%0t] until inside or, fileline:%d", $time, 55); + 61 | assert property (@(posedge clk) val s_until_with (val ##1 val)) $display("[%0t] sequence in until_with, fileline:%d", $time, 61); + | ^~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:63:40: Unsupported: 'until' in complex property expression + : ... note: In instance 't' + 63 | assert property (@(posedge clk) (val until val) or val) $display("[%0t] until inside or, fileline:%d", $time, 63); | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_property_sexpr_unsup.out b/test_regress/t/t_property_sexpr_unsup.out index a13d0fd31..078d116e2 100644 --- a/test_regress/t/t_property_sexpr_unsup.out +++ b/test_regress/t/t_property_sexpr_unsup.out @@ -7,16 +7,32 @@ : ... note: In instance 't' 49 | assert property (@(posedge clk) val until (val ##1 val)) $display("[%0t] sequence in until, fileline:%d", $time, 49); | ^~~~~ -%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:51:39: Unsupported: s_until (in property expresion) +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:51:44: Unsupported: s_until (in property expression) : ... note: In instance 't' - 51 | assert property (@(posedge clk) val s_until !val) $display("[%0t] s_until, fileline:%d", $time, 51); + 51 | assert property (@(posedge clk) ##1 (val s_until val)) $display("[%0t] s_until in sequence, fileline:%d", $time, 51); + | ^~~~~~~ +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:53:39: Unsupported: s_until (in property expression) + : ... note: In instance 't' + 53 | assert property (@(posedge clk) val s_until val s_until val) $display("[%0t] nested s_until, fileline:%d", $time, 53); | ^~~~~~~ -%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:53:39: Unsupported: s_until_with (in property expresion) +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:55:39: Unsupported: s_until (in property expression) : ... note: In instance 't' - 53 | assert property (@(posedge clk) val s_until_with val) $display("[%0t] s_until_with, fileline:%d", $time, 53); + 55 | assert property (@(posedge clk) val s_until (val ##1 val)) $display("[%0t] sequence in until_with, fileline:%d", $time, 55); + | ^~~~~~~ +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:57:44: Unsupported: s_until_with (in property expression) + : ... note: In instance 't' + 57 | assert property (@(posedge clk) ##1 (val s_until_with val)) $display("[%0t] s_until_with in sequence, fileline:%d", $time, 57); + | ^~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:59:39: Unsupported: s_until_with (in property expression) + : ... note: In instance 't' + 59 | assert property (@(posedge clk) val s_until_with val s_until_with val) $display("[%0t] nested s_until_with, fileline:%d", $time, 59); | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:55:40: Unsupported: 'until' in complex property expression +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:61:39: Unsupported: s_until_with (in property expression) : ... note: In instance 't' - 55 | assert property (@(posedge clk) (val until val) or val) $display("[%0t] until inside or, fileline:%d", $time, 55); + 61 | assert property (@(posedge clk) val s_until_with (val ##1 val)) $display("[%0t] sequence in until_with, fileline:%d", $time, 61); + | ^~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_property_sexpr_unsup.v:63:40: Unsupported: 'until' in complex property expression + : ... note: In instance 't' + 63 | assert property (@(posedge clk) (val until val) or val) $display("[%0t] until inside or, fileline:%d", $time, 63); | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_property_sexpr_unsup.v b/test_regress/t/t_property_sexpr_unsup.v index d6a5b4aca..9fedd15b8 100644 --- a/test_regress/t/t_property_sexpr_unsup.v +++ b/test_regress/t/t_property_sexpr_unsup.v @@ -48,9 +48,17 @@ module t ( /*AUTOARG*/ assert property (@(posedge clk) val until (val ##1 val)) $display("[%0t] sequence in until, fileline:%d", $time, `__LINE__); - assert property (@(posedge clk) val s_until !val) $display("[%0t] s_until, fileline:%d", $time, `__LINE__); + assert property (@(posedge clk) ##1 (val s_until val)) $display("[%0t] s_until in sequence, fileline:%d", $time, `__LINE__); - assert property (@(posedge clk) val s_until_with val) $display("[%0t] s_until_with, fileline:%d", $time, `__LINE__); + assert property (@(posedge clk) val s_until val s_until val) $display("[%0t] nested s_until, fileline:%d", $time, `__LINE__); + + assert property (@(posedge clk) val s_until (val ##1 val)) $display("[%0t] sequence in until_with, fileline:%d", $time, `__LINE__); + + assert property (@(posedge clk) ##1 (val s_until_with val)) $display("[%0t] s_until_with in sequence, fileline:%d", $time, `__LINE__); + + assert property (@(posedge clk) val s_until_with val s_until_with val) $display("[%0t] nested s_until_with, fileline:%d", $time, `__LINE__); + + assert property (@(posedge clk) val s_until_with (val ##1 val)) $display("[%0t] sequence in until_with, fileline:%d", $time, `__LINE__); assert property (@(posedge clk) (val until val) or val) $display("[%0t] until inside or, fileline:%d", $time, `__LINE__); diff --git a/test_regress/t/t_property_until.v b/test_regress/t/t_property_until.v index 137c9417c..1223cb37c 100644 --- a/test_regress/t/t_property_until.v +++ b/test_regress/t/t_property_until.v @@ -46,6 +46,40 @@ module t ( /*AUTOARG*/ results[5].passs++; else results[5].fails++; + assert property (@(posedge clk) 0 s_until 1) + results[6].passs++; + else results[6].fails++; + + assert property (@(posedge clk) cyc < 5 s_until cyc >= 5) + results[7].passs++; + else results[7].fails++; + + assert property (@(posedge clk) cyc % 3 == 0 s_until cyc % 5 == 0) + results[8].passs++; + else results[8].fails++; + + // Check that s_until accepts immediately when RHS is true, even if LHS is false. + assert property (@(posedge clk) cyc % 2 == 0 s_until 1) + results[9].passs++; + else results[9].fails++; + + // Check that s_until_with requires LHS when RHS is true on the same tick. + assert property (@(posedge clk) 0 s_until_with 1) + results[10].passs++; + else results[10].fails++; + + assert property (@(posedge clk) 1 s_until_with cyc >= 5) + results[11].passs++; + else results[11].fails++; + + assert property (@(posedge clk) cyc <= 5 s_until_with cyc >= 5) + results[12].passs++; + else results[12].fails++; + + assert property (@(posedge clk) cyc < 5 s_until_with cyc >= 5) + results[13].passs++; + else results[13].fails++; + always @(edge clk) begin ++cyc; if (cyc == MAX) begin @@ -54,6 +88,14 @@ module t ( /*AUTOARG*/ expected[3] = '{0, 7}; expected[4] = '{5, 2}; expected[5] = '{2, 5}; + expected[6] = '{0, 7}; + expected[7] = '{0, 7}; + expected[8] = '{5, 2}; + expected[9] = '{0, 7}; + expected[10] = '{7, 0}; + expected[11] = '{0, 7}; + expected[12] = '{4, 3}; + expected[13] = '{7, 0}; `checkh(results, expected); $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_structu_dataType_assignment.v b/test_regress/t/t_structu_dataType_assignment.v index cae4c5c35..667c5f70c 100644 --- a/test_regress/t/t_structu_dataType_assignment.v +++ b/test_regress/t/t_structu_dataType_assignment.v @@ -66,122 +66,122 @@ module top(); HIJ_struct HIJ; initial begin - // struct ab - ab = '{0, 0}; //constant member by position - if (ab.a != 0) $stop; - if (ab.b != 0) $stop; + // struct ab + ab = '{0, 0}; //constant member by position + if (ab.a != 0) $stop; + if (ab.b != 0) $stop; - ab = '{default: 0}; //default value - if (ab.a != 0) $stop; - if (ab.b != 0) $stop; + ab = '{default: 0}; //default value + if (ab.a != 0) $stop; + if (ab.b != 0) $stop; - ab = '{int: 1, shortint: 0}; //data type and default value - if (ab.a != 1) $stop; - if (ab.b != 0) $stop; + ab = '{int: 1, shortint: 0}; //data type and default value + if (ab.a != 1) $stop; + if (ab.b != 0) $stop; - abkey[1:0] = '{'{a:1, b:2}, '{int:2, shortint:3}}; // member: value & data_type: value - if (abkey[1].a != 1) $stop; - if (abkey[1].b != 2) $stop; - if (abkey[0].a != 2) $stop; - if (abkey[0].b != 3) $stop; + abkey[1:0] = '{'{a:1, b:2}, '{int:2, shortint:3}}; // member: value & data_type: value + if (abkey[1].a != 1) $stop; + if (abkey[1].b != 2) $stop; + if (abkey[0].a != 2) $stop; + if (abkey[0].b != 3) $stop; - // struct st - st = '{1, 2+k}; //constant member by position - if (st.x != 1) $stop; - if (st.y != 2+k) $stop; + // struct st + st = '{1, 2+k}; //constant member by position + if (st.x != 1) $stop; + if (st.y != 2+k) $stop; - st = '{x:2, y:3+k}; //member: value - if (st.x != 2) $stop; - if (st.y != 3+k) $stop; + st = '{x:2, y:3+k}; //member: value + if (st.x != 2) $stop; + if (st.y != 3+k) $stop; - st = '{int:2, int:3+k}; //data_type: value override - if (st.x != 3+k) $stop; - if (st.y != 3+k) $stop; + st = '{int:2, int:3+k}; //data_type: value override + if (st.x != 3+k) $stop; + if (st.y != 3+k) $stop; - // struct sa - sa = '{default:'1}; - if (sa.a != '1) $stop; - if (sa.b != '1) $stop; - if (sa.c != '1) $stop; - if (sa.s != '1) $stop; + // struct sa + sa = '{default:'1}; + if (sa.a != '1) $stop; + if (sa.b != '1) $stop; + if (sa.c != '1) $stop; + if (sa.s != '1) $stop; - sa = '{default:'1, int: 5}; - if (sa.a != '1) $stop; - if (sa.b != '1) $stop; - if (sa.c != '1) $stop; - if (sa.s != 5) $stop; + sa = '{default:'1, int: 5}; + if (sa.a != '1) $stop; + if (sa.b != '1) $stop; + if (sa.c != '1) $stop; + if (sa.s != 5) $stop; - sa = '{default:'1, int: 5, b: 0}; - if (sa.a != '1) $stop; - if (sa.b != 0) $stop; - if (sa.c != '1) $stop; - if (sa.s != 5) $stop; + sa = '{default:'1, int: 5, b: 0}; + if (sa.a != '1) $stop; + if (sa.b != 0) $stop; + if (sa.c != '1) $stop; + if (sa.s != 5) $stop; - // struct DEF - DEF = '{A:1, BC1:'{B:2, C:3}, BC2:'{B:4,C:5}}; - if (DEF.A != 1) $stop; - if (DEF.BC1.B != 2) $stop; - if (DEF.BC1.C != 3) $stop; - if (DEF.BC2.B != 4) $stop; - if (DEF.BC2.C != 5) $stop; + // struct DEF + DEF = '{A:1, BC1:'{B:2, C:3}, BC2:'{B:4,C:5}}; + if (DEF.A != 1) $stop; + if (DEF.BC1.B != 2) $stop; + if (DEF.BC1.C != 3) $stop; + if (DEF.BC2.B != 4) $stop; + if (DEF.BC2.C != 5) $stop; - DEF = '{int:0, BC1:'{int:10}, BC2:'{default:5}}; - if (DEF.A != 0) $stop; - if (DEF.BC1.B != 10) $stop; - if (DEF.BC1.C != 10) $stop; - if (DEF.BC2.B != 5) $stop; - if (DEF.BC2.C != 5) $stop; + DEF = '{int:0, BC1:'{int:10}, BC2:'{default:5}}; + if (DEF.A != 0) $stop; + if (DEF.BC1.B != 10) $stop; + if (DEF.BC1.C != 10) $stop; + if (DEF.BC2.B != 5) $stop; + if (DEF.BC2.C != 5) $stop; - DEF = '{default:1, BC1:'{int:10}, BC2:'{default:5}}; - if (DEF.A != 1) $stop; - if (DEF.BC1.B != 10) $stop; - if (DEF.BC1.C != 10) $stop; - if (DEF.BC2.B != 5) $stop; - if (DEF.BC2.C != 5) $stop; + DEF = '{default:1, BC1:'{int:10}, BC2:'{default:5}}; + if (DEF.A != 1) $stop; + if (DEF.BC1.B != 10) $stop; + if (DEF.BC1.C != 10) $stop; + if (DEF.BC2.B != 5) $stop; + if (DEF.BC2.C != 5) $stop; - DEF = '{default:10}; - if (DEF.A != 10) $stop; - if (DEF.BC1.B != 10) $stop; - if (DEF.BC1.C != 10) $stop; - if (DEF.BC2.B != 10) $stop; - if (DEF.BC2.C != 10) $stop; + DEF = '{default:10}; + if (DEF.A != 10) $stop; + if (DEF.BC1.B != 10) $stop; + if (DEF.BC1.C != 10) $stop; + if (DEF.BC2.B != 10) $stop; + if (DEF.BC2.C != 10) $stop; - DEF = '{int:10}; - if (DEF.A != 10) $stop; - if (DEF.BC1.B != 10) $stop; - if (DEF.BC1.C != 10) $stop; - if (DEF.BC2.B != 10) $stop; - if (DEF.BC2.C != 10) $stop; + DEF = '{int:10}; + if (DEF.A != 10) $stop; + if (DEF.BC1.B != 10) $stop; + if (DEF.BC1.C != 10) $stop; + if (DEF.BC2.B != 10) $stop; + if (DEF.BC2.C != 10) $stop; - // struct HIJ - HIJ = '{int:10, default: 5}; - if (HIJ.A != 10) $stop; - if (HIJ.BC1.B != 10) $stop; - if (HIJ.BC1.C != 10) $stop; - if (HIJ.BC1.DE1.D != 10) $stop; - if (HIJ.BC1.DE1.E != 10) $stop; - if (HIJ.BC1.DE1.FG1.F != 10) $stop; - if (HIJ.BC1.DE1.FG1.G != 5) $stop; + // struct HIJ + HIJ = '{int:10, default: 5}; + if (HIJ.A != 10) $stop; + if (HIJ.BC1.B != 10) $stop; + if (HIJ.BC1.C != 10) $stop; + if (HIJ.BC1.DE1.D != 10) $stop; + if (HIJ.BC1.DE1.E != 10) $stop; + if (HIJ.BC1.DE1.FG1.F != 10) $stop; + if (HIJ.BC1.DE1.FG1.G != 5) $stop; - HIJ = '{shortint:10, default: 5}; - if (HIJ.A != 5) $stop; - if (HIJ.BC1.B != 5) $stop; - if (HIJ.BC1.C != 5) $stop; - if (HIJ.BC1.DE1.D != 5) $stop; - if (HIJ.BC1.DE1.E != 5) $stop; - if (HIJ.BC1.DE1.FG1.F != 5) $stop; - if (HIJ.BC1.DE1.FG1.G != 10) $stop; + HIJ = '{shortint:10, default: 5}; + if (HIJ.A != 5) $stop; + if (HIJ.BC1.B != 5) $stop; + if (HIJ.BC1.C != 5) $stop; + if (HIJ.BC1.DE1.D != 5) $stop; + if (HIJ.BC1.DE1.E != 5) $stop; + if (HIJ.BC1.DE1.FG1.F != 5) $stop; + if (HIJ.BC1.DE1.FG1.G != 10) $stop; - $write("*-* All Finished *-*\n"); - $finish; + $write("*-* All Finished *-*\n"); + $finish; end endmodule diff --git a/test_regress/t/t_timing_unset1.out b/test_regress/t/t_timing_unset1.out index 79ef98aee..cb0cc5420 100644 --- a/test_regress/t/t_timing_unset1.out +++ b/test_regress/t/t_timing_unset1.out @@ -52,4 +52,12 @@ 38 | assert property (@(e) s_eventually 1'h1); | ^~~~~~~~~~~~ ... For error description see https://verilator.org/warn/NOTIMING?v=latest +%Error-NOTIMING: t/t_notiming.v:39:31: s_until requires --timing + : ... note: In instance 't' + 39 | assert property (@(e) 1'h1 s_until 1'h1); + | ^~~~~~~ +%Error-NOTIMING: t/t_notiming.v:40:31: s_until_with requires --timing + : ... note: In instance 't' + 40 | assert property (@(e) 1'h1 s_until_with 1'h1); + | ^~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_unroll_pragma_none.py b/test_regress/t/t_unroll_pragma_none.py index 24144ca3f..ea66b1ba4 100755 --- a/test_regress/t/t_unroll_pragma_none.py +++ b/test_regress/t/t_unroll_pragma_none.py @@ -20,6 +20,6 @@ test.compile(verilator_flags2=['--unroll-count 4 --unroll-stmts 9999 --stats -DT test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 5) test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled iterations\s+(\d+)', 25) test.file_grep(test.stats, - r'Optimizations, Loop unrolling, Failed - reached --unroll-count\s+(\d+)', 2) + r'Optimizations, Loop unrolling, Failed - reached --unroll-count\s+(\d+)', 7) test.passes() diff --git a/test_regress/t/t_unroll_stmt.out b/test_regress/t/t_unroll_stmt.out index 50d32c577..69c4f7630 100644 --- a/test_regress/t/t_unroll_stmt.out +++ b/test_regress/t/t_unroll_stmt.out @@ -31,4 +31,33 @@ loop_6 3 loop_6 4 loop_6 5 stopping loop_6 +loop_7 0 +loop_8 0 +loop_8 1 +loop_8 2 +loop_8 3 +loop_7 1 +loop_8 0 +loop_8 1 +loop_8 2 +loop_8 3 +loop_7 2 +loop_8 0 +loop_8 1 +loop_8 2 +loop_8 3 +loop_7 3 +loop_8 0 +loop_8 1 +loop_8 2 +loop_8 3 +loop_7 4 +loop_8 0 +loop_8 1 +loop_8 2 +loop_8 3 +loop_9 0 +loop_9 1 +loop_9 2 +loop_9 3 *-* All Finished *-* diff --git a/test_regress/t/t_unroll_stmt.py b/test_regress/t/t_unroll_stmt.py index d7b6c0bfe..6d15f7c2e 100755 --- a/test_regress/t/t_unroll_stmt.py +++ b/test_regress/t/t_unroll_stmt.py @@ -26,7 +26,7 @@ test.file_grep(test.stats, test.file_grep(test.stats, r'Optimizations, Loop unrolling, Failed - unknown loop condition\s+(\d+)', 0) test.file_grep(test.stats, r'Optimizations, Loop unrolling, Pragma unroll_disable\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 6) -test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled iterations\s+(\d+)', 40) +test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 13) +test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled iterations\s+(\d+)', 77) test.passes() diff --git a/test_regress/t/t_unroll_stmt.v b/test_regress/t/t_unroll_stmt.v index 2b651df4b..a40495dfd 100644 --- a/test_regress/t/t_unroll_stmt.v +++ b/test_regress/t/t_unroll_stmt.v @@ -12,6 +12,8 @@ module t; return ++static_loop_cond < 8; endfunction + int nonConst3 = $c("3"); + initial begin // Basic loop for (int i = 0; i < 3; ++i) begin : loop_0 @@ -59,6 +61,22 @@ module t; end end end + // Outer condition updated in inner loop + begin + automatic int i = 0; + while (i < 5) begin : loop_7 + $display("loop_7 %0d", i); + for (int j = 0 ; j < 4; ++j) begin : loop_8 + $display("loop_8 %0d", j); + if (j == 3) ++i; + end + end + end + // Data dependent break + for (int i = 0; i < 5; ++i) begin : loop_9 + $display("loop_9 %0d", i); + if (i == nonConst3) break; + end // Done $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_vlt_warn_file_bad_b.vh b/test_regress/t/t_vlt_warn_file_bad_b.vh index f97aad506..e20daea9a 100644 --- a/test_regress/t/t_vlt_warn_file_bad_b.vh +++ b/test_regress/t/t_vlt_warn_file_bad_b.vh @@ -5,5 +5,5 @@ // SPDX-License-Identifier: CC0-1.0 module sub; - int warn_sub = 64'h1; + int warn_sub = 64'h1; endmodule