merge upstream master
This commit is contained in:
commit
cc9b7676ba
|
|
@ -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
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -16,6 +16,7 @@ on:
|
|||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ on:
|
|||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: |-
|
||||
|
|
|
|||
|
|
@ -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
|
||||
########################################################################
|
||||
|
|
|
|||
|
|
@ -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: |-
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)'"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
37
configure.ac
37
configure.ac
|
|
@ -129,6 +129,21 @@ AC_ARG_ENABLE([dev-gcov],
|
|||
AC_SUBST(CFG_WITH_DEV_GCOV)
|
||||
AC_MSG_RESULT($CFG_WITH_DEV_GCOV)
|
||||
|
||||
# Flag to reduce the debug info in the debug executable to minimize its size
|
||||
AC_MSG_CHECKING(whether to reduce debug info in the debug executable)
|
||||
AC_ARG_ENABLE([light-debug],
|
||||
[AS_HELP_STRING([--enable-light-debug],
|
||||
[Reduce the amount of debug information in the debug
|
||||
Verilator executable to minimize its size. Enable
|
||||
slight optimization. Enough for backtraces only.])],
|
||||
[case "${enableval}" in
|
||||
yes) CFG_WITH_LIGHT_DEBUG=yes ;;
|
||||
no) CFG_WITH_LIGHT_DEBUG=no ;;
|
||||
*) AC_MSG_ERROR([bad value '${enableval}' for --enable-light-debug]) ;;
|
||||
esac],
|
||||
CFG_WITH_LIGHT_DEBUG=no)
|
||||
AC_MSG_RESULT($CFG_WITH_LIGHT_DEBUG)
|
||||
|
||||
# Special Substitutions - CFG_WITH_DEFENV
|
||||
AC_MSG_CHECKING(whether to use hardcoded paths)
|
||||
AC_ARG_ENABLE([defenv],
|
||||
|
|
@ -518,15 +533,27 @@ _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_PARSER,-Wno-unused)
|
|||
AC_SUBST(CFG_CXXFLAGS_PARSER)
|
||||
|
||||
# Flags for compiling the debug version of Verilator (in addition to above CFG_CXXFLAGS_SRC)
|
||||
if test "$CFG_WITH_DEV_GCOV" = "no"; then # Do not optimize for the coverage build
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-Og)
|
||||
if test "$CFG_WITH_LIGHT_DEBUG" = "yes"; then
|
||||
# Slight optimization and minimal compressed debug info. This is enough for
|
||||
# --gdb/--gdbbt backtraces, but omits the bulk of the debug info, which
|
||||
# significantly reduces object file sizes. For CI or release builds.
|
||||
if test "$CFG_WITH_DEV_GCOV" = "no"; then # Do not optimize for the coverage build
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-Og)
|
||||
fi
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb1)
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-fdebug-info-for-profiling) # For fully-qualified names with clang
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-gz)
|
||||
else
|
||||
# Full debug: no optimization, full debug info, for development.
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-O0)
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb)
|
||||
fi
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-ggdb)
|
||||
_MY_CXX_CHECK_OPT(CFG_CXXFLAGS_DBG,-gz)
|
||||
AC_SUBST(CFG_CXXFLAGS_DBG)
|
||||
|
||||
# Flags for linking the debug version of Verilator (in addition to above CFG_LDFLAGS_SRC)
|
||||
_MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_DBG,-gz)
|
||||
if test "$CFG_WITH_LIGHT_DEBUG" = "yes"; then
|
||||
_MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_DBG,-gz)
|
||||
fi
|
||||
AC_SUBST(CFG_LDFLAGS_DBG)
|
||||
|
||||
# Flags for compiling the optimized version of Verilator (in addition to above CFG_CXXFLAGS_SRC)
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@ Nikolai Kumar
|
|||
Nikolay Puzanov
|
||||
Nolan Poe
|
||||
Oleh Maksymenko
|
||||
Patrick Creighton
|
||||
Patrick Stewart
|
||||
Paul Bowen-Huggett
|
||||
Paul Swirhun
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
174
src/V3Dfg.cpp
174
src/V3Dfg.cpp
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(" ");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 + "*/");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
24
src/astgen
24
src/astgen
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -11,7 +11,7 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["--binary"])
|
||||
test.compile(verilator_flags2=["--binary", "-Wno-IEEEMAYDEPRECATE"])
|
||||
|
||||
test.execute()
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue