Merge remote-tracking branch 'upstream/master' into fix/7792-disable-midwindow

This commit is contained in:
Yilou Wang 2026-07-07 16:23:39 +02:00
commit b5a68cf315
81 changed files with 2235 additions and 1145 deletions

View File

@ -0,0 +1,165 @@
---
# DESCRIPTION: Github actions composite action
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: Artifact cache
description: >-
Cache a directory across workflow runs using a GitHub Actions artifact instead
of actions/cache (useful where artifacts are preferable, e.g. no 10 GB limit
on public repos). The base 'key' is scoped automatically: per pull request
(<key>-pr-<N>) or per branch (<key>-branch-<name>), and on a PR restore falls
back to the target branch's cache. Call once with mode=restore before the work
and once with mode=save after, passing the same 'key' and 'path'.
Branch-scoped restores are provenance-checked (the artifact must come from a
same-repo push to that branch) - artifact names are a global, unauthenticated
namespace, so otherwise a fork PR could forge one.
mode=restore calls the GitHub API via 'gh', so the caller's job must grant
'actions: read'.
inputs:
mode:
description: "'restore' or 'save'"
required: true
path:
description: "Directory to cache (archived on save, extracted into on restore)"
required: true
key:
description: "Base artifact name; scoped per PR/branch automatically"
required: true
retention-days:
description: "Artifact retention in days (mode=save)"
required: false
default: '3'
token:
description: "Token for the GitHub API (mode=restore); defaults to the job's GITHUB_TOKEN"
required: false
default: ''
outputs:
key:
description: "Echoes the input 'key', so a later save can reuse it without repeating the expression"
value: ${{ inputs.key }}
cache-hit:
description: "'true' if an artifact was restored (mode=restore)"
value: ${{ steps.restore.outputs.cache-hit }}
runs:
using: composite
steps:
- name: Check mode
shell: bash
env:
MODE: ${{ inputs.mode }}
run: |
case "$MODE" in
restore|save) ;;
*) echo "::error::artifact-cache: 'mode' must be 'restore' or 'save' (got '$MODE')"; exit 1 ;;
esac
# Scope the base key: <key>-pr-<N> for pull requests (with the target branch
# as fallback), else <key>-branch-<name>. Refs come via env, not ${{ }}
# interpolation, so a branch name with shell metacharacters can't inject.
- name: Compute artifact name
id: name
shell: bash
env:
BASE_KEY: ${{ inputs.key }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
sanitize() { printf '%s' "$1" | tr '/' '-'; } # artifact names cannot contain '/'
if [ "$EVENT_NAME" = pull_request ]; then
{
echo "primary=${BASE_KEY}-pr-${PR_NUMBER}"
echo "primary-branch="
echo "fallback=${BASE_KEY}-branch-$(sanitize "$BASE_REF")"
echo "fallback-branch=$BASE_REF"
} >> "$GITHUB_OUTPUT"
else
{
echo "primary=${BASE_KEY}-branch-$(sanitize "$REF_NAME")"
echo "primary-branch=$REF_NAME"
echo "fallback="
echo "fallback-branch="
} >> "$GITHUB_OUTPUT"
fi
- name: Restore artifact cache
id: restore
if: ${{ inputs.mode == 'restore' }}
continue-on-error: true # a cold start is fine; a restore failure must not fail the caller
shell: bash
env:
GH_TOKEN: ${{ inputs.token || github.token }}
CACHE_PATH: ${{ inputs.path }}
PRIMARY: ${{ steps.name.outputs.primary }}
PRIMARY_BRANCH: ${{ steps.name.outputs.primary-branch }}
FALLBACK: ${{ steps.name.outputs.fallback }}
FALLBACK_BRANCH: ${{ steps.name.outputs.fallback-branch }}
run: |
set -euo pipefail
mkdir -p "$CACHE_PATH"
# Newest non-expired artifact named $1 (empty if none). With a non-empty
# $2 (branch), require provenance: a same-repo run (head_repository_id ==
# repository_id) whose head branch is $2, so a fork cannot forge it.
newest_run_id() {
want="$1" br="$2" gh api \
"/repos/$GITHUB_REPOSITORY/actions/artifacts?name=$1&per_page=100" \
--jq '.artifacts[]
| select(.expired == false and .name == env.want)
| select(env.br == ""
or (.workflow_run.head_branch == env.br
and .workflow_run.head_repository_id == .workflow_run.repository_id))
| [.created_at, (.workflow_run.id | tostring)] | @tsv' \
| sort | tail -n1 | cut -f2
}
restore() { # $1=name $2=branch(empty ok); returns 0 on a successful restore
local name="$1" br="$2" rid tarball
[ -n "$name" ] || return 1
rid="$(newest_run_id "$name" "$br")" || return 1
[ -n "$rid" ] || return 1
echo "Restoring '$CACHE_PATH' from artifact '$name' (run $rid)"
gh run download "$rid" -R "$GITHUB_REPOSITORY" --name "$name" --dir "$RUNNER_TEMP/artifact-cache-dl" || return 1
# Each artifact holds a single tarball; match by extension so the inner
# filename is not part of the contract.
tarball="$(find "$RUNNER_TEMP/artifact-cache-dl" -name '*.tar.zst' -print -quit)"
[ -n "$tarball" ] || return 1
tar -I zstd -x -f "$tarball" -C "$CACHE_PATH"
}
if restore "$PRIMARY" "$PRIMARY_BRANCH"; then
echo "Restored from primary key"
echo "cache-hit=true" >> "$GITHUB_OUTPUT"
elif restore "$FALLBACK" "$FALLBACK_BRANCH"; then
echo "Restored from fallback key"
echo "cache-hit=true" >> "$GITHUB_OUTPUT"
else
echo "No matching artifact found; starting cold"
echo "cache-hit=false" >> "$GITHUB_OUTPUT"
fi
- name: Pack artifact cache
id: pack
if: ${{ inputs.mode == 'save' }}
shell: bash
env:
CACHE_PATH: ${{ inputs.path }}
run: |
set -euo pipefail
packdir="$(mktemp -d)" # unique dir so concurrent saves cannot collide
tar -I 'zstd -T0' -cf "$packdir/cache.tar.zst" -C "$CACHE_PATH" .
echo "tarball=$packdir/cache.tar.zst" >> "$GITHUB_OUTPUT"
- name: Upload artifact cache
if: ${{ inputs.mode == 'save' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ steps.name.outputs.primary }}
path: ${{ steps.pack.outputs.tarball }}
retention-days: ${{ inputs.retention-days }}
overwrite: true
compression-level: 0 # tarball is already zstd-compressed

56
.github/actions/setup-venv/action.yml vendored Normal file
View File

@ -0,0 +1,56 @@
---
# DESCRIPTION: Github actions composite action
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: Set up Python venv
description: >-
Restore, create, and optionally save the make-managed Python venv in a GitHub
Actions cache. The cache key combines the venv path (venvs are not
relocatable), the host OS and version, the Python major.minor version, and
the python-dev-requirements.txt hash, so any of those changing invalidates
it. Requires the already-configured Verilator tree to be present in 'repo'.
inputs:
repo:
description: "Verilator checkout holding the Makefile and .venv"
required: false
default: repo
save:
description: "Save the cache on a miss (set true only on trusted branches)"
required: false
default: 'false'
runs:
using: composite
steps:
- name: Compute Python venv cache key
id: cachekey
shell: bash
working-directory: ${{ inputs.repo }}
run: |
source ci/ci-common.bash
host="${DISTRO_ID:-$HOST_OS}${DISTRO_VERSION:+-$DISTRO_VERSION}"
pyver="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')"
reqs="$(sha256sum python-dev-requirements.txt | cut -d' ' -f1)"
venv="${PWD#/}/.venv" # absolute venv path (leading '/' trimmed)
echo "key=venv-${host}-py${pyver}-${reqs}-${venv//\//-}" >> "$GITHUB_OUTPUT"
- name: Restore Python venv
id: venv
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ inputs.repo }}/.venv
key: ${{ steps.cachekey.outputs.key }}
- name: Create Python venv
if: ${{ steps.venv.outputs.cache-hit != 'true' }}
shell: bash
working-directory: ${{ inputs.repo }}
run: make venv
- name: Save Python venv
if: ${{ inputs.save == 'true' && steps.venv.outputs.cache-hit != 'true' }}
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ inputs.repo }}/.venv
key: ${{ steps.venv.outputs.cache-primary-key }}

View File

@ -16,6 +16,7 @@ on:
permissions:
contents: read
actions: read
defaults:
run:
@ -29,272 +30,141 @@ concurrency:
jobs:
build-2604-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
name: Build | 26.04 | gcc
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
runs-on: ubuntu-26.04
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-26.04, cc: gcc, asan: 0}
build-2604-clang:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
name: Build | 26.04 | clang
uses: ./.github/workflows/reusable-build.yml
with:
cc: clang
dev-asan: true # Build (and run) with address sanitizer
runs-on: ubuntu-26.04
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-26.04, cc: clang, asan: 1}
build-2404-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
name: Build | 24.04 | gcc
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
runs-on: ubuntu-24.04
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-24.04, cc: gcc, asan: 0}
build-2404-clang:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
name: Build | 24.04 | clang
uses: ./.github/workflows/reusable-build.yml
with:
cc: clang
runs-on: ubuntu-24.04
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-24.04, cc: clang, asan: 0}
build-2204-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
name: Build | 22.04 | gcc
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
runs-on: ubuntu-22.04
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: linux
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-22.04, cc: gcc, asan: 0}
build-osx-gcc:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
build-macos-15-clang:
name: Build | macos-15 | clang
uses: ./.github/workflows/reusable-build.yml
with:
cc: clang
runs-on: macos-15
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: osx
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: macos-15, cc: gcc, asan: 0}
build-osx-clang:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }}
uses: ./.github/workflows/reusable-build.yml
with:
sha: ${{ github.sha }}
os: ${{ matrix.os }}
os-name: osx
cc: ${{ matrix.cc }}
dev-asan: ${{ matrix.asan }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
- {os: macos-15, cc: clang, asan: 0}
build-windows:
name: Build | ${{ matrix.os }} | ${{ matrix.cc }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- {os: windows-2025-vs2026, cc: msvc}
env:
CI_OS_NAME: win
CCACHE_COMPRESS: 1
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_LIMIT_MULTIPLE: 0.95
name: Build | windows-2025-vs2026 | msvc
runs-on: windows-2025-vs2026
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
path: repo
- name: Cache $CCACHE_DIR
- name: Cache win_flex_bison
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ env.CCACHE_DIR }}
key: msbuild-msvc-cmake
path: ${{ github.workspace }}/win_flex_bison
key: win_flex_bison
- name: compile
env:
WIN_FLEX_BISON: ${{ github.workspace }}/.ccache
run: ./ci/ci-win-compile.ps1
- name: test build
run: ./ci/ci-win-test.ps1
- name: Zip up repository
run: Compress-Archive -LiteralPath install -DestinationPath verilator.zip
- name: Upload zip archive
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
path: ${{ github.workspace }}/repo/verilator.zip
name: verilator-win.zip
test-2604-gcc:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
name: Test | 26.04 | gcc | ${{ matrix.suite }}
needs: build-2604-gcc
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2604-gcc.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
cc: gcc
runs-on: ubuntu-26.04
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 26.04 gcc
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-0}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-1}
- {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-2}
suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2]
test-2604-clang:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
name: Test | 26.04 | clang | ${{ matrix.suite }}
needs: build-2604-clang
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2604-clang.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
cc: clang
runs-on: ubuntu-26.04
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 26.04 clang
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-0}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-1}
- {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-2}
suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2]
test-2404-gcc:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
name: Test | 24.04 | gcc | ${{ matrix.suite }}
needs: build-2404-gcc
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2404-gcc.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
cc: gcc
runs-on: ubuntu-24.04
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 24.04 gcc
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-0}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-1}
- {os: ubuntu-24.04, cc: gcc, reloc: 0, suite: vltmt-2}
suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2]
test-2404-clang:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
name: Test | 24.04 | clang | ${{ matrix.suite }}
needs: build-2404-clang
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2404-clang.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
cc: clang
reloc: true # Test with relocated installation
runs-on: ubuntu-24.04
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 24.04 clang
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-0}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-1}
- {os: ubuntu-24.04, cc: clang, reloc: 0, suite: vltmt-2}
suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2]
test-2204-gcc:
name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }}
name: Test | 22.04 | gcc | ${{ matrix.suite }}
needs: build-2204-gcc
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build-2204-gcc.outputs.archive }}
os: ${{ matrix.os }}
cc: ${{ matrix.cc }}
reloc: ${{ matrix.reloc }}
cc: gcc
runs-on: ubuntu-22.04
suite: ${{ matrix.suite }}
dev-gcov: 0
strategy:
fail-fast: false
matrix:
include:
# Ubuntu 22.04 gcc
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-0}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-1}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-2}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: dist-vlt-3}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-0}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-1}
- {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-2}
suite: [dist-vlt-0, dist-vlt-1, dist-vlt-2, dist-vlt-3, vltmt-0, vltmt-1, vltmt-2]
lint-py:
name: Lint Python

View File

@ -13,6 +13,7 @@ on:
permissions:
contents: read
actions: read
defaults:
run:
@ -38,15 +39,13 @@ jobs:
(github.event_name == 'workflow_dispatch')
uses: ./.github/workflows/reusable-build.yml
with:
cc: gcc
dev-gcov: true
runs-on: ubuntu-24.04
# For pull requests, build the head of the pull request branch, not the
# merge commit, otherwise patch coverage would include the changes
# between the root of the pull request and the target branch
sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
os: ubuntu-24.04
os-name: linux
cc: gcc
dev-asan: 0
dev-gcov: 1
test:
name: Test | ${{ matrix.test }}${{ matrix.num }}
@ -54,11 +53,10 @@ jobs:
uses: ./.github/workflows/reusable-test.yml
with:
archive: ${{ needs.build.outputs.archive }}
os: ubuntu-24.04
cc: gcc
reloc: 0
dev-gcov: true
runs-on: ubuntu-24.04
suite: ${{ matrix.test }}${{ matrix.num }}
dev-gcov: 1
strategy:
fail-fast: false
matrix:
@ -121,7 +119,7 @@ jobs:
- name: Unpack repository archive
run: |
tar -x -z -f ${{ needs.build.outputs.archive }}
tar --zstd -x -f ${{ needs.build.outputs.archive }}
ls -lsha
- name: Download code coverage data

View File

@ -54,7 +54,7 @@ jobs:
- name: Docker meta
id: docker_meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
with:
images: |
${{ vars.DOCKER_HUB_NAMESPACE }}/${{ env.image_name }}
@ -64,21 +64,21 @@ jobs:
type=raw,value=latest,enable=${{ inputs.add_latest_tag == true }}
- name: Set up QEMU
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
with:
buildkitd-flags: --debug
- name: Login to Docker Hub
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
with:
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and Push to Docker
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
with:
context: ${{ env.build_context }}

View File

@ -11,35 +11,44 @@ on:
permissions:
contents: write
defaults:
run:
working-directory: repo
jobs:
format:
runs-on: ubuntu-24.04
name: Ubuntu 24.04 | format
env:
CI_OS_NAME: linux
CI_RUNS_ON: ubuntu-24.04
CI_COMMIT: ${{ github.sha }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
path: repo
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install packages for build
env:
CI_BUILD_STAGE_NAME: build
run: |
sudo apt install clang-format-18 || \
sudo apt install clang-format-18
git config --global user.email "action@example.com"
git config --global user.name "github action"
- name: Format code
- name: Configure
run: |
autoconf
./configure
make venv
- name: Set up Python venv
uses: ./repo/.github/actions/setup-venv
with:
save: ${{ github.ref == 'refs/heads/master' }}
- name: Format code
run: |
source .venv/bin/activate
make -j 4 format CLANGFORMAT=clang-format-18
git status
- name: Push
run: |-
if [ -n "$(git status --porcelain)" ]; then

View File

@ -7,7 +7,7 @@ name: Pages
on:
push:
branches: [master]
paths: ["ci/**", ".github/workflows"]
paths: ["ci/**", ".github/workflows/**"]
workflow_dispatch:
workflow_run:
workflows: ["Code coverage", "RTLMeter"]
@ -22,8 +22,11 @@ permissions:
# Allow only one concurrent deployment, skipping runs queued between the run
# in-progress and latest queued. However, do NOT cancel in-progress runs as we
# want to allow these deployments to complete.
# A skipped-upstream run does no work (see the build job's if:), so put it in its
# own throwaway group: otherwise, as the newest queued run, it would cancel a
# pending real run and then deploy nothing.
concurrency:
group: "pages"
group: ${{ (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'skipped') && format('pages-skip-{0}', github.run_id) || 'pages' }}
cancel-in-progress: false
defaults:
@ -34,6 +37,10 @@ jobs:
build:
name: Build content
runs-on: ubuntu-24.04
# A skipped upstream run (e.g. Code coverage / RTLMeter on a branch push)
# still fires workflow_run; don't rebuild and redeploy for it. deploy and
# notify need this job, so they cascade-skip too.
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion != 'skipped' }}
outputs:
pr-run-ids: ${{ steps.build.outputs.pr-run-ids }}
steps:
@ -58,9 +65,16 @@ jobs:
runs-on: ubuntu-24.04
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
url: ${{ steps.deploy-2.outputs.page_url || steps.deploy-1.outputs.page_url }}
steps:
# GitHub's Pages backend intermittently fails a deployment mid-sync, retry
- name: Deploy to GitHub Pages
id: deploy-1
continue-on-error: true
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
- name: Deploy to GitHub Pages (retry)
id: deploy-2
if: steps.deploy-1.outcome == 'failure'
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
notify:

View File

@ -7,37 +7,47 @@ name: reusable-build
on:
workflow_call:
inputs:
cc:
description: "Compiler to use: 'gcc' or 'clang'"
required: true
type: string
ccwarn:
description: "Build Verilator with warnings treated as errors (--enable-ccwarn)"
required: false
default: true
type: boolean
dev-asan:
description: "Build Verilator with the address sanitizer (--enable-dev-asan)"
required: false
default: false
type: boolean
dev-gcov:
description: "Build Verilator with gcov instrumentation (--enable-dev-gcov)"
required: false
default: false
type: boolean
install:
description: "Archive the Verilator installation, not the repo tree"
required: false
default: false
type: boolean
runs-on:
description: "Runner to build on, e.g. ubuntu-24.04"
required: true
type: string
sha:
description: "Commit SHA to build"
required: true
type: string
os: # e.g. ubuntu-24.04
required: true
type: string
cc: # 'clang' or 'gcc'
required: true
type: string
os-name: # 'linux' or 'osx'
required: true
type: string
dev-asan:
required: true
type: number
dev-gcov:
required: true
type: number
outputs:
archive:
description: "Name of the built repository archive artifact"
description: "Name of the built archive artifact"
value: ${{ jobs.build.outputs.archive }}
env:
CI_OS_NAME: ${{ inputs.os-name }}
CCACHE_COMPRESS: 1
CACHE_BASE_KEY: build-${{ inputs.runs-on }}-${{ inputs.cc }}${{ inputs.ccwarn && '-ccwarn' || '' }}${{ inputs.dev-asan && '-asan' || '' }}${{ inputs.dev-gcov && '-gcov' || '' }}
CCACHE_COMPILERCHECK: content
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_LIMIT_MULTIPLE: 0.95
INSTALL_DIR: ${{ github.workspace }}/install
RELOC_DIR: ${{ github.workspace }}/relloc
defaults:
run:
@ -48,26 +58,28 @@ jobs:
build:
name: Build
runs-on: ${{ inputs.os }}
runs-on: ${{ inputs.runs-on }}
outputs:
archive: ${{ steps.create-archive.outputs.archive }}
env:
CI_BUILD_STAGE_NAME: build
CI_DEV_ASAN: ${{ inputs.dev-asan }}
CI_DEV_GCOV: ${{ inputs.dev-gcov }}
CI_RUNS_ON: ${{ inputs.os }}
CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }}
CACHE_BASE_KEY: build-${{ inputs.os }}-${{ inputs.cc }}
CCACHE_MAXSIZE: 1000M # Per build matrix entry (* 5 = 5000M in total)
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
path: repo
ref: ${{ inputs.sha }}
fetch-depth: ${{ inputs.dev-gcov && '0' || '1' }} # Coverage flow needs full history
# Coverage needs full history; install needs it for the 'git describe'
# behind 'verilator --version'
fetch-depth: ${{ (inputs.install || inputs.dev-gcov) && '0' || '1' }}
- name: Configure ccache
run: |
# ccache is unreliable on macOS, and does nothing with 'gcc --coverage'
if [ "${{ startsWith(inputs.runs-on, 'macos') }}" = true ] || [ "${{ inputs.dev-gcov }}" = true ]; then
echo "CCACHE_DISABLE=1" >> "$GITHUB_ENV"
fi
- name: Cache $CCACHE_DIR
if: ${{ env.CCACHE_DISABLE != '1' }}
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
env:
CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache
@ -75,25 +87,43 @@ jobs:
path: ${{ env.CCACHE_DIR }}
key: ${{ env.CACHE_KEY }}-${{ inputs.sha }}
restore-keys: |
${{ env.CACHE_KEY }}-
${{ env.CACHE_KEY }}
- name: Install packages for build
run: ./ci/ci-install.bash
run: ./ci/ci-install.bash build
- name: Build
run: ./ci/ci-script.bash
run: |
./ci/ci-build.bash \
--prefix ${{ github.workspace }}/install \
--compiler ${{ inputs.cc }} \
${{ inputs.ccwarn && '--ccwarn' || '' }} \
${{ inputs.dev-asan && '--asan' || '' }} \
${{ inputs.dev-gcov && '--gcov' || '' }}
- name: Create repository archive
- name: Install
if: ${{ inputs.install }}
run: make install
- name: Create archive
id: create-archive
working-directory: ${{ github.workspace }}
run: |
# Name of the archive must be unique based on the build parameters
ARCHIVE=verilator-${{ inputs.sha }}-${{ inputs.os }}-${{ inputs.cc }}-${{ inputs.dev-asan }}-${{ inputs.dev-gcov }}.tar.gz
tar --posix -c -z -f $ARCHIVE repo
# Archive name, unique per build; flavour tags mark non-default builds
ARCHIVE="verilator-${{ inputs.sha }}-${{ inputs.runs-on }}-${{ inputs.cc }}"
ARCHIVE="$ARCHIVE${{ inputs.ccwarn && '-ccwarn' || '' }}"
ARCHIVE="$ARCHIVE${{ inputs.dev-asan && '-asan' || '' }}"
ARCHIVE="$ARCHIVE${{ inputs.dev-gcov && '-gcov' || '' }}"
ARCHIVE="$ARCHIVE.tar.zst"
# zstd compresses faster than gzip and decompresses far faster in the many downstream jobs
ZSTD_NBTHREADS=0 tar --posix --zstd -c -f "$ARCHIVE" ${{ inputs.install && 'install' || 'repo' }}
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
- name: Upload repository archive
- name: Upload archive
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
path: ${{ github.workspace }}/${{ steps.create-archive.outputs.archive }}
name: ${{ steps.create-archive.outputs.archive }}
overwrite: true
# Archive is already zstd-compressed; skip upload-artifact's zip pass
compression-level: 0

View File

@ -7,14 +7,6 @@ name: reusable-lint-py
on:
workflow_call:
env:
CI_OS_NAME: linux
CI_BUILD_STAGE_NAME: build
CI_RUNS_ON: ubuntu-22.04
CCACHE_COMPRESS: 1
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_LIMIT_MULTIPLE: 0.95
defaults:
run:
shell: bash
@ -31,19 +23,18 @@ jobs:
with:
path: repo
- name: Install packages for build
run: ./ci/ci-install.bash
- name: Install dependencies
run: ./ci/ci-install.bash lint-py
- name: Configure
run: |
autoconf
./configure --enable-longtests --enable-ccwarn
- name: Install python dependencies
run: |
sudo apt install python3-clang || \
sudo apt install python3-clang
make venv
- name: Set up Python venv
uses: ./repo/.github/actions/setup-venv
with:
save: ${{ github.ref == 'refs/heads/master' }}
- name: Lint
run: |-

View File

@ -1,96 +0,0 @@
---
# DESCRIPTION: Github actions config
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
name: reusable-rtlmeter-build
on:
workflow_call:
inputs:
runs-on:
description: "Runner to use, e.g.: ubuntu-24.04"
type: string
required: true
cc:
description: "Compiler to use: 'gcc' or 'clang'"
type: string
required: true
sha:
description: "Git SHA to build"
type: string
required: true
outputs:
archive:
description: "Name of the built installation archive artifact"
value: ${{ jobs.build.outputs.archive }}
defaults:
run:
shell: bash
env:
CCACHE_DIR: ${{ github.workspace }}/ccache
CCACHE_MAXSIZE: 512M
jobs:
build:
name: Build
runs-on: ${{ inputs.runs-on }}
outputs:
archive: ${{ steps.create-archive.outputs.archive }}
steps:
- name: Install dependencies
run: |
echo "path-exclude /usr/share/doc/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
echo "path-exclude /usr/share/man/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
sudo apt update || \
sudo apt update
sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev || \
sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev
- name: Use saved ccache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ccache
key: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: |
rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }}
rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}
rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
path: repo
ref: ${{ inputs.sha }}
fetch-depth: 0 # Required for 'git describe' used for 'verilator --version'
- name: Configure
working-directory: repo
run: |
autoconf
./configure --prefix=${{ github.workspace }}/install CXX=${{ inputs.cc == 'clang' && 'clang++' || 'g++' }}
- name: Make
working-directory: repo
run: make -j $(nproc)
- name: Install
working-directory: repo
run: make install
- name: Tar up installation
id: create-archive
run: |
SHA=$(git -C repo rev-parse HEAD)
ARCHIVE=verilator-$SHA-rtlmeter-${{ inputs.runs-on }}-${{ inputs.cc }}.tar.gz
tar --posix -c -z -f $ARCHIVE install
echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT
- name: Upload Verilator installation archive
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
path: ${{ steps.create-archive.outputs.archive }}
name: ${{ steps.create-archive.outputs.archive }}
overwrite: true

View File

@ -20,11 +20,11 @@ on:
type: string
required: true
verilator-archive-new:
description: "Name of the installation archive artifact from reusable-rtlmeter-build, new version"
description: "Name of the installation archive artifact from reusable-build, new version"
type: string
required: true
verilator-archive-old:
description: "Name of the installation archive artifact from reusable-rtlmeter-build, old version"
description: "Name of the installation archive artifact from reusable-build, old version"
type: string
required: false
default: ""
@ -53,8 +53,8 @@ defaults:
shell: bash
env:
CCACHE_DIR: ${{ github.workspace }}/ccache
CCACHE_MAXSIZE: 512M
# RTLMeter measures compile/execute times, so caching must stay off to keep
# timings representative; ccache is still on PATH but acts as a pass-through
CCACHE_DISABLE: 1
jobs:
@ -82,14 +82,6 @@ jobs:
working-directory: rtlmeter
run: make venv
- name: Use saved ccache
if: ${{ env.CCACHE_DISABLE == 0 }}
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ env.CCACHE_DIR }}
key: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }}
########################################################################
# Run with new Verilator
########################################################################
@ -101,7 +93,7 @@ jobs:
- name: Unpack Verilator installation archive - new
run: |
tar -x -z -f ${{ inputs.verilator-archive-new }}
tar --zstd -x -f ${{ inputs.verilator-archive-new }}
mv install verilator-new
- name: Compile cases - new
@ -164,7 +156,7 @@ jobs:
- name: Unpack Verilator installation archive - old
if: ${{ inputs.verilator-archive-old != '' }}
run: |
tar -x -z -f ${{ inputs.verilator-archive-old }}
tar --zstd -x -f ${{ inputs.verilator-archive-old }}
mv install verilator-old
- name: Compile cases - old

View File

@ -11,29 +11,37 @@ on:
description: "Name of the repository archive artifact from reusable-build"
required: true
type: string
os: # e.g. ubuntu-24.04
required: true
type: string
cc: # gcc or clang
required: true
type: string
reloc: # 0 or 1
required: true
type: number
suite: # e.g. dist-vlt-0
cc:
description: "Compiler to use: 'gcc' or 'clang'"
required: true
type: string
dev-gcov:
description: "Collect gcov coverage data from the test run"
required: false
default: false
type: boolean
reloc:
description: "Relocate the installation before testing"
required: false
default: false
type: boolean
runs-on:
description: "Runner to test on, e.g. ubuntu-24.04"
required: true
type: number
type: string
suite:
description: "Test suite to run, e.g. dist-vlt-0"
required: true
type: string
permissions:
contents: read
actions: read
env:
CI_OS_NAME: linux
CCACHE_COMPRESS: 1
CCACHE_COMPILERCHECK: content
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_LIMIT_MULTIPLE: 0.95
INSTALL_DIR: ${{ github.workspace }}/install
RELOC_DIR: ${{ github.workspace }}/relloc
CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }}
defaults:
run:
@ -43,15 +51,8 @@ defaults:
jobs:
test:
runs-on: ${{ inputs.os }}
runs-on: ${{ inputs.runs-on }}
name: Test
env:
CI_BUILD_STAGE_NAME: test
CI_RUNS_ON: ${{ inputs.os }}
CI_RELOC: ${{inputs.reloc }}
CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }}
CACHE_BASE_KEY: test-${{ inputs.os }}-${{ inputs.cc }}-${{inputs.reloc }}-${{ inputs.suite }}
CCACHE_MAXSIZE: 100M # Per build per suite (* 5 * 5 = 2500M in total)
steps:
- name: Download repository archive
@ -63,32 +64,43 @@ jobs:
- name: Unpack repository archive
working-directory: ${{ github.workspace }}
run: |
tar -x -z -f ${{ inputs.archive }}
tar --zstd -x -f ${{ inputs.archive }}
ls -lsha
- name: Cache $CCACHE_DIR
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
env:
CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache2
- name: Configure ccache
run: |
# ccache is unreliable on macOS, and does nothing with 'gcc --coverage'
if [ "${{ startsWith(inputs.runs-on, 'macos') }}" = true ] || [ "${{ inputs.dev-gcov }}" = true ]; then
echo "CCACHE_DISABLE=1" >> "$GITHUB_ENV"
fi
# Test-job ccache is stored as an artifact, not actions/cache
- name: Restore ccache
id: ccache
if: ${{ env.CCACHE_DISABLE != '1' }}
uses: ./repo/.github/actions/artifact-cache
with:
mode: restore
path: ${{ env.CCACHE_DIR }}
key: ${{ env.CACHE_KEY }}-${{ github.sha }}
restore-keys: |
${{ env.CACHE_KEY }}-
key: ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.suite }}
- name: Install test dependencies
run: |
./ci/ci-install.bash
make venv
./ci/ci-install.bash test
- name: Set up Python venv
uses: ./repo/.github/actions/setup-venv
with:
save: ${{ github.ref == 'refs/heads/master' }}
- name: Test
id: run-test
continue-on-error: true
env:
TESTS: ${{ inputs.suite }}
run: |
source .venv/bin/activate
./ci/ci-script.bash
./ci/ci-test.bash \
--suite ${{ inputs.suite }} \
${{ inputs.reloc && format('--reloc {0}/reloc', github.workspace) || '' }}
- name: Combine code coverage data
if: ${{ inputs.dev-gcov }}
@ -104,6 +116,16 @@ jobs:
path: ${{ github.workspace }}/repo/obj_coverage/verilator-${{ inputs.suite }}.info
name: code-coverage-${{ inputs.suite }}
- name: Save ccache
if: ${{ env.CCACHE_DISABLE != '1' && !cancelled() }}
uses: ./repo/.github/actions/artifact-cache
with:
mode: save
path: ${{ env.CCACHE_DIR }}
key: ${{ steps.ccache.outputs.key }}
# Keep master's cache around longer; PR/branch caches churn faster
retention-days: ${{ github.ref == 'refs/heads/master' && 7 || 3 }}
- name: Fail job if a test failed
if: ${{ steps.run-test.outcome == 'failure' && !cancelled() }}
run: |-

View File

@ -65,39 +65,47 @@ jobs:
build-gcc-new:
name: Build New Verilator - GCC
needs: start
uses: ./.github/workflows/reusable-rtlmeter-build.yml
uses: ./.github/workflows/reusable-build.yml
with:
runs-on: ubuntu-24.04
cc: gcc
ccwarn: false
install: true
runs-on: ubuntu-24.04
sha: ${{ github.sha }}
build-clang-new:
name: Build New Verilator - Clang
needs: start
uses: ./.github/workflows/reusable-rtlmeter-build.yml
uses: ./.github/workflows/reusable-build.yml
with:
runs-on: ubuntu-24.04
cc: clang
ccwarn: false
install: true
runs-on: ubuntu-24.04
sha: ${{ github.sha }}
build-gcc-old:
name: Build Old Verilator - GCC
needs: start
if: ${{ needs.start.outputs.old-sha != '' }}
uses: ./.github/workflows/reusable-rtlmeter-build.yml
uses: ./.github/workflows/reusable-build.yml
with:
runs-on: ubuntu-24.04
cc: gcc
ccwarn: false
install: true
runs-on: ubuntu-24.04
sha: ${{ needs.start.outputs.old-sha }}
build-clang-old:
name: Build Old Verilator - Clang
needs: start
if: ${{ needs.start.outputs.old-sha != '' }}
uses: ./.github/workflows/reusable-rtlmeter-build.yml
uses: ./.github/workflows/reusable-build.yml
with:
runs-on: ubuntu-24.04
cc: clang
ccwarn: false
install: true
runs-on: ubuntu-24.04
sha: ${{ needs.start.outputs.old-sha }}
run-gcc:

85
ci/ci-build.bash Executable file
View File

@ -0,0 +1,85 @@
#!/usr/bin/env bash
# DESCRIPTION: Verilator: CI build job script
#
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
################################################################################
# Executed in the 'build' stage.
################################################################################
# Destructive to the checkout (reconfigures and rebuilds); never run locally
if [ "$GITHUB_ACTIONS" != "true" ]; then
echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2
exit 1
fi
set -e
set -x
source "$(dirname "$0")/ci-common.bash"
################################################################################
# Parse arguments
OPT_ASAN=0
OPT_CCWARN=0
OPT_GCOV=0
OPT_COMPILER=
OPT_PREFIX=
while [ $# -gt 0 ]; do
case "$1" in
--asan) OPT_ASAN=1 ;;
--ccwarn) OPT_CCWARN=1 ;;
--compiler)
[ $# -ge 2 ] || fatal "--compiler requires an argument"
OPT_COMPILER="$2"
shift
;;
--gcov) OPT_GCOV=1 ;;
--prefix)
[ $# -ge 2 ] || fatal "--prefix requires an argument"
OPT_PREFIX="$2"
shift
;;
*) fatal "Unknown option: '$1'" ;;
esac
shift
done
[ -n "$OPT_COMPILER" ] || fatal "--compiler is required"
[ -n "$OPT_PREFIX" ] || fatal "--prefix is required"
# Map the compiler name to the executable name configure CXX expects
case "$OPT_COMPILER" in
clang) CXX=clang++ ;;
gcc) CXX=g++ ;;
*) fatal "Unknown compiler: '$OPT_COMPILER'" ;;
esac
################################################################################
# Configure
CONFIGURE_ARGS="--prefix=$OPT_PREFIX --enable-longtests --enable-light-debug"
if [ "$OPT_CCWARN" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-ccwarn"
fi
if [ "$OPT_ASAN" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-asan"
CXX="$CXX -DVL_LEAK_CHECKS"
fi
if [ "$OPT_GCOV" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-gcov"
fi
autoconf
./configure $CONFIGURE_ARGS CXX="$CXX"
################################################################################
# Build
ccache -z
BUILD_START=$SECONDS
"$MAKE" -j "$NPROC" -k
ccache -svv
ccache --evict-older-than "$((SECONDS - BUILD_START + 60))s"
ccache -svv

35
ci/ci-common.bash Normal file
View File

@ -0,0 +1,35 @@
# DESCRIPTION: Verilator: CI common definitions, sourced by the stage scripts
#
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
################################################################################
# Common definitions sourced by the CI stage scripts; not executed directly.
################################################################################
fatal() {
echo "ERROR: $(basename "$0"): $1" >&2; exit 1;
}
case "$(uname -s)" in
Linux)
HOST_OS=linux
MAKE=make
NPROC=$(nproc)
# Distro id/version; subshell keeps os-release out of our namespace
if [ -r /etc/os-release ]; then
DISTRO_ID=$(. /etc/os-release; echo "$ID")
DISTRO_VERSION=$(. /etc/os-release; echo "$VERSION_ID")
fi
;;
Darwin)
HOST_OS=macOS
MAKE=make
NPROC=$(sysctl -n hw.logicalcpu)
;;
*)
fatal "Unknown host OS: '$(uname -s)'"
;;
esac
export MAKE

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
# DESCRIPTION: Verilator: CI dependency install script
#
# SPDX-FileCopyrightText: 2020 Geza Lore
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
################################################################################
@ -10,141 +10,168 @@
# required by the particular build stage.
################################################################################
# Installs system packages with sudo; never run locally
if [ "$GITHUB_ACTIONS" != "true" ]; then
echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2
exit 1
fi
set -e
set -x
cd $(dirname "$0")/..
source "$(dirname "$0")/ci-common.bash"
################################################################################
# Parse arguments
# Which stage to install dependencies for: 'build' or 'test'
STAGE="$1"
################################################################################
# Install dependencies
# Avoid occasional cpan failures "Issued certificate has expired."
export PERL_LWP_SSL_VERIFY_HOSTNAME=0
echo "check_certificate = off" >> ~/.wgetrc
fatal() {
echo "ERROR: $(basename "$0"): $1" >&2; exit 1;
}
if [ "$CI_OS_NAME" = "linux" ]; then
MAKE=make
elif [ "$CI_OS_NAME" = "osx" ]; then
MAKE=make
elif [ "$CI_OS_NAME" = "freebsd" ]; then
MAKE=gmake
else
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fi
if [ "$CI_OS_NAME" = "linux" ]; then
if [ "$HOST_OS" = "linux" ]; then
# Avoid slow "processing triggers for man db"
echo "path-exclude /usr/share/doc/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
echo "path-exclude /usr/share/man/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
elif [ "$HOST_OS" = "macOS" ]; then
# The macos runner image ships an untrusted third-party tap we don't use;
# untap it so brew stops emitting a tap-trust warning. Force + '|| true' since
# untap fails if a formula was installed from it, which is harmless here.
brew untap --force aws/tap || true
fi
install-wavediff() {
source ci/docker/buildenv/wavetools.conf
local _base_url="https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}"
local _platform
if [ "$CI_OS_NAME" = "linux" ]; then
if [ "$HOST_OS" = "linux" ]; then
_platform="linux-x86_64"
elif [ "$CI_OS_NAME" = "osx" ]; then
elif [ "$HOST_OS" = "macOS" ]; then
_platform="macos-arm64"
elif [ "$CI_OS_NAME" = "windows" ]; then
_platform="windows-x86_64"
else
echo "WARNING: No wavetools binary available for CI_OS_NAME=$CI_OS_NAME, skipping"
echo "WARNING: No wavetools binary available for HOST_OS=$HOST_OS, skipping"
return 0
fi
local _tmpdir
_tmpdir=$(mktemp -d)
local _archive="wavetools-${WAVETOOLS_VERSION}-${_platform}"
if [ "$CI_OS_NAME" = "windows" ]; then
wget -q -O "${_tmpdir}/${_archive}.zip" "${_base_url}/${_archive}.zip"
unzip -o "${_tmpdir}/${_archive}.zip" -d "${_tmpdir}"
else
wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz"
tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}"
fi
wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz"
tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}"
sudo cp "${_tmpdir}/${_archive}/wavediff" /usr/local/bin/wavediff
rm -rf "${_tmpdir}"
}
if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then
if [ "$STAGE" = "build" ]; then
##############################################################################
# Dependencies of jobs in the 'build' stage, i.e.: packages required to
# build Verilator
if [ "$CI_OS_NAME" = "linux" ]; then
sudo apt-get update ||
sudo apt-get update
sudo apt-get install --yes ccache help2man libfl-dev ||
sudo apt-get install --yes ccache help2man libfl-dev
if [[ ! "$CI_RUNS_ON" =~ "ubuntu-22.04" ]]; then
# Some conflict of libunwind verison on 22.04, can live without it for now
sudo apt-get install --yes libjemalloc-dev ||
sudo apt-get install --yes libjemalloc-dev
fi
if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then
if [[ ! "$CI_RUNS_ON" =~ "-riscv" ]]; then
sudo apt-get install --yes libsystemc libsystemc-dev ||
sudo apt-get install --yes libsystemc libsystemc-dev
if [ "$HOST_OS" = "linux" ]; then
if [ "$DISTRO_ID" = "ubuntu" ]; then
PACKAGES=(
bear
ccache
help2man
libfl-dev
libsystemc-dev
mold
)
# libunwind conflict on 22.04, can live without libjemalloc there
if [ "$DISTRO_VERSION" != "22.04" ]; then
PACKAGES+=(libjemalloc-dev)
fi
sudo apt-get update ||
sudo apt-get update
sudo apt-get install --yes "${PACKAGES[@]}" ||
sudo apt-get install --yes "${PACKAGES[@]}"
fi
if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then
sudo apt-get install --yes bear mold ||
sudo apt-get install --yes bear mold
fi
elif [ "$CI_OS_NAME" = "osx" ]; then
elif [ "$HOST_OS" = "macOS" ]; then
PACKAGES=(
autoconf
bison
ccache
flex
gperftools
help2man
perl
)
brew update ||
brew update
brew install ccache perl gperftools autoconf bison flex help2man ||
brew install ccache perl gperftools autoconf bison flex help2man
elif [ "$CI_OS_NAME" = "freebsd" ]; then
sudo pkg install -y autoconf bison ccache gmake perl5
brew install "${PACKAGES[@]}" ||
brew install "${PACKAGES[@]}"
else
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fatal "Unknown HOST_OS: '$HOST_OS'"
fi
if [ -n "$CCACHE_DIR" ]; then
mkdir -p "$CCACHE_DIR"
fi
elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then
elif [ "$STAGE" = "test" ]; then
##############################################################################
# Dependencies of jobs in the 'test' stage, i.e.: packages required to
# run the tests
if [ "$CI_OS_NAME" = "linux" ]; then
sudo apt-get update ||
sudo apt-get update
# libfl-dev needed for internal coverage's test runs
sudo apt-get install --yes gdb gtkwave lcov libfl-dev ccache jq z3 ||
sudo apt-get install --yes gdb gtkwave lcov libfl-dev ccache jq z3
# Required for test_regress/t/t_dist_attributes.py
if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then
sudo apt-get install --yes python3-clang mold ||
sudo apt-get install --yes python3-clang mold
if [ "$HOST_OS" = "linux" ]; then
if [ "$DISTRO_ID" = "ubuntu" ]; then
PACKAGES=(
ccache
gdb
jq
lcov
libfl-dev
libsystemc-dev
mold
python3-clang
z3
)
sudo apt-get update ||
sudo apt-get update
sudo apt-get install --yes "${PACKAGES[@]}" ||
sudo apt-get install --yes "${PACKAGES[@]}"
fi
if [[ "$CI_RUNS_ON" =~ "ubuntu-22.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-24.04" ]] || [[ "$CI_RUNS_ON" =~ "ubuntu-26.04" ]]; then
if [[ ! "$CI_RUNS_ON" =~ "-riscv" ]]; then
sudo apt-get install --yes libsystemc libsystemc-dev ||
sudo apt-get install --yes libsystemc libsystemc-dev
fi
fi
elif [ "$CI_OS_NAME" = "osx" ]; then
elif [ "$HOST_OS" = "macOS" ]; then
PACKAGES=(
ccache
jq
perl
z3
)
brew update ||
brew update
# brew cask install gtkwave # fst2vcd hangs at launch, so don't bother
brew install ccache perl jq z3
elif [ "$CI_OS_NAME" = "freebsd" ]; then
# fst2vcd fails with "Could not open '<input file>', exiting."
sudo pkg install -y ccache gmake perl5 python3 jq z3
brew install "${PACKAGES[@]}" ||
brew install "${PACKAGES[@]}"
else
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fatal "Unknown HOST_OS: '$HOST_OS'"
fi
# Common installs
install-wavediff
# Workaround -fsanitize=address crash
sudo sysctl -w vm.mmap_rnd_bits=28
elif [ "$STAGE" = "lint-py" ]; then
# nodist/clang_check_attributes.
if [ "$HOST_OS" = "linux" ] && [ "$DISTRO_ID" = "ubuntu" ]; then
PACKAGES=(
python3-clang # Not run, but importers are linted
)
sudo apt-get update ||
sudo apt-get update
sudo apt-get install --yes "${PACKAGES[@]}" ||
sudo apt-get install --yes "${PACKAGES[@]}"
fi
else
##############################################################################
# Unknown build stage
fatal "Unknown CI_BUILD_STAGE_NAME: '$CI_BUILD_STAGE_NAME'"
fatal "Unknown stage '$STAGE' (expected 'build', 'test' or 'lint-py')"
fi
# Report where the tools we may have installed live (ok if some are missing)
set +x
echo "Tools:"
for bin in autoconf bear bison ccache flex gdb help2man jq lcov mold perl wavediff z3; do
echo -n " $bin: "
which "$bin" || echo "Not found"
done

View File

@ -34,9 +34,14 @@ for RUN_ID in ${PR_RUN_IDS//,/ }; do
cat ${ARTIFACTS_DIR}/body.txt
gh pr comment $(cat ${ARTIFACTS_DIR}/pr-number.txt) --body-file ${ARTIFACTS_DIR}/body.txt
# Get the artifact ID
ARTIFACT_ID=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id')
# Get the artifact IDs. Note there can be more than one artifact named
# 'pr-notification' for a single run, as the artifacts endpoint lists
# artifacts across all run attempts, and a re-run uploads a new one while
# keeping the previous attempt's artifact.
ARTIFACT_IDS=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id')
# Delete it, so we only notify once
gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}"
# Delete them all, so we only notify once
for ARTIFACT_ID in ${ARTIFACT_IDS}; do
gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}"
done
done

View File

@ -1,208 +0,0 @@
#!/usr/bin/env bash
# DESCRIPTION: Verilator: CI main job script
#
# SPDX-FileCopyrightText: 2020 Geza Lore
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
################################################################################
# This is the main script executed in the 'script' phase by all jobs. We use a
# single script to keep the CI setting simple. We pass job parameters via
# environment variables using 'env' keys.
################################################################################
set -e
set -x
fatal() {
echo "ERROR: $(basename "$0"): $1" >&2; exit 1;
}
if [ "$CI_OS_NAME" = "linux" ]; then
export MAKE=make
NPROC=$(nproc)
elif [ "$CI_OS_NAME" = "osx" ]; then
export MAKE=make
NPROC=$(sysctl -n hw.logicalcpu)
# Disable ccache, doesn't always work in GitHub Actions
export OBJCACHE=
elif [ "$CI_OS_NAME" = "freebsd" ]; then
export MAKE=gmake
NPROC=$(sysctl -n hw.ncpu)
else
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fi
NPROC=$(expr $NPROC '+' 1)
if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then
##############################################################################
# Build verilator
autoconf
CONFIGURE_ARGS="--enable-longtests --enable-ccwarn"
if [ "$CI_DEV_ASAN" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-asan"
CXX="$CXX -DVL_LEAK_CHECKS"
fi
if [ "$CI_DEV_GCOV" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-dev-gcov"
fi
./configure $CONFIGURE_ARGS --prefix="$INSTALL_DIR"
ccache -z
"$MAKE" -j "$NPROC" -k
# 22.04: ccache -s -v
ccache -s
if [ "$CI_OS_NAME" = "osx" ]; then
file bin/verilator_bin
file bin/verilator_bin_dbg
md5 bin/verilator_bin
md5 bin/verilator_bin_dbg
stat bin/verilator_bin
stat bin/verilator_bin_dbg
fi
elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then
##############################################################################
# Run tests
export VERILATOR_TEST_NO_CONTRIBUTORS=1 # Separate workflow check
export VERILATOR_TEST_NO_LINT_PY=1 # Separate workflow check
if [ "$CI_OS_NAME" = "osx" ]; then
export VERILATOR_TEST_NO_GDB=1 # Pain to get GDB to work on OS X
# TODO below may no longer be required as configure checks for -pg
export VERILATOR_TEST_NO_GPROF=1 # Apple Clang has no -pg
# export PATH="/Applications/gtkwave.app/Contents/Resources/bin:$PATH" # fst2vcd
file bin/verilator_bin
file bin/verilator_bin_dbg
md5 bin/verilator_bin
md5 bin/verilator_bin_dbg
stat bin/verilator_bin
stat bin/verilator_bin_dbg
# For some reason, the dbg exe is corrupted by this point ('file' reports
# it as data rather than a Mach-O). Unclear if this is an OS X issue or
# CI's. Remove the file and re-link...
rm bin/verilator_bin_dbg
"$MAKE" -j "$NPROC" -k
elif [ "$CI_OS_NAME" = "freebsd" ]; then
export VERILATOR_TEST_NO_GDB=1 # Disable for now, ideally should run
# TODO below may no longer be required as configure checks for -pg
export VERILATOR_TEST_NO_GPROF=1 # gprof is a bit different on FreeBSD, disable
fi
TEST_REGRESS=test_regress
if [ "$CI_RELOC" == 1 ]; then
# Testing that the installation is relocatable.
"$MAKE" install
mkdir -p "$RELOC_DIR"
mv "$INSTALL_DIR" "$RELOC_DIR/relocated-install"
export VERILATOR_ROOT="$RELOC_DIR/relocated-install/share/verilator"
TEST_REGRESS="$RELOC_DIR/test_regress"
mv test_regress "$TEST_REGRESS"
NODIST="$RELOC_DIR/nodist"
mv nodist "$NODIST"
# Feeling brave?
find . -delete
ls -la .
fi
# Run the specified test
ccache -z
case $TESTS in
dist-vlt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=0/4
;;
dist-vlt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=1/4
;;
dist-vlt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=2/4
;;
dist-vlt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=3/4
;;
vltmt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=0/3
;;
vltmt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=1/3
;;
vltmt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=2/3
;;
coverage-dist)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist"
;;
coverage-vlt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=0/10
;;
coverage-vlt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=1/10
;;
coverage-vlt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=2/10
;;
coverage-vlt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=3/10
;;
coverage-vlt-4)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=4/10
;;
coverage-vlt-5)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=5/10
;;
coverage-vlt-6)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=6/10
;;
coverage-vlt-7)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=7/10
;;
coverage-vlt-8)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=8/10
;;
coverage-vlt-9)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=9/10
;;
coverage-vltmt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=0/10
;;
coverage-vltmt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=1/10
;;
coverage-vltmt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=2/10
;;
coverage-vltmt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=3/10
;;
coverage-vltmt-4)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=4/10
;;
coverage-vltmt-5)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=5/10
;;
coverage-vltmt-6)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=6/10
;;
coverage-vltmt-7)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=7/10
;;
coverage-vltmt-8)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=8/10
;;
coverage-vltmt-9)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=9/10
;;
*)
fatal "Unknown TESTS: $TESTS"
;;
esac
# To see load average (1 minute, 5 minute, 15 minute)
uptime
# 22.04: ccache -s -v
ccache -s
else
##############################################################################
# Unknown build stage
fatal "Unknown CI_BUILD_STAGE_NAME: '$CI_BUILD_STAGE_NAME'"
fi

169
ci/ci-test.bash Executable file
View File

@ -0,0 +1,169 @@
#!/usr/bin/env bash
# DESCRIPTION: Verilator: CI test job script
#
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
################################################################################
# Executed in the 'test' stage.
################################################################################
# Destructive to the checkout (wipes most of it); never run locally
if [ "$GITHUB_ACTIONS" != "true" ]; then
echo "ERROR: $(basename "$0") must only be run in GitHub Actions CI" >&2
exit 1
fi
set -e
set -x
source "$(dirname "$0")/ci-common.bash"
################################################################################
# Parse arguments
OPT_RELOC=
OPT_SUITE=
while [ $# -gt 0 ]; do
case "$1" in
--reloc)
[ $# -ge 2 ] || fatal "--reloc requires an argument"
OPT_RELOC="$2"
shift
;;
--suite)
[ $# -ge 2 ] || fatal "--suite requires an argument"
OPT_SUITE="$2"
shift
;;
*) fatal "Unknown option: '$1'" ;;
esac
shift
done
[ -n "$OPT_SUITE" ] || fatal "--suite is required"
################################################################################
# Run tests
export VERILATOR_TEST_NO_CONTRIBUTORS=1 # Separate workflow check
export VERILATOR_TEST_NO_LINT_PY=1 # Separate workflow check
if [ "$HOST_OS" = "macOS" ]; then
export VERILATOR_TEST_NO_GDB=1 # No working GDB on macOS
fi
TEST_REGRESS=test_regress
if [ -n "$OPT_RELOC" ]; then
# Testing that the installation is relocatable.
"$MAKE" install
# Install prefix, as configured into the Makefile at build time
INSTALL_DIR=$(sed -n 's/^prefix = //p' Makefile)
mkdir -p "$OPT_RELOC"
mv "$INSTALL_DIR" "$OPT_RELOC/relocated-install"
export VERILATOR_ROOT="$OPT_RELOC/relocated-install/share/verilator"
TEST_REGRESS="$OPT_RELOC/test_regress"
mv test_regress "$TEST_REGRESS"
NODIST="$OPT_RELOC/nodist"
mv nodist "$NODIST"
# Delete everything else, but keep the CI infrastructure
find . -mindepth 1 -maxdepth 1 ! -name .github ! -name ci -exec rm -rf {} +
ls -la .
fi
# Run the specified suite
ccache -z
TEST_START=$SECONDS
case $OPT_SUITE in
dist-vlt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=0/4
;;
dist-vlt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=1/4
;;
dist-vlt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=2/4
;;
dist-vlt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist --vlt --driver-clean" DRIVER_HASHSET=--hashset=3/4
;;
vltmt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=0/3
;;
vltmt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=1/3
;;
vltmt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt --driver-clean" DRIVER_HASHSET=--hashset=2/3
;;
coverage-dist)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--dist"
;;
coverage-vlt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=0/10
;;
coverage-vlt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=1/10
;;
coverage-vlt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=2/10
;;
coverage-vlt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=3/10
;;
coverage-vlt-4)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=4/10
;;
coverage-vlt-5)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=5/10
;;
coverage-vlt-6)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=6/10
;;
coverage-vlt-7)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=7/10
;;
coverage-vlt-8)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=8/10
;;
coverage-vlt-9)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vlt" DRIVER_HASHSET=--hashset=9/10
;;
coverage-vltmt-0)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=0/10
;;
coverage-vltmt-1)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=1/10
;;
coverage-vltmt-2)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=2/10
;;
coverage-vltmt-3)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=3/10
;;
coverage-vltmt-4)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=4/10
;;
coverage-vltmt-5)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=5/10
;;
coverage-vltmt-6)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=6/10
;;
coverage-vltmt-7)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=7/10
;;
coverage-vltmt-8)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=8/10
;;
coverage-vltmt-9)
"$MAKE" -C "$TEST_REGRESS" SCENARIOS="--vltmt" DRIVER_HASHSET=--hashset=9/10
;;
*)
fatal "Unknown suite: $OPT_SUITE"
;;
esac
ccache -svv
ccache --evict-older-than "$((SECONDS - TEST_START + 60))s"
ccache -svv
uptime # To see load average

View File

@ -6,19 +6,33 @@
Set-PSDebug -Trace 1
if (-Not (Test-Path $PWD/../.ccache/win_bison.exe)) {
$NPROC = $env:NUMBER_OF_PROCESSORS
# Absolute path for the win_flex_bison install; CMake reads this from the environment
$env:WIN_FLEX_BISON = "$(Resolve-Path ..)/win_flex_bison"
if (-Not (Test-Path $env:WIN_FLEX_BISON/win_bison.exe)) {
git clone --depth 1 https://github.com/lexxmark/winflexbison
cd winflexbison
mkdir build
cd build
cmake .. --install-prefix $PWD/../../../.ccache
cmake --build . --config Release -j 3
cmake --install . --prefix $PWD/../../../.ccache
cmake .. --install-prefix $env:WIN_FLEX_BISON
cmake --build . --config Release -j $NPROC
cmake --install . --prefix $env:WIN_FLEX_BISON
cd ../..
}
# Enter the MSVC developer shell so cl/link are on PATH for the Ninja generator
$VsPath = & "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" -latest -products * -property installationPath
Import-Module "$VsPath/Common7/Tools/Microsoft.VisualStudio.DevShell.dll"
Enter-VsDevShell -VsInstallPath $VsPath -SkipAutomaticLocation -DevCmdArguments "-arch=x64 -host_arch=x64"
mkdir build
cd build
cmake .. --install-prefix $PWD/../install
cmake --build . --config Release -j 3
# Ninja saturates all cores; the MSBuild generator only parallelizes across
# projects and Verilator is a single target, so it compiles serially. /Od skips
# optimization: this job only checks that Verilator builds and can verilate an
# example, so an optimized binary is not needed and the codegen time dominates.
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release --install-prefix $PWD/../install "-DCMAKE_CXX_FLAGS_RELEASE=/Od /DNDEBUG"
cmake --build .
cmake --install . --prefix $PWD/../install

View File

@ -7,13 +7,17 @@
Set-PSDebug -Trace 1
$NPROC = $env:NUMBER_OF_PROCESSORS
cd install
$Env:VERILATOR_ROOT=$PWD
cd examples/cmake_tracing_c
mkdir build
cd build
cmake ..
cmake --build . --config Release -j 3
# /Od skips optimization; this only checks the example verilates and builds, so
# an optimized binary is not needed (see ci-win-compile.ps1).
cmake .. "-DCMAKE_CXX_FLAGS_RELEASE=/Od /DNDEBUG"
cmake --build . --config Release -j $NPROC
# TODO put this back in, see issue# 5163
# Release/example.exe

View File

@ -129,6 +129,21 @@ AC_ARG_ENABLE([dev-gcov],
AC_SUBST(CFG_WITH_DEV_GCOV)
AC_MSG_RESULT($CFG_WITH_DEV_GCOV)
# Flag to reduce the debug info in the debug executable to minimize its size
AC_MSG_CHECKING(whether to reduce debug info in the debug executable)
AC_ARG_ENABLE([light-debug],
[AS_HELP_STRING([--enable-light-debug],
[Reduce the amount of debug information in the debug
Verilator executable to minimize its size. Enable
slight optimization. Enough for backtraces only.])],
[case "${enableval}" in
yes) CFG_WITH_LIGHT_DEBUG=yes ;;
no) CFG_WITH_LIGHT_DEBUG=no ;;
*) AC_MSG_ERROR([bad value '${enableval}' for --enable-light-debug]) ;;
esac],
CFG_WITH_LIGHT_DEBUG=no)
AC_MSG_RESULT($CFG_WITH_LIGHT_DEBUG)
# Special Substitutions - CFG_WITH_DEFENV
AC_MSG_CHECKING(whether to use hardcoded paths)
AC_ARG_ENABLE([defenv],
@ -518,15 +533,27 @@ _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_PARSER,-Wno-unused)
AC_SUBST(CFG_CXXFLAGS_PARSER)
# Flags for compiling the debug version of Verilator (in addition to above CFG_CXXFLAGS_SRC)
if test "$CFG_WITH_DEV_GCOV" = "no"; then # Do not optimize for the coverage build
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-Og)
if test "$CFG_WITH_LIGHT_DEBUG" = "yes"; then
# Slight optimization and minimal compressed debug info. This is enough for
# --gdb/--gdbbt backtraces, but omits the bulk of the debug info, which
# significantly reduces object file sizes. For CI or release builds.
if test "$CFG_WITH_DEV_GCOV" = "no"; then # Do not optimize for the coverage build
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-Og)
fi
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb1)
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-fdebug-info-for-profiling) # For fully-qualified names with clang
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-gz)
else
# Full debug: no optimization, full debug info, for development.
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-O0)
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb)
fi
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb)
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-gz)
AC_SUBST(CFG_CXXFLAGS_DBG)
# Flags for linking the debug version of Verilator (in addition to above CFG_LDFLAGS_SRC)
_MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_DBG,-gz)
if test "$CFG_WITH_LIGHT_DEBUG" = "yes"; then
_MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_DBG,-gz)
fi
AC_SUBST(CFG_LDFLAGS_DBG)
# Flags for compiling the optimized version of Verilator (in addition to above CFG_CXXFLAGS_SRC)

View File

@ -33,4 +33,4 @@ sphinxcontrib-spelling==8.0.2
yamlfix==1.19.1
yapf==0.43.0
git+https://github.com/antmicro/astsee.git
git+https://github.com/antmicro/astsee.git@222480a8ec13b312809ea4acc08c81b4c0f4da2f

View File

@ -90,6 +90,58 @@ public:
explicit DefaultDisablePropagateVisitor(AstNetlist* nodep) { iterate(nodep); }
};
// Lower a sequence used as an event control ('@seq', IEEE 1800-2023 9.4.2.4) into a
// synthesized event fired by an internal 'cover sequence' on each end-of-match
class SeqEventLowerVisitor final : public VNVisitor {
// STATE
AstNodeModule* m_modp = nullptr; // Current module
V3UniqueNames m_names{"__VseqEvent"}; // Synthesized event names
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
m_modp = nodep;
iterateChildren(nodep);
}
void visit(AstSenItem* nodep) override {
AstFuncRef* const funcrefp = VN_CAST(nodep->sensp(), FuncRef);
if (funcrefp && VN_IS(funcrefp->taskp(), Sequence)) {
FileLine* const flp = nodep->fileline();
AstVar* const eventp = new AstVar{flp, VVarType::MODULETEMP, m_names.get(nodep),
m_modp->findBasicDType(VBasicDTypeKwd::EVENT)};
eventp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(eventp);
v3Global.setHasEvents();
funcrefp->unlinkFrBack();
nodep->sensp(new AstVarRef{flp, eventp, VAccess::READ});
const bool automaticActual = funcrefp->exists([](const AstNodeVarRef* refp) {
return refp->varp() && refp->varp()->lifetime().isAutomatic();
});
if (automaticActual) {
nodep->v3error("Arguments to a sequence used as an event control must be"
" static (IEEE 1800-2023 9.4.2.4)");
VN_AS(funcrefp->taskp(), Sequence)->isReferenced(false);
VL_DO_DANGLING(pushDeletep(funcrefp), funcrefp);
return;
}
AstFireEvent* const firep
= new AstFireEvent{flp, new AstVarRef{flp, eventp, VAccess::WRITE}, false};
AstCover* const coverp
= new AstCover{flp, new AstPropSpec{flp, nullptr, nullptr, funcrefp}, firep,
VAssertType::CONCURRENT};
coverp->isCoverSeq(true);
coverp->isSeqEvent(true);
m_modp->addStmtsp(coverp);
return;
}
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
explicit SeqEventLowerVisitor(AstNetlist* nodep) { iterate(nodep); }
};
} // namespace
void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) {
@ -97,6 +149,11 @@ void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) {
{ DefaultDisablePropagateVisitor{nodep}; }
}
void V3AssertCommon::lowerSequenceEvents(AstNetlist* nodep) {
{ SeqEventLowerVisitor{nodep}; }
V3Global::dumpCheckGlobalTree("assertseqevent", 0, dumpTreeEitherLevel() >= 3);
}
//######################################################################
// AssertDeFutureVisitor
// If any AstFuture, then move all non-future varrefs to be one cycle behind,
@ -546,14 +603,19 @@ class AssertVisitor final : public VNVisitor {
bool selfDestruct = false;
bool passspGated = false;
if (const AstCover* const snodep = VN_CAST(nodep, Cover)) {
const AstCover* const coverp = VN_CAST(nodep, Cover);
// A sequence event control is not an assertion directive; no assertion control
const bool seqEvent = coverp && coverp->isSeqEvent();
if (coverp) {
++m_statCover;
if (!v3Global.opt.coverageUser()) {
if (seqEvent) {
// Keep the event-fire action, with no coverage bucket
} else if (!v3Global.opt.coverageUser()) {
selfDestruct = true;
} else {
// V3Coverage assigned us a bucket to increment.
AstCoverInc* const covincp = VN_AS(snodep->coverincsp(), CoverInc);
UASSERT_OBJ(covincp, snodep, "Missing AstCoverInc under assertion");
AstCoverInc* const covincp = VN_AS(coverp->coverincsp(), CoverInc);
UASSERT_OBJ(covincp, coverp, "Missing AstCoverInc under assertion");
covincp->unlinkFrBackWithNext(); // next() might have AstAssign for trace
if (message != "") covincp->declp()->comment(message);
if (passsp) {
@ -597,7 +659,8 @@ class AssertVisitor final : public VNVisitor {
FileLine* const flp = nodep->fileline();
bool passspAlreadyGated = false;
if (passsp && VN_IS(passsp, If)) passspAlreadyGated = VN_AS(passsp, If)->user1();
if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr)) {
if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr)
&& !seqEvent) {
passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(),
/*vacuous=*/false);
}
@ -607,7 +670,7 @@ class AssertVisitor final : public VNVisitor {
AstNode* bodysp = assertBody(nodep, propExprp, passsp, failsp);
if (disablep) bodysp = new AstIf{flp, new AstLogNot{flp, disablep}, bodysp};
// Add assertOn check last, for better combining
bodysp = newIfAssertOn(bodysp, nodep->directive(), nodep->userType());
if (!seqEvent) bodysp = newIfAssertOn(bodysp, nodep->directive(), nodep->userType());
if (sentreep) bodysp = new AstAlways{flp, VAlwaysKwd::ALWAYS, sentreep, bodysp};
if (passsp && !passsp->backp()) VL_DO_DANGLING(pushDeletep(passsp), passsp);
@ -1153,12 +1216,11 @@ class AssertVisitor final : public VNVisitor {
VL_RESTORER(m_modp);
VL_RESTORER(m_modPastNum);
VL_RESTORER(m_modStrobeNum);
VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp);
VL_RESTORER(m_finalp);
VL_RESTORER_CLEAR(m_modExpr2Sen2DelayedAlwaysp);
m_modp = nodep;
m_modPastNum = 0;
m_modStrobeNum = 0;
m_modExpr2Sen2DelayedAlwaysp.clear();
m_finalp = nullptr;
iterateChildren(nodep);
}

View File

@ -27,6 +27,7 @@
class V3AssertCommon final {
public:
static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED;
static void lowerSequenceEvents(AstNetlist* nodep) VL_MT_DISABLED;
};
class V3Assert final {

View File

@ -291,6 +291,8 @@ class SvaNfaBuilder final {
// not just the first. Builder builds parallel-branch (no first-match-wins)
// topology when true. Default false preserves cover_property semantics.
bool m_isCoverSeq = false;
// Unsupported endpoint topology must reject, not ignore, or the wait hangs
bool m_isSeqEvent = false;
struct RangeDelayRejectInfo final {
SvaStateVertex* startp = nullptr;
@ -298,6 +300,15 @@ class SvaNfaBuilder final {
int rhsLen = 0;
};
void warnEndpointUnsupported(FileLine* flp, const string& what) const {
if (m_isSeqEvent) {
flp->v3warn(E_UNSUPPORTED,
"Unsupported: sequence used as an event control with " << what);
} else {
flp->v3warn(COVERIGN, "Ignoring unsupported: cover sequence with " << what);
}
}
AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) {
if (m_throughoutStack.empty()) return baseCondp;
// AND all throughout conditions (supports nesting)
@ -465,8 +476,8 @@ class SvaNfaBuilder final {
AstVar* tryHoistSampled(AstNodeExpr* exprp, FileLine* flp, int cloneCount) {
constexpr int kHoistThreshold = 2;
if (cloneCount < kHoistThreshold) return nullptr;
AstVar* const tempVarp = new AstVar{flp, VVarType::MODULETEMP, m_propTempNames.get(exprp),
m_modp->findBitDType()};
AstVar* const tempVarp
= new AstVar{flp, VVarType::MODULETEMP, m_propTempNames.get(exprp), exprp->dtypep()};
m_modp->addStmtsp(tempVarp);
AstAssign* const assignp = new AstAssign{flp, new AstVarRef{flp, tempVarp, VAccess::WRITE},
sampled(exprp->cloneTreePure(false))};
@ -578,8 +589,7 @@ class SvaNfaBuilder final {
// overlapping ends and the nested-sequence merge collapses them, so
// reject those for a cover sequence rather than under-count.
if (m_isCoverSeq && (range > kChainLimit || VN_IS(rhsExprp, SExpr))) {
flp->v3warn(COVERIGN,
"Ignoring unsupported: cover sequence with this ranged cycle delay");
warnEndpointUnsupported(flp, "this ranged cycle delay");
outErrorEmitted = true;
return false;
}
@ -885,8 +895,7 @@ class SvaNfaBuilder final {
// terminal, so a cover sequence would under-count. Reject the ranged form
// (the single-count b[->N] has one end and is enumerated correctly).
if (m_isCoverSeq && hasMax && maxN > minN) {
flp->v3warn(COVERIGN,
"Ignoring unsupported: cover sequence with a ranged goto repetition");
warnEndpointUnsupported(flp, "a ranged goto repetition");
return BuildResult::failWithError();
}
@ -950,8 +959,7 @@ class SvaNfaBuilder final {
// than under-count. Plain boolean disjunction has one end per cycle and
// is handled by the OR-fold.
if (m_isCoverSeq && (lhs.termVertexp != entryVtxp || rhs.termVertexp != entryVtxp)) {
flp->v3warn(COVERIGN,
"Ignoring unsupported: cover sequence with a sequence operand of 'or'");
warnEndpointUnsupported(flp, "a sequence operand of 'or'");
return BuildResult::failWithError();
}
SvaStateVertex* const mergeVtxp = scopedCreateVertex();
@ -1446,11 +1454,12 @@ class SvaNfaBuilder final {
public:
SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames,
bool isCoverSeq = false)
bool isCoverSeq = false, bool isSeqEvent = false)
: m_graph{graph}
, m_modp{modp}
, m_propTempNames{propTempNames}
, m_isCoverSeq{isCoverSeq} {}
, m_isCoverSeq{isCoverSeq}
, m_isSeqEvent{isSeqEvent} {}
// Reset scope between antecedent and consequent: liveness must not leak.
void resetScope() {
@ -3054,11 +3063,17 @@ class AssertNfaVisitor final : public VNVisitor {
bool senTreeOwned = false; // True if we created senTreep locally
AstPropSpec* const propSpecp = VN_CAST(assertp->propp(), PropSpec);
UASSERT_OBJ(propSpecp, assertp, "Concurrent assertion must have PropSpec");
AstCover* const coverp = VN_CAST(assertp, Cover);
const bool isCover = coverp != nullptr;
const bool isCoverSeq = coverp && coverp->isCoverSeq();
// A sequence event control is not an assertion directive; no default
// disable iff, no assertion control
const bool isSeqEvent = coverp && coverp->isSeqEvent();
// Inherit module defaults (IEEE 14.12, 16.15) when assertion has none.
if (!propSpecp->sensesp() && m_defaultClockingp) {
propSpecp->sensesp(m_defaultClockingp->sensesp()->cloneTree(true));
}
if (!propSpecp->disablep() && m_defaultDisablep) {
if (!propSpecp->disablep() && m_defaultDisablep && !isSeqEvent) {
propSpecp->disablep(m_defaultDisablep->condp()->cloneTreePure(true));
}
if (!senTreep && propSpecp->sensesp()) {
@ -3070,12 +3085,9 @@ class AssertNfaVisitor final : public VNVisitor {
if (!senTreep) return;
FileLine* const flp = assertp->fileline();
const bool isCover = VN_IS(assertp, Cover);
AstCover* const coverp = VN_CAST(assertp, Cover);
const bool isCoverSeq = coverp && coverp->isCoverSeq();
SvaGraph graph;
SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq};
SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq, isSeqEvent};
const BuildResult result = buildAssertionGraph(builder, graph, seqBodyp, parts, flp);
if (result.valid()) wireMatchAndMidSources(graph, result, flp);
@ -3117,13 +3129,16 @@ class AssertNfaVisitor final : public VNVisitor {
std::vector<AstNodeExpr*> perMidSrcs;
AstNodeExpr* const alwaysTriggerp
= assertOnCond(flp, assertp->userType(), assertp->directive());
= isSeqEvent ? new AstConst{flp, AstConst::BitTrue{}}
: assertOnCond(flp, assertp->userType(), assertp->directive());
AstNodeExpr* const outputExprp = m_loweringp->lower(
flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover,
disableExprp ? disableExprp->cloneTreePure(false) : nullptr, negated,
needMatch ? &matchExprp : nullptr, disableCntVarp, snapshotVarp,
needPerSrcFail ? &requiredStepSrcs : nullptr, isCoverSeq ? &perMidSrcs : nullptr,
assertp->userType(), assertp->directive());
isSeqEvent ? VAssertType{VAssertType::INTERNAL} : assertp->userType(),
isSeqEvent ? VAssertDirectiveType{VAssertDirectiveType::INTERNAL}
: assertp->directive());
AstSenTree* const perSrcSenTreep
= (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr;

View File

@ -1466,7 +1466,9 @@ private:
iterateAndNextNull(nodep->sensesp());
if (m_senip && m_senip != nodep->sensesp())
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Only one PSL clock allowed per assertion");
if (!nodep->disablep() && m_defaultDisablep) {
const AstCover* const coverp = VN_CAST(nodep->backp(), Cover);
const bool seqEvent = coverp && coverp->isSeqEvent();
if (!nodep->disablep() && m_defaultDisablep && !seqEvent) {
nodep->disablep(m_defaultDisablep->condp()->cloneTreePure(true));
}
m_disablep = nodep->disablep();

View File

@ -1612,6 +1612,8 @@ class AstCover final : public AstNodeCoverOrAssert {
// @astgen op3 := coverincsp: List[AstNode] // Coverage node
bool m_isCoverSeq = false; // 'cover sequence' (IEEE 1800-2023 16.14.3): fires per
// end-of-match, not per property success
bool m_isSeqEvent = false; // Synthesized for a sequence used as an event control
// (IEEE 1800-2023 9.4.2.4)
public:
ASTGEN_MEMBERS_AstCover;
AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type,
@ -1622,6 +1624,8 @@ public:
void dumpJson(std::ostream& str) const override;
bool isCoverSeq() const { return m_isCoverSeq; }
void isCoverSeq(bool flag) { m_isCoverSeq = flag; }
bool isSeqEvent() const { return m_isSeqEvent; }
void isSeqEvent(bool flag) { m_isSeqEvent = flag; }
};
class AstRestrict final : public AstNodeCoverOrAssert {
public:

View File

@ -2241,9 +2241,11 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const {
void AstCover::dump(std::ostream& str) const {
this->AstNodeCoverOrAssert::dump(str);
if (isCoverSeq()) str << " [COVERSEQ]";
if (isSeqEvent()) str << " [SEQEVENT]";
}
void AstCover::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, isCoverSeq);
dumpJsonBoolFuncIf(str, isSeqEvent);
this->AstNodeCoverOrAssert::dumpJson(str);
}
void AstClocking::dump(std::ostream& str) const {

View File

@ -137,12 +137,9 @@ class BeginVisitor final : public VNVisitor {
UINFO(8, " rename to " << nodep->name());
m_statep->userMarkChanged(nodep);
}
VL_RESTORER(m_displayScope);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
m_displayScope = "";
m_namedScope = "";
m_unnamedScope = "";
VL_RESTORER_CLEAR(m_displayScope);
VL_RESTORER_CLEAR(m_namedScope);
VL_RESTORER_CLEAR(m_unnamedScope);
iterateChildren(nodep);
}
void visit(AstNodeProcedure* nodep) override {
@ -163,14 +160,13 @@ class BeginVisitor final : public VNVisitor {
// naming; so that any begin's inside the function will rename
// inside the function.
// Process children
VL_RESTORER(m_displayScope);
VL_RESTORER_COPY(m_displayScope);
VL_RESTORER(m_ftaskp);
VL_RESTORER(m_liftedp);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
VL_RESTORER_CLEAR(m_namedScope);
VL_RESTORER_CLEAR(m_unnamedScope);
m_displayScope = dot(m_displayScope, nodep->name());
m_namedScope = "";
m_unnamedScope = "";
m_ftaskp = nodep;
m_liftedp = nullptr;
iterateChildren(nodep);
@ -191,9 +187,9 @@ class BeginVisitor final : public VNVisitor {
void visit(AstGenBlock* nodep) override {
// GenBlocks were only useful in variable creation, change names and delete
UINFO(8, " " << nodep);
VL_RESTORER(m_displayScope);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
VL_RESTORER_COPY(m_displayScope);
VL_RESTORER_COPY(m_namedScope);
VL_RESTORER_COPY(m_unnamedScope);
UASSERT_OBJ(!m_keepBegins, nodep, "Should be able to eliminate all AstGenBlock");
dotNames(nodep->name(), nodep->fileline(), "__BEGIN__");
iterateAndNextNull(nodep->itemsp());
@ -227,9 +223,9 @@ class BeginVisitor final : public VNVisitor {
void visit(AstBegin* nodep) override {
// Begin blocks were only useful in variable creation, change names and delete
UINFO(8, " " << nodep);
VL_RESTORER(m_displayScope);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
VL_RESTORER_COPY(m_displayScope);
VL_RESTORER_COPY(m_namedScope);
VL_RESTORER_COPY(m_unnamedScope);
{
VL_RESTORER(m_keepBegins);
m_keepBegins = false;
@ -261,9 +257,9 @@ class BeginVisitor final : public VNVisitor {
void visit(AstNodeBlock* nodep) override {
// Begin/Fork blocks were only useful in variable creation, change names and delete
UINFO(8, " " << nodep);
VL_RESTORER(m_displayScope);
VL_RESTORER(m_namedScope);
VL_RESTORER(m_unnamedScope);
VL_RESTORER_COPY(m_displayScope);
VL_RESTORER_COPY(m_namedScope);
VL_RESTORER_COPY(m_unnamedScope);
{
VL_RESTORER(m_keepBegins);
m_keepBegins = VN_IS(nodep, Fork);

View File

@ -252,18 +252,15 @@ private:
void visit(AstScope* nodep) override {
VL_RESTORER(m_inScope);
m_inScope = true;
VL_RESTORER(m_cFuncNames);
m_cFuncNames.clear();
VL_RESTORER_CLEAR(m_cFuncNames);
processAndIterate(nodep);
}
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_cFuncNames);
m_cFuncNames.clear();
VL_RESTORER_CLEAR(m_cFuncNames);
processAndIterate(nodep);
}
void visit(AstNodeUOrStructDType* nodep) override {
VL_RESTORER(m_cFuncNames);
m_cFuncNames.clear();
VL_RESTORER_CLEAR(m_cFuncNames);
processAndIterate(nodep);
}
void visit(AstNodeVarRef* nodep) override {

View File

@ -114,7 +114,7 @@ class ClassVisitor final : public VNVisitor {
classScopep->aboveScopep(), classScopep->aboveCellp()};
packagep->addStmtsp(scopep);
// Iterate
VL_RESTORER(m_prefix);
VL_RESTORER_CLEAR(m_prefix);
VL_RESTORER(m_classPackagep);
VL_RESTORER(m_classScopep);
VL_RESTORER(m_packageScopep);
@ -129,7 +129,7 @@ class ClassVisitor final : public VNVisitor {
void visit(AstNodeModule* nodep) override {
// Visit for NodeModules that are not AstClass (AstClass is-a AstNodeModule)
// Classes are always under a Package (perhaps $unit) or a module
VL_RESTORER(m_prefix);
VL_RESTORER_CLEAR(m_prefix);
VL_RESTORER(m_modp);
m_modp = nodep;
m_prefix = nodep->name() + "__03a__03a"; // ::

View File

@ -279,8 +279,8 @@ class CoverageVisitor final : public VNVisitor {
const AstNodeModule* const origModp = m_modp;
VL_RESTORER(m_modp);
VL_RESTORER(m_state);
VL_RESTORER(m_exprTempNames);
VL_RESTORER(m_funcTemps);
VL_RESTORER_COPY(m_exprTempNames);
VL_RESTORER_COPY(m_funcTemps);
createHandle(nodep);
m_modp = nodep;
m_state.m_inModOff = false; // Haven't made top shell, so tops are real tops
@ -294,8 +294,8 @@ class CoverageVisitor final : public VNVisitor {
void visit(AstClass* nodep) override {
VL_RESTORER(m_modp);
VL_RESTORER(m_state);
VL_RESTORER(m_exprTempNames);
VL_RESTORER(m_funcTemps);
VL_RESTORER_COPY(m_exprTempNames);
VL_RESTORER_COPY(m_funcTemps);
createHandle(nodep);
m_modp = nodep;
// Covergroup declarations are not executable statements; suppress line/expr/toggle
@ -356,8 +356,8 @@ class CoverageVisitor final : public VNVisitor {
void visit(AstNodeFTask* nodep) override {
VL_RESTORER(m_ftaskp);
VL_RESTORER(m_exprTempNames);
VL_RESTORER(m_funcTemps);
VL_RESTORER_COPY(m_exprTempNames);
VL_RESTORER_COPY(m_funcTemps);
m_ftaskp = nodep;
if (!nodep->dpiImport()) iterateProcedure(nodep);
}
@ -762,7 +762,7 @@ class CoverageVisitor final : public VNVisitor {
}
void visit(AstGenBlock* nodep) override {
// Similar to AstBegin
VL_RESTORER(m_beginHier);
VL_RESTORER_COPY(m_beginHier);
if (nodep->name() != "") {
m_beginHier = m_beginHier + (m_beginHier != "" ? "__DOT__" : "") + nodep->name();
}
@ -776,7 +776,7 @@ class CoverageVisitor final : public VNVisitor {
// generate blocks; each point should get separate consideration.
// (Currently ignored for line coverage, since any generate iteration
// covers the code in that line.)
VL_RESTORER(m_beginHier);
VL_RESTORER_COPY(m_beginHier);
VL_RESTORER(m_inToggleOff);
VL_RESTORER(m_exprStmtsp);
m_exprStmtsp = nodep;
@ -874,7 +874,7 @@ class CoverageVisitor final : public VNVisitor {
UASSERT_OBJ(m_exprs.empty(), nodep, "unexpected expression coverage garbage");
VL_RESTORER(m_seeking);
VL_RESTORER(m_objective);
VL_RESTORER(m_exprs);
VL_RESTORER_CLEAR(m_exprs); // Already asserted above it's empty.
m_seeking = SEEKING;
m_objective = false;

View File

@ -1727,15 +1727,12 @@ class FunctionalCoverageVisitor final : public VNVisitor {
VL_RESTORER(m_covergroupp);
VL_RESTORER(m_sampleFuncp);
VL_RESTORER(m_constructorp);
VL_RESTORER(m_coverpoints);
VL_RESTORER(m_coverpointMap);
VL_RESTORER(m_coverCrosses);
VL_RESTORER_CLEAR(m_coverpoints);
VL_RESTORER_CLEAR(m_coverpointMap);
VL_RESTORER_CLEAR(m_coverCrosses);
m_covergroupp = nodep;
m_sampleFuncp = nullptr;
m_constructorp = nullptr;
m_coverpoints.clear();
m_coverpointMap.clear();
m_coverCrosses.clear();
// Extract and store the clocking event from AstCovergroup node
// The parser creates this node to preserve the event information

View File

@ -173,6 +173,7 @@ class TraceDriver final : public DfgVisitor {
uint64_t m_component = 0;
uint32_t m_lsb = 0; // LSB to extract from the currently visited Vertex
uint32_t m_msb = 0; // MSB to extract from the currently visited Vertex
std::vector<uint32_t> m_idxs; // Indices to extract from the currently visited Vertex
// Result of tracing the currently visited Vertex. Use SET_RESULT below!
DfgVertex* m_resp = nullptr;
DfgVertex* m_defaultp = nullptr; // When tracing a variable, this is its 'defaultp', if any
@ -228,8 +229,7 @@ class TraceDriver final : public DfgVisitor {
return varp;
}
// Continue tracing drivers of the given vertex, at the given LSB.
// Every visitor should call this to continue the traversal.
// Trace drivers of the given packed vertex, at the given bit range.
DfgVertex* trace(DfgVertex* const vtxp, const uint32_t msb, const uint32_t lsb) {
UASSERT_OBJ(vtxp->isPacked(), vtxp, "Can only trace packed type vertices");
UASSERT_OBJ(vtxp->size() > msb, vtxp, "Traced Vertex too narrow");
@ -271,6 +271,7 @@ class TraceDriver final : public DfgVisitor {
// Otherwise visit the vertex to trace it
VL_RESTORER(m_msb);
VL_RESTORER(m_lsb);
VL_RESTORER_CLEAR(m_idxs);
VL_RESTORER(m_resp);
m_msb = msb;
m_lsb = lsb;
@ -285,6 +286,40 @@ class TraceDriver final : public DfgVisitor {
return respr;
}
// Trace drivers of the given array vertex, at the current m_idxs, m_msb, m_lsb.
DfgVertex* traceSameIdx(DfgVertex* const vtxp) {
UASSERT_OBJ(vtxp->isArray(), vtxp, "Should be an array vertex");
UASSERT_OBJ(!m_idxs.empty(), vtxp, "Should have a pending index");
VL_RESTORER(m_resp);
m_resp = nullptr;
iterate(vtxp); // Resolve the element, the array visitors set 'm_resp'
UASSERT_OBJ(m_resp, vtxp, "Tracing driver failed for " << vtxp->typeName());
UASSERT_OBJ(m_resp->width() == (m_msb - m_lsb + 1), vtxp, "Wrong result width");
return m_resp;
}
// Trace drivers of the given array vertex, pushing the given index, at current m_msb, m_lsb.
DfgVertex* tracePushIdx(DfgVertex* const vtxp, const uint32_t idx) {
const size_t nIdxs = m_idxs.size();
m_idxs.push_back(idx);
DfgVertex* const resp = traceSameIdx(vtxp);
m_idxs.pop_back();
UASSERT_OBJ(m_idxs.size() == nIdxs, vtxp, "Index stack size mismatch");
return resp;
}
// Trace drivers of the given vertex, popping the innermost index, at current m_msb, m_lsb.
DfgVertex* tracePopIdx(DfgVertex* const vtxp) {
UASSERT_OBJ(!m_idxs.empty(), vtxp, "Should have a pending index");
const size_t nIdxs = m_idxs.size();
const uint32_t idx = m_idxs.back();
m_idxs.pop_back();
DfgVertex* const resp = m_idxs.empty() ? trace(vtxp, m_msb, m_lsb) : traceSameIdx(vtxp);
m_idxs.push_back(idx);
UASSERT_OBJ(m_idxs.size() == nIdxs, vtxp, "Index stack size mismatch");
return resp;
}
template <typename Vertex>
Vertex* traceBitwiseBinary(Vertex* vtxp) {
static_assert(std::is_base_of<DfgVertexBinary, Vertex>::value,
@ -388,25 +423,26 @@ class TraceDriver final : public DfgVisitor {
// Gather terms
std::vector<DfgVertex*> termps;
uint32_t lsb = m_lsb;
for (const Driver& driver : drivers) {
// Driver is below the searched LSB, move on
if (m_lsb > driver.m_msb) continue;
if (lsb > driver.m_msb) continue;
// Driver is above the searched MSB, done
if (driver.m_lsb > m_msb) break;
// Gap below this driver, trace default to fill it
if (driver.m_lsb > m_lsb) {
if (driver.m_lsb > lsb) {
UASSERT_OBJ(m_defaultp, vtxp, "Should have a default driver if needs tracing");
termps.emplace_back(trace(m_defaultp, driver.m_lsb - 1, m_lsb));
m_lsb = driver.m_lsb;
termps.emplace_back(trace(m_defaultp, driver.m_lsb - 1, lsb));
lsb = driver.m_lsb;
}
// Driver covers searched range, pick the needed/available bits
const uint32_t lim = std::min(m_msb, driver.m_msb);
termps.emplace_back(trace(driver.m_vtxp, lim - driver.m_lsb, m_lsb - driver.m_lsb));
m_lsb = lim + 1;
termps.emplace_back(trace(driver.m_vtxp, lim - driver.m_lsb, lsb - driver.m_lsb));
lsb = lim + 1;
}
if (m_msb >= m_lsb) {
if (m_msb >= lsb) {
UASSERT_OBJ(m_defaultp, vtxp, "Should have a default driver if needs tracing");
termps.emplace_back(trace(m_defaultp, m_msb, m_lsb));
termps.emplace_back(trace(m_defaultp, m_msb, lsb));
}
// The earlier cheks cover the case when either a whole driver or the default covers
@ -425,46 +461,58 @@ class TraceDriver final : public DfgVisitor {
SET_RESULT(resp);
}
void visit(DfgVarPacked* vtxp) override {
UASSERT_OBJ(!vtxp->isVolatile(), vtxp, "Should not trace through volatile VarPacked");
void visit(DfgSpliceArray* vtxp) override {
// Explicit per-element driver (a UnitArray wrapping the element value)
if (DfgVertex* const driverp = vtxp->driverAt(m_idxs.back())) {
// Consume this index, then trace the element value
SET_RESULT(tracePopIdx(driverp->as<DfgUnitArray>()->srcp()));
return;
}
// TODO: this is unreachable, as syntheis can't create it today.
// // Element not driven explicitly, so it comes from the default array. Keep the
// // index pending (the default is the whole array, indexed the same way) and
// // continue tracing it.
// UASSERT_OBJ(m_defaultp, vtxp, "Independent array element should have a driver or
// default"); SET RESULT(traceSameIdx(m_defaultp));
}
void visit(DfgVertexVar* vtxp) override {
UASSERT_OBJ(!vtxp->isVolatile(), vtxp, "Should not trace through volatile variable");
VL_RESTORER(m_defaultp);
m_defaultp = vtxp->defaultp();
SET_RESULT(trace(vtxp->srcp(), m_msb, m_lsb));
DfgVertex* const drvp = vtxp->srcp() ? vtxp->srcp() : m_defaultp;
UASSERT_OBJ(drvp, vtxp, "Should not have to trace undriven variable");
// Packed variable: trace the driver. Array variable: continue navigating it at
// the pending element (both at the same bit range).
SET_RESULT(m_idxs.empty() ? trace(drvp, m_msb, m_lsb) : traceSameIdx(drvp));
}
void visit(DfgArraySel* vtxp) override {
// From a variable
const DfgVarArray* varp = vtxp->fromp()->cast<DfgVarArray>();
if (!varp) return;
// If index is not constant, independence was proven only if the 'fromp' is
// independent, so no need to trace that
if (!vtxp->bitp()->is<DfgConst>()) {
DfgArraySel* const resp = make<DfgArraySel>(vtxp, vtxp->width());
resp->fromp(vtxp->fromp());
resp->bitp(trace(vtxp->bitp(), vtxp->bitp()->width() - 1, 0));
DfgSel* const selp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
selp->fromp(resp);
selp->lsb(m_lsb);
SET_RESULT(selp);
// If constant index, push it and trace the selected element through the array
// structure of 'fromp'. This handles arbitrarily nested (multi-dimensional)
// arrays, as each nested ArraySel pushes a further index.
if (const DfgConst* const idxp = vtxp->bitp()->cast<DfgConst>()) {
SET_RESULT(tracePushIdx(vtxp->fromp(), idxp->toU32()));
return;
}
// Trace the relevant driver based on the static index
const DfgConst* const idxp = vtxp->bitp()->as<DfgConst>();
UASSERT_OBJ(!varp->isVolatile(), vtxp, "Should not trace through volatile VarArray");
// Skip through intermediate variables
while (varp->srcp() && varp->srcp()->is<DfgVarArray>()) {
varp = varp->srcp()->as<DfgVarArray>();
UASSERT_OBJ(!varp->isVolatile(), vtxp, "Should not trace through volatile VarArray");
}
// Find driver
const DfgVertex* srcp = varp->srcp();
if (const DfgSpliceArray* const splicep = srcp->cast<DfgSpliceArray>()) {
srcp = splicep->driverAt(idxp->toSizeT());
}
// Trace the driver, which at this point must be a UnitArray
SET_RESULT(trace(srcp->as<DfgUnitArray>()->srcp(), m_msb, m_lsb));
// If index is not constant, independence was proven only if the 'fromp' is
// independent, so no need to trace that, just reference it with the traced
// index. This only happens at the packed ArraySel leaf.
UASSERT_OBJ(m_idxs.empty(), vtxp, "Non-constant index below an outer array index");
DfgArraySel* const resp = make<DfgArraySel>(vtxp, vtxp->width());
resp->fromp(vtxp->fromp());
resp->bitp(trace(vtxp->bitp(), vtxp->bitp()->width() - 1, 0));
DfgSel* const selp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
selp->fromp(resp);
selp->lsb(m_lsb);
SET_RESULT(selp);
}
void visit(DfgUnitArray* vtxp) override {
// Single-element array adapter, the pending index must be 0, unwrap the element
UASSERT_OBJ(m_idxs.back() == 0, vtxp, "UnitArray element index should be 0");
SET_RESULT(tracePopIdx(vtxp->srcp()));
}
void visit(DfgConcat* vtxp) override {
@ -710,7 +758,7 @@ class TraceDriver final : public DfgVisitor {
DfgSel* const selp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
selp->fromp(resp);
selp->lsb(m_lsb);
SET_RESULT(resp);
SET_RESULT(selp);
}
#undef SET_RESULT
@ -741,6 +789,142 @@ public:
}
};
// A bit mask for each bit in a value, which can be either packed or aggregate type
class BitMask final {
// TYPES
const DfgDataType& m_dtype;
union {
V3Number m_num; // The bits, if packed
std::vector<BitMask> m_sub; // The per element masks, if aggregate
};
public:
// CONSTRUCTOR
BitMask(FileLine* flp, const DfgDataType& dtype)
: m_dtype{dtype} {
UASSERT_OBJ(!m_dtype.isNull(), flp, "Expected non-null data type");
if (m_dtype.isPacked()) {
new (&m_num) V3Number{flp, static_cast<int>(m_dtype.size()), 0};
} else {
new (&m_sub) std::vector<BitMask>{};
m_sub.reserve(m_dtype.size());
for (uint32_t i = 0; i < m_dtype.size(); ++i)
m_sub.emplace_back(flp, m_dtype.elemDtype());
}
}
BitMask(const DfgVertex& vtx)
: BitMask{vtx.fileline(), vtx.dtype()} {}
BitMask(const BitMask& other)
: m_dtype{other.m_dtype} {
if (m_dtype.isPacked()) {
new (&m_num) V3Number{other.m_num};
} else {
new (&m_sub) std::vector<BitMask>{other.m_sub};
}
}
BitMask& operator=(const BitMask& other) {
if (this != &other) {
UASSERT(m_dtype == other.m_dtype, "Expected same data type");
if (other.m_dtype.isPacked()) {
m_num = other.m_num;
} else {
m_sub = other.m_sub;
}
}
return *this;
}
~BitMask() {
if (m_dtype.isPacked()) {
m_num.~V3Number();
} else {
m_sub.~vector<BitMask>();
}
}
// METHODS
V3Number& num() {
UASSERT(m_dtype.isPacked(), "Expected packed data type");
return m_num;
}
const V3Number& num() const {
UASSERT(m_dtype.isPacked(), "Expected packed data type");
return m_num;
}
std::vector<BitMask>& sub() {
UASSERT(m_dtype.isArray(), "Expected array data type");
return m_sub;
}
bool isZero() const {
if (m_dtype.isPacked()) return m_num.isEqZero();
for (const BitMask& sub : m_sub) {
if (!sub.isZero()) return false;
}
return true;
}
bool isOnes() const {
if (m_dtype.isPacked()) return m_num.isEqAllOnes();
for (const BitMask& sub : m_sub) {
if (!sub.isOnes()) return false;
}
return true;
}
void setZero() {
if (m_dtype.isPacked()) {
m_num.setAllBits0();
} else {
for (BitMask& sub : m_sub) sub.setZero();
}
}
void setOnes() {
if (m_dtype.isPacked()) {
m_num.setAllBits1();
} else {
for (BitMask& sub : m_sub) sub.setOnes();
}
}
bool operator==(const BitMask& other) const {
UASSERT(m_dtype == other.m_dtype, "Expected same data type");
if (m_dtype.isPacked()) return m_num.isCaseEq(other.m_num);
return m_sub == other.m_sub;
}
bool operator!=(const BitMask& other) const { return !(*this == other); }
// 'this' has a clear bit where 'that' has a set bit, that is,
// 'this' has a proper subset of bits set compared to 'that'.
bool isContractionOf(const BitMask& that) const {
UASSERT(m_dtype == that.m_dtype, "Expected same data type");
if (m_dtype.isPacked()) {
const size_t words = VL_WORDS_I(m_dtype.size());
for (size_t i = 0; i < words; ++i) {
if (~m_num.edataWord(i) & that.m_num.edataWord(i)) return true;
}
return false;
}
UASSERT(m_dtype.isArray(), "Expected array data type");
for (size_t i = 0; i < m_sub.size(); ++i) {
if (m_sub[i].isContractionOf(that.m_sub[i])) return true;
}
return false;
}
std::string toString() const {
if (m_dtype.isPacked()) return "0x" + m_num.displayed(m_num.fileline(), "%x");
std::string result;
result += "{";
for (const BitMask& sub : m_sub) {
if (&sub != &m_sub.front()) result += ", ";
result += sub.toString();
}
result += "}";
return result;
}
};
class IndependentBits final : public DfgVisitor {
// TYPES
struct VertexState final {
@ -751,7 +935,7 @@ class IndependentBits final : public DfgVisitor {
// STATE
const SccInfo& m_sccInfo; // The SccInfo instance
// Vertex to current bit mask map. The mask is set for the bits that are independent of the SCC
std::unordered_map<const DfgVertex*, V3Number> m_vtxp2Mask;
std::unordered_map<const DfgVertex*, BitMask> m_vtxp2Mask;
// Work list for the traversal (min-queue of vertex RPO numbers)
std::priority_queue<size_t, std::vector<size_t>, std::greater<size_t>> m_workQueue;
std::unordered_map<const DfgVertex*, VertexState> m_vtxp2State; // State of each vertex
@ -761,21 +945,13 @@ class IndependentBits final : public DfgVisitor {
#endif
// METHODS
// Predicate to check if a vertex should be analysed directly
bool handledDirectly(const DfgVertex& vtx) const {
if (!vtx.isPacked()) return false;
if (vtx.is<DfgSplicePacked>()) return false;
return true;
}
// Retrieve the mask for the given vertex (create it with value 0 if needed)
V3Number& mask(const DfgVertex& vtx) {
UASSERT_OBJ(handledDirectly(vtx), &vtx, "Vertex should not be handled direclty");
// Look up (or create) mask for 'vtxp'
BitMask& mask(const DfgVertex& vtx) {
// Look up (or create) mask for 'vtx'
return m_vtxp2Mask
.emplace(std::piecewise_construct, //
std::forward_as_tuple(&vtx), //
std::forward_as_tuple(vtx.fileline(), static_cast<int>(vtx.width()), 0)) //
std::forward_as_tuple(&vtx), std::forward_as_tuple(vtx))
.first->second;
}
@ -784,7 +960,7 @@ class IndependentBits final : public DfgVisitor {
// TODO: Use C++20 std::source_location instead of a macro
#ifdef VL_DEBUG
#define MASK(vtxp) \
([this](const DfgVertex& vtx) -> V3Number& { \
([this](const DfgVertex& vtx) -> BitMask& { \
if (VL_UNLIKELY(m_lineCoverageFile.is_open())) m_lineCoverageFile << __LINE__ << '\n'; \
return mask(vtx); \
}(*vtxp))
@ -810,13 +986,24 @@ class IndependentBits final : public DfgVisitor {
}
}
void propagateFromDriver(V3Number& m, const DfgVertex* srcp) {
void propagateFromDriver(BitMask& m, const DfgVertex* srcp) {
// If there is no driver, we are done
if (!srcp) return;
// If it is driven by a splice, we need to combine the masks of the drivers
if (const DfgSplicePacked* const splicep = srcp->cast<DfgSplicePacked>()) {
splicep->foreachDriver([&](const DfgVertex& src, uint32_t lo) {
m.opSelInto(MASK(&src), lo, src.width());
m.num().opSelInto(MASK(&src).num(), lo, src.width());
return false;
});
return;
}
if (const DfgSpliceArray* const splicep = srcp->cast<DfgSpliceArray>()) {
splicep->foreachDriver([&](const DfgVertex& src, uint32_t lo) {
if (const DfgUnitArray* const uap = src.cast<DfgUnitArray>()) {
m.sub().at(lo) = MASK(uap->srcp());
} else {
// m.sub().at(lo) = Can't happen MASK(&src);
}
return false;
});
return;
@ -827,15 +1014,15 @@ class IndependentBits final : public DfgVisitor {
// VISITORS
void visit(DfgVertex* vtxp) override { // LCOV_EXCL_START
UASSERT_OBJ(handledDirectly(*vtxp), vtxp, "Vertex should be handled direclty");
UINFO(9, "IndependentBits - Unhandled vertex type: " << vtxp->typeName());
// Conservative assumption about all bits being dependent prevails
} // LCOV_EXCL_STOP
void visit(DfgVarPacked* vtxp) override {
void visit(DfgVertexVar* vtxp) override {
// We cannot trace through a volatile variable, so pretend all bits are dependent
if (vtxp->isVolatile()) return;
V3Number& m = MASK(vtxp);
BitMask& m = MASK(vtxp);
DfgVertex* const srcp = vtxp->srcp();
DfgVertex* const defaultp = vtxp->defaultp();
// If there is a default driver, we start from that
@ -844,81 +1031,62 @@ class IndependentBits final : public DfgVisitor {
propagateFromDriver(m, srcp);
}
void visit(DfgArraySel* vtxp) override {
// From a variable
const DfgVarArray* varp = vtxp->fromp()->cast<DfgVarArray>();
if (!varp) return;
void visit(DfgVertexSplice* vtxp) override {
propagateFromDriver(MASK(vtxp), vtxp); // Needed to continue traversal
}
// If index is not constant, independent only if the variable index
// is indenpendent and the array is independent. We don't track arrays,
// so we will assume an array is only independent if it has no drivers
// in the graph. TODO: could check all drivers.
if (!vtxp->bitp()->is<DfgConst>()) {
if (MASK(vtxp->bitp()).isEqAllOnes() && !varp->srcp() && !varp->defaultp()) {
MASK(vtxp).setAllBits1();
}
void visit(DfgUnitArray* vtxp) override { MASK(vtxp).sub().at(0) = MASK(vtxp->srcp()); }
void visit(DfgArraySel* vtxp) override {
DfgVertex* const fromp = vtxp->fromp();
// If constant index, copy mask of relevant element
if (const DfgConst* const idxp = vtxp->bitp()->cast<DfgConst>()) {
MASK(vtxp) = MASK(vtxp->fromp()).sub().at(idxp->toSizeT());
return;
}
// Trace the relevant driver based on the static index
const DfgConst* const idxp = vtxp->bitp()->as<DfgConst>();
// We cannot trace through a volatile variable, so pretend all bits are dependent
if (varp->isVolatile()) return;
// Skip through intermediate variables
while (varp->srcp() && varp->srcp()->is<DfgVarArray>()) {
varp = varp->srcp()->as<DfgVarArray>();
if (varp->isVolatile()) return;
}
// Find driver
const DfgVertex* srcp = varp->srcp();
if (!srcp) return;
if (const DfgSpliceArray* const splicep = srcp->cast<DfgSpliceArray>()) {
srcp = splicep->driverAt(idxp->toSizeT());
if (!srcp) return;
}
const DfgUnitArray* uap = srcp->cast<DfgUnitArray>();
if (!uap) return;
srcp = uap->srcp();
// Propagate from driver
propagateFromDriver(MASK(vtxp), srcp);
// If index is not constant, independent only if the index is indenpendent, and the array
// is independent. TODO: could relax by '&' reducing, not sure if worth it.
if (MASK(vtxp->bitp()).isOnes() && MASK(fromp).isOnes()) MASK(vtxp).setOnes();
}
void visit(DfgConcat* vtxp) override {
const DfgVertex* const rhsp = vtxp->rhsp();
const DfgVertex* const lhsp = vtxp->lhsp();
V3Number& m = MASK(vtxp);
m.opSelInto(MASK(rhsp), 0, rhsp->width());
m.opSelInto(MASK(lhsp), rhsp->width(), lhsp->width());
V3Number& m = MASK(vtxp).num();
m.opSelInto(MASK(rhsp).num(), 0, rhsp->width());
m.opSelInto(MASK(lhsp).num(), rhsp->width(), lhsp->width());
}
void visit(DfgRep* vtxp) override {
const uint32_t count = vtxp->count();
const DfgVertex* const srcp = vtxp->srcp();
const uint32_t sWidth = srcp->width();
V3Number& vMask = MASK(vtxp);
V3Number& sMask = MASK(srcp);
V3Number& vMask = MASK(vtxp).num();
V3Number& sMask = MASK(srcp).num();
for (uint32_t i = 0; i < count; ++i) vMask.opSelInto(sMask, i * sWidth, sWidth);
}
void visit(DfgSel* vtxp) override {
const uint32_t lsb = vtxp->lsb();
const uint32_t msb = lsb + vtxp->width() - 1;
MASK(vtxp).opSel(MASK(vtxp->fromp()), msb, lsb);
MASK(vtxp).num().opSel(MASK(vtxp->fromp()).num(), msb, lsb);
}
void visit(DfgExtend* vtxp) override {
const DfgVertex* const srcp = vtxp->srcp();
const uint32_t sWidth = srcp->width();
V3Number& s = MASK(srcp);
V3Number& m = MASK(vtxp);
V3Number& s = MASK(srcp).num();
V3Number& m = MASK(vtxp).num();
m.opSelInto(s, 0, sWidth);
m.opSetRange(sWidth, vtxp->width() - sWidth, '1');
}
void visit(DfgExtendS* vtxp) override {
const DfgVertex* const srcp = vtxp->srcp();
const uint32_t sWidth = srcp->width();
V3Number& s = MASK(srcp);
V3Number& m = MASK(vtxp);
V3Number& s = MASK(srcp).num();
V3Number& m = MASK(vtxp).num();
m.opSelInto(s, 0, sWidth);
m.opSetRange(sWidth, vtxp->width() - sWidth, s.bitIs0(sWidth - 1) ? '0' : '1');
}
@ -928,71 +1096,62 @@ class IndependentBits final : public DfgVisitor {
}
void visit(DfgAnd* vtxp) override { //
MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp()));
MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num());
}
void visit(DfgOr* vtxp) override { //
MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp()));
MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num());
}
void visit(DfgXor* vtxp) override { //
MASK(vtxp).opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp()));
MASK(vtxp).num().opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num());
}
void visit(DfgAdd* vtxp) override {
V3Number& m = MASK(vtxp);
m.opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp()));
V3Number& m = MASK(vtxp).num();
m.opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num());
floodTowardsMsb(m);
}
void visit(DfgSub* vtxp) override { // Same as Add: 2's complement (a - b) == (a + ~b + 1)
V3Number& m = MASK(vtxp);
m.opAnd(MASK(vtxp->lhsp()), MASK(vtxp->rhsp()));
V3Number& m = MASK(vtxp).num();
m.opAnd(MASK(vtxp->lhsp()).num(), MASK(vtxp->rhsp()).num());
floodTowardsMsb(m);
}
void visit(DfgRedAnd* vtxp) override { //
if (MASK(vtxp->lhsp()).isEqAllOnes()) { //
MASK(vtxp).setAllBits1();
}
void visit(DfgRedAnd* vtxp) override {
const bool independent = MASK(vtxp->lhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgRedOr* vtxp) override { //
if (MASK(vtxp->lhsp()).isEqAllOnes()) { //
MASK(vtxp).setAllBits1();
}
void visit(DfgRedOr* vtxp) override {
const bool independent = MASK(vtxp->lhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgRedXor* vtxp) override { //
if (MASK(vtxp->lhsp()).isEqAllOnes()) { //
MASK(vtxp).setAllBits1();
}
void visit(DfgRedXor* vtxp) override {
const bool independent = MASK(vtxp->lhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgEq* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgNeq* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgLt* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgLte* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgGt* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgGte* vtxp) override {
const bool independent
= MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->rhsp()).isEqAllOnes();
MASK(vtxp).setBit(0, independent ? '1' : '0');
const bool independent = MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->rhsp()).isOnes();
if (independent) MASK(vtxp).setOnes();
}
void visit(DfgShiftRS* vtxp) override {
@ -1004,14 +1163,10 @@ class IndependentBits final : public DfgVisitor {
if (DfgConst* const rConstp = rhsp->cast<DfgConst>()) {
const uint32_t shiftAmount = rConstp->toU32();
if (shiftAmount >= width) {
if (MASK(lhsp).bitIs0(width - 1)) {
MASK(vtxp).setAllBits0();
} else {
MASK(vtxp).setAllBits1();
}
if (MASK(lhsp).num().bitIs1(width - 1)) { MASK(vtxp).setOnes(); }
} else {
V3Number& m = MASK(vtxp);
m.opShiftRS(MASK(lhsp), rConstp->num(), width);
V3Number& m = MASK(vtxp).num();
m.opShiftRS(MASK(lhsp).num(), rConstp->num(), width);
m.opSetRange(width - shiftAmount, shiftAmount, '1');
}
return;
@ -1020,9 +1175,9 @@ class IndependentBits final : public DfgVisitor {
// Otherwise, as the shift amount is non-negative, all independent
// and consecutive top bits in the lhs yield an independent result
// if the shift amount is independent.
if (MASK(rhsp).isEqAllOnes()) {
V3Number& m = MASK(vtxp);
m = MASK(lhsp);
if (MASK(rhsp).isOnes()) {
V3Number& m = MASK(vtxp).num();
m = MASK(lhsp).num();
floodTowardsLsb(m);
}
}
@ -1036,10 +1191,10 @@ class IndependentBits final : public DfgVisitor {
if (DfgConst* const rConstp = rhsp->cast<DfgConst>()) {
const uint32_t shiftAmount = rConstp->toU32();
if (shiftAmount >= width) {
MASK(vtxp).setAllBits1();
MASK(vtxp).setOnes();
} else {
V3Number& m = MASK(vtxp);
m.opShiftR(MASK(lhsp), rConstp->num());
V3Number& m = MASK(vtxp).num();
m.opShiftR(MASK(lhsp).num(), rConstp->num());
m.opSetRange(width - shiftAmount, shiftAmount, '1');
}
return;
@ -1048,9 +1203,9 @@ class IndependentBits final : public DfgVisitor {
// Otherwise, as the shift amount is non-negative, all independent
// and consecutive top bits in the lhs yield an independent result
// if the shift amount is independent.
if (MASK(rhsp).isEqAllOnes()) {
V3Number& m = MASK(vtxp);
m = MASK(lhsp);
if (MASK(rhsp).isOnes()) {
V3Number& m = MASK(vtxp).num();
m = MASK(lhsp).num();
floodTowardsLsb(m);
}
}
@ -1064,10 +1219,10 @@ class IndependentBits final : public DfgVisitor {
if (DfgConst* const rConstp = rhsp->cast<DfgConst>()) {
const uint32_t shiftAmount = rConstp->toU32();
if (shiftAmount >= width) {
MASK(vtxp).setAllBits1();
MASK(vtxp).setOnes();
} else {
V3Number& m = MASK(vtxp);
m.opShiftL(MASK(lhsp), rConstp->num());
V3Number& m = MASK(vtxp).num();
m.opShiftL(MASK(lhsp).num(), rConstp->num());
m.opSetRange(0, shiftAmount, '1');
}
return;
@ -1076,23 +1231,21 @@ class IndependentBits final : public DfgVisitor {
// Otherwise, as the shift amount is non-negative, all independent
// and consecutive bottom bits in the lhs yield an independent result
// if the shift amount is independent.
if (MASK(rhsp).isEqAllOnes()) {
V3Number& m = MASK(vtxp);
m = MASK(lhsp);
if (MASK(rhsp).isOnes()) {
V3Number& m = MASK(vtxp).num();
m = MASK(lhsp).num();
floodTowardsMsb(m);
}
}
void visit(DfgCond* vtxp) override {
if (MASK(vtxp->condp()).isEqAllOnes()) {
MASK(vtxp).opAnd(MASK(vtxp->thenp()), MASK(vtxp->elsep()));
if (MASK(vtxp->condp()).isOnes()) {
MASK(vtxp).num().opAnd(MASK(vtxp->thenp()).num(), MASK(vtxp->elsep()).num());
}
}
void visit(DfgMatchMasked* vtxp) override {
if (MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->matchp()).isEqAllOnes()) { //
MASK(vtxp).setAllBits1();
}
if (MASK(vtxp->lhsp()).isOnes() && MASK(vtxp->matchp()).isOnes()) { MASK(vtxp).setOnes(); }
}
#undef MASK
@ -1102,11 +1255,6 @@ class IndependentBits final : public DfgVisitor {
vtx.foreachSink([&](DfgVertex& sink) {
// Ignore if sink is not part of an SCC, already has all bits marked independent
if (!m_sccInfo.get(sink)) return false;
// If a vertex is not handled directly, recursively enqueue its sinks instead
if (!handledDirectly(sink)) {
enqueueSinks(sink);
return false;
}
// Otherwise just enqueue it
VertexState& state = m_vtxp2State.at(&sink);
if (!state.m_isOnWorkList) {
@ -1169,18 +1317,15 @@ class IndependentBits final : public DfgVisitor {
// always assign constants bits, which are always independent (eg Extend/Shift)
// Enqueue sinks of all SCC vertices that have at least one independent bit
for (DfgVertex* const vtxp : rpoEnumeration) {
if (!handledDirectly(*vtxp)) continue;
if (m_sccInfo.get(*vtxp)) continue;
mask(*vtxp).setAllBits1();
mask(*vtxp).setOnes();
}
for (DfgVertex* const vtxp : rpoEnumeration) {
if (!handledDirectly(*vtxp)) continue;
if (!m_sccInfo.get(*vtxp)) continue;
iterate(vtxp);
UINFO(9, "Initial independent bits of "
<< vtxp << " " << vtxp->typeName()
<< " are: " << mask(*vtxp).displayed(vtxp->fileline(), "%b"));
if (!mask(*vtxp).isEqZero()) enqueueSinks(*vtxp);
UINFO(9, "Initial independent bits of " << vtxp << " " << vtxp->typeName()
<< " are: " << mask(*vtxp).toString());
if (!mask(*vtxp).isZero()) enqueueSinks(*vtxp);
}
// Propagate independent bits until no more changes are made
@ -1191,13 +1336,11 @@ class IndependentBits final : public DfgVisitor {
m_vtxp2State.at(currp).m_isOnWorkList = false;
// Should not enqueue vertices that are not in an SCC
UASSERT_OBJ(m_sccInfo.get(*currp), currp, "Vertex should be in an SCC");
// Should only enqueue packed vertices
UASSERT_OBJ(handledDirectly(*currp), currp, "Vertex should be handled directly");
// Grab current mask of item
const V3Number& currMask = mask(*currp);
const BitMask& currMask = mask(*currp);
// Remember current mask so we can check if it changed
const V3Number prevMask = currMask;
const BitMask prevMask = currMask;
UINFO(9, "Analyzing independent bits of " << currp << " " << currp->typeName());
@ -1205,19 +1348,16 @@ class IndependentBits final : public DfgVisitor {
iterate(currp);
// If mask changed, enqueue sinks
if (!prevMask.isCaseEq(currMask)) {
if (prevMask != currMask) {
UINFO(9, "Independent bits of " //
<< currp << " " << currp->typeName() << " changed" //
<< "\n form: " << prevMask.displayed(currp->fileline(), "%b")
<< "\n to: " << currMask.displayed(currp->fileline(), "%b"));
<< "\n form: " << prevMask.toString()
<< "\n to: " << currMask.toString());
enqueueSinks(*currp);
// Check the mask only ever expands (no bit goes 1 -> 0)
if (VL_UNLIKELY(v3Global.opt.debugCheck())) {
V3Number notCurr{currMask};
notCurr.opNot(currMask);
V3Number prevAndNotCurr{currMask};
prevAndNotCurr.opAnd(prevMask, notCurr);
UASSERT_OBJ(prevAndNotCurr.isEqZero(), currp, "Mask should only expand");
UASSERT_OBJ(!currMask.isContractionOf(prevMask), currp,
"Mask should only expand");
}
}
}
@ -1228,8 +1368,8 @@ public:
// returns a map from vertices to a bit mask, where a bit in the mask is
// set if the corresponding bit in that vertex is known to be independent
// of the values of vertices in the same SCC as the vertex resides in.
static std::unordered_map<const DfgVertex*, V3Number> apply(DfgGraph& dfg,
const SccInfo& sccInfo) {
static std::unordered_map<const DfgVertex*, BitMask> apply(DfgGraph& dfg,
const SccInfo& sccInfo) {
return std::move(IndependentBits{dfg, sccInfo}.m_vtxp2Mask);
}
};
@ -1239,21 +1379,21 @@ class FixUp final {
SccInfo& m_sccInfo; // The SccInfo instance
TraceDriver m_traceDriver{m_dfg, m_sccInfo};
// The independent bits map
const std::unordered_map<const DfgVertex*, V3Number> m_independentBits
const std::unordered_map<const DfgVertex*, BitMask> m_independentBits
= IndependentBits::apply(m_dfg, m_sccInfo);
size_t m_nImprovements = 0; // Number of improvements mde
size_t m_nImprovements = 0; // Number of improvements made
// Returns a bitmask set if that bit of 'vtx' is used (has a sink)
static V3Number computeUsedBits(DfgVertex& vtx) {
V3Number result{vtx.fileline(), static_cast<int>(vtx.width()), 0};
static BitMask computeUsedBits(DfgVertex& vtx) {
BitMask result{vtx};
vtx.foreachSink([&result](DfgVertex& sink) {
// If used via a Sel, mark the selected bits used
if (const DfgSel* const selp = sink.cast<DfgSel>()) {
result.opSetRange(selp->lsb(), selp->width(), '1');
result.num().opSetRange(selp->lsb(), selp->width(), '1');
return false;
}
// Used without a Sel, so all bits are used
result.setAllBits1();
result.setOnes();
return true;
});
return result;
@ -1306,20 +1446,18 @@ class FixUp final {
UASSERT_OBJ(!vtx.is<DfgSplicePacked>(), &vtx, "Should not be a splice");
// Get which bits of 'vtxp' are independent of the SCCs
const V3Number& indpBits = m_independentBits.at(&vtx);
UINFO(9, "Independent bits of '" << debugStr(vtx) << "' are "
<< indpBits.displayed(vtx.fileline(), "%b"));
const BitMask& indpBits = m_independentBits.at(&vtx);
UINFO(9, "Independent bits of '" << debugStr(vtx) << "' are " << indpBits.toString());
// Can't do anything if all bits are dependent
if (indpBits.isEqZero()) return;
if (indpBits.isZero()) return;
// Figure out which bits of 'vtxp' are used
const V3Number usedBits = computeUsedBits(vtx);
UINFO(9, "Used bits of '" << debugStr(vtx) << "' are "
<< usedBits.displayed(vtx.fileline(), "%b"));
const BitMask usedBits = computeUsedBits(vtx);
UINFO(9, "Used bits of '" << debugStr(vtx) << "' are " << usedBits.toString());
// Nothing to do if no used bits are independen (all used bits are dependent)
V3Number usedAndIndependent{vtx.fileline(), static_cast<int>(vtx.width()), 0};
usedAndIndependent.opAnd(usedBits, indpBits);
usedAndIndependent.opAnd(usedBits.num(), indpBits.num());
if (usedAndIndependent.isEqZero()) return;
// We are computing the terms to concatenate and replace 'vtxp' with
@ -1328,14 +1466,14 @@ class FixUp final {
// Iterate through consecutive used/unused ranges
FileLine* const flp = vtx.fileline();
const uint32_t width = vtx.width();
bool isUsed = usedBits.bitIs1(0); // Is current range used
bool isUsed = usedBits.num().bitIs1(0); // Is current range used
uint32_t lsb = 0; // LSB of current range
for (uint32_t msb = 0; msb < width; ++msb) {
const bool endRange = msb == width - 1 || isUsed != usedBits.bitIs1(msb + 1);
const bool endRange = msb == width - 1 || isUsed != usedBits.num().bitIs1(msb + 1);
if (!endRange) continue;
if (isUsed) {
// The range is used, compute the replacement terms
gatherTerms(termps, vtx, indpBits, msb, lsb);
gatherTerms(termps, vtx, indpBits.num(), msb, lsb);
} else {
// The range is not used, just use constant 0 as a placeholder
DfgConst* const constp = new DfgConst{m_dfg, flp, msb - lsb + 1, 0};

View File

@ -78,7 +78,13 @@ void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) {
&& !vVtxp->hasExtWrRefs() //
&& !vVtxp->hasModWrRefs();
VL_DO_DANGLING(vVtxp->unlinkDelete(dfg), vVtxp);
if (srcp) VL_DO_DANGLING(srcp->unlinkDelete(dfg), srcp);
if (srcp) {
srcp->foreachSource([&](DfgVertex& src) {
src.as<DfgLogic>()->setDrivesUnusedVars();
return false;
});
VL_DO_DANGLING(srcp->unlinkDelete(dfg), srcp);
}
if (delAst) {
VL_DO_DANGLING(vscp->unlinkFrBack()->deleteTree(), vscp);
++ctx.m_varsDeleted;

View File

@ -2005,6 +2005,11 @@ static void dfgSelectLogicForSynthesis(DfgGraph& dfg) {
for (DfgVertex& vtx : dfg.opVertices()) {
DfgLogic* const logicp = vtx.cast<DfgLogic>();
if (!logicp) continue;
// If drives an unused variable, synthesize it so the partial logic can be removed
if (logicp->drivesUnusedVars()) {
worklist.push_front(*logicp);
continue;
}
// Blocks corresponding to continuous assignments
if (logicp->nodep()->keyword() == VAlwaysKwd::CONT_ASSIGN) {
worklist.push_front(*logicp);
@ -2016,10 +2021,8 @@ static void dfgSelectLogicForSynthesis(DfgGraph& dfg) {
worklist.push_front(*logicp);
continue;
}
// Simple blocks driving exactly 1 variable, e.g if (rst) a = b else a = c;
if (!logicp->hasMultipleSinks() && cfg.nBlocks() <= 4 && cfg.nEdges() <= 4) {
worklist.push_front(*logicp);
}
// Blocks driving exactly 1 variable
if (!logicp->hasMultipleSinks()) worklist.push_front(*logicp);
}
// Now expand to cover all logic driving the same set of variables and mark

View File

@ -512,6 +512,7 @@ class DfgLogic final : public DfgVertexVariadic {
AstScope* const m_scopep; // The AstScope m_nodep is under, iff scoped
const std::unique_ptr<CfgGraph> m_cfgp;
std::vector<DfgVertex*> m_synth; // Vertices this logic was synthesized into
bool m_drivesUnusedVars = false; // Logic drives unused variables
bool m_selectedForSynthesis = false; // Logic selected for synthesis
bool m_nonSynthesizable = false; // Logic is not synthesizeable (by DfgSynthesis)
bool m_reverted = false; // Logic was synthesized (in part if non-synthesizable) then reverted
@ -538,6 +539,8 @@ public:
const CfgGraph& cfg() const { return *m_cfgp; }
std::vector<DfgVertex*>& synth() { return m_synth; }
const std::vector<DfgVertex*>& synth() const { return m_synth; }
bool drivesUnusedVars() const { return m_drivesUnusedVars; }
void setDrivesUnusedVars() { m_drivesUnusedVars = true; }
bool selectedForSynthesis() const { return m_selectedForSynthesis; }
void setSelectedForSynthesis() { m_selectedForSynthesis = true; }
bool nonSynthesizable() const { return m_nonSynthesizable; }

View File

@ -1050,8 +1050,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
if (nodep->packed()) puts("packed ");
{
puts("{\n");
VL_RESTORER(m_packedps);
m_packedps.clear();
VL_RESTORER_CLEAR(m_packedps);
for (AstMemberDType* itemp = nodep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) {
iterateConst(itemp);

View File

@ -561,10 +561,9 @@ class ForkVisitor final : public VNVisitor {
// replace body with a call to that task. Returns true iff wrapped.
bool taskify(AstBegin* beginp) {
// Visit statement to gather variables (And recursively process)
VL_RESTORER(m_forkLocalsp);
VL_RESTORER_CLEAR(m_forkLocalsp);
VL_RESTORER(m_capturedVarsp);
VL_RESTORER(m_capturedArgsp);
m_forkLocalsp.clear();
m_capturedVarsp = nullptr;
m_capturedArgsp = nullptr;
iterate(beginp);

View File

@ -33,6 +33,7 @@
#include <string>
#include <thread>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
@ -44,27 +45,90 @@ class V3ThreadPool;
//======================================================================
// Restorer
/// Save a given variable's value on the stack, restoring it at end-of-scope.
// Object must be named, or it will not persist until end-of-scope.
// Constructor needs () or GCC 4.8 false warning.
#define VL_RESTORER(var) const VRestorer<typename std::decay_t<decltype(var)>> restorer_##var(var);
/// Get the copy of the variable previously saved by VL_RESTORER()
// For all flavours, 'var' must be a named scalar variable (simple identifier).
// Copy given variable's value on the stack, copy saved value back at end-of-scope.
// Only usable for trivially copyable types; use VL_RESTORER_COPY/VL_RESTORER_CLEAR for
// non-trivially copyable types, which are more efficient.
#define VL_RESTORER(var) \
const VRestorerTrivial<typename std::decay_t<decltype(var)>> restorer_##var(var);
// This is the equivalent of VL_RESTORER for non-trivially copyable types. Still more efficient
// than VL_RESTORER as uses move to restore.
#define VL_RESTORER_COPY(var) \
const VRestorerCopy<typename std::decay_t<decltype(var)>> restorer_##var(var);
// Swap 'var' with a no-args constructed object of the same type, effectively clearing it, then
// swap back at end-of-scope. No copying involved. Use this e.g. for std containers where they
// would be .clear()'d right after the VL_RESTORER instance.
#define VL_RESTORER_CLEAR(var) \
const VRestorerClear<typename std::decay_t<decltype(var)>> restorer_##var(var);
// Get const reference to the saved copy
#define VL_RESTORER_PREV(var) restorer_##var.saved()
// Object used by VL_RESTORER. This object must be an auto variable, not
// allocated on the heap or otherwise.
// Implementation of VL_RESTORER
template <typename T>
class VRestorer final {
class VRestorerTrivial final {
static_assert(std::is_trivially_copyable<T>::value,
"Use VL_RESTORER_{COPY,CLEAR} for non trivially copyable types");
T& m_ref; // Reference to object we're saving and restoring
const T m_saved; // Value saved, for later restore
public:
explicit VRestorer(T& permr)
: m_ref{permr}
, m_saved{permr} {}
~VRestorer() { m_ref = m_saved; }
explicit VRestorerTrivial(T& val)
: m_ref{val}
, m_saved{val} {}
~VRestorerTrivial() { m_ref = m_saved; }
VL_UNCOPYABLE(VRestorerTrivial);
// Must be stack allocated
void* operator new(size_t) = delete;
void operator delete(void*) = delete;
const T& saved() const { return m_saved; }
};
// Implementation of VL_RESTORER_COPY
template <typename T>
class VRestorerCopy final {
static_assert(!std::is_trivially_copyable<T>::value,
"Use VL_RESTORER for trivially copyable types");
T& m_ref; // Reference to object we're saving and restoring
T m_saved; // Value saved, for later restore
public:
explicit VRestorerCopy(T& val)
: m_ref{val}
, m_saved{val} {}
~VRestorerCopy() { m_ref = std::move(m_saved); }
VL_UNCOPYABLE(VRestorerCopy);
// Must be stack allocated
void* operator new(size_t) = delete;
void operator delete(void*) = delete;
const T& saved() const { return m_saved; }
};
// Implementation of VL_RESTORER_CLEAR.
template <typename T>
class VRestorerClear final {
static_assert(!std::is_trivially_copyable<T>::value,
"Use VL_RESTORER for trivially copyable types");
T& m_ref; // Reference to object we're saving and restoring
T m_saved{}; // Owns the swapped-out value; starts empty
public:
explicit VRestorerClear(T& val)
: m_ref{val} {
std::swap(m_ref, m_saved);
}
~VRestorerClear() { std::swap(m_ref, m_saved); }
VL_UNCOPYABLE(VRestorerClear);
// Must be stack allocated
void* operator new(size_t) = delete;
void operator delete(void*) = delete;
const T& saved() const { return m_saved; }
VL_UNCOPYABLE(VRestorer);
};
//######################################################################

View File

@ -331,12 +331,9 @@ class HierBlockUsageCollectVisitor final : public VNVisitorConst {
}
// This is a hierarchical block, gather parts
VL_RESTORER(m_params);
VL_RESTORER(m_typeParams);
VL_RESTORER(m_childrenp);
m_params.clear();
m_typeParams.clear();
m_childrenp.clear();
VL_RESTORER_CLEAR(m_params);
VL_RESTORER_CLEAR(m_typeParams);
VL_RESTORER_CLEAR(m_childrenp);
iterateChildrenConst(nodep);
// Create the graph vertex for this hier block
V3HierBlock* const blockp = new V3HierBlock{m_graphp, nodep, m_params, m_typeParams};

View File

@ -43,7 +43,7 @@ class InlineIntfRefVisitor final : public VNVisitor {
// VISITORS
void visit(AstNetlist* nodep) override { iterateChildren(nodep->topModulep()); }
void visit(AstCell* nodep) override {
VL_RESTORER(m_scope);
VL_RESTORER_COPY(m_scope);
if (m_scope.empty()) {
m_scope = nodep->name();
} else {

View File

@ -47,8 +47,8 @@ class LibMapVisitor final : public VNVisitor {
}
void visit(AstLibrary* nodep) override {
VL_RESTORER(m_lib);
VL_RESTORER(m_rel);
VL_RESTORER_CLEAR(m_lib);
VL_RESTORER_CLEAR(m_rel);
m_lib = nodep->name();
m_rel = V3Os::filenameDir(nodep->fileline()->filename());
iterateChildren(nodep);

View File

@ -157,11 +157,11 @@ class LinkConfigsVisitor final : public VNVisitor {
m_isDefault = true;
iterateAndNextNull(nodep->usep());
} else if (nodep->isCell()) {
VL_RESTORER(m_cell);
VL_RESTORER_CLEAR(m_cell);
m_cell = nodep->cellp()->name();
iterateAndNextNull(nodep->usep());
} else {
VL_RESTORER(m_hierInst);
VL_RESTORER_COPY(m_hierInst);
{
VL_RESTORER(m_dotp);
m_dotp = VN_AS(nodep->cellp(), Dot);

View File

@ -1259,7 +1259,7 @@ class LinkDotFindVisitor final : public VNVisitor {
const bool standalonePkg
= !m_modSymp && (m_statep->forPrearray() && VN_IS(nodep, Package));
const bool doit = (m_modSymp || standalonePkg);
VL_RESTORER(m_scope);
VL_RESTORER_COPY(m_scope);
VL_RESTORER(m_classOrPackagep);
VL_RESTORER(m_modSymp);
VL_RESTORER(m_curSymp);
@ -1335,7 +1335,7 @@ class LinkDotFindVisitor final : public VNVisitor {
void visit(AstClass* nodep) override { // FindVisitor::
UASSERT_OBJ(m_curSymp, nodep, "Class not under module/package/$unit");
UINFO(8, " " << nodep);
VL_RESTORER(m_scope);
VL_RESTORER_COPY(m_scope);
VL_RESTORER(m_classOrPackagep);
VL_RESTORER(m_modSymp);
VL_RESTORER(m_curSymp);
@ -1384,7 +1384,7 @@ class LinkDotFindVisitor final : public VNVisitor {
if (nodep->recursive() && m_inRecursion) return;
iterateChildren(nodep);
// Recurse in, preserving state
VL_RESTORER(m_scope);
VL_RESTORER_COPY(m_scope);
VL_RESTORER(m_modSymp);
VL_RESTORER(m_curSymp);
VL_RESTORER(m_paramNum);
@ -3584,7 +3584,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
void symIterateChildren(AstNode* nodep, VSymEnt* symp) {
// Iterate children, changing to given context, with restore to old context
VL_RESTORER(m_ds);
VL_RESTORER_COPY(m_ds);
VL_RESTORER(m_curSymp);
m_curSymp = symp;
m_ds.init(m_curSymp);
@ -3592,7 +3592,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
void symIterateNull(AstNode* nodep, VSymEnt* symp) {
// Iterate node, changing to given context, with restore to old context
VL_RESTORER(m_ds);
VL_RESTORER_COPY(m_ds);
VL_RESTORER(m_curSymp);
m_curSymp = symp;
m_ds.init(m_curSymp);
@ -3805,10 +3805,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
LINKDOT_VISIT_START();
UINFO(5, indent() << "visit " << nodep);
checkNoDot(nodep);
VL_RESTORER(m_usedPins);
VL_RESTORER(m_usedDefParamPins);
m_usedPins.clear();
m_usedDefParamPins.clear();
VL_RESTORER_CLEAR(m_usedPins);
VL_RESTORER_CLEAR(m_usedDefParamPins);
UASSERT_OBJ(nodep->modp(), nodep,
"Instance has unlinked module"); // V3LinkCell should have errored out
VL_RESTORER(m_cellp);
@ -3845,10 +3843,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
LINKDOT_VISIT_START();
UINFO(5, indent() << "visit " << nodep);
// Can be under dot if called as package::class and that class resolves, so no checkNoDot
VL_RESTORER(m_usedPins);
VL_RESTORER(m_usedDefParamPins);
m_usedPins.clear();
m_usedDefParamPins.clear();
VL_RESTORER_CLEAR(m_usedPins);
VL_RESTORER_CLEAR(m_usedDefParamPins);
UASSERT_OBJ(nodep->classp(), nodep, "ClassRef has unlinked class");
UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp() || V3Error::errorCount(), nodep,
"class reference parameter not removed by V3Param");
@ -4753,14 +4749,12 @@ class LinkDotResolveVisitor final : public VNVisitor {
LINKDOT_VISIT_START();
UINFO(8, indent() << "visit " << nodep);
UINFO(9, indent() << m_ds.ascii());
VL_RESTORER(m_usedPins);
VL_RESTORER(m_usedDefParamPins);
m_usedPins.clear();
m_usedDefParamPins.clear();
VL_RESTORER_CLEAR(m_usedPins);
VL_RESTORER_CLEAR(m_usedDefParamPins);
UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp(), nodep,
"class reference parameter not removed by V3Param");
{
VL_RESTORER(m_ds);
VL_RESTORER_COPY(m_ds);
VL_RESTORER(m_pinSymp);
if (!nodep->classOrPackageSkipp() && nodep->name() != "local::") {
@ -4834,7 +4828,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
}
VL_RESTORER(m_curSymp);
VL_RESTORER(m_ds);
VL_RESTORER_COPY(m_ds);
m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep);
iterateChildren(nodep);
}
@ -5054,7 +5048,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
// Created here so should already be resolved.
LINKDOT_VISIT_START();
UINFO(5, indent() << "visit " << nodep);
VL_RESTORER(m_ds);
VL_RESTORER_COPY(m_ds);
VL_RESTORER(m_randSymp);
VL_RESTORER(m_randMethodCallp);
{
@ -5509,7 +5503,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
checkNoDot(nodep);
{
VL_RESTORER(m_curSymp);
VL_RESTORER(m_ds);
VL_RESTORER_COPY(m_ds);
if (nodep->name() != "") {
m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep);
UINFO(5, indent() << "cur=se" << cvtToHex(m_curSymp));
@ -5524,7 +5518,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
checkNoDot(nodep);
{
VL_RESTORER(m_curSymp);
VL_RESTORER(m_ds);
VL_RESTORER_COPY(m_ds);
if (nodep->name() != "") {
m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep);
UINFO(5, indent() << "cur=se" << cvtToHex(m_curSymp));
@ -5643,7 +5637,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
checkNoDot(nodep);
VL_RESTORER(m_curSymp);
VL_RESTORER(m_currentWithp);
VL_RESTORER(m_restrictedNamesUsed);
VL_RESTORER_COPY(m_restrictedNamesUsed);
{
m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep);
m_currentWithp = nodep;
@ -5813,7 +5807,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
VL_RESTORER(m_curSymp);
VL_RESTORER(m_modSymp);
VL_RESTORER(m_modp);
VL_RESTORER(m_ifClassImpNames);
VL_RESTORER_COPY(m_ifClassImpNames);
VL_RESTORER(m_insideClassExtParam);
{
m_ds.init(m_curSymp);
@ -6169,7 +6163,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
void visit(AstDisable* nodep) override {
LINKDOT_VISIT_START();
checkNoDot(nodep);
VL_RESTORER(m_ds);
VL_RESTORER_COPY(m_ds);
m_ds.init(m_curSymp);
m_ds.m_dotPos = DP_FIRST;
m_ds.m_disablep = nodep;
@ -6249,10 +6243,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
UASSERT_OBJ(ifacep, nodep, "Port parameters of AstIfaceRefDType without ifacep()");
if (ifacep->dead()) return;
checkNoDot(nodep);
VL_RESTORER(m_usedPins);
VL_RESTORER(m_usedDefParamPins);
m_usedPins.clear();
m_usedDefParamPins.clear();
VL_RESTORER_CLEAR(m_usedPins);
VL_RESTORER_CLEAR(m_usedDefParamPins);
VL_RESTORER(m_pinSymp);
m_pinSymp = m_statep->getNodeSym(ifacep);
iterateAndNextNull(nodep->paramsp());

View File

@ -273,7 +273,7 @@ class LinkParseVisitor final : public VNVisitor {
<< nodep->verilogKwd() << "'");
}
VL_RESTORER(m_portDups);
VL_RESTORER_COPY(m_portDups);
collectPorts(nodep->stmtsp());
iterateChildren(nodep);
@ -763,12 +763,12 @@ class LinkParseVisitor final : public VNVisitor {
VL_RESTORER(m_genblkAbove);
VL_RESTORER(m_genblkNum);
VL_RESTORER(m_beginDepth);
VL_RESTORER(m_implTypedef);
VL_RESTORER(m_lifetime);
VL_RESTORER(m_lifetimeAllowed);
VL_RESTORER(m_moduleWithGenericIface);
VL_RESTORER(m_randSequenceNum);
VL_RESTORER(m_valueModp);
VL_RESTORER_CLEAR(m_implTypedef);
// Module: Create sim table for entire module and iterate
cleanFileline(nodep);
@ -780,7 +780,6 @@ class LinkParseVisitor final : public VNVisitor {
m_genblkAbove = 0;
m_genblkNum = 0;
m_beginDepth = 0;
m_implTypedef.clear();
m_valueModp = nodep;
m_lifetime = nodep->lifetime().makeImplicit();
m_lifetimeAllowed = VN_IS(nodep, Class);
@ -797,7 +796,7 @@ class LinkParseVisitor final : public VNVisitor {
"Verilator top-level internals");
}
VL_RESTORER(m_portDups);
VL_RESTORER_COPY(m_portDups);
collectPorts(nodep->stmtsp());
iterateChildren(nodep);

View File

@ -32,7 +32,7 @@ class LinkWithVisitor final : public VNVisitor {
// STATE
// Below state needs to be preserved between each module call.
string m_randcIllegalWhy; // Why randc illegal
const char* m_randcIllegalWhyp = nullptr; // Why randc illegal
AstNode* m_randcIllegalp = nullptr; // Node causing randc illegal
AstNodeExpr* m_currentRandomizeSelectp = nullptr; // fromp() of current `randomize()` call
bool m_inRandomizeWith = false; // If in randomize() with (and no other with afterwards)
@ -49,24 +49,24 @@ class LinkWithVisitor final : public VNVisitor {
iterateChildren(nodep);
}
void visit(AstConstraintBefore* nodep) override {
VL_RESTORER(m_randcIllegalWhy);
VL_RESTORER(m_randcIllegalWhyp);
VL_RESTORER(m_randcIllegalp);
m_randcIllegalWhy = "'solve before' (IEEE 1800-2023 18.5.9)";
m_randcIllegalWhyp = "'solve before' (IEEE 1800-2023 18.5.9)";
m_randcIllegalp = nodep;
iterateChildrenConst(nodep);
}
void visit(AstDist* nodep) override {
VL_RESTORER(m_randcIllegalWhy);
VL_RESTORER(m_randcIllegalWhyp);
VL_RESTORER(m_randcIllegalp);
m_randcIllegalWhy = "'constraint dist' (IEEE 1800-2023 18.5.3)";
m_randcIllegalWhyp = "'constraint dist' (IEEE 1800-2023 18.5.3)";
m_randcIllegalp = nodep;
iterateChildrenConst(nodep);
}
void visit(AstConstraintExpr* nodep) override {
VL_RESTORER(m_randcIllegalWhy);
VL_RESTORER(m_randcIllegalWhyp);
VL_RESTORER(m_randcIllegalp);
if (nodep->isSoft()) {
m_randcIllegalWhy = "'constraint soft' (IEEE 1800-2023 18.5.13.1)";
m_randcIllegalWhyp = "'constraint soft' (IEEE 1800-2023 18.5.13.1)";
m_randcIllegalp = nodep;
}
iterateChildrenConst(nodep);
@ -76,7 +76,7 @@ class LinkWithVisitor final : public VNVisitor {
if (nodep->varp()) { // Else due to dead code, might not have var pointer
if (nodep->varp()->isRandC() && m_randcIllegalp) {
nodep->v3error("Randc variables not allowed in "
<< m_randcIllegalWhy << '\n'
<< m_randcIllegalWhyp << '\n'
<< nodep->warnContextPrimary() << '\n'
<< m_randcIllegalp->warnOther()
<< "... Location of restricting expression\n"

View File

@ -2571,11 +2571,9 @@ class ParamVisitor final : public VNVisitor {
// Iterate the body
{
VL_RESTORER(m_modp);
VL_RESTORER(m_ifacePortNames);
VL_RESTORER(m_ifaceInstCells);
VL_RESTORER_CLEAR(m_ifacePortNames);
VL_RESTORER_CLEAR(m_ifaceInstCells);
m_modp = modp;
m_ifacePortNames.clear();
m_ifaceInstCells.clear();
iterateChildren(modp);
}
}
@ -3241,7 +3239,7 @@ class ParamVisitor final : public VNVisitor {
// Note this clears nodep->genforp(), so begin is no longer special
}
} else {
VL_RESTORER(m_generateHierName);
VL_RESTORER_COPY(m_generateHierName);
m_generateHierName += "." + nodep->prettyName();
iterateChildren(nodep);
}

View File

@ -136,7 +136,7 @@ class RandSequenceVisitor final : public VNVisitor {
m_modp->addStmtsp(taskp);
// Create local (not output) break variable
VL_RESTORER(m_localizeRemaps);
VL_RESTORER_COPY(m_localizeRemaps);
AstVar* breakVarp = newBreakVar(nodep->fileline(), false);
taskp->addStmtsp(breakVarp);
@ -417,9 +417,9 @@ class RandSequenceVisitor final : public VNVisitor {
m_rsp = nodep;
VL_RESTORER(m_startProdp);
VL_RESTORER(m_localizes);
VL_RESTORER(m_localizeNames);
VL_RESTORER(m_localizeRemaps);
VL_RESTORER_COPY(m_localizes);
VL_RESTORER_COPY(m_localizeNames);
VL_RESTORER_COPY(m_localizeRemaps);
findLocalizes(nodep);
// Find first production
@ -482,7 +482,7 @@ class RandSequenceVisitor final : public VNVisitor {
// TODO we could do this only if break exists in the downstream production,
// but as-is we'll optimize it away in most cases anyways
VL_RESTORER(m_breakVarp);
VL_RESTORER(m_localizeRemaps);
VL_RESTORER_COPY(m_localizeRemaps);
m_breakVarp = newBreakVar(nodep->fileline(), true);
m_prodFuncp->addStmtsp(m_breakVarp);

View File

@ -26,6 +26,7 @@
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3AstUserAllocator.h"
#include "V3EmitCBase.h"
#include "V3Sched.h"
@ -179,7 +180,8 @@ AstCCall* TimingKit::createReady(AstNetlist* const netlistp) {
class AwaitVisitor final : public VNVisitor {
// NODE STATE
// AstSenTree::user1() -> bool. Set true if the sentree has been visited.
// AstSenTree::user1() -> bool. Set true if the sentree has been visited.
// AstCFunc::user1() -> AstUser1Allocator. See alocator below
const VNUser1InUse m_inuser1;
// STATE
@ -193,6 +195,34 @@ class AwaitVisitor final : public VNVisitor {
std::set<AstSenTree*> m_processDomains; // Sentrees from the current process
// Variables written by suspendable processes
std::vector<AstVarScope*> m_writtenBySuspendable;
struct CFuncCache final {
std::set<AstSenTree*> m_processDomains; // What shall be added to m_processDomains
std::vector<AstVarScope*>
m_writtenBySuspendable; // What shall be added to m_writtenBySuspendable
enum State : uint8_t {
UNINITIALIZED = 0, // Not initialized members are empty
VISITING, // Visiting - needed for breaking recursion
INITIALIZED, // Members contains correct values
} m_state // Current state of Cache
= UNINITIALIZED;
};
// Caches how visiting the function with given value of m_gatherVars changes
// m_processDomains and m_writtenBySuspendable - only accessed from visit(AstCFunc* nodep)
AstUser1Allocator<AstCFunc, std::array<CFuncCache, 2>> m_cfuncsCache;
// Count uses of not inlined writes to signals in suspendables
VDouble0 m_notInlinedWritesInSuspendableUsage;
// Set containing information whether an AstCFunc was hit (called) recursively
// This is needed to know whether cache is complete. E.g.:
// A->B->C->B
// Callstack:
// visit(A) -> Ok go to successors
// visit(B) -> Ok go to successors
// visit(C) -> Ok go to successors
// visit(B) -> Already visiting B
// visit(C) -> Cache must not be created since it is not complete - A was not visited
// visit(B) -> Cache may be created since all successors have been transitionally visited
// visit(A) -> Cache may be created since all successors have been transitionally visited
std::array<std::unordered_set<const AstCFunc*>, 2> m_hitVisiting;
// METHODS
// Add arguments to a resume() call based on arguments in the suspending call
@ -267,6 +297,7 @@ class AwaitVisitor final : public VNVisitor {
if (!sentreep->user1SetOnce()) createResumeActive(nodep);
if (m_inProcess) m_processDomains.insert(sentreep);
}
iterateChildren(nodep);
}
void visit(AstNodeVarRef* nodep) override {
if (m_gatherVars && nodep->access().isWriteOrRW() && !nodep->varp()->ignoreSchedWrite()
@ -274,6 +305,59 @@ class AwaitVisitor final : public VNVisitor {
m_writtenBySuspendable.push_back(nodep->varScopep());
}
}
void visit(AstNodeCCall* const nodep) override {
iterateChildren(nodep);
// We need to visit bodies of non-inlined functions
iterate(nodep->funcp());
}
void visit(AstCFunc* nodep) override {
UASSERT_OBJ(!m_gatherVars || m_inProcess, nodep,
"Variables shall be gathered only inside processes");
// Cache key does not depends on a m_inProcess variable since it is forced to be true
const size_t cacheKey = static_cast<size_t>(m_gatherVars);
CFuncCache& value = m_cfuncsCache(nodep)[cacheKey];
{
VL_RESTORER(m_inProcess);
// Force m_inProcess since visiting a tree with !m_inProcess does not change the
// m_writtenBySuspendable and m_processDomains (so it would be a bit of a dry run).
// However, we must visit children since there might be an AstCAwait which is modified
// in its visit() but since the function does not depend on a state of m_inProcess (nor
// anything that may be affected by changing the state of m_inProcess) we may force the
// variable, visit children and gather cache for m_inProcess && !m_gatherVars
m_inProcess = true;
switch (value.m_state) {
case CFuncCache::UNINITIALIZED: {
// Save current state
VL_RESTORER_CLEAR(m_processDomains);
VL_RESTORER_CLEAR(m_writtenBySuspendable);
// Visit
value.m_state = CFuncCache::VISITING;
iterateChildren(nodep);
m_hitVisiting[cacheKey].erase(nodep);
value.m_state = m_hitVisiting[cacheKey].empty() ? CFuncCache::INITIALIZED
: CFuncCache::UNINITIALIZED;
// Save a cache
std::swap(m_processDomains, value.m_processDomains);
std::swap(m_writtenBySuspendable, value.m_writtenBySuspendable);
} break;
case CFuncCache::VISITING:
m_hitVisiting[cacheKey].insert(nodep);
return; // Break recursion
case CFuncCache::INITIALIZED: break;
}
}
if (m_inProcess) {
// Add cached values to the visitor state if the m_inProcess was initially true - when
// it is false none of these variable shall be modified
m_writtenBySuspendable.insert(m_writtenBySuspendable.end(),
value.m_writtenBySuspendable.begin(),
value.m_writtenBySuspendable.end());
m_notInlinedWritesInSuspendableUsage += value.m_writtenBySuspendable.size();
m_processDomains.insert(value.m_processDomains.begin(), value.m_processDomains.end());
}
}
void visit(AstExprStmt* nodep) override { iterateChildren(nodep); }
//--------------------
@ -289,7 +373,10 @@ public:
, m_externalDomains{externalDomains} {
iterate(nodep);
}
~AwaitVisitor() override = default;
~AwaitVisitor() override {
V3Stats::addStat("Scheduling, count of non-inlined signal writes in suspendables",
m_notInlinedWritesInSuspendableUsage);
}
};
TimingKit prepareTiming(AstNetlist* const netlistp) {

View File

@ -306,10 +306,9 @@ private:
}
void visit(AstClass* nodep) override {
// Move initial statements into the constructor
VL_RESTORER(m_initialps);
VL_RESTORER_CLEAR(m_initialps);
VL_RESTORER(m_ctorp);
VL_RESTORER(m_classp);
m_initialps.clear();
m_ctorp = nullptr;
m_classp = nodep;
{ // Find m_initialps, m_ctor

View File

@ -485,7 +485,7 @@ class TraceDeclVisitor final : public VNVisitor {
AstNodeDType* const skipTypep = nodep->skipRefp();
// offset and direction args added in EmitCImp
std::string callArgs{"tracep, \"" + VIdProtect::protect(m_traName) + "\""};
VL_RESTORER(m_traName);
VL_RESTORER_COPY(m_traName);
FileLine* const flp = skipTypep->fileline();
const DtypeFuncKey dtypeKey{skipTypep, m_traVscp->varp()->varType()};
@ -530,7 +530,7 @@ class TraceDeclVisitor final : public VNVisitor {
void declUnpackedArray(AstUnpackArrayDType* const nodep, bool newFunc) {
string prefixName(newFunc ? "name" : m_traName);
VL_RESTORER(m_traName);
VL_RESTORER_COPY(m_traName);
FileLine* const flp = nodep->fileline();
addToSubFunc(new AstTracePushPrefix{flp, prefixName, VTracePrefixType::ARRAY_UNPACKED,
@ -568,7 +568,7 @@ class TraceDeclVisitor final : public VNVisitor {
string prefixName(newFunc ? "name" : m_traName);
AstNodeDType* const subtypep = nodep->subDTypep()->skipRefToEnump();
VL_RESTORER(m_traName);
VL_RESTORER_COPY(m_traName);
FileLine* const flp = nodep->fileline();
addToSubFunc(new AstTracePushPrefix{flp, prefixName, VTracePrefixType::ARRAY_PACKED,
@ -918,7 +918,7 @@ class TraceDeclVisitor final : public VNVisitor {
return;
}
VL_RESTORER(m_traName);
VL_RESTORER_COPY(m_traName);
FileLine* const flp = nodep->fileline();
int nMembers = 0;

View File

@ -2191,8 +2191,8 @@ class TristateVisitor final : public TristateBaseVisitor {
VL_RESTORER(m_modp);
VL_RESTORER(m_graphing);
VL_RESTORER(m_unique);
VL_RESTORER(m_lhsmap);
VL_RESTORER(m_assigns);
VL_RESTORER_CLEAR(m_lhsmap);
VL_RESTORER_CLEAR(m_assigns);
// Not preserved, needs pointer instead: TristateGraph origTgraph = m_tgraph;
UASSERT_OBJ(m_tgraph.empty(), nodep, "Unsupported: NodeModule under NodeModule");
@ -2201,8 +2201,6 @@ class TristateVisitor final : public TristateBaseVisitor {
m_tgraph.clearAndCheck();
m_unique = 0;
m_logicp = nullptr;
m_lhsmap.clear();
m_assigns.clear();
m_modp = nodep;
// Walk the graph, finding all variables and tristate constructs
m_graphing = true;

View File

@ -56,14 +56,12 @@ class UdpVisitor final : public VNVisitor {
VL_RESTORER(m_primp);
VL_RESTORER(m_outputInitVerfp);
VL_RESTORER(m_isFirstOutput);
VL_RESTORER(m_inputVars);
VL_RESTORER(m_outputVars);
VL_RESTORER_CLEAR(m_inputVars);
VL_RESTORER_CLEAR(m_outputVars);
m_outputInitVerfp = nullptr;
m_primp = nodep;
m_isFirstOutput = false;
iterateChildren(nodep);
m_inputVars.clear();
m_outputVars.clear();
}
void visit(AstVar* nodep) override {
// Push the input and output vars for primitive.

View File

@ -731,6 +731,17 @@ class WidthVisitor final : public VNVisitor {
iterateCheckBool(nodep, "default disable iff condition", nodep->condp(), BOTH);
}
void visit(AstDelay* nodep) override {
if (nodep->isCycleDelay() && m_underSExpr) {
// Fold parameterized SVA cycle-delay bounds
userIterateAndNext(nodep->lhsp(), WidthVP{SELF, BOTH}.p());
V3Const::constifyParamsNoWarnEdit(nodep->lhsp());
if (nodep->rhsp() && !nodep->isUnbounded()) {
// Fold parametrized SVA cycle-delay max bound
userIterateAndNext(nodep->rhsp(), WidthVP{SELF, BOTH}.p());
V3Const::constifyParamsNoWarnEdit(nodep->rhsp());
}
return;
}
if (AstNodeExpr* const fallDelayp = nodep->fallDelay()) {
iterateCheckDelay(nodep, "delay", nodep->lhsp(), BOTH);
iterateCheckDelay(nodep, "delay", fallDelayp, BOTH);
@ -7852,7 +7863,7 @@ class WidthVisitor final : public VNVisitor {
}
void visit(AstNodeModule* nodep) override {
assertAtStatement(nodep);
VL_RESTORER(m_insideTempNames);
VL_RESTORER_COPY(m_insideTempNames);
if (AstClass* const classp = VN_CAST(nodep, Class)) {
visitClass(classp);
} else {
@ -9564,9 +9575,11 @@ class WidthVisitor final : public VNVisitor {
// No width change on output;... // All below have bool or double outputs
switch (nodep->type()) {
case VNType::Eq:
case VNType::EqCase: newp = new AstEqN{fl, lhsp, rhsp}; break;
case VNType::EqCase:
case VNType::EqWild: newp = new AstEqN{fl, lhsp, rhsp}; break;
case VNType::Neq:
case VNType::NeqCase: newp = new AstNeqN{fl, lhsp, rhsp}; break;
case VNType::NeqCase:
case VNType::NeqWild: newp = new AstNeqN{fl, lhsp, rhsp}; break;
case VNType::Gt:
case VNType::GtS: newp = new AstGtN{fl, lhsp, rhsp}; break;
case VNType::Gte:

View File

@ -47,7 +47,7 @@ class WidthCommitVisitor final : public VNVisitor {
// STATE
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
AstNodeModule* m_modp = nullptr; // Current module
std::string m_contNba; // In continuous- or non-blocking assignment
const char* m_contNbap = nullptr; // In continuous- or non-blocking assignment
bool m_contReads = false; // Check read continuous automatic variables
bool m_dynsizedelem = false; // Writing dynamically-sized array element, not the array itself
VMemberMap m_memberMap; // Member names cached for fast lookup
@ -148,7 +148,7 @@ private:
void varLifetimeCheck(AstNode* nodep, AstVar* varp) {
// Skip if we are under a member select (lhs of a dot)
// We don't care about lifetime of anything else than rhs of a dot
if (!m_underSel && !m_contNba.empty()) {
if (!m_underSel && m_contNbap) {
std::string varType;
const AstNodeDType* const varDtp = varp->dtypep()->skipRefp();
if (varp->lifetime().isAutomatic() && !VN_IS(varDtp, IfaceRefDType)
@ -162,7 +162,7 @@ private:
if (!varType.empty()) {
UINFO(1, " Related var dtype: " << varDtp);
nodep->v3error(varType
<< " variable not allowed in " << m_contNba
<< " variable not allowed in " << m_contNbap
<< " assignment (IEEE 1800-2023 6.21): " << varp->prettyNameQ());
}
}
@ -419,9 +419,9 @@ private:
void visit(AstAssignCont* nodep) override {
iterateAndNextNull(nodep->timingControlp());
{
VL_RESTORER(m_contNba);
VL_RESTORER(m_contNbap);
VL_RESTORER(m_contReads);
m_contNba = "continuous";
m_contNbap = "continuous";
m_contReads = true;
iterateAndNextNull(nodep->lhsp());
iterateAndNextNull(nodep->rhsp());
@ -432,9 +432,9 @@ private:
iterateAndNextNull(nodep->timingControlp());
iterateAndNextNull(nodep->rhsp());
{
VL_RESTORER(m_contNba);
VL_RESTORER(m_contNbap);
VL_RESTORER(m_contReads);
m_contNba = "nonblocking";
m_contNbap = "nonblocking";
m_contReads = false;
iterateAndNextNull(nodep->lhsp());
}
@ -444,9 +444,9 @@ private:
iterateAndNextNull(nodep->timingControlp());
iterateAndNextNull(nodep->rhsp());
{
VL_RESTORER(m_contNba);
VL_RESTORER(m_contNbap);
VL_RESTORER(m_contReads);
m_contNba = "continuous";
m_contNbap = "continuous";
m_contReads = false;
iterateAndNextNull(nodep->lhsp());
}

View File

@ -261,6 +261,7 @@ static void process() {
// Assertion insertion
// After we've added block coverage, but before other nasty transforms
V3AssertCommon::collectDefaultDisable(v3Global.rootp());
V3AssertCommon::lowerSequenceEvents(v3Global.rootp());
V3AssertNfa::assertNfaAll(v3Global.rootp());
// V3AssertProp removed: NFA subsumes multi-cycle property lowering.
// Unsupported constructs fall through to V3AssertPre.

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=['--timing'])
test.execute()
test.passes()

View File

@ -0,0 +1,97 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
module t (
input clk
);
int unsigned crc = 32'h1;
bit a, b, c;
bit a1, a2, a3, b1;
int cyc;
int seq_hits, seq_hits2, ref_hits, one_hits, dc_hits;
int rng_hits, rng_ref;
// verilog_format: off // verible does not support clocking events inside sequence declarations
sequence seq;
@(posedge clk) a ##1 b ##1 c;
endsequence
sequence seq_one;
@(posedge clk) a;
endsequence
sequence seq_rng;
@(posedge clk) a ##[1:3] b;
endsequence
// verilog_format: on
// seq_dc inherits the default clocking; counts must match seq
default clocking @(posedge clk);
endclocking
sequence seq_dc;
a ##1 b ##1 c;
endsequence
// ref_hits and rng_ref are independent shift-register oracles;
// simultaneous end points resume a blocked waiter once
initial forever begin
@seq;
seq_hits = seq_hits + 1;
end
initial forever begin
@seq;
seq_hits2 = seq_hits2 + 1;
end
initial forever begin
@seq_one;
one_hits = one_hits + 1;
end
initial forever begin
@seq_dc;
dc_hits = dc_hits + 1;
end
initial forever begin
@seq_rng;
rng_hits = rng_hits + 1;
end
always @(posedge clk) begin
if (a2 && b1 && c) ref_hits = ref_hits + 1;
if (b && (a1 || a2 || a3)) rng_ref = rng_ref + 1;
a3 <= a2;
a2 <= a1;
a1 <= a;
b1 <= b;
end
// a/b/c bit spacing exceeds the ##2 window to decorrelate the LFSR taps
always @(posedge clk) begin
cyc <= cyc + 1;
crc <= {crc[30:0], crc[31] ^ crc[21] ^ crc[1] ^ crc[0]};
a <= crc[0];
b <= crc[4];
c <= crc[8];
if (cyc == 60) $finish;
end
// Counts read in final (Postponed) to avoid same-timestep races.
final begin
`checkd(seq_hits, 14);
`checkd(seq_hits2, 14);
`checkd(ref_hits, 14);
`checkd(one_hits, 30);
`checkd(dc_hits, 14);
`checkd(rng_hits, 26);
`checkd(rng_ref, 26);
$write("*-* All Finished *-*\n");
end
endmodule

View File

@ -0,0 +1,6 @@
%Error: t/t_assert_seq_event_bad.v:19:7: Arguments to a sequence used as an event control must be static (IEEE 1800-2023 9.4.2.4)
: ... note: In instance 't'
19 | @(s_arg(x));
| ^~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.lint(expect_filename=test.golden_filename, verilator_flags2=['--timing'], fails=True)
test.passes()

View File

@ -0,0 +1,23 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
// verilog_format: off
sequence s_arg(x);
@(posedge clk) x;
endsequence
// verilog_format: on
task automatic f;
bit x = 1;
@(s_arg(x));
endtask
initial f();
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile(verilator_flags2=['--timing'])
test.execute()
test.passes()

View File

@ -0,0 +1,73 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
module t (
input clk
);
int unsigned crc = 32'h1;
bit a, b, rst;
bit a1;
int cyc;
int hits, ref_hits, one_hits, one_ref;
// Neither the default disable iff nor $assertoff may suppress a sequence event control
default disable iff (rst);
// verilog_format: off // verible does not support clocking events inside sequence declarations
sequence seq;
@(posedge clk) a ##1 b;
endsequence
sequence seq_one;
@(posedge clk) 1;
endsequence
// verilog_format: on
initial begin
$assertoff;
#300 $assertkill;
end
initial forever begin
@seq;
hits = hits + 1;
end
initial forever begin
@seq_one;
one_hits = one_hits + 1;
end
always @(posedge clk) begin
if (a1 && b) ref_hits = ref_hits + 1;
one_ref = one_ref + 1;
a1 <= a;
end
always @(posedge clk) begin
cyc <= cyc + 1;
crc <= {crc[30:0], crc[31] ^ crc[21] ^ crc[1] ^ crc[0]};
a <= crc[0];
b <= crc[4];
rst <= crc[2];
end
always @(negedge clk) begin
if (cyc == 60) $finish;
end
final begin
`checkd(hits, ref_hits);
`checkd(one_hits, one_ref);
`checkd(hits, 19);
`checkd(one_hits, 60);
$write("*-* All Finished *-*\n");
end
endmodule

View File

@ -0,0 +1,13 @@
%Error-UNSUPPORTED: t/t_assert_seq_event_unsup.v:18:5: Unsupported: non-edge clocking event on a sequence; use an edge such as @(posedge clk)
: ... note: In instance 't'
18 | @(g) a ##1 b;
| ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_assert_seq_event_unsup.v:28:30: Unsupported: sequence used as an event control with a sequence operand of 'or'
28 | @(posedge clk) (a ##1 b) or (a ##2 b);
| ^~
%Error-UNSUPPORTED: t/t_assert_seq_event_unsup.v:21:12: Unsupported: sequence referenced outside assertion property
: ... note: In instance 't'
21 | sequence s_ref;
| ^~~~~
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.lint(expect_filename=test.golden_filename, verilator_flags2=['--timing'], fails=True)
test.passes()

View File

@ -0,0 +1,42 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
bit a, b;
logic g = 0;
default clocking @(posedge clk);
endclocking
// verilog_format: off
sequence s_nonedge;
@(g) a ##1 b;
endsequence
sequence s_ref;
@(posedge clk) a;
endsequence
// Legal but its endpoint topology is not buildable, so the wait could never
// resume; rejected rather than silently ignored.
sequence s_or;
@(posedge clk) (a ##1 b) or (a ##2 b);
endsequence
// verilog_format: on
// Legal: p is never asserted, so s_ref stays referenced outside any
// assertion property, which is not yet supported.
property p;
s_ref;
endproperty
initial begin
@s_nonedge;
@s_or;
end
endmodule

View File

@ -1318,6 +1318,21 @@ module Vt_debug_emitv_sub;
endfunction
real r;
endmodule
module Vt_debug_emitv_seq_event;
input logic clk;
bit a;
bit b;
bit c;
sequence sq;
@(posedge clk) a##1 b##1 c
endsequence
initial begin
begin
???? // EVENTCONTROL
@( sq())end
end
endmodule
package Vt_debug_emitv_p;
logic pkgvar;
endpackage

View File

@ -138,6 +138,7 @@ module t (/*AUTOARG*/
endfunction
sub sub(.*);
seq_event seq_event(.*);
initial begin
int other;
@ -443,6 +444,16 @@ module sub(input logic clk);
real r;
endmodule
module seq_event(input logic clk);
bit a, b, c;
sequence sq;
@(posedge clk) a ##1 b ##1 c;
endsequence
initial begin
@sq;
end
endmodule
package p;
logic pkgvar;
endpackage

View File

@ -92,6 +92,7 @@ test.compile(verilator_flags2=[
"--prefix", "Vopt",
"-Werror-UNOPTFLAT",
"--dumpi-V3DfgBreakCycles", "9", # To fill code coverage
"--debugi-V3DfgBreakCycles", "9", # To fill code coverage
"--debug", "--debugi", "0", "--dumpi-tree", "0",
"-CFLAGS \"-I .. -I ../obj_ref\"",
"../obj_ref/Vref__ALL.a",

View File

@ -361,6 +361,19 @@ module t (
assign ALWAYS_2 = always_2;
// verilator lint_on ALWCOMBORDER
// verilator lint_off ALWCOMBORDER
logic [4:0] always_3;
always_comb begin
always_3[4] = always_3[0];
always_3[0] = rand_a[0];
always_3[2] = rand_a[2];
always_3[3] = |always_3[2:1];
end
assign always_3[1] = always_3[0];
`signal(ALWAYS_3, 5); // UNOPTFLAT
assign ALWAYS_3 = always_3;
// verilator lint_on ALWCOMBORDER
logic [31:0] array_4[3]; // UNOPTFLAT
// Input
assign array_4[0] = rand_a[31:0];

View File

@ -4,7 +4,16 @@
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
module t;
localparam string AB = "AB";
bit r;
initial begin
if (('bxz10 ==? 'bxxx0) !== 1) $stop;
if (('bxz10 ==? 'bxxx1) !== 0) $stop;
@ -12,6 +21,16 @@ module t;
if (('bxz10 !=? 'bxxx1) !== 1) $stop;
if (('bxz10 !=? 'bxxx0) !== 0) $stop;
if (('bxz10 !=? 'b1xx0) !== 'x) $stop;
r = (AB ==? "AA");
`checkd(r, 0);
r = (AB ==? "AB");
`checkd(r, 1);
r = (AB !=? "AB");
`checkd(r, 0);
// real/shortreal is illegal so not tested here
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -13,6 +13,8 @@
module t (
input clk
);
parameter P = 1;
integer cyc = 0;
reg [63:0] crc = '0;
reg [63:0] sum = '0;
@ -64,6 +66,11 @@ module t (
assert property (@(posedge clk) disable iff (cyc < 2)
a |-> ##[1:3] (a | b | c | d | e));
// Parameterized range bound
assert property (@(posedge clk) disable iff (cyc < 2)
a |-> ##[P:P+3] (a | b | c | d | e));
assert property (@(posedge clk) ##[P:P+3] 1);
// ##[2:4] range delay
assert property (@(posedge clk) disable iff (cyc < 2)
b |-> ##[2:4] (a | b | c | d | e));

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--binary', '--stats', '-fno-dfg'])
test.execute()
test.file_grep(test.stats,
r'Scheduling, count of non-inlined signal writes in suspendables\s+(\d+)', 7)
test.passes()

View File

@ -0,0 +1,93 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
// verilog_format: off
package pkg;
bit [2:0] y3;
endpackage
module t;
bit [2:0] y;
bit [2:0] z;
assign z[0] = 1'b1;
assign z[1] = !(y[0]);
assign z[2] = !(|y[1:0]);
bit [2:0] y2;
bit [2:0] z2;
assign z2[0] = 1'b1;
assign z2[1] = !(y2[0]);
assign z2[2] = !(|y2[1:0]);
import pkg::y3;
bit [2:0] z3;
assign z3[0] = 1'b1;
assign z3[1] = !(y3[0]);
assign z3[2] = !(|y3[1:0]);
bit [2:0] y4;
bit [2:0] z4;
assign z4[0] = 1'b1;
assign z4[1] = !(y4[0]);
assign z4[2] = !(|y4[1:0]);
class Foo;
function automatic int bar();
// verilator no_inline_task
y2 = 3'b111;
y3 = 3'b111;
return 1;
endfunction
task run();
y = 3'b111;
#1;
`checkh(z, 3'b001);
`checkh(z2, 3'b001);
`checkh(z3, 3'b001);
`checkh(z4, 3'b111);
endtask
task a(bit x = 0);
// verilator no_inline_task
y4 = ~y4;
#1;
if (!x) b(!x);
endtask
task b(bit x = 0);
// verilator no_inline_task
if (!x) a(!x);
endtask
endclass
initial begin
static Foo foo = new;
#1;
`checkh(z, 3'b111);
`checkh(z2, 3'b111);
`checkh(z3, 3'b111);
`checkh(z4, 3'b111);
void'(foo.bar());
#1;
`checkh(z, 3'b111);
`checkh(z2, 3'b001);
`checkh(z3, 3'b001);
`checkh(z4, 3'b111);
foo.run();
foo.a();
`checkh(z, 3'b001);
`checkh(z2, 3'b001);
`checkh(z3, 3'b001);
`checkh(z4, 3'b001);
foo.b();
`checkh(z, 3'b001);
`checkh(z2, 3'b001);
`checkh(z3, 3'b001);
`checkh(z4, 3'b111);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,7 @@
%Error: t/t_sequence_ref_bad.v:14:6: Concurrent assertion has no clock (IEEE 1800-2023 16.16)
: ... note: In instance 't'
: ... Suggest provide a clocking event, a default clocking, or a clocked procedural context
14 | @s_one;
| ^~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -1,6 +0,0 @@
%Error-UNSUPPORTED: t/t_sequence_ref_unsup.v:9:12: Unsupported: sequence referenced outside assertion property
: ... note: In instance 't'
9 | sequence s_one;
| ^~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -78,7 +78,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -110,7 +110,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -171,7 +171,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -247,7 +247,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -278,7 +278,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -339,7 +339,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -463,7 +463,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -498,7 +498,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -581,7 +581,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -676,7 +676,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -711,7 +711,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -794,7 +794,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -831,7 +831,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 0 is active: @([event] t.ec.e)
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -906,7 +906,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -936,7 +936,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -994,7 +994,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1060,7 +1060,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1125,7 +1125,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1152,7 +1152,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1215,7 +1215,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1280,7 +1280,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1320,7 +1320,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdynSched.evaluate())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1371,7 +1371,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1427,7 +1427,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1486,7 +1486,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1541,7 +1541,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1596,7 +1596,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume
@ -1649,7 +1649,7 @@
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#} 'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#} 'act' region trigger index 2 is active: @([true] __VdlySched.awaitingCurrentTime())
-V{t#,#}+ Vt_timing_debug2___024root___trigger_orInto__act_vec_vec
-V{t#,#}+ Vt_timing_debug2___024root___trigger_anySet__act
-V{t#,#}+ Vt_timing_debug2___024root___timing_resume