merge upstream master

This commit is contained in:
Yilou Wang 2026-07-08 23:03:52 +02:00
commit cc9b7676ba
79 changed files with 1715 additions and 571 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:

View File

@ -13,6 +13,7 @@ on:
permissions:
contents: read
actions: read
defaults:
run:

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,6 +11,10 @@ on:
permissions:
contents: write
defaults:
run:
working-directory: repo
jobs:
format:
runs-on: ubuntu-24.04
@ -19,21 +23,32 @@ jobs:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
path: repo
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install packages for 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

@ -45,9 +45,9 @@ on:
value: ${{ jobs.build.outputs.archive }}
env:
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
defaults:
run:
@ -61,9 +61,6 @@ jobs:
runs-on: ${{ inputs.runs-on }}
outputs:
archive: ${{ steps.create-archive.outputs.archive }}
env:
CACHE_BASE_KEY: build-${{ inputs.runs-on }}-${{ inputs.cc }}
CCACHE_MAXSIZE: 1000M
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
@ -74,7 +71,15 @@ jobs:
# 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
@ -82,7 +87,7 @@ 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 build

View File

@ -7,11 +7,6 @@ name: reusable-lint-py
on:
workflow_call:
env:
CCACHE_COMPRESS: 1
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_LIMIT_MULTIPLE: 0.95
defaults:
run:
shell: bash
@ -28,19 +23,18 @@ jobs:
with:
path: repo
- name: Install packages for build
run: ./ci/ci-install.bash build
- 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

@ -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
########################################################################

View File

@ -34,10 +34,14 @@ on:
required: true
type: string
permissions:
contents: read
actions: read
env:
CCACHE_COMPRESS: 1
CCACHE_COMPILERCHECK: content
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_LIMIT_MULTIPLE: 0.95
CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }}
defaults:
run:
@ -49,10 +53,6 @@ jobs:
test:
runs-on: ${{ inputs.runs-on }}
name: Test
env:
CXX: ${{ inputs.cc == 'clang' && 'clang++' || 'g++' }}
CACHE_BASE_KEY: test-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.reloc }}-${{ inputs.suite }}
CCACHE_MAXSIZE: 100M # Per build per suite (* 5 * 5 = 2500M in total)
steps:
- name: Download repository archive
@ -67,20 +67,31 @@ jobs:
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 test
make venv
- name: Set up Python venv
uses: ./repo/.github/actions/setup-venv
with:
save: ${{ github.ref == 'refs/heads/master' }}
- name: Test
id: run-test
@ -105,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

@ -60,7 +60,7 @@ esac
################################################################################
# Configure
CONFIGURE_ARGS="--prefix=$OPT_PREFIX --enable-longtests"
CONFIGURE_ARGS="--prefix=$OPT_PREFIX --enable-longtests --enable-light-debug"
if [ "$OPT_CCWARN" = 1 ]; then
CONFIGURE_ARGS="$CONFIGURE_ARGS --enable-ccwarn"
fi
@ -78,5 +78,8 @@ autoconf
# Build
ccache -z
BUILD_START=$SECONDS
"$MAKE" -j "$NPROC" -k
ccache -svv
ccache --evict-older-than "$((SECONDS - BUILD_START + 60))s"
ccache -svv

View File

@ -26,8 +26,6 @@ case "$(uname -s)" in
HOST_OS=macOS
MAKE=make
NPROC=$(sysctl -n hw.logicalcpu)
# Disable ccache, doesn't always work in GitHub Actions
export OBJCACHE=
;;
*)
fatal "Unknown host OS: '$(uname -s)'"

View File

@ -41,6 +41,11 @@ if [ "$HOST_OS" = "linux" ]; then
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() {
@ -146,10 +151,21 @@ elif [ "$STAGE" = "test" ]; then
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 stage '$STAGE' (expected 'build' or 'test')"
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)

View File

@ -8,7 +8,7 @@
# Executed in the 'test' stage.
################################################################################
# Destructive to the checkout (runs 'find . -delete'); never run locally
# 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
@ -66,13 +66,14 @@ if [ -n "$OPT_RELOC" ]; then
mv test_regress "$TEST_REGRESS"
NODIST="$OPT_RELOC/nodist"
mv nodist "$NODIST"
# Delete everything else
find . -delete
# 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
@ -163,4 +164,6 @@ case $OPT_SUITE in
;;
esac
ccache -svv
ccache --evict-older-than "$((SECONDS - TEST_START + 60))s"
ccache -svv
uptime # To see load average

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

@ -220,6 +220,7 @@ Nikolai Kumar
Nikolay Puzanov
Nolan Poe
Oleh Maksymenko
Patrick Creighton
Patrick Stewart
Paul Bowen-Huggett
Paul Swirhun

View File

@ -294,6 +294,25 @@ or "`ifdef`"'s may break other tools.
(if appropriate :vlopt:`--coverage` flags are passed) after being
disabled earlier with :option:`/*verilator&32;coverage_off*/`.
.. option:: /*verilator&32;dpi_c_decl "<C function declaration>"*/
Specifies C function declaration that will be emitted for given DPI-C
function into the Verilator-generated __Dpi.h header, replacing the declaration
that Verilator would build by default using the Verilog function signature.
This enables use of C functions with types not specified in the
standard. For example, it enables use of functions that return ``char*``:
.. code-block:: sv
module t;
import "DPI-C" function string getenv(input string arg) /*verilator dpi_c_decl "char* getenv(const char*)"*/;
initial begin
$display("%s", getenv("HOME"));
end
endmodule
.. option:: /*verilator&32;fargs <arguments>*/
For Verilator developers only. When a source file containing these `fargs`

View File

@ -98,8 +98,10 @@ void VerilatedFst::close() VL_MT_SAFE_EXCLUDES(m_mutex) {
const VerilatedLockGuard lock{m_mutex};
Super::closeBase();
emitTimeChangeMaybe();
if (m_fst) m_fst->close(); // LCOV_EXCL_BR_LINE
m_fst = nullptr;
if (m_fst) {
m_fst->close();
VL_DO_CLEAR(delete m_fst, m_fst = nullptr);
}
}
void VerilatedFst::flush() VL_MT_SAFE_EXCLUDES(m_mutex) {

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);

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

@ -48,8 +48,8 @@ class SvaStateVertex;
// Per-vertex algorithm data, stored via V3GraphVertex::userp() during lowering
struct SvaVertexData final {
AstVar* stateVarp = nullptr; // NBA state register for this vertex
AstVar* counterActiveVarp = nullptr; // Counter FSM active flag
AstVar* counterCountVarp = nullptr; // Counter FSM count register
AstVar* delayRingVarp = nullptr; // Bitset ring buffer
AstVar* delayRingIdxVarp = nullptr; // Next slot written in delayRingVarp
AstVar* doneLVarp = nullptr; // SAnd LHS done-latch
AstVar* doneRVarp = nullptr; // SAnd RHS done-latch
AstNodeExpr* stateSigp = nullptr; // Combinational state signal (owned during lowering)
@ -64,10 +64,10 @@ public:
bool m_isMatch = false;
// Owned throughout-guard condition clones; IEEE 1800-2023 16.9.9
std::vector<AstNodeExpr*> m_throughoutConds;
// Counter FSM vertex for ##[M:N] when N-M > kChainLimit
bool m_isCounter = false;
int m_counterMax
= 0; // Counter window maximum (min is always 0; pre-chain handles the M offset)
// Nonzero for a bitset ring-buffer vertex for ## delays.
bool m_isFixedDelayRing = false;
int m_delayRingSize = 0; // Fixed delay cycles. Range: max-min+1.
AstNodeExpr* m_delayRingClearCondp = nullptr; // local RHS for pure-boolean range
// Liveness terminal (IEEE weak semantics): reject must not fire from this source
bool m_isUnbounded = false;
// Temporal sequence AND combiner; IEEE 1800-2023 16.9.5
@ -88,15 +88,25 @@ public:
: V3GraphVertex{graphp} {}
~SvaStateVertex() override {
for (AstNodeExpr* cp : m_throughoutConds) VL_DO_DANGLING(cp->deleteTree(), cp);
if (m_delayRingClearCondp)
VL_DO_DANGLING(m_delayRingClearCondp->deleteTree(), m_delayRingClearCondp);
if (m_andLhsCondp) VL_DO_DANGLING(m_andLhsCondp->deleteTree(), m_andLhsCondp);
if (m_andRhsCondp) VL_DO_DANGLING(m_andRhsCondp->deleteTree(), m_andRhsCondp);
}
// METHODS
string name() const override { return "s" + cvtToStr(color()); } // LCOV_EXCL_LINE
// LCOV_EXCL_START -- Graphviz dump only
string name() const override {
string name = "s" + cvtToStr(color());
if (m_delayRingSize) {
name += "\\n";
name += m_isFixedDelayRing ? "fixed chain " : "range chain ";
name += cvtToStr(m_delayRingSize) + " bits";
}
return name;
}
string dotColor() const override {
if (m_isMatch) return "red";
if (m_isCounter) return "blue";
if (m_delayRingSize) return "blue";
if (m_isAndCombiner) return "purple";
return "black";
}
@ -287,6 +297,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;
@ -294,6 +306,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)
@ -461,8 +482,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))};
@ -513,14 +534,28 @@ class SvaNfaBuilder final {
return m_graph.addClockedEdge(fromp, top, throughoutCond(nullptr, flp));
}
SvaStateVertex* addDelayChain(SvaStateVertex* startp, int n, FileLine* flp) {
SvaStateVertex* currentp = startp;
for (int i = 0; i < n; ++i) {
SvaStateVertex* addDelayChain(SvaStateVertex* startp, int size, FileLine* flp,
bool isFixed = true, AstNodeExpr* clearCondp = nullptr) {
if (isFixed && size == 0) return startp;
UASSERT_OBJ(size > 0, startp, "Delay chain needs at least one slot");
if (isFixed && size == 1) {
SvaStateVertex* const nextp = scopedCreateVertex();
guardedEdge(currentp, nextp, flp);
currentp = nextp;
guardedEdge(startp, nextp, flp);
return nextp;
}
return currentp;
SvaStateVertex* const ringVtxp = scopedCreateVertex();
ringVtxp->m_isFixedDelayRing = isFixed;
ringVtxp->m_delayRingSize = size;
if (clearCondp) {
UASSERT_OBJ(!isFixed, startp, "Fixed delay cannot have a clear condition");
ringVtxp->m_delayRingClearCondp = clearCondp->cloneTreePure(false);
}
if (isFixed) {
guardedEdge(startp, ringVtxp, flp);
} else {
guardedLink(startp, ringVtxp, flp);
}
return ringVtxp;
}
// Build NFA for an SExpr. finalCond = RHS (not yet added as a vertex).
@ -565,7 +600,7 @@ class SvaNfaBuilder final {
const int range = maxDelay - minDelay;
currentp = addDelayChain(currentp, minDelay, flp);
// kChainLimit bounds per-attempt unrolled vertices. Above this, a
// counter FSM (constant-size state) is used instead, so the vertex
// ring buffer (constant-size state) is used instead, so the vertex
// count is O(1) in range regardless of user input; no adversarial N
// blowup is possible.
constexpr int kChainLimit = 256;
@ -574,19 +609,13 @@ 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;
}
if (range > kChainLimit) {
// Large range: counter FSM. Overlapping triggers during an active
// count are dropped (non-overlapping semantics only).
SvaStateVertex* const counterVtxp = scopedCreateVertex();
counterVtxp->m_isCounter = true;
counterVtxp->m_counterMax = range;
guardedEdge(currentp, counterVtxp, flp);
currentp = counterVtxp;
currentp = addDelayChain(currentp, range + 1, flp, false,
rhsExprp->isMultiCycleSva() ? nullptr : rhsExprp);
} else if (VN_IS(rhsExprp, SExpr)) {
// Nested-SExpr RHS: merge all [M,N] positions. Candidate-local misses
// are not assertion rejects while a later position can still match.
@ -881,8 +910,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();
}
@ -946,8 +974,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();
@ -1442,11 +1469,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() {
@ -1640,6 +1668,25 @@ class SvaNfaLowering final {
if (!exprp) return nullptr;
return new AstLogAnd{c.flp, exprp, notKillActive(c)};
}
static AstNodeExpr* nextRingIndex(FileLine* flp, AstVar* idxp, uint32_t size) {
const auto u32Const = [flp](uint32_t value) {
return new AstConst{flp, AstConst::WidthedValue{}, 32, value};
};
UASSERT(size > 1, "Delay ring index needs at least two slots");
// idx == size - 1 ? 0 : idx + 1
AstAdd* const addp = new AstAdd{flp, new AstVarRef{flp, idxp, VAccess::READ}, u32Const(1)};
addp->dtypeFrom(idxp);
AstCond* const condp = new AstCond{
flp, new AstEq{flp, new AstVarRef{flp, idxp, VAccess::READ}, u32Const(size - 1)},
u32Const(0), addp};
condp->dtypeFrom(idxp);
return condp;
}
static AstNodeExpr* delayRingBit(FileLine* flp, AstVar* ringp, AstNodeExpr* idxExprp,
VAccess access = VAccess::READ) {
// ring[idx]
return new AstSel{flp, new AstVarRef{flp, ringp, access}, idxExprp, 1};
}
// Phase 3 output signals
struct SignalSet final {
@ -1650,12 +1697,14 @@ class SvaNfaLowering final {
};
// Phase 2/2b/2c: Emit NBA state-update always blocks for registered vertices,
// counter FSMs, and SAnd combiner done-latches.
// delay rings, and SAnd combiner done-latches.
// Phase 2: State register NBA always block. Each clocked-edge target
// latches the OR of its incoming contributions.
void emitStateRegisterNba(LowerCtx& c) {
AstNode* bodyp = nullptr;
bool hasDelayRing = false;
for (int i = 0; i < c.N; ++i) {
if (c.vtx[i]->datap()->delayRingVarp) hasDelayRing = true;
if (!c.vtx[i]->datap()->stateVarp) continue;
AstNodeExpr* nextStatep = nullptr;
@ -1684,100 +1733,104 @@ class SvaNfaLowering final {
AstAssignDly* const assignp = new AstAssignDly{
c.flp, new AstVarRef{c.flp, c.vtx[i]->datap()->stateVarp, VAccess::WRITE},
nextStatep};
if (!bodyp) {
bodyp = assignp;
} else {
bodyp->addNext(assignp);
}
bodyp = AstNode::addNextNull(bodyp, assignp);
}
if (!bodyp) return;
// Capture disableCnt in Phase-2 NBA before any reactive re-evaluation.
// snapshotVarp and disableCntVarp are allocated together.
if (c.snapshotVarp) {
if (c.snapshotVarp && (bodyp || hasDelayRing)) {
UASSERT_OBJ(c.disableCntVarp, c.senTreep, "snapshotVarp set without disableCntVarp");
bodyp->addNext(
new AstAssignDly{c.flp, new AstVarRef{c.flp, c.snapshotVarp, VAccess::WRITE},
new AstVarRef{c.flp, c.disableCntVarp, VAccess::READ}});
// disable_snapshot <= disable_count;
AstAssignDly* const snapshotp
= new AstAssignDly{c.flp, new AstVarRef{c.flp, c.snapshotVarp, VAccess::WRITE},
new AstVarRef{c.flp, c.disableCntVarp, VAccess::READ}};
bodyp = AstNode::addNextNull(bodyp, snapshotp);
}
if (!bodyp) return;
m_modp->addStmtsp(
new AstAlways{c.flp, VAlwaysKwd::ALWAYS, c.senTreep->cloneTree(false), bodyp});
}
// Phase 2b: Counter FSM always block.
// if (active) { if (done) active<=0; else counter<=counter+1; }
// else if (incoming) { active<=1; counter<=0; }
void emitCounterFsmNba(LowerCtx& c) {
for (int ci = 0; ci < c.N; ++ci) {
if (!c.vtx[ci]->datap()->counterActiveVarp) continue;
AstVar* const activep = c.vtx[ci]->datap()->counterActiveVarp;
AstVar* const cntp = c.vtx[ci]->datap()->counterCountVarp;
const uint32_t counterMax = static_cast<uint32_t>(c.vtx[ci]->m_counterMax);
// Phase 2b: Bitset ring-buffer delay always block.
void emitDelayRingNba(LowerCtx& c) {
for (int ri = 0; ri < c.N; ++ri) {
SvaStateVertex* const vtxp = c.vtx[ri];
if (!vtxp->datap()->delayRingVarp) continue;
AstVar* const ringp = vtxp->datap()->delayRingVarp;
AstVar* const idxp = vtxp->datap()->delayRingIdxVarp;
const uint32_t size = static_cast<uint32_t>(vtxp->m_delayRingSize);
// Builder only adds clocked edges to counter vertices (guardedEdge
// in buildSExpr), so m_consumesCycle is always true here.
AstNodeExpr* incomingp = nullptr;
for (const SvaTransEdge* const tep : c.edges) {
const int toIdx = tep->toVtxp()->color();
if (toIdx != ci) continue;
if (static_cast<int>(tep->toVtxp()->color()) != ri) continue;
UASSERT_OBJ(tep->m_consumesCycle == vtxp->m_isFixedDelayRing, vtxp,
"Delay-ring incoming edge kind mismatch");
const int fi = tep->fromVtxp()->color();
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp, c.vtx[fi],
"Clocked edge source missing stateSig");
"Delay-ring incoming source missing stateSig");
AstNodeExpr* contribp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false);
contribp = andCond(c.flp, contribp, tep->m_condp);
if (c.disableExprp) {
if (c.disableExprp && !c.snapshotVarp) {
AstNodeExpr* const notDisp
= new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)};
contribp = new AstLogAnd{c.flp, contribp, notDisp};
}
incomingp = orExprs(c.flp, incomingp, contribp);
}
UASSERT_OBJ(incomingp, c.vtx[ci], "Counter vertex has no incoming contribution");
// Counter window is always [0, m_counterMax]; M offset is handled by
// the pre-chain in buildSExpr, so every tick inside an active count
// is in-window.
AstNodeExpr* inWindowp = new AstConst{c.flp, AstConst::BitTrue{}};
AstNodeExpr* matchedNowp = nullptr;
if (c.matchCondp) {
matchedNowp
= new AstLogAnd{c.flp, inWindowp, sampled(c.matchCondp->cloneTreePure(false))};
} else { // LCOV_EXCL_LINE -- no counter-FSM caller leaves matchCondp null
matchedNowp = inWindowp; // LCOV_EXCL_LINE
UASSERT_OBJ(incomingp, vtxp, "Delay ring has no incoming edge");
AstNode* updateBodyp = nullptr;
if (vtxp->m_isFixedDelayRing) {
// ring[idx] <= incoming;
updateBodyp = new AstAssignDly{
c.flp,
delayRingBit(c.flp, ringp, new AstVarRef{c.flp, idxp, VAccess::READ},
VAccess::WRITE),
incomingp};
} else {
// ring[next_idx] <= 1'b0; ring[idx] <= incoming;
AstAssignDly* const clearExpirep = new AstAssignDly{
c.flp,
delayRingBit(c.flp, ringp, nextRingIndex(c.flp, idxp, size), VAccess::WRITE),
new AstConst{c.flp, AstConst::BitFalse{}}};
AstAssignDly* const writeIncomingp = new AstAssignDly{
c.flp,
delayRingBit(c.flp, ringp, new AstVarRef{c.flp, idxp, VAccess::READ},
VAccess::WRITE),
incomingp};
clearExpirep->addNext(writeIncomingp);
updateBodyp = clearExpirep;
}
AstNodeExpr* const counterAtEndp
= new AstEq{c.flp, new AstVarRef{c.flp, cntp, VAccess::READ},
new AstConst{c.flp, AstConst::WidthedValue{}, 32, counterMax}};
AstNodeExpr* clearCondp = nullptr;
if (vtxp->m_delayRingClearCondp) {
clearCondp = sampled(vtxp->m_delayRingClearCondp->cloneTreePure(false));
}
if (c.disableExprp && !c.snapshotVarp) {
clearCondp = orExprs(c.flp, clearCondp, c.disableExprp->cloneTreePure(false));
}
AstNodeExpr* guardp = nullptr;
for (AstNodeExpr* const cp : vtxp->m_throughoutConds) {
AstNodeExpr* const sampledp = sampled(cp->cloneTreePure(false));
guardp = guardp ? static_cast<AstNodeExpr*>(new AstLogAnd{c.flp, guardp, sampledp})
: sampledp;
}
if (guardp) clearCondp = orExprs(c.flp, clearCondp, new AstLogNot{c.flp, guardp});
if (clearCondp) {
// if (clear) ring <= '0;
AstConst* const zerop = new AstConst{c.flp, AstConst::DTyped{}, ringp->dtypep()};
zerop->num().setAllBits0();
updateBodyp = new AstIf{
c.flp, clearCondp,
new AstAssignDly{c.flp, new AstVarRef{c.flp, ringp, VAccess::WRITE}, zerop},
updateBodyp};
}
// idx <= next_idx;
updateBodyp->addNext(new AstAssignDly{c.flp,
new AstVarRef{c.flp, idxp, VAccess::WRITE},
nextRingIndex(c.flp, idxp, size)});
AstNodeExpr* const donep = new AstLogOr{
c.flp, killActive(c), new AstLogOr{c.flp, matchedNowp, counterAtEndp}};
AstAssignDly* const clearActivep
= new AstAssignDly{c.flp, new AstVarRef{c.flp, activep, VAccess::WRITE},
new AstConst{c.flp, AstConst::BitFalse{}}};
AstAdd* const addExprp
= new AstAdd{c.flp, new AstVarRef{c.flp, cntp, VAccess::READ},
new AstConst{c.flp, AstConst::WidthedValue{}, 32, 1u}};
addExprp->dtypeFrom(cntp);
AstAssignDly* const incCountp
= new AstAssignDly{c.flp, new AstVarRef{c.flp, cntp, VAccess::WRITE}, addExprp};
AstIf* const doneIfp = new AstIf{c.flp, donep, clearActivep, incCountp};
AstAssignDly* const setActivep
= new AstAssignDly{c.flp, new AstVarRef{c.flp, activep, VAccess::WRITE},
new AstConst{c.flp, AstConst::BitTrue{}}};
AstAssignDly* const resetCountp
= new AstAssignDly{c.flp, new AstVarRef{c.flp, cntp, VAccess::WRITE},
new AstConst{c.flp, AstConst::WidthedValue{}, 32, 0u}};
setActivep->addNext(resetCountp);
AstIf* const startIfp
= new AstIf{c.flp, gateNotKill(c, incomingp), setActivep, nullptr};
AstIf* const topIfp = new AstIf{c.flp, new AstVarRef{c.flp, activep, VAccess::READ},
doneIfp, startIfp};
m_modp->addStmtsp(
new AstAlways{c.flp, VAlwaysKwd::ALWAYS, c.senTreep->cloneTree(false), topIfp});
m_modp->addStmtsp(new AstAlways{c.flp, VAlwaysKwd::ALWAYS,
c.senTreep->cloneTree(false), updateBodyp});
}
}
@ -1880,16 +1933,22 @@ class SvaNfaLowering final {
outPerMidSrcsp->push_back(perMidp);
}
if (tep->fromVtxp()->m_isCounter) {
if (tep->fromVtxp()->m_delayRingSize && !tep->fromVtxp()->m_isFixedDelayRing) {
sigs.terminalActivep
= orExprs(c.flp, sigs.terminalActivep, srcSigp->cloneTreePure(false));
AstNodeExpr* const atEndp = new AstEq{
c.flp,
new AstVarRef{c.flp, c.vtx[fi]->datap()->counterCountVarp, VAccess::READ},
new AstConst{c.flp, AstConst::WidthedValue{}, 32,
static_cast<uint32_t>(tep->fromVtxp()->m_counterMax)}};
AstNodeExpr* const expireContribp = new AstLogAnd{c.flp, srcSigp, atEndp};
AstVar* const ringp = c.vtx[fi]->datap()->delayRingVarp;
AstVar* const idxp = c.vtx[fi]->datap()->delayRingIdxVarp;
const uint32_t size = static_cast<uint32_t>(c.vtx[fi]->m_delayRingSize);
// reject |= ring[next_idx] && final_condition;
AstNodeExpr* expireContribp
= delayRingBit(c.flp, ringp, nextRingIndex(c.flp, idxp, size));
expireContribp = andCond(c.flp, expireContribp, tep->m_condp);
if (snapshotOkp) {
expireContribp
= new AstLogAnd{c.flp, expireContribp, snapshotOkp->cloneTreePure(false)};
}
sigs.rejectBasep = orExprs(c.flp, sigs.rejectBasep, expireContribp);
VL_DO_DANGLING(srcSigp->deleteTree(), srcSigp);
} else if (tep->fromVtxp()->m_isUnbounded || tep->fromVtxp()->m_isAndCombiner) {
sigs.terminalActivep = orExprs(c.flp, sigs.terminalActivep, srcSigp);
} else {
@ -1913,6 +1972,10 @@ class SvaNfaLowering final {
AstNodeExpr* stateExprp = nullptr;
if (c.vtx[i]->datap()->stateVarp) {
stateExprp = new AstVarRef{c.flp, c.vtx[i]->datap()->stateVarp, VAccess::READ};
} else if (c.vtx[i]->datap()->delayRingVarp && c.vtx[i]->m_isFixedDelayRing) {
// fixed_chain_active = |ring;
stateExprp = new AstRedOr{
c.flp, new AstVarRef{c.flp, c.vtx[i]->datap()->delayRingVarp, VAccess::READ}};
} else {
UASSERT_OBJ(c.vtx[i]->datap()->stateSigp, c.vtx[i],
"Throughout-conds vertex missing state representation");
@ -2027,10 +2090,18 @@ class SvaNfaLowering final {
if (c.vtx[i]->datap()->stateVarp) {
c.vtx[i]->datap()->stateSigp
= new AstVarRef{c.flp, c.vtx[i]->datap()->stateVarp, VAccess::READ};
} else if (c.vtx[i]->datap()->counterActiveVarp) {
// Counter window is always [0, m_counterMax]; see buildSExpr.
c.vtx[i]->datap()->stateSigp
= new AstVarRef{c.flp, c.vtx[i]->datap()->counterActiveVarp, VAccess::READ};
} else if (c.vtx[i]->datap()->delayRingVarp) {
if (c.vtx[i]->m_isFixedDelayRing) {
// state = ring[idx];
c.vtx[i]->datap()->stateSigp = delayRingBit(
c.flp, c.vtx[i]->datap()->delayRingVarp,
new AstVarRef{c.flp, c.vtx[i]->datap()->delayRingIdxVarp, VAccess::READ});
} else {
// state = |ring;
c.vtx[i]->datap()->stateSigp = new AstRedOr{
c.flp,
new AstVarRef{c.flp, c.vtx[i]->datap()->delayRingVarp, VAccess::READ}};
}
}
}
// Fixed-point propagation along zero-delay (Link) edges.
@ -2247,18 +2318,22 @@ public:
vtx[i]->datap()->doneRVarp = rp;
continue;
}
if (vtx[i]->m_isCounter) {
const std::string base = baseName + "__c" + std::to_string(i);
AstVar* const activep = new AstVar{flp, VVarType::MODULETEMP, base + "_active",
m_modp->findBitDType()};
activep->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(activep);
vtx[i]->datap()->counterActiveVarp = activep;
AstVar* const cntp
= new AstVar{flp, VVarType::MODULETEMP, base + "_cnt", u32DTypep};
cntp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(cntp);
vtx[i]->datap()->counterCountVarp = cntp;
if (vtx[i]->m_delayRingSize) {
const std::string base = baseName + "__d" + std::to_string(i);
// bit [size-1:0] ring;
AstNodeDType* const ringDTypep = m_modp->findBitDType(
vtx[i]->m_delayRingSize, vtx[i]->m_delayRingSize, VSigning::UNSIGNED);
AstVar* const ringp
= new AstVar{flp, VVarType::MODULETEMP, base + "_ring", ringDTypep};
ringp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(ringp);
vtx[i]->datap()->delayRingVarp = ringp;
// int unsigned idx;
AstVar* const idxp
= new AstVar{flp, VVarType::MODULETEMP, base + "_idx", u32DTypep};
idxp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(idxp);
vtx[i]->datap()->delayRingIdxVarp = idxp;
continue;
}
if (!vtx[i]->datap()->needsReg) continue;
@ -2279,9 +2354,9 @@ public:
// Phase 1: Resolve combinational Links via fixed-point propagation.
resolveLinks(c, triggerExprp);
// Phase 2/2b/2c: Emit NBA state-update, counter FSM, and SAnd done-latch logic.
// Phase 2/2b/2c: Emit NBA state-update, delay-ring, and SAnd done-latch logic.
emitStateRegisterNba(c);
emitCounterFsmNba(c);
emitDelayRingNba(c);
emitAndCombinerDoneLatchNba(c);
emitKillAckNba(c);
@ -2876,11 +2951,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()) {
@ -2892,12 +2973,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);
@ -2939,13 +3017,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;
@ -2999,6 +3080,8 @@ class AssertNfaVisitor final : public VNVisitor {
}
UINFO(4, "NFA converted assertion at " << flp << endl);
if (dumpGraphLevel() >= 6) graph.m_graph.dumpDotFilePrefixed("assert-nfa");
}
// VISITORS

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

@ -94,6 +94,7 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode {
// @astgen op4 := scopeNamep : Optional[AstScopeName]
string m_name; // Name of task
string m_cname; // Name of task if DPI import
string m_dpiCDecl; // Custom DPI-C function declaration
string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8)
uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees
bool m_taskPublic : 1; // Public task
@ -199,6 +200,9 @@ public:
void dpiOpenChild(bool flag) { m_dpiOpenChild = flag; }
bool dpiTask() const { return m_dpiTask; }
void dpiTask(bool flag) { m_dpiTask = flag; }
bool dpiCDeclOverride() const { return !m_dpiCDecl.empty(); }
const string& dpiCDecl() const { return m_dpiCDecl; }
void dpiCDecl(const string& cDecl) { m_dpiCDecl = cDecl; }
bool isConstructor() const { return m_isConstructor; }
void isConstructor(bool flag) { m_isConstructor = flag; }
bool isHideLocal() const { return m_isHideLocal; }
@ -511,6 +515,7 @@ class AstCFunc final : public AstNode {
string m_rtnType; // void, bool, or other return type
string m_argTypes; // Argument types
string m_ifdef; // #ifdef symbol around this function
string m_cDecl; // Custom DPI-C function declaration
VBoolOrUnknown m_isConst; // Function is declared const (*this not changed)
bool m_isStatic : 1; // Function is static (no need for a 'this' pointer)
bool m_isTrace : 1; // Function is related to tracing
@ -640,6 +645,9 @@ public:
void dpiImportPrototype(bool flag) { m_dpiImportPrototype = flag; }
bool dpiImportWrapper() const { return m_dpiImportWrapper; }
void dpiImportWrapper(bool flag) { m_dpiImportWrapper = flag; }
bool dpiCDeclOverride() const { return !m_cDecl.empty(); }
const string& dpiCDecl() const { return m_cDecl; }
void dpiCDecl(const string& cDecl) { m_cDecl = cDecl; }
bool isCoroutine() const { return m_rtnType == "VlCoroutine"; }
void recursive(bool flag) { m_recursive = flag; }
bool recursive() const { return m_recursive; }

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

@ -385,8 +385,7 @@ class CoverageVisitor final : public VNVisitor {
VL_RESTORER(m_state);
VL_RESTORER(m_exprStmtsp);
VL_RESTORER(m_inToggleOff);
// skip properties for expresison coverage
if (!VN_IS(nodep, Property)) m_exprStmtsp = nodep;
m_exprStmtsp = nodep;
m_inToggleOff = true;
createHandle(nodep);
iterateChildren(nodep);
@ -745,6 +744,11 @@ class CoverageVisitor final : public VNVisitor {
newCoverInc(nodep->fileline(), declp, m_beginHier + "_vlCoverageUserTrace"));
}
}
void visit(AstPropSpec* nodep) override {
VL_RESTORER(m_exprStmtsp);
m_exprStmtsp = nullptr;
iterateChildren(nodep);
}
void visit(AstStop* nodep) override {
UINFO(4, " STOP: " << nodep);
m_state.m_on = false;

View File

@ -1692,32 +1692,37 @@ class FunctionalCoverageVisitor final : public VNVisitor {
}
// VISITORS
AstNode* findEnclosingMemberRef(AstClass* cgClassp) {
// An embedded covergroup is lowered into a sibling AstClass that has no handle to
// the enclosing object. A coverpoint/iff/cross expression that references a
// (non-static) member of the enclosing class therefore emits C++ that accesses the
// member as if it were static ("invalid use of non-static data member"). Detect
// such references so the caller can skip lowering with a clean warning instead of
// producing uncompilable code. Returns the first offending node, or nullptr.
// Collect the covergroup class's own member variables (sample/constructor args);
// references to those are legitimate.
AstNode* findUnsupportedCoverpointRef(AstClass* cgClassp) {
// An embedded covergroup is lowered into a sibling AstClass that (currently) has
// no handle to the enclosing object. Identify refs to the containing context
// or a formal param and flag as unsupported
std::set<const AstVar*> ownVars;
for (AstNode* itemp = cgClassp->membersp(); itemp; itemp = itemp->nextp()) {
if (const AstVar* const varp = VN_CAST(itemp, Var)) ownVars.insert(varp);
}
// Flag non-static enclosing-class members reached without a handle as unsupported
AstNode* offenderp = nullptr;
const auto scan = [&](AstNode* rootp) {
const auto scanEnclosing = [&](AstNode* rootp) {
rootp->foreach([&](AstVarRef* refp) {
if (offenderp) return;
const AstVar* const varp = refp->varp(); // Always set post-LinkDot
// A member of another class (the enclosing class) reached with no handle.
// Members of the covergroup class itself (sample/constructor args) are
// legitimate and excluded via ownVars.
const AstVar* const varp = refp->varp();
if (varp->isClassMember() && !ownVars.count(varp)) offenderp = refp;
});
};
for (AstCoverpoint* cpp : m_coverpoints) scan(cpp);
for (AstCoverCross* crossp : m_coverCrosses) scan(crossp);
for (AstCoverpoint* cpp : m_coverpoints) scanEnclosing(cpp);
for (AstCoverCross* crossp : m_coverCrosses) scanEnclosing(crossp);
if (offenderp) return offenderp;
// Flag references to covergroup formal parameters as currently unsupported
const auto scanHandleDeref = [&](AstNode* rootp) {
if (!rootp) return;
rootp->foreach([&](AstMemberSel* selp) {
if (!offenderp) offenderp = selp;
});
};
for (AstCoverpoint* cpp : m_coverpoints) {
scanHandleDeref(cpp->exprp());
scanHandleDeref(cpp->iffp());
}
return offenderp;
}
@ -1801,16 +1806,16 @@ class FunctionalCoverageVisitor final : public VNVisitor {
iterateChildren(nodep);
// Option B safety net for embedded covergroups: if a coverpoint/iff/cross
// references a member of the enclosing class, lowering would emit uncompilable
// C++ (no handle to the enclosing instance). Skip this covergroup with a clean
// warning rather than crashing the C++ compile. (Full support - an enclosing
// back-pointer - is the planned follow-up.)
if (AstNode* const offenderp = findEnclosingMemberRef(nodep)) {
// Identify embedded covergroup refs to enclosing class members or
// covergroup formal parameters and flag as currently unsupported.
if (AstNode* const offenderp = findUnsupportedCoverpointRef(nodep)) {
const bool viaHandle = VN_IS(offenderp, MemberSel);
offenderp->v3warn(COVERIGN,
"Unsupported: 'covergroup' coverpoint referencing enclosing "
"class member; ignoring covergroup "
<< nodep->prettyNameQ());
"Unsupported: 'covergroup' coverpoint "
<< (viaHandle ? "dereferencing a class handle member "
"(parameterized covergroup)"
: "referencing enclosing class member")
<< "; ignoring covergroup " << nodep->prettyNameQ());
for (AstCoverpoint* cpp : m_coverpoints) {
VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp);
}

View File

@ -34,180 +34,6 @@ DfgGraph::~DfgGraph() {
forEachVertex([&](DfgVertex& vtx) { vtx.unlinkDelete(*this); });
}
std::unique_ptr<DfgGraph> DfgGraph::clone() const {
// Create the new graph
DfgGraph* const clonep = new DfgGraph{name()};
// Map from original vertex to clone
std::unordered_map<const DfgVertex*, DfgVertex*> vtxp2clonep(size() * 2);
// Clone constVertices
for (const DfgConst& vtx : m_constVertices) {
DfgConst* const cp = new DfgConst{*clonep, vtx.fileline(), vtx.num()};
vtxp2clonep.emplace(&vtx, cp);
}
// Clone variable vertices
for (const DfgVertexVar& vtx : m_varVertices) {
const DfgVertexVar* const vp = vtx.as<DfgVertexVar>();
DfgVertexVar* cp = nullptr;
switch (vtx.type()) {
case VDfgType::VarArray: {
cp = new DfgVarArray{*clonep, vp->vscp()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::VarPacked: {
cp = new DfgVarPacked{*clonep, vp->vscp()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
default: {
vtx.v3fatalSrc("Unhandled variable vertex type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
if (AstVarScope* const tmpForp = vp->tmpForp()) cp->tmpForp(tmpForp);
}
// Clone ast reference vertices
for (const DfgVertexAst& vtx : m_astVertices) { // LCOV_EXCL_START
switch (vtx.type()) {
case VDfgType::AstRd: {
const DfgAstRd* const vp = vtx.as<DfgAstRd>();
DfgAstRd* const cp = new DfgAstRd{*clonep, vp->exprp(), vp->inSenItem(), vp->inLoop()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
default: {
vtx.v3fatalSrc("Unhandled ast reference vertex type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
} // LCOV_EXCL_STOP
// Clone operation vertices
for (const DfgVertex& vtx : m_opVertices) {
switch (vtx.type()) {
#include "V3Dfg__gen_clone_cases.h" // From ./astgen
case VDfgType::CReset: { // LCOV_EXCL_START - No algorithm actually hits this today
DfgCReset* const cp = new DfgCReset{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
} // LCOV_EXCL_STOP
case VDfgType::MatchMasked: {
DfgMatchMasked* const cp = new DfgMatchMasked{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::Sel: {
DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtype()};
cp->lsb(vtx.as<DfgSel>()->lsb());
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::Rep: {
DfgRep* const cp = new DfgRep{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::UnitArray: {
DfgUnitArray* const cp = new DfgUnitArray{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::Mux: {
DfgMux* const cp = new DfgMux{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::SpliceArray: {
DfgSpliceArray* const cp = new DfgSpliceArray{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::SplicePacked: {
DfgSplicePacked* const cp = new DfgSplicePacked{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::Logic: {
vtx.v3fatalSrc("DfgLogic cannot be cloned");
VL_UNREACHABLE;
break;
}
case VDfgType::Unresolved: {
vtx.v3fatalSrc("DfgUnresolved cannot be cloned");
VL_UNREACHABLE;
break;
}
case VDfgType::AstRd: // LCOV_EXCL_START
case VDfgType::Const:
case VDfgType::VarArray:
case VDfgType::VarPacked: {
vtx.v3fatalSrc("Vertex should have been handled above: " + vtx.typeName());
VL_UNREACHABLE;
break;
} // LCOV_EXCL_STOP
}
}
UASSERT(size() == clonep->size(), "Size of clone should be the same");
// Constants have no inputs
// Hook up inputs of cloned variables
for (const DfgVertexVar& vtx : m_varVertices) {
DfgVertexVar* const cp = vtxp2clonep.at(&vtx)->as<DfgVertexVar>();
if (const DfgVertex* const srcp = vtx.srcp()) cp->srcp(vtxp2clonep.at(srcp));
if (const DfgVertex* const defp = vtx.defaultp()) cp->defaultp(vtxp2clonep.at(defp));
}
// Hook up inputs of cloned ast references
for (const DfgVertexAst& vtx : m_astVertices) { // LCOV_EXCL_START
switch (vtx.type()) {
case VDfgType::AstRd: {
const DfgAstRd* const vp = vtx.as<DfgAstRd>();
DfgAstRd* const cp = vtxp2clonep.at(&vtx)->as<DfgAstRd>();
if (const DfgVertex* const srcp = vp->srcp()) cp->srcp(vtxp2clonep.at(srcp));
break;
}
default: {
vtx.v3fatalSrc("Unhandled DfgVertexAst sub type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
} // LCOV_EXCL_STOP
// Hook up inputs of cloned operation vertices
for (const DfgVertex& vtx : m_opVertices) {
if (vtx.is<DfgVertexVariadic>()) {
switch (vtx.type()) {
case VDfgType::SpliceArray:
case VDfgType::SplicePacked: {
const DfgVertexSplice* const vp = vtx.as<DfgVertexSplice>();
DfgVertexSplice* const cp = vtxp2clonep.at(vp)->as<DfgVertexSplice>();
vp->foreachDriver([&](const DfgVertex& src, uint32_t lo, FileLine* flp) {
cp->addDriver(vtxp2clonep.at(&src), lo, flp);
return false;
});
break;
}
default: {
vtx.v3fatalSrc("Unhandled DfgVertexVariadic sub type: " + vtx.typeName());
VL_UNREACHABLE;
break;
}
}
} else {
DfgVertex* const cp = vtxp2clonep.at(&vtx);
for (size_t i = 0; i < vtx.nInputs(); ++i) {
cp->inputp(i, vtxp2clonep.at(vtx.inputp(i)));
}
}
}
return std::unique_ptr<DfgGraph>{clonep};
}
void DfgGraph::mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) {
if (otherps.empty()) return;

View File

@ -487,9 +487,6 @@ public:
for (const DfgVertex& vtx : m_opVertices) f(vtx);
}
// Return an identical, independent copy of this graph. Vertex and edge order might differ.
std::unique_ptr<DfgGraph> clone() const VL_MT_DISABLED;
// Merge contents of other graphs into this graph. Deletes the other graphs.
// DfgVertexVar instances representing the same Ast variable are unified.
void mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) VL_MT_DISABLED;

View File

@ -1583,35 +1583,18 @@ public:
}
};
std::pair<std::unique_ptr<DfgGraph>, bool> //
breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
bool breakCycles(DfgGraph& dfg, V3DfgBreakCyclesContext& ctx) {
// Shorthand for dumping graph at given dump level
const auto dump = [&](int level, const DfgGraph& dfg, const std::string& name) {
if (dumpDfgLevel() >= level) dfg.dumpDotFilePrefixed("breakCycles-" + name);
};
// Can't do much with trivial things ('a = a' or 'a[1] = a[0]'), so bail
if (dfg.size() <= 2) {
UINFO(7, "Graph is trivial");
dump(9, dfg, "trivial");
++ctx.m_breakCyclesContext.m_nTrivial;
return {nullptr, false};
}
// AstNetlist/AstNodeModule user2 used as sequence numbers for temporaries
const VNUser2InUse user2InUse;
// Show input for debugging
dump(7, dfg, "input");
// We might fail to make any improvements, so first create a clone of the
// graph. This is what we will be working on, and return if successful.
// Do not touch the input graph.
std::unique_ptr<DfgGraph> resultp = dfg.clone();
// Just shorthand for code below
DfgGraph& res = *resultp;
dump(9, res, "clone");
// How many improvements have we made
size_t nImprovements = 0;
size_t prevNImprovements;
@ -1619,16 +1602,16 @@ breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
// Iterate while an improvement can be made and the graph is still cyclic
do {
// Compute SCCs
SccInfo sccInfo{res};
SccInfo sccInfo{dfg};
// Fix up independent ranges in vertices
UINFO(9, "New iteration after " << nImprovements << " improvements");
prevNImprovements = nImprovements;
const size_t nFixed = FixUp::apply(res, sccInfo);
const size_t nFixed = FixUp::apply(dfg, sccInfo);
if (nFixed) {
nImprovements += nFixed;
ctx.m_breakCyclesContext.m_nImprovements += nFixed;
dump(9, res, "FixUp");
ctx.m_nImprovements += nFixed;
dump(9, dfg, "FixUp");
}
// Validate SccInfo if in debug mode
@ -1637,42 +1620,38 @@ breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
// Congrats if it has become acyclic
if (!sccInfo.isCyclic()) {
UINFO(7, "Graph became acyclic after " << nImprovements << " improvements");
dump(7, res, "result-acyclic");
++ctx.m_breakCyclesContext.m_nFixed;
return {std::move(resultp), true};
dump(7, dfg, "result-acyclic");
++ctx.m_nFixed;
return true;
}
} while (nImprovements != prevNImprovements);
// Debug dump
if (dumpDfgLevel() >= 9) {
const SccInfo sccInfo{res};
res.dumpDotFilePrefixed("breakCycles-remaining", [&](const DfgVertex& vtx) {
const SccInfo sccInfo{dfg};
dfg.dumpDotFilePrefixed("breakCycles-remaining", [&](const DfgVertex& vtx) {
return sccInfo.get(vtx); //
});
}
// If an improvement was made, return the still cyclic improved graph
// Accounting
if (nImprovements) {
UINFO(7, "Graph was improved " << nImprovements << " times");
dump(7, res, "result-improved");
++ctx.m_breakCyclesContext.m_nImproved;
return {std::move(resultp), false};
dump(7, dfg, "result-improved");
++ctx.m_nImproved;
} else {
UINFO(7, "Graph NOT improved");
dump(7, dfg, "result-original");
++ctx.m_nUnchanged;
}
// No improvement was made
UINFO(7, "Graph NOT improved");
dump(7, res, "result-original");
++ctx.m_breakCyclesContext.m_nUnchanged;
return {nullptr, false};
return false;
}
} //namespace V3DfgBreakCycles
std::pair<std::unique_ptr<DfgGraph>, bool> //
V3DfgPasses::breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
auto pair = V3DfgBreakCycles::breakCycles(dfg, ctx);
if (pair.first) {
if (v3Global.opt.debugCheck()) V3DfgPasses::typeCheck(*pair.first);
V3DfgPasses::removeUnused(*pair.first);
}
return pair;
bool V3DfgPasses::breakCycles(DfgGraph& dfg, V3DfgContext& ctx) {
const bool res = V3DfgBreakCycles::breakCycles(dfg, ctx.m_breakCyclesContext);
if (v3Global.opt.debugCheck()) V3DfgPasses::typeCheck(dfg);
V3DfgPasses::removeUnused(dfg);
return res;
}

View File

@ -100,9 +100,8 @@ class V3DfgBreakCyclesContext final : public V3DfgSubContext {
public:
// STATE
VDouble0 m_nFixed; // Number of graphs that became acyclic
VDouble0 m_nImproved; // Number of graphs that were imporoved but still cyclic
VDouble0 m_nImproved; // Number of graphs that were improved but still cyclic
VDouble0 m_nUnchanged; // Number of graphs that were left unchanged
VDouble0 m_nTrivial; // Number of graphs that were not changed
VDouble0 m_nImprovements; // Number of changes made to graphs
private:
@ -112,7 +111,6 @@ private:
addStat("made acyclic", m_nFixed);
addStat("improved", m_nImproved);
addStat("left unchanged", m_nUnchanged);
addStat("trivial", m_nTrivial);
addStat("changes applied", m_nImprovements);
}
};

View File

@ -126,19 +126,15 @@ class DataflowOptimize final {
std::vector<std::unique_ptr<DfgGraph>> madeAcyclicComponents;
if (v3Global.opt.fDfgBreakCycles()) {
for (auto it = cyclicComps.begin(); it != cyclicComps.end();) {
auto result = V3DfgPasses::breakCycles(**it, m_ctx);
if (!result.first) {
// No improvement, moving on.
const bool madeAcyclic = V3DfgPasses::breakCycles(**it, m_ctx);
// If not made acyclic, keep it in 'cyclicComps'
if (!madeAcyclic) {
++it;
} else if (!result.second) {
// Improved, but still cyclic. Replace the original cyclic component.
*it = std::move(result.first);
++it;
} else {
// Result became acyclic. Move to madeAcyclicComponents, delete original.
madeAcyclicComponents.emplace_back(std::move(result.first));
it = cyclicComps.erase(it);
continue;
}
// Otherwise move to 'madeAcyclicComponents'
madeAcyclicComponents.emplace_back(std::move(*it));
it = cyclicComps.erase(it);
}
}
// Merge those that were made acyclic back to the graph, this enables optimizing more

View File

@ -44,14 +44,9 @@ void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
// Remove redundant selects
void removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) VL_MT_DISABLED;
// Attempt to make the given cyclic graph into an acyclic, or "less cyclic"
// equivalent. If the returned pointer is null, then no improvement was
// possible on the input graph. Otherwise the returned graph is an improvement
// on the input graph, with at least some cycles eliminated. The returned
// graph is always independent of the original. If an imporoved graph is
// returned, then the returned 'bool' flag indicated if the returned graph is
// acyclic (flag 'true'), or still cyclic (flag 'false').
std::pair<std::unique_ptr<DfgGraph>, bool> //
breakCycles(const DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
// equivalent. Genuine combinational cycles can exist, so this might be
// unsuccessful. Returns true if the graph became acyclic, false otherwise.
bool breakCycles(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
// Construct binary to oneHot decoders
void binToOneHot(DfgGraph&, V3DfgBinToOneHotContext&) VL_MT_DISABLED;
// Common subexpression elimination

View File

@ -137,6 +137,10 @@ void EmitCBaseVisitorConst::emitCDefaultConstructor(const AstNodeModule* const m
void EmitCBaseVisitorConst::emitCFuncHeader(const AstCFunc* funcp, const AstNodeModule* modp,
bool withScope) {
if (funcp->slow()) putns(funcp, "VL_ATTR_COLD ");
if (funcp->dpiCDeclOverride()) {
putns(funcp, funcp->dpiCDecl() + ";\n");
return;
}
if (!funcp->isDestructor()) {
putns(funcp, funcp->rtnTypeVoid());
puts(" ");

View File

@ -1434,8 +1434,12 @@ void EmitCSyms::emitDpiHdr() {
if (!firstImp++) puts("\n// DPI IMPORTS\n");
putsDecoration(nodep, "// DPI import" + sourceLoc + "\n");
}
putns(nodep, "extern " + nodep->rtnTypeVoid() + " " + nodep->nameProtect() + "("
+ cFuncArgs(nodep) + ");\n");
if (nodep->dpiCDeclOverride()) {
putns(nodep, "extern " + nodep->dpiCDecl() + ";\n");
} else {
putns(nodep, "extern " + nodep->rtnTypeVoid() + " " + nodep->nameProtect() + "("
+ cFuncArgs(nodep) + ");\n");
}
}
puts("\n");

View File

@ -545,6 +545,19 @@ void V3PreProcImp::comment(const string& text) {
if (arg.size() && baseCmd == "public_flat_rw_on")
baseCmd += "_sns"; // different cmd for applying sensitivity
if (!printed) insertUnreadback("/*verilator " + baseCmd + "*/ " + arg + " /**/");
} else if (VString::startsWith(cmd, "dpi_c_decl")) {
// "/*verilator dpi_c_decl foo(bar) */" -> "/*verilator dpi_c_decl*/ \"foo(bar)\""
string baseCmd = cmd.substr(0, std::strlen("dpi_c_decl"));
string::size_type startOfArg = baseCmd.size();
while (std::isspace(cmd[startOfArg])) startOfArg++;
string arg = cmd.substr(startOfArg);
if (arg.empty()) {
fileline()->v3warn(
BADVLTPRAGMA,
"No function declaration provided in /*verilator dpi_c_decl*/ meta-comment");
return;
}
if (!printed) insertUnreadback("/*verilator " + baseCmd + "*/ \"" + arg + "\" /**/");
} else {
if (!printed) insertUnreadback("/*verilator " + cmd + "*/");
}

View File

@ -4499,39 +4499,59 @@ class RandomizeVisitor final : public VNVisitor {
return buckets;
}
static bool distBoundRefsRandVar(const AstNode* boundp) {
bool found = false;
boundp->foreach([&](const AstVarRef* vrefp) {
if (vrefp->varp()->rand().isRandomizable()) found = true;
});
return found;
}
// (distExpr >= lo) && (distExpr <= hi); signed comparisons for signed vars
static AstNodeExpr* newDistRangeMembership(AstDist* distp, const AstInsideRange* irp) {
FileLine* const fl = distp->fileline();
const bool isSigned = distp->exprp()->isSigned();
AstNodeExpr* const distExprGtep = distp->exprp()->cloneTreePure(false);
AstNodeExpr* const distExprLtep = distp->exprp()->cloneTreePure(false);
distExprGtep->user1(true);
distExprLtep->user1(true);
AstNodeExpr* const gep
= isSigned ? static_cast<AstNodeExpr*>(
new AstGteS{fl, distExprGtep, irp->lhsp()->cloneTreePure(false)})
: static_cast<AstNodeExpr*>(
new AstGte{fl, distExprGtep, irp->lhsp()->cloneTreePure(false)});
AstNodeExpr* const lep
= isSigned ? static_cast<AstNodeExpr*>(
new AstLteS{fl, distExprLtep, irp->rhsp()->cloneTreePure(false)})
: static_cast<AstNodeExpr*>(
new AstLte{fl, distExprLtep, irp->rhsp()->cloneTreePure(false)});
gep->user1(true);
lep->user1(true);
AstNodeExpr* const andp = new AstLogAnd{fl, gep, lep};
andp->user1(true);
return andp;
}
// Membership test for one bucket: a range comparison or an equality.
static AstNodeExpr* newDistMembershipTerm(AstDist* distp, AstNodeExpr* rangep) {
if (const AstInsideRange* const irp = VN_CAST(rangep, InsideRange))
return newDistRangeMembership(distp, irp);
FileLine* const fl = distp->fileline();
AstNodeExpr* const eqExprp = distp->exprp()->cloneTreePure(false);
eqExprp->user1(true);
AstNodeExpr* const eqp = new AstEq{fl, eqExprp, rangep->cloneTreePure(false)};
eqp->user1(true);
return eqp;
}
// Hard constraint that the dist value stays inside the union of its ranges
// (IEEE 1800-2023 18.5.3: values outside the set must never appear).
AstConstraintExpr* buildDistMembership(AstDist* distp,
const std::vector<DistBucket>& buckets) {
FileLine* const fl = distp->fileline();
const bool isSigned = distp->exprp()->isSigned();
AstNodeExpr* unionExprp = nullptr;
for (const auto& bucket : buckets) {
AstNodeExpr* memberp;
if (const AstInsideRange* const irp = VN_CAST(bucket.rangep, InsideRange)) {
AstNodeExpr* const gtExprp = distp->exprp()->cloneTreePure(false);
AstNodeExpr* const ltExprp = distp->exprp()->cloneTreePure(false);
gtExprp->user1(true);
ltExprp->user1(true);
AstNodeExpr* const gep
= isSigned ? static_cast<AstNodeExpr*>(
new AstGteS{fl, gtExprp, irp->lhsp()->cloneTreePure(false)})
: static_cast<AstNodeExpr*>(
new AstGte{fl, gtExprp, irp->lhsp()->cloneTreePure(false)});
AstNodeExpr* const lep
= isSigned ? static_cast<AstNodeExpr*>(
new AstLteS{fl, ltExprp, irp->rhsp()->cloneTreePure(false)})
: static_cast<AstNodeExpr*>(
new AstLte{fl, ltExprp, irp->rhsp()->cloneTreePure(false)});
gep->user1(true);
lep->user1(true);
memberp = new AstLogAnd{fl, gep, lep};
} else {
AstNodeExpr* const eqExprp = distp->exprp()->cloneTreePure(false);
eqExprp->user1(true);
memberp = new AstEq{fl, eqExprp, bucket.rangep->cloneTreePure(false)};
}
memberp->user1(true);
AstNodeExpr* const memberp = newDistMembershipTerm(distp, bucket.rangep);
if (!unionExprp) {
unionExprp = memberp;
} else {
@ -4601,24 +4621,7 @@ class RandomizeVisitor final : public VNVisitor {
AstNodeExpr* unionExprp = nullptr;
for (auto& bucket : buckets) {
AstNodeExpr* termp;
if (const AstInsideRange* const irp = VN_CAST(bucket.rangep, InsideRange)) {
AstNodeExpr* const gtExprp = distp->exprp()->cloneTreePure(false);
AstNodeExpr* const ltExprp = distp->exprp()->cloneTreePure(false);
gtExprp->user1(true);
ltExprp->user1(true);
AstGte* const gtep = new AstGte{fl, gtExprp, irp->lhsp()->cloneTreePure(false)};
gtep->user1(true);
AstLte* const ltep = new AstLte{fl, ltExprp, irp->rhsp()->cloneTreePure(false)};
ltep->user1(true);
termp = new AstLogAnd{fl, gtep, ltep};
termp->user1(true);
} else {
AstNodeExpr* const eqExprp = distp->exprp()->cloneTreePure(false);
eqExprp->user1(true);
termp = new AstEq{fl, eqExprp, bucket.rangep->cloneTreePure(false)};
termp->user1(true);
}
AstNodeExpr* termp = newDistMembershipTerm(distp, bucket.rangep);
// A weight that is zero only at runtime excludes the bucket's values too.
bool runtimeWeight = false;
bucket.weightExprp->foreach([&](const AstNodeVarRef*) { runtimeWeight = true; });
@ -4777,7 +4780,13 @@ class RandomizeVisitor final : public VNVisitor {
AstNode* chainp = nullptr;
for (int i = static_cast<int>(buckets.size()) - 1; i >= 0; --i) {
AstNodeExpr* constraintExprp;
if (const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange)) {
const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange);
if (irp
&& (distBoundRefsRandVar(irp->lhsp()) || distBoundRefsRandVar(irp->rhsp()))) {
// Bounds solved concurrently cannot pin a pre-solve value; softly
// prefer the symbolic range so the hard membership stays satisfiable
constraintExprp = newDistRangeMembership(distp, irp);
} else if (irp) {
// Pick distExpr = lo + rand64() % (hi - lo + 1) for a uniform value in range
AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false);
distExprCopyp->user1(true);

View File

@ -183,6 +183,7 @@ class SubstValidVisitor final : public VNVisitorConst {
}
void visit(AstConst*) override {} // Accelerate
void visit(AstText*) override {} // CExpr/CStmt literal text has no variable dependencies
void visit(AstNodeExpr* nodep) override {
if (!m_valid) return;

View File

@ -1060,6 +1060,7 @@ class TaskVisitor final : public VNVisitor {
// Add DPI Import to top, since it's a global function
m_topScopep->scopep()->addBlocksp(funcp);
funcp->dpiCDecl(nodep->dpiCDecl());
if (!makePortList(nodep, funcp)) return nullptr;
return funcp;
}

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);
@ -3453,9 +3464,11 @@ class WidthVisitor final : public VNVisitor {
for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) {
nextip = itemp->nextp();
itemp = VN_AS(itemp, DistItem)->rangep();
// InsideRange will get replaced with Lte&Gte and finalized later
if (!VN_IS(itemp, InsideRange))
if (VN_IS(itemp, InsideRange)) {
userIterate(itemp, WidthVP{subDTypep, FINAL}.p());
} else {
iterateCheck(nodep, "Dist Item", itemp, CONTEXT_DET, FINAL, subDTypep, EXTEND_EXP);
}
}
// Inside a constraint, V3Randomize handles dist lowering with proper weights,
@ -3530,10 +3543,12 @@ class WidthVisitor final : public VNVisitor {
EXTEND_EXP);
for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) {
nextip = itemp->nextp(); // iterate may cause the node to get replaced
// InsideRange will get replaced with Lte&Gte and finalized later
if (!VN_IS(itemp, InsideRange) && !itemp->dtypep()->isNonPackedArray())
if (VN_IS(itemp, InsideRange)) {
userIterate(itemp, WidthVP{expDTypep, FINAL}.p());
} else if (!itemp->dtypep()->isNonPackedArray()) {
iterateCheck(nodep, "Inside Item", itemp, CONTEXT_DET, FINAL, expDTypep,
EXTEND_EXP);
}
}
AstNodeExpr* exprp;
@ -3625,8 +3640,25 @@ class WidthVisitor final : public VNVisitor {
V3Const::constifyEdit(nodep->lhsp()); // lhsp may change
V3Const::constifyEdit(nodep->rhsp()); // rhsp may change
} else {
userIterateAndNext(nodep->lhsp(), m_vup);
userIterateAndNext(nodep->rhsp(), m_vup);
if (m_vup->prelim()) {
userIterateAndNext(nodep->lhsp(), m_vup);
userIterateAndNext(nodep->rhsp(), m_vup);
}
if (m_vup->final()) {
AstNodeDType* const expDTypep = m_vup->dtypeOverridep(nodep->dtypep());
// Warning waivers match visit_cmp_eq_gt on the lowered Gte/Lte
const int expWidth = expDTypep->width();
const bool waiveLhs = expWidth == 32
&& !(expDTypep->isSigned() && nodep->lhsp()->isSigned())
&& expDTypep->widthMin() >= nodep->lhsp()->width();
const bool waiveRhs = expWidth == 32
&& !(expDTypep->isSigned() && nodep->rhsp()->isSigned())
&& expWidth >= nodep->rhsp()->widthMin();
iterateCheck(nodep, "Range LHS", nodep->lhsp(), CONTEXT_DET, FINAL, expDTypep,
EXTEND_EXP, !waiveLhs);
iterateCheck(nodep, "Range RHS", nodep->rhsp(), CONTEXT_DET, FINAL, expDTypep,
EXTEND_EXP, !waiveRhs);
}
}
nodep->dtypeFrom(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

@ -1279,29 +1279,6 @@ def write_dfg_auto_classes(filename):
fh.write("\n")
def write_dfg_clone_cases(filename):
with open_file(filename) as fh:
def emitBlock(pattern, **fmt):
fh.write(textwrap.dedent(pattern).format(**fmt))
for node in DfgVertexList:
# Only generate code for automatically derived leaf nodes
if (node.file is not None) or not node.isLeaf:
continue
emitBlock('''\
case VDfgType::{t}: {{
Dfg{t}* const cp = new Dfg{t}{{*clonep, vtx.fileline(), vtx.dtype()}};
vtxp2clonep.emplace(&vtx, cp);
break;
}}
''',
t=node.name,
s=node.superClass.name)
fh.write("\n")
def write_dfg_ast_to_dfg(filename):
with open_file(filename) as fh:
for node in DfgVertexList:
@ -1499,7 +1476,6 @@ if Args.classes:
write_type_tests("Dfg", DfgVertexList)
write_dfg_macros("V3Dfg__gen_macros.h")
write_dfg_auto_classes("V3Dfg__gen_auto_classes.h")
write_dfg_clone_cases("V3Dfg__gen_clone_cases.h")
write_dfg_ast_to_dfg("V3Dfg__gen_ast_to_dfg.h")
write_dfg_dfg_to_ast("V3Dfg__gen_dfg_to_ast.h")

View File

@ -825,6 +825,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
"/*verilator coverage_block_off*/" { FL; return yVL_COVERAGE_BLOCK_OFF; }
"/*verilator coverage_off*/" { FL_FWD; PARSEP->lexFileline()->coverageOn(false); FL_BRK; }
"/*verilator coverage_on*/" { FL_FWD; PARSEP->lexFileline()->coverageOn(true); FL_BRK; }
"/*verilator dpi_c_decl*/" { FL; return yVL_DPI_C_DECL; } // The "foo(bar)" is converted by the preproc
"/*verilator forceable*/" { FL; return yVL_FORCEABLE; }
"/*verilator full_case*/" { FL; return yVL_FULL_CASE; }
"/*verilator hier_block*/" { FL; return yVL_HIER_BLOCK; }

View File

@ -780,6 +780,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yVL_CLOCKER "/*verilator clocker*/"
%token<fl> yVL_CLOCK_ENABLE "/*verilator clock_enable*/"
%token<fl> yVL_COVERAGE_BLOCK_OFF "/*verilator coverage_block_off*/"
%token<fl> yVL_DPI_C_DECL "/*verilator dpi_c_decl*/"
%token<fl> yVL_FORCEABLE "/*verilator forceable*/"
%token<fl> yVL_FULL_CASE "/*verilator full_case*/"
%token<fl> yVL_HIER_BLOCK "/*verilator hier_block*/"
@ -4928,6 +4929,14 @@ dpi_import_export<nodep>: // ==IEEE: dpi_import_export
$5->dpiPure($3 == iprop_PURE);
$5->dpiImport(true);
GRAMMARP->checkDpiVer($1, *$2); v3Global.dpi(true); }
| yIMPORT yaSTRING dpi_tf_import_propertyE dpi_importLabelE function_prototype yVL_DPI_C_DECL yaSTRING ';'
{ $$ = $5;
if (*$4 != "") $5->cname(*$4);
$5->dpiContext($3 == iprop_CONTEXT);
$5->dpiPure($3 == iprop_PURE);
$5->dpiImport(true);
if (*$7 != "") $5->dpiCDecl(*$7);
GRAMMARP->checkDpiVer($1, *$2); v3Global.dpi(true); }
| yIMPORT yaSTRING dpi_tf_import_propertyE dpi_importLabelE task_prototype ';'
{ $$ = $5;
if (*$4 != "") $5->cname(*$4);

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('simulator')
test.compile(verilator_flags2=['--assert --cc --coverage'])
test.execute()
test.passes()

View File

@ -0,0 +1,43 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
integer cyc = 0;
logic rst_n = 0;
logic en = 0;
logic q = 0;
logic [7:0] cnt = 0;
// Synchronous active-low reset driving runtime-varying signals, so the
// asserted and covered properties are not constant-folded away.
always_ff @(posedge clk) begin
rst_n <= (cyc >= 2);
en <= cyc[0];
if (!rst_n) begin
q <= 1'b0;
cnt <= '0;
end else if (en) begin
q <= ~q;
cnt <= cnt + 8'd1;
end
end
a : assert property (@(posedge clk) !rst_n |=> q == 1'b0);
c : cover property (@(posedge clk) disable iff (!rst_n) en && cnt == $past(cnt));
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 10) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
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,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

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
if not test.have_solver:
test.skip("No constraint solver installed")
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,155 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
// verilator lint_off WIDTHEXPAND
class Impl;
rand bit [63:0] x, y;
rand bit [31:0] g;
constraint c {
g inside {[1 : 10]};
y == 64'h100;
y != 0 -> x inside {[y - g : y]};
}
endclass
class Neg; // inside under logical-not
rand bit [63:0] x, y;
rand bit [31:0] g;
constraint c {
g inside {[1 : 10]};
y == 64'h100;
y != 0 -> !(x inside {[y - g : y - 1]});
}
endclass
class LAnd; // inside as a logical-and operand
rand bit [63:0] x, y;
rand bit [31:0] g;
constraint c {
g inside {[1 : 10]};
y == 64'h100;
(x inside {[y - g : y]}) && (x[0] == 1'b0);
}
endclass
class Nest; // nested implication a -> (b -> inside)
rand bit [63:0] x, y;
rand bit [31:0] g;
rand bit a, b;
constraint c {
g inside {[1 : 10]};
y == 64'h100;
a == 1;
b == 1;
a -> (b -> x inside {[y - g : y]});
}
endclass
class CondCtx; // inside as a ?: condition
rand bit [63:0] x, y;
rand bit [31:0] g;
rand bit s;
constraint c {
g inside {[1 : 10]};
y == 64'h100;
(x inside {[y - g : y]}) ? (s == 1'b1) : (s == 1'b0);
}
endclass
class Ctl; // all-32-bit control
rand bit [31:0] x, y, g;
constraint c {
g inside {[1 : 10]};
y == 32'h100;
y != 0 -> x inside {[y - g : y]};
}
endclass
class DistRange; // mixed-width dist range bound
rand bit [63:0] x, y;
rand bit [31:0] g;
constraint c {
g inside {[1 : 10]};
y == 64'h100;
x dist {
[y - g : y] := 1,
5 := 1
};
}
endclass
class Bare; // bare narrow variable as a bound
rand bit [63:0] x, y;
rand bit [31:0] g;
constraint c {
g inside {[1 : 10]};
y == 64'h100;
y != 0 -> x inside {[g : y]};
}
endclass
module t;
Impl im;
Neg ng;
LAnd la;
Nest ne;
CondCtx cx;
Ctl ct;
DistRange dr;
Bare br;
int ok;
initial begin
im = new;
ng = new;
la = new;
ne = new;
cx = new;
ct = new;
dr = new;
br = new;
for (int i = 0; i < 20; ++i) begin
ok = im.randomize();
`checkd(ok, 1);
if (im.x < (64'h100 - im.g) || im.x > 64'h100) `checkd(0, 1);
ok = ng.randomize();
`checkd(ok, 1);
if (ng.x >= (64'h100 - ng.g) && ng.x <= 64'hFF) `checkd(0, 1);
ok = la.randomize();
`checkd(ok, 1);
if (la.x < (64'h100 - la.g) || la.x > 64'h100 || la.x[0] !== 1'b0) `checkd(0, 1);
ok = ne.randomize();
`checkd(ok, 1);
if (ne.x < (64'h100 - ne.g) || ne.x > 64'h100) `checkd(0, 1);
ok = cx.randomize();
`checkd(ok, 1);
if (cx.s !== ((cx.x >= (64'h100 - cx.g)) && (cx.x <= 64'h100))) `checkd(0, 1);
ok = ct.randomize();
`checkd(ok, 1);
if (ct.x < (32'h100 - ct.g) || ct.x > 32'h100) `checkd(0, 1);
ok = dr.randomize();
`checkd(ok, 1);
if (dr.x != 5 && (dr.x < (64'h100 - dr.g) || dr.x > 64'h100)) `checkd(0, 1);
ok = br.randomize();
`checkd(ok, 1);
if (br.x < {32'h0, br.g} || br.x > 64'h100) `checkd(0, 1);
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule
// verilator lint_on WIDTHEXPAND

View File

@ -1,7 +1,11 @@
%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:27:34: Unsupported: 'covergroup' coverpoint referencing enclosing class member; ignoring covergroup '__vlAnonCG_cov_trans'
%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:23:34: Unsupported: 'covergroup' coverpoint referencing enclosing class member; ignoring covergroup '__vlAnonCG_cov_trans'
: ... note: In instance 't'
27 | trans_start_addr: coverpoint trans_collected.addr {option.auto_bin_max = 16;}
23 | trans_start_addr: coverpoint trans_collected.addr {option.auto_bin_max = 16;}
| ^~~~~~~~~~~~~~~
... For warning description see https://verilator.org/warn/COVERIGN?v=latest
... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message.
%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:46:23: Unsupported: 'covergroup' coverpoint dereferencing a class handle member (parameterized covergroup); ignoring covergroup '__vlAnonCG_cov_param'
: ... note: In instance 't'
46 | cp: coverpoint st.test;
| ^~~~
%Error: Exiting due to

View File

@ -5,13 +5,9 @@
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// Test the graceful-degradation safety net for embedded covergroups (the dominant
// UVM pattern: a covergroup declared inside a class whose coverpoints reference the
// enclosing object's members). Such a covergroup is lowered into a sibling class
// with no handle to the enclosing instance, so emitting it would produce
// uncompilable C++ ("invalid use of non-static data member"). Until the enclosing
// back-pointer feature exists, Verilator must emit a clean COVERIGN warning and skip
// lowering the covergroup, rather than crashing the C++ compile.
// Test that two currently-unsupported coverpoint reference styles are properly flagged
// as COVIGN: references to containing-class members ; references to covergroup formal
// parameters
class ubus_transfer;
bit [15:0] addr;
@ -35,10 +31,34 @@ class ubus_master_monitor;
endfunction
endclass
class coverage_state;
bit [3:0] test;
bit [3:0] test2;
endclass
class parameterized_monitor;
coverage_state cs;
// Parameterized covergroup: the coverpoints dereference the class-handle argument 'st'.
// Two handle-dereferencing coverpoints ensure the safety net reports only the first
// offender (a second AstMemberSel is seen with the offender already latched).
covergroup cov_param(coverage_state st);
cp: coverpoint st.test;
cp2: coverpoint st.test2;
endgroup
function new();
cs = new;
cov_param = new(cs);
endfunction
endclass
module t;
ubus_master_monitor m;
parameterized_monitor p;
initial begin
m = new;
p = new;
$write("*-* All Finished *-*\n");
$finish;
end

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

@ -0,0 +1,22 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "Vt_dpi_decl__Dpi.h"
char* func(const char* arg) {
static char str[] = "abc";
return str;
}
// some functions (e.g. getenv() from glibc) may have additional specifier, lack of it in the
// declaration will cause build errors
char* func_with_specifier(const char* arg) throw() {
static char str[] = "efd";
return str;
}

19
test_regress/t/t_dpi_decl.py Executable file
View File

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

View File

@ -0,0 +1,20 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
`define stop $stop
`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
module t;
import "DPI-C" function string func(input string arg) /*verilator dpi_c_decl "char* func(const char*)"*/;
import "DPI-C" function string func_with_specifier(input string arg) /*verilator dpi_c_decl "char* func_with_specifier(const char*) throw()"*/;
initial begin
`checks(func("arg"), "abc");
`checks(func_with_specifier("arg"), "efd");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,5 @@
%Error-BADVLTPRAGMA: t/t_dpi_decl_bad.v:8:57: No function declaration provided in /*verilator dpi_c_decl*/ meta-comment
8 | import "DPI-C" function string func(input string arg) /*verilator dpi_c_decl*/;
| ^~~~~~~~~~~~~~~~~~~~~~~~
... For error description see https://verilator.org/warn/BADVLTPRAGMA?v=latest
%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('simulator')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,14 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
module t;
import "DPI-C" function string func(input string arg) /*verilator dpi_c_decl*/;
initial begin
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=["--binary"])
test.compile(verilator_flags2=["--binary", "-Wno-IEEEMAYDEPRECATE"])
test.execute()

View File

@ -5,7 +5,7 @@
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop // TODO
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
// verilog_format: on
@ -14,11 +14,12 @@
module t;
logic a, b, c, d;
wire e;
and and1 (e, a, b, c);
initial begin
$monitor("%d d=%b,e=%b", $stime, d, e);
d = a & b & c;
assign d = a & b & c;
a = 1;
b = 0;
c = 1;

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.sim_time = 2700
test.compile(timing_loop=True,
verilator_flags2=['--assert', '--timing', '--coverage-user', '--dumpi-graph', '6'])
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 Antmicro
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
`define checkp(gotv,expv_s) do begin string gotv_s; gotv_s = $sformatf("%p", gotv); if ((gotv_s) != (expv_s)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv_s), (expv_s)); `stop; end end while(0);
// verilog_format: on
module t (
input clk
);
localparam int BIG_DELAY = 1024;
localparam int END_CYC = BIG_DELAY + 300;
int cyc = 0;
int fixed_pass_q[$];
int fixed_fail_q[$];
int range_pass_q[$];
int range_fail_q[$];
// Fixed delay: starts 1..12. Odd starts pass; even starts fail.
wire fixed_a = (cyc >= 1 && cyc <= 12);
wire fixed_b = (cyc == BIG_DELAY + 1) || (cyc == BIG_DELAY + 3)
|| (cyc == BIG_DELAY + 5) || (cyc == BIG_DELAY + 7)
|| (cyc == BIG_DELAY + 9) || (cyc == BIG_DELAY + 11);
// Range delay: single passing starts match 10 cycles later. Two blocks never
// match and expire 300 cycles after each start.
wire range_pass_a = (cyc == 10) || (cyc == 40) || (cyc == 70) || (cyc == 100)
|| (cyc == 130) || (cyc == 600) || (cyc == 640)
|| (cyc == 680) || (cyc == 720);
wire range_fail_a = (cyc >= 250 && cyc <= 257) || (cyc >= 820 && cyc <= 827);
wire range_a = range_pass_a || range_fail_a;
wire range_b = (cyc == 20) || (cyc == 50) || (cyc == 80) || (cyc == 110)
|| (cyc == 140) || (cyc == 610) || (cyc == 650) || (cyc == 690)
|| (cyc == 730);
// Questa action blocks observe the cycle after the sampled property cycle.
cover property (@(posedge clk) fixed_a ##1024 fixed_b) fixed_pass_q.push_back($sampled(cyc) + 1);
assert property (@(posedge clk) fixed_a |-> ##1024 fixed_b)
else fixed_fail_q.push_back($sampled(cyc) + 1);
cover property (@(posedge clk) range_a ##[5:300] range_b) range_pass_q.push_back($sampled(cyc) + 1);
assert property (@(posedge clk) range_a |-> ##[5:300] range_b)
else range_fail_q.push_back($sampled(cyc) + 1);
assert property (@(posedge clk)
1'b0 |-> (fixed_a throughout (range_a throughout (range_b ##40 fixed_b))));
assert property (@(posedge clk) 1'b0 |-> ##[5:300] (range_b ##1 fixed_b));
assert property (@(posedge clk) 1'b0 |-> (fixed_a throughout (range_a ##[5:300] range_b)));
always_ff @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == END_CYC) begin
`checkp(fixed_pass_q, "'{'h402, 'h404, 'h406, 'h408, 'h40a, 'h40c}");
`checkp(fixed_fail_q, "'{'h403, 'h405, 'h407, 'h409, 'h40b, 'h40d}");
`checkp(range_pass_q, "'{'h15, 'h33, 'h51, 'h6f, 'h8d, 'h263, 'h28b, 'h2b3, 'h2db}");
`checkp(range_fail_q,
"'{'h227, 'h228, 'h229, 'h22a, 'h22b, 'h22c, 'h22d, 'h22e, 'h461, 'h462, 'h463, 'h464, 'h465, 'h466, 'h467, 'h468}");
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -11,7 +11,7 @@ import vltest_bootstrap
test.scenarios('simulator')
test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing', '--dumpi-V3AssertProp 6'])
test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing'])
test.execute()

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));
@ -80,6 +87,9 @@ module t (
assert property (@(posedge clk) disable iff (cyc < 2)
a |-> ##[1:10000] (a | b | c | d | e));
cover property (@(posedge clk) disable iff (cyc < 2)
##[0:10000] 1'b1);
// Range with binary SExpr: nextStep has delay > 0 after range match
assert property (@(posedge clk) disable iff (cyc < 2)
a |-> b ##[1:2] (a | b | c | d | e) ##3 (a | b | c | d | e))

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